Expand my Community achievements bar.

SOLVED

Downloading multipart file in AEM Sling filter - how?

Avatar

Level 1

Hi, I am uploading assets to AEM's servlet, but before the file reaches servlet, I want to intercept it with a filter and scan it.

All details aside, I am unable to extract file from ServletRequest.


Here is my filter:

   @Component

   @SlingServletFilter(scope = {SlingServletFilterScope.REQUEST},

                       pattern = ".*createasset.html",

                       methods = {"POST"})

   public class UploadFileFilter implements Filter {

       @Override

       public void init(FilterConfig filterConfig) throws ServletException {

       }

       @Override

       public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

           SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response;

          //try to download multipart file...

           chain.doFilter(request, slingResponse);

       }

       @Override

       public void destroy() {

       }

   }

I can't read input stream directly in filter, because it has already been read.

I have tried solution from this post:

Re: AEM File Upload

But request.getRequestParameterMap() always return empty map. Is it because I try to access parameters in a filter, not a servlet?

Do you know any working way to download multipart file in a filter?

1 Accepted Solution

Avatar

Correct answer by
Level 10
9 Replies

Avatar

Level 10

As I stated in the other thread - Sling Has its own way in a Servlet to read the InputStream. That code works to read a file in a Sling Servlet. I have never tried to capture it within a filter. Lets see if other community members have.

Avatar

Level 10

Since you have the same request object in the filter servlet, you should be able to access the inputstream, download it to a local file and scan it.

Are you not able to find the input stream in your filter ? what is the error thrown ?

Avatar

Level 1

When I request.getInputStream() I get error saying that input data has already been read, which, as far as I know, is the expected behaviour in AEM servlets and filters.

Avatar

Correct answer by
Level 10

Avatar

Level 1

Hi, thank you for answering!

Using the code you posted, I get and error inside CreateAssetServlet.java:

22.01.2019 11:54:39.041 *ERROR* [] [] [85.222.102.38 [1548158078795] POST /content/dam/catalogs/myfolder.createasset.html HTTP/1.1] org.apache.sling.engine.impl.SlingMainServlet service: Uncaught Problem handling the request

org.apache.commons.fileupload.FileItemStream$ItemSkippedException: null

  at org.apache.commons.fileupload.MultipartStream$ItemInputStream.read(MultipartStream.java:896)

  at java.io.InputStream.read(InputStream.java:101)

  at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2146)

  at org.apache.commons.io.IOUtils.copy(IOUtils.java:2102)

  at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2123)

  at org.apache.commons.io.IOUtils.copy(IOUtils.java:2078)

  at org.apache.commons.io.IOUtils.toByteArray(IOUtils.java:721)

  at com.day.cq.dam.core.impl.StreamedRequestParameter.<init>(StreamedRequestParameter.java:45)

  at com.day.cq.dam.core.impl.servlet.CreateAssetServlet.doPost(CreateAssetServlet.java:247)

  at org.apache.sling.api.servlets.SlingAllMethodsServlet.mayService(SlingAllMethodsServlet.java:146)

  at org.apache.sling.api.servlets.SlingSafeMethodsServlet.service(SlingSafeMethodsServlet.java:342)

  at org.apache.sling.api.servlets.SlingSafeMethodsServlet.service(SlingSafeMethodsServlet.java:374)

  at org.apache.sling.engine.impl.request.RequestData.service(RequestData.java:552)

  at org.apache.sling.engine.impl.filter.SlingComponentFilterChain.render(SlingComponentFilterChain.java:44)

This is my code in my filter:

@Override

  public void doFilter(ServletRequest request, ServletResponse response,

    FilterChain chain)

    throws IOException, ServletException {

    if (!(request instanceof SlingHttpServletRequest)

      || !(response instanceof SlingHttpServletResponse)) {

      chain.doFilter(request, response);

      return;

    }

    final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

    if (!StringUtils.equals("POST", slingRequest.getMethod())

      || !isCreateAssetRequest(slingRequest)) {

      chain.doFilter(request, response);

      return;

    }

    Iterator<Part> partsIter = (Iterator) request.getAttribute("request-parts-iterator");

    if ((partsIter == null) || !partsIter.hasNext()) {

      chain.doFilter(request, response);

      return;

    }

    List<Part> parts = new ArrayList<>();

    while (partsIter.hasNext()) {

      Part part = partsIter.next();

      String tmpDir = System.getProperty(TEMPORARY_DIRECTORY);

      File tmpFile = new File(tmpDir + "/temp-upload-asset.tmp");

      FileOutputStream fileOutputStream = new FileOutputStream(tmpFile);

      ByteArrayOutputStream baos = new ByteArrayOutputStream();

      IOUtils.copy(part.getInputStream(), baos);

      fileOutputStream.write(baos.toByteArray());

      fileOutputStream.close();

      parts.add(part);

    }

    request.setAttribute("request-parts-iterator", parts.iterator());

    chain.doFilter(request, response);

  }

