diff --git a/java/ql/lib/change-notes/2025-05-22-spring-request-mapping-value.md b/java/ql/lib/change-notes/2025-05-22-spring-request-mapping-value.md new file mode 100644 index 000000000000..8b7effc535de --- /dev/null +++ b/java/ql/lib/change-notes/2025-05-22-spring-request-mapping-value.md @@ -0,0 +1,4 @@ +--- +category: deprecated +--- +* The predicate `getValue()` on `SpringRequestMappingMethod` is now deprecated. Use `getAValue()` instead. diff --git a/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll b/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll index a222be20c20a..c93993336d95 100644 --- a/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll +++ b/java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll @@ -153,8 +153,16 @@ class SpringRequestMappingMethod extends SpringControllerMethod { result = this.getProducesExpr().(CompileTimeConstantExpr).getStringValue() } - /** Gets the "value" @RequestMapping annotation value, if present. */ - string getValue() { result = requestMappingAnnotation.getStringValue("value") } + /** DEPRECATED: Use `getAValue()` instead. */ + deprecated string getValue() { result = requestMappingAnnotation.getStringValue("value") } + + /** + * Gets a "value" @RequestMapping annotation string value, if present. + * + * If the annotation element is defined with an array initializer, then the result will be one of the + * elements of that array. Otherwise, the result will be the single expression used as value. + */ + string getAValue() { result = requestMappingAnnotation.getAStringArrayValue("value") } /** Gets the "method" @RequestMapping annotation value, if present. */ string getMethodValue() { diff --git a/java/ql/test/library-tests/frameworks/spring/controller/RequestController.expected b/java/ql/test/library-tests/frameworks/spring/controller/RequestController.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/test/library-tests/frameworks/spring/controller/RequestController.ql b/java/ql/test/library-tests/frameworks/spring/controller/RequestController.ql new file mode 100644 index 000000000000..b1c1c1c86000 --- /dev/null +++ b/java/ql/test/library-tests/frameworks/spring/controller/RequestController.ql @@ -0,0 +1,18 @@ +import java +import utils.test.InlineExpectationsTest +private import semmle.code.java.frameworks.spring.SpringController + +module TestRequestController implements TestSig { + string getARelevantTag() { result = "RequestMappingURL" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "RequestMappingURL" and + exists(SpringRequestMappingMethod m | + m.getLocation() = location and + element = m.toString() and + value = "\"" + m.getAValue() + "\"" + ) + } +} + +import MakeTest diff --git a/java/ql/test/library-tests/frameworks/spring/controller/Test.java b/java/ql/test/library-tests/frameworks/spring/controller/Test.java index 6267073ce876..ad4fbc89f44f 100644 --- a/java/ql/test/library-tests/frameworks/spring/controller/Test.java +++ b/java/ql/test/library-tests/frameworks/spring/controller/Test.java @@ -32,92 +32,93 @@ public class Test { - static void sink(Object o) {} + static void sink(Object o) { + } @Controller static class NotTaintedTest { @RequestMapping("/") - public void get(WebRequest src) { + public void get(WebRequest src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(NativeWebRequest src) { + public void get(NativeWebRequest src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(ServletRequest src) { + public void get(ServletRequest src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(HttpSession src) { + public void get(HttpSession src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(PushBuilder src) { + public void get(PushBuilder src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(Principal src) { + public void get(Principal src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(HttpMethod src) { + public void get(HttpMethod src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(Locale src) { + public void get(Locale src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(TimeZone src) { + public void get(TimeZone src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(ZoneId src) { + public void get(ZoneId src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(OutputStream src) { + public void get(OutputStream src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(Writer src) { + public void get(Writer src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(RedirectAttributes src) { + public void get(RedirectAttributes src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(Errors src) { + public void get(Errors src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(SessionStatus src) { + public void get(SessionStatus src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(UriComponentsBuilder src) { + public void get(UriComponentsBuilder src) { // $ RequestMappingURL="/" sink(src); } @RequestMapping("/") - public void get(Pageable src) { + public void get(Pageable src) { // $ RequestMappingURL="/" sink(src); } } @@ -125,62 +126,62 @@ public void get(Pageable src) { @Controller static class ExplicitlyTaintedTest { @RequestMapping("/") - public void get(InputStream src) { + public void get(InputStream src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void get(Reader src) { + public void get(Reader src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void matrixVariable(@MatrixVariable Object src) { + public void matrixVariable(@MatrixVariable Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void requestParam(@RequestParam Object src) { + public void requestParam(@RequestParam Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void requestHeader(@RequestHeader Object src) { + public void requestHeader(@RequestHeader Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void cookieValue(@CookieValue Object src) { + public void cookieValue(@CookieValue Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void requestPart(@RequestPart Object src) { + public void requestPart(@RequestPart Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void pathVariable(@PathVariable Object src) { + public void pathVariable(@PathVariable Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void requestBody(@RequestBody Object src) { + public void requestBody(@RequestBody Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void get(HttpEntity src) { + public void get(HttpEntity src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void requestAttribute(@RequestAttribute Object src) { + public void requestAttribute(@RequestAttribute Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void sessionAttribute(@SessionAttribute Object src) { + public void sessionAttribute(@SessionAttribute Object src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } } @@ -191,13 +192,20 @@ static class Pojo { } @RequestMapping("/") - public void get(String src) { + public void get(String src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } @RequestMapping("/") - public void get1(Pojo src) { + public void get1(Pojo src) { // $ RequestMappingURL="/" sink(src); // $hasValueFlow } } + + @Controller + static class MultipleValuesTest { + @RequestMapping({"/a", "/b"}) + public void get(WebRequest src) { // $ RequestMappingURL="/a" RequestMappingURL="/b" + } + } }