Revision: 8466
Author: fre...@google.com
Date: Mon Aug  2 20:55:43 2010
Log: Change RequestFactory based requests:
- No longer require a 'Content-Length' HTTP request header, thus enabling support for 'Transfer-Encoding: chunked' - Use 'application/json; charset=utf-8' Content-Type instead of 'text/plain; charset=utf=8'

Review at http://gwt-code-reviews.appspot.com/674804

Review by: amitman...@google.com
http://code.google.com/p/google-web-toolkit/source/detail?r=8466

Modified:
 /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java
/trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java /trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
 /trunk/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
/trunk/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java
 /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java
 /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java

=======================================
--- /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java Fri Apr 23 06:39:33 2010 +++ /trunk/user/src/com/google/gwt/junit/server/JUnitHostImpl.java Mon Aug 2 20:55:43 2010
@@ -144,7 +144,7 @@
       HttpServletResponse response) throws ServletException, IOException {
     String requestURI = request.getRequestURI();
     if (requestURI.endsWith("/junithost/loadError")) {
-      String requestPayload = RPCServletUtils.readContentAsUtf8(request);
+      String requestPayload = RPCServletUtils.readContentAsGwtRpc(request);
       JUnitResult result = new JUnitResult();
       initResult(request, result);
       result.setException(new JUnitFatalLaunchException(requestPayload));
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java Fri Jul 30 17:29:09 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java Mon Aug 2 20:55:43 2010
@@ -90,6 +90,8 @@
   public void fire(final RequestObject<?> requestObject) {
     RequestBuilder builder = new RequestBuilder(RequestBuilder.POST,
         GWT.getHostPageBaseURL() + RequestFactory.URL);
+    builder.setHeader(
+        "Content-Type", RequestFactory.JSON_CONTENT_TYPE_UTF8);
     // TODO: do something better here...
     if (requestObject.getDeltaValueStore().isChanged()) {
builder.setRequestData(ClientRequestHelper.getRequestString(JsonRequestDataUtil.getRequestMap(
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Mon Aug 2 15:57:18 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Mon Aug 2 20:55:43 2010
@@ -15,10 +15,13 @@
  */
 package com.google.gwt.requestfactory.server;

-import java.io.BufferedInputStream;
+import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.user.server.rpc.RPCServletUtils;
+
 import java.io.IOException;
 import java.io.PrintWriter;

+import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -49,14 +52,17 @@
  */
 @SuppressWarnings("serial")
 public class RequestFactoryServlet extends HttpServlet {
-
+  private static final String JSON_CHARSET = "UTF-8";
+  private static final String JSON_CONTENT_TYPE = "application/json";
+
   @SuppressWarnings("unchecked")
   @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
-      throws IOException {
+      throws IOException, ServletException {

     ensureConfig();
-    String jsonRequestString = getContent(request);
+    String jsonRequestString = RPCServletUtils.readContent(
+        request, JSON_CONTENT_TYPE, JSON_CHARSET);
     response.setStatus(HttpServletResponse.SC_OK);
     PrintWriter writer = response.getWriter();

@@ -72,6 +78,8 @@
         JsonRequestProcessor requestProcessor = new JsonRequestProcessor();
requestProcessor.setOperationRegistry(new ReflectionBasedOperationRegistry(
             new DefaultSecurityProvider()));
+        response.setHeader(
+            "Content-Type", RequestFactory.JSON_CONTENT_TYPE_UTF8);
writer.print(requestProcessor.decodeAndInvokeRequest(jsonRequestString));
         writer.flush();
       }
@@ -91,22 +99,4 @@
       UserInformation.setUserInformationImplClass(userInfoClass);
     }
   }
-
- private String getContent(HttpServletRequest request) throws IOException {
-    int contentLength = request.getContentLength();
-    byte contentBytes[] = new byte[contentLength];
- BufferedInputStream bis = new BufferedInputStream(request.getInputStream());
-    try {
-      int contentBytesOffset = 0;
-      int readLen;
-      while ((readLen = bis.read(contentBytes, contentBytesOffset,
-          contentLength - contentBytesOffset)) > 0) {
-        contentBytesOffset += readLen;
-      }
-      // TODO: encoding issues?
-      return new String(contentBytes);
-    } finally {
-      bis.close();
-    }
-  }
-}
+}
=======================================
--- /trunk/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java Fri Jul 30 11:32:15 2010 +++ /trunk/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java Mon Aug 2 20:55:43 2010
@@ -28,12 +28,14 @@
  * Marker interface for the RequestFactory code generator.
  */
 public interface RequestFactory {
+  public static final String JSON_CONTENT_TYPE_UTF8 =
+    "application/json; charset=utf-8";
+
+  String SYNC = "SYNC";

   // TODO: this must be configurable
   String URL = "gwtRequest";

-  String SYNC = "SYNC";
-
   Record create(Class token);

   ValueStore getValueStore();
=======================================
--- /trunk/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java Thu Nov 19 14:53:22 2009 +++ /trunk/user/src/com/google/gwt/user/server/rpc/AbstractRemoteServiceServlet.java Mon Aug 2 20:55:43 2010
@@ -169,14 +169,17 @@
* request. For example, you may want to bypass the check of the Content-Type * and character encoding headers in the request, as some proxies re-write the * request headers. Note that bypassing these checks may expose the servlet to
-   * some cross-site vulnerabilities.
+ * some cross-site vulnerabilities. Your implementation should comply with the + * HTTP/1.1 specification, which includes handling both requests which include + * a Content-Length header and requests utilizing <code>Transfer-Encoding:
+   * chuncked</code>.
    *
    * @param request the incoming request
    * @return the content of the incoming request encoded as a string.
    */
   protected String readContent(HttpServletRequest request)
       throws ServletException, IOException {
-    return RPCServletUtils.readContentAsUtf8(request, true);
+    return RPCServletUtils.readContentAsGwtRpc(request);
   }

   /**
=======================================
--- /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Fri Jul 30 12:54:15 2010 +++ /trunk/user/src/com/google/gwt/user/server/rpc/RPCServletUtils.java Mon Aug 2 20:55:43 2010
@@ -40,6 +40,9 @@

   private static final String ATTACHMENT = "attachment";

+  /**
+   * Used both as expected request charset and encoded response charset.
+   */
   private static final String CHARSET_UTF8 = "UTF-8";

   private static final String CONTENT_DISPOSITION = "Content-Disposition";
@@ -50,12 +53,10 @@

private static final String CONTENT_TYPE_APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";

-  private static final String EXPECTED_CHARSET = "charset=utf-8";
-
-  private static final String EXPECTED_CONTENT_TYPE = "text/x-gwt-rpc";
-
private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";

+  private static final String GWT_RPC_CONTENT_TYPE = "text/x-gwt-rpc";
+
   /**
* Controls the compression threshold at and below which no compression will
    * take place.
@@ -132,43 +133,34 @@

   /**
* Returns the content of an {...@link HttpServletRequest} by decoding it using
-   * the UTF-8 charset.
+   * <code>expectedCharSet</code>, or <code>UTF-8</code> if
+   * <code>expectedCharSet</code> is <code>null</null>.
    *
    * @param request the servlet request whose content we want to read
+ * @param expectedContentType the expected content (i.e. 'type/subtype' only) + * in the Content-Type request header, or <code>null</code> if no + * validation is to be performed, and you are willing to allow for
+   *          some types of cross type security attacks
+ * @param expectedCharSet the expected request charset, or <code>null</code> + * if no charset validation is to be performed and <code>UTF-8</code>
+   *          should be assumed
* @return the content of an {...@link HttpServletRequest} by decoding it using
-   *         the UTF-8 charset
- * @throws IOException if the requests input stream cannot be accessed, read
-   *           from or closed
-   * @throws ServletException if the content length of the request is not
-   *           specified of if the request's content type is not
-   *           'text/x-gwt-rpc' and 'charset=utf-8'
+   *         <code>expectedCharSet</code>, or <code>UTF-8</code> if
+   *         <code>expectedCharSet</code> is <code>null</null>
+ * @throws IOException if the request's input stream cannot be accessed, read
+   *         from or closed
+   * @throws ServletException if the request's content type does not
+   *         equal the supplied <code>expectedContentType</code> or
+   *         <code>expectedCharSet</code>
    */
-  public static String readContentAsUtf8(HttpServletRequest request)
+  public static String readContent(HttpServletRequest request,
+      String expectedContentType, String expectedCharSet)
       throws IOException, ServletException {
-    return readContentAsUtf8(request, true);
-  }
-
-  /**
- * Returns the content of an {...@link HttpServletRequest} by decoding it using
-   * the UTF-8 charset.
-   *
-   * @param request the servlet request whose content we want to read
- * @param checkHeaders Specify 'true' to check the Content-Type header to see
-   *          that it matches the expected value 'text/x-gwt-rpc' and the
- * content encoding is UTF-8. Disabling this check may allow some
-   *          types of cross type security attacks.
- * @return the content of an {...@link HttpServletRequest} by decoding it using
-   *         the UTF-8 charset
- * @throws IOException if the requests input stream cannot be accessed, read
-   *           from or closed
-   * @throws ServletException if the request's content type is not
-   *         'text/x-gwt-rpc' and 'charset=utf-8'
-   */
-  public static String readContentAsUtf8(HttpServletRequest request,
-      boolean checkHeaders) throws IOException, ServletException {
-    if (checkHeaders) {
-      checkContentType(request);
-      checkCharacterEncoding(request);
+    if (expectedContentType != null) {
+      checkContentTypeIgnoreCase(request, expectedContentType);
+    }
+    if (expectedCharSet != null) {
+      checkCharacterEncodingIgnoreCase(request, expectedCharSet);
     }

     /*
@@ -186,13 +178,32 @@
         }
         out.write(buffer, 0, byteCount);
       }
-      return out.toString(CHARSET_UTF8);
+      String contentCharSet = expectedCharSet != null
+          ? expectedCharSet : CHARSET_UTF8;
+      return out.toString(contentCharSet);
     } finally {
       if (in != null) {
         in.close();
       }
     }
   }
+
+  /**
+ * Returns the content of an {...@link HttpServletRequest}, after verifying a
+   * <code>gwt/x-gwt-rpc; charset=utf-8</code> content type.
+   *
+   * @param request the servlet request whose content we want to read
+ * @return the content of an {...@link HttpServletRequest} by decoding it using
+   *         <code>UTF-8</code>
+ * @throws IOException if the request's input stream cannot be accessed, read
+   *         from or closed
+   * @throws ServletException if the request's content type is not
+   *         <code>gwt/x-gwt-rpc; charset=utf-8</code>, ignoring case
+   */
+  public static String readContentAsGwtRpc(HttpServletRequest request)
+      throws IOException, ServletException {
+      return readContent(request, GWT_RPC_CONTENT_TYPE, CHARSET_UTF8);
+  }

   /**
    * Sets the correct header to indicate that a response is gzipped.
@@ -307,12 +318,15 @@
   }

   /**
-   * Performs validation of the character encoding.
+   * Performs validation of the character encoding, ignoring case.
    *
-   * @param request the incoming request.
-   * @throws ServletException if requests encoding is not UTF-8
+   * @param request the incoming request
+   * @param expectedCharSet the expected charset of the request
+ * @throws ServletException if requests encoding is not <code>null</code> and
+   *         does not equal, ignoring case, <code>expectedCharSet</code>
    */
-  private static void checkCharacterEncoding(HttpServletRequest request)
+  private static void checkCharacterEncodingIgnoreCase(
+      HttpServletRequest request, String expectedCharSet)
       throws ServletException {
     boolean encodingOkay = false;
     String characterEncoding = request.getCharacterEncoding();
@@ -323,7 +337,8 @@
* properly parsed character encoding string if we decide to make this
        * change.
        */
- if (characterEncoding.toLowerCase().indexOf(CHARSET_UTF8.toLowerCase()) != -1) { + if (characterEncoding.toLowerCase().indexOf(expectedCharSet.toLowerCase())
+          != -1) {
         encodingOkay = true;
       }
     }
@@ -331,18 +346,24 @@
     if (!encodingOkay) {
       throw new ServletException("Character Encoding is '"
           + (characterEncoding == null ? "(null)" : characterEncoding)
-          + "'.  Expected '" + EXPECTED_CHARSET + "'");
+          + "'.  Expected '" + expectedCharSet + "'");
     }
   }

   /**
-   * Performs Content-Type validation of the incoming request.
-   *
-   * @param request the incoming request.
+ * Performs Content-Type validation of the incoming request, ignoring case
+   * and any <code>charset</code> parameter.
+   *
+   * @see   #checkCharacterEncodingIgnoreCase(HttpServletRequest, String)
+   * @param request the incoming request
+   * @param expectedContentType the expected Content-Type for the incoming
+   *        request
    * @throws ServletException if the request's content type is not
-   *           'text/x-gwt-rpc'
+   *         <code>null</code> and does not, ignoring case, equal
+   *         <code>expectedContentType</code>,
    */
-  private static void checkContentType(HttpServletRequest request)
+  private static void checkContentTypeIgnoreCase(
+      HttpServletRequest request, String expectedContentType)
       throws ServletException {
     String contentType = request.getContentType();
     boolean contentTypeIsOkay = false;
@@ -350,12 +371,10 @@
     if (contentType != null) {
       contentType = contentType.toLowerCase();
       /*
-       * The Content-Type must be text/x-gwt-rpc.
-       *
* NOTE:We use startsWith because some servlet engines, i.e. Tomcat, do
        * not remove the charset component but others do.
        */
-      if (contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
+      if (contentType.startsWith(expectedContentType.toLowerCase())) {
         contentTypeIsOkay = true;
       }
     }
@@ -363,7 +382,7 @@
     if (!contentTypeIsOkay) {
       throw new ServletException("Content-Type was '"
+ (contentType == null ? "(null)" : contentType) + "'. Expected '"
-          + EXPECTED_CONTENT_TYPE + "'.");
+          + expectedContentType + "'.");
     }
   }

=======================================
--- /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java Fri Jul 30 12:54:15 2010 +++ /trunk/user/test/com/google/gwt/user/server/rpc/RPCServletUtilsTest.java Mon Aug 2 20:55:43 2010
@@ -178,7 +178,7 @@
       }
     };

-    RPCServletUtils.readContentAsUtf8(m, false);
+    RPCServletUtils.readContent(m, null, null);
   }

   /**
@@ -186,7 +186,7 @@
    */
public void testIgnoreContentType() throws IOException, ServletException { HttpServletRequest m = new MockReqContentType("application/www-form-encoded");
-    RPCServletUtils.readContentAsUtf8(m, false);
+    RPCServletUtils.readContent(m, null, null);
   }

   /**
@@ -203,7 +203,7 @@
     boolean gotException = false;

     try {
-      RPCServletUtils.readContentAsUtf8(m);
+      RPCServletUtils.readContentAsGwtRpc(m);
     } catch (ServletException se) {
       if (se.getMessage().indexOf("Character Encoding") != 0) {
         fail(" Unexpected exception " + se);
@@ -224,7 +224,7 @@
         "application/www-form-encoded");
     boolean gotException = false;
     try {
-      RPCServletUtils.readContentAsUtf8(m);
+      RPCServletUtils.readContentAsGwtRpc(m);
     } catch (ServletException se) {
       if (se.getMessage().indexOf("Content-Type") != 0) {
         fail(" Unexpected exception " + se);
@@ -241,7 +241,16 @@
    */
public void testReadGoodContentType() throws IOException, ServletException {
     HttpServletRequest m = new MockReqContentType("text/x-gwt-rpc");
-    RPCServletUtils.readContentAsUtf8(m);
+    RPCServletUtils.readContentAsGwtRpc(m);
+  }
+
+  /**
+   * Content-Type validation should ignore case.
+   */
+  public void testReadGoodContentTypeIgnoreCase()
+      throws IOException, ServletException {
+    HttpServletRequest m = new MockReqContentType("tExt/X-gwt-rPc");
+    RPCServletUtils.readContentAsGwtRpc(m);
   }

   /**
@@ -257,7 +266,7 @@
     };
     boolean gotException = false;
     try {
-      RPCServletUtils.readContentAsUtf8(m);
+      RPCServletUtils.readContentAsGwtRpc(m);
     } catch (ServletException se) {
       if (se.getMessage().indexOf("Character Encoding") != 0) {
         fail(" Unexpected exception " + se);
@@ -276,7 +285,7 @@
     HttpServletRequest m = new MockReqContentType(null);
     boolean gotException = false;
     try {
-      RPCServletUtils.readContentAsUtf8(m);
+      RPCServletUtils.readContentAsGwtRpc(m);
     } catch (ServletException se) {
       if (se.getMessage().indexOf("Content-Type") != 0) {
         fail(" Unexpected exception " + se);
@@ -289,7 +298,8 @@
   }

private String readContentAsUtf8(String content) throws IOException, ServletException { - HttpServletRequest m = new MockReqContentType("text/x-gwt-rpc", content);
-    return RPCServletUtils.readContentAsUtf8(m, false);
+    HttpServletRequest m = new MockReqContentType(null, content);
+    // ignore Content-Type, read as UTF-8
+    return RPCServletUtils.readContent(m, null, null);
   }
 }

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to