diff options
author | Max Romanov <max.romanov@nginx.com> | 2019-08-14 15:24:41 +0300 |
---|---|---|
committer | Max Romanov <max.romanov@nginx.com> | 2019-08-14 15:24:41 +0300 |
commit | 4bef4256c05c3f905e3d65e40585bba5ce0f3327 (patch) | |
tree | c5b8b67273cc639e3ac0cd062a65865e6a5cc61a /src/java | |
parent | 8904c87c6beb4f2b080bf4269fb211e6f2eea7f2 (diff) | |
download | unit-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.java | 31 | ||||
-rw-r--r-- | src/java/nginx/unit/ForwardRequestWrapper.java | 12 | ||||
-rw-r--r-- | src/java/nginx/unit/IncludeRequestWrapper.java | 12 | ||||
-rw-r--r-- | src/java/nginx/unit/Request.java | 151 |
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; |