I have a HeaderFilter containing simple String which I want to add to a servlet's html.
When I invoke chain.doFilter(req, resp) in HeaderFilter doFilter() method, the mentioned text is invisible and I thought it could be somehow overwritten? However, when I do not invoke chain.doFilter(req, resp), the text is visible but the rest is not.
What is the problem?
So that's my code in HeaderFilter class:
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class HeaderFilter implements Filter {
private String header = "<table cellpadding='2' cellspacing='2' border='1' width='100%'>"
+ "<tbody><tr><td valign='Top' bgcolor='#000099'>"
+ "<div align='Center'><font color='#ffffff'>Header</font></div></td>"
+ "</tr></tbody></table>";
private Properties encodings = new Properties();
public void init(FilterConfig fc) throws ServletException {
}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
Locale locale = req.getLocale();
String charset = (String) encodings.get(locale);
if (charset == null)
charset = "windows-1250";
resp.setContentType("text/html; charset=" + charset);
PrintWriter out = resp.getWriter();
out.println(header);
chain.doFilter(req, resp);
}
public void destroy() {
}
}
Not sure if I should post any other code?
If you read filter essentials, there is written:
Modifying the response headers and data. You do this by providing a
customized version of the response.
and
A filter that modifies a response must usually capture the response
before it is returned to the client. The way to do this is to pass the
servlet that generates the response a stand-in stream. The stand-in
stream prevents the servlet from closing the original response stream
when it completes and allows the filter to modify the servlet's
response.
So the explanation is easy:
When you do not call next item in the filter chain, your code will be written and returned to the browser. But when you pass control to the next filter, it will be replaced.
To achieve your effect, you need to:
call filter chain
grab final response to StringBuilder
find the location of HTML table tag
insert your HTML code
write modified response
See the linked document for code samples.
Related
I have a use case where I need to verify if the incoming request body to my controller contains any special characters in a Hybris storefront. Though it can be achieved from the front-end by blocking any special characters, we require back-end validation.
I tried using HandlerIntercepterAdapter to intercept the request and validate for any special characters. But whenever I use request.getReader() or request.getInputStream() and read the data, request body is cleared.
I tried using IOUtils.copy() but this too reads from the original request and makes the body empty.
Even after wrapping the request with HttpServletRequestWrapper or ContentCachingRequestWrapper, the request body gets cleared. I guess internally somewhere it uses the same reference.
I tried following this thread but was unable to solve this issue.
I am looking for a solution where I can extract the request body and validate it without letting it get cleared so it can be used in different controllers [or] any alternative approach which can help in preventing any special characters to hit the controller.
any alternative approach which can help in preventing any special characters to hit the controller.
What if you try to do the following ...
Get the request body
Process it
Set the request body again in your filter by setting the body to the processed version ?
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest originalRequest = (HttpServletRequest) request;
HttpServletResponse originalResponse = (HttpServletResponse) response;
/**
* 2.Read the original request body and change it
*/
String originalRequestBody = ServletUtil.readRequestBody(originalRequest); // Read the original request body
// Body is processed here !
String modifyRequestBody = processBody(originalRequestBody); // Modify request body (clear text)
HttpServletRequest orginalRequest = (HttpServletRequest) request;
ModifyRequestBodyWrapper requestWrapper = new ModifyRequestBodyWrapper(orginalRequest, modifyRequestBody);
/**
* 3. Build a new response object
*/
ModifyResponseBodyWrapper responseWrapper = new ModifyResponseBodyWrapper(originalResponse);
chain.doFilter(requestWrapper, responseWrapper);
String originalResponseBody = responseWrapper.getResponseBody(); // Original response body (clear text)
String modifyResponseBody = this.encryptBody(originalResponseBody); // Modified response volume (ciphertext)
/**
* 4.Output the modified response body with the output stream of the original response object
* To ensure that the response type is consistent with the original request, and reset the response body size
*/
originalResponse.setContentType(requestWrapper.getOrginalRequest().getContentType()); // Be consistent with the request
byte[] responseData = modifyResponseBody.getBytes(responseWrapper.getCharacterEncoding()); // The coding is consistent with the actual response
originalResponse.setContentLength(responseData.length);
#Cleanup ServletOutputStream out = originalResponse.getOutputStream();
out.write(responseData);
}
Here is a code example, which implements this.
The input should be set inside a form.
In your controller, you can use a validator :
#RequestMapping(value = "/process", method = RequestMethod.POST)
public String doValidateAndPost(final MyForm form, final BindingResult bindingResult,
final HttpServletRequest request, final Model model){
getMyValidator().validate(form, bindingResult);
if (bindingResult.hasErrors())
{
return MY_PAGE;
}
The validator will look like this :
#Override
public void validate(final Object object, final Errors errors)
{
final MyForm form = (MyForm ) object;
final String data = form.getMyData();
Pattern p = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(data );
boolean b = m.find();
if (b)
{
errors.rejectValue("myData", "myData.invalid");
}
}
You can also use the #Valid annotation :
public String doValidateAndPost(#Valid final MyForm form ...
And set in your form :
#Pattern(regexp = "[a-z0-9 ]")
private String myData;
Cannot get response body in my filter. Caching enabled.
I have tried many filter implementations - Filter, OncePerRequestFilter and GenericFilterBean. I have also tried writing custom caching mechanism but non of that work. I have already one logging filter working - this filter is executed before the new one and serves for reading request and response. The logging filter works but I want to add another one which reads response and validate it against XSD. Problem is, that logging filter gets the response filled OK but the XML validator filter gets empty string.
My #Controller method just returns Callable. Asynchronous processing may not be problem tho because logger filter works well.
#Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
#Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
I expect all my filters to be able to read response body which is cached.
Update
I was mistaken myself. Even LoggerFilter cannot read seponse. I think this has something to do with asynchronous processing of a request.
Use Case:
We are developing an AEM Closed User Group site where users will need to submit forms which trigger workflows. Since the users are authenticated, part of the workflow payload needs to include the user who initiated the form.
I'm considering using AEM Forms for this, which saves to nodes under /content/usergenerated/content/forms/af/my-site but the user is not mentioned in the payload (only the service user). In this case, there are two service users: workflow-service running the workflow, and fd-service which handled the form processing and initial saving. E.G. the following code called from the workflow step reports 'fd-service'
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
To work around this constraint,
Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.
I am adding a filter servlet to intercept the initial form submission before the AEM Forms servlet using a request wrapper to modify the request body adding the original userID.
In terms of forms, workflows and launchers.. This is basically the setup I have
https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html
I have reviewed the following resources:
How to change servlet request body in java filter?
https://coderanch.com/t/364591/java/read-request-body-filter
https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71
https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html
Here is the code for my wrapper
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;
public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);
public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
super(slingRequest);
// read the original payload into the requestPayload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = slingRequest.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
log.error("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
log.error("Error closing bufferedReader", iox);
}
}
}
requestPayload = stringBuilder.toString();
}
/**
* Override of the getInputStream() method which returns an InputStream that reads from the
* stored requestPayload string instead of from the request's actual InputStream.
*/
#Override
public ServletInputStream getInputStream ()
throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
}
Here is my filter
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;
#Component(service = Filter.class,
immediate = true,
property = {
Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
Constants.SERVICE_RANKING + ":Integer=3000",
EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
})
public class DecorateUserGeneratedFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;
FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);
log.info("starting ConfirmAlumniStatus workflow");
log.info(getCurrentUserId(slingRequest));
chain.doFilter(wrappedRequest, slingResponse);
}
#Override
public void destroy() {
}
public String getCurrentUserId(SlingHttpServletRequest request) {
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
String userId = session.getUserID();
return userId;
}
}
When POST submissions get processed by this filter, I'm getting the error below stating the request body has already been read. So it seems the filter ranking might not be high enough.
25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp
HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl
service: Uncaught Throwable java.lang.IllegalStateException: Request
Data has already been read at
org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669)
at
org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292)
at
javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136)
at
my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26)
at
my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10
I don't think the service ranking is working. When I view
http://localhost:4502/system/console/status-slingfilter
my filter is listed as shown. Judging from the other filters listed, I think the leftmost number is the filter ranking. For some reason my filter is ranked 0 even though I set is as service.ranking=700
0 : class my.site.servlets.DecorateUserGeneratedFilter (id:
8402, property: service.ranking=700); called: 0; time: 0ms; time/call:
-1µs
Update: I was able to fix the filter rank, making it 700 still gave the IllegalStateException. Making it 3000 made that problem go away. But when request.getInputStream() is called from my wrapper. It returns null.
What you are trying to do might be the easy route, but might not be future-proof for new AEM releases.
You need total control of how your workflow is triggered!:
Your forms should have a field that contains the workflow path (and maybe other information needed for that workflow)
Create a custom servlet that your forms will post to.
In that servlet process all user posted values (from the form). But especially get a hold of the intended workflow path and trigger it using the workflow API.
This way you don't have to mess with launchers and the workflows are triggered by your users using their user id.
Hope this helps.
Right idea, wrong location.
The short answer is that when you implement the SlingHttpServletRequestWrapper it provides a default handling of method calls to the original SlingHttpServletRequest if you're adding a parameter on the fly what you want to do is to make sure that the methods that are interacting with the parameters are overridden so that you can be sure yours is added. So on initialization, call the original parameter map, copy those items in a new map which includes your own values.
Then over ride any methods that would request those values
getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)
Don't touch the InputStream, that's already been processed to obtain any parameters that are being passed in.
Additionally, that is one of two ways you can handle this type of use case, the other option is to use the SlingPOSTProcessors as documented
https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html
which allows you to detect what is being written to the repository and modify the data to include, like your case, an additional field.
if you are looking for code example :
#SlingFilter(order = 1)
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
return;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
ServletRequest request = servletRequest;
if (servletRequest instanceof SlingHttpServletRequest) {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
request = new SlingHttpServletRequestWrapper(slingRequest) {
String userId = getCurrentUserId(slingRequest);
};
}
filterChain.doFilter(request, servletResponse);
}
#Override
public void destroy() {
return;
}
This question already has an answer here:
Servlet filter wrapper - trouble changing content type
(1 answer)
Closed 7 years ago.
I'm trying to set the content type of my gzipped files to be of the correct mime type, rather than application/gzip, in a filter. Here's some of my code:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
String reqUrl = request.getRequestURI();
if (reqUrl.endsWith(gzExt))
{
response.setHeader("Content-Encoding", "gzip");
response.setContentType("text/javascript");
System.out.println("Set header " +reqUrl +", " + response.getContentType() );
filterChain.doFilter(request, response);
System.out.println("Header now: " + reqUrl + ", " + response.getContentType() );
return;
}
}
Output:
Set header /test.js.gz, text/javascript
Header now: /test.js.gz, application/x-gzip
In the browser, I see that the content-encoding is correctly set to gzip, but the content-type remains at application/x-gzip. It seems that filterChain.doFilter() is resetting the content type.
Any idea how to permanently reset the content type?
I don't have any other filters.
I solved it with the help of this answer:
private class ForcableContentTypeWrapper extends HttpServletResponseWrapper
{
public ForcableContentTypeWrapper(HttpServletResponse response)
{
super(response);
}
#Override
public void setContentType(String type)
{
}
public void forceContentType(String type)
{
super.setContentType(type);
}
}
Then changed my above code to:
if (reqUrl.endsWith(gzExt))
{
ForcableContentTypeWrapper newResponse = new ForcableContentTypeWrapper(response);
newResponse.setHeader("Content-Encoding", "gzip");
newResponse.forceContentType("text/javascript");
filterChain.doFilter(request, newResponse);
return;
}
Its not a pretty workaround, but it works.
First of all, you should always call (or have very good reason not to do so)
filterChain.doFilter(request, response);
or filters declared after this one will not be called.
Next, move call filterChain.doFilter() to the top of method, set your headers after and declare your filter mapping as last in web.xml.
And this will work as expected.
I am writing a servlet filter to forward Jersy requests based on certain condition. But they does not seem to forwarding.
public class SampleFilter
extends GenericFilterBean
{
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException
{
String generateRedirectUrl=FormURL((HttpServletRequest)req);
RequestDispatcher dispatcher = req.getRequestDispatcher(generateRedirectUrl);
dispatcher.forward(req, resp);
}
private String FormURL(HttpServletRequest req)
{
// get the request check if it contains the customer
String reqUrl = req.getRequestURI();
log.info("Original Url is"+reqUrl);
if(reqUrl.contains("test"))
{
return "/api/abcd/" +"test";
}
return "Someurl";
}
}
I need to forward the url as below.
Original: http://localhost/api/test/1234/true
New URL:http://localhost/api/abcd/1234/true
Am I doing any thing wrong.
"Am I doing any thing wrong."
In general I think this will work - you can forward from a filter, but your logic is wrong.
Using your rule below:
Original: http://localhost/api/test/1234/true
New URL:http://localhost/api/abcd/1234/true
The code:
if(reqUrl.contains("test"))
{
return "/api/abcd/" +"test";
}
will produce /api/abcd/test which is not what you're after.
I would do something like the following:
private String formURL(HttpServletRequest req) {
// get the request check if it contains the customer
String reqUrl = req.getRequestURI();
if (log.isInfoEnabled() {
log.info("Original Url is"+reqUrl);
}
if(reqUrl.contains("test")) {
return reqUrl.replace("test", "abcd");
}
return "Someurl";
Also, the code is very brutal. This will also change the URL
http://test.site.com/abd/def
to
http://abcd.site.com/abd/def
which is probably not what you want. You'll probably need to either do more clever string manipulation or convert to something like the URI class which will allow you to target the path more accurately.
Hope this helps,
Will