From 98c911ef716c99a313d30bf6b616dd367c3f9156 Mon Sep 17 00:00:00 2001 From: Eric Giffon Date: Wed, 12 Apr 2023 16:15:36 +0200 Subject: [PATCH 1/6] PLUGINAPI-42 Create new custom types for http request and response --- plugin-api/build.gradle | 3 +- .../sonar/api/server/http/HttpRequest.java | 101 ++++++++++++++++++ .../sonar/api/server/http/HttpResponse.java | 68 ++++++++++++ .../api/server/http/JakartaHttpRequest.java | 100 +++++++++++++++++ .../api/server/http/JakartaHttpResponse.java | 71 ++++++++++++ .../api/server/http/JavaxHttpRequest.java | 100 +++++++++++++++++ .../api/server/http/JavaxHttpResponse.java | 71 ++++++++++++ settings.gradle | 5 +- 8 files changed, 516 insertions(+), 3 deletions(-) create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java create mode 100644 plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 1f3bd3c7..ed79d233 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -12,7 +12,8 @@ dependencies { implementation project(':check-api') compileOnly libs.jsr305 - compileOnly libs.servlet.api + compileOnly libs.javax.servlet.api + compileOnly libs.jakarta.servlet.api testImplementation libs.junit4 testImplementation libs.junit5 diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java new file mode 100644 index 00000000..bd17c343 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java @@ -0,0 +1,101 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.util.Enumeration; + +/** + * Framework-agnostic definition of an HTTP request. + * + * @since 9.16 + */ +public interface HttpRequest { + + + /** + * Returns the port number to which the request was sent. + */ + int getServerPort(); + + /** + * Returns a boolean indicating whether this request was made using a secure channel, such as HTTPS. + */ + boolean isSecure(); + + /** + * Returns the name of the scheme used to make this request, for example, http, https, or ftp. + */ + String getScheme(); + + /** + * Returns the host name of the server to which the request was sent. + */ + String getServerName(); + + /** + * Returns the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and server path, + * but it does not include query string parameters. + */ + String getRequestURL(); + + /** + * Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request. + */ + String getRequestURI(); + + /** + * Returns the query string that is contained in the request URL after the path. This method returns null if the URL does not have a + * query string. + */ + String getQueryString(); + + /** + * Returns the value of a request parameter as a String, or null if the parameter does not exist. + * You should only use this method when you are sure the parameter has only one value. If the parameter might have more than one value, + * use {@link #getParameterValues}. + */ + String getParameter(String name); + + /** + * Returns an array containing all of the values the given request parameter has, or null if the parameter does not exist. + */ + String[] getParameterValues(String name); + + /** + * Returns the value of the specified request header as a String. If the request did not include a header of the specified name, this + * method returns null. If there are multiple headers with the same name, this method returns the first head in the request. + */ + String getHeader(String name); + + /** + * Returns all the values of the specified request header as an Enumeration of String objects. + */ + Enumeration getHeaders(String name); + + /** + * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. + */ + String getMethod(); + + /** + * Returns the raw request object from the Servlet API that matches this request. + */ + Object getRawRequest(); +} diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java new file mode 100644 index 00000000..424ecdca --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java @@ -0,0 +1,68 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.io.IOException; +import java.util.Collection; + +/** + * Framework-agnostic definition of an HTTP response. + * + * @since 9.16 + */ +public interface HttpResponse { + + /** + * Adds a response header with the given name and value. This method allows response headers to have multiple values. + */ + void addHeader(String name, String value); + + /** + * Gets the value of the response header with the given name. + * If a response header with the given name exists and contains multiple values, the value that was added first will be returned. + */ + String getHeader(String name); + + /** + * Gets the values of the response header with the given name. + */ + Collection getHeaders(String name); + + /** + * Sets the status code for this response. + */ + void setStatus(int sc); + + /** + * Gets the current status code of this response. + */ + int getStatus(); + + /** + * Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. + * The buffer will be replaced with the data set by this method. + */ + void sendRedirect(String location) throws IOException; + + /** + * Returns the raw response object from the Servlet API that matches this response. + */ + Object getRawResponse(); +} diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java new file mode 100644 index 00000000..4a80ebf4 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java @@ -0,0 +1,100 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Enumeration; + +/** + * Implementation of {@link HttpRequest} based on a delegate of {@link HttpServletRequest} from the Jakarta Servlet API. + */ +public class JakartaHttpRequest implements HttpRequest { + + private final HttpServletRequest delegate; + + public JakartaHttpRequest(HttpServletRequest delegate) { + this.delegate = delegate; + } + + @Override + public int getServerPort() { + return delegate.getServerPort(); + } + + @Override + public boolean isSecure() { + return delegate.isSecure(); + } + + @Override + public String getScheme() { + return delegate.getScheme(); + } + + @Override + public String getServerName() { + return delegate.getServerName(); + } + + @Override + public String getRequestURL() { + return delegate.getRequestURL().toString(); + } + + @Override + public String getRequestURI() { + return delegate.getRequestURI(); + } + + @Override + public String getQueryString() { + return delegate.getQueryString(); + } + + @Override + public String getParameter(String name) { + return delegate.getParameter(name); + } + + @Override + public String[] getParameterValues(String name) { + return delegate.getParameterValues(name); + } + + @Override + public String getHeader(String name) { + return delegate.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + return delegate.getHeaders(name); + } + + @Override + public String getMethod() { + return delegate.getMethod(); + } + + @Override + public HttpServletRequest getRawRequest() { + return delegate; + } +} diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java new file mode 100644 index 00000000..f1b9e197 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java @@ -0,0 +1,71 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collection; + +/** + * Implementation of {@link HttpResponse} based on a delegate of {@link HttpServletResponse} from the Jakarta Servlet API. + */ +public class JakartaHttpResponse implements HttpResponse { + + private final HttpServletResponse delegate; + + public JakartaHttpResponse(HttpServletResponse delegate) { + this.delegate = delegate; + } + + @Override + public void addHeader(String name, String value) { + delegate.addHeader(name, value); + } + + @Override + public String getHeader(String name) { + return delegate.getHeader(name); + } + + @Override + public Collection getHeaders(String name) { + return delegate.getHeaders(name); + } + + @Override + public void setStatus(int sc) { + delegate.setStatus(sc); + } + + @Override + public int getStatus() { + return delegate.getStatus(); + } + + @Override + public void sendRedirect(String location) throws IOException { + delegate.sendRedirect(location); + } + + @Override + public HttpServletResponse getRawResponse() { + return delegate; + } +} diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java new file mode 100644 index 00000000..d2c29224 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java @@ -0,0 +1,100 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; + +/** + * Implementation of {@link HttpRequest} based on a delegate of {@link HttpServletRequest} from the Javax Servlet API. + */ +public class JavaxHttpRequest implements HttpRequest { + + private final HttpServletRequest delegate; + + public JavaxHttpRequest(HttpServletRequest delegate) { + this.delegate = delegate; + } + + @Override + public int getServerPort() { + return delegate.getServerPort(); + } + + @Override + public boolean isSecure() { + return delegate.isSecure(); + } + + @Override + public String getScheme() { + return delegate.getScheme(); + } + + @Override + public String getServerName() { + return delegate.getServerName(); + } + + @Override + public String getRequestURL() { + return delegate.getRequestURL().toString(); + } + + @Override + public String getRequestURI() { + return delegate.getRequestURI(); + } + + @Override + public String getQueryString() { + return delegate.getQueryString(); + } + + @Override + public String getParameter(String name) { + return delegate.getParameter(name); + } + + @Override + public String[] getParameterValues(String name) { + return delegate.getParameterValues(name); + } + + @Override + public String getHeader(String name) { + return delegate.getHeader(name); + } + + @Override + public Enumeration getHeaders(String name) { + return delegate.getHeaders(name); + } + + @Override + public String getMethod() { + return delegate.getMethod(); + } + + @Override + public HttpServletRequest getRawRequest() { + return delegate; + } +} diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java new file mode 100644 index 00000000..4c890ec0 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java @@ -0,0 +1,71 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.io.IOException; +import java.util.Collection; +import javax.servlet.http.HttpServletResponse; + +/** + * Implementation of {@link HttpResponse} based on a delegate of {@link HttpServletResponse} from the Javax Servlet API. + */ +public class JavaxHttpResponse implements HttpResponse { + + private final HttpServletResponse delegate; + + public JavaxHttpResponse(HttpServletResponse delegate) { + this.delegate = delegate; + } + + @Override + public void addHeader(String name, String value) { + delegate.addHeader(name, value); + } + + @Override + public String getHeader(String name) { + return delegate.getHeader(name); + } + + @Override + public Collection getHeaders(String name) { + return delegate.getHeaders(name); + } + + @Override + public void setStatus(int status) { + delegate.setStatus(status); + } + + @Override + public int getStatus() { + return delegate.getStatus(); + } + + @Override + public void sendRedirect(String location) throws IOException { + delegate.sendRedirect(location); + } + + @Override + public HttpServletResponse getRawResponse() { + return delegate; + } +} diff --git a/settings.gradle b/settings.gradle index 3ed6ec76..c811e2e8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,7 +12,8 @@ dependencyResolutionManagement { library('guava', 'com.google.guava:guava:31.1-jre') library('gson', 'com.google.code.gson:gson:2.9.0') library('jsr305', 'com.google.code.findbugs:jsr305:3.0.2') - library('servlet-api', 'javax.servlet:javax.servlet-api:3.1.0') + library('javax-servlet-api', 'javax.servlet:javax.servlet-api:3.1.0') + library('jakarta-servlet-api', 'jakarta.servlet:jakarta.servlet-api:6.0.0') library('junit4', 'junit:junit:4.13.2') library('junit5', 'org.junit.jupiter:junit-jupiter-api:5.8.2') library('slf4j', 'org.slf4j', 'slf4j-api').versionRef('slf4j') @@ -22,4 +23,4 @@ dependencyResolutionManagement { library('junit-dataprovider', 'com.tngtech.java:junit-dataprovider:1.13.1') } } -} \ No newline at end of file +} From afdd1699f5e26f40c2ec9879d2842a3a62c821e0 Mon Sep 17 00:00:00 2001 From: Eric Giffon Date: Thu, 13 Apr 2023 14:55:45 +0200 Subject: [PATCH 2/6] PLUGINAPI-42 Use new types for identity providers, deprecate javax methods --- .../authentication/BaseIdentityProvider.java | 22 +++++++++++++++++++ .../OAuth2IdentityProvider.java | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java b/plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java index 1c3b86e8..5510204e 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java +++ b/plugin-api/src/main/java/org/sonar/api/server/authentication/BaseIdentityProvider.java @@ -21,6 +21,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.HttpResponse; /** * @since 5.4 @@ -35,16 +37,36 @@ public interface BaseIdentityProvider extends IdentityProvider { interface Context { + /** + * Get the received HTTP request. + * + * @since 9.16 + */ + HttpRequest getHttpRequest(); + + /** + * Get the HTTP response to send. + * + * @since 9.16 + */ + HttpResponse getHttpResponse(); + /** * Get the received HTTP request. * Note - {@code getRequest().getSession()} must not be used in order to support * future clustering of web servers without stateful server sessions. + * + * @deprecated since 9.16. Use {@link #getHttpRequest()} instead. */ + @Deprecated(since = "9.16", forRemoval = true) HttpServletRequest getRequest(); /** * Get the HTTP response to send + * + * @deprecated since 9.16. Use {@link #getHttpResponse()} instead. */ + @Deprecated(since = "9.16", forRemoval = true) HttpServletResponse getResponse(); /** diff --git a/plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java b/plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java index e638e63d..7f93a64f 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java +++ b/plugin-api/src/main/java/org/sonar/api/server/authentication/OAuth2IdentityProvider.java @@ -21,6 +21,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.HttpResponse; /** * @since 5.4 @@ -45,16 +47,36 @@ interface OAuth2Context { */ String getCallbackUrl(); + /** + * Get the received HTTP request. + * + * @since 9.16 + */ + HttpRequest getHttpRequest(); + + /** + * Get the HTTP response to send. + * + * @since 9.16 + */ + HttpResponse getHttpResponse(); + /** * Get the received HTTP request. * Note - {@code getRequest().getSession()} must not be used in order to support * future clustering of web servers without stateful server sessions. + * + * @deprecated since 9.16. Use {@link #getHttpRequest()} instead. */ + @Deprecated(since = "9.16", forRemoval = true) HttpServletRequest getRequest(); /** * Get the HTTP response to send + * + * @deprecated since 9.16. Use {@link #getHttpResponse()} instead. */ + @Deprecated(since = "9.16", forRemoval = true) HttpServletResponse getResponse(); } From 945a31435ad832bda679ab0c8d9108a32ef0e49c Mon Sep 17 00:00:00 2001 From: Jacek Poreda Date: Thu, 13 Apr 2023 18:06:44 +0200 Subject: [PATCH 3/6] PLUGINAPI-42 Introduce HttpFilter class and deprecate ServletFilter which is using javax.servlet.* --- .../java/org/sonar/api/web/FilterChain.java | 44 ++++ .../java/org/sonar/api/web/HttpFilter.java | 103 ++++++++ .../java/org/sonar/api/web/ServletFilter.java | 7 +- .../java/org/sonar/api/web/UrlPattern.java | 197 +++++++++++++++ .../org/sonar/api/web/HttpFilterTest.java | 78 ++++++ .../org/sonar/api/web/UrlPatternTest.java | 230 ++++++++++++++++++ 6 files changed, 656 insertions(+), 3 deletions(-) create mode 100644 plugin-api/src/main/java/org/sonar/api/web/FilterChain.java create mode 100644 plugin-api/src/main/java/org/sonar/api/web/HttpFilter.java create mode 100644 plugin-api/src/main/java/org/sonar/api/web/UrlPattern.java create mode 100644 plugin-api/src/test/java/org/sonar/api/web/HttpFilterTest.java create mode 100644 plugin-api/src/test/java/org/sonar/api/web/UrlPatternTest.java diff --git a/plugin-api/src/main/java/org/sonar/api/web/FilterChain.java b/plugin-api/src/main/java/org/sonar/api/web/FilterChain.java new file mode 100644 index 00000000..3aa2a221 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/web/FilterChain.java @@ -0,0 +1,44 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.web; + +import java.io.IOException; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.HttpResponse; + +/** + * Filters use the FilterChain to invoke the next filter in the chain, or if the calling filter + * is the last filter in the chain, to invoke the resource at the end of the chain. + * + * @see HttpFilter + * @since 9.16 + **/ +public interface FilterChain { + + /** + * Causes the next filter in the chain to be invoked, or if the calling filter is the last filter + * in the chain, causes the resource at the end of the chain to be invoked. + * + * @param request the request to pass along the chain. + * @param response the response to pass along the chain. + */ + void doFilter(HttpRequest request, HttpResponse response) throws IOException; + +} diff --git a/plugin-api/src/main/java/org/sonar/api/web/HttpFilter.java b/plugin-api/src/main/java/org/sonar/api/web/HttpFilter.java new file mode 100644 index 00000000..7b895035 --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/web/HttpFilter.java @@ -0,0 +1,103 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.web; + +import java.io.IOException; + +import org.sonar.api.ExtensionPoint; +import org.sonar.api.server.ServerSide; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.HttpResponse; + +/** + * A filter is an object that performs filtering tasks on either the + * request to a resource (a servlet or static content), or on the response + * from a resource, or both.. + * + * @since 9.16 + */ +@ServerSide +@ExtensionPoint +public abstract class HttpFilter { + + /** + * This method is called exactly once after instantiating the filter. The init + * method must complete successfully before the filter is asked to do any + * filtering work. + */ + public void init() { + } + + + /** + * The doFilter method of the Filter is called by the + * SonarQube each time a request/response pair is passed through the + * chain due to a client request for a resource at the end of the chain. + * The FilterChain passed in to this method allows the Filter to pass + * on the request and response to the next entity in the chain. + * + *

A typical implementation of this method would follow the following + * pattern: + *

    + *
  1. Examine the request + *
  2. Optionally wrap the request object with a custom implementation to + * filter content or headers for input filtering + *
  3. Optionally wrap the response object with a custom implementation to + * filter content or headers for output filtering + *
  4. + *
      + *
    • Either invoke the next entity in the chain + * using the FilterChain object + * (chain.doFilter()), + *
    • or not pass on the request/response pair to + * the next entity in the filter chain to + * block the request processing + *
    + *
  5. Directly set headers on the response after invocation of the + * next entity in the filter chain. + *
+ */ + public abstract void doFilter(HttpRequest request, HttpResponse response, FilterChain chain) throws IOException; + + + /** + * Called by the SonarQube to indicate to a filter that it is being + * taken out of service. + * + *

This method is only called once all threads within the filter's + * doFilter method have exited or after a timeout period has passed. + * After the web container calls this method, it will not call the + * doFilter method again on this instance of the filter. + * + *

This method gives the filter an opportunity to clean up any + * resources that are being held (for example, memory, file handles, + * threads) and make sure that any persistent state is synchronized + * with the filter's current state in memory. + */ + public void destroy() { + } + + /** + * Override to change URL. Default is /* + */ + public UrlPattern doGetPattern() { + return UrlPattern.builder().build(); + } +} diff --git a/plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java b/plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java index 46cbbb14..c5eac494 100644 --- a/plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java +++ b/plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java @@ -28,7 +28,6 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; -import javax.servlet.Filter; import org.sonar.api.ExtensionPoint; import org.sonar.api.server.ServerSide; @@ -38,11 +37,13 @@ import static org.sonar.api.utils.Preconditions.checkArgument; /** + * {@code @deprecated} since 9.16. Use {@link org.sonar.api.web.HttpFilter} instead. * @since 3.1 */ @ServerSide @ExtensionPoint -public abstract class ServletFilter implements Filter { +@Deprecated(forRemoval = true, since = "9.16") +public abstract class ServletFilter implements javax.servlet.Filter { /** * Override to change URL. Default is /* @@ -65,7 +66,7 @@ private UrlPattern(Builder builder) { this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions)); if (builder.inclusionPredicates.isEmpty()) { // because Stream#anyMatch() returns false if stream is empty - this.inclusionPredicates = new Predicate[] {s -> true}; + this.inclusionPredicates = new Predicate[]{s -> true}; } else { this.inclusionPredicates = builder.inclusionPredicates.stream().toArray(Predicate[]::new); } diff --git a/plugin-api/src/main/java/org/sonar/api/web/UrlPattern.java b/plugin-api/src/main/java/org/sonar/api/web/UrlPattern.java new file mode 100644 index 00000000..420cc4fc --- /dev/null +++ b/plugin-api/src/main/java/org/sonar/api/web/UrlPattern.java @@ -0,0 +1,197 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.web; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static org.apache.commons.lang.StringUtils.substringBeforeLast; +import static org.sonar.api.utils.Preconditions.checkArgument; + +public final class UrlPattern { + + private static final String MATCH_ALL = "/*"; + + private final List inclusions; + private final List exclusions; + private final Predicate[] inclusionPredicates; + private final Predicate[] exclusionPredicates; + + private UrlPattern(Builder builder) { + this.inclusions = unmodifiableList(new ArrayList<>(builder.inclusions)); + this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions)); + if (builder.inclusionPredicates.isEmpty()) { + // because Stream#anyMatch() returns false if stream is empty + this.inclusionPredicates = new Predicate[]{s -> true}; + } else { + this.inclusionPredicates = builder.inclusionPredicates.stream().toArray(Predicate[]::new); + } + this.exclusionPredicates = builder.exclusionPredicates.stream().toArray(Predicate[]::new); + } + + public boolean matches(String path) { + return !Arrays.stream(exclusionPredicates).anyMatch(pattern -> pattern.test(path)) && + Arrays.stream(inclusionPredicates).anyMatch(pattern -> pattern.test(path)); + } + + /** + * @since 6.0 + */ + public Collection getInclusions() { + return inclusions; + } + + /** + * @since 6.0 + */ + public Collection getExclusions() { + return exclusions; + } + + public String label() { + return "UrlPattern{" + + "inclusions=[" + convertPatternsToString(inclusions) + "]" + + ", exclusions=[" + convertPatternsToString(exclusions) + "]" + + '}'; + } + + private static String convertPatternsToString(List input) { + StringBuilder output = new StringBuilder(); + if (input.isEmpty()) { + return ""; + } + if (input.size() == 1) { + return output.append(input.get(0)).toString(); + } + return output.append(input.get(0)).append(", ...").toString(); + } + + /** + * Defines only a single inclusion pattern. This is a shortcut for {@code builder().includes(inclusionPattern).build()}. + */ + public static UrlPattern create(String inclusionPattern) { + return builder().includes(inclusionPattern).build(); + } + + /** + * @since 6.0 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * @since 6.0 + */ + public static class Builder { + private static final String WILDCARD_CHAR = "*"; + private static final Collection STATIC_RESOURCES = unmodifiableList(asList( + "*.css", "*.css.map", "*.ico", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.svg", "*.js", "*.js.map", "*.pdf", "/json/*", "*.woff2", + "/static/*", "/robots.txt", "/favicon.ico", "/apple-touch-icon*", "/mstile*")); + + private final Set inclusions = new LinkedHashSet<>(); + private final Set exclusions = new LinkedHashSet<>(); + private final Set> inclusionPredicates = new HashSet<>(); + private final Set> exclusionPredicates = new HashSet<>(); + + private Builder() { + } + + public static Collection staticResourcePatterns() { + return STATIC_RESOURCES; + } + + /** + * Add inclusion patterns. Supported formats are: + *

    + *
  • path prefixed by / and ended by * or /*, for example "/api/foo/*", to match all paths "/api/foo" and "api/api/foo/something/else"
  • + *
  • path prefixed by / and ended by .*, for example "/api/foo.*", to match exact path "/api/foo" with any suffix like "/api/foo.protobuf"
  • + *
  • path prefixed by *, for example "*\/foo", to match all paths "/api/foo" and "something/else/foo"
  • + *
  • path with leading slash and no wildcard, for example "/api/foo", to match exact path "/api/foo"
  • + *
+ */ + public Builder includes(String... includePatterns) { + return includes(asList(includePatterns)); + } + + /** + * Add exclusion patterns. See format described in {@link #includes(String...)} + */ + public Builder includes(Collection includePatterns) { + this.inclusions.addAll(includePatterns); + this.inclusionPredicates.addAll(includePatterns.stream() + .filter(pattern -> !MATCH_ALL.equals(pattern)) + .map(Builder::compile) + .collect(Collectors.toList())); + return this; + } + + public Builder excludes(String... excludePatterns) { + return excludes(asList(excludePatterns)); + } + + public Builder excludes(Collection excludePatterns) { + this.exclusions.addAll(excludePatterns); + this.exclusionPredicates.addAll(excludePatterns.stream() + .map(Builder::compile) + .collect(Collectors.toList())); + return this; + } + + public UrlPattern build() { + return new UrlPattern(this); + } + + private static Predicate compile(String pattern) { + int countStars = pattern.length() - pattern.replace(WILDCARD_CHAR, "").length(); + if (countStars == 0) { + checkArgument(pattern.startsWith("/"), "URL pattern must start with slash '/': %s", pattern); + return url -> url.equals(pattern); + } + checkArgument(countStars == 1, "URL pattern accepts only zero or one wildcard character '*': %s", pattern); + if (pattern.charAt(0) == '/') { + checkArgument(pattern.endsWith(WILDCARD_CHAR), "URL pattern must end with wildcard character '*': %s", pattern); + if (pattern.endsWith("/*")) { + String path = pattern.substring(0, pattern.length() - "/*".length()); + return url -> url.startsWith(path); + } + if (pattern.endsWith(".*")) { + String path = pattern.substring(0, pattern.length() - ".*".length()); + return url -> substringBeforeLast(url, ".").equals(path); + } + String path = pattern.substring(0, pattern.length() - "*".length()); + return url -> url.startsWith(path); + } + checkArgument(pattern.startsWith(WILDCARD_CHAR), "URL pattern must start with wildcard character '*': %s", pattern); + // remove the leading * + String path = pattern.substring(1); + return url -> url.endsWith(path); + } + } +} diff --git a/plugin-api/src/test/java/org/sonar/api/web/HttpFilterTest.java b/plugin-api/src/test/java/org/sonar/api/web/HttpFilterTest.java new file mode 100644 index 00000000..316eadb0 --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/web/HttpFilterTest.java @@ -0,0 +1,78 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.web; + +import java.io.IOException; +import org.junit.Test; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.HttpResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HttpFilterTest { + + @Test + public void filter_should_return_url_pattern() { + HttpFilter filter = new FakeFilter(); + assertThat(filter.doGetPattern()).isNotNull(); + } + + @Test + public void filter_should_apply_to_all_urls_by_default() { + HttpFilter filter = new DefaultFilter(); + assertThat(filter.doGetPattern().matches("/")).isTrue(); + assertThat(filter.doGetPattern().matches("/foo/bar")).isTrue(); + } + + private static class FakeFilter extends HttpFilter { + @Override + public UrlPattern doGetPattern() { + return UrlPattern.create("/fake"); + } + + @Override + public void init() { + + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, org.sonar.api.web.FilterChain chain) { + + } + + public void destroy() { + } + } + + private static class DefaultFilter extends HttpFilter { + @Override + public void init() { + + } + + @Override + public void doFilter(HttpRequest request, HttpResponse response, org.sonar.api.web.FilterChain chain) throws IOException { + + } + + public void destroy() { + } + } +} diff --git a/plugin-api/src/test/java/org/sonar/api/web/UrlPatternTest.java b/plugin-api/src/test/java/org/sonar/api/web/UrlPatternTest.java new file mode 100644 index 00000000..0510f9cb --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/web/UrlPatternTest.java @@ -0,0 +1,230 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.web; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class UrlPatternTest { + + @Test + public void include_all() { + UrlPattern pattern = UrlPattern.create("/*"); + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo/ooo")).isTrue(); + + assertThat(pattern.getInclusions()).containsOnly("/*"); + assertThat(pattern.getExclusions()).isEmpty(); + } + + @Test + public void include_end_of_url() { + UrlPattern pattern = UrlPattern.create("*foo"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/hello/foo")).isTrue(); + assertThat(pattern.matches("/hello/bar")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo2")).isFalse(); + } + + @Test + public void include_beginning_of_url() { + UrlPattern pattern = UrlPattern.create("/foo/*"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo/bar")).isTrue(); + assertThat(pattern.matches("/bar")).isFalse(); + } + + @Test + public void include_exact_url() { + UrlPattern pattern = UrlPattern.create("/foo"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo/")).isFalse(); + assertThat(pattern.matches("/bar")).isFalse(); + } + + @Test + public void exclude_all() { + UrlPattern pattern = UrlPattern.builder() + .excludes("/*") + .build(); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo/ooo")).isFalse(); + } + + @Test + public void exclude_end_of_url() { + UrlPattern pattern = UrlPattern.builder() + .excludes("*foo") + .build(); + + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/hello/foo")).isFalse(); + assertThat(pattern.matches("/hello/bar")).isTrue(); + assertThat(pattern.matches("/foo")).isFalse(); + assertThat(pattern.matches("/foo2")).isTrue(); + } + + @Test + public void exclude_beginning_of_url() { + UrlPattern pattern = UrlPattern.builder() + .excludes("/foo/*") + .build(); + + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo")).isFalse(); + assertThat(pattern.matches("/foo/bar")).isFalse(); + assertThat(pattern.matches("/bar")).isTrue(); + } + + @Test + public void exclude_exact_url() { + UrlPattern pattern = UrlPattern.builder() + .excludes("/foo") + .build(); + + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo")).isFalse(); + assertThat(pattern.matches("/foo/")).isTrue(); + assertThat(pattern.matches("/bar")).isTrue(); + } + + @Test + public void use_multiple_include_patterns() { + UrlPattern pattern = UrlPattern.builder() + .includes("/foo", "/foo2") + .build(); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo2")).isTrue(); + assertThat(pattern.matches("/foo/")).isFalse(); + assertThat(pattern.matches("/bar")).isFalse(); + } + + @Test + public void use_multiple_exclude_patterns() { + UrlPattern pattern = UrlPattern.builder() + .excludes("/foo", "/foo2") + .build(); + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo")).isFalse(); + assertThat(pattern.matches("/foo2")).isFalse(); + assertThat(pattern.matches("/foo/")).isTrue(); + assertThat(pattern.matches("/bar")).isTrue(); + } + + @Test + public void use_include_and_exclude_patterns() { + UrlPattern pattern = UrlPattern.builder() + .includes("/foo/*", "/foo/lo*") + .excludes("/foo/login", "/foo/logout", "/foo/list") + .build(); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo/login")).isFalse(); + assertThat(pattern.matches("/foo/logout")).isFalse(); + assertThat(pattern.matches("/foo/list")).isFalse(); + assertThat(pattern.matches("/foo/locale")).isTrue(); + assertThat(pattern.matches("/foo/index")).isTrue(); + } + + @Test + public void use_include_and_exclude_prefix() { + UrlPattern pattern = UrlPattern.builder() + .includes("/foo_2") + .excludes("/foo") + .build(); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo_2")).isTrue(); + assertThat(pattern.matches("/foo")).isFalse(); + } + + @Test + public void exclude_pattern_has_higher_priority_than_include_pattern() { + UrlPattern pattern = UrlPattern.builder() + .includes("/foo") + .excludes("/foo") + .build(); + assertThat(pattern.matches("/foo")).isFalse(); + } + + @Test + public void accept_empty_patterns() { + UrlPattern pattern = UrlPattern.builder() + .excludes() + .includes() + .build(); + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo/bar")).isTrue(); + } + + @Test + public void create_throws_IAE_if_empty_url() { + assertThatThrownBy(() -> UrlPattern.create("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("URL pattern must start with slash '/': "); + } + + @Test + public void getUrl_returns_single_inclusion() { + assertThat(UrlPattern.create("/*").getInclusions()).containsOnly("/*"); + assertThat(UrlPattern.create("/foo/bar").getInclusions()).containsOnly("/foo/bar"); + } + + @Test + public void test_staticResourcePatterns() { + assertThat(UrlPattern.Builder.staticResourcePatterns()).containsOnly( + "*.css", + "*.css.map", + "*.ico", + "*.png", + "*.jpg", + "*.jpeg", + "*.gif", + "*.svg", + "*.js", + "*.js.map", + "*.pdf", + "*.woff2", + "/json/*", + "/static/*", + "/robots.txt", + "/favicon.ico", + "/apple-touch-icon*", + "/mstile*"); + } + + @Test + public void test_label() { + assertThat(UrlPattern.builder().build().label()).isEqualTo("UrlPattern{inclusions=[], exclusions=[]}"); + assertThat(UrlPattern.builder() + .includes("/foo/*") + .excludes("/foo/login") + .build().label()).isEqualTo("UrlPattern{inclusions=[/foo/*], exclusions=[/foo/login]}"); + assertThat(UrlPattern.builder() + .includes("/foo/*", "/foo/lo*") + .excludes("/foo/login", "/foo/logout", "/foo/list") + .build().label()).isEqualTo("UrlPattern{inclusions=[/foo/*, ...], exclusions=[/foo/login, ...]}"); + } +} From f5e6e85a0f54982231cf48ec05942ab7fea98ca6 Mon Sep 17 00:00:00 2001 From: Jacek Poreda Date: Fri, 14 Apr 2023 12:36:19 +0200 Subject: [PATCH 4/6] PLUGINAPI-42 Add new methods required by filters in SonarQube --- .../org/sonar/api/server/http/HttpRequest.java | 9 ++++++++- .../sonar/api/server/http/HttpResponse.java | 18 ++++++++++++++++++ .../api/server/http/JakartaHttpRequest.java | 5 +++++ .../api/server/http/JakartaHttpResponse.java | 16 ++++++++++++++++ .../api/server/http/JavaxHttpRequest.java | 5 +++++ .../api/server/http/JavaxHttpResponse.java | 16 ++++++++++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java index bd17c343..6cedbcab 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java @@ -66,6 +66,13 @@ public interface HttpRequest { */ String getQueryString(); + /** + * Returns the portion of the request URI that indicates the context of the request. The context path always comes first + * in a request URI. The path starts with a "/" character but does not end with a "/" character. For servlets in the + * default (root) context, this method returns "". The container does not decode this string. + */ + String getContextPath(); + /** * Returns the value of a request parameter as a String, or null if the parameter does not exist. * You should only use this method when you are sure the parameter has only one value. If the parameter might have more than one value, @@ -74,7 +81,7 @@ public interface HttpRequest { String getParameter(String name); /** - * Returns an array containing all of the values the given request parameter has, or null if the parameter does not exist. + * Returns an array containing all the values the given request parameter has, or null if the parameter does not exist. */ String[] getParameterValues(String name); diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java index 424ecdca..17d6a760 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/HttpResponse.java @@ -20,6 +20,7 @@ package org.sonar.api.server.http; import java.io.IOException; +import java.io.PrintWriter; import java.util.Collection; /** @@ -55,6 +56,23 @@ public interface HttpResponse { */ int getStatus(); + /** + * Sets the content type of the response being sent to the client, if the response has not been committed yet. + */ + void setContentType(String contentType); + + /** + * Returns a PrintWriter object that can send character text to the client. Calling flush() + * on the PrintWriter commits the response. + */ + PrintWriter getWriter() throws IOException; + + /** + * Sets a response header with the given name and value. If the header had already been set, the new value overwrites + * the previous one. + */ + void setHeader(String name, String value); + /** * Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. * The buffer will be replaced with the data set by this method. diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java index 4a80ebf4..4e57e10f 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpRequest.java @@ -68,6 +68,11 @@ public String getQueryString() { return delegate.getQueryString(); } + @Override + public String getContextPath() { + return delegate.getContextPath(); + } + @Override public String getParameter(String name) { return delegate.getParameter(name); diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java index f1b9e197..96a7e28e 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JakartaHttpResponse.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.PrintWriter; import java.util.Collection; /** @@ -59,6 +60,21 @@ public int getStatus() { return delegate.getStatus(); } + @Override + public void setContentType(String contentType) { + delegate.setContentType(contentType); + } + + @Override + public PrintWriter getWriter() throws IOException { + return delegate.getWriter(); + } + + @Override + public void setHeader(String name, String value) { + delegate.setHeader(name, value); + } + @Override public void sendRedirect(String location) throws IOException { delegate.sendRedirect(location); diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java index d2c29224..84c9573d 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpRequest.java @@ -68,6 +68,11 @@ public String getQueryString() { return delegate.getQueryString(); } + @Override + public String getContextPath() { + return delegate.getContextPath(); + } + @Override public String getParameter(String name) { return delegate.getParameter(name); diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java index 4c890ec0..f0b8ea36 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/JavaxHttpResponse.java @@ -20,6 +20,7 @@ package org.sonar.api.server.http; import java.io.IOException; +import java.io.PrintWriter; import java.util.Collection; import javax.servlet.http.HttpServletResponse; @@ -59,6 +60,21 @@ public int getStatus() { return delegate.getStatus(); } + @Override + public void setContentType(String contentType) { + delegate.setContentType(contentType); + } + + @Override + public PrintWriter getWriter() throws IOException { + return delegate.getWriter(); + } + + @Override + public void setHeader(String name, String value) { + delegate.setHeader(name, value); + } + @Override public void sendRedirect(String location) throws IOException { delegate.sendRedirect(location); From 8c1002093fefac601392639a78faa8765b55f51f Mon Sep 17 00:00:00 2001 From: Antoine Vinot Date: Thu, 13 Apr 2023 15:30:15 +0200 Subject: [PATCH 5/6] PLUGINAPI-42 Deprecate javax.servlet usage in org.sonar.api.security package --- .../org/sonar/api/security/Authenticator.java | 31 +++++++++++++++++++ .../api/security/ExternalGroupsProvider.java | 29 +++++++++++++++++ .../api/security/ExternalUsersProvider.java | 29 +++++++++++++++++ .../security/ExternalGroupsProviderTest.java | 9 +++--- .../security/ExternalUsersProviderTest.java | 7 ++--- .../sonar/api/security/SecurityRealmTest.java | 4 +-- 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/plugin-api/src/main/java/org/sonar/api/security/Authenticator.java b/plugin-api/src/main/java/org/sonar/api/security/Authenticator.java index 820efdb4..5dfbeebb 100644 --- a/plugin-api/src/main/java/org/sonar/api/security/Authenticator.java +++ b/plugin-api/src/main/java/org/sonar/api/security/Authenticator.java @@ -23,6 +23,8 @@ import javax.servlet.http.HttpServletRequest; import org.sonar.api.ExtensionPoint; import org.sonar.api.server.ServerSide; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.JavaxHttpRequest; import static java.util.Objects.requireNonNull; @@ -43,13 +45,31 @@ public abstract class Authenticator { public static final class Context { private String username; private String password; + /** + * @deprecated since 9.16 use {@link #httpRequest} instead + */ + @Deprecated(since = "9.16", forRemoval = true) private HttpServletRequest request; + private HttpRequest httpRequest; + /** + * @deprecated since 9.16 use {@link #Context(String, String, HttpRequest)} instead + */ + @Deprecated(since = "9.16", forRemoval = true) public Context(@Nullable String username, @Nullable String password, HttpServletRequest request) { requireNonNull(request); this.request = request; this.username = username; this.password = password; + this.httpRequest = new JavaxHttpRequest(request); + } + + public Context(@Nullable String username, @Nullable String password, HttpRequest httpRequest) { + requireNonNull(httpRequest); + this.httpRequest = httpRequest; + this.username = username; + this.password = password; + this.request = (HttpServletRequest) httpRequest.getRawRequest(); } /** @@ -66,8 +86,19 @@ public String getPassword() { return password; } + /** + * @deprecated since 9.16. Use {@link #getHttpRequest()} instead. + */ + @Deprecated(since = "9.16", forRemoval = true) public HttpServletRequest getRequest() { return request; } + + /** + * @since 9.16 + */ + public HttpRequest getHttpRequest() { + return httpRequest; + } } } diff --git a/plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java b/plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java index cf55c2a0..ee141628 100644 --- a/plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java +++ b/plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java @@ -22,6 +22,8 @@ import java.util.Collection; import javax.annotation.CheckForNull; import javax.servlet.http.HttpServletRequest; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.JavaxHttpRequest; /** * Note that prefix "do" for names of methods is reserved for future enhancements, thus should not be used in subclasses. @@ -45,19 +47,46 @@ public Collection doGetGroups(Context context) { public static final class Context { private String username; + /** + * @deprecated since 9.16 use {@link #httpRequest} instead + */ + @Deprecated(since = "9.16", forRemoval = true) private HttpServletRequest request; + private HttpRequest httpRequest; + /** + * @deprecated since 9.16 use {@link #Context(String, HttpRequest)} instead + */ + @Deprecated(since = "9.16", forRemoval = true) public Context(String username, HttpServletRequest request) { this.username = username; this.request = request; + this.httpRequest = new JavaxHttpRequest(request); + } + + public Context(String username, HttpRequest httpRequest) { + this.username = username; + this.httpRequest = httpRequest; + this.request = (HttpServletRequest) httpRequest.getRawRequest(); } public String getUsername() { return username; } + /** + * @deprecated since 9.16. Use {@link #getHttpRequest()} instead. + */ + @Deprecated(since = "9.16", forRemoval = true) public HttpServletRequest getRequest() { return request; } + + /** + * @since 9.16 + */ + public HttpRequest getHttpRequest() { + return httpRequest; + } } } diff --git a/plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java b/plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java index 5688703b..0fa9c99f 100644 --- a/plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java +++ b/plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java @@ -21,6 +21,8 @@ import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; +import org.sonar.api.server.http.HttpRequest; +import org.sonar.api.server.http.JavaxHttpRequest; /** * Note that prefix "do" for names of methods is reserved for future enhancements, thus should not be used in subclasses. @@ -42,19 +44,46 @@ public UserDetails doGetUserDetails(Context context) { public static final class Context { private String username; + /** + * @deprecated since 9.16 use {@link #httpRequest} instead + */ + @Deprecated(since = "9.16", forRemoval = true) private HttpServletRequest request; + private HttpRequest httpRequest; + /** + * @deprecated since 9.16 use {@link #Context(String, HttpRequest)} instead + */ + @Deprecated(since = "9.16", forRemoval = true) public Context(@Nullable String username, HttpServletRequest request) { this.username = username; this.request = request; + this.httpRequest = new JavaxHttpRequest(request); + } + + public Context(@Nullable String username, HttpRequest httpRequest) { + this.username = username; + this.httpRequest = httpRequest; + this.request = (HttpServletRequest) httpRequest.getRawRequest(); } public String getUsername() { return username; } + /** + * @deprecated since 9.16. Use {@link #getHttpRequest()} instead. + */ + @Deprecated(since = "9.16", forRemoval = true) public HttpServletRequest getRequest() { return request; } + + /** + * @since 9.16 + */ + public HttpRequest getHttpRequest() { + return httpRequest; + } } } diff --git a/plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java b/plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java index fc6e7a43..703cabd6 100644 --- a/plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java +++ b/plugin-api/src/test/java/org/sonar/api/security/ExternalGroupsProviderTest.java @@ -20,14 +20,13 @@ package org.sonar.api.security; import com.google.common.base.Preconditions; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; import org.junit.Test; +import org.sonar.api.server.http.HttpRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -40,7 +39,7 @@ public void doGetGroupsNoOverride() { String userName = "foo"; assertThat(groupsProvider.doGetGroups(new ExternalGroupsProvider.Context(userName, - mock(HttpServletRequest.class)))).isNull(); + mock(HttpRequest.class)))).isNull(); } @Test @@ -51,7 +50,7 @@ public void doGetGroupsTests() { @Override public Collection doGetGroups(Context context) { Preconditions.checkNotNull(context.getUsername()); - Preconditions.checkNotNull(context.getRequest()); + Preconditions.checkNotNull(context.getHttpRequest()); return userGroupsMap.get(context.getUsername()); } @@ -63,7 +62,7 @@ public Collection doGetGroups(Context context) { private static void runDoGetGroupsTests(ExternalGroupsProvider groupsProvider, Map> userGroupsMap) { for (Map.Entry> userGroupMapEntry : userGroupsMap.entrySet()) { Collection groups = groupsProvider.doGetGroups(new ExternalGroupsProvider.Context( - userGroupMapEntry.getKey(), mock(HttpServletRequest.class))); + userGroupMapEntry.getKey(), mock(HttpRequest.class))); assertThat(groups).isEqualTo(userGroupMapEntry.getValue()); } } diff --git a/plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java b/plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java index a9d0c16c..e22baf5e 100644 --- a/plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java +++ b/plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java @@ -21,8 +21,7 @@ import com.google.common.base.Preconditions; import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; +import org.sonar.api.server.http.HttpRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -35,14 +34,14 @@ public void doGetUserDetails() { @Override public UserDetails doGetUserDetails(Context context) { Preconditions.checkNotNull(context.getUsername()); - Preconditions.checkNotNull(context.getRequest()); + Preconditions.checkNotNull(context.getHttpRequest()); UserDetails user = new UserDetails(); user.setName(context.getUsername()); user.setEmail("foo@bar.com"); return user; } }; - UserDetails user = provider.doGetUserDetails(new ExternalUsersProvider.Context("foo", mock(HttpServletRequest.class))); + UserDetails user = provider.doGetUserDetails(new ExternalUsersProvider.Context("foo", mock(HttpRequest.class))); assertThat(user.getName()).isEqualTo("foo"); assertThat(user.getEmail()).isEqualTo("foo@bar.com"); diff --git a/plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java b/plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java index 2e043494..5ebd8bb3 100644 --- a/plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java +++ b/plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java @@ -21,7 +21,7 @@ import org.junit.Test; -import javax.servlet.http.HttpServletRequest; +import org.sonar.api.server.http.HttpRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -52,7 +52,7 @@ public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { } }; Authenticator proxy = realm.doGetAuthenticator(); - Authenticator.Context context = new Authenticator.Context("foo", "bar", mock(HttpServletRequest.class)); + Authenticator.Context context = new Authenticator.Context("foo", "bar", mock(HttpRequest.class)); proxy.doAuthenticate(context); verify(deprecatedAuthenticator).authenticate("foo", "bar"); From 3e275a16e0d2e6e44efef7426bffeef5908d1a52 Mon Sep 17 00:00:00 2001 From: Eric Giffon Date: Tue, 18 Apr 2023 10:54:27 +0200 Subject: [PATCH 6/6] PLUGINAPI-42 Add unit tests --- .../sonar/api/server/http/HttpRequest.java | 1 - .../server/http/JakartaHttpRequestTest.java | 67 +++++++++++++++++++ .../server/http/JakartaHttpResponseTest.java | 63 +++++++++++++++++ .../api/server/http/JavaxHttpRequestTest.java | 67 +++++++++++++++++++ .../server/http/JavaxHttpResponseTest.java | 63 +++++++++++++++++ 5 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpRequestTest.java create mode 100644 plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpResponseTest.java create mode 100644 plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpRequestTest.java create mode 100644 plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpResponseTest.java diff --git a/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java index 6cedbcab..30497300 100644 --- a/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java +++ b/plugin-api/src/main/java/org/sonar/api/server/http/HttpRequest.java @@ -28,7 +28,6 @@ */ public interface HttpRequest { - /** * Returns the port number to which the request was sent. */ diff --git a/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpRequestTest.java b/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpRequestTest.java new file mode 100644 index 00000000..1b7339e7 --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpRequestTest.java @@ -0,0 +1,67 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class JakartaHttpRequestTest { + + @Test + public void initRequest() { + HttpServletRequest requestMock = mock(HttpServletRequest.class); + when(requestMock.getServerPort()).thenReturn(80); + when(requestMock.isSecure()).thenReturn(true); + when(requestMock.getScheme()).thenReturn("https"); + when(requestMock.getServerName()).thenReturn("hostname"); + when(requestMock.getRequestURL()).thenReturn(new StringBuffer("https://hostname:80/path")); + when(requestMock.getRequestURI()).thenReturn("/path"); + when(requestMock.getQueryString()).thenReturn("param1=value1"); + when(requestMock.getContextPath()).thenReturn("/path"); + when(requestMock.getMethod()).thenReturn("POST"); + when(requestMock.getParameter("param1")).thenReturn("value1"); + when(requestMock.getParameterValues("param1")).thenReturn(new String[]{"value1"}); + when(requestMock.getHeader("header1")).thenReturn("hvalue1"); + Enumeration headers = mock(Enumeration.class); + when(requestMock.getHeaders("header1")).thenReturn(headers); + + JakartaHttpRequest request = new JakartaHttpRequest(requestMock); + + assertThat(request.getRawRequest()).isSameAs(requestMock); + assertThat(request.getServerPort()).isEqualTo(80); + assertThat(request.isSecure()).isTrue(); + assertThat(request.getScheme()).isEqualTo("https"); + assertThat(request.getServerName()).isEqualTo("hostname"); + assertThat(request.getRequestURL()).isEqualTo("https://hostname:80/path"); + assertThat(request.getRequestURI()).isEqualTo("/path"); + assertThat(request.getQueryString()).isEqualTo("param1=value1"); + assertThat(request.getContextPath()).isEqualTo("/path"); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getParameter("param1")).isEqualTo("value1"); + assertThat(request.getParameterValues("param1")).containsExactly("value1"); + assertThat(request.getHeader("header1")).isEqualTo("hvalue1"); + assertThat(request.getHeaders("header1")).isEqualTo(headers); + } +} diff --git a/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpResponseTest.java b/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpResponseTest.java new file mode 100644 index 00000000..4c62e296 --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/server/http/JakartaHttpResponseTest.java @@ -0,0 +1,63 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class JakartaHttpResponseTest { + + @Test + public void initResponse() throws IOException { + HttpServletResponse responseMock = mock(HttpServletResponse.class); + when(responseMock.getHeader("h1")).thenReturn("hvalue1"); + when(responseMock.getHeaders("h1")).thenReturn(List.of("hvalue1")); + when(responseMock.getStatus()).thenReturn(200); + PrintWriter writer = mock(PrintWriter.class); + when(responseMock.getWriter()).thenReturn(writer); + + JakartaHttpResponse response = new JakartaHttpResponse(responseMock); + + assertThat(response.getRawResponse()).isSameAs(responseMock); + assertThat(response.getHeader("h1")).isEqualTo("hvalue1"); + assertThat(response.getHeaders("h1")).asList().containsExactly("hvalue1"); + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getWriter()).isEqualTo(writer); + + response.addHeader("h2", "hvalue2"); + response.setHeader("h3", "hvalue3"); + response.setStatus(201); + response.setContentType("text/plain"); + response.sendRedirect("http://redirect"); + verify(responseMock).addHeader("h2", "hvalue2"); + verify(responseMock).setHeader("h3", "hvalue3"); + verify(responseMock).setStatus(201); + verify(responseMock).setContentType("text/plain"); + verify(responseMock).sendRedirect("http://redirect"); + } +} diff --git a/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpRequestTest.java b/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpRequestTest.java new file mode 100644 index 00000000..15881771 --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpRequestTest.java @@ -0,0 +1,67 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class JavaxHttpRequestTest { + + @Test + public void initRequest() { + HttpServletRequest requestMock = mock(HttpServletRequest.class); + when(requestMock.getServerPort()).thenReturn(80); + when(requestMock.isSecure()).thenReturn(true); + when(requestMock.getScheme()).thenReturn("https"); + when(requestMock.getServerName()).thenReturn("hostname"); + when(requestMock.getRequestURL()).thenReturn(new StringBuffer("https://hostname:80/path")); + when(requestMock.getRequestURI()).thenReturn("/path"); + when(requestMock.getQueryString()).thenReturn("param1=value1"); + when(requestMock.getContextPath()).thenReturn("/path"); + when(requestMock.getMethod()).thenReturn("POST"); + when(requestMock.getParameter("param1")).thenReturn("value1"); + when(requestMock.getParameterValues("param1")).thenReturn(new String[]{"value1"}); + when(requestMock.getHeader("header1")).thenReturn("hvalue1"); + Enumeration headers = mock(Enumeration.class); + when(requestMock.getHeaders("header1")).thenReturn(headers); + + JavaxHttpRequest request = new JavaxHttpRequest(requestMock); + + assertThat(request.getRawRequest()).isSameAs(requestMock); + assertThat(request.getServerPort()).isEqualTo(80); + assertThat(request.isSecure()).isTrue(); + assertThat(request.getScheme()).isEqualTo("https"); + assertThat(request.getServerName()).isEqualTo("hostname"); + assertThat(request.getRequestURL()).isEqualTo("https://hostname:80/path"); + assertThat(request.getRequestURI()).isEqualTo("/path"); + assertThat(request.getQueryString()).isEqualTo("param1=value1"); + assertThat(request.getContextPath()).isEqualTo("/path"); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getParameter("param1")).isEqualTo("value1"); + assertThat(request.getParameterValues("param1")).containsExactly("value1"); + assertThat(request.getHeader("header1")).isEqualTo("hvalue1"); + assertThat(request.getHeaders("header1")).isEqualTo(headers); + } +} diff --git a/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpResponseTest.java b/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpResponseTest.java new file mode 100644 index 00000000..39e35ade --- /dev/null +++ b/plugin-api/src/test/java/org/sonar/api/server/http/JavaxHttpResponseTest.java @@ -0,0 +1,63 @@ +/* + * Sonar Plugin API + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.server.http; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class JavaxHttpResponseTest { + + @Test + public void initResponse() throws IOException { + HttpServletResponse responseMock = mock(HttpServletResponse.class); + when(responseMock.getHeader("h1")).thenReturn("hvalue1"); + when(responseMock.getHeaders("h1")).thenReturn(List.of("hvalue1")); + when(responseMock.getStatus()).thenReturn(200); + PrintWriter writer = mock(PrintWriter.class); + when(responseMock.getWriter()).thenReturn(writer); + + JavaxHttpResponse response = new JavaxHttpResponse(responseMock); + + assertThat(response.getRawResponse()).isSameAs(responseMock); + assertThat(response.getHeader("h1")).isEqualTo("hvalue1"); + assertThat(response.getHeaders("h1")).asList().containsExactly("hvalue1"); + assertThat(response.getStatus()).isEqualTo(200); + assertThat(response.getWriter()).isEqualTo(writer); + + response.addHeader("h2", "hvalue2"); + response.setHeader("h3", "hvalue3"); + response.setStatus(201); + response.setContentType("text/plain"); + response.sendRedirect("http://redirect"); + verify(responseMock).addHeader("h2", "hvalue2"); + verify(responseMock).setHeader("h3", "hvalue3"); + verify(responseMock).setStatus(201); + verify(responseMock).setContentType("text/plain"); + verify(responseMock).sendRedirect("http://redirect"); + } +}