From f5c3723a992b63d6bafbaeb0546dc8631ff37775 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 20 Jan 2021 15:23:01 -0500 Subject: [PATCH 01/29] Java: Simple support for Ratpack HTTP Framework --- .../java/frameworks/ratpack/RatpackHttp.qll | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll new file mode 100644 index 000000000000..fef4434cc48a --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll @@ -0,0 +1,59 @@ +/** + * Provides classes and predicates related to `ratpack.http.*`. + */ + +import java + +/** + * The interface `ratpack.http.Request`. + * https://ratpack.io/manual/current/api/ratpack/http/Request.html + */ +library class RatpackRequest extends RefType { + RatpackRequest() { hasQualifiedName("ratpack.http", "Request") } +} + +/** + * Methods on `ratpack.http.Request` that return user tainted data. + */ +library class RatpackHttpRequestGetMethod extends Method { + RatpackHttpRequestGetMethod() { + getDeclaringType() instanceof RatpackRequest and + hasName([ + "getContentLength", "getCookies", "getHeaders", "getPath", "getQuery", "getQueryParams", + "getRawUri", "getUri" + ]) + } +} + +/** + * The interface `ratpack.http.TypedData`. + * https://ratpack.io/manual/current/api/ratpack/http/TypedData.html + */ +library class RatpackTypedData extends RefType { + RatpackTypedData() { hasQualifiedName("ratpack.http", "TypedData") } +} + +/** + * Methods on `ratpack.http.TypedData` that return user tainted data. + */ +library class RatpackHttpTypedDataGetMethod extends Method { + RatpackHttpTypedDataGetMethod() { + getDeclaringType() instanceof RatpackTypedData and + hasName(["getBuffer", "getBytes", "getContentType", "getInputStream", "getText"]) + } +} + +/** + * The interface `ratpack.form.UploadedFile`. + * https://ratpack.io/manual/current/api/ratpack/form/UploadedFile.html + */ +library class RatpackUploadFile extends RefType { + RatpackUploadFile() { hasQualifiedName("ratpack.form", "UploadedFile") } +} + +library class RatpackUploadFileGetMethod extends Method { + RatpackUploadFileGetMethod() { + getDeclaringType() instanceof RatpackUploadFile and + hasName("getFileName") + } +} From dabf00e8b40746d13d636971fe1a02c05d64f84a Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Thu, 29 Apr 2021 20:38:32 -0400 Subject: [PATCH 02/29] Add Tests to Ratpack Framework Support --- .../code/java/frameworks/ratpack/Ratpack.qll | 83 +++ .../java/frameworks/ratpack/RatpackExec.qll | 64 +++ .../java/frameworks/ratpack/RatpackHttp.qll | 59 --- .../frameworks/ratpack/flow.expected | 0 .../library-tests/frameworks/ratpack/flow.ql | 35 ++ .../library-tests/frameworks/ratpack/options | 1 + .../ratpack/resources/Resource.java | 27 + .../com/google/common/reflect/TypeToken.java | 20 + java/ql/test/stubs/netty-4.1.x/LICENSE.txt | 203 ++++++++ .../netty-4.1.x/io/netty/buffer/ByteBuf.java | 19 + .../handler/codec/http/cookie/Cookie.java | 146 ++++++ java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt | 14 + .../ratpack/core/handling/Context.java | 472 ++++++++++++++++++ .../ratpack/core/handling/Handler.java | 128 +++++ .../ratpack/core/http/Headers.java | 132 +++++ .../ratpack/core/http/MediaType.java | 129 +++++ .../ratpack/core/http/MutableHeaders.java | 108 ++++ .../ratpack/core/http/Request.java | 361 ++++++++++++++ .../ratpack/core/http/Response.java | 210 ++++++++ .../ratpack/core/http/TypedData.java | 94 ++++ .../ratpack/core/parse/Parse.java | 106 ++++ .../core/render/NoSuchRendererException.java | 35 ++ .../ratpack-1.9.x/ratpack/exec/Promise.java | 49 ++ .../ratpack/exec/api/NonBlocking.java | 40 ++ .../exec/registry/NotInRegistryException.java | 47 ++ .../ratpack/exec/registry/Registry.java | 244 +++++++++ .../exec/registry/RegistryBuilder.java | 19 + .../exec/stream/TransformablePublisher.java | 29 ++ .../ratpack-1.9.x/ratpack/func/Action.java | 266 ++++++++++ .../ratpack-1.9.x/ratpack/func/Function.java | 208 ++++++++ .../ratpack/func/MultiValueMap.java | 101 ++++ .../ratpack-1.9.x/ratpack/func/Nullable.java | 33 ++ .../ratpack-1.9.x/ratpack/func/Predicate.java | 96 ++++ 33 files changed, 3519 insertions(+), 59 deletions(-) create mode 100644 java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll delete mode 100644 java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll create mode 100644 java/ql/test/library-tests/frameworks/ratpack/flow.expected create mode 100644 java/ql/test/library-tests/frameworks/ratpack/flow.ql create mode 100644 java/ql/test/library-tests/frameworks/ratpack/options create mode 100644 java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java create mode 100644 java/ql/test/stubs/guava-30.0/com/google/common/reflect/TypeToken.java create mode 100644 java/ql/test/stubs/netty-4.1.x/LICENSE.txt create mode 100644 java/ql/test/stubs/netty-4.1.x/io/netty/buffer/ByteBuf.java create mode 100644 java/ql/test/stubs/netty-4.1.x/io/netty/handler/codec/http/cookie/Cookie.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/TypedData.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/RegistryBuilder.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Nullable.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll new file mode 100644 index 000000000000..d8797f4afb80 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -0,0 +1,83 @@ +/** + * Provides classes and predicates related to `ratpack.*`. + */ + +import java + +/** + * Ratpack methods that access user-supplied request data. + */ +abstract class RatpackGetRequestDataMethod extends Method { } + +/** + * The interface `ratpack.http.Request`. + * https://ratpack.io/manual/current/api/ratpack/http/Request.html + */ +class RatpackRequest extends RefType { + RatpackRequest() { + hasQualifiedName("ratpack.http", "Request") or + hasQualifiedName("ratpack.core.http", "Request") + } +} + +/** + * Methods on `ratpack.http.Request` that return user tainted data. + */ +class RatpackHttpRequestGetMethod extends RatpackGetRequestDataMethod { + RatpackHttpRequestGetMethod() { + getDeclaringType() instanceof RatpackRequest and + hasName([ + "getContentLength", "getCookies", "oneCookie", "getHeaders", "getPath", "getQuery", + "getQueryParams", "getRawUri", "getUri" + ]) + } +} + +/** + * The interface `ratpack.http.TypedData`. + * https://ratpack.io/manual/current/api/ratpack/http/TypedData.html + */ +class RatpackTypedData extends RefType { + RatpackTypedData() { + hasQualifiedName("ratpack.http", "TypedData") or + hasQualifiedName("ratpack.core.http", "TypedData") + } +} + +/** + * Methods on `ratpack.http.TypedData` that return user tainted data. + */ +class RatpackHttpTypedDataGetMethod extends RatpackGetRequestDataMethod { + RatpackHttpTypedDataGetMethod() { + getDeclaringType() instanceof RatpackTypedData and + hasName(["getBuffer", "getBytes", "getContentType", "getInputStream", "getText"]) + } +} + +/** + * Methods on `ratpack.http.TypedData` that taint the parameter passed in. + */ +class RatpackHttpTypedDataWriteMethod extends Method { + RatpackHttpTypedDataWriteMethod() { + getDeclaringType() instanceof RatpackTypedData and + hasName("writeTo") + } +} + +/** + * The interface `ratpack.form.UploadedFile`. + * https://ratpack.io/manual/current/api/ratpack/form/UploadedFile.html + */ +class RatpackUploadFile extends RefType { + RatpackUploadFile() { + hasQualifiedName("ratpack.form", "UploadedFile") or + hasQualifiedName("ratpack.core.form", "UploadedFile") + } +} + +class RatpackUploadFileGetMethod extends RatpackGetRequestDataMethod { + RatpackUploadFileGetMethod() { + getDeclaringType() instanceof RatpackUploadFile and + hasName("getFileName") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll new file mode 100644 index 000000000000..ef4722cc745d --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -0,0 +1,64 @@ +import java +private import semmle.code.java.dataflow.DataFlow +private import semmle.code.java.dataflow.FlowSteps + +/** A reference type that extends a parameterization the Promise type. */ +class RatpackPromise extends RefType { + RatpackPromise() { + getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") + } +} + +class RatpackPromiseMapMethod extends Method { + RatpackPromiseMapMethod() { + getDeclaringType() instanceof RatpackPromise and + hasName("map") + } +} + +class RatpackPromiseMapMethodAccess extends MethodAccess { + RatpackPromiseMapMethodAccess() { getMethod() instanceof RatpackPromiseMapMethod } +} + +class RatpackPromiseThenMethod extends Method { + RatpackPromiseThenMethod() { + getDeclaringType() instanceof RatpackPromise and + hasName("then") + } +} + +class RatpackPromiseThenMethodAccess extends MethodAccess { + RatpackPromiseThenMethodAccess() { getMethod() instanceof RatpackPromiseThenMethod } +} + +private class RatpackPromiseTaintPreservingCallable extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + stepFromFunctionalExpToPromise(node1, node2) or + stepFromPromiseToFunctionalArgument(node1, node2) + } + + /** + * Tracks taint from return from lambda function to the outer `Promise`. + */ + private predicate stepFromFunctionalExpToPromise(DataFlow::Node node1, DataFlow::Node node2) { + exists(FunctionalExpr fe | + fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and + node2.asExpr().(RatpackPromiseMapMethodAccess).getArgument(0) = fe + ) + } + + /** + * Tracks taint from the previous `Promise` to the first argument of lambda passed to `map` or `then`. + */ + private predicate stepFromPromiseToFunctionalArgument(DataFlow::Node node1, DataFlow::Node node2) { + exists(RatpackPromiseMapMethodAccess ma | + node1.asExpr() = ma.getQualifier() and + ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() + ) + or + exists(RatpackPromiseThenMethodAccess ma | + node1.asExpr() = ma.getQualifier() and + ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() + ) + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll deleted file mode 100644 index fef4434cc48a..000000000000 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackHttp.qll +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Provides classes and predicates related to `ratpack.http.*`. - */ - -import java - -/** - * The interface `ratpack.http.Request`. - * https://ratpack.io/manual/current/api/ratpack/http/Request.html - */ -library class RatpackRequest extends RefType { - RatpackRequest() { hasQualifiedName("ratpack.http", "Request") } -} - -/** - * Methods on `ratpack.http.Request` that return user tainted data. - */ -library class RatpackHttpRequestGetMethod extends Method { - RatpackHttpRequestGetMethod() { - getDeclaringType() instanceof RatpackRequest and - hasName([ - "getContentLength", "getCookies", "getHeaders", "getPath", "getQuery", "getQueryParams", - "getRawUri", "getUri" - ]) - } -} - -/** - * The interface `ratpack.http.TypedData`. - * https://ratpack.io/manual/current/api/ratpack/http/TypedData.html - */ -library class RatpackTypedData extends RefType { - RatpackTypedData() { hasQualifiedName("ratpack.http", "TypedData") } -} - -/** - * Methods on `ratpack.http.TypedData` that return user tainted data. - */ -library class RatpackHttpTypedDataGetMethod extends Method { - RatpackHttpTypedDataGetMethod() { - getDeclaringType() instanceof RatpackTypedData and - hasName(["getBuffer", "getBytes", "getContentType", "getInputStream", "getText"]) - } -} - -/** - * The interface `ratpack.form.UploadedFile`. - * https://ratpack.io/manual/current/api/ratpack/form/UploadedFile.html - */ -library class RatpackUploadFile extends RefType { - RatpackUploadFile() { hasQualifiedName("ratpack.form", "UploadedFile") } -} - -library class RatpackUploadFileGetMethod extends Method { - RatpackUploadFileGetMethod() { - getDeclaringType() instanceof RatpackUploadFile and - hasName("getFileName") - } -} diff --git a/java/ql/test/library-tests/frameworks/ratpack/flow.expected b/java/ql/test/library-tests/frameworks/ratpack/flow.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/test/library-tests/frameworks/ratpack/flow.ql b/java/ql/test/library-tests/frameworks/ratpack/flow.ql new file mode 100644 index 000000000000..1743a0412e65 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/ratpack/flow.ql @@ -0,0 +1,35 @@ +import java +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.security.XSS +import semmle.code.java.security.UrlRedirect +import TestUtilities.InlineExpectationsTest + +class Conf extends TaintTracking::Configuration { + Conf() { this = "qltest:frameworks:ratpack" } + + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("taint") + or + n instanceof RemoteFlowSource + } + + override predicate isSink(DataFlow::Node n) { + exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument()) + } +} + +class HasFlowTest extends InlineExpectationsTest { + HasFlowTest() { this = "HasFlowTest" } + + override string getARelevantTag() { result = "hasTaintFlow" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasTaintFlow" and + exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + } +} diff --git a/java/ql/test/library-tests/frameworks/ratpack/options b/java/ql/test/library-tests/frameworks/ratpack/options new file mode 100644 index 000000000000..3263fdcf4150 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/ratpack/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-databind-2.10:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java new file mode 100644 index 000000000000..55b57abcff14 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -0,0 +1,27 @@ +import ratpack.core.handling.Context; +import ratpack.core.http.TypedData; + +class Resource { + + void sink(Object o) {} + + void test1(Context ctx) { + sink(ctx.getRequest().getContentLength()); //$hasTaintFlow + sink(ctx.getRequest().getCookies()); //$hasTaintFlow + sink(ctx.getRequest().oneCookie("Magic-Cookie")); //$hasTaintFlow + sink(ctx.getRequest().getHeaders()); //$hasTaintFlow + sink(ctx.getRequest().getPath()); //$hasTaintFlow + sink(ctx.getRequest().getQuery()); //$hasTaintFlow + sink(ctx.getRequest().getQueryParams()); //$hasTaintFlow + sink(ctx.getRequest().getRawUri()); //$hasTaintFlow + sink(ctx.getRequest().getUri()); //$hasTaintFlow + } + + void test2(TypedData td) { + sink(td.getText()); //$hasTaintFlow + } + + void test2(Context ctx) { + ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/guava-30.0/com/google/common/reflect/TypeToken.java b/java/ql/test/stubs/guava-30.0/com/google/common/reflect/TypeToken.java new file mode 100644 index 000000000000..b6ac36271b9a --- /dev/null +++ b/java/ql/test/stubs/guava-30.0/com/google/common/reflect/TypeToken.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.common.reflect; + +import java.io.Serializable; + +public abstract class TypeToken implements Serializable { +} \ No newline at end of file diff --git a/java/ql/test/stubs/netty-4.1.x/LICENSE.txt b/java/ql/test/stubs/netty-4.1.x/LICENSE.txt new file mode 100644 index 000000000000..5f3c6540d8f7 --- /dev/null +++ b/java/ql/test/stubs/netty-4.1.x/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + https://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 + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + \ No newline at end of file diff --git a/java/ql/test/stubs/netty-4.1.x/io/netty/buffer/ByteBuf.java b/java/ql/test/stubs/netty-4.1.x/io/netty/buffer/ByteBuf.java new file mode 100644 index 000000000000..cb327fdf0021 --- /dev/null +++ b/java/ql/test/stubs/netty-4.1.x/io/netty/buffer/ByteBuf.java @@ -0,0 +1,19 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.buffer; + +public abstract class ByteBuf implements Comparable { +} \ No newline at end of file diff --git a/java/ql/test/stubs/netty-4.1.x/io/netty/handler/codec/http/cookie/Cookie.java b/java/ql/test/stubs/netty-4.1.x/io/netty/handler/codec/http/cookie/Cookie.java new file mode 100644 index 000000000000..ecb0c6894386 --- /dev/null +++ b/java/ql/test/stubs/netty-4.1.x/io/netty/handler/codec/http/cookie/Cookie.java @@ -0,0 +1,146 @@ +/* + * Copyright 2015 The Netty Project + * + * The Netty Project 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: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.http.cookie; + +/** + * An interface defining an + * HTTP cookie. + */ +public interface Cookie extends Comparable { + + /** + * Constant for undefined MaxAge attribute value. + */ + long UNDEFINED_MAX_AGE = Long.MIN_VALUE; + + /** + * Returns the name of this {@link Cookie}. + * + * @return The name of this {@link Cookie} + */ + String name(); + + /** + * Returns the value of this {@link Cookie}. + * + * @return The value of this {@link Cookie} + */ + String value(); + + /** + * Sets the value of this {@link Cookie}. + * + * @param value The value to set + */ + void setValue(String value); + + /** + * Returns true if the raw value of this {@link Cookie}, + * was wrapped with double quotes in original Set-Cookie header. + * + * @return If the value of this {@link Cookie} is to be wrapped + */ + boolean wrap(); + + /** + * Sets true if the value of this {@link Cookie} + * is to be wrapped with double quotes. + * + * @param wrap true if wrap + */ + void setWrap(boolean wrap); + + /** + * Returns the domain of this {@link Cookie}. + * + * @return The domain of this {@link Cookie} + */ + String domain(); + + /** + * Sets the domain of this {@link Cookie}. + * + * @param domain The domain to use + */ + void setDomain(String domain); + + /** + * Returns the path of this {@link Cookie}. + * + * @return The {@link Cookie}'s path + */ + String path(); + + /** + * Sets the path of this {@link Cookie}. + * + * @param path The path to use for this {@link Cookie} + */ + void setPath(String path); + + /** + * Returns the maximum age of this {@link Cookie} in seconds or {@link Cookie#UNDEFINED_MAX_AGE} if unspecified + * + * @return The maximum age of this {@link Cookie} + */ + long maxAge(); + + /** + * Sets the maximum age of this {@link Cookie} in seconds. + * If an age of {@code 0} is specified, this {@link Cookie} will be + * automatically removed by browser because it will expire immediately. + * If {@link Cookie#UNDEFINED_MAX_AGE} is specified, this {@link Cookie} will be removed when the + * browser is closed. + * + * @param maxAge The maximum age of this {@link Cookie} in seconds + */ + void setMaxAge(long maxAge); + + /** + * Checks to see if this {@link Cookie} is secure + * + * @return True if this {@link Cookie} is secure, otherwise false + */ + boolean isSecure(); + + /** + * Sets the security getStatus of this {@link Cookie} + * + * @param secure True if this {@link Cookie} is to be secure, otherwise false + */ + void setSecure(boolean secure); + + /** + * Checks to see if this {@link Cookie} can only be accessed via HTTP. + * If this returns true, the {@link Cookie} cannot be accessed through + * client side script - But only if the browser supports it. + * For more information, please look here + * + * @return True if this {@link Cookie} is HTTP-only or false if it isn't + */ + boolean isHttpOnly(); + + /** + * Determines if this {@link Cookie} is HTTP only. + * If set to true, this {@link Cookie} cannot be accessed by a client + * side script. However, this works only if the browser supports it. + * For for information, please look + * here. + * + * @param httpOnly True if the {@link Cookie} is HTTP only, otherwise false. + */ + void setHttpOnly(boolean httpOnly); +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt b/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt new file mode 100644 index 000000000000..6b26ad45235b --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt @@ -0,0 +1,14 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java new file mode 100644 index 000000000000..457ed317060b --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java @@ -0,0 +1,472 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.handling; + +import com.google.common.reflect.TypeToken; +import ratpack.core.http.*; +import ratpack.core.parse.Parse; +import ratpack.core.render.*; +import ratpack.exec.api.NonBlocking; +import ratpack.exec.registry.NotInRegistryException; +import ratpack.exec.registry.Registry; +import ratpack.exec.Promise; + +import java.nio.file.Path; +import java.time.Instant; +import java.util.Arrays; +import java.util.Date; +import java.util.Optional; + +/** + * The context of an individual {@link Handler} invocation. + *

+ * It provides: + *

    + *
  • Access the HTTP {@link #getRequest() request} and {@link #getResponse() response}
  • + *
  • Delegation (via the {@link #next} and {@link #insert} family of methods)
  • + *
  • Access to contextual objects (see below)
  • + *
  • Convenience for common handler operations
  • + *
+ * + *

Contextual objects

+ *

+ * A context is also a {@link Registry} of objects. + * Arbitrary objects can be "pushed" into the context for use by downstream handlers. + *

+ * There are some significant contextual objects that drive key infrastructure. + * For example, error handling is based on informing the contextual {@link ServerErrorHandler} of exceptions. + * The error handling strategy for an application can be changed by pushing a new implementation of this interface into the context that is used downstream. + *

+ * See {@link #insert(Handler...)} for more on how to do this. + *

Default contextual objects

+ *

There is also a set of default objects that are made available via the Ratpack infrastructure: + *

    + *
  • A {@link FileSystemBinding} that is the application {@link ServerConfig#getBaseDir()}
  • + *
  • A {@link MimeTypes} implementation
  • + *
  • A {@link ServerErrorHandler}
  • + *
  • A {@link ClientErrorHandler}
  • + *
  • A {@link PublicAddress}
  • + *
  • A {@link Redirector}
  • + *
+ */ +public interface Context extends Registry { + + /** + * A type token for this type. + * + * @since 1.1 + */ + TypeToken TYPE = null; + + /** + * Returns this. + * + * @return this. + */ + Context getContext(); + + /** + * The HTTP request. + * + * @return The HTTP request. + */ + Request getRequest(); + + /** + * The HTTP response. + * + * @return The HTTP response. + */ + Response getResponse(); + + /** + * Delegate handling to the next handler in line. + *

+ * The request and response of this object should not be accessed after this method is called. + */ + @NonBlocking + void next(); + + /** + * Invokes the next handler, after adding the given registry. + *

+ * The given registry is appended to the existing. + * This means that it can shadow objects previously available. + *

{@code
+   * import ratpack.exec.registry.Registry;
+   * import ratpack.test.embed.EmbeddedApp;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *
+   *   public static void main(String... args) throws Exception {
+   *     EmbeddedApp.fromHandlers(chain -> chain
+   *         .all(ctx -> ctx.next(Registry.single("foo")))
+   *         .all(ctx -> ctx.render(ctx.get(String.class)))
+   *     ).test(httpClient -> {
+   *       assertEquals("foo", httpClient.getText());
+   *     });
+   *   }
+   * }
+   * }
+ * + * @param registry The registry to make available for subsequent handlers. + */ + @NonBlocking + void next(Registry registry); + + /** + * Inserts some handlers into the pipeline, then delegates to the first. + *

+ * The request and response of this object should not be accessed after this method is called. + * + * @param handlers The handlers to insert. + */ + @NonBlocking + void insert(Handler... handlers); + + /** + * Inserts some handlers into the pipeline to execute with the given registry, then delegates to the first. + *

+ * The given registry is only applicable to the inserted handlers. + *

+ * Almost always, the registry should be a super set of the current registry. + * + * @param handlers The handlers to insert + * @param registry The registry for the inserted handlers + */ + @NonBlocking + void insert(Registry registry, Handler... handlers); + + + /** + * Handles any error thrown during request handling. + *

+ * Uncaught exceptions that are thrown any time during request handling will end up here. + *

+ * Forwards the exception to the {@link ServerErrorHandler} within the current registry. + * Add an implementation of this interface to the registry to handle errors. + * The default implementation is not suitable for production usage. + *

+ * If the exception is-a {@link ClientErrorException}, + * the {@link #clientError(int)} method will be called with the exception's status code + * instead of being forward to the server error handler. + * + * @param throwable The exception that occurred + */ + @NonBlocking + void error(Throwable throwable); + + /** + * Forwards the error to the {@link ClientErrorHandler} in this service. + * + * The default configuration of Ratpack includes a {@link ClientErrorHandler} in all contexts. + * A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used. + * + * @param statusCode The 4xx range status code that indicates the error type + * @throws NotInRegistryException if no {@link ClientErrorHandler} can be found in the service + */ + @NonBlocking + void clientError(int statusCode) throws NotInRegistryException; + + /** + * Render the given object, using the rendering framework. + *

+ * The first {@link Renderer}, that is able to render the given object will be delegated to. + * If the given argument is {@code null}, this method will have the same effect as {@link #clientError(int) clientError(404)}. + *

+ * If no renderer can be found for the given type, a {@link NoSuchRendererException} will be given to {@link #error(Throwable)}. + *

+ * If a renderer throws an exception during its execution it will be wrapped in a {@link RendererException} and given to {@link #error(Throwable)}. + *

+ * Ratpack has built in support for rendering the following types: + *

    + *
  • {@link Path}
  • + *
  • {@link CharSequence}
  • + *
  • {@link JsonRender} (Typically created via {@link Jackson#json(Object)})
  • + *
  • {@link Promise} (renders the promised value, using this {@code render()} method)
  • + *
  • {@link Publisher} (converts the publisher to a promise using {@link Streams#toPromise(Publisher)} and renders it)
  • + *
  • {@link Renderable} (Delegates to the {@link Renderable#render(Context)} method of the object)
  • + *
+ *

+ * See {@link Renderer} for more on how to contribute to the rendering framework. + *

+ * The object-to-render will be decorated by all registered {@link RenderableDecorator} whose type is exactly equal to the type of the object-to-render, before being passed to the selected renderer. + * + * @param object The object-to-render + * @throws NoSuchRendererException if no suitable renderer can be found + */ + @NonBlocking + void render(Object object) throws NoSuchRendererException; + + /** + * Returns the request header with the specified name. + *

+ * If there is more than value for the specified header, the first value is returned. + *

+ * Shorthand for {@code getRequest().getHeaders().get(String)}. + * + * @param name the case insensitive name of the header to get retrieve the first value of + * @return the header value or {@code null} if there is no such header + * @since 1.4 + */ + default Optional header(CharSequence name) { + return Optional.ofNullable(getRequest().getHeaders().get(name)); + } + + /** + * Sets a response header. + *

+ * Any previously set values for the header will be removed. + *

+ * Shorthand for {@code getResponse().getHeaders().set(CharSequence, Iterable)}. + * + * @param name the name of the header to set + * @param values the header values + * @return {@code this} + * @since 1.4 + * @see MutableHeaders#set(CharSequence, Iterable) + */ + default Context header(CharSequence name, Object... values) { + getResponse().getHeaders().set(name, Arrays.asList(values)); + return this; + } + + /** + * Sends a temporary redirect response (i.e. 302) to the client using the specified redirect location. + * + * @param to the location to redirect to + * @see #redirect(int, Object) + * @since 1.3 + */ + void redirect(Object to); + + /** + * Sends a redirect response to the given location, and with the given status code. + *

+ * This method retrieves the {@link Redirector} from the registry, and forwards the given arguments along with {@code this} context. + * + * @param code The status code of the redirect + * @param to the redirect location URL + * @see Redirector + * @since 1.3 + */ + void redirect(int code, Object to); + + /** + * Convenience method for handling last-modified based HTTP caching. + *

+ * The given date is the "last modified" value of the response. + * If the client sent an "If-Modified-Since" header that is of equal or greater value than date, + * a 304 will be returned to the client. + * Otherwise, the given runnable will be executed (it should send a response) + * and the "Last-Modified" header will be set by this method. + * + * @param lastModified the effective last modified date of the response + * @param serve the response sending action if the response needs to be sent + */ + @NonBlocking + default void lastModified(Date lastModified, Runnable serve) { + lastModified(lastModified.toInstant(), serve); + } + + /** + * Convenience method for handling last-modified based HTTP caching. + *

+ * The given date is the "last modified" value of the response. + * If the client sent an "If-Modified-Since" header that is of equal or greater value than date, + * a 304 will be returned to the client. + * Otherwise, the given runnable will be executed (it should send a response) + * and the "Last-Modified" header will be set by this method. + * + * @param lastModified the effective last modified date of the response + * @param serve the response sending action if the response needs to be sent + * @since 1.4 + */ + @NonBlocking + void lastModified(Instant lastModified, Runnable serve); + + /** + * Parse the request into the given type, using no options (or more specifically an instance of {@link NullParseOpts} as the options). + *

+ * The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant… + *

{@code
+   * import ratpack.core.handling.Handler;
+   * import ratpack.core.handling.Context;
+   * import ratpack.core.form.Form;
+   *
+   * public class FormHandler implements Handler {
+   *   public void handle(Context context) {
+   *     context.parse(Form.class).then(form -> context.render(form.get("someFormParam")));
+   *   }
+   * }
+   * }
+ *

+ * That is, it is a convenient form of {@code parse(Parse.of(T))}. + * + * @param type the type to parse to + * @param the type to parse to + * @return a promise for the parsed object + */ + Promise parse(Class type); + + /** + * Parse the request into the given type, using no options (or more specifically an instance of {@link NullParseOpts} as the options). + *

+ * The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant… + *

{@code
+   * import ratpack.core.handling.Handler;
+   * import ratpack.core.handling.Context;
+   * import ratpack.core.form.Form;
+   * import com.google.common.reflect.TypeToken;
+   *
+   * public class FormHandler implements Handler {
+   *   public void handle(Context context) {
+   *     context.parse(new TypeToken
() {}).then(form -> context.render(form.get("someFormParam"))); + * } + * } + * }
+ *

+ * That is, it is a convenient form of {@code parse(Parse.of(T))}. + * + * @param type the type to parse to + * @param the type to parse to + * @return a promise for the parsed object + */ + Promise parse(TypeToken type); + + /** + * Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}. + * + * @param type The type to parse to + * @param options The parse options + * @param The type to parse to + * @param The type of the parse opts + * @return a promise for the parsed object + */ + Promise parse(Class type, O options); + + /** + * Constructs a {@link Parse} from the given args and delegates to {@link #parse(Parse)}. + * + * @param type The type to parse to + * @param options The parse options + * @param The type to parse to + * @param The type of the parse opts + * @return a promise for the parsed object + */ + Promise parse(TypeToken type, O options); + + /** + * Parses the request body into an object. + *

+ * How to parse the request is determined by the given {@link Parse} object. + * + *

Parser Resolution

+ *

+ * Parser resolution happens as follows: + *

    + *
  1. All {@link Parser parsers} are retrieved from the context registry (i.e. {@link #getAll(Class) getAll(Parser.class)});
  2. + *
  3. Found parsers are checked (in order returned by {@code getAll()}) for compatibility with the options type;
  4. + *
  5. If a parser is found that is compatible, its {@link Parser#parse(Context, TypedData, Parse)} method is called;
  6. + *
  7. If the parser returns {@code null} the next parser will be tried, if it returns a value it will be returned by this method;
  8. + *
  9. If no compatible parser could be found, a {@link NoSuchParserException} will be thrown.
  10. + *
+ * + *

Parser Compatibility

+ *

+ * A parser is compatible if all of the following hold true: + *

    + *
  • The opts of the given {@code parse} object is an {@code instanceof} its {@link Parser#getOptsType()}, or the opts are {@code null}.
  • + *
  • The {@link Parser#parse(Context, TypedData, Parse)} method returns a non null value.
  • + *
+ * + *

Core Parsers

+ *

+ * Ratpack core provides parsers for {@link Form}, and JSON (see {@link Jackson}). + * + *

Example Usage

+ *
{@code
+   * import ratpack.core.handling.Handler;
+   * import ratpack.core.handling.Context;
+   * import ratpack.core.form.Form;
+   * import ratpack.core.parse.NullParseOpts;
+   *
+   * public class FormHandler implements Handler {
+   *   public void handle(Context context) {
+   *     context.parse(Form.class).then(form -> context.render(form.get("someFormParam")));
+   *   }
+   * }
+   * }
+ * + * @param parse The specification of how to parse the request + * @param The type of object the request is parsed into + * @param the type of the parse options object + * @return a promise for the parsed object + * @see #parse(Class) + * @see #parse(Class, Object) + * @see Parser + */ + Promise parse(Parse parse); + + /** + * Parses the provided request body into an object. + *

+ * This variant can be used when a reference to the request body has already been obtained. + * For example, this can be used during the implementation of a {@link Parser} that needs to delegate to another parser. + *

+ * From within a handler, it is more common to use {@link #parse(Parse)} or similar. + * + * @param body The request body + * @param parse The specification of how to parse the request + * @param The type of object the request is parsed into + * @param The type of the parse options object + * @return a promise for the parsed object + * @see #parse(Parse) + * @throws Exception any thrown by the parser + */ + T parse(TypedData body, Parse parse) throws Exception; + + /** + * Gets the file relative to the contextual {@link FileSystemBinding}. + *

+ * Shorthand for {@code get(FileSystemBinding.class).file(path)}. + *

+ * The default configuration of Ratpack includes a {@link FileSystemBinding} in all contexts. + * A {@link NotInRegistryException} will only be thrown if a very custom service setup is being used. + * + * @param path The path to pass to the {@link FileSystemBinding#file(String)} method. + * @return The file relative to the contextual {@link FileSystemBinding} + * @throws NotInRegistryException if there is no {@link FileSystemBinding} in the current service + */ + Path file(String path) throws NotInRegistryException; + + /** + * Issues a 404 client error. + *

+ * This method is literally a shorthand for {@link #clientError(int) clientError(404)}. + *

+ * This is a terminal handler operation. + * + * @since 1.1 + */ + default void notFound() { + clientError(404); + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java new file mode 100644 index 000000000000..01da4ddbb83d --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.handling; + +import ratpack.exec.registry.Registry; + +/** + * A handler participates in the processing of a request/response pair, operating on a {@link Context}. + *

+ * Handlers are the key component of Ratpack applications. + * A handler either generate a response, or delegate to another handler in some way. + *

+ *

Non blocking/Asynchronous

+ *

+ * Handlers are expected to be asynchronous. + * That is, there is no expectation that the handler is “finished” when its {@link #handle(Context)} method returns. + * This facilitates the use of non blocking IO without needing to enter some kind of special mode. + * An implication is that handlers must ensure that they either send a response or delegate to another handler. + *

+ *

Handler pipeline

+ *

+ * Handlers are always part of a pipeline structure. + * The {@link Context} that the handler operates on provides the {@link Context#next()} method that passes control to the next handler in the pipeline. + * The last handler in the pipeline is always that generates a {@code 404} client error. + *

+ * Handlers can themselves insert other handlers into the pipeline, using the {@link Context#insert(Handler...)} family of methods. + *

Examples

+ * While there is no strict taxonomy of handlers, the following are indicative examples of common functions. + * + *
+ * import ratpack.core.handling.*;
+ *
+ * // A responder may just return a response to the client…
+ *
+ * class SimpleHandler implements Handler {
+ *   void handle(Context exchange) {
+ *     exchange.getResponse().send("Hello World!");
+ *   }
+ * }
+ *
+ * // A responder may add a response header, before delegating to the next in the pipeline…
+ *
+ * class DecoratingHandler implements Handler {
+ *   void handle(Context exchange) {
+ *     exchange.getResponse().getHeaders().set("Cache-Control", "no-cache");
+ *     exchange.next();
+ *   }
+ * }
+ *
+ * // Or a handler may conditionally respond…
+ *
+ * class ConditionalHandler implements Handler {
+ *   void handle(Context exchange) {
+ *     if (exchange.getRequest().getPath().equals("foo")) {
+ *       exchange.getResponse().send("Hello World!");
+ *     } else {
+ *       exchange.next();
+ *     }
+ *   }
+ * }
+ *
+ * // A handler does not need to participate in the response, but can instead "route" the exchange to different handlers…
+ *
+ * class RoutingHandler implements Handler {
+ *   private final Handler[] fooHandlers;
+ *
+ *   public RoutingHandler(Handler... fooHandlers) {
+ *     this.fooHandlers = fooHandlers;
+ *   }
+ *
+ *   void handle(Context exchange) {
+ *     if (exchange.getRequest().getPath().startsWith("foo/")) {
+ *       exchange.insert(fooHandlers);
+ *     } else {
+ *       exchange.next();
+ *     }
+ *   }
+ * }
+ *
+ * // It can sometimes be appropriate to directly delegate to a handler, instead of using exchange.insert() …
+ *
+ * class FilteringHandler implements Handler {
+ *   private final Handler nestedHandler;
+ *
+ *   public FilteringHandler(Handler nestedHandler) {
+ *     this.nestedHandler = nestedHandler;
+ *   }
+ *
+ *   void handle(Context exchange) {
+ *     if (exchange.getRequest().getPath().startsWith("foo/")) {
+ *       nestedHandler.handle(exchange);
+ *     } else {
+ *       exchange.next();
+ *     }
+ *   }
+ * }
+ * 
+ * + * @see Handlers + * @see Chain + * @see Registry + */ +@FunctionalInterface +public interface Handler { + + /** + * Handles the context. + * + * @param ctx The context to handle + * @throws Exception if anything goes wrong (exception will be implicitly passed to the context's {@link Context#error(Throwable)} method) + */ + void handle(Context ctx) throws Exception; + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java new file mode 100644 index 000000000000..f98e8b4095e0 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java @@ -0,0 +1,132 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import ratpack.func.Nullable; +import ratpack.func.MultiValueMap; + +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.Set; + +/** + * An immutable set of HTTP headers. + */ +public interface Headers { + + /** + * Returns the header value with the specified header name. + *

+ * If there is more than one header value for the specified header name, the first value is returned. + * + * @param name The case insensitive name of the header to get retrieve the first value of + * @return the header value or {@code null} if there is no such header + */ + @Nullable + String get(CharSequence name); + + /** + * Returns the header value with the specified header name. + *

+ * If there is more than one header value for the specified header name, the first value is returned. + * + * @param name The case insensitive name of the header to get retrieve the first value of + * @return the header value or {@code null} if there is no such header + */ + @Nullable + String get(String name); + + /** + * Returns the header value as a date with the specified header name. + *

+ * If there is more than one header value for the specified header name, the first value is returned. + * + * @param name The case insensitive name of the header to get retrieve the first value of + * @return the header value as a date or {@code null} if there is no such header or the header value is not a valid date format + */ + @Nullable + Date getDate(CharSequence name); + + /** + * Returns the header value as an instant with the specified header name. + *

+ * If there is more than one header value for the specified header name, the first value is returned. + * + * @param name the case insensitive name of the header to get retrieve the first value of + * @return the header value as an instant or {@code null} if there is no such header or the header value is not a valid date format + * @since 1.4 + */ + @Nullable + default Instant getInstant(CharSequence name) { + Date date = getDate(name); + return date == null ? null : Instant.ofEpochMilli(date.getTime()); + } + + /** + * Returns the header value as a date with the specified header name. + *

+ * If there is more than one header value for the specified header name, the first value is returned. + * + * @param name The case insensitive name of the header to get retrieve the first value of + * @return the header value as a date or {@code null} if there is no such header or the header value is not a valid date format + */ + @Nullable + Date getDate(String name); + + /** + * Returns all of the header values with the specified header name. + * + * @param name The case insensitive name of the header to retrieve all of the values of + * @return the {@link java.util.List} of header values, or an empty list if there is no such header + */ + List getAll(CharSequence name); + + /** + * Returns all of the header values with the specified header name. + * + * @param name The case insensitive name of the header to retrieve all of the values of + * @return the {@link java.util.List} of header values, or an empty list if there is no such header + */ + List getAll(String name); + + /** + * Checks whether a header has been specified for the given value. + * + * @param name The name of the header to check the existence of + * @return True if there is a header with the specified header name + */ + boolean contains(CharSequence name); + + /** + * Checks whether a header has been specified for the given value. + * + * @param name The name of the header to check the existence of + * @return True if there is a header with the specified header name + */ + boolean contains(String name); + + /** + * All header names. + * + * @return The names of all headers that were sent + */ + Set getNames(); + + MultiValueMap asMultiValueMap(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java new file mode 100644 index 000000000000..4e664e36766b --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java @@ -0,0 +1,129 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import com.google.common.collect.ImmutableListMultimap; +import ratpack.func.Nullable; + +/** + * A structured value for a Content-Type header value. + *

+ * Can also represent a non existent (i.e. empty) value. + */ +@SuppressWarnings("UnusedDeclaration") +public interface MediaType { + + /** + * {@value}. + */ + String PLAIN_TEXT_UTF8 = "text/plain;charset=utf-8"; + + /** + * {@value}. + */ + String APPLICATION_JSON = "application/json"; + + /** + * {@value}. + */ + String JSON_SUFFIX = "+json"; + + /** + * {@value}. + */ + String APPLICATION_FORM = "application/x-www-form-urlencoded"; + + /** + * {@value}. + */ + String TEXT_HTML = "text/html"; + + /** + * The type without parameters. + *

+ * Given a mime type of "text/plain;charset=utf-8", returns "text/plain". + *

+ * May be null to represent no content type. + * + * @return The mime type without parameters, or null if this represents the absence of a value. + */ + @Nullable + String getType(); + + /** + * The multimap containing parameters of the mime type. + *

+ * Given a mime type of "application/json;charset=utf-8", the {@code get("charset")} returns {@code ["utf-8"]}". + * May be empty, never null. + *

+ * All param names have been lower cased. The {@code charset} parameter values has been lower cased too. + * + * @return the immutable multimap of the media type params. + */ + ImmutableListMultimap getParams(); + + /** + * The value of the "charset" parameter. + * + * @return the value of the charset parameter, or {@code null} if the no charset parameter was specified + */ + @Nullable + String getCharset(); + + /** + * The value of the "charset" parameter, or the given default value of no charset was specified. + * + * @param defaultValue the value if this type has no charset + * @return the value of the charset parameter, or the given default + */ + String getCharset(String defaultValue); + + /** + * True if this type starts with "{@code text/}". + * + * @return True if this type starts with "{@code text/}". + */ + boolean isText(); + + /** + * True if this type equals {@value #APPLICATION_JSON}, or ends with {@value #JSON_SUFFIX}. + * + * @return if this represents a JSON like type + */ + boolean isJson(); + + /** + * True if this type equals {@value #APPLICATION_FORM}. + * + * @return True if this type equals {@value #APPLICATION_FORM}. + */ + boolean isForm(); + + /** + * True if this type equals {@value #TEXT_HTML}. + * + * @return True if this type equals {@value #TEXT_HTML}. + */ + boolean isHtml(); + + /** + * True if this represents the absence of a value (i.e. no Content-Type header) + * + * @return True if this represents the absence of a value (i.e. no Content-Type header) + */ + boolean isEmpty(); +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java new file mode 100644 index 000000000000..e0696b783e59 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import java.time.Instant; +import java.util.Calendar; +import java.util.Date; + +/** + * A set of HTTP headers that can be changed. + */ +public interface MutableHeaders extends Headers { + + /** + * Adds a new header with the specified name and value. + *

+ * Will not replace any existing values for the header. + *

+ * Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a + * RFC 7231 date/time string. + * + * @param name The name of the header + * @param value The value of the header + * @return this + */ + MutableHeaders add(CharSequence name, Object value); + + /** + * Sets the (only) value for the header with the specified name. + *

+ * All existing values for the same header will be removed. + *

+ * Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a + * RFC 7231 date/time string. + * + * @param name The name of the header + * @param value The value of the header + * @return this + */ + MutableHeaders set(CharSequence name, Object value); + + /** + * Set a header with the given date as the value. + * + * @param name The name of the header + * @param value The date value + * @return this + */ + MutableHeaders setDate(CharSequence name, Date value); + + /** + * Set a header with the given date as the value. + * + * @param name the name of the header + * @param value the date value + * @return this + * @since 1.4 + */ + default MutableHeaders setDate(CharSequence name, Instant value) { + return setDate(name, new Date(value.toEpochMilli())); + } + + /** + * Sets a new header with the specified name and values. + *

+ * All existing values for the same header will be removed. + *

+ * Objects of type {@link Instant}, {@link Calendar} or {@link Date} will be converted to a + * RFC 7231 date/time string. + * + * @param name The name of the header + * @param values The values of the header + * @return this + */ + MutableHeaders set(CharSequence name, Iterable values); + + /** + * Removes the header with the specified name. + * + * @param name The name of the header to remove. + * @return this + */ + MutableHeaders remove(CharSequence name); + + /** + * Removes all headers from this message. + * + * @return this + */ + MutableHeaders clear(); + + MutableHeaders copy(Headers headers); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java new file mode 100644 index 000000000000..724a85c195f2 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java @@ -0,0 +1,361 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import com.google.common.reflect.TypeToken; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.cookie.Cookie; +import ratpack.exec.Promise; +import ratpack.exec.stream.TransformablePublisher; +import ratpack.func.MultiValueMap; + +import java.time.Duration; +import java.time.Instant; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A request to be handled. + */ +public interface Request { + + /** + * A type token for this type. + * + * @since 1.1 + */ + TypeToken TYPE = null; + + /** + * The raw URI of the request. + *

+ * This value may be an absolute URI or an absolute path. + * + * @return The raw URI of the request. + */ + String getRawUri(); + + /** + * The complete URI of the request (path + query string). + *

+ * This value is always absolute (i.e. begins with "{@code /}"). + * + * @return The complete URI of the request (path + query string). + */ + String getUri(); + + /** + * The query string component of the request URI, without the "?". + *

+ * If the request does not contain a query component, an empty string will be returned. + * + * @return The query string component of the request URI, without the "?". + */ + String getQuery(); + + /** + * The URI without the query string and leading forward slash. + * + * @return The URI without the query string and leading forward slash + */ + String getPath(); + + /** + * TBD. + * + * @return TBD. + */ + MultiValueMap getQueryParams(); + + /** + * The cookies that were sent with the request. + *

+ * An empty set will be returned if no cookies were sent. + * + * @return The cookies that were sent with the request. + */ + Set getCookies(); + + /** + * Returns the value of the cookie with the specified name if it was sent. + *

+ * If there is more than one cookie with this name, this method will throw an exception. + * + * @param name The name of the cookie to get the value of + * @return The cookie value, or null if not present + */ + @Nullable + String oneCookie(String name); + + /** + * The body of the request. + *

+ * If this request does not have a body, a non null object is still returned but it effectively has no data. + *

+ * If the transmitted content is larger than provided {@link ServerConfig#getMaxContentLength()}, the given block will be invoked. + * If the block completes successfully, the promise will be terminated. + * If the block errors, the promise will carry the failure. + *

+ * If the body is larger then {@link #getMaxContentLength()}, a {@link RequestBodyTooLargeException} will be propagated. + * + * @return the body of the request + */ + Promise getBody(); + + /** + * Overrides the idle timeout for this connection. + *

+ * The default is set as part of server config via {@link ServerConfigBuilder#idleTimeout(Duration)}. + *

+ * The override strictly applies to only the request/response exchange that this request is involved in. + * + * @param idleTimeout the idle timeout ({@link Duration#ZERO} = no timeout, must not be negative, must not be null) + * @since 1.5 + * @see ServerConfig#getIdleTimeout() + */ + void setIdleTimeout(Duration idleTimeout); + + /** + * The body of the request allowing up to the provided size for the content. + *

+ * If this request does not have a body, a non null object is still returned but it effectively has no data. + *

+ * If the transmitted content is larger than the provided {@code maxContentLength}, an {@code 413} client error will be issued. + * + * @param maxContentLength the maximum number of bytes allowed for the request. + * @return the body of the request. + * @since 1.1 + */ + Promise getBody(long maxContentLength); + + /** + * Allows reading the body as a stream, with back pressure. + *

+ * Similar to {@link #getBodyStream(long)}, except uses {@link ServerConfig#getMaxContentLength()} as the max content length. + * + * @return a publisher of the request body + * @see #getBodyStream(long) + * @since 1.2 + */ + TransformablePublisher getBodyStream(); + + /** + * Allows reading the body as a stream, with back pressure. + *

+ * The returned publisher emits the body as {@link ByteBuf}s. + * The subscriber MUST {@code release()} each emitted byte buf. + * Failing to do so will leak memory. + *

+ * If the request body is larger than the given {@code maxContentLength}, a {@link RequestBodyTooLargeException} will be emitted. + * If the request body has already been read, a {@link RequestBodyAlreadyReadException} will be emitted. + *

+ * The returned publisher is bound to the calling execution via {@link Streams#bindExec(Publisher)}. + * If your subscriber's onNext(), onComplete() or onError() methods are asynchronous they MUST use {@link Promise}, + * {@link Blocking} or similar execution control constructs. + *

+ * The following demonstrates how to use this method to stream the request body to a file, using asynchronous IO. + * + *

{@code
+   * import com.google.common.io.Files;
+   * import io.netty.buffer.ByteBuf;
+   * import org.apache.commons.lang3.RandomStringUtils;
+   * import org.reactivestreams.Subscriber;
+   * import org.reactivestreams.Subscription;
+   * import org.junit.Assert;
+   * import ratpack.exec.Promise;
+   * import ratpack.core.http.client.ReceivedResponse;
+   * import ratpack.test.embed.EmbeddedApp;
+   * import ratpack.test.embed.EphemeralBaseDir;
+   *
+   * import java.io.IOException;
+   * import java.nio.channels.AsynchronousFileChannel;
+   * import java.nio.charset.StandardCharsets;
+   * import java.nio.file.Path;
+   * import java.nio.file.StandardOpenOption;
+   *
+   * public class Example {
+   *   public static void main(String[] args) throws Exception {
+   *     EphemeralBaseDir.tmpDir().use(dir -> {
+   *       String string = RandomStringUtils.random(1024 * 1024);
+   *       int length = string.getBytes(StandardCharsets.UTF_8).length;
+   *
+   *       Path file = dir.path("out.txt");
+   *
+   *       EmbeddedApp.fromHandler(ctx ->
+   *         ctx.getRequest().getBodyStream(length).subscribe(new Subscriber() {
+   *
+   *           private Subscription subscription;
+   *           private AsynchronousFileChannel out;
+   *           long written;
+   *
+   *           {@literal @}Override
+   *           public void onSubscribe(Subscription s) {
+   *             subscription = s;
+   *             try {
+   *               this.out = AsynchronousFileChannel.open(
+   *                 file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING
+   *               );
+   *               subscription.request(1);
+   *             } catch (IOException e) {
+   *               subscription.cancel();
+   *               ctx.error(e);
+   *             }
+   *           }
+   *
+   *           {@literal @}Override
+   *           public void onNext(ByteBuf byteBuf) {
+   *             Promise.async(down ->
+   *               out.write(byteBuf.nioBuffer(), written, null, down.completionHandler())
+   *             ).onError(error -> {
+   *               byteBuf.release();
+   *               subscription.cancel();
+   *               out.close();
+   *               ctx.error(error);
+   *             }).then(bytesWritten -> {
+   *               byteBuf.release();
+   *               written += bytesWritten;
+   *               subscription.request(1);
+   *             });
+   *           }
+   *
+   *           {@literal @}Override
+   *           public void onError(Throwable t) {
+   *             ctx.error(t);
+   *             try {
+   *               out.close();
+   *             } catch (IOException ignore) {
+   *               // ignore
+   *             }
+   *           }
+   *
+   *           {@literal @}Override
+   *           public void onComplete() {
+   *             try {
+   *               out.close();
+   *             } catch (IOException ignore) {
+   *               // ignore
+   *             }
+   *             ctx.render("ok");
+   *           }
+   *         })
+   *       ).test(http -> {
+   *         ReceivedResponse response = http.request(requestSpec -> requestSpec.method("POST").getBody().text(string));
+   *         Assert.assertEquals(response.getBody().getText(), "ok");
+   *
+   *         String fileContents = Files.asCharSource(file.toFile(), StandardCharsets.UTF_8).read();
+   *         Assert.assertEquals(fileContents, string);
+   *       });
+   *     });
+   *   }
+   * }
+   * }
+ * + * @return a publisher of the request body + * @see #getBodyStream(long) + * @since 1.2 + */ + TransformablePublisher getBodyStream(long maxContentLength); + + /** + * The request headers. + * + * @return The request headers. + */ + Headers getHeaders(); + + /** + * The type of the data as specified in the {@code "content-type"} header. + *

+ * If no {@code "content-type"} header is specified, an empty {@link MediaType} is returned. + * + * @return The type of the data. + * @see MediaType#isEmpty() + */ + MediaType getContentType(); + + /** + * A flag representing whether or not the request originated via AJAX. + * @return A flag representing whether or not the request originated via AJAX. + */ + boolean isAjaxRequest(); + + /** + * Whether this request contains a {@code Expect: 100-Continue} header. + *

+ * You do not need to send the {@code 100 Continue} status response. + * It will be automatically sent when the body is read via {@link #getBody()} or {@link #getBodyStream(long)} (or variants). + * Ratpack will not emit a {@code 417 Expectation Failed} response if the body is not read. + * The response specified by the handling code will not be altered, as per normal. + *

+ * If the header is present for the request, this method will return {@code true} before and after the {@code 100 Continue} response has been sent. + * + * @return whether this request includes the {@code Expect: 100-Continue} header + * @since 1.2 + */ + boolean isExpectsContinue(); + + /** + * Whether this request contains a {@code Transfer-Encoding: chunked} header. + *

+ * See https://en.wikipedia.org/wiki/Chunked_transfer_encoding. + * + * @return whether this request contains a {@code Transfer-Encoding: chunked} header + * @since 1.2 + */ + boolean isChunkedTransfer(); + + /** + * The advertised content length of the request body. + *

+ * Will return {@code -1} if no {@code Content-Length} header is present, or is not valid long value. + * + * @return the advertised content length of the request body. + * @since 1.2 + */ + long getContentLength(); + + /** + * The timestamp for when this request was received. + * Specifically, this is the timestamp of creation of the request object. + * + * @return the instant timestamp for the request. + */ + Instant getTimestamp(); + + /** + * Sets the allowed max content length for the request body. + *

+ * This setting will be used when {@link #getBody()} or {@link #getBodyStream()} are called, + * and when implicitly reading the request body in order to respond (e.g. when issuing a response without trying to read the body). + * + * @param maxContentLength the maximum request body length in bytes + * @since 1.5 + */ + void setMaxContentLength(long maxContentLength); + + /** + * The max allowed content length for the request body. + * + * @see #setMaxContentLength(long) + * @return the maximum request body length in bytes + * @since 1.5 + */ + long getMaxContentLength(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java new file mode 100644 index 000000000000..90e635321df2 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java @@ -0,0 +1,210 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import com.google.common.reflect.TypeToken; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.cookie.Cookie; +import ratpack.core.handling.Context; +import ratpack.exec.api.NonBlocking; + +import java.nio.file.Path; +import java.util.Set; +import java.util.function.Supplier; + +/** + * A response to a request. + *

+ * The headers and status are configured, before committing the response with one of the {@link #send} methods. + */ +public interface Response { + + /** + * A type token for this type. + * + * @since 1.1 + */ + TypeToken TYPE = null; + + /** + * Creates a new cookie with the given name and value. + *

+ * The cookie will have no expiry. Use the returned cookie object to fine tune the cookie. + * + * @param name The name of the cookie + * @param value The value of the cookie + * @return The cookie that will be sent + */ + Cookie cookie(String name, String value); + + /** + * Adds a cookie to the response with a 0 max-age, forcing the client to expire it. + *

+ * If the cookie that you want to expire has an explicit path, you must use {@link Cookie#setPath(String)} on the return + * value of this method to have the cookie expire. + * + * @param name The name of the cookie to expire. + * @return The created cookie + */ + Cookie expireCookie(String name); + + /** + * The cookies that are to be part of the response. + *

+ * The cookies are mutable. + * + * @return The cookies that are to be part of the response. + */ + Set getCookies(); + + /** + * The response headers. + * + * @return The response headers. + */ + MutableHeaders getHeaders(); + + /** + * Sets the status line of the response. + *

+ * The message used will be the standard for the code. + * + * @param code The status code of the response to use when it is sent. + * @return This + */ + default Response status(int code) { + return null; + } + + /** + * Sends the response back to the client, with no body. + */ + @NonBlocking + void send(); + + Response contentTypeIfNotSet(Supplier contentType); + + /** + * Sends the response, using "{@code text/plain}" as the content type and the given string as the response body. + *

+ * Equivalent to calling "{@code send\("text/plain", text)}. + * + * @param text The text to render as a plain text response. + */ + @NonBlocking + void send(String text); + + /** + * Sends the response, using the given content type and string as the response body. + *

+ * The string will be sent in "utf8" encoding, and the given content type will have this appended. + * That is, given a {@code contentType} of "{@code application/json}" the actual value for the {@code Content-Type} + * header will be "{@code application/json;charset=utf8}". + *

+ * The value given for content type will override any previously set value for this header. + * @param contentType The value of the content type header + * @param body The string to render as the body of the response + */ + @NonBlocking + void send(CharSequence contentType, String body); + + /** + * Sends the response, using "{@code application/octet-stream}" as the content type (if a content type hasn't + * already been set) and the given byte array as the response body. + * + * @param bytes The response body + */ + @NonBlocking + void send(byte[] bytes); + + /** + * Sends the response, using the given content type and byte array as the response body. + * @param contentType The value of the {@code Content-Type} header + * @param bytes The response body + */ + @NonBlocking + void send(CharSequence contentType, byte[] bytes); + + /** + * Sends the response, using "{@code application/octet-stream}" as the content type (if a content type hasn't + * already been set) and the given bytes as the response body. + * + * @param buffer The response body + */ + @NonBlocking + void send(ByteBuf buffer); + + /** + * Sends the response, using the given content type and bytes as the response body. + * @param contentType The value of the {@code Content-Type} header + * @param buffer The response body + */ + @NonBlocking + void send(CharSequence contentType, ByteBuf buffer); + + /** + * Sets the response {@code Content-Type} header. + * + * @param contentType The value of the {@code Content-Type} header + * @return This + */ + Response contentType(CharSequence contentType); + + /** + * Sets the response {@code Content-Type} header, if it has not already been set. + * + * @param contentType The value of the {@code Content-Type} header + * @return This + */ + default Response contentTypeIfNotSet(CharSequence contentType) { + return null; + } + + /** + * Sends the response, using the file as the response body. + *

+ * This method does not set the content length, content type or anything else. + * It is generally preferable to use the {@link Context#render(Object)} method with a file/path object, + * or an {@link Chain#files(Action)}. + * + * @param file the response body + */ + @NonBlocking + void sendFile(Path file); + + /** + * Prevents the response from being compressed. + * + * @return {@code this} + */ + Response noCompress(); + + /** + * Forces the closing of the current connection, even if the client requested it to be kept alive. + *

+ * This method can be used when it is desirable to force the client's connection to close, defeating HTTP keep alive. + * This can be desirable in some networking environments where rate limiting or throttling is performed via edge routers or similar. + *

+ * This method simply calls {@code getHeaders().set("Connection", "close")}, which has the same effect. + * + * @return {@code this} + * @since 1.1 + */ + default Response forceCloseConnection() { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/TypedData.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/TypedData.java new file mode 100644 index 000000000000..c6fb4f1ef19c --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/TypedData.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.http; + +import io.netty.buffer.ByteBuf; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * Data that potentially has a content type. + */ +public interface TypedData { + + /** + * The type of the data. + * + * @return The type of the data. + */ + MediaType getContentType(); + + /** + * The data as text. + *

+ * If a content type was provided, and it provided a charset parameter, that charset will be used to decode the text. + * If no charset was provided, {@code UTF-8} will be assumed. + *

+ * This can lead to incorrect results for non {@code text/*} type content types. + * For example, {@code application/json} is implicitly {@code UTF-8} but this method will not know that. + * + * @return The data decoded as text + */ + String getText(); + + String getText(Charset charset); + + /** + * The raw data as bytes. + * + * The returned array should not be modified. + * + * @return the raw data as bytes. + */ + byte[] getBytes(); + + /** + * The raw data as a (unmodifiable) buffer. + * + * @return the raw data as bytes. + */ + ByteBuf getBuffer(); + + /** + * Writes the data to the given output stream. + *

+ * Data is effectively piped from {@link #getInputStream()} to the given output stream. + * As such, if the given output stream might block (e.g. is backed by a file or socket) then + * this should be called from a blocking thread using {@link Blocking#get(Factory)} + * This method does not flush or close the stream. + * + * @param outputStream The stream to write to + * @throws IOException any thrown when writing to the output stream + */ + void writeTo(OutputStream outputStream) throws IOException; + + /** + * An input stream of the data. + *

+ * This input stream is backed by an in memory buffer. + * Reading from this input stream will never block. + *

+ * It is not necessary to close the input stream. + * + * @return an input stream of the data + */ + InputStream getInputStream(); + +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java new file mode 100644 index 000000000000..a61318cd2cd8 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java @@ -0,0 +1,106 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.parse; + +import com.google.common.reflect.TypeToken; +import ratpack.core.handling.Context; + +import java.util.Optional; + +/** + * The specification of a particular parse. + *

+ * Construct instances via the {@link #of} methods. + * + * @param the type of object to construct from the request body + * @param the type of object that provides options/configuration for the parsing + * @see Context#parse(Parse) + * @see Parser + * @see ParserSupport + */ +public class Parse { + + /** + * The type of object to construct from the request body. + * + * @return the type of object to construct from the request body + */ + public TypeToken getType() { + return null; + } + + /** + * The type of object that provides options/configuration for the parsing. + *

+ * For any parse request, no options may be specified. + * Parser implementations should throw an exception if they require an options object when none is supplied. + * + * @return the type of object that provides options/configuration for the parsing + */ + public Optional getOpts() { + return null; + } + + /** + * Creates a parse object. + * + * @param type the type of object to construct from the request body + * @param opts the options object + * @param the type of object to construct from the request body + * @param the type of object that provides options/configuration for the parsing + * @return a parse instance from the given arguments + */ + public static Parse of(TypeToken type, O opts) { + return null; + } + + /** + * Creates a parse object, with no options. + * + * @param type the type of object to construct from the request body + * @param the type of object to construct from the request body + * @return a parse instance to the given type + */ + public static Parse of(TypeToken type) { + return null; + } + + /** + * Creates a parse object. + * + * @param type the type of object to construct from the request body + * @param opts the options object + * @param the type of object to construct from the request body + * @param the type of object that provides options/configuration for the parsing + * @return a parse instance from the given arguments + */ + public static Parse of(Class type, O opts) { + return null; + } + + /** + * Creates a parse object, with no options. + * + * @param type the type of object to construct from the request body + * @param the type of object to construct from the request body + * @return a parse instance to the given type + */ + public static Parse of(Class type) { + return null; + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java new file mode 100644 index 000000000000..efc5d26ebfcc --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.render; + +/** + * Thrown when a request is made to render an object, but no suitable renderer can be found. + */ +public class NoSuchRendererException extends RuntimeException { + + private static final long serialVersionUID = 0; + + /** + * Constructor. + * + * @param object The object to be rendered. + */ + public NoSuchRendererException(Object object) { + super("No renderer for object '" + object + "' of type '" + object.getClass() + "'"); + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java new file mode 100644 index 000000000000..206a247844a8 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec; + +import ratpack.func.Action; +import ratpack.func.Function; + +/** + * A promise for a single value. + *

+ * A promise is a representation of a value which will become available later. + * Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of “operations” to be specified, + * that the value will travel through as it becomes available. + * Such operations are implemented via the {@link #transform(Function)} method. + * Each operation returns a new promise object, not the original promise object. + *

+ * To create a promise, use the {@link Promise#async(Upstream)} method (or one of the variants such as {@link Promise#sync(Factory)}. + * To test code that uses promises, use the {@link ratpack.test.exec.ExecHarness}. + *

+ * The promise is not “activated” until the {@link #then(Action)} method is called. + * This method terminates the pipeline, and receives the final value. + *

+ * Promise objects are multi use. + * Every promise pipeline has a value producing function at its start. + * Activating a promise (i.e. calling {@link #then(Action)}) invokes the function. + * The {@link #cache()} operation can be used to change this behaviour. + * + * @param the type of promised value + */ +@SuppressWarnings("JavadocReference") +public interface Promise { + Promise map(Function transformer); + + void then(Action then); +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java new file mode 100644 index 000000000000..6338881a9312 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec.api; + +import java.lang.annotation.*; + +/** + * Declares that a method or function-like method parameter is non-blocking and can freely use {@link ratpack.exec.Promise} and other async constructs. + *

+ * If this annotation is present on a method, it indicates that the method may be asynchronous. + * That is, it is not necessarily expected to have completed its logical work when the method returns. + * The method must however use {@link ratpack.exec.Promise}, {@link ratpack.exec.Operation}, or other execution mechanisms to perform asynchronous work. + *

+ * Most such methods are invoked as part of Ratpack. + * If you need to invoke such a method, do so as part of a discrete {@link ratpack.exec.Operation}. + *

+ * If this annotation is present on a function type method parameter, it indicates that the annotated function may be asynchronous. + * Similarly, if you need to invoke such a parameter, do so as part of a discrete {@link ratpack.exec.Operation}. + *

+ * Note: the ability to annotate method parameters with this annotation was added in version {@code 1.1.0}. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.PARAMETER}) +public @interface NonBlocking { +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java new file mode 100644 index 000000000000..7391a68f81c5 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec.registry; + +import com.google.common.reflect.TypeToken; + +/** + * Thrown when a request is made for an object that a registry cannot provide. + * + * @see Registry#get(Class) + */ +public class NotInRegistryException extends RuntimeException { + + private static final long serialVersionUID = 0; + + /** + * Constructs the exception. + * + * @param type The requested type of the object + */ + public NotInRegistryException(TypeToken type) { + this(String.format("No object for type '%s' in registry", type)); + } + + /** + * Constructor. + * + * @param message The exception message + */ + public NotInRegistryException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java new file mode 100644 index 000000000000..6c0ad85970bc --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java @@ -0,0 +1,244 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec.registry; + +import com.google.common.reflect.TypeToken; + +import java.util.Optional; +import java.util.function.Supplier; + +/** + * An object registry. + *

+ * Registries are primarily used for inter {@link ratpack.core.handling.Handler} communication in request processing. + * The {@link ratpack.core.handling.Context} object that handlers operate on implements the {@link Registry} interface. + *

+ * import ratpack.core.handling.Handler;
+ * import ratpack.core.handling.Context;
+ * import ratpack.exec.registry.Registry;
+ *
+ * import static org.junit.Assert.assertTrue;
+ *
+ * public class Thing {
+ *   private final String name
+ *   public Thing(String name) { this.name = name; }
+ *   public String getName() { return name; }
+ * }
+ *
+ * public class UpstreamHandler implements Handler {
+ *   public void handle(Context context) {
+ *     context.next(Registry.single(new Thing("foo")));
+ *   }
+ * }
+ *
+ * public class DownstreamHandler implements Handler {
+ *   public void handle(Context context) {
+ *     assertTrue(context instanceof Registry);
+ *     Thing thing = context.get(Thing.class);
+ *     context.render(thing.getName());
+ *   }
+ * }
+ *
+ * import ratpack.test.handling.HandlingResult;
+ * import ratpack.test.handling.RequestFixture;
+ *
+ * import static ratpack.core.handling.Handlers.chain;
+ * import static ratpack.func.Action.noop;
+ *
+ * import static org.junit.Assert.assertEquals;
+ *
+ * Handler chain = chain(new UpstreamHandler(), new DownstreamHandler());
+ * HandlingResult result = RequestFixture.handle(chain, noop());
+ *
+ * assertEquals("foo", result.rendered(String.class));
+ * 
+ *

Thread safety

+ *

+ * Registry objects are assumed to be thread safe. + * No external synchronization is performed around registry access. + * As registry objects may be used across multiple requests, they should be thread safe. + *

+ * Registries that are created per request however do not need to be thread safe. + * + *

Ordering

+ *

+ * Registry objects are returned in the reverse order that they were added (i.e. Last-In-First-Out). + * + *

{@code
+ * import com.google.common.base.Joiner;
+ * import ratpack.exec.registry.Registry;
+ *
+ * import static org.junit.Assert.assertEquals;
+ *
+ * public class Example {
+ *   public static void main(String... args) throws Exception {
+ *     Registry registry = Registry.of(r -> r
+ *         .add("Ratpack")
+ *         .add("foo")
+ *         .add("bar")
+ *     );
+ *
+ *     assertEquals("bar", registry.get(String.class));
+ *
+ *     String joined = Joiner.on(", ").join(registry.getAll(String.class));
+ *     assertEquals("bar, foo, Ratpack", joined);
+ *   }
+ * }
+ * }
+ *

+ * While this is strictly the case for the core registry implementations in Ratpack, adapted implementations (e.g. Guice, Spring etc.) may have more nuanced ordering semantics. + * To the greatest extent possible, registry implementations should strive to honor LIFO ordering. + */ +public interface Registry { + + /** + * Provides an object of the specified type, or throws an exception if no object of that type is available. + * + * @param type The type of the object to provide + * @param The type of the object to provide + * @return An object of the specified type + * @throws NotInRegistryException If no object of this type can be returned + */ + default O get(Class type) throws NotInRegistryException { + return null; + } + + /** + * Provides an object of the specified type, or throws an exception if no object of that type is available. + * + * @param type The type of the object to provide + * @param The type of the object to provide + * @return An object of the specified type + * @throws NotInRegistryException If no object of this type can be returned + */ + default O get(TypeToken type) throws NotInRegistryException { + return null; + } + + /** + * Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception. + * + * @param type The type of the object to provide + * @param The type of the object to provide + * @return An object of the specified type, or null if no object of this type is available. + */ + default Optional maybeGet(Class type) { + return null; + } + + /** + * Does the same thing as {@link #get(Class)}, except returns null instead of throwing an exception. + * + * @param type The type of the object to provide + * @param The type of the object to provide + * @return An optional of an object of the specified type + */ + Optional maybeGet(TypeToken type); + + /** + * Returns all of the objects whose declared type is assignment compatible with the given type. + * + * @param type the type of objects to search for + * @param the type of objects to search for + * @return All objects of the given type + */ + default Iterable getAll(Class type) { + return null; + } + + /** + * Returns all of the objects whose declared type is assignment compatible with the given type. + * + * @param type the type of objects to search for + * @param the type of objects to search for + * @return All objects of the given type + */ + Iterable getAll(TypeToken type); + + /** + * Creates a new registry by joining {@code this} registry with the given registry + *

+ * The returned registry is effectively the union of the two registries, with the {@code child} registry taking precedence. + * This means that child entries are effectively “returned first”. + *

{@code
+   * import ratpack.exec.registry.Registry;
+   *
+   * import java.util.List;
+   * import com.google.common.collect.Lists;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *
+   *   public static interface Thing {
+   *     String getName();
+   *   }
+   *
+   *   public static class ThingImpl implements Thing {
+   *     private final String name;
+   *
+   *     public ThingImpl(String name) {
+   *       this.name = name;
+   *     }
+   *
+   *     public String getName() {
+   *       return name;
+   *     }
+   *   }
+   *
+   *   public static void main(String[] args) {
+   *     Registry child = Registry.builder().add(Thing.class, new ThingImpl("child-1")).add(Thing.class, new ThingImpl("child-2")).build();
+   *     Registry parent = Registry.builder().add(Thing.class, new ThingImpl("parent-1")).add(Thing.class, new ThingImpl("parent-2")).build();
+   *     Registry joined = parent.join(child);
+   *
+   *     assertEquals("child-2", joined.get(Thing.class).getName());
+   *     List all = Lists.newArrayList(joined.getAll(Thing.class));
+   *     assertEquals("child-2", all.get(0).getName());
+   *     assertEquals("child-1", all.get(1).getName());
+   *     assertEquals("parent-2", all.get(2).getName());
+   *     assertEquals("parent-1", all.get(3).getName());
+   *   }
+   * }
+   * }
+ * + * @param child the child registry + * @return a registry which is the combination of the {@code this} and the given child + */ + default Registry join(Registry child) { + return null; + } + + /** + * Returns an empty registry. + * + * @return an empty registry + */ + static Registry empty() { + return null; + } + + /** + * Creates a new {@link RegistryBuilder registry builder}. + * + * @return a new registry builder + * @see RegistryBuilder + */ + static RegistryBuilder builder() { + return null; + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/RegistryBuilder.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/RegistryBuilder.java new file mode 100644 index 000000000000..b89a711d8356 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/RegistryBuilder.java @@ -0,0 +1,19 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec.registry; + +public interface RegistryBuilder {} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java new file mode 100644 index 000000000000..488b8b07dd7f --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.exec.stream; + +/** + * A wrapper over a {@link Publisher} that makes it more convenient to chain transformations of different kinds. + *

+ * Note that this type implements the publisher interface, + * so behaves just like the publisher that it is wrapping with respect to the + * {@link Publisher#subscribe(Subscriber)} method. + * + * @param the type of item emitted by this publisher + */ +public interface TransformablePublisher { +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java new file mode 100644 index 000000000000..6e420e4c3727 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java @@ -0,0 +1,266 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +import java.util.function.Consumer; + +/** + * A generic type for an object that does some work with a thing. + *

+ * This type serves the same purpose as the JDK's {@link java.util.function.Consumer}, but allows throwing checked exceptions. + * It contains methods for bridging to and from the JDK type. + * + * @param The type of thing. + */ +@FunctionalInterface +public interface Action { + + /** + * Executes the action against the given thing. + * + * @param t the thing to execute the action against + * @throws Exception if anything goes wrong + */ + void execute(T t) throws Exception; + + /** + * Returns an action that does precisely nothing. + * + * @return an action that does precisely nothing + */ + static Action noop() { + return null; + } + + /** + * If the given action is {@code null}, returns {@link #noop()}, otherwise returns the given action. + * + * @param action an action, maybe {@code null}. + * @param the type of parameter received by the action + * @return the given {@code action} param if it is not {@code null}, else a {@link #noop()}. + */ + static Action noopIfNull(@Nullable Action action) { + return null; + } + + /** + * Returns a new action that executes the given actions in order. + * + * @param actions the actions to join into one action + * @param the type of object the action accepts + * @return the newly created aggregate action + */ + @SafeVarargs + static Action join(final Action... actions) { + return null; + } + + /** + * Returns a new action that executes this action and then the given action. + * + * @param action the action to execute after this action + * @param the type of object the action accepts + * @return the newly created aggregate action + */ + default Action append(Action action) { + return null; + } + + /** + * Returns a new action that executes the given action and then this action. + * + * @param action the action to execute before this action + * @param the type of object the action accepts + * @return the newly created aggregate action + */ + default Action prepend(Action action) { + return null; + } + + /** + * Returns an action that receives a throwable and immediately throws it. + * + * @return an action that receives a throwable and immediately throws it + */ + static Action throwException() { + return null; + } + + /** + * An action that receives a throwable to thrown, suppressing the given value. + * + * @return an action that receives a throwable to thrown, suppressing the given value + * @since 1.5 + */ + static Action suppressAndThrow(Throwable toSuppress) { + return null; + } + + /** + * Returns an action that immediately throws the given exception. + *

+ * The exception is thrown via {@link Exceptions#toException(Throwable)} + * + * @param the argument type (anything, as the argument is ignored) + * @param throwable the throwable to immediately throw when the returned action is executed + * @return an action that immediately throws the given exception. + */ + static Action throwException(final Throwable throwable) { + return null; + } + + /** + * Executes the action with the given argument, then returns the argument. + *

{@code
+   * import ratpack.func.Action;
+   * import java.util.ArrayList;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *   public static void main(String... args) throws Exception {
+   *     assertEquals("foo", Action.with(new ArrayList<>(), list -> list.add("foo")).get(0));
+   *   }
+   * }
+   * }
+ * @param t the argument to execute the given action with + * @param action the action to execute with the given argument + * @param the type of the argument + * @return the given argument (i.e. {@code t}) + * @throws Exception any thrown by {@code action} + */ + static T with(T t, Action action) throws Exception { + return null; + } + + /** + * Executes with the given argument, then returns the argument. + *
{@code
+   * import ratpack.func.Action;
+   * import java.util.List;
+   * import java.util.ArrayList;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *   public static void main(String... args) throws Exception {
+   *     assertEquals("foo", run(list -> list.add("foo")).get(0));
+   *   }
+   *
+   *   private static List run(Action> action) throws Exception {
+   *     return action.with(new ArrayList<>());
+   *   }
+   * }
+   * }
+ * @param o the argument to execute the given action with + * @param the type of the argument + * @return the given argument (i.e. {@code o}) + * @throws Exception any thrown by {@link #execute(Object)} + */ + default O with(O o) throws Exception { + return null; + } + + /** + * Like {@link #with(Object, Action)}, but unchecks any exceptions thrown by the action via {@link Exceptions#uncheck(Throwable)}. + * + * @param t the argument to execute the given action with + * @param action the action to execute with the given argument + * @param the type of the argument + * @return the given argument (i.e. {@code t}) + */ + static T uncheckedWith(T t, Action action) { + return null; + } + + /** + * Like {@link #with(Object)}, but unchecks any exceptions thrown by the action via {@link Exceptions#uncheck(Throwable)}. + * + * @param o the argument to execute with + * @param the type of the argument + * @return the given argument (i.e. {@code o}) + */ + default O uncheckedWith(O o) { + return null; + } + + /** + * Creates a JDK {@link Consumer} from this action. + *

+ * Any exceptions thrown by {@code this} action will be unchecked via {@link Exceptions#uncheck(Throwable)} and rethrown. + * + * @return this function as a JDK style consumer. + */ + default Consumer toConsumer() { + return null; + } + + /** + * Creates an exception-taking action that executes the given action before throwing the exception. + * + * @param action the action to perform before throwing the exception + * @return an action that performs the given action before throwing its argument + * @since 1.5 + */ + static Action beforeThrow(Action action) { + return null; + } + + /** + * Creates an action that delegates to the given action if the given predicate applies, else delegates to {@link #noop()}. + *

+ * This is equivalent to {@link #when(Predicate, Action, Action) when(predicate, action, noop())}. + * + * @param predicate the condition for the argument + * @param action the action to execute if the predicate applies + * @param the type of argument + * @return an action that delegates to the given action if the predicate applies, else noops + * @see #when(Predicate, Action, Action) + * @see #conditional(Action, Action) + * @since 1.5 + */ + static Action when(Predicate predicate, Action action) { + return null; + } + + /** + * Creates an action that delegates to the first action if the given predicate applies, else the second action. + * + * @param predicate the condition for the argument + * @param onTrue the action to execute if the predicate applies + * @param onFalse the action to execute if the predicate DOES NOT apply + * @param the type of argument + * @return an action that delegates to the first action if the predicate applies, else the second argument + * @see #when(Predicate, Action) + * @see #conditional(Action, Action) + * @since 1.5 + */ + static Action when(Predicate predicate, Action onTrue, Action onFalse) { + return null; + } + + /** + * A spec for adding conditions to a conditional action. + * + * @param the input type + * @see #conditional(Action, Action) + * @since 1.5 + */ + interface ConditionalSpec { + ConditionalSpec when(Predicate predicate, Action action); + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java new file mode 100644 index 000000000000..86b0f6612a86 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java @@ -0,0 +1,208 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +import java.util.Objects; + +/** + * A single argument function. + *

+ * This type serves the same purpose as the JDK's {@link java.util.function.Function}, but allows throwing checked exceptions. + * It contains methods for bridging to and from the JDK type. + * + * @param the type of the input + * @param the type of the output + */ +@FunctionalInterface +public interface Function { + + /** + * The function implementation. + * + * @param i the input to the function + * @return the output of the function + * @throws Exception any + */ + O apply(I i) throws Exception; + + /** + * Joins {@code this} function with the given function. + * + *

{@code
+   * import ratpack.func.Function;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *   public static void main(String[] args) throws Exception {
+   *     Function function = in -> in + "-bar";
+   *     assertEquals("FOO-BAR", function.andThen(String::toUpperCase).apply("foo"));
+   *   }
+   * }
+   * }
+ *

+ * Analogous to {@link java.util.function.Function#andThen(java.util.function.Function)}. + * + * @param after the function to apply to the result of {@code this} function + * @param the type of the final output + * @return the result of applying the given function to {@code this} function + * @throws Exception any thrown by {@code this} or {@code after} + */ + default Function andThen(Function after) throws Exception { + return null; + } + + /** + * Joins the given function with {@code this} function. + * + *

{@code
+   * import ratpack.func.Function;
+   *
+   * import static org.junit.Assert.assertEquals;
+   *
+   * public class Example {
+   *   public static void main(String... args) throws Exception {
+   *     Function function = String::toUpperCase;
+   *     assertEquals("FOO-BAR", function.compose(in -> in + "-BAR").apply("foo"));
+   *   }
+   * }
+   * }
+ *

+ * Analogous to {@link java.util.function.Function#compose(java.util.function.Function)}. + * + * @param before the function to apply {@code this} function to the result of + * @param the type of the new input + * @return the result of applying {@code this} function to the result of the given function + * @throws Exception any thrown by {@code this} or {@code before} + */ + default Function compose(Function before) throws Exception { + return null; + } + + + + /** + * Returns an identity function (return value always same as input). + * + * @param the type of the input and output objects to the function + * @return a function that always returns its input argument + */ + static Function identity() { + return null; + } + + /** + * Returns a function that always returns the given argument. + * + * @param t the value to always return + * @param the type of returned value + * @return a function that returns the given value + */ + static Function constant(T t) { + return null; + } + + /** + * Creates a function that delegates to the given function if the given predicate applies, else delegates to {@link #identity()}. + *

+ * This is equivalent to {@link #when(Predicate, Function, Function) when(predicate, function, identity())}. + * + * @param predicate the condition for the argument + * @param function the function to apply if the predicate applies + * @param the type of argument and return value + * @return a function that delegates to the given function if the predicate applies, else returns the argument + * @see #when(Predicate, Function, Function) + * @see #conditional(Function, Action) + * @since 1.5 + */ + static Function when(Predicate predicate, Function function) { + return null; + } + + /** + * Creates a function that delegates to the first function if the given predicate applies, else the second function. + * + * @param predicate the condition for the argument + * @param onTrue the function to apply if the predicate applies + * @param onFalse the function to apply if the predicate DOES NOT apply + * @param the type of argument + * @param the type of return value + * @return a function that delegates to the first function if the predicate applies, else the second argument + * @see #when(Predicate, Function) + * @see #conditional(Function, Action) + * @since 1.5 + */ + static Function when(Predicate predicate, Function onTrue, Function onFalse) { + return null; + } + + /** + * A spec for adding conditions to a conditional function. + * + * @param the input type + * @param the output type + * @see #conditional(Function, Action) + * @since 1.5 + */ + interface ConditionalSpec { + + /** + * Adds a conditional function. + * + * @param predicate the condition predicate + * @param function the function to apply if the predicate applies + * @return {@code this} + */ + ConditionalSpec when(Predicate predicate, Function function); + } + + /** + * Creates a function that delegates based on the specified conditions. + *

+ * If no conditions match, an {@link IllegalArgumentException} will be thrown. + * Use {@link #conditional(Function, Action)} alternatively to specify a different “else” strategy. + * + * @param conditions the conditions + * @param the input type + * @param the output type + * @return a conditional function + * @see #conditional(Function, Action) + * @throws Exception any thrown by {@code conditions} + * @since 1.5 + */ + static Function conditional(Action> conditions) throws Exception { + return null; + } + + /** + * Creates a function that delegates based on the specified conditions. + *

+ * If no condition applies, the {@code onElse} function will be delegated to. + * + * @param onElse the function to delegate to if no condition matches + * @param conditions the conditions + * @param the input type + * @param the output type + * @return a conditional function + * @see #conditional(Action) + * @throws Exception any thrown by {@code conditions} + * @since 1.5 + */ + static Function conditional(Function onElse, Action> conditions) throws Exception { + return null; + } +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java new file mode 100644 index 000000000000..5ed83ef5438b --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java @@ -0,0 +1,101 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +import com.google.common.collect.ListMultimap; + +import java.util.List; +import java.util.Map; + +/** + * A map that may contain multiple values for a given key, but typically only one value. + *

+ * Unlike other multi map types, this type is optimized for the case where there is only one value for a key. + * The map acts just like a normal {@link Map}, but has extra methods for getting all values for a key. + *

+ * All implementations of this type are immutable. Mutating operations throw {@link UnsupportedOperationException}. + *

+ * Where there is multiple values for a given key, retrieving a single value will return the first value, + * where the first value is intrinsic to the service in which the map is being used. + * + * @param The type of key objects + * @param The type of value objects + */ +public interface MultiValueMap extends Map { + + static MultiValueMap empty() { + return null; + } + + /** + * All of the values for the given key. An empty list if there are no values for the key. + *

+ * The returned list is immutable. + * + * @param key The key to return all values of + * @return all of the values for the given key, or an empty list if there are no values for the key. + */ + List getAll(K key); + + /** + * Returns a new view of the map where each map value is a list of all the values for the given key (i.e. a traditional multi map). + *

+ * The returned map is immutable. + * @return A new view of the map where each map value is a list of all the values for the given key + */ + Map> getAll(); + + /** + * Get the first value for the key, or {@code null} if there are no values for the key. + * + * @param key The key to obtain the first value for + * @return The first value for the given key, or {@code null} if there are no values for the given key + */ + V get(Object key); + + /** + * Throws {@link UnsupportedOperationException}. + * + * {@inheritDoc} + */ + V put(K key, V value); + + /** + * Throws {@link UnsupportedOperationException}. + * + * {@inheritDoc} + */ + V remove(Object key); + + /** + * Throws {@link UnsupportedOperationException}. + * + * {@inheritDoc} + */ + @SuppressWarnings("NullableProblems") + void putAll(Map m); + + /** + * Throws {@link UnsupportedOperationException}. + * + * {@inheritDoc} + */ + void clear(); + + ListMultimap asMultimap(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Nullable.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Nullable.java new file mode 100644 index 000000000000..9456fc77d05e --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Nullable.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +import java.lang.annotation.*; + +/** + * Denotes that something may be null. + *

    + *
  • On a parameter, denotes that it is valid to supply null as the value for the parameter. + *
  • On a method, denotes that the method may return null. + *
  • On a field, denotes that the field value may be null. + *
+ */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +public @interface Nullable { +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java new file mode 100644 index 000000000000..050b07620036 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +/** + * A function that returns {@code true} or {@code false} for a value. + *

+ * This type serves the same purpose as the JDK's {@link java.util.function.Predicate}, but allows throwing checked exceptions. + * It contains methods for bridging to and from the JDK type. + * + * @param the type of object “tested” by the predicate + */ +@FunctionalInterface +public interface Predicate { + + /** + * Tests the given value. + * + * @param t the value to “test” + * @return {@code true} if the predicate applied, otherwise {@code false} + * @throws Exception any + */ + boolean apply(T t) throws Exception; + + /** + * Creates a predicate from a JDK predicate. + * + * @param predicate the JDK predicate + * @param the type of object this predicate tests + * @return the given JDK predicate as a predicate + */ + static Predicate from(java.util.function.Predicate predicate) { + return null; + } + + /** + * Creates a predicate from a Guava predicate. + * + * @param predicate the Guava predicate + * @param the type of object this predicate tests + * @return the given Guava predicate as a predicate + */ + static Predicate fromGuava(com.google.common.base.Predicate predicate) { + return null; + } + + /** + * A predicate that always returns {@code true}, regardless of the input object. + * + * @param the type of input object + * @return a predicate that always returns {@code true} + * @since 1.1 + */ + static Predicate alwaysTrue() { + return null; + } + + /** + * A predicate that always returns {@code false}, regardless of the input object. + * + * @param the type of input object + * @return a predicate that always returns {@code false} + * @since 1.1 + */ + static Predicate alwaysFalse() { + return null; + } + + /** + * Creates a function the returns one of the given values. + * + * @param onTrue the value to return if the predicate applies + * @param onFalse the value to return if the predicate does not apply + * @param the output value + * @return a function + * @since 1.5 + */ + default Function function(O onTrue, O onFalse) { + return null; + } + +} \ No newline at end of file From 170657b9a49d4393b99345a0be178fe6d6f4b23b Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 30 Apr 2021 14:58:37 -0400 Subject: [PATCH 03/29] Add additional Ratpack test and improve Promise based dataflow tracking --- .../code/java/frameworks/ratpack/Ratpack.qll | 30 +++++++++++++++ .../java/frameworks/ratpack/RatpackExec.qll | 28 ++++++++++---- .../ratpack/resources/Resource.java | 31 +++++++++++++++- .../ratpack/core/form/UploadedFile.java | 37 +++++++++++++++++++ .../ratpack-1.9.x/ratpack/exec/Promise.java | 2 + 5 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index d8797f4afb80..5d2537d25530 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -3,6 +3,8 @@ */ import java +private import semmle.code.java.dataflow.DataFlow +private import semmle.code.java.dataflow.FlowSteps /** * Ratpack methods that access user-supplied request data. @@ -81,3 +83,31 @@ class RatpackUploadFileGetMethod extends RatpackGetRequestDataMethod { hasName("getFileName") } } + +class RatpackHeader extends RefType { + RatpackHeader() { + hasQualifiedName("ratpack.http", "Headers") or + hasQualifiedName("ratpack.core.http", "Headers") + } +} + +private class RatpackHeaderTaintPropigatingMethod extends Method { + RatpackHeaderTaintPropigatingMethod() { + getDeclaringType() instanceof RatpackHeader and + hasName(["get", "getAll", "getNames", "asMultiValueMap"]) + } +} + +class TaintPropigatingHeaderMethod extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + stepHeaderPropigatingTaint(node1, node2) + } + + private predicate stepHeaderPropigatingTaint(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma | + ma.getMethod() instanceof RatpackHeaderTaintPropigatingMethod and + node2.asExpr() = ma and + node1.asExpr() = ma.getQualifier() + ) + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index ef4722cc745d..453e75743c83 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -9,6 +9,12 @@ class RatpackPromise extends RefType { } } +/** + * Taint flows from the qualifier to the first argument of the lambda passed to this method access. + * Eg. `tainted.map(stillTainted -> ..)` + */ +abstract private class TaintFromQualifierToFunctionalArgumentMethodAccess extends MethodAccess { } + class RatpackPromiseMapMethod extends Method { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and @@ -16,7 +22,7 @@ class RatpackPromiseMapMethod extends Method { } } -class RatpackPromiseMapMethodAccess extends MethodAccess { +class RatpackPromiseMapMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { RatpackPromiseMapMethodAccess() { getMethod() instanceof RatpackPromiseMapMethod } } @@ -27,10 +33,21 @@ class RatpackPromiseThenMethod extends Method { } } -class RatpackPromiseThenMethodAccess extends MethodAccess { +class RatpackPromiseThenMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { RatpackPromiseThenMethodAccess() { getMethod() instanceof RatpackPromiseThenMethod } } +class RatpackPromiseNextMethod extends FluentMethod { + RatpackPromiseNextMethod() { + getDeclaringType() instanceof RatpackPromise and + hasName("next") + } +} + +class RatpackPromiseNextMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { + RatpackPromiseNextMethodAccess() { getMethod() instanceof RatpackPromiseNextMethod } +} + private class RatpackPromiseTaintPreservingCallable extends AdditionalTaintStep { override predicate step(DataFlow::Node node1, DataFlow::Node node2) { stepFromFunctionalExpToPromise(node1, node2) or @@ -51,12 +68,7 @@ private class RatpackPromiseTaintPreservingCallable extends AdditionalTaintStep * Tracks taint from the previous `Promise` to the first argument of lambda passed to `map` or `then`. */ private predicate stepFromPromiseToFunctionalArgument(DataFlow::Node node1, DataFlow::Node node2) { - exists(RatpackPromiseMapMethodAccess ma | - node1.asExpr() = ma.getQualifier() and - ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() - ) - or - exists(RatpackPromiseThenMethodAccess ma | + exists(TaintFromQualifierToFunctionalArgumentMethodAccess ma | node1.asExpr() = ma.getQualifier() and ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() ) diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 55b57abcff14..6e1970e0606e 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -1,5 +1,7 @@ import ratpack.core.handling.Context; import ratpack.core.http.TypedData; +import ratpack.core.form.UploadedFile; +import java.io.OutputStream; class Resource { @@ -10,18 +12,45 @@ void test1(Context ctx) { sink(ctx.getRequest().getCookies()); //$hasTaintFlow sink(ctx.getRequest().oneCookie("Magic-Cookie")); //$hasTaintFlow sink(ctx.getRequest().getHeaders()); //$hasTaintFlow + sink(ctx.getRequest().getHeaders().get("questionable_header")); //$hasTaintFlow + sink(ctx.getRequest().getHeaders().getAll("questionable_header")); //$hasTaintFlow + sink(ctx.getRequest().getHeaders().getNames()); //$hasTaintFlow + sink(ctx.getRequest().getHeaders().asMultiValueMap()); //$hasTaintFlow + sink(ctx.getRequest().getHeaders().asMultiValueMap().get("questionable_header")); //$hasTaintFlow sink(ctx.getRequest().getPath()); //$hasTaintFlow sink(ctx.getRequest().getQuery()); //$hasTaintFlow sink(ctx.getRequest().getQueryParams()); //$hasTaintFlow + sink(ctx.getRequest().getQueryParams().get("questionable_parameter")); //$hasTaintFlow sink(ctx.getRequest().getRawUri()); //$hasTaintFlow sink(ctx.getRequest().getUri()); //$hasTaintFlow } void test2(TypedData td) { sink(td.getText()); //$hasTaintFlow + sink(td.getBuffer()); //$hasTaintFlow + sink(td.getBytes()); //$hasTaintFlow + sink(td.getContentType()); //$hasTaintFlow + sink(td.getInputStream()); //$hasTaintFlow } - void test2(Context ctx) { + void test3(TypedData td, OutputStream os) throws java.io.IOException { + sink(os); + td.writeTo(os); + sink(os); //$hasTaintFlow + } + + void test4(UploadedFile uf) { + sink(uf.getFileName()); //$hasTaintFlow + } + + void test5(Context ctx) { + sink(ctx.getRequest().getBody().map(TypedData::getText)); //$hasTaintFlow ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow + ctx + .getRequest() + .getBody() + .map(TypedData::getText) + .next(this::sink) //$hasTaintFlow + .then(this::sink); //$hasTaintFlow } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java new file mode 100644 index 000000000000..3f2cde5371b9 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.form; + +import ratpack.core.http.TypedData; +import ratpack.func.Nullable; + +/** + * A file that was uploaded via a form. + * + * @see Form + */ +public interface UploadedFile extends TypedData { + + /** + * The name given for the file. + * + * @return The name given for the file, or {@code null} if no name was provided. + */ + @Nullable + String getFileName(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 206a247844a8..1a17a364bf40 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -46,4 +46,6 @@ public interface Promise { Promise map(Function transformer); void then(Action then); + + Promise next(Action action); } From b2ad128beb5fb29afc6f433554415e24e9685e92 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 30 Apr 2021 17:09:51 -0400 Subject: [PATCH 04/29] Refactors Ratpack lambda taint tracking to use generic API --- .../java/frameworks/ratpack/RatpackExec.qll | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index 453e75743c83..f309fee906b1 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -9,68 +9,74 @@ class RatpackPromise extends RefType { } } -/** - * Taint flows from the qualifier to the first argument of the lambda passed to this method access. - * Eg. `tainted.map(stillTainted -> ..)` - */ -abstract private class TaintFromQualifierToFunctionalArgumentMethodAccess extends MethodAccess { } +abstract private class SimpleFluentLambdaMethod extends Method { + SimpleFluentLambdaMethod() { getNumberOfParameters() = 1 } -class RatpackPromiseMapMethod extends Method { + /** + * Holds if this lambda consumes taint from the quaifier when `arg` is tainted. + * Eg. `tainted.map(stillTainted -> ..)` + */ + abstract predicate consumesTaint(int arg); + + /** + * Holds if the lambda passed produces taint that taints the result of this method. + * Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());` + */ + predicate doesReturnTaint() { none() } +} + +class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and hasName("map") } -} -class RatpackPromiseMapMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { - RatpackPromiseMapMethodAccess() { getMethod() instanceof RatpackPromiseMapMethod } + override predicate consumesTaint(int arg) { arg = 0 } + + override predicate doesReturnTaint() { any() } } -class RatpackPromiseThenMethod extends Method { +class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { RatpackPromiseThenMethod() { getDeclaringType() instanceof RatpackPromise and hasName("then") } -} -class RatpackPromiseThenMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { - RatpackPromiseThenMethodAccess() { getMethod() instanceof RatpackPromiseThenMethod } + override predicate consumesTaint(int arg) { arg = 0 } } -class RatpackPromiseNextMethod extends FluentMethod { +class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod { RatpackPromiseNextMethod() { getDeclaringType() instanceof RatpackPromise and hasName("next") } -} -class RatpackPromiseNextMethodAccess extends TaintFromQualifierToFunctionalArgumentMethodAccess { - RatpackPromiseNextMethodAccess() { getMethod() instanceof RatpackPromiseNextMethod } + override predicate consumesTaint(int arg) { arg = 0 } } private class RatpackPromiseTaintPreservingCallable extends AdditionalTaintStep { override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - stepFromFunctionalExpToPromise(node1, node2) or - stepFromPromiseToFunctionalArgument(node1, node2) + stepIntoLambda(node1, node2) or + stepOutOfLambda(node1, node2) } - /** - * Tracks taint from return from lambda function to the outer `Promise`. - */ - private predicate stepFromFunctionalExpToPromise(DataFlow::Node node1, DataFlow::Node node2) { - exists(FunctionalExpr fe | - fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and - node2.asExpr().(RatpackPromiseMapMethodAccess).getArgument(0) = fe + predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | + ma.getMethod() = sflm and + sflm.consumesTaint(arg) and + node1.asExpr() = ma.getQualifier() and + ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(arg) = node2.asParameter() ) } - /** - * Tracks taint from the previous `Promise` to the first argument of lambda passed to `map` or `then`. - */ - private predicate stepFromPromiseToFunctionalArgument(DataFlow::Node node1, DataFlow::Node node2) { - exists(TaintFromQualifierToFunctionalArgumentMethodAccess ma | - node1.asExpr() = ma.getQualifier() and - ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(0) = node2.asParameter() + predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { + exists(SimpleFluentLambdaMethod sflm, MethodAccess ma, FunctionalExpr fe | + sflm.doesReturnTaint() + | + fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and + ma.getMethod() = sflm and + node2.asExpr() = ma and + ma.getArgument(0) = fe ) } } From b2e3df29b3aeb51e2f523a0e9bcb88a9696e5d69 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 30 Apr 2021 17:48:41 -0400 Subject: [PATCH 05/29] Add support for Promise.value and Promise::flatMap --- .../java/frameworks/ratpack/RatpackExec.qll | 28 +++++++++++++------ .../ratpack/resources/Resource.java | 15 ++++++++++ .../ratpack-1.9.x/ratpack/exec/Promise.java | 6 ++++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index f309fee906b1..f79366ffb04b 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -3,12 +3,18 @@ private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.dataflow.FlowSteps /** A reference type that extends a parameterization the Promise type. */ -class RatpackPromise extends RefType { +private class RatpackPromise extends RefType { RatpackPromise() { getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") } } +private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable { + RatpackPromiseValueMethod() { isStatic() and hasName("value") } + + override predicate returnsTaintFrom(int arg) { arg = 0 } +} + abstract private class SimpleFluentLambdaMethod extends Method { SimpleFluentLambdaMethod() { getNumberOfParameters() = 1 } @@ -25,10 +31,10 @@ abstract private class SimpleFluentLambdaMethod extends Method { predicate doesReturnTaint() { none() } } -class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { +private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and - hasName("map") + hasName(["map", "flatMap"]) } override predicate consumesTaint(int arg) { arg = 0 } @@ -36,7 +42,7 @@ class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { override predicate doesReturnTaint() { any() } } -class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { +private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { RatpackPromiseThenMethod() { getDeclaringType() instanceof RatpackPromise and hasName("then") @@ -45,7 +51,7 @@ class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { override predicate consumesTaint(int arg) { arg = 0 } } -class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod { +private class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod { RatpackPromiseNextMethod() { getDeclaringType() instanceof RatpackPromise and hasName("next") @@ -54,21 +60,27 @@ class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod { override predicate consumesTaint(int arg) { arg = 0 } } -private class RatpackPromiseTaintPreservingCallable extends AdditionalTaintStep { +private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { override predicate step(DataFlow::Node node1, DataFlow::Node node2) { stepIntoLambda(node1, node2) or stepOutOfLambda(node1, node2) } + /** + * Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`. + */ predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | + exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | sflm.consumesTaint(arg) | ma.getMethod() = sflm and - sflm.consumesTaint(arg) and node1.asExpr() = ma.getQualifier() and ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(arg) = node2.asParameter() ) } + /** + * Holds if the return statement result of the functional expression `node1` has dataflow to the + * method access result `node2`. + */ predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { exists(SimpleFluentLambdaMethod sflm, MethodAccess ma, FunctionalExpr fe | sflm.doesReturnTaint() diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 6e1970e0606e..6b8da23afab8 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -1,12 +1,17 @@ import ratpack.core.handling.Context; import ratpack.core.http.TypedData; import ratpack.core.form.UploadedFile; +import ratpack.exec.Promise; import java.io.OutputStream; class Resource { void sink(Object o) {} + String taint() { + return null; + } + void test1(Context ctx) { sink(ctx.getRequest().getContentLength()); //$hasTaintFlow sink(ctx.getRequest().getCookies()); //$hasTaintFlow @@ -53,4 +58,14 @@ void test5(Context ctx) { .next(this::sink) //$hasTaintFlow .then(this::sink); //$hasTaintFlow } + + void test6() { + String tainted = taint(); + Promise.value(tainted); + sink(Promise.value(tainted)); //$hasTaintFlow + Promise + .value(tainted) + .flatMap(a -> Promise.value(a)) + .then(this::sink); //$hasTaintFlow + } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 1a17a364bf40..fb9c32467a1a 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -43,8 +43,14 @@ */ @SuppressWarnings("JavadocReference") public interface Promise { + static Promise value(T t) { + return null; + } + Promise map(Function transformer); + Promise flatMap(Function> transformer); + void then(Action then); Promise next(Action action); From 18c74c5030c5695645e71566d0b2c895f8818675 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 30 Apr 2021 17:57:42 -0400 Subject: [PATCH 06/29] Simplify Ratpack API using standard abstract classes --- .../code/java/frameworks/ratpack/Ratpack.qll | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index 5d2537d25530..ec0e078927fe 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -91,23 +91,11 @@ class RatpackHeader extends RefType { } } -private class RatpackHeaderTaintPropigatingMethod extends Method { +private class RatpackHeaderTaintPropigatingMethod extends Method, TaintPreservingCallable { RatpackHeaderTaintPropigatingMethod() { getDeclaringType() instanceof RatpackHeader and hasName(["get", "getAll", "getNames", "asMultiValueMap"]) } -} - -class TaintPropigatingHeaderMethod extends AdditionalTaintStep { - override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - stepHeaderPropigatingTaint(node1, node2) - } - private predicate stepHeaderPropigatingTaint(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma | - ma.getMethod() instanceof RatpackHeaderTaintPropigatingMethod and - node2.asExpr() = ma and - node1.asExpr() = ma.getQualifier() - ) - } + override predicate returnsTaintFrom(int arg) { arg = -1 } } From 4f658df0ac0dd49c9e57c84f2c908fe6591966de Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 3 May 2021 13:02:16 -0400 Subject: [PATCH 07/29] Apply suggestions from code review Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com> --- java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll | 2 +- java/ql/test/library-tests/frameworks/ratpack/flow.ql | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index ec0e078927fe..500eb7adc7d9 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -91,7 +91,7 @@ class RatpackHeader extends RefType { } } -private class RatpackHeaderTaintPropigatingMethod extends Method, TaintPreservingCallable { +private class RatpackHeaderTaintPropagatingMethod extends Method, TaintPreservingCallable { RatpackHeaderTaintPropigatingMethod() { getDeclaringType() instanceof RatpackHeader and hasName(["get", "getAll", "getNames", "asMultiValueMap"]) diff --git a/java/ql/test/library-tests/frameworks/ratpack/flow.ql b/java/ql/test/library-tests/frameworks/ratpack/flow.ql index 1743a0412e65..a7f6f9aa4601 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/flow.ql +++ b/java/ql/test/library-tests/frameworks/ratpack/flow.ql @@ -1,8 +1,6 @@ import java import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.FlowSources -import semmle.code.java.security.XSS -import semmle.code.java.security.UrlRedirect import TestUtilities.InlineExpectationsTest class Conf extends TaintTracking::Configuration { From 563e5690df992abd1ff5b571ccf519f81423d213 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 10 May 2021 14:04:20 -0400 Subject: [PATCH 08/29] Refactor Ratpack to use CSV format --- .../code/java/frameworks/ratpack/Ratpack.qll | 127 ++++++------------ .../ratpack/resources/Resource.java | 35 +++-- 2 files changed, 58 insertions(+), 104 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index 500eb7adc7d9..5a385e6b5da6 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -5,97 +5,52 @@ import java private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.dataflow.FlowSteps +private import semmle.code.java.dataflow.ExternalFlow /** * Ratpack methods that access user-supplied request data. */ abstract class RatpackGetRequestDataMethod extends Method { } -/** - * The interface `ratpack.http.Request`. - * https://ratpack.io/manual/current/api/ratpack/http/Request.html - */ -class RatpackRequest extends RefType { - RatpackRequest() { - hasQualifiedName("ratpack.http", "Request") or - hasQualifiedName("ratpack.core.http", "Request") - } -} - -/** - * Methods on `ratpack.http.Request` that return user tainted data. - */ -class RatpackHttpRequestGetMethod extends RatpackGetRequestDataMethod { - RatpackHttpRequestGetMethod() { - getDeclaringType() instanceof RatpackRequest and - hasName([ - "getContentLength", "getCookies", "oneCookie", "getHeaders", "getPath", "getQuery", - "getQueryParams", "getRawUri", "getUri" - ]) +private class RatpackHttpSource extends SourceModelCsv { + override predicate row(string row) { + row = + ["ratpack.http;", "ratpack.core.http;"] + + [ + "Request;true;getContentLength;;;ReturnValue;remote", + "Request;true;getCookies;;;ReturnValue;remote", + "Request;true;oneCookie;;;ReturnValue;remote", + "Request;true;getHeaders;;;ReturnValue;remote", + "Request;true;getPath;;;ReturnValue;remote", "Request;true;getQuery;;;ReturnValue;remote", + "Request;true;getQueryParams;;;ReturnValue;remote", + "Request;true;getRawUri;;;ReturnValue;remote", "Request;true;getUri;;;ReturnValue;remote", + "Request;true;getBody;;;ReturnValue;remote" + ] + } +} + +/** + * Ratpack methods that propagate user-supplied request data as tainted. + */ +private class RatpackHttpModel extends SummaryModelCsv { + override predicate row(string row) { + row = + ["ratpack.http;", "ratpack.core.http;"] + + [ + "TypedData;true;getBuffer;;;Argument[-1];ReturnValue;taint", + "TypedData;true;getBytes;;;Argument[-1];ReturnValue;taint", + "TypedData;true;getContentType;;;Argument[-1];ReturnValue;taint", + "TypedData;true;getInputStream;;;Argument[-1];ReturnValue;taint", + "TypedData;true;getText;;;Argument[-1];ReturnValue;taint", + "TypedData;true;writeTo;;;Argument[-1];Argument[0];taint", + "Headers;true;get;;;Argument[-1];ReturnValue;taint", + "Headers;true;getAll;;;Argument[-1];ReturnValue;taint", + "Headers;true;getNames;;;Argument[-1];ReturnValue;taint", + "Headers;true;asMultiValueMap;;;Argument[-1];ReturnValue;taint" + ] + or + row = + ["ratpack.form;", "ratpack.core.form;"] + + ["UploadedFile;true;getFileName;;;Argument[-1];ReturnValue;taint"] } } - -/** - * The interface `ratpack.http.TypedData`. - * https://ratpack.io/manual/current/api/ratpack/http/TypedData.html - */ -class RatpackTypedData extends RefType { - RatpackTypedData() { - hasQualifiedName("ratpack.http", "TypedData") or - hasQualifiedName("ratpack.core.http", "TypedData") - } -} - -/** - * Methods on `ratpack.http.TypedData` that return user tainted data. - */ -class RatpackHttpTypedDataGetMethod extends RatpackGetRequestDataMethod { - RatpackHttpTypedDataGetMethod() { - getDeclaringType() instanceof RatpackTypedData and - hasName(["getBuffer", "getBytes", "getContentType", "getInputStream", "getText"]) - } -} - -/** - * Methods on `ratpack.http.TypedData` that taint the parameter passed in. - */ -class RatpackHttpTypedDataWriteMethod extends Method { - RatpackHttpTypedDataWriteMethod() { - getDeclaringType() instanceof RatpackTypedData and - hasName("writeTo") - } -} - -/** - * The interface `ratpack.form.UploadedFile`. - * https://ratpack.io/manual/current/api/ratpack/form/UploadedFile.html - */ -class RatpackUploadFile extends RefType { - RatpackUploadFile() { - hasQualifiedName("ratpack.form", "UploadedFile") or - hasQualifiedName("ratpack.core.form", "UploadedFile") - } -} - -class RatpackUploadFileGetMethod extends RatpackGetRequestDataMethod { - RatpackUploadFileGetMethod() { - getDeclaringType() instanceof RatpackUploadFile and - hasName("getFileName") - } -} - -class RatpackHeader extends RefType { - RatpackHeader() { - hasQualifiedName("ratpack.http", "Headers") or - hasQualifiedName("ratpack.core.http", "Headers") - } -} - -private class RatpackHeaderTaintPropagatingMethod extends Method, TaintPreservingCallable { - RatpackHeaderTaintPropigatingMethod() { - getDeclaringType() instanceof RatpackHeader and - hasName(["get", "getAll", "getNames", "asMultiValueMap"]) - } - - override predicate returnsTaintFrom(int arg) { arg = -1 } -} diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 6b8da23afab8..0c65f56f9b5e 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -30,25 +30,24 @@ void test1(Context ctx) { sink(ctx.getRequest().getUri()); //$hasTaintFlow } - void test2(TypedData td) { - sink(td.getText()); //$hasTaintFlow - sink(td.getBuffer()); //$hasTaintFlow - sink(td.getBytes()); //$hasTaintFlow - sink(td.getContentType()); //$hasTaintFlow - sink(td.getInputStream()); //$hasTaintFlow + void test2(Context ctx, OutputStream os) { + ctx.getRequest().getBody().then(td -> { + sink(td.getText()); //$hasTaintFlow + sink(td.getBuffer()); //$hasTaintFlow + sink(td.getBytes()); //$hasTaintFlow + sink(td.getContentType()); //$hasTaintFlow + sink(td.getInputStream()); //$hasTaintFlow + sink(os); + td.writeTo(os); + sink(os); //$hasTaintFlow + if (td instanceof UploadedFile) { + UploadedFile uf = (UploadedFile) td; + sink(uf.getFileName()); //$hasTaintFlow + } + }); } - void test3(TypedData td, OutputStream os) throws java.io.IOException { - sink(os); - td.writeTo(os); - sink(os); //$hasTaintFlow - } - - void test4(UploadedFile uf) { - sink(uf.getFileName()); //$hasTaintFlow - } - - void test5(Context ctx) { + void test3(Context ctx) { sink(ctx.getRequest().getBody().map(TypedData::getText)); //$hasTaintFlow ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow ctx @@ -59,7 +58,7 @@ void test5(Context ctx) { .then(this::sink); //$hasTaintFlow } - void test6() { + void test4() { String tainted = taint(); Promise.value(tainted); sink(Promise.value(tainted)); //$hasTaintFlow From ac185d9bd52eab19964c59dc4648f99184163094 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 10 May 2021 14:05:23 -0400 Subject: [PATCH 09/29] Remove RatpackGetRequestDataMethod --- java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index 5a385e6b5da6..2f405f4bdd98 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -10,8 +10,6 @@ private import semmle.code.java.dataflow.ExternalFlow /** * Ratpack methods that access user-supplied request data. */ -abstract class RatpackGetRequestDataMethod extends Method { } - private class RatpackHttpSource extends SourceModelCsv { override predicate row(string row) { row = From a3b1736a734f0f7e0a7ce1da48937e59f26b47d9 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 10 May 2021 16:52:12 -0400 Subject: [PATCH 10/29] Ratpack improve support for parsing types --- .../code/java/frameworks/ratpack/Ratpack.qll | 31 ++++- .../ratpack/resources/Resource.java | 34 ++++++ .../ratpack-1.9.x/ratpack/core/form/Form.java | 108 ++++++++++++++++++ .../ratpack/core/form/FormParseOpts.java | 29 +++++ 4 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/FormParseOpts.java diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index 2f405f4bdd98..1cb0ce102184 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -24,13 +24,22 @@ private class RatpackHttpSource extends SourceModelCsv { "Request;true;getRawUri;;;ReturnValue;remote", "Request;true;getUri;;;ReturnValue;remote", "Request;true;getBody;;;ReturnValue;remote" ] + or + // All Context#parse methods that return a Promise are remote flow sources. + row = + ["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" + + [ + "(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,O);", + "(com.google.common.reflect.TypeToken,O);", "(ratpack.core.parse.Parse);", + "(ratpack.parse.Parse);" + ] + ";ReturnValue;remote" } } /** * Ratpack methods that propagate user-supplied request data as tainted. */ -private class RatpackHttpModel extends SummaryModelCsv { +private class RatpackModel extends SummaryModelCsv { override predicate row(string row) { row = ["ratpack.http;", "ratpack.core.http;"] + @@ -49,6 +58,24 @@ private class RatpackHttpModel extends SummaryModelCsv { or row = ["ratpack.form;", "ratpack.core.form;"] + - ["UploadedFile;true;getFileName;;;Argument[-1];ReturnValue;taint"] + [ + "UploadedFile;true;getFileName;;;Argument[-1];ReturnValue;taint", + "Form;true;file;;;Argument[-1];ReturnValue;taint", + "Form;true;files;;;Argument[-1];ReturnValue;taint" + ] + or + row = + ["ratpack.handling;", "ratpack.core.handling;"] + + [ + "Context;true;parse;(ratpack.http.TypedData,ratpack.parse.Parse);;Argument[0];ReturnValue;taint", + "Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];ReturnValue;taint" + ] + or + row = + ["ratpack.util;", "ratpack.func;"] + + [ + "MultiValueMap;true;getAll;;;Argument[-1];ReturnValue;taint", + "MultiValueMap;true;asMultimap;;;Argument[-1];ReturnValue;taint" + ] } } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 0c65f56f9b5e..f564c16cf05a 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -1,6 +1,8 @@ import ratpack.core.handling.Context; import ratpack.core.http.TypedData; +import ratpack.core.form.Form; import ratpack.core.form.UploadedFile; +import ratpack.core.parse.Parse; import ratpack.exec.Promise; import java.io.OutputStream; @@ -67,4 +69,36 @@ void test4() { .flatMap(a -> Promise.value(a)) .then(this::sink); //$hasTaintFlow } + + void test5(Context ctx) { + ctx + .getRequest() + .getBody() + .map(data -> { + Form form = ctx.parse(data, Form.form()); + sink(form); //$hasTaintFlow + return form; + }) + .then(form -> { + sink(form.file("questionable_file")); //$hasTaintFlow + sink(form.file("questionable_file").getFileName()); //$hasTaintFlow + sink(form.files("questionable_files")); //$hasTaintFlow + sink(form.files()); //$hasTaintFlow + sink(form.asMultimap()); //$hasTaintFlow + sink(form.asMultimap().asMap()); //$hasTaintFlow + }); + } + + void test6(Context ctx) { + ctx + .parse(Parse.of(Form.class)) + .then(form -> { + sink(form); //$hasTaintFlow + }); + ctx + .parse(Form.class) + .then(form -> { + sink(form); //$hasTaintFlow + }); + } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java new file mode 100644 index 000000000000..574391e0cf84 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.form; + +import ratpack.core.handling.Context; +import ratpack.core.parse.Parse; +import ratpack.func.Nullable; +import ratpack.func.MultiValueMap; + +import java.util.List; + +/** + * An uploaded form. + *

+ * The form is modelled as a {@link MultiValueMap}, with extra methods for dealing with file uploads. + * That is, uploaded files are not visible via the methods provided by {@link MultiValueMap}. + *

+ * All instances of this type are immutable. + * Calling any mutative method of {@link MultiValueMap} will result in an {@link UnsupportedOperationException}. + *

Example usage:

+ *
{@code
+ * import ratpack.core.handling.Handler;
+ * import ratpack.core.handling.Context;
+ * import ratpack.core.form.Form;
+ * import ratpack.core.form.UploadedFile;
+ *
+ * import java.util.List;
+ *
+ * public class Example {
+ *   public static class FormHandler implements Handler {
+ *     public void handle(Context context) throws Exception {
+ *       context.parse(Form.class).then(form -> {
+ *         UploadedFile file = form.file("someFile.txt");
+ *         String param = form.get("param");
+ *         List multi = form.getAll("multi");
+ *         context.render("form uploaded!");
+ *       });
+ *     }
+ *   }
+ * }
+ * }
+ * + *

+ * To include the query parameters from the request in the parsed form, use {@link Form#form(boolean)}. + * This can be useful if you want to support both {@code GET} and {@code PUT} submission with a single handler. + */ +public interface Form extends MultiValueMap { + + /** + * Return the first uploaded file with the given name. + * + * @param name The name of the uploaded file in the form + * @return The uploaded file, or {@code null} if no file was uploaded by that name + */ + @Nullable + UploadedFile file(String name); + + /** + * Return all of the uploaded files with the given name. + * + * @param name The name of the uploaded files in the form + * @return The uploaded files, or an empty list if no files were uploaded by that name + */ + List files(String name); + + /** + * Returns all of the uploaded files. + * + * @return all of the uploaded files. + */ + MultiValueMap files(); + + /** + * Creates a {@link Context#parse parseable object} to parse a request body into a {@link Form}. + *

+ * Default options will be used (no query parameters included). + * + * @return a parse object + */ + static Parse form() { + return null; + } + + /** + * Creates a {@link Context#parse parseable object} to parse a request body into a {@link Form}. + * + * @param includeQueryParams whether to include the query parameters from the request in the parsed form + * @return a parse object + */ + static Parse form(boolean includeQueryParams) { + return null; + } + +} \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/FormParseOpts.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/FormParseOpts.java new file mode 100644 index 000000000000..7a338d428c1d --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/FormParseOpts.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.core.form; + +/** + * Options for parsing a {@link Form}. + */ +public interface FormParseOpts { + /** + * Whether to include the query parameters from the request in the parsed form. + * + * @return whether to include the query parameters from the request in the parsed form + */ + boolean isIncludeQueryParams(); +} From cdfdcc66bd97e6cf35246ca06333601b5813f8ca Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 11 May 2021 10:58:33 -0400 Subject: [PATCH 11/29] Ratpack fix formatting and non-ascii characters --- java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt | 3 ++- .../ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/core/handling/Context.java | 4 ++-- .../stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java | 4 ++-- .../test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java | 2 +- .../ratpack-1.9.x/ratpack/core/http/MutableHeaders.java | 2 +- .../test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/core/http/Response.java | 2 +- .../test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java | 2 +- .../ratpack/core/render/NoSuchRendererException.java | 2 +- java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java | 2 +- .../ratpack/exec/registry/NotInRegistryException.java | 2 +- .../stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java | 2 +- .../ratpack/exec/stream/TransformablePublisher.java | 2 +- java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java | 2 +- java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java | 4 ++-- .../stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java | 2 +- .../ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java | 6 +++--- 21 files changed, 27 insertions(+), 26 deletions(-) diff --git a/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt b/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt index 6b26ad45235b..e0908d496f62 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt +++ b/java/ql/test/stubs/ratpack-1.9.x/LICENSE.txt @@ -11,4 +11,5 @@ * See the License for the specific language governing permissions and * limitations under the License. * - */ \ No newline at end of file + */ + \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java index 574391e0cf84..dd4c14991b34 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/Form.java @@ -105,4 +105,4 @@ static Parse form(boolean includeQueryParams) { return null; } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java index 3f2cde5371b9..ab0c7b8facd4 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/form/UploadedFile.java @@ -34,4 +34,4 @@ public interface UploadedFile extends TypedData { @Nullable String getFileName(); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java index 457ed317060b..39fa9929b468 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Context.java @@ -304,7 +304,7 @@ default void lastModified(Date lastModified, Runnable serve) { /** * Parse the request into the given type, using no options (or more specifically an instance of {@link NullParseOpts} as the options). *

- * The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant… + * The code sample is functionally identical to the sample given for the {@link #parse(Parse)} variant *

{@code
    * import ratpack.core.handling.Handler;
    * import ratpack.core.handling.Context;
@@ -469,4 +469,4 @@ default void notFound() {
     clientError(404);
   }
 
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
index 01da4ddbb83d..6239a06fdc28 100644
--- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
+++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
@@ -27,7 +27,7 @@
  * 

Non blocking/Asynchronous

*

* Handlers are expected to be asynchronous. - * That is, there is no expectation that the handler is “finished” when its {@link #handle(Context)} method returns. + * That is, there is no expectation that the handler is “finished" when its {@link #handle(Context)} method returns. * This facilitates the use of non blocking IO without needing to enter some kind of special mode. * An implication is that handlers must ensure that they either send a response or delegate to another handler. *

@@ -125,4 +125,4 @@ public interface Handler { */ void handle(Context ctx) throws Exception; -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java index f98e8b4095e0..7a5eb2f4072d 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Headers.java @@ -129,4 +129,4 @@ default Instant getInstant(CharSequence name) { MultiValueMap asMultiValueMap(); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java index 4e664e36766b..0c8427bd56c1 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MediaType.java @@ -126,4 +126,4 @@ public interface MediaType { * @return True if this represents the absence of a value (i.e. no Content-Type header) */ boolean isEmpty(); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java index e0696b783e59..c6dd20c5439a 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/MutableHeaders.java @@ -105,4 +105,4 @@ default MutableHeaders setDate(CharSequence name, Instant value) { MutableHeaders copy(Headers headers); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java index 724a85c195f2..6df96b963e43 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Request.java @@ -358,4 +358,4 @@ public interface Request { */ long getMaxContentLength(); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java index 90e635321df2..c5a13ab5a497 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/http/Response.java @@ -207,4 +207,4 @@ default Response contentTypeIfNotSet(CharSequence contentType) { default Response forceCloseConnection() { return null; } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java index a61318cd2cd8..e88a0c7c1e70 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/parse/Parse.java @@ -103,4 +103,4 @@ public static Parse of(Class type, O opts) { return null; } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java index efc5d26ebfcc..3970860711c5 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/render/NoSuchRendererException.java @@ -32,4 +32,4 @@ public NoSuchRendererException(Object object) { super("No renderer for object '" + object + "' of type '" + object.getClass() + "'"); } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index fb9c32467a1a..661a9ce67e1d 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -23,7 +23,7 @@ * A promise for a single value. *

* A promise is a representation of a value which will become available later. - * Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of “operations” to be specified, + * Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of “operations" to be specified, * that the value will travel through as it becomes available. * Such operations are implemented via the {@link #transform(Function)} method. * Each operation returns a new promise object, not the original promise object. diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java index 6338881a9312..8c77398f144c 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/api/NonBlocking.java @@ -37,4 +37,4 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.PARAMETER}) public @interface NonBlocking { -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java index 7391a68f81c5..9e6ec9583f5d 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/NotInRegistryException.java @@ -44,4 +44,4 @@ public NotInRegistryException(TypeToken type) { public NotInRegistryException(String message) { super(message); } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java index 6c0ad85970bc..b372f792083a 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/registry/Registry.java @@ -173,7 +173,7 @@ default Iterable getAll(Class type) { * Creates a new registry by joining {@code this} registry with the given registry *

* The returned registry is effectively the union of the two registries, with the {@code child} registry taking precedence. - * This means that child entries are effectively “returned first”. + * This means that child entries are effectively "returned first". *

{@code
    * import ratpack.exec.registry.Registry;
    *
diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java
index 488b8b07dd7f..9442fcabd7dc 100644
--- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java
+++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/stream/TransformablePublisher.java
@@ -26,4 +26,4 @@
  * @param  the type of item emitted by this publisher
  */
 public interface TransformablePublisher {
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java
index 6e420e4c3727..cc1c67fa07af 100644
--- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java
+++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Action.java
@@ -263,4 +263,4 @@ static  Action when(Predicate predicate, Action onTr
   interface ConditionalSpec {
     ConditionalSpec when(Predicate predicate, Action action);
   }
-}
\ No newline at end of file
+}
diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java
index 86b0f6612a86..eab01ada8fdc 100644
--- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java
+++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Function.java
@@ -174,7 +174,7 @@ interface ConditionalSpec {
    * Creates a function that delegates based on the specified conditions.
    * 

* If no conditions match, an {@link IllegalArgumentException} will be thrown. - * Use {@link #conditional(Function, Action)} alternatively to specify a different “else” strategy. + * Use {@link #conditional(Function, Action)} alternatively to specify a different "else" strategy. * * @param conditions the conditions * @param the input type @@ -205,4 +205,4 @@ static Function conditional(Action> c static Function conditional(Function onElse, Action> conditions) throws Exception { return null; } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java index 5ed83ef5438b..7846313291cf 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/MultiValueMap.java @@ -98,4 +98,4 @@ static MultiValueMap empty() { ListMultimap asMultimap(); -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java index 050b07620036..96ac048aaecd 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Predicate.java @@ -22,7 +22,7 @@ * This type serves the same purpose as the JDK's {@link java.util.function.Predicate}, but allows throwing checked exceptions. * It contains methods for bridging to and from the JDK type. * - * @param the type of object “tested” by the predicate + * @param the type of object "tested" by the predicate */ @FunctionalInterface public interface Predicate { @@ -30,7 +30,7 @@ public interface Predicate { /** * Tests the given value. * - * @param t the value to “test” + * @param t the value to "test" * @return {@code true} if the predicate applied, otherwise {@code false} * @throws Exception any */ @@ -93,4 +93,4 @@ default Function function(O onTrue, O onFalse) { return null; } -} \ No newline at end of file +} From b9dc3d0cfebb169175f2d5dbacc3b2806967b1bf Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 11 May 2021 17:39:29 -0400 Subject: [PATCH 12/29] Ratpack: Better support for Promise API --- .../code/java/frameworks/ratpack/Ratpack.qll | 4 +- .../java/frameworks/ratpack/RatpackExec.qll | 111 ++++++++++++++---- .../ratpack/resources/Resource.java | 88 ++++++++++++++ .../ratpack-1.9.x/ratpack/exec/Promise.java | 59 ++++++++++ .../ratpack-1.9.x/ratpack/func/Factory.java | 50 ++++++++ 5 files changed, 288 insertions(+), 24 deletions(-) create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Factory.java diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll index 1cb0ce102184..b19c612a777e 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -29,8 +29,8 @@ private class RatpackHttpSource extends SourceModelCsv { row = ["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" + [ - "(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,O);", - "(com.google.common.reflect.TypeToken,O);", "(ratpack.core.parse.Parse);", + "(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,java.lang.Object);", + "(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);", "(ratpack.parse.Parse);" ] + ";ReturnValue;remote" } diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index f79366ffb04b..258ef845a705 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -15,31 +15,82 @@ private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable override predicate returnsTaintFrom(int arg) { arg = 0 } } -abstract private class SimpleFluentLambdaMethod extends Method { - SimpleFluentLambdaMethod() { getNumberOfParameters() = 1 } - +abstract private class FluentLambdaMethod extends Method { /** - * Holds if this lambda consumes taint from the quaifier when `arg` is tainted. + * Holds if this lambda consumes taint from the quaifier when `lambdaArg` + * for `methodArg` is tainted. * Eg. `tainted.map(stillTainted -> ..)` */ - abstract predicate consumesTaint(int arg); + abstract predicate consumesTaint(int methodArg, int lambdaArg); /** - * Holds if the lambda passed produces taint that taints the result of this method. + * Holds if the lambda passed at the given `arg` position produces taint + * that taints the result of this method. * Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());` */ - predicate doesReturnTaint() { none() } + predicate doesReturnTaint(int arg) { none() } +} + +private class RatpackPromiseProviderethod extends Method, FluentLambdaMethod { + RatpackPromiseProviderethod() { isStatic() and hasName(["flatten", "sync"]) } + + override predicate consumesTaint(int methodArg, int lambdaArg) { none() } + + override predicate doesReturnTaint(int arg) { arg = 0 } +} + +abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod { + override predicate consumesTaint(int methodArg, int lambdaArg) { + methodArg = 0 and consumesTaint(lambdaArg) + } + + /** + * Holds if this lambda consumes taint from the quaifier when `arg` is tainted. + * Eg. `tainted.map(stillTainted -> ..)` + */ + abstract predicate consumesTaint(int lambdaArg); } private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["map", "flatMap"]) + hasName(["map", "flatMap", "blockingMap"]) } - override predicate consumesTaint(int arg) { arg = 0 } + override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } - override predicate doesReturnTaint() { any() } + override predicate doesReturnTaint(int arg) { arg = 0 } +} + +/** + * Represents the `mapIf` and `flatMapIf` method. + * + * ` Promise mapIf(Predicate predicate, Function onTrue, Function onFalse)` + * ` Promise flatMapIf(Predicate predicate, Function> onTrue, Function> onFalse)` + */ +private class RatpackPromiseMapIfMethod extends FluentLambdaMethod { + RatpackPromiseMapIfMethod() { + getDeclaringType() instanceof RatpackPromise and + hasName(["mapIf", "flatMapIf"]) and + getNumberOfParameters() = 3 + } + + override predicate consumesTaint(int methodArg, int lambdaArg) { + methodArg = [1, 2, 3] and lambdaArg = 0 + } + + override predicate doesReturnTaint(int arg) { arg = [1, 2] } +} + +private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod { + RatpackPromiseMapErrorMethod() { + getDeclaringType() instanceof RatpackPromise and + hasName(["mapError", "flatMapError"]) + } + + override predicate consumesTaint(int methodArg, int lambdaArg) { none() } + + override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 } } private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { @@ -48,16 +99,29 @@ private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { hasName("then") } - override predicate consumesTaint(int arg) { arg = 0 } + override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } } -private class RatpackPromiseNextMethod extends FluentMethod, SimpleFluentLambdaMethod { - RatpackPromiseNextMethod() { +private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod { + RatpackPromiseFluentMethod() { getDeclaringType() instanceof RatpackPromise and - hasName("next") + not isStatic() and + exists(ParameterizedType t | + t instanceof RatpackPromise and + t = getDeclaringType() and + t = getReturnType() + ) } - override predicate consumesTaint(int arg) { arg = 0 } + override predicate consumesTaint(int methodArg, int lambdaArg) { + hasName(["next"]) and methodArg = 0 and lambdaArg = 0 + or + hasName(["cacheIf"]) and methodArg = 0 and lambdaArg = 0 + or + hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0 + } + + override predicate doesReturnTaint(int arg) { hasName(["flatMapIf"]) and arg = 1 } } private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { @@ -70,10 +134,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { * Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`. */ predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, SimpleFluentLambdaMethod sflm, int arg | sflm.consumesTaint(arg) | - ma.getMethod() = sflm and + exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg | + flm.consumesTaint(methodArg, lambdaArg) + | + ma.getMethod() = flm and node1.asExpr() = ma.getQualifier() and - ma.getArgument(0).(FunctionalExpr).asMethod().getParameter(arg) = node2.asParameter() + ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) = + node2.asParameter() ) } @@ -82,13 +149,13 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { * method access result `node2`. */ predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(SimpleFluentLambdaMethod sflm, MethodAccess ma, FunctionalExpr fe | - sflm.doesReturnTaint() + exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg | + flm.doesReturnTaint(arg) | fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and - ma.getMethod() = sflm and + ma.getMethod() = flm and node2.asExpr() = ma and - ma.getArgument(0) = fe + ma.getArgument(arg) = fe ) } } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index f564c16cf05a..06c39c5c329f 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -4,6 +4,7 @@ import ratpack.core.form.UploadedFile; import ratpack.core.parse.Parse; import ratpack.exec.Promise; +import ratpack.func.Action; import java.io.OutputStream; class Resource { @@ -100,5 +101,92 @@ void test6(Context ctx) { .then(form -> { sink(form); //$hasTaintFlow }); + ctx + .parse(Form.class, "Some Object") + .then(form -> { + sink(form); //$hasTaintFlow + }); + } + + void test7() { + String tainted = taint(); + Promise + .flatten(() -> Promise.value(tainted)) + .next(value -> { + sink(value); //$hasTaintFlow + }) + .onError(Action.noop()) + .next(value -> { + sink(value); //$hasTaintFlow + }) + .cache() + .next(value -> { + sink(value); //$hasTaintFlow + }) + .fork() + .next(value -> { + sink(value); //$hasTaintFlow + }) + .route(value -> { + sink(value); //$hasTaintFlow + return false; + }, value -> { + sink(value); //$hasTaintFlow + }) + .next(value -> { + sink(value); //$hasTaintFlow + }) + .cacheIf(value -> { + sink(value); //$hasTaintFlow + return true; + }) + .next(value -> { + sink(value); //$hasTaintFlow + }) + .onError(RuntimeException.class, Action.noop()) + .next(value -> { + sink(value); //$hasTaintFlow + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + } + + void test8() { + String tainted = taint(); + Promise + .sync(() -> tainted) + .mapError(RuntimeException.class, exception -> { + sink(exception); // no taint + return "potato"; + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .value("potato") + .mapError(RuntimeException.class, exception -> { + return taint(); + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .value(tainted) + .flatMapError(RuntimeException.class, exception -> { + sink(exception); // no taint + return Promise.value("potato"); + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .value("potato") + .flatMapError(RuntimeException.class, exception -> { + return Promise.value(taint()); + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 661a9ce67e1d..75fb8e3457d5 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -17,7 +17,9 @@ package ratpack.exec; import ratpack.func.Action; +import ratpack.func.Factory; import ratpack.func.Function; +import ratpack.func.Predicate; /** * A promise for a single value. @@ -43,6 +45,15 @@ */ @SuppressWarnings("JavadocReference") public interface Promise { + + static Promise sync(Factory factory) { + return null; + } + + static Promise flatten(Factory> factory) { + return null; + } + static Promise value(T t) { return null; } @@ -54,4 +65,52 @@ static Promise value(T t) { void then(Action then); Promise next(Action action); + + default Promise onError(Class errorType, Action errorHandler) { + return null; + } + + default Promise onError(Action errorHandler) { + return null; + } + + default Promise mapError(Function transformer) { + return null; + } + + default Promise mapError(Class type, Function function) { + return null; + } + + default Promise mapError(Predicate predicate, Function function) { + return null; + } + + default Promise flatMapError(Function> function) { + return null; + } + + default Promise flatMapError(Class type, Function> function) { + return null; + } + + default Promise flatMapError(Predicate predicate, Function> function) { + return null; + } + + default Promise route(Predicate predicate, Action action) { + return null; + } + + default Promise cache() { + return null; + } + + default Promise cacheIf(Predicate shouldCache) { + return null; + } + + default Promise fork() { + return null; + } } diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Factory.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Factory.java new file mode 100644 index 000000000000..fb86f767ddf6 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/func/Factory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.func; + +/** + * An object that creates another. + * + * Factories are expected to create a new object each time. + * Implementors should explain there behaviour if they do not do this. + * + * @param the type of object that this factory creates + */ +@FunctionalInterface +public interface Factory { + + /** + * Creates a new object. + * + * @return a newly created object + * @throws Exception any + */ + T create() throws Exception; + + /** + * Creates a factory that always returns the given item. + * + * @param item the item to always provide + * @param the type of the item + * @return a factory that always returns {@code item} + * @since 1.1 + */ + static Factory constant(T item) { + return () -> item; + } + +} From 901631ceb837e2ec56149d258c5b81b8fe9fb2e8 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 11 May 2021 22:22:25 -0400 Subject: [PATCH 13/29] Ratpack Promise add support for `apply` method --- .../java/frameworks/ratpack/RatpackExec.qll | 2 +- .../ratpack/resources/Resource.java | 20 +++++++++++++++++++ .../ratpack-1.9.x/ratpack/exec/Promise.java | 4 ++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index 258ef845a705..a4e39ca69e2a 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -54,7 +54,7 @@ abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod { private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["map", "flatMap", "blockingMap"]) + hasName(["map", "flatMap", "blockingMap", "apply"]) } override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 06c39c5c329f..9788926be5b6 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -189,4 +189,24 @@ void test8() { sink(value); //$hasTaintFlow }); } + + void test9() { + String tainted = taint(); + Promise + .value(tainted) + .apply(Resource::identity) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .value("potato") + .apply(Resource::identity) + .then(value -> { + sink(value); // no taints flow + }); + } + + public static Promise identity(Promise input) { + return input.map(i -> i); + } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 75fb8e3457d5..3165bffcd6d3 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -113,4 +113,8 @@ default Promise cacheIf(Predicate shouldCache) { default Promise fork() { return null; } + + default Promise apply(Function, ? extends Promise> function) { + return null; + } } From af90b00e63bcb2e9299f23ba7b7ed1dfeb025495 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 11 May 2021 22:29:12 -0400 Subject: [PATCH 14/29] Ratpack: Release note and typo fix --- java/change-notes/2021-05-11-ratpack-support.md | 2 ++ .../src/semmle/code/java/frameworks/ratpack/RatpackExec.qll | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 java/change-notes/2021-05-11-ratpack-support.md diff --git a/java/change-notes/2021-05-11-ratpack-support.md b/java/change-notes/2021-05-11-ratpack-support.md new file mode 100644 index 000000000000..f490643d62da --- /dev/null +++ b/java/change-notes/2021-05-11-ratpack-support.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Add support for [Ratpack](https://ratpack.io/) HTTP framework. diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index a4e39ca69e2a..fb92b671ff7d 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -31,8 +31,8 @@ abstract private class FluentLambdaMethod extends Method { predicate doesReturnTaint(int arg) { none() } } -private class RatpackPromiseProviderethod extends Method, FluentLambdaMethod { - RatpackPromiseProviderethod() { isStatic() and hasName(["flatten", "sync"]) } +private class RatpackPromiseProviderMethod extends Method, FluentLambdaMethod { + RatpackPromiseProviderMethod() { isStatic() and hasName(["flatten", "sync"]) } override predicate consumesTaint(int methodArg, int lambdaArg) { none() } From 6497a61c1d0a2fbbc8fda6c98db0d2cee85fd500 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 18 May 2021 12:33:32 -0400 Subject: [PATCH 15/29] Ratpack: Drop support for `flatMap` like methods --- .../java/frameworks/ratpack/RatpackExec.qll | 69 +++++++++---------- .../ratpack/resources/Resource.java | 43 ++++++++---- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll index fb92b671ff7d..ba807c36d94d 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -54,7 +54,7 @@ abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod { private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["map", "flatMap", "blockingMap", "apply"]) + hasName(["map", "blockingMap"]) // "flatMap" & "apply" cause false positives. Wait for fluent lambda support. } override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } @@ -63,15 +63,14 @@ private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { } /** - * Represents the `mapIf` and `flatMapIf` method. + * Represents the `mapIf` method. * * ` Promise mapIf(Predicate predicate, Function onTrue, Function onFalse)` - * ` Promise flatMapIf(Predicate predicate, Function> onTrue, Function> onFalse)` */ private class RatpackPromiseMapIfMethod extends FluentLambdaMethod { RatpackPromiseMapIfMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["mapIf", "flatMapIf"]) and + hasName(["mapIf"]) and // "flatMapIf" causes false positives. Wait for fluent lambda support. getNumberOfParameters() = 3 } @@ -85,7 +84,7 @@ private class RatpackPromiseMapIfMethod extends FluentLambdaMethod { private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod { RatpackPromiseMapErrorMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["mapError", "flatMapError"]) + hasName(["mapError"]) // "flatMapError" causes false positives. Wait for fluent lambda support. } override predicate consumesTaint(int methodArg, int lambdaArg) { none() } @@ -121,7 +120,36 @@ private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMetho hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0 } - override predicate doesReturnTaint(int arg) { hasName(["flatMapIf"]) and arg = 1 } + override predicate doesReturnTaint(int arg) { none() } // "flatMapIf" causes false positives. Wait for fluent lambda support. +} + +/** + * Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`. + */ +private predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { + exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg | + flm.consumesTaint(methodArg, lambdaArg) + | + ma.getMethod() = flm and + node1.asExpr() = ma.getQualifier() and + ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) = + node2.asParameter() + ) +} + +/** + * Holds if the return statement result of the functional expression `node1` has dataflow to the + * method access result `node2`. + */ +private predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { + exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg | + flm.doesReturnTaint(arg) + | + fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and + ma.getMethod() = flm and + node2.asExpr() = ma and + ma.getArgument(arg) = fe + ) } private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { @@ -129,33 +157,4 @@ private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { stepIntoLambda(node1, node2) or stepOutOfLambda(node1, node2) } - - /** - * Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`. - */ - predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg | - flm.consumesTaint(methodArg, lambdaArg) - | - ma.getMethod() = flm and - node1.asExpr() = ma.getQualifier() and - ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) = - node2.asParameter() - ) - } - - /** - * Holds if the return statement result of the functional expression `node1` has dataflow to the - * method access result `node2`. - */ - predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg | - flm.doesReturnTaint(arg) - | - fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and - ma.getMethod() = flm and - node2.asExpr() = ma and - ma.getArgument(arg) = fe - ) - } } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 9788926be5b6..fbcd9f679986 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -5,6 +5,7 @@ import ratpack.core.parse.Parse; import ratpack.exec.Promise; import ratpack.func.Action; +import ratpack.func.Function; import java.io.OutputStream; class Resource { @@ -67,7 +68,7 @@ void test4() { sink(Promise.value(tainted)); //$hasTaintFlow Promise .value(tainted) - .flatMap(a -> Promise.value(a)) + .map(a -> a) .then(this::sink); //$hasTaintFlow } @@ -180,33 +181,51 @@ void test8() { .then(value -> { sink(value); //$hasTaintFlow }); + // Waiting for support for lambda data flow + // Promise + // .value("potato") + // .flatMapError(RuntimeException.class, exception -> { + // return Promise.value(taint()); + // }) + // .then(value -> { + // sink(value); //$hasTaintFlow + // }); + } + + void test9() { + String tainted = taint(); Promise - .value("potato") - .flatMapError(RuntimeException.class, exception -> { - return Promise.value(taint()); - }) + .value(tainted) + .map(Resource::identity) .then(value -> { sink(value); //$hasTaintFlow }); + Promise + .value("potato") + .map(Resource::identity) + .then(value -> { + sink(value); // no taints flow + }); } - void test9() { + public static String identity(String input) { + return input; + } + + void test10() { String tainted = taint(); Promise .value(tainted) - .apply(Resource::identity) + .map(a -> a) .then(value -> { sink(value); //$hasTaintFlow }); Promise .value("potato") - .apply(Resource::identity) + .map(a -> a) .then(value -> { sink(value); // no taints flow }); } - - public static Promise identity(Promise input) { - return input.map(i -> i); - } + } \ No newline at end of file From 4f90f0a7483e338208c0b6bfc507fa62e819b2df Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 6 Oct 2021 13:54:51 +0200 Subject: [PATCH 16/29] Begin refactoring Ratpack to use functional taint tracking Signed-off-by: Jonathan Leitschuh --- .../code/java/dataflow/ExternalFlow.qll | 2 ++ .../code/java/frameworks/ratpack/Ratpack.qll | 7 ++-- .../java/frameworks/ratpack/RatpackExec.qll | 32 ++++++++++++++----- .../library-tests/frameworks/ratpack/options | 2 +- .../ratpack/resources/Resource.java | 7 ++++ 5 files changed, 38 insertions(+), 12 deletions(-) rename java/ql/{src => lib}/semmle/code/java/frameworks/ratpack/Ratpack.qll (93%) rename java/ql/{src => lib}/semmle/code/java/frameworks/ratpack/RatpackExec.qll (83%) diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll index cfbcd16f75e5..6c5d8ff67e5c 100644 --- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll @@ -95,6 +95,8 @@ private module Frameworks { private import semmle.code.java.frameworks.Optional private import semmle.code.java.frameworks.Stream private import semmle.code.java.frameworks.Strings + private import semmle.code.java.frameworks.ratpack.Ratpack + private import semmle.code.java.frameworks.ratpack.RatpackExec private import semmle.code.java.frameworks.spring.SpringCache private import semmle.code.java.frameworks.spring.SpringHttp private import semmle.code.java.frameworks.spring.SpringUtil diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll similarity index 93% rename from java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll rename to java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll index b19c612a777e..5de671bc4480 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -29,7 +29,8 @@ private class RatpackHttpSource extends SourceModelCsv { row = ["ratpack.handling;", "ratpack.core.handling;"] + "Context;true;parse;" + [ - "(java.lang.Class);", "(com.google.common.reflect.TypeToken);", "(java.lang.Class,java.lang.Object);", + "(java.lang.Class);", "(com.google.common.reflect.TypeToken);", + "(java.lang.Class,java.lang.Object);", "(com.google.common.reflect.TypeToken,java.lang.Object);", "(ratpack.core.parse.Parse);", "(ratpack.parse.Parse);" ] + ";ReturnValue;remote" @@ -74,8 +75,8 @@ private class RatpackModel extends SummaryModelCsv { row = ["ratpack.util;", "ratpack.func;"] + [ - "MultiValueMap;true;getAll;;;Argument[-1];ReturnValue;taint", - "MultiValueMap;true;asMultimap;;;Argument[-1];ReturnValue;taint" + "MultiValueMap;true;getAll;;;Element of Argument[-1];ReturnValue;value", + "MultiValueMap;true;asMultimap;;;Element of Argument[-1];ReturnValue;value" ] } } diff --git a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll similarity index 83% rename from java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll rename to java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll index ba807c36d94d..250d2efd9dd5 100644 --- a/java/ql/src/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -1,6 +1,7 @@ import java private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.dataflow.FlowSteps +private import semmle.code.java.dataflow.ExternalFlow /** A reference type that extends a parameterization the Promise type. */ private class RatpackPromise extends RefType { @@ -9,6 +10,21 @@ private class RatpackPromise extends RefType { } } +/** + * Ratpack methods that propagate user-supplied data as tainted. + */ +private class RatpackExecModel extends SummaryModelCsv { + override predicate row(string row) { + //"namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind", + row = [ + "ratpack.exec;Promise;true;map;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "ratpack.exec;Promise;true;map;;;ReturnValue of Argument[0];Element of ReturnValue;value", + "ratpack.exec;Promise;true;then;;;Element of Argument[-1];Parameter[0] of Argument[0];value" + ] + } +} + + private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable { RatpackPromiseValueMethod() { isStatic() and hasName("value") } @@ -54,7 +70,7 @@ abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod { private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { RatpackPromiseMapMethod() { getDeclaringType() instanceof RatpackPromise and - hasName(["map", "blockingMap"]) // "flatMap" & "apply" cause false positives. Wait for fluent lambda support. + hasName(["blockingMap"]) // "flatMap" & "apply" cause false positives. Wait for fluent lambda support. } override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } @@ -92,14 +108,14 @@ private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod { override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 } } -private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { - RatpackPromiseThenMethod() { - getDeclaringType() instanceof RatpackPromise and - hasName("then") - } +// private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { +// RatpackPromiseThenMethod() { +// getDeclaringType() instanceof RatpackPromise and +// hasName("then") +// } - override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } -} +// override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } +// } private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod { RatpackPromiseFluentMethod() { diff --git a/java/ql/test/library-tests/frameworks/ratpack/options b/java/ql/test/library-tests/frameworks/ratpack/options index 3263fdcf4150..109f07058870 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/options +++ b/java/ql/test/library-tests/frameworks/ratpack/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-databind-2.10:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-databind-2.12:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index fbcd9f679986..3c13845d19af 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -36,6 +36,7 @@ void test1(Context ctx) { void test2(Context ctx, OutputStream os) { ctx.getRequest().getBody().then(td -> { + sink(td); //$hasTaintFlow sink(td.getText()); //$hasTaintFlow sink(td.getBuffer()); //$hasTaintFlow sink(td.getBytes()); //$hasTaintFlow @@ -53,6 +54,12 @@ void test2(Context ctx, OutputStream os) { void test3(Context ctx) { sink(ctx.getRequest().getBody().map(TypedData::getText)); //$hasTaintFlow + Promise mapResult = ctx.getRequest().getBody().map(b -> { + sink(b); //$hasTaintFlow + sink(b.getText()); //$hasTaintFlow + return b.getText(); + }); + sink(mapResult); //$hasTaintFlow ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow ctx .getRequest() From 6562ac3680dcd440a8181158114bf3056cacffab Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 6 Oct 2021 17:57:14 +0200 Subject: [PATCH 17/29] Ratpack conversion to new lambda model Signed-off-by: Jonathan Leitschuh --- .../code/java/frameworks/ratpack/Ratpack.qll | 5 +- .../java/frameworks/ratpack/RatpackExec.qll | 193 ++++-------------- .../ratpack/resources/Resource.java | 82 ++++++-- .../ratpack-1.9.x/ratpack/exec/Promise.java | 8 + 4 files changed, 125 insertions(+), 163 deletions(-) diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll index 5de671bc4480..ca3d18a6679b 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -75,8 +75,9 @@ private class RatpackModel extends SummaryModelCsv { row = ["ratpack.util;", "ratpack.func;"] + [ - "MultiValueMap;true;getAll;;;Element of Argument[-1];ReturnValue;value", - "MultiValueMap;true;asMultimap;;;Element of Argument[-1];ReturnValue;value" + "MultiValueMap;true;getAll;;;MapKey of Argument[-1];MapKey of ReturnValue;value", + "MultiValueMap;true;getAll;;;MapValue of Argument[-1];Element of MapValue of ReturnValue;value", + "MultiValueMap;true;asMultimap;;;Element of Argument[-1];Element of ReturnValue;value" ] } } diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll index 250d2efd9dd5..c0ce43989516 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -3,174 +3,71 @@ private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.dataflow.FlowSteps private import semmle.code.java.dataflow.ExternalFlow -/** A reference type that extends a parameterization the Promise type. */ -private class RatpackPromise extends RefType { - RatpackPromise() { - getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") - } -} - -/** +/** * Ratpack methods that propagate user-supplied data as tainted. */ private class RatpackExecModel extends SummaryModelCsv { override predicate row(string row) { //"namespace;type;overrides;name;signature;ext;inputspec;outputspec;kind", - row = [ - "ratpack.exec;Promise;true;map;;;Element of Argument[-1];Parameter[0] of Argument[0];value", - "ratpack.exec;Promise;true;map;;;ReturnValue of Argument[0];Element of ReturnValue;value", - "ratpack.exec;Promise;true;then;;;Element of Argument[-1];Parameter[0] of Argument[0];value" - ] - } -} - - -private class RatpackPromiseValueMethod extends Method, TaintPreservingCallable { - RatpackPromiseValueMethod() { isStatic() and hasName("value") } - - override predicate returnsTaintFrom(int arg) { arg = 0 } -} - -abstract private class FluentLambdaMethod extends Method { - /** - * Holds if this lambda consumes taint from the quaifier when `lambdaArg` - * for `methodArg` is tainted. - * Eg. `tainted.map(stillTainted -> ..)` - */ - abstract predicate consumesTaint(int methodArg, int lambdaArg); - - /** - * Holds if the lambda passed at the given `arg` position produces taint - * that taints the result of this method. - * Eg. `var tainted = CompletableFuture.supplyAsync(() -> taint());` - */ - predicate doesReturnTaint(int arg) { none() } -} - -private class RatpackPromiseProviderMethod extends Method, FluentLambdaMethod { - RatpackPromiseProviderMethod() { isStatic() and hasName(["flatten", "sync"]) } - - override predicate consumesTaint(int methodArg, int lambdaArg) { none() } - - override predicate doesReturnTaint(int arg) { arg = 0 } -} - -abstract private class SimpleFluentLambdaMethod extends FluentLambdaMethod { - override predicate consumesTaint(int methodArg, int lambdaArg) { - methodArg = 0 and consumesTaint(lambdaArg) + row = + ["ratpack.exec;Promise;true;"] + + [ + // `Promise` creation methods + "value;;;Argument[0];Element of ReturnValue;value", + "flatten;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", + "sync;;;ReturnValue of Argument[0];Element of ReturnValue;value", + // `Promise` value transformation methods + "map;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "map;;;ReturnValue of Argument[0];Element of ReturnValue;value", + "blockingMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "blockingMap;;;ReturnValue of Argument[0];Element of ReturnValue;value", + "mapError;;;ReturnValue of Argument[0];Element of ReturnValue;value", + // `Promise` termination method + "then;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + // 'next' accesses qualfier the 'Promise' value and also returns the qualifier + "next;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "next;;;Argument[-1];ReturnValue;value", + // 'cacheIf' accesses qualfier the 'Promise' value and also returns the qualifier + "cacheIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "cacheIf;;;Argument[-1];ReturnValue;value", + // 'route' accesses qualfier the 'Promise' value, and conditionally returns the qualifier or + // the result of the second argument + "route;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "route;;;Element of Argument[-1];Parameter[0] of Argument[1];value", + "route;;;Argument[-1];ReturnValue;value", + // `flatMap` type methods return their returned `Promise` + "flatMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "flatMap;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", + "flatMapError;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", + // `mapIf` methods conditionally map their values, or return themselves + "mapIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value", + "mapIf;;;Element of Argument[-1];Parameter[0] of Argument[1];value", + "mapIf;;;Element of Argument[-1];Parameter[0] of Argument[2];value", + "mapIf;;;ReturnValue of Argument[1];Element of ReturnValue;value", + "mapIf;;;ReturnValue of Argument[2];Element of ReturnValue;value" + ] } - - /** - * Holds if this lambda consumes taint from the quaifier when `arg` is tainted. - * Eg. `tainted.map(stillTainted -> ..)` - */ - abstract predicate consumesTaint(int lambdaArg); } -private class RatpackPromiseMapMethod extends SimpleFluentLambdaMethod { - RatpackPromiseMapMethod() { - getDeclaringType() instanceof RatpackPromise and - hasName(["blockingMap"]) // "flatMap" & "apply" cause false positives. Wait for fluent lambda support. +/** A reference type that extends a parameterization the Promise type. */ +private class RatpackPromise extends RefType { + RatpackPromise() { + getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") } - - override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } - - override predicate doesReturnTaint(int arg) { arg = 0 } } /** - * Represents the `mapIf` method. - * - * ` Promise mapIf(Predicate predicate, Function onTrue, Function onFalse)` + * Ratpack `Promise` method that will return `this`. */ -private class RatpackPromiseMapIfMethod extends FluentLambdaMethod { - RatpackPromiseMapIfMethod() { - getDeclaringType() instanceof RatpackPromise and - hasName(["mapIf"]) and // "flatMapIf" causes false positives. Wait for fluent lambda support. - getNumberOfParameters() = 3 - } - - override predicate consumesTaint(int methodArg, int lambdaArg) { - methodArg = [1, 2, 3] and lambdaArg = 0 - } - - override predicate doesReturnTaint(int arg) { arg = [1, 2] } -} - -private class RatpackPromiseMapErrorMethod extends FluentLambdaMethod { - RatpackPromiseMapErrorMethod() { - getDeclaringType() instanceof RatpackPromise and - hasName(["mapError"]) // "flatMapError" causes false positives. Wait for fluent lambda support. - } - - override predicate consumesTaint(int methodArg, int lambdaArg) { none() } - - override predicate doesReturnTaint(int arg) { arg = getNumberOfParameters() - 1 } -} - -// private class RatpackPromiseThenMethod extends SimpleFluentLambdaMethod { -// RatpackPromiseThenMethod() { -// getDeclaringType() instanceof RatpackPromise and -// hasName("then") -// } - -// override predicate consumesTaint(int lambdaArg) { lambdaArg = 0 } -// } - -private class RatpackPromiseFluentMethod extends FluentMethod, FluentLambdaMethod { +private class RatpackPromiseFluentMethod extends FluentMethod { RatpackPromiseFluentMethod() { getDeclaringType() instanceof RatpackPromise and not isStatic() and + // It's generally safe to assume that if the return type exactly matches the declaring type, `this` will be returned. exists(ParameterizedType t | t instanceof RatpackPromise and t = getDeclaringType() and t = getReturnType() ) } - - override predicate consumesTaint(int methodArg, int lambdaArg) { - hasName(["next"]) and methodArg = 0 and lambdaArg = 0 - or - hasName(["cacheIf"]) and methodArg = 0 and lambdaArg = 0 - or - hasName(["route"]) and methodArg = [0, 1] and lambdaArg = 0 - } - - override predicate doesReturnTaint(int arg) { none() } // "flatMapIf" causes false positives. Wait for fluent lambda support. -} - -/** - * Holds if the method access qualifier `node1` has dataflow to the functional expression parameter `node2`. - */ -private predicate stepIntoLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(MethodAccess ma, FluentLambdaMethod flm, int methodArg, int lambdaArg | - flm.consumesTaint(methodArg, lambdaArg) - | - ma.getMethod() = flm and - node1.asExpr() = ma.getQualifier() and - ma.getArgument(methodArg).(FunctionalExpr).asMethod().getParameter(lambdaArg) = - node2.asParameter() - ) -} - -/** - * Holds if the return statement result of the functional expression `node1` has dataflow to the - * method access result `node2`. - */ -private predicate stepOutOfLambda(DataFlow::Node node1, DataFlow::Node node2) { - exists(FluentLambdaMethod flm, MethodAccess ma, FunctionalExpr fe, int arg | - flm.doesReturnTaint(arg) - | - fe.asMethod().getBody().getAStmt().(ReturnStmt).getResult() = node1.asExpr() and - ma.getMethod() = flm and - node2.asExpr() = ma and - ma.getArgument(arg) = fe - ) -} - -private class RatpackPromiseTaintPreservingStep extends AdditionalTaintStep { - override predicate step(DataFlow::Node node1, DataFlow::Node node2) { - stepIntoLambda(node1, node2) or - stepOutOfLambda(node1, node2) - } } diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 3c13845d19af..5b8ef88c60cf 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -53,13 +53,16 @@ void test2(Context ctx, OutputStream os) { } void test3(Context ctx) { - sink(ctx.getRequest().getBody().map(TypedData::getText)); //$hasTaintFlow - Promise mapResult = ctx.getRequest().getBody().map(b -> { + ctx.getRequest().getBody().map(TypedData::getText).then(s -> { + sink(s); //$hasTaintFlow + }); + ctx.getRequest().getBody().map(b -> { sink(b); //$hasTaintFlow sink(b.getText()); //$hasTaintFlow return b.getText(); + }).then(t -> { + sink(t); //$hasTaintFlow }); - sink(mapResult); //$hasTaintFlow ctx.getRequest().getBody().map(TypedData::getText).then(this::sink); //$hasTaintFlow ctx .getRequest() @@ -72,7 +75,9 @@ void test3(Context ctx) { void test4() { String tainted = taint(); Promise.value(tainted); - sink(Promise.value(tainted)); //$hasTaintFlow + Promise + .value(tainted) + .then(this::sink); //$hasTaintFlow Promise .value(tainted) .map(a -> a) @@ -188,15 +193,14 @@ void test8() { .then(value -> { sink(value); //$hasTaintFlow }); - // Waiting for support for lambda data flow - // Promise - // .value("potato") - // .flatMapError(RuntimeException.class, exception -> { - // return Promise.value(taint()); - // }) - // .then(value -> { - // sink(value); //$hasTaintFlow - // }); + Promise + .value("potato") + .flatMapError(RuntimeException.class, exception -> { + return Promise.value(taint()); + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); } void test9() { @@ -213,6 +217,12 @@ void test9() { .then(value -> { sink(value); // no taints flow }); + Promise + .value(tainted) + .flatMap(v -> Promise.value(v)) + .then(value -> { + sink(value); //$hasTaintFlow + }); } public static String identity(String input) { @@ -234,5 +244,51 @@ void test10() { sink(value); // no taints flow }); } + + void test11() { + String tainted = taint(); + Promise + .sync(() -> tainted) + .mapIf(v -> { + sink(v); //$hasTaintFlow + return true; + }, v -> { + sink(v); //$hasTaintFlow + return v; + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .sync(() -> tainted) + .mapIf(v -> { + sink(v); //$hasTaintFlow + return true; + }, vTrue -> { + sink(vTrue); //$hasTaintFlow + return vTrue; + }, vFalse -> { + sink(vFalse); //$hasTaintFlow + return vFalse; + }) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .sync(() -> tainted) + .mapIf(v -> { + sink(v); //$hasTaintFlow + return true; + }, vTrue -> { + sink(vTrue); //$hasTaintFlow + return "potato"; + }, vFalse -> { + sink(vFalse); //$hasTaintFlow + return "potato"; + }) + .then(value -> { + sink(value); // no tainted flow + }); + } } \ No newline at end of file diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 3165bffcd6d3..67f310c66c30 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -74,6 +74,14 @@ default Promise onError(Action errorHandler) { return null; } + default Promise mapIf(Predicate predicate, Function transformer) { + return null; + } + + default Promise mapIf(Predicate predicate, Function onTrue, Function onFalse) { + return null; + } + default Promise mapError(Function transformer) { return null; } From fe374f5e9ca728886c712388fae518c00779d6c9 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 6 Oct 2021 18:12:07 +0200 Subject: [PATCH 18/29] Ratpack: Add support for `Promise::apply` Signed-off-by: Jonathan Leitschuh --- .../java/frameworks/ratpack/RatpackExec.qll | 3 +++ .../ratpack/resources/Resource.java | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll index c0ce43989516..96b5673bf388 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -22,6 +22,9 @@ private class RatpackExecModel extends SummaryModelCsv { "blockingMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value", "blockingMap;;;ReturnValue of Argument[0];Element of ReturnValue;value", "mapError;;;ReturnValue of Argument[0];Element of ReturnValue;value", + // `apply` passes the qualifier to the function as the first argument + "apply;;;Element of Argument[-1];Element of Parameter[0] of Argument[0];value", + "apply;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", // `Promise` termination method "then;;;Element of Argument[-1];Parameter[0] of Argument[0];value", // 'next' accesses qualfier the 'Promise' value and also returns the qualifier diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 5b8ef88c60cf..5a5a3b9aabbc 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -230,6 +230,26 @@ public static String identity(String input) { } void test10() { + String tainted = taint(); + Promise + .value(tainted) + .apply(Resource::promiseIdentity) + .then(value -> { + sink(value); //$hasTaintFlow + }); + Promise + .value("potato") + .apply(Resource::promiseIdentity) + .then(value -> { + sink(value); // no taints flow + }); + } + + public static Promise promiseIdentity(Promise input) { + return input.map(i -> i); + } + + void test11() { String tainted = taint(); Promise .value(tainted) @@ -245,7 +265,7 @@ void test10() { }); } - void test11() { + void test12() { String tainted = taint(); Promise .sync(() -> tainted) From ebbbda70c05a906faf416b82ed9292527abb4fad Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Thu, 7 Oct 2021 12:19:07 +0200 Subject: [PATCH 19/29] Ratpack tests all passing Signed-off-by: Jonathan Leitschuh --- .../semmle/code/java/frameworks/ratpack/Ratpack.qll | 10 +++++++--- .../code/java/frameworks/ratpack/RatpackExec.qll | 10 +++++++--- .../frameworks/ratpack/resources/Resource.java | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll index ca3d18a6679b..3b894c24060f 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -69,15 +69,19 @@ private class RatpackModel extends SummaryModelCsv { ["ratpack.handling;", "ratpack.core.handling;"] + [ "Context;true;parse;(ratpack.http.TypedData,ratpack.parse.Parse);;Argument[0];ReturnValue;taint", - "Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];ReturnValue;taint" + "Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];ReturnValue;taint", + "Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapKey of ReturnValue;taint", + "Context;true;parse;(ratpack.core.http.TypedData,ratpack.core.parse.Parse);;Argument[0];MapValue of ReturnValue;taint" ] or row = ["ratpack.util;", "ratpack.func;"] + [ "MultiValueMap;true;getAll;;;MapKey of Argument[-1];MapKey of ReturnValue;value", - "MultiValueMap;true;getAll;;;MapValue of Argument[-1];Element of MapValue of ReturnValue;value", - "MultiValueMap;true;asMultimap;;;Element of Argument[-1];Element of ReturnValue;value" + "MultiValueMap;true;getAll;();;MapValue of Argument[-1];Element of MapValue of ReturnValue;value", + "MultiValueMap;true;getAll;(Object);;MapValue of Argument[-1];Element of ReturnValue;value", + "MultiValueMap;true;asMultimap;;;MapKey of Argument[-1];MapKey of ReturnValue;value", + "MultiValueMap;true;asMultimap;;;MapValue of Argument[-1];Element of MapValue of ReturnValue;value" ] } } diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll index 96b5673bf388..682e4e52afa6 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -1,10 +1,14 @@ +/** + * Provides classes and predicates related to `ratpack.exec.*`. + */ + import java private import semmle.code.java.dataflow.DataFlow private import semmle.code.java.dataflow.FlowSteps private import semmle.code.java.dataflow.ExternalFlow /** - * Ratpack methods that propagate user-supplied data as tainted. + * Model for Ratpack `Promise` methods. */ private class RatpackExecModel extends SummaryModelCsv { override predicate row(string row) { @@ -21,7 +25,7 @@ private class RatpackExecModel extends SummaryModelCsv { "map;;;ReturnValue of Argument[0];Element of ReturnValue;value", "blockingMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value", "blockingMap;;;ReturnValue of Argument[0];Element of ReturnValue;value", - "mapError;;;ReturnValue of Argument[0];Element of ReturnValue;value", + "mapError;;;ReturnValue of Argument[1];Element of ReturnValue;value", // `apply` passes the qualifier to the function as the first argument "apply;;;Element of Argument[-1];Element of Parameter[0] of Argument[0];value", "apply;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", @@ -41,7 +45,7 @@ private class RatpackExecModel extends SummaryModelCsv { // `flatMap` type methods return their returned `Promise` "flatMap;;;Element of Argument[-1];Parameter[0] of Argument[0];value", "flatMap;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", - "flatMapError;;;Element of ReturnValue of Argument[0];Element of ReturnValue;value", + "flatMapError;;;Element of ReturnValue of Argument[1];Element of ReturnValue;value", // `mapIf` methods conditionally map their values, or return themselves "mapIf;;;Element of Argument[-1];Parameter[0] of Argument[0];value", "mapIf;;;Element of Argument[-1];Parameter[0] of Argument[1];value", diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index 5a5a3b9aabbc..b940cd17ea0d 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -98,8 +98,11 @@ void test5(Context ctx) { sink(form.file("questionable_file").getFileName()); //$hasTaintFlow sink(form.files("questionable_files")); //$hasTaintFlow sink(form.files()); //$hasTaintFlow - sink(form.asMultimap()); //$hasTaintFlow - sink(form.asMultimap().asMap()); //$hasTaintFlow + sink(form.get("questionable_parameter")); //$hasTaintFlow + sink(form.getAll().get("questionable_parameter").get(0)); //$hasTaintFlow + sink(form.getAll("questionable_parameter").get(0)); //$hasTaintFlow + sink(form.asMultimap().get("questionable_parameter")); //$hasTaintFlow // fails! + sink(form.asMultimap().asMap()); //$hasTaintFlow // fails! }); } From 23e60e2c52a92dfd036aa364fc140b80a8c5939a Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 11 Oct 2021 17:24:35 -0400 Subject: [PATCH 20/29] Add full integration test for Ratpack example Signed-off-by: Jonathan Leitschuh --- .../library-tests/frameworks/ratpack/options | 2 +- .../ratpack/resources/IntegrationTest.java | 175 ++++++++++++++++++ .../ratpack/resources/Resource.java | 8 +- .../com/fasterxml/jackson/core/TreeNode.java | 8 +- .../jackson/databind/JsonSerializable.java | 10 + .../jackson/databind/node/ArrayNode.java | 10 + .../jackson/databind/node/BaseJsonNode.java | 7 + .../jackson/databind/node/ContainerNode.java | 5 + .../jackson/databind/node/ObjectNode.java | 9 + .../jackson/databind/node/POJONode.java | 76 ++++++++ .../jackson/databind/node/ValueNode.java | 5 + .../ratpack/jackson/Jackson.java | 32 ++++ .../ratpack/jackson/JsonParseOpts.java | 27 +++ 13 files changed, 369 insertions(+), 5 deletions(-) create mode 100644 java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/JsonSerializable.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ArrayNode.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/BaseJsonNode.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ContainerNode.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ObjectNode.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/POJONode.java create mode 100644 java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ValueNode.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/Jackson.java create mode 100644 java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/JsonParseOpts.java diff --git a/java/ql/test/library-tests/frameworks/ratpack/options b/java/ql/test/library-tests/frameworks/ratpack/options index 109f07058870..f96335495587 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/options +++ b/java/ql/test/library-tests/frameworks/ratpack/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-databind-2.12:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/ratpack-1.9.x:${testdir}/../../../stubs/jackson-core-2.12:${testdir}/../../../stubs/jackson-databind-2.12:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/netty-4.1.x diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java new file mode 100644 index 000000000000..dd79db00802c --- /dev/null +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java @@ -0,0 +1,175 @@ +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; +import com.fasterxml.jackson.databind.JsonSerializable; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import ratpack.core.handling.Context; +import ratpack.core.http.TypedData; +import ratpack.core.form.Form; +import ratpack.core.form.UploadedFile; +import ratpack.core.parse.Parse; +import ratpack.exec.Promise; +import ratpack.func.Action; +import ratpack.func.Function; +import ratpack.func.MultiValueMap; + +import java.io.OutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.function.Predicate; + +import static ratpack.jackson.Jackson.jsonNode; + +class IntegrationTest { + + static class Pojo { + String value; + + String getValue() { + return value; + } + } + + private final ObjectMapper objectMapper = new ObjectMapper(); + + void sink(Object o) {} + + String taint() { + return null; + } + + void test1(Context ctx) { + bindJson(ctx, Pojo.class) + .then(pojo ->{ + sink(pojo); //$hasTaintFlow + sink(pojo.value); //$hasTaintFlow + sink(pojo.getValue()); //$hasTaintFlow + }); + } + + void test2(Context ctx) { + bindForm(ctx, Pojo.class, defaults -> defaults.put("another", "potato")) + .then(pojo ->{ + sink(pojo); //$hasTaintFlow + sink(pojo.value); //$hasTaintFlow + sink(pojo.getValue()); //$hasTaintFlow + }); + } + + void test3() { + Object value = extractSingleValueIfPossible(ImmutableList.of("a", taint())); + sink(value); //$hasTaintFlow + } + + void test4(Context ctx) { + parseToForm(ctx, Pojo.class) + .map(pojoForm -> { + Map mergedParams = new HashMap<>(); + filterAndMerge(pojoForm, mergedParams, name -> false); + return mergedParams; + }).then(pojoMap -> { + sink(pojoMap); //$hasTaintFlow + sink(pojoMap.get("value")); //$hasTaintFlow + }); + } + + private Promise bindJson(Context ctx, Class type) { + return ctx.getRequest().getBody() + .map(data -> { + String dataText = data.getText(); + + try { + return ctx.parse(data, jsonNode(objectMapper)); + } catch (Exception e) { + String msg = "Unable to parse json data while binding type " + type.getCanonicalName() + " [jsonData: " + dataText + "]"; + throw new RuntimeException(msg, e); + } + }) + .map(json -> + bind(ctx, json, type) + ); + } + + private T bind(Context ctx, JsonNode input, Class type) { + T value; + try { + value = objectMapper.convertValue(input, type); + } catch (Exception e) { + throw new RuntimeException("Failed to convert input to " + type.getName(), e); + } + return value; + } + + private static Promise parseToForm(Context ctx, Class type) { + return ctx.getRequest().getBody() + .map(data -> { + try { + return ctx.parse(data, Form.form()); + } catch (Exception e) { + String msg = "Unable to parse form data while binding type " + type.getCanonicalName() + " [formData: " + data.getText() + "]"; + throw new RuntimeException(msg, e); + } + }); + } + + private Promise bindForm(Context ctx, Class type, Action> defaults) { + return parseToForm(ctx, type) + .map(form -> { + ObjectNode input = toObjectNode(form, defaults, s -> false); + Map> filesMap = form.files().getAll(); + filesMap.forEach((name, files) -> { + ArrayNode array = input.putArray(name); + files.forEach(f -> array.add(new POJONode(new UploadedFileWrapper(f)))); + }); + return bind(ctx, input, type); + }); + } + + private ObjectNode toObjectNode(MultiValueMap params, Action> defaults, Predicate paramFilter) throws Exception { + Map mergedParams = new HashMap<>(defaults.with(ImmutableMap.builder()).build()); + filterAndMerge(params, mergedParams, paramFilter); + return objectMapper.valueToTree(mergedParams); + } + + private static void filterAndMerge(MultiValueMap params, Map defaults, Predicate filter) { + params.asMultimap().asMap().forEach((name, values) -> { + if (!isEmptyAndHasDefault(name, values, defaults) && !filter.test(name)) { + defaults.put(name, extractSingleValueIfPossible(values)); + } + }); + } + + private static boolean isEmptyAndHasDefault(String name, Collection values, Map defaults) { + // STUB - This is to make the compiler happy + return false; + } + + private static Object extractSingleValueIfPossible(Collection values) { + return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values); + } + + private static class UploadedFileWrapper implements JsonSerializable { + + private final UploadedFile file; + + private UploadedFileWrapper(UploadedFile file) { + this.file = file; + } + + @Override + public void serialize(Object gen, Object serializers) throws IOException { + // empty + } + + @Override + public void serializeWithType(Object gen, Object serializers, Object typeSer) throws IOException { + // empty + } + } +} \ No newline at end of file diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index b940cd17ea0d..e48826e578bc 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -101,8 +101,12 @@ void test5(Context ctx) { sink(form.get("questionable_parameter")); //$hasTaintFlow sink(form.getAll().get("questionable_parameter").get(0)); //$hasTaintFlow sink(form.getAll("questionable_parameter").get(0)); //$hasTaintFlow - sink(form.asMultimap().get("questionable_parameter")); //$hasTaintFlow // fails! - sink(form.asMultimap().asMap()); //$hasTaintFlow // fails! + sink(form.asMultimap().get("questionable_parameter")); //$hasTaintFlow + sink(form.asMultimap().asMap()); //$hasTaintFlow + form.asMultimap().asMap().forEach((name, values) -> { + sink(name); //$hasTaintFlow + sink(values); //$hasTaintFlow + }); }); } diff --git a/java/ql/test/stubs/jackson-core-2.12/com/fasterxml/jackson/core/TreeNode.java b/java/ql/test/stubs/jackson-core-2.12/com/fasterxml/jackson/core/TreeNode.java index 338f58961dac..7a6cf357a4d8 100644 --- a/java/ql/test/stubs/jackson-core-2.12/com/fasterxml/jackson/core/TreeNode.java +++ b/java/ql/test/stubs/jackson-core-2.12/com/fasterxml/jackson/core/TreeNode.java @@ -9,7 +9,9 @@ import java.util.Iterator; public interface TreeNode { - JsonParser.NumberType numberType(); + default JsonParser.NumberType numberType() { + return null; + } int size(); @@ -35,6 +37,8 @@ public interface TreeNode { TreeNode at(String jsonPointerExpression) throws IllegalArgumentException; - JsonParser traverse(); + default JsonParser traverse() { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/JsonSerializable.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/JsonSerializable.java new file mode 100644 index 000000000000..78c5f8746420 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/JsonSerializable.java @@ -0,0 +1,10 @@ +package com.fasterxml.jackson.databind; + +import java.io.IOException; + +// This interface does not actually have these types.. This is a significantly oversimplified stub. +public interface JsonSerializable { + public void serialize(Object gen, Object serializers) throws IOException; + + public void serializeWithType(Object gen, Object serializers, Object typeSer) throws IOException; +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ArrayNode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ArrayNode.java new file mode 100644 index 000000000000..3f0d995623e9 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ArrayNode.java @@ -0,0 +1,10 @@ +package com.fasterxml.jackson.databind.node; + +import com.fasterxml.jackson.databind.JsonNode; + +public abstract class ArrayNode extends ContainerNode { + + public ArrayNode add(JsonNode value) { + return null; + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/BaseJsonNode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/BaseJsonNode.java new file mode 100644 index 000000000000..85564f3eb340 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/BaseJsonNode.java @@ -0,0 +1,7 @@ +package com.fasterxml.jackson.databind.node; + +import com.fasterxml.jackson.databind.JsonNode; + +public abstract class BaseJsonNode extends JsonNode { + +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ContainerNode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ContainerNode.java new file mode 100644 index 000000000000..7a52ca08e759 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ContainerNode.java @@ -0,0 +1,5 @@ +package com.fasterxml.jackson.databind.node; + +public abstract class ContainerNode> extends BaseJsonNode { + +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ObjectNode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ObjectNode.java new file mode 100644 index 000000000000..b19f9f9e5562 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ObjectNode.java @@ -0,0 +1,9 @@ +package com.fasterxml.jackson.databind.node; + +public abstract class ObjectNode extends ContainerNode { + public ArrayNode putArray(String propertyName) + { + return null; + } + +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/POJONode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/POJONode.java new file mode 100644 index 000000000000..ab268242d87b --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/POJONode.java @@ -0,0 +1,76 @@ +package com.fasterxml.jackson.databind.node; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; + +public class POJONode extends ValueNode { + protected final Object _value; + + public POJONode(Object v) { + _value = v; + } + + @Override + public boolean equals(Object o) { + return false; + } + + public String toString() { + return null; + } + + @Override + public T deepCopy() { + return null; + } + + @Override + public JsonNode get(int index) { + return null; + } + + @Override + public JsonNode path(String fieldName) { + return null; + } + + @Override + public JsonNode path(int index) { + return null; + } + + @Override + public String asText() { + return null; + } + + @Override + public JsonNode findValue(String fieldName) { + return null; + } + + @Override + public JsonNode findPath(String fieldName) { + return null; + } + + @Override + public JsonNode findParent(String fieldName) { + return null; + } + + @Override + public List findValues(String fieldName, List foundSoFar) { + return null; + } + + @Override + public List findValuesAsText(String fieldName, List foundSoFar) { + return null; + } + + @Override + public List findParents(String fieldName, List foundSoFar) { + return null; + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ValueNode.java b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ValueNode.java new file mode 100644 index 000000000000..318818bc9af3 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.12/com/fasterxml/jackson/databind/node/ValueNode.java @@ -0,0 +1,5 @@ +package com.fasterxml.jackson.databind.node; + +public abstract class ValueNode extends BaseJsonNode { + +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/Jackson.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/Jackson.java new file mode 100644 index 000000000000..9121d81fda13 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/Jackson.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ratpack.jackson; + +import ratpack.func.Nullable; +import ratpack.core.parse.Parse; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + + +public abstract class Jackson { + + private Jackson() { + } + + public static Parse jsonNode(@Nullable ObjectMapper objectMapper) { + return null; + } +} diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/JsonParseOpts.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/JsonParseOpts.java new file mode 100644 index 000000000000..eaac56229812 --- /dev/null +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/jackson/JsonParseOpts.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ratpack.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Optional; + +public interface JsonParseOpts { + + Optional getObjectMapper(); + +} From 8fecc158ff347d7900609dee20a7d6e43983afa4 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 11 Oct 2021 18:05:07 -0400 Subject: [PATCH 21/29] Add support for Map.forEach Signed-off-by: Jonathan Leitschuh --- .../ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll | 2 ++ .../frameworks/ratpack/resources/IntegrationTest.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll b/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll index 6c72bbd451b3..ef376ed7db33 100644 --- a/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll +++ b/java/ql/lib/semmle/code/java/dataflow/internal/ContainerFlow.qll @@ -135,6 +135,8 @@ private class ContainerFlowSummaries extends SummaryModelCsv { "java.util;Map;true;merge;(Object,Object,BiFunction);;Argument[1];MapValue of Argument[-1];value", "java.util;Map;true;putAll;(Map);;MapKey of Argument[0];MapKey of Argument[-1];value", "java.util;Map;true;putAll;(Map);;MapValue of Argument[0];MapValue of Argument[-1];value", + "java.util;Map;true;forEach;(BiConsumer);;MapKey of Argument[-1];Parameter[0] of Argument[0];value", + "java.util;Map;true;forEach;(BiConsumer);;MapValue of Argument[-1];Parameter[1] of Argument[0];value", "java.util;Collection;true;parallelStream;();;Element of Argument[-1];Element of ReturnValue;value", "java.util;Collection;true;stream;();;Element of Argument[-1];Element of ReturnValue;value", "java.util;Collection;true;toArray;;;Element of Argument[-1];ArrayElement of ReturnValue;value", diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java index dd79db00802c..1b002b2af614 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java @@ -74,7 +74,7 @@ void test4(Context ctx) { filterAndMerge(pojoForm, mergedParams, name -> false); return mergedParams; }).then(pojoMap -> { - sink(pojoMap); //$hasTaintFlow + sinlk(pojoMap); //$hasTaintFlow sink(pojoMap.get("value")); //$hasTaintFlow }); } From 5a2bdc9a0fdedabf513eefa8e1b379bcaf27449b Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 13 Oct 2021 17:11:18 -0400 Subject: [PATCH 22/29] Jackson taint tracking of elements Signed-off-by: Jonathan Leitschuh --- .../jackson/JacksonSerializability.qll | 1 + .../ratpack/resources/IntegrationTest.java | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/frameworks/jackson/JacksonSerializability.qll b/java/ql/lib/semmle/code/java/frameworks/jackson/JacksonSerializability.qll index f5d2bfb78f99..5f6a5ea08113 100644 --- a/java/ql/lib/semmle/code/java/frameworks/jackson/JacksonSerializability.qll +++ b/java/ql/lib/semmle/code/java/frameworks/jackson/JacksonSerializability.qll @@ -285,6 +285,7 @@ private class JacksonModel extends SummaryModelCsv { [ "com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Argument[0];ReturnValue;taint", "com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;MapValue of Argument[0];ReturnValue;taint", + "com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Element of MapValue of Argument[0];ReturnValue;taint", "com.fasterxml.jackson.databind;ObjectMapper;true;convertValue;;;Argument[0];ReturnValue;taint", "com.fasterxml.jackson.databind;ObjectMapper;false;createParser;;;Argument[0];ReturnValue;taint", "com.fasterxml.jackson.databind;ObjectReader;false;createParser;;;Argument[0];ReturnValue;taint", diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java index 1b002b2af614..f4b71e03d36e 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java @@ -65,6 +65,15 @@ void test2(Context ctx) { void test3() { Object value = extractSingleValueIfPossible(ImmutableList.of("a", taint())); sink(value); //$hasTaintFlow + List values = (List) value; + sink(values.get(1)); //$hasTaintFlow + Map weirdMap = new HashMap<>(); + weirdMap.put("a", value); + weirdMap.forEach((key, mapValue) -> { + sink(mapValue); //$hasTaintFlow + List values2 = (List) mapValue; + sink(values2.get(0)); //$hasTaintFlow + }); } void test4(Context ctx) { @@ -74,8 +83,32 @@ void test4(Context ctx) { filterAndMerge(pojoForm, mergedParams, name -> false); return mergedParams; }).then(pojoMap -> { - sinlk(pojoMap); //$hasTaintFlow + sink(pojoMap.keySet().iterator().next()); //$hasTaintFlow sink(pojoMap.get("value")); //$hasTaintFlow + pojoMap.forEach((key, value) -> { + sink(key); //$hasTaintFlow + sink(value); //$hasTaintFlow + List values = (List) value; + sink(values.get(0)); //$hasTaintFlow + }); + }); + } + + void test5(Context ctx) { + parseToForm(ctx, Pojo.class) + .map(pojoForm -> { + Map mergedParams = new HashMap<>(); + filterAndMerge_2(pojoForm, mergedParams, name -> false); + return mergedParams; + }).then(pojoMap -> { + sink(pojoMap.keySet().iterator().next()); //TODO:$hasTaintFlow + sink(pojoMap.get("value")); //TODO:$hasTaintFlow + pojoMap.forEach((key, value) -> { + sink(key); //TODO:$hasTaintFlow + sink(value); //TODO:$hasTaintFlow + List values = (List) value; + sink(values.get(0)); //TODO:$hasTaintFlow + }); }); } @@ -138,6 +171,16 @@ private ObjectNode toObjectNode(MultiValueMap params, Action params, Map defaults, Predicate filter) { + for(Map.Entry> entry : params.asMultimap().asMap().entrySet()) { + String name = entry.getKey(); + Collection values = entry.getValue(); + if (!isEmptyAndHasDefault(name, values, defaults) && !filter.test(name)) { + defaults.put(name, extractSingleValueIfPossible(values)); + } + } + } + + private static void filterAndMerge_2(MultiValueMap params, Map defaults, Predicate filter) { params.asMultimap().asMap().forEach((name, values) -> { if (!isEmptyAndHasDefault(name, values, defaults) && !filter.test(name)) { defaults.put(name, extractSingleValueIfPossible(values)); From db2892b9eaec01d1b278ef42d698170b7ffda308 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 18 Oct 2021 14:21:35 -0400 Subject: [PATCH 23/29] Resove taint tracking issues from `asMultimap` Signed-off-by: Jonathan Leitschuh --- .../code/java/frameworks/ratpack/Ratpack.qll | 2 +- .../ratpack/CollectionPassingTest.java | 70 +++++++++++++++++++ .../ratpack/resources/IntegrationTest.java | 26 ++++++- .../ratpack/resources/Resource.java | 5 +- 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll index 3b894c24060f..a53ac3df8740 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/Ratpack.qll @@ -81,7 +81,7 @@ private class RatpackModel extends SummaryModelCsv { "MultiValueMap;true;getAll;();;MapValue of Argument[-1];Element of MapValue of ReturnValue;value", "MultiValueMap;true;getAll;(Object);;MapValue of Argument[-1];Element of ReturnValue;value", "MultiValueMap;true;asMultimap;;;MapKey of Argument[-1];MapKey of ReturnValue;value", - "MultiValueMap;true;asMultimap;;;MapValue of Argument[-1];Element of MapValue of ReturnValue;value" + "MultiValueMap;true;asMultimap;;;MapValue of Argument[-1];MapValue of ReturnValue;value" ] } } diff --git a/java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java b/java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java new file mode 100644 index 000000000000..eb0ca9cad92e --- /dev/null +++ b/java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java @@ -0,0 +1,70 @@ +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import ratpack.core.handling.Context; +import ratpack.core.form.Form; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.function.Predicate; + +public class CollectionPassingTest { + + void sink(Object o) {} + + String taint() { + return null; + } + + void test_1(Context ctx) { + // Given + ctx + .getRequest() + .getBody() + .map(data -> ctx.parse(data, Form.form())) + .then(form -> { + // When + Map pojoMap = new HashMap<>(); + merge(form.asMultimap().asMap(), pojoMap); + // Then + sink(pojoMap.get("value")); //$hasTaintFlow + pojoMap.forEach((key, value) -> { + sink(value); //$hasTaintFlow + List values = (List) value; + sink(values.get(0)); //$hasTaintFlow + }); + }); + } + + void test_2() { + // Given + Map> taintedMap = new HashMap<>(); + taintedMap.put("value", ImmutableList.of(taint())); + Map pojoMap = new HashMap<>(); + // When + merge(taintedMap, pojoMap); + // Then + sink(pojoMap.get("value")); //$hasTaintFlow + pojoMap.forEach((key, value) -> { + sink(value); //$hasTaintFlow + List values = (List) value; + sink(values.get(0)); //$hasTaintFlow + }); + } + + + private static void merge(Map> params, Map defaults) { + for(Map.Entry> entry : params.entrySet()) { + String name = entry.getKey(); + Collection values = entry.getValue(); + defaults.put(name, extractSingleValueIfPossible(values)); + } + } + + private static Object extractSingleValueIfPossible(Collection values) { + return values.size() == 1 ? values.iterator().next() : ImmutableList.copyOf(values); + } + +} diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java index f4b71e03d36e..83ca80fc37fb 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/IntegrationTest.java @@ -31,9 +31,15 @@ class IntegrationTest { static class Pojo { String value; + List values; + String getValue() { return value; } + + List getValues() { + return values; + } } private final ObjectMapper objectMapper = new ObjectMapper(); @@ -112,6 +118,24 @@ void test5(Context ctx) { }); } + void test6(Context ctx) { + bindQuery(ctx, Pojo.class) + .then(pojo -> { + sink(pojo.getValue()); //$hasTaintFlow + sink(pojo.getValues()); //$hasTaintFlow + }); + } + + public Promise bindQuery(Context ctx, Class type) { + return bindQuery(ctx, type, Action.noop()); + } + + public Promise bindQuery(Context ctx, Class type, Action> defaults) { + return Promise.sync(() -> + bind(ctx, toObjectNode(ctx.getRequest().getQueryParams(), defaults, name -> false), type) + ); + } + private Promise bindJson(Context ctx, Class type) { return ctx.getRequest().getBody() .map(data -> { @@ -215,4 +239,4 @@ public void serializeWithType(Object gen, Object serializers, Object typeSer) th // empty } } -} \ No newline at end of file +} diff --git a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java index e48826e578bc..d85924234f29 100644 --- a/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java +++ b/java/ql/test/library-tests/frameworks/ratpack/resources/Resource.java @@ -316,6 +316,5 @@ void test12() { .then(value -> { sink(value); // no tainted flow }); - } - -} \ No newline at end of file + } +} From 823190711672a90fc4f24c126397270c75f35c7e Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 19 Oct 2021 11:42:35 -0400 Subject: [PATCH 24/29] Ratpack code cleanup from code review --- .../semmle/code/java/frameworks/ratpack/RatpackExec.qll | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll index 682e4e52afa6..ee73901224a5 100644 --- a/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll +++ b/java/ql/lib/semmle/code/java/frameworks/ratpack/RatpackExec.qll @@ -59,7 +59,7 @@ private class RatpackExecModel extends SummaryModelCsv { /** A reference type that extends a parameterization the Promise type. */ private class RatpackPromise extends RefType { RatpackPromise() { - getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") + this.getSourceDeclaration().getASourceSupertype*().hasQualifiedName("ratpack.exec", "Promise") } } @@ -68,13 +68,12 @@ private class RatpackPromise extends RefType { */ private class RatpackPromiseFluentMethod extends FluentMethod { RatpackPromiseFluentMethod() { - getDeclaringType() instanceof RatpackPromise and - not isStatic() and + not this.isStatic() and // It's generally safe to assume that if the return type exactly matches the declaring type, `this` will be returned. exists(ParameterizedType t | t instanceof RatpackPromise and - t = getDeclaringType() and - t = getReturnType() + t = this.getDeclaringType() and + t = this.getReturnType() ) } } From 584c27a2f8fe56ee07e84afb4d969220185c32a9 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 19 Oct 2021 11:44:12 -0400 Subject: [PATCH 25/29] Move CollectionPassingTest to correct directory --- .../frameworks/ratpack/{ => resources}/CollectionPassingTest.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename java/ql/test/library-tests/frameworks/ratpack/{ => resources}/CollectionPassingTest.java (100%) diff --git a/java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java b/java/ql/test/library-tests/frameworks/ratpack/resources/CollectionPassingTest.java similarity index 100% rename from java/ql/test/library-tests/frameworks/ratpack/CollectionPassingTest.java rename to java/ql/test/library-tests/frameworks/ratpack/resources/CollectionPassingTest.java From cce3aad62ef6d41bd5e4aeddae2dff4dca6b8e6a Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Wed, 20 Oct 2021 11:34:59 -0400 Subject: [PATCH 26/29] Remove non-ASCII characters from Handler.java Signed-off-by: Jonathan Leitschuh --- .../ratpack-1.9.x/ratpack/core/handling/Handler.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java index 6239a06fdc28..153759cf61f8 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java @@ -44,7 +44,7 @@ *
  * import ratpack.core.handling.*;
  *
- * // A responder may just return a response to the client…
+ * // A responder may just return a response to the client
  *
  * class SimpleHandler implements Handler {
  *   void handle(Context exchange) {
@@ -52,7 +52,7 @@
  *   }
  * }
  *
- * // A responder may add a response header, before delegating to the next in the pipeline…
+ * // A responder may add a response header, before delegating to the next in the pipeline
  *
  * class DecoratingHandler implements Handler {
  *   void handle(Context exchange) {
@@ -61,7 +61,7 @@
  *   }
  * }
  *
- * // Or a handler may conditionally respond…
+ * // Or a handler may conditionally respond
  *
  * class ConditionalHandler implements Handler {
  *   void handle(Context exchange) {
@@ -73,7 +73,7 @@
  *   }
  * }
  *
- * // A handler does not need to participate in the response, but can instead "route" the exchange to different handlers…
+ * // A handler does not need to participate in the response, but can instead "route" the exchange to different handlers
  *
  * class RoutingHandler implements Handler {
  *   private final Handler[] fooHandlers;
@@ -91,7 +91,7 @@
  *   }
  * }
  *
- * // It can sometimes be appropriate to directly delegate to a handler, instead of using exchange.insert() …
+ * // It can sometimes be appropriate to directly delegate to a handler, instead of using exchange.insert()
  *
  * class FilteringHandler implements Handler {
  *   private final Handler nestedHandler;

From 5eb28398f03c18b93e315280ef7544ce28a23397 Mon Sep 17 00:00:00 2001
From: Jonathan Leitschuh 
Date: Fri, 22 Oct 2021 10:50:45 -0400
Subject: [PATCH 27/29] Remove non-ASCII characters from Promise.java

Signed-off-by: Jonathan Leitschuh 
---
 .../test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java | 2 +-
 java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
index 153759cf61f8..13d4ec1781ab 100644
--- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
+++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/core/handling/Handler.java
@@ -27,7 +27,7 @@
  * 

Non blocking/Asynchronous

*

* Handlers are expected to be asynchronous. - * That is, there is no expectation that the handler is “finished" when its {@link #handle(Context)} method returns. + * That is, there is no expectation that the handler is "finished" when its {@link #handle(Context)} method returns. * This facilitates the use of non blocking IO without needing to enter some kind of special mode. * An implication is that handlers must ensure that they either send a response or delegate to another handler. *

diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 67f310c66c30..550b8bf4fe1a 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -25,7 +25,7 @@ * A promise for a single value. *

* A promise is a representation of a value which will become available later. - * Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of “operations" to be specified, + * Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of "operations" to be specified, * that the value will travel through as it becomes available. * Such operations are implemented via the {@link #transform(Function)} method. * Each operation returns a new promise object, not the original promise object. From ebe2c26f4dba098d4dd8127842f1543cacced50c Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Mon, 25 Oct 2021 11:30:12 -0400 Subject: [PATCH 28/29] Remove the last non-ascii quote from Promise Signed-off-by: Jonathan Leitschuh --- java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index 550b8bf4fe1a..de0b68e761e7 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -33,7 +33,7 @@ * To create a promise, use the {@link Promise#async(Upstream)} method (or one of the variants such as {@link Promise#sync(Factory)}. * To test code that uses promises, use the {@link ratpack.test.exec.ExecHarness}. *

- * The promise is not “activated” until the {@link #then(Action)} method is called. + * The promise is not "activated” until the {@link #then(Action)} method is called. * This method terminates the pipeline, and receives the final value. *

* Promise objects are multi use. From 21aeee6378d549aea371cecefaf1131ae79e0d22 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Tue, 26 Oct 2021 08:28:44 -0400 Subject: [PATCH 29/29] Actually remove the last non-ascii quote from Promise Signed-off-by: Jonathan Leitschuh --- java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java index de0b68e761e7..8c07b48c97ff 100644 --- a/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java +++ b/java/ql/test/stubs/ratpack-1.9.x/ratpack/exec/Promise.java @@ -33,7 +33,7 @@ * To create a promise, use the {@link Promise#async(Upstream)} method (or one of the variants such as {@link Promise#sync(Factory)}. * To test code that uses promises, use the {@link ratpack.test.exec.ExecHarness}. *

- * The promise is not "activated” until the {@link #then(Action)} method is called. + * The promise is not "activated" until the {@link #then(Action)} method is called. * This method terminates the pipeline, and receives the final value. *

* Promise objects are multi use.