summaryrefslogtreecommitdiffhomepage
path: root/src/java
diff options
context:
space:
mode:
authorMax Romanov <max.romanov@nginx.com>2019-08-14 15:24:41 +0300
committerMax Romanov <max.romanov@nginx.com>2019-08-14 15:24:41 +0300
commit4bef4256c05c3f905e3d65e40585bba5ce0f3327 (patch)
treec5b8b67273cc639e3ac0cd062a65865e6a5cc61a /src/java
parent8904c87c6beb4f2b080bf4269fb211e6f2eea7f2 (diff)
downloadunit-4bef4256c05c3f905e3d65e40585bba5ce0f3327.tar.gz
unit-4bef4256c05c3f905e3d65e40585bba5ce0f3327.tar.bz2
Java: implementing multipart message support.
This closes #265 issue on GitHub.
Diffstat (limited to 'src/java')
-rw-r--r--src/java/nginx/unit/Context.java31
-rw-r--r--src/java/nginx/unit/ForwardRequestWrapper.java12
-rw-r--r--src/java/nginx/unit/IncludeRequestWrapper.java12
-rw-r--r--src/java/nginx/unit/Request.java151
4 files changed, 192 insertions, 14 deletions
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java
index 3c64e99f..e1482903 100644
--- a/src/java/nginx/unit/Context.java
+++ b/src/java/nginx/unit/Context.java
@@ -81,6 +81,7 @@ import javax.servlet.ServletSecurityElement;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.annotation.HandlesTypes;
+import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.annotation.WebFilter;
@@ -954,6 +955,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST);
fc.doFilter(req, resp);
@@ -1074,6 +1077,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR);
fc.doFilter(req, resp);
@@ -1852,11 +1857,13 @@ public class Context implements ServletContext, InitParams
private boolean initialized_ = false;
private final List<FilterMap> filters_ = new ArrayList<>();
private boolean system_jsp_servlet_ = false;
+ private MultipartConfigElement multipart_config_;
public ServletReg(String name, Class<?> servlet_class)
{
super(name, servlet_class.getName());
servlet_class_ = servlet_class;
+ getAnnotationMultipartConfig();
}
public ServletReg(String name, Servlet servlet)
@@ -1893,6 +1900,7 @@ public class Context implements ServletContext, InitParams
try {
if (servlet_class_ == null) {
servlet_class_ = loader_.loadClass(getClassName());
+ getAnnotationMultipartConfig();
}
Constructor<?> ctor = servlet_class_.getConstructor();
@@ -1948,6 +1956,20 @@ public class Context implements ServletContext, InitParams
super.setClassName(servlet_class.getName());
servlet_class_ = servlet_class;
+ getAnnotationMultipartConfig();
+ }
+
+ private void getAnnotationMultipartConfig() {
+ if (servlet_class_ == null) {
+ return;
+ }
+
+ MultipartConfig mpc = servlet_class_.getAnnotation(MultipartConfig.class);
+ if (mpc == null) {
+ return;
+ }
+
+ multipart_config_ = new MultipartConfigElement(mpc);
}
public void service(ServletRequest request, ServletResponse response)
@@ -2027,7 +2049,8 @@ public class Context implements ServletContext, InitParams
public void setMultipartConfig(
MultipartConfigElement multipartConfig)
{
- log("ServletReg.setMultipartConfig");
+ trace("ServletReg.setMultipartConfig");
+ multipart_config_ = multipartConfig;
}
@Override
@@ -2508,6 +2531,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
req.setRequestURI(uri_.getRawPath());
req.setQueryString(uri_.getRawQuery());
req.setDispatcherType(DispatcherType.FORWARD);
@@ -2577,6 +2602,8 @@ public class Context implements ServletContext, InitParams
ServletReg servlet = findServlet(path, req);
+ req.setMultipartConfig(servlet.multipart_config_);
+
req.setRequestURI(uri_.getRawPath());
req.setQueryString(uri_.getRawQuery());
req.setDispatcherType(DispatcherType.INCLUDE);
@@ -2757,7 +2784,7 @@ public class Context implements ServletContext, InitParams
{
trace("getRealPath for " + path);
- File f = new File(webapp_, path.substring(1));
+ File f = new File(webapp_, path.isEmpty() ? "" : path.substring(1));
return f.getAbsolutePath();
}
diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java
index f88b6aef..fe8adf8a 100644
--- a/src/java/nginx/unit/ForwardRequestWrapper.java
+++ b/src/java/nginx/unit/ForwardRequestWrapper.java
@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
@@ -24,6 +25,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
private final String orig_context_path;
private final String orig_query;
+ private final MultipartConfigElement orig_multipart_config;
+
private final DispatcherType orig_dtype;
private MultiMap<String> orig_parameters;
@@ -46,6 +49,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
orig_uri = request_.getRequestURI();
orig_context_path = request_.getContextPath();
orig_query = request_.getQueryString();
+
+ orig_multipart_config = request_.getMultipartConfig();
}
@Override
@@ -125,6 +130,11 @@ public class ForwardRequestWrapper implements DynamicPathRequest
return request_.getFilterPath();
}
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ request_.setMultipartConfig(mce);
+ }
+
public void close()
{
request_.setDispatcherType(orig_dtype);
@@ -137,6 +147,8 @@ public class ForwardRequestWrapper implements DynamicPathRequest
request_.setParameters(orig_parameters);
}
+ request_.setMultipartConfig(orig_multipart_config);
+
if (keep_attrs) {
return;
}
diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java
index 67a51b24..761a0d52 100644
--- a/src/java/nginx/unit/IncludeRequestWrapper.java
+++ b/src/java/nginx/unit/IncludeRequestWrapper.java
@@ -1,6 +1,7 @@
package nginx.unit;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
@@ -14,6 +15,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest
private final Object orig_context_path_attr;
private final Object orig_query_string_attr;
+ private final MultipartConfigElement orig_multipart_config;
+
private final DispatcherType orig_dtype;
private String filter_path_;
@@ -32,6 +35,8 @@ public class IncludeRequestWrapper implements DynamicPathRequest
orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH);
orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+ orig_multipart_config = request_.getMultipartConfig();
+
orig_dtype = request_.getDispatcherType();
request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath());
@@ -75,6 +80,11 @@ public class IncludeRequestWrapper implements DynamicPathRequest
return filter_path_;
}
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ request_.setMultipartConfig(mce);
+ }
+
public void close()
{
request_.setDispatcherType(orig_dtype);
@@ -84,5 +94,7 @@ public class IncludeRequestWrapper implements DynamicPathRequest
request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr);
request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr);
request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr);
+
+ request_.setMultipartConfig(orig_multipart_config);
}
}
diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java
index 3ba46f6c..98584efe 100644
--- a/src/java/nginx/unit/Request.java
+++ b/src/java/nginx/unit/Request.java
@@ -1,6 +1,8 @@
package nginx.unit;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -14,6 +16,9 @@ import java.lang.StringBuffer;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -32,6 +37,7 @@ import java.security.Principal;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -49,11 +55,14 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.server.CookieCutter;
+import org.eclipse.jetty.http.MultiPartFormInputStream;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.MimeTypes;
public class Request implements HttpServletRequest, DynamicPathRequest
@@ -109,6 +118,9 @@ public class Request implements HttpServletRequest, DynamicPathRequest
public static final String BARE = "nginx.unit.request.bare";
+ private MultiPartFormInputStream multi_parts;
+ private MultipartConfigElement multipart_config;
+
public Request(Context ctx, long req_info, long req) {
context = ctx;
req_info_ptr = req_info;
@@ -271,17 +283,64 @@ public class Request implements HttpServletRequest, DynamicPathRequest
@Override
public Part getPart(String name) throws IOException, ServletException
{
- log("getPart: " + name);
+ trace("getPart: " + name);
- return null;
+ if (multi_parts == null) {
+ parseMultiParts();
+ }
+
+ return multi_parts.getPart(name);
}
@Override
public Collection<Part> getParts() throws IOException, ServletException
{
- log("getParts");
+ trace("getParts");
+
+ if (multi_parts == null) {
+ parseMultiParts();
+ }
+
+ return multi_parts.getParts();
+ }
+
+ private boolean checkMultiPart(String content_type)
+ {
+ return content_type != null
+ && MimeTypes.Type.MULTIPART_FORM_DATA.is(HttpFields.valueParameters(content_type, null));
+ }
+
+ private void parseMultiParts() throws IOException, ServletException, IllegalStateException
+ {
+ String content_type = getContentType();
+
+ if (!checkMultiPart(content_type)) {
+ throw new ServletException("Content-Type != multipart/form-data");
+ }
+
+ if (multipart_config == null) {
+ throw new IllegalStateException("No multipart config for servlet");
+ }
- return Collections.emptyList();
+ parseMultiParts(content_type);
+ }
+
+ private void parseMultiParts(String content_type) throws IOException
+ {
+ File tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR);
+
+ multi_parts = new MultiPartFormInputStream(getInputStream(),
+ content_type, multipart_config, tmpDir);
+ }
+
+ public void setMultipartConfig(MultipartConfigElement mce)
+ {
+ multipart_config = mce;
+ }
+
+ public MultipartConfigElement getMultipartConfig()
+ {
+ return multipart_config;
}
@Override
@@ -766,16 +825,84 @@ public class Request implements HttpServletRequest, DynamicPathRequest
UrlEncoded.decodeUtf8To(query, parameters);
}
- if (getContentLength() > 0 &&
- getMethod().equals("POST") &&
- getContentType().startsWith("application/x-www-form-urlencoded"))
- {
- try {
+ int content_length = getContentLength();
+
+ if (content_length == 0 || !getMethod().equals("POST")) {
+ return parameters;
+ }
+
+ String content_type = getContentType();
+
+ try {
+ if (content_type.startsWith("application/x-www-form-urlencoded")) {
UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr),
- parameters, getContentLength(), -1);
- } catch (IOException e) {
- log("Unhandled IOException: " + e);
+ parameters, content_length, -1);
+ } else if (checkMultiPart(content_type) && multipart_config != null) {
+ if (multi_parts == null) {
+ parseMultiParts(content_type);
+ }
+
+ if (multi_parts != null) {
+ Collection<Part> parts = multi_parts.getParts();
+
+ String _charset_ = null;
+ Part charset_part = multi_parts.getPart("_charset_");
+ if (charset_part != null) {
+ try (java.io.InputStream is = charset_part.getInputStream())
+ {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ IO.copy(is, os);
+ _charset_ = new String(os.toByteArray(),StandardCharsets.UTF_8);
+ }
+ }
+
+ /*
+ Select Charset to use for this part. (NOTE: charset behavior is for the part value only and not the part header/field names)
+ 1. Use the part specific charset as provided in that part's Content-Type header; else
+ 2. Use the overall default charset. Determined by:
+ a. if part name _charset_ exists, use that part's value.
+ b. if the request.getCharacterEncoding() returns a value, use that.
+ (note, this can be either from the charset field on the request Content-Type
+ header, or from a manual call to request.setCharacterEncoding())
+ c. use utf-8.
+ */
+ Charset def_charset;
+ if (_charset_ != null) {
+ def_charset = Charset.forName(_charset_);
+ } else if (getCharacterEncoding() != null) {
+ def_charset = Charset.forName(getCharacterEncoding());
+ } else {
+ def_charset = StandardCharsets.UTF_8;
+ }
+
+ ByteArrayOutputStream os = null;
+ for (Part p : parts) {
+ if (p.getSubmittedFileName() != null) {
+ continue;
+ }
+
+ // Servlet Spec 3.0 pg 23, parts without filename must be put into params.
+ String charset = null;
+ if (p.getContentType() != null) {
+ charset = MimeTypes.getCharsetFromContentType(p.getContentType());
+ }
+
+ try (java.io.InputStream is = p.getInputStream())
+ {
+ if (os == null) {
+ os = new ByteArrayOutputStream();
+ }
+ IO.copy(is, os);
+
+ String content = new String(os.toByteArray(), charset == null ? def_charset : Charset.forName(charset));
+ parameters.add(p.getName(), content);
+ }
+ os.reset();
+ }
+ }
}
+ } catch (IOException e) {
+ log("Unhandled IOException: " + e);
}
return parameters;