-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added sanitization filter for request sanitization #78
Changes from all commits
4094d3e
9c4b7bc
36b5fdb
7ff2e5d
3caac81
fc6a6f8
0f05622
f9b5c06
808643a
30128bb
d93d616
6fe53b7
458e989
179ec79
2d2dec8
e6541a2
2806314
0f1a202
573ace1
bfe04e8
9c9d440
4af29a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package com.salessparrow.api.filter; | ||
|
||
import jakarta.servlet.Filter; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.FilterConfig; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.ServletRequest; | ||
import jakarta.servlet.ServletResponse; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import org.jsoup.Jsoup; | ||
import org.jsoup.nodes.Document; | ||
import org.jsoup.safety.Safelist; | ||
import org.jsoup.nodes.Entities.EscapeMode; | ||
|
||
import com.salessparrow.api.lib.wrappers.SanitizedRequestWrapper; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.Enumeration; | ||
|
||
/** | ||
* Class to sanitize the request | ||
*/ | ||
public class SanitizationFilter implements Filter { | ||
|
||
@Override | ||
public void init(FilterConfig filterConfig) { | ||
} | ||
|
||
/** | ||
* Method to sanitize the request | ||
* @param servletRequest - Servlet request object | ||
* @param servletResponse - Servlet response object | ||
* @param chain - Filter chain - Filter chain | ||
* @throws IOException - IOException | ||
* @throws ServletException - ServletException | ||
* @return void | ||
*/ | ||
@Override | ||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) | ||
throws IOException, ServletException { | ||
if (!(servletRequest instanceof HttpServletRequest)) { | ||
throw new ServletException("Can only process HttpServletRequest"); | ||
} | ||
|
||
HttpServletRequest request = (HttpServletRequest) servletRequest; | ||
SanitizedRequestWrapper sanitizedRequest = sanitizeRequestBody(request); | ||
sanitizeRequestParams(request, sanitizedRequest); | ||
sanitizeRequestHeaders(request, sanitizedRequest); | ||
|
||
chain.doFilter(sanitizedRequest, servletResponse); | ||
} | ||
|
||
/** | ||
* Method to sanitize the request body | ||
* @param request - Servlet request object | ||
* @return void | ||
*/ | ||
private SanitizedRequestWrapper sanitizeRequestBody(HttpServletRequest request) { | ||
String originalBody = getRequestBody(request); | ||
String sanitizedBody = sanitizeHtml(originalBody); | ||
|
||
return new SanitizedRequestWrapper(request, sanitizedBody); | ||
} | ||
Comment on lines
+59
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method |
||
|
||
/** | ||
* Method to sanitize the request params | ||
* @return void | ||
*/ | ||
private void sanitizeRequestParams(HttpServletRequest request, SanitizedRequestWrapper sanitizedRequest) { | ||
request.getParameterMap().forEach((key, values) -> { | ||
for (int index = 0; index < values.length; index++) { | ||
String sanitizedValue = sanitizeHtml(values[index]); | ||
sanitizedRequest.setParameter(key, sanitizedValue); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Method to sanitize the request headers | ||
* @return void | ||
*/ | ||
private void sanitizeRequestHeaders(HttpServletRequest request, SanitizedRequestWrapper sanitizedRequest) { | ||
Enumeration<String> headerNames = request.getHeaderNames(); | ||
|
||
while (headerNames != null && headerNames.hasMoreElements()) { | ||
String headerName = headerNames.nextElement(); | ||
Enumeration<String> headerValues = request.getHeaders(headerName); | ||
|
||
while (headerValues != null && headerValues.hasMoreElements()) { | ||
String headerValue = headerValues.nextElement(); | ||
String sanitizedValue = sanitizeHtml(headerValue); | ||
sanitizedRequest.setHeader(headerName, sanitizedValue); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Method to get the request body | ||
* @param request - Servlet request object | ||
* @return String - Request body | ||
*/ | ||
private String getRequestBody(HttpServletRequest request) { | ||
try { | ||
BufferedReader reader = request.getReader(); | ||
String line; | ||
// TODO: Consider using a more efficient way to read the request body, such as | ||
// streaming | ||
StringBuilder requestBody = new StringBuilder(); | ||
while ((line = reader.readLine()) != null) { | ||
requestBody.append(line); | ||
} | ||
return requestBody.toString(); | ||
} | ||
catch (IOException e) { | ||
throw new RuntimeException("Error reading request body: ", e.getCause()); | ||
} | ||
Comment on lines
+104
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The BufferedReader is not closed after use which can lead to resource leaks. Always close resources in a finally block or use try-with-resources statement. - BufferedReader reader = request.getReader();
- String line;
- StringBuilder requestBody = new StringBuilder();
- while ((line = reader.readLine()) != null) {
- requestBody.append(line);
- }
- return requestBody.toString();
+ try (BufferedReader reader = request.getReader()) {
+ String line;
+ StringBuilder requestBody = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ requestBody.append(line);
+ }
+ return requestBody.toString();
+ } catch (IOException e) {
+ throw new RuntimeException("Error reading request body: ", e.getCause());
+ } |
||
} | ||
Comment on lines
+104
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method |
||
|
||
/** | ||
* Sanitizes a given HTML string by removing all HTML tags and attributes, leaving | ||
* only the text content. Also handles basic HTML entity escaping using the 'base' | ||
* escape mode. | ||
* | ||
* This function uses the Jsoup library's 'clean' method with a 'none' safelist, which | ||
* means no HTML tags are allowed and will be removed. | ||
* | ||
* The function also disables pretty-printing to ensure the resulting HTML is not | ||
* reformatted, and sets the escape mode to 'base' to handle basic HTML entities like | ||
* & and <. | ||
* | ||
* If the input is null or empty, the function returns an empty string. | ||
* @param input The original HTML string that needs to be sanitized. | ||
* @return A sanitized version of the input string, safe for display as text. | ||
*/ | ||
private String sanitizeHtml(String input) { | ||
if (input == null || input.isEmpty()) { | ||
return ""; | ||
} | ||
|
||
Safelist safelist = Safelist.none(); | ||
|
||
Document.OutputSettings outputSettings = new Document.OutputSettings(); | ||
outputSettings.prettyPrint(false); | ||
outputSettings.escapeMode(EscapeMode.base); | ||
|
||
String sanitizedInput = Jsoup.clean(input, "", safelist, outputSettings); | ||
|
||
return sanitizedInput; | ||
} | ||
Comment on lines
+136
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method |
||
|
||
/** | ||
* Method to destroy the filter | ||
*/ | ||
@Override | ||
public void destroy() { | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,7 @@ public static String generateHeaderLogString(HttpServletRequest request) { | |
} | ||
|
||
/** | ||
* <<<<<<< HEAD Encode plain text to base64 | ||
* Encode plain text to base64 | ||
* @param plainText - String to be encoded | ||
* @return String - Encoded string | ||
*/ | ||
|
@@ -160,4 +160,25 @@ public String getDateFormatFromDatetime(Date date) { | |
return null; | ||
} | ||
|
||
/** | ||
* Unescape special characters in a string intended for plaintext contexts only. | ||
* | ||
* This function is intended for use only with strings that will be rendered as | ||
* plaintext. It is NOT suitable for HTML strings, XML strings, or any other context | ||
* where special characters may have syntactic meaning. | ||
* | ||
* Current Implementation: - The ampersand ("&") is unescaped to "&" | ||
* | ||
* Future versions may include additional un-escaping rules as needed. | ||
* @param input The original string containing escaped special characters. | ||
* @return A new string where certain special characters have been unescaped. | ||
*/ | ||
public String unEscapeSpecialCharactersForPlainText(String input) { | ||
return input.replace("&", "&"); | ||
} | ||
kreloaded marked this conversation as resolved.
Show resolved
Hide resolved
kreloaded marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public String replaceNewLineWithBreak(String input) { | ||
return input.replace("\n", "<br>"); | ||
} | ||
|
||
} | ||
Comment on lines
160
to
184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two new methods The The - public String replaceNewLineWithBreak(String input) {
- return input.replace("\n", "<br>");
- }
+ public String replaceNewLineWithBreak(String input) {
+ return sanitizeHtml(input.replace("\n", "<br>"));
+ } Where |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filter should be able to handle all types of
ServletRequest
. Instead of throwing an exception when the request is not an instance ofHttpServletRequest
, consider skipping the sanitization process and just pass the request to the next filter in the chain.