Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions http/base/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,6 @@
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.http.wrappers</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.http.base.internal.dispatch;

import java.io.IOException;

/**
* This exception is thrown if a request contains more files than the specified
* limit.
*/
public class FileCountLimitExceededException extends IOException {

private static final long serialVersionUID = 6904179610227521789L;

/**
* The limit that was exceeded.
*/
private final long limit;

/**
* Creates a new instance.
*
* @param message The detail message
* @param limit The limit that was exceeded
*/
public FileCountLimitExceededException(final String message, final long limit) {
super(message);
this.limit = limit;
}

/**
* Gets the limit that was exceeded.
*
* @return The limit that was exceeded by the request
*/
public long getLimit() {
return limit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

import java.util.Objects;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

public final class MultipartConfig
{
public static final MultipartConfig DEFAULT_CONFIG = new MultipartConfig(null, null, -1, -1, -1);
Expand Down Expand Up @@ -63,7 +61,7 @@ public MultipartConfig(final Integer threshold,
}
else
{
this.multipartThreshold = DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD;
this.multipartThreshold = 10240;
}
this.multipartLocation = location;
if ( maxFileSize > 0 || maxFileSize == -1 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,21 @@
*/
package org.apache.felix.http.base.internal.dispatch;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.felix.http.base.internal.context.ExtServletContext;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;

final class ServletRequestMultipartWrapper extends ServletRequestWrapper
{
/**
* Constant for HTTP POST method.
*/
private static final String POST_METHOD = "POST";

private final MultipartConfig multipartConfig;

private Collection<PartImpl> parts;

private Map<String, String[]> partsParameterMap;
private long maxFileCount;

public ServletRequestMultipartWrapper(final HttpServletRequest req,
final ExtServletContext servletContext,
Expand All @@ -61,235 +39,47 @@ public ServletRequestMultipartWrapper(final HttpServletRequest req,
final boolean asyncSupported,
final MultipartConfig multipartConfig)
{
super(req, servletContext, requestInfo, type, asyncSupported);

this.multipartConfig = multipartConfig;
}

private RequestContext getMultipartContext() {
final RequestContext multipartContext;
if (!POST_METHOD.equalsIgnoreCase(this.getMethod())) {
multipartContext = null;
} else {
multipartContext = new RequestContext() {

@Override
public InputStream getInputStream() throws IOException {
return ServletRequestMultipartWrapper.this.getInputStream();
}
super(req, servletContext, requestInfo, type, asyncSupported);

@Override
public String getContentType() {
return ServletRequestMultipartWrapper.this.getContentType();
}
// adapt the multipart configuration for jetty
MultipartConfigElement mce = new MultipartConfigElement(
multipartConfig.multipartLocation,
multipartConfig.multipartMaxFileSize,
multipartConfig.multipartMaxRequestSize,
multipartConfig.multipartThreshold
);

@Override
public int getContentLength() {
return ServletRequestMultipartWrapper.this.getContentLength();
}
// Override the multipart configuration for the current request
setAttribute("org.eclipse.jetty.multipartConfig", mce);

@Override
public String getCharacterEncoding() {
return ServletRequestMultipartWrapper.this.getCharacterEncoding();
}
};
}
return multipartContext;
this.maxFileCount = multipartConfig.multipartMaxFileCount;
}

private Collection<PartImpl> checkMultipart() throws IOException, ServletException {
if ( parts == null ) {
final RequestContext multipartContext = getMultipartContext();
if ( multipartContext != null && FileUploadBase.isMultipartContent(multipartContext) ) {
if ( this.multipartConfig == null) {
throw new IllegalStateException("Multipart not enabled for servlet.");
}

handleMultipart(multipartContext);

} else {
throw new ServletException("Not a multipart request");
}
/**
* Enforces the non-standard "maxFileCount" configuration
*
* @return the parts the collection that was checked
*/
private Collection<Part> checkMultipart() throws IOException, ServletException {
Collection<Part> parts = getOriginalParts();
long filePartCount = parts.stream().filter(p -> p.getSubmittedFileName() != null).count();
if (filePartCount > maxFileCount) {
throw new FileCountLimitExceededException("Request exceeds maximum file part count", maxFileCount);
}
return parts;
}

private void handleMultipart(final RequestContext multipartContext) throws IOException {
// Create a new file upload handler
final FileUpload upload = new FileUpload();
upload.setSizeMax(this.multipartConfig.multipartMaxRequestSize);
upload.setFileSizeMax(this.multipartConfig.multipartMaxFileSize);
upload.setFileItemFactory(new DiskFileItemFactory(this.multipartConfig.multipartThreshold,
new File(this.multipartConfig.multipartLocation)));
upload.setFileCountMax(this.multipartConfig.multipartMaxFileCount);
// Parse the request
List<FileItem> items = null;
try {
items = upload.parseRequest(multipartContext);
} catch (final FileUploadException fue) {
throw new IOException("Error parsing multipart request", fue);
}
this.parts = new ArrayList<>();
for(final FileItem item : items) {
this.parts.add(new PartImpl(item));
}
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Collection<Part> getParts() throws IOException, ServletException {
return (Collection)checkMultipart();

// enforce the non-standard "maxFileCount" condition
return checkMultipart();
}

@Override
public Part getPart(String name) throws IOException, ServletException {
Collection<PartImpl> parts = this.checkMultipart();
for(final Part p : parts) {
if ( p.getName().equals(name) ) {
return p;
}
}
return null;
}

private Map<String, String[]> getPartsParameterMap() {
if ( this.partsParameterMap == null ) {
try {
final Collection<PartImpl> parts = this.checkMultipart();
final Map<String, String[]> params = new HashMap<>();
for(final PartImpl p : parts) {
if (p.getFileItem().isFormField()) {
String[] current = params.get(p.getName());
if (current == null) {
current = new String[] {p.getFileItem().getString()};
} else {
String[] newCurrent = new String[current.length + 1];
System.arraycopy( current, 0, newCurrent, 0, current.length );
newCurrent[current.length] = p.getFileItem().getString();
current = newCurrent;
}
params.put(p.getName(), current);
}
}
this.partsParameterMap = params;
} catch (final IOException | ServletException ignore) {
// ignore all exceptions and use default
}
if ( this.partsParameterMap == null ) {
// use map from container implementation as default
this.partsParameterMap = super.getParameterMap();
}
}
return this.partsParameterMap;
}

@Override
public String getParameter(final String name) {
final String[] values = this.getParameterValues(name);
if (values != null && values.length > 0) {
return values[0];
}
return null;
}

@Override
public Map<String, String[]> getParameterMap() {
final RequestContext multipartContext = getMultipartContext();
if ( multipartContext != null && FileUploadBase.isMultipartContent(multipartContext) && this.multipartConfig != null) {
return this.getPartsParameterMap();
}
return super.getParameterMap();
}

@Override
public Enumeration<String> getParameterNames() {
final Map<String, String[]> params = this.getParameterMap();
return Collections.enumeration(params.keySet());
}

@Override
public String[] getParameterValues(final String name) {
final Map<String, String[]> params = this.getParameterMap();
return params.get(name);
// enforce the non-standard "maxFileCount" condition
checkMultipart();
return getOriginalPart(name);
}

private static final class PartImpl implements Part {

private final FileItem item;

public PartImpl(final FileItem item) {
this.item = item;
}

@Override
public InputStream getInputStream() throws IOException {
return item.getInputStream();
}

@Override
public String getContentType() {
return item.getContentType();
}

@Override
public String getName() {
return item.getFieldName();
}

@Override
public String getSubmittedFileName() {
return item.getName();
}

@Override
public long getSize() {
return item.getSize();
}

@Override
public void write(final String fileName) throws IOException {
try {
item.write(new File(fileName));
} catch (final IOException e) {
throw e;
} catch (final Exception e) {
throw new IOException(e);
}
}

@Override
public void delete() throws IOException {
item.delete();
}

@Override
public String getHeader(final String name) {
return item.getHeaders().getHeader(name);
}

@Override
public Collection<String> getHeaders(final String name) {
final List<String> values = new ArrayList<>();
final Iterator<String> iter = item.getHeaders().getHeaders(name);
while ( iter.hasNext() ) {
values.add(iter.next());
}
return values;
}

@Override
public Collection<String> getHeaderNames() {
final List<String> names = new ArrayList<>();
final Iterator<String> iter = item.getHeaders().getHeaderNames();
while ( iter.hasNext() ) {
names.add(iter.next());
}
return names;
}

public FileItem getFileItem() {
return this.item;
}
}
}
Loading
Loading