From 16c93a70051e729bd9b97e087fac6e2af4e04710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Santos=20Rodr=C3=ADguez?= Date: Tue, 6 Aug 2019 11:42:18 +0200 Subject: [PATCH 1/3] req.getContentLength() honoring Transfer-Encoding `MockHttpServletRequest#getContentLength()` returning -1 if `Transfer-Encoding` header set to `chunked` --- .../stripes/mock/MockHttpServletRequest.java | 112 +++++++++--------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/stripes/src/main/java/net/sourceforge/stripes/mock/MockHttpServletRequest.java b/stripes/src/main/java/net/sourceforge/stripes/mock/MockHttpServletRequest.java index caf9c7708..8751e16b4 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/mock/MockHttpServletRequest.java +++ b/stripes/src/main/java/net/sourceforge/stripes/mock/MockHttpServletRequest.java @@ -113,7 +113,7 @@ public void setAuthType(String authType) { /** * Gets the auth type being used by this request. - * @return + * @return */ public String getAuthType() { return this.authType; @@ -129,7 +129,7 @@ public void setCookies(Cookie[] cookies) { /** * Returns any cookies that are set on the request. - * @return + * @return */ public Cookie[] getCookies() { return this.cookies; @@ -151,7 +151,7 @@ public void addHeader(String name, Object value) { * Gets the named header as a long. Must have been set as a long with * addHeader(). * @param name - * @return + * @return */ public long getDateHeader(String name) { return (Long) this.headers.get(name); @@ -160,7 +160,7 @@ public long getDateHeader(String name) { /** * Returns any header as a String if it exists. * @param name - * @return + * @return */ public String getHeader(String name) { final Object header = this.headers.get(name == null ? null : name.toLowerCase()); @@ -171,7 +171,7 @@ public String getHeader(String name) { * Returns an enumeration with single value of the named header, or an empty * enum if no value. * @param name - * @return + * @return */ public Enumeration getHeaders(String name) { String header = getHeader(name); @@ -184,7 +184,7 @@ public Enumeration getHeaders(String name) { /** * Returns an enumeration containing all the names of headers supplied. - * @return + * @return */ public Enumeration getHeaderNames() { return Collections.enumeration(headers.keySet()); @@ -194,7 +194,7 @@ public Enumeration getHeaderNames() { * Gets the named header as an int. Must have been set as an Integer with * addHeader(). * @param name - * @return + * @return */ public int getIntHeader(String name) { String headerValue = getHeader(name); @@ -214,7 +214,7 @@ public void setMethod(String method) { /** * Gets the method used by the request. Defaults to POST. - * @return + * @return */ public String getMethod() { return this.method; @@ -230,7 +230,7 @@ public void setPathInfo(String pathInfo) { /** * Returns the path info. Defaults to the empty string. - * @return + * @return */ public String getPathInfo() { return this.pathInfo; @@ -238,7 +238,7 @@ public String getPathInfo() { /** * Always returns the same as getPathInfo(). - * @return + * @return */ public String getPathTranslated() { return getPathInfo(); @@ -254,7 +254,7 @@ public void setContextPath(String contextPath) { /** * Returns the context path. Defaults to the empty string. - * @return + * @return */ public String getContextPath() { return this.contextPath; @@ -271,7 +271,7 @@ public void setQueryString(String queryString) { /** * Returns the query string set on the request. - * @return + * @return */ public String getQueryString() { return this.queryString; @@ -279,7 +279,7 @@ public String getQueryString() { /** * Returns the name from the user principal if one exists, otherwise null. - * @return + * @return */ public String getRemoteUser() { Principal p = getUserPrincipal(); @@ -298,7 +298,7 @@ public void setRoles(Set roles) { * Returns true if the set of roles contains the role specified, false * otherwise. * @param role - * @return + * @return */ public boolean isUserInRole(String role) { return this.roles.contains(role); @@ -314,7 +314,7 @@ public void setUserPrincipal(Principal userPrincipal) { /** * Returns the Principal if one is set on the request. - * @return + * @return */ public Principal getUserPrincipal() { return this.userPrincipal; @@ -323,7 +323,7 @@ public Principal getUserPrincipal() { /** * Returns the ID of the session if one is attached to this request. * Otherwise null. - * @return + * @return */ public String getRequestedSessionId() { if (this.session == null) { @@ -334,7 +334,7 @@ public String getRequestedSessionId() { /** * Returns the request URI as defined by the servlet spec. - * @return + * @return */ public String getRequestURI() { return this.contextPath + this.servletPath + this.pathInfo; @@ -343,7 +343,7 @@ public String getRequestURI() { /** * Returns (an attempt at) a reconstructed URL based on its constituent * parts. - * @return + * @return */ public StringBuffer getRequestURL() { return new StringBuffer().append(this.protocol) @@ -358,7 +358,7 @@ public StringBuffer getRequestURL() { /** * Gets the part of the path which matched the servlet. - * @return + * @return */ public String getServletPath() { return this.servletPath; @@ -367,7 +367,7 @@ public String getServletPath() { /** * Gets the session object attached to this request. * @param b - * @return + * @return */ public HttpSession getSession(boolean b) { return this.session; @@ -375,7 +375,7 @@ public HttpSession getSession(boolean b) { /** * Gets the session object attached to this request. - * @return + * @return */ public HttpSession getSession() { return this.session; @@ -391,7 +391,7 @@ public void setSession(HttpSession session) { /** * Always returns true. - * @return + * @return */ public boolean isRequestedSessionIdValid() { return true; @@ -399,7 +399,7 @@ public boolean isRequestedSessionIdValid() { /** * Always returns true. - * @return + * @return */ public boolean isRequestedSessionIdFromCookie() { return true; @@ -407,7 +407,7 @@ public boolean isRequestedSessionIdFromCookie() { /** * Always returns false. - * @return + * @return */ public boolean isRequestedSessionIdFromURL() { return false; @@ -415,7 +415,7 @@ public boolean isRequestedSessionIdFromURL() { /** * Always returns false. - * @return + * @return */ public boolean isRequestedSessionIdFromUrl() { return false; @@ -424,7 +424,7 @@ public boolean isRequestedSessionIdFromUrl() { /** * Gets the named request attribute from an internal Map. * @param key - * @return + * @return */ public Object getAttribute(String key) { return this.attributes.get(key); @@ -432,7 +432,7 @@ public Object getAttribute(String key) { /** * Gets an enumeration of all request attribute names. - * @return + * @return */ public Enumeration getAttributeNames() { return Collections.enumeration(this.attributes.keySet()); @@ -440,7 +440,7 @@ public Enumeration getAttributeNames() { /** * Gets the character encoding, defaults to UTF-8. - * @return + * @return */ public String getCharacterEncoding() { return this.characterEncoding; @@ -457,15 +457,19 @@ public void setCharacterEncoding(String encoding) { /** * Always returns -1 (unknown). - * @return + * @return */ public int getContentLength() { - return requestBody.length; + if ("chunked".equals(getHeader("Transfer-Encoding"))) { + return -1; + } else { + return requestBody.length; + } } /** * Always returns null. - * @return + * @return */ public String getContentType() { return getHeader("content-type"); @@ -473,7 +477,7 @@ public String getContentType() { /** * Always returns null. - * @return + * @return * @throws java.io.IOException */ public ServletInputStream getInputStream() throws IOException { @@ -516,7 +520,7 @@ public void setReadListener(ReadListener readListener) { * Gets the first value of the named parameter or null if a value does not * exist. * @param name - * @return + * @return */ public String getParameter(String name) { String[] values = getParameterValues(name); @@ -529,7 +533,7 @@ public String getParameter(String name) { /** * Gets an enumeration containing all the parameter names present. - * @return + * @return */ public Enumeration getParameterNames() { return Collections.enumeration(this.parameters.keySet()); @@ -539,7 +543,7 @@ public Enumeration getParameterNames() { * Returns an array of all values for a parameter, or null if the parameter * does not exist. * @param name - * @return + * @return */ public String[] getParameterValues(String name) { return this.parameters.get(name); @@ -549,7 +553,7 @@ public String[] getParameterValues(String name) { * Provides access to the parameter map. Note that this returns a reference * to the live, modifiable parameter map. As a result it can be used to * insert parameters when constructing the request. - * @return + * @return */ public Map getParameterMap() { return this.parameters; @@ -565,7 +569,7 @@ public void setProtocol(String protocol) { /** * Gets the protocol for the request. Defaults to "https". - * @return + * @return */ public String getProtocol() { return this.protocol; @@ -573,7 +577,7 @@ public String getProtocol() { /** * Always returns the same as getProtocol. - * @return + * @return */ public String getScheme() { return getProtocol(); @@ -589,7 +593,7 @@ public void setServerName(String serverName) { /** * Gets the server name. Defaults to "localhost". - * @return + * @return */ public String getServerName() { return this.serverName; @@ -605,7 +609,7 @@ public void setServerPort(int serverPort) { /** * Returns the server port. Defaults to 8080. - * @return + * @return */ public int getServerPort() { return this.serverPort; @@ -613,7 +617,7 @@ public int getServerPort() { /** * Always returns null. - * @return + * @return * @throws java.io.IOException */ public BufferedReader getReader() throws IOException { @@ -622,7 +626,7 @@ public BufferedReader getReader() throws IOException { /** * Aways returns "127.0.0.1". - * @return + * @return */ public String getRemoteAddr() { return "127.0.0.1"; @@ -630,7 +634,7 @@ public String getRemoteAddr() { /** * Always returns "localhost". - * @return + * @return */ public String getRemoteHost() { return "localhost"; @@ -673,7 +677,7 @@ public void addLocale(Locale locale) { /** * Returns the preferred locale. Defaults to the system locale. - * @return + * @return */ public Locale getLocale() { return getLocales().nextElement(); @@ -682,7 +686,7 @@ public Locale getLocale() { /** * Returns an enumeration of requested locales. Defaults to the system * locale. - * @return + * @return */ public Enumeration getLocales() { if (this.locales.size() == 0) { @@ -694,7 +698,7 @@ public Enumeration getLocales() { /** * Returns true if the protocol is set to https (default), false otherwise. - * @return + * @return */ public boolean isSecure() { return this.protocol.equalsIgnoreCase("https"); @@ -705,7 +709,7 @@ public boolean isSecure() { * are forwarded to or included. The results can be examined later by * calling getForwardUrl() and getIncludedUrls(). * @param url - * @return + * @return */ public MockRequestDispatcher getRequestDispatcher(String url) { return new MockRequestDispatcher(url); @@ -714,7 +718,7 @@ public MockRequestDispatcher getRequestDispatcher(String url) { /** * Always returns the path passed in without any alteration. * @param path - * @return + * @return */ public String getRealPath(String path) { return path; @@ -722,7 +726,7 @@ public String getRealPath(String path) { /** * Always returns 1088 (and yes, that was picked arbitrarily). - * @return + * @return */ public int getRemotePort() { return 1088; @@ -730,7 +734,7 @@ public int getRemotePort() { /** * Always returns the same value as getServerName(). - * @return + * @return */ public String getLocalName() { return getServerName(); @@ -738,7 +742,7 @@ public String getLocalName() { /** * Always returns 127.0.0.1). - * @return + * @return */ public String getLocalAddr() { return "127.0.0.1"; @@ -746,7 +750,7 @@ public String getLocalAddr() { /** * Always returns the same value as getServerPort(). - * @return + * @return */ public int getLocalPort() { return getServerPort(); @@ -763,7 +767,7 @@ void setForwardUrl(String url) { /** * Gets the URL that was forwarded to, if a forward was processed. Null * otherwise. - * @return + * @return */ public String getForwardUrl() { return this.forwardUrl; @@ -779,7 +783,7 @@ void addIncludedUrl(String url) { /** * Gets the list (potentially empty) or URLs that were included during the * request. - * @return + * @return */ public List getIncludedUrls() { return this.includedUrls; From 7bdfa65f983cc64aa7b76cef7ababd4fa48c49e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Santos=20Rodr=C3=ADguez?= Date: Tue, 6 Aug 2019 11:47:38 +0200 Subject: [PATCH 2/3] fix for issue #74 --- .../sourceforge/stripes/controller/StripesRequestWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stripes/src/main/java/net/sourceforge/stripes/controller/StripesRequestWrapper.java b/stripes/src/main/java/net/sourceforge/stripes/controller/StripesRequestWrapper.java index e42bac21d..73c3502f8 100644 --- a/stripes/src/main/java/net/sourceforge/stripes/controller/StripesRequestWrapper.java +++ b/stripes/src/main/java/net/sourceforge/stripes/controller/StripesRequestWrapper.java @@ -123,7 +123,7 @@ public StripesRequestWrapper(HttpServletRequest request) throws StripesServletEx if (contentType != null) { if (isPost && contentType.startsWith("multipart/form-data")) { constructMultipartWrapper(request); - } else if (contentType.toLowerCase().contains("json") && request.getContentLength() > 0) { + } else if (contentType.toLowerCase().contains("json") && (request.getContentLength() > 0 || "chunked".equals(request.getHeader("Transfer-Encoding")))) { this.contentTypeRequestWrapper = new JsonContentTypeRequestWrapper(); try { this.contentTypeRequestWrapper.build(request); From 0609a155e99949bd9fb73bce144d4d8fc99c8f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Pablo=20Santos=20Rodr=C3=ADguez?= Date: Tue, 6 Aug 2019 11:48:38 +0200 Subject: [PATCH 3/3] test for issue #74 --- .../stripes/action/RestActionBeanTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/stripes/src/test/java/net/sourceforge/stripes/action/RestActionBeanTest.java b/stripes/src/test/java/net/sourceforge/stripes/action/RestActionBeanTest.java index 8569f5264..766451621 100644 --- a/stripes/src/test/java/net/sourceforge/stripes/action/RestActionBeanTest.java +++ b/stripes/src/test/java/net/sourceforge/stripes/action/RestActionBeanTest.java @@ -223,6 +223,29 @@ public void testJsonBindingFromRequestBody() throws Exception { logTripResponse(trip); } + @Test(groups = "fast") + public void testJsonBindingFromRequestBodyWithTransferEncoding() throws Exception { + MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), "/test/1"); + trip.getRequest().addHeader("Content-Type", "application/json"); + trip.getRequest().addHeader("Transfer-Encoding", "chunked"); + trip.getRequest().setMethod("POST"); + String json = "{ \"person\" : { \"firstName\":\"Jane\", \"lastName\":\"Johnson\", \"favoriteFoods\" : [\"Snickers\",\"Scotch\",\"Pizza\"], \"children\" : [{ \"firstName\": \"Jackie\"},{\"firstName\":\"Janie\"}]}}"; + trip.getRequest().setRequestBody(json); + trip.execute("boundPersonEvent"); + RestActionBeanTest bean = trip.getActionBean(getClass()); + Assert.assertEquals(bean.getPerson().getId(), "1"); + Assert.assertEquals(bean.getPerson().getFirstName(), "Jane"); + Assert.assertEquals(bean.getPerson().getLastName(), "Johnson"); + Assert.assertEquals(bean.getPerson().getChildren().size(), 2); + List< String> favoriteFoods = new ArrayList< String>(); + favoriteFoods.add("Snickers"); + favoriteFoods.add("Scotch"); + favoriteFoods.add("Pizza"); + Assert.assertEquals(bean.getPerson().getFavoriteFoods(), favoriteFoods); + + logTripResponse(trip); + } + @Test(groups = "fast") public void testThereIsNoJsonBindingWithoutRequestBody() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), "/test/1");