Avatar

Level 10

FileItemStream$ItemSkippedException  would happen if you try to use the stream outside the context/block where it was initially read/opened.

I'll test the code later & revert..

Avatar

Level 10

Below is the code that worked for me. I've highlighted couple of changes that I did in DecryptAssetsFilter shared in that link:

  • Used slingRequest.getParts() to iterate rather than the custom request attribute
  • changed the method getSubmittedFileName() to getName()

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException, ServletException {

if (!(request instanceof SlingHttpServletRequest) || !(response instanceof SlingHttpServletResponse)) {

chain.doFilter(request, response);

return;

}

final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

if (!StringUtils.equals("POST", slingRequest.getMethod()) || !isCreateAssetRequest(slingRequest)) {

chain.doFilter(request, response);

return;

}

log.info("Decoding create asset request - " + slingRequest.getRequestURI());

List parts = (ArrayList) slingRequest.getParts();

if ((parts == null) || parts.size() == 0) {

chain.doFilter(request, response);

return;

}

List<Part> otherParts = new ArrayList<Part>();

Part part = null;

for (int i = 0; i < parts.size() - 1; i++) {

part = (Part) parts.get(i);

otherParts.add(new EAEMDecryptRequestPart(part));

}

chain.doFilter(request, response);

}

private boolean isCreateAssetRequest(SlingHttpServletRequest slingRequest) {

String[] selectors = slingRequest.getRequestPathInfo().getSelectors();

if (ArrayUtils.isEmpty(selectors) || (selectors.length > 1)) {

return false;

}

return selectors[0].equals("createasset");

}

public void destroy() {

}

private static class EAEMDecryptRequestPart implements Part {

private final Part part;

private final InputStream inputStream;

private static final String TEMP_PREFIX = "eaem_decrypt_";

public EAEMDecryptRequestPart(Part part) throws IOException {

this.part = part;

if (!isFilePart(part)) {

this.inputStream = new ByteArrayInputStream(IOUtils.toByteArray(part.getInputStream()));

} else {

this.inputStream = this.getDecodedStream(part);

}

}

private InputStream getDecodedStream(Part part) throws IOException {

File tmpFile = File.createTempFile(TEMP_PREFIX, ".tmp");

// byte[] decoded =

// Base64.getDecoder().decode(IOUtils.toByteArray(part.getInputStream()));  //use decode, if the input file is encoded

byte[] decoded = IOUtils.toByteArray(part.getInputStream());

FileOutputStream decodedStream = new FileOutputStream(tmpFile);

decodedStream.write(decoded);

decodedStream.close();

return new FileInputStream(tmpFile);

}

private boolean isFilePart(Part part) {

return StringUtils.isNotEmpty(part.getName());

}

public InputStream getInputStream() throws IOException {

return inputStream;

}

public String getContentType() {

return part.getContentType();

}

public String getName() {

return part.getName();

}

public long getSize() {

return 0;

}

public void write(String s) throws IOException {

throw new UnsupportedOperationException(

"Writing parts directly to disk is not supported by this implementation, use getInputStream instead");

}

public void delete() throws IOException {

}

public String getHeader(String headerName) {

return part.getHeader(headerName);

}

public Collection<String> getHeaders(String headerName) {

return part.getHeaders(headerName);

}

public Collection<String> getHeaderNames() {

return part.getHeaderNames();

}

public String getSubmittedFileName() {

return part.getName();

}

private <T> Collection<T> toCollection(Iterator<T> i) {

if (i == null) {

return Collections.emptyList();

} else {

List<T> c = new ArrayList<T>();

while (i.hasNext()) {

c.add(i.next());

}

return c;

}

}

}

}

Avatar

Level 1

Hello, unfortunatelly my SlingHttpServletRequest does not have getParts() method. Mine comes from com.adobe.aem:uber-jar:apis:6.3.3

Anyway, I have tried the code that you previously posted from:

https://experience-aem.blogspot.com/2018/11/aem-6420-file-de…

And it seems to work now, except I cant extract code from getDecodedStream method, or else it will throw, the exception I have described above, inside CreateAsserServlet​.