You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@wookie.apache.org by sc...@apache.org on 2011/10/30 01:18:54 UTC
svn commit: r1195073 - in /incubator/wookie/trunk:
src-tests/org/apache/wookie/tests/functional/
src-tests/org/apache/wookie/tests/helpers/ src/org/apache/wookie/proxy/
Author: scottbw
Date: Sat Oct 29 23:18:54 2011
New Revision: 1195073
URL: http://svn.apache.org/viewvc?rev=1195073&view=rev
Log:
A substantial rewrite of the proxy service to support full headers being sent and returned, including cookies, and for any content types to be returned in the response body (see WOOKIE-251). Also added more tests, using a NanoHTTPD test server to check that custom headers and cookies are preserved across proxied requests.
Added:
incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
Modified:
incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
Modified: incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java?rev=1195073&r1=1195072&r2=1195073&view=diff
==============================================================================
--- incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java (original)
+++ incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java Sat Oct 29 23:18:54 2011
@@ -20,6 +20,7 @@ import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Properties;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
@@ -29,6 +30,7 @@ import org.apache.commons.httpclient.aut
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.wookie.tests.helpers.NanoHTTPD;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
@@ -39,386 +41,516 @@ import org.junit.Test;
*/
public class ProxyTest extends AbstractControllerTest {
- private static String instance_id_key;
- private static String other_instance_id_key;
- private static final String OTHER_WIDGET_ID = "http://www.getwookie.org/widgets/natter";
- private static final String PROXY_URL = TEST_SERVER_LOCATION+"proxy";
-
- /**
- * The valid site URL is our Apache incubator page. This is added by default to the global site whitelist
- */
- private static final String VALID_SITE_URL = "http://incubator.apache.org/wookie/";
-
- /**
- * Returns XML for checking we get the correct content-type
- */
- private static final String VALID_SITE_XML_URL = TEST_SERVER_LOCATION+"widgets?all=true";
-
- /**
- * An invalid URL
- */
- private static final String INVALID_SITE_URL = "DFASFAFEQ3FQ32145235123452";
-
- /**
- * A site not allowed in any policies
- */
- private static final String BLOCKED_SITE_URL = "http://very.bad.place";
-
- /**
- * The policy allowed site URL is one that the Weather widget is granted acccess to using WARP in the default install
- */
- private static final String POLICY_ALLOWED_SITE_URL = "http://newsrss.bbc.co.uk/weather/forecast/9/Next3DaysRSS.xml";
- private static final String POLICY_DISALLOWED_SITE_URL = "http://news.bbc.co.uk";
- private static final String PROTECTED_SITE_URL = TEST_SERVER_LOCATION+"admin/";
-
- /**
- * Create two instances of a widget for testing
- * @throws IOException
- * @throws HttpException
- */
- @BeforeClass
- public static void setup() throws HttpException, IOException{
- HttpClient client = new HttpClient();
- PostMethod post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
- post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest");
- client.executeMethod(post);
- String response = post.getResponseBodyAsString();
- instance_id_key = response.substring(response.indexOf("<identifier>")+12, response.indexOf("</identifier>"));
- post.releaseConnection();
-
- client = new HttpClient();
- post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
- post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+OTHER_WIDGET_ID+"&userid=test&shareddatakey=proxytest");
- client.executeMethod(post);
- response = post.getResponseBodyAsString();
- other_instance_id_key = response.substring(response.indexOf("<identifier>")+12, response.indexOf("</identifier>"));
- post.releaseConnection();
-
- }
-
-/**
- * Check we can access a site allowed under the global whitelist using our widget instance's id key
- */
- @Test
- public void getValidSite(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
- assertEquals(200,send(url,"GET"));
- assertEquals(500,send(url,"POST")); // This URL doesn't support POST
- }
-
- /**
- * Check we can access a site allowed under a widget-specific policy using an instance's id key
- */
- @Test
- public void getAllowedSite(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
- assertEquals(200,send(url,"GET"));
- }
-
- /**
- * Check that we can't access a site which is not in the whitelist or WARP
- */
- @Test
- public void getDisallowedSite(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_DISALLOWED_SITE_URL;
- assertEquals(403,send(url,"GET"));
- assertEquals(403,send(url,"POST")); // This URL doesn't support POST
- }
-
- /**
- * Test trying to get to a site allowed for a different widget, but not this one
- */
- @Test
- public void getAllowedSiteWrongInstance(){
- String url = PROXY_URL+"?instanceid_key="+other_instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
- assertEquals(403,send(url,"GET"));
- assertEquals(403,send(url,"POST"));
- }
-
- /**
- * Test we can get the content from a site with the correct content-type (in this case XML)
- * @throws Exception
- */
- @Test
- public void getValidSiteAndValidXMLContentType() throws Exception{
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_XML_URL;
- HttpClient client = new HttpClient();
- HttpMethod req = new GetMethod(url);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/xml"));
- }
-
- /**
- * Test we can get the content from a site with the correct content-type (in this case HTML)
- * @throws Exception
- */
- @Test
- public void getValidSiteAndValidHTMLContentType() throws Exception{
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
- HttpClient client = new HttpClient();
- HttpMethod req = new GetMethod(url);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/html"));
- }
-
-
-/**
- * Test that we can't access a site with an invalid instance ID key
- */
- @Test
- public void getValidSiteInvalidIdKey(){
- String url = PROXY_URL+"?instanceid_key=TEST&url="+VALID_SITE_URL;
- assertEquals(403,send(url,"GET"));
- assertEquals(403,send(url,"POST"));
- }
-
- /**
- * Test that we can't access a site no instance id key
- */
- @Test
- public void getValidSiteNoIdKey(){
- String url = PROXY_URL+"?url="+VALID_SITE_URL;
- assertEquals(403,send(url,"GET"));
- assertEquals(403,send(url,"POST"));
- }
-
- /**
- * Test we can't get access to a site using an invalid URL
- */
- @Test
- public void getInvalidSite(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+INVALID_SITE_URL;
- assertEquals(400,send(url,"GET"));
- assertEquals(400,send(url,"POST"));
- }
+ private static String instance_id_key;
+ private static String other_instance_id_key;
+ private static final String OTHER_WIDGET_ID = "http://www.getwookie.org/widgets/natter";
+ private static final String PROXY_URL = TEST_SERVER_LOCATION+"proxy";
+
+ /**
+ * The valid site URL is our Apache incubator page. This is added by default to the global site whitelist
+ */
+ private static final String VALID_SITE_URL = "http://incubator.apache.org/wookie/";
+
+ /**
+ * Returns XML for checking we get the correct content-type
+ */
+ private static final String VALID_SITE_XML_URL = TEST_SERVER_LOCATION+"widgets?all=true";
+
+ /**
+ * An invalid URL
+ */
+ private static final String INVALID_SITE_URL = "DFASFAFEQ3FQ32145235123452";
+
+ /**
+ * A site not allowed in any policies
+ */
+ private static final String BLOCKED_SITE_URL = "http://very.bad.place";
+
+ /**
+ * The policy allowed site URL is one that the Weather widget is granted acccess to using WARP in the default install
+ */
+ private static final String POLICY_ALLOWED_SITE_URL = "http://newsrss.bbc.co.uk/weather/forecast/9/Next3DaysRSS.xml";
+ private static final String POLICY_DISALLOWED_SITE_URL = "http://news.bbc.co.uk";
+ private static final String PROTECTED_SITE_URL = TEST_SERVER_LOCATION+"admin/";
+
+ /**
+ * Create two instances of a widget for testing
+ * @throws IOException
+ * @throws HttpException
+ */
+ @BeforeClass
+ public static void setup() throws HttpException, IOException{
+ HttpClient client = new HttpClient();
+ PostMethod post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
+ post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest");
+ client.executeMethod(post);
+ String response = post.getResponseBodyAsString();
+ instance_id_key = response.substring(response.indexOf("<identifier>")+12, response.indexOf("</identifier>"));
+ post.releaseConnection();
+
+ client = new HttpClient();
+ post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
+ post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+OTHER_WIDGET_ID+"&userid=test&shareddatakey=proxytest");
+ client.executeMethod(post);
+ response = post.getResponseBodyAsString();
+ other_instance_id_key = response.substring(response.indexOf("<identifier>")+12, response.indexOf("</identifier>"));
+ post.releaseConnection();
+
+ }
+
+ /**
+ * Check we can access a site allowed under the global whitelist using our widget instance's id key
+ */
+ @Test
+ public void getValidSite(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
+ assertEquals(200,send(url,"GET"));
+ assertEquals(405,send(url,"POST")); // This URL doesn't support POST
+ }
+
+ /**
+ * Check we can access a site allowed under a widget-specific policy using an instance's id key
+ */
+ @Test
+ public void getAllowedSite(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
+ assertEquals(200,send(url,"GET"));
+ }
+
+ /**
+ * Check that we can't access a site which is not in the whitelist or WARP
+ */
+ @Test
+ public void getDisallowedSite(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_DISALLOWED_SITE_URL;
+ assertEquals(403,send(url,"GET"));
+ assertEquals(403,send(url,"POST")); // This URL doesn't support POST
+ }
+
+ /**
+ * Test trying to get to a site allowed for a different widget, but not this one
+ */
+ @Test
+ public void getAllowedSiteWrongInstance(){
+ String url = PROXY_URL+"?instanceid_key="+other_instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
+ assertEquals(403,send(url,"GET"));
+ assertEquals(403,send(url,"POST"));
+ }
+
+ /**
+ * Test we can get the content from a site with the correct content-type (in this case XML)
+ * @throws Exception
+ */
+ @Test
+ public void getValidSiteAndValidXMLContentType() throws Exception{
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_XML_URL;
+ HttpClient client = new HttpClient();
+ HttpMethod req = new GetMethod(url);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/xml"));
+ }
+
+ /**
+ * Test we can get the content from a site with the correct content-type (in this case HTML)
+ * @throws Exception
+ */
+ @Test
+ public void getValidSiteAndValidHTMLContentType() throws Exception{
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
+ HttpClient client = new HttpClient();
+ HttpMethod req = new GetMethod(url);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/html"));
+ }
+
- /**
+ /**
+ * Test that we can't access a site with an invalid instance ID key
+ */
+ @Test
+ public void getValidSiteInvalidIdKey(){
+ String url = PROXY_URL+"?instanceid_key=TEST&url="+VALID_SITE_URL;
+ assertEquals(403,send(url,"GET"));
+ assertEquals(403,send(url,"POST"));
+ }
+
+ /**
+ * Test that we can't access a site no instance id key
+ */
+ @Test
+ public void getValidSiteNoIdKey(){
+ String url = PROXY_URL+"?url="+VALID_SITE_URL;
+ assertEquals(403,send(url,"GET"));
+ assertEquals(403,send(url,"POST"));
+ }
+
+ /**
+ * Test we can't get access to a site using an invalid URL
+ */
+ @Test
+ public void getInvalidSite(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+INVALID_SITE_URL;
+ assertEquals(400,send(url,"GET"));
+ assertEquals(400,send(url,"POST"));
+ }
+
+ /**
* Test we can't get access to a site that isn't in either the global whitelist of WARP
*/
- @Test
- public void getBlockedSite(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+BLOCKED_SITE_URL;
- assertEquals(403,send(url,"GET"));
- }
-
- /**
- * Tests that we can get valid returns when using querystring parameters in a POST request
- * @throws Exception
- */
- @Test
- public void postWithOnlyQueryStringParameters() throws Exception{
- HttpClient client = new HttpClient();
- List<String> authPrefs = new ArrayList<String>(2);
- authPrefs.add(AuthPolicy.DIGEST );
- authPrefs.add(AuthPolicy.BASIC);
- client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
- //
- // send the basic authentication response even before the server gives an unauthorized response
- //
- client.getParams().setAuthenticationPreemptive(true);
- client.getState().setCredentials(
- new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
- new UsernamePasswordCredentials("java", "java"));
- PostMethod req;
- req = new PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- }
-
- /**
- * Ignored - this was a case for handling double-encoding, but which is no longer valid
- */
- @Ignore // Not a valid use case
- @Test
- public void getWithEncodedParameters(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"%3Fx=1%26y=2";
- assertEquals(200,send(url,"GET"));
- }
-
- /**
- * Test the proxy can handle a site URL that contains unencoded parameters including a second "?" character
- */
- @Test
- public void getWithUnencodedParameters(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"?x=1&y=2";
- assertEquals(200,send(url,"GET"));
- }
-
- /**
+ @Test
+ public void getBlockedSite(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+BLOCKED_SITE_URL;
+ assertEquals(403,send(url,"GET"));
+ }
+
+ /**
+ * Tests that we can get valid returns when using querystring parameters in a POST request
+ * @throws Exception
+ */
+ @Test
+ public void postWithOnlyQueryStringParameters() throws Exception{
+ HttpClient client = new HttpClient();
+ List<String> authPrefs = new ArrayList<String>(2);
+ authPrefs.add(AuthPolicy.DIGEST );
+ authPrefs.add(AuthPolicy.BASIC);
+ client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+ //
+ // send the basic authentication response even before the server gives an unauthorized response
+ //
+ client.getParams().setAuthenticationPreemptive(true);
+ client.getState().setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+ new UsernamePasswordCredentials("java", "java"));
+ PostMethod req;
+ req = new PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ }
+
+ /**
+ * Ignored - this was a case for handling double-encoding, but which is no longer valid
+ */
+ @Ignore // Not a valid use case
+ @Test
+ public void getWithEncodedParameters(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"%3Fx=1%26y=2";
+ assertEquals(200,send(url,"GET"));
+ }
+
+ /**
+ * Test the proxy can handle a site URL that contains unencoded parameters including a second "?" character
+ */
+ @Test
+ public void getWithUnencodedParameters(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"?x=1&y=2";
+ assertEquals(200,send(url,"GET"));
+ }
+
+ /**
* Test the proxy can handle a site URL that contains unencoded parameters appended in the usual form generated
* by JQuery
*/
- @Test
- public void getWithJQueryEncodedParameters(){
- String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"&x=y";
- assertEquals(200,send(url,"GET"));
- assertEquals(500,send(url,"POST")); // This URL doesn't support POST
- }
-
- /**
- * Test that the proxy behaves as expected when a POST request contains a mix of querystring parameters and post parameters
- * @throws Exception
- */
- @Test
- public void postWithMixedQueryAndParameters() throws Exception{
- HttpClient client = new HttpClient();
- List<String> authPrefs = new ArrayList<String>(2);
- authPrefs.add(AuthPolicy.DIGEST );
- authPrefs.add(AuthPolicy.BASIC);
- client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
- //
- // send the basic authentication response even before the server gives an unauthorized response
- //
- client.getParams().setAuthenticationPreemptive(true);
- client.getState().setCredentials(
- new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
- new UsernamePasswordCredentials("java", "java"));
- PostMethod req;
- req = new PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key);
- req.addParameter("url", PROTECTED_SITE_URL);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- }
-
- /**
- * This tests accessing a site passing through POST parameters
- * @throws Exception
- */
- @Test
- public void postWithPassingParameters() throws Exception{
- String url = PROXY_URL;
- HttpClient client = new HttpClient();
- PostMethod post = new PostMethod(url);
- post.addParameter("instanceid_key", instance_id_key);
- post.addParameter("url", TEST_PROPERTIES_SERVICE_URL_VALID);
- post.addParameter("api_key", API_KEY_VALID);
- post.addParameter("widgetid", WIDGET_ID_VALID);
- post.addParameter("userid", "test");
- post.addParameter("is_public", "false");
- post.addParameter("shareddatakey","proxytest");
- post.addParameter("propertyname", "pass");
- post.addParameter("propertyvalue","pass");
- client.executeMethod(post);
- int code = post.getStatusCode();
- assertEquals(200,code);
- post.releaseConnection();
-
- client = new HttpClient();
- GetMethod get = new GetMethod(TEST_PROPERTIES_SERVICE_URL_VALID);
- get.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest&propertyname=pass");
- client.executeMethod(get);
- code = get.getStatusCode();
- assertEquals(200, code);
- String resp = get.getResponseBodyAsString();
- assertEquals("pass",resp);
- get.releaseConnection();
- }
-
- /**
- * Tests that the proxy behaves correctly when using POST parameters only
- * @throws Exception
- */
- @Test
- public void postWithOnlyParameters() throws Exception{
- HttpClient client = new HttpClient();
- List<String> authPrefs = new ArrayList<String>(2);
- authPrefs.add(AuthPolicy.DIGEST );
- authPrefs.add(AuthPolicy.BASIC);
- client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
- //
- // send the basic authentication response even before the server gives an unauthorized response
- //
- client.getParams().setAuthenticationPreemptive(true);
- client.getState().setCredentials(
- new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
- new UsernamePasswordCredentials("java", "java"));
- PostMethod req;
- req = new PostMethod(PROXY_URL);
- req.addParameter("url", PROTECTED_SITE_URL);
- req.addParameter("instanceid_key", instance_id_key);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- }
-
- /**
- * Test that the proxy passes through the correct status code when trying to access
- * an allowed resource without authn credentials required at the final target site
- * @throws Exception
- */
- @Test
- public void getProtectedSiteWithoutAuth() throws Exception{
- HttpClient client = new HttpClient();
- HttpMethod req;
- req = new GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(401,code);
- }
-
- /**
- * Test that the proxy passes along credentials to the proxied site and is granted access
- * @throws Exception
- */
- @Test
- public void getProtectedSiteWithBasicAuth() throws Exception{
- HttpClient client = new HttpClient();
- List<String> authPrefs = new ArrayList<String>(2);
- authPrefs.add(AuthPolicy.DIGEST );
- authPrefs.add(AuthPolicy.BASIC);
- client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
- //
- // send the basic authentication response even before the server gives an unauthorized response
- //
- client.getParams().setAuthenticationPreemptive(true);
- client.getState().setCredentials(
- new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
- new UsernamePasswordCredentials("java", "java"));
- HttpMethod req;
- req = new GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- assertEquals(200,code);
- }
-
- /**
- * Utility method for sending requests
- * @param url
- * @param method
- * @return
- */
- private int send(String url, String method){
- try {
- HttpClient client = new HttpClient();
- HttpMethod req;
- if (method.equals("POST")){
- req = new PostMethod(url);
- } else {
- req = new GetMethod(url);
- }
- client.executeMethod(req);
- int code = req.getStatusCode();
- req.releaseConnection();
- return code;
- }
- catch (Exception e) {
- e.printStackTrace();
- fail("post failed");
- return -1;
- }
- }
+ @Test
+ public void getWithJQueryEncodedParameters(){
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"&x=y";
+ assertEquals(200,send(url,"GET"));
+ assertEquals(405,send(url,"POST")); // This URL doesn't support POST
+ }
+
+ /**
+ * Test that the proxy behaves as expected when a POST request contains a mix of querystring parameters and post parameters
+ * @throws Exception
+ */
+ @Test
+ public void postWithMixedQueryAndParameters() throws Exception{
+ HttpClient client = new HttpClient();
+ List<String> authPrefs = new ArrayList<String>(2);
+ authPrefs.add(AuthPolicy.DIGEST );
+ authPrefs.add(AuthPolicy.BASIC);
+ client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+ //
+ // send the basic authentication response even before the server gives an unauthorized response
+ //
+ client.getParams().setAuthenticationPreemptive(true);
+ client.getState().setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+ new UsernamePasswordCredentials("java", "java"));
+ PostMethod req;
+ req = new PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key);
+ req.addParameter("url", PROTECTED_SITE_URL);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ }
+
+ /**
+ * This tests accessing a site passing through POST parameters
+ * @throws Exception
+ */
+ @Test
+ public void postWithPassingParameters() throws Exception{
+ String url = PROXY_URL;
+ HttpClient client = new HttpClient();
+ PostMethod post = new PostMethod(url);
+ post.addParameter("instanceid_key", instance_id_key);
+ post.addParameter("url", TEST_PROPERTIES_SERVICE_URL_VALID);
+ post.addParameter("api_key", API_KEY_VALID);
+ post.addParameter("widgetid", WIDGET_ID_VALID);
+ post.addParameter("userid", "test");
+ post.addParameter("is_public", "false");
+ post.addParameter("shareddatakey","proxytest");
+ post.addParameter("propertyname", "pass");
+ post.addParameter("propertyvalue","pass");
+ client.executeMethod(post);
+ int code = post.getStatusCode();
+ assertEquals(201,code);
+ post.releaseConnection();
+
+ client = new HttpClient();
+ GetMethod get = new GetMethod(TEST_PROPERTIES_SERVICE_URL_VALID);
+ get.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest&propertyname=pass");
+ client.executeMethod(get);
+ code = get.getStatusCode();
+ assertEquals(200, code);
+ String resp = get.getResponseBodyAsString();
+ assertEquals("pass",resp);
+ get.releaseConnection();
+ }
+
+ /**
+ * Tests that the proxy behaves correctly when using POST parameters only
+ * @throws Exception
+ */
+ @Test
+ public void postWithOnlyParameters() throws Exception{
+ HttpClient client = new HttpClient();
+ List<String> authPrefs = new ArrayList<String>(2);
+ authPrefs.add(AuthPolicy.DIGEST );
+ authPrefs.add(AuthPolicy.BASIC);
+ client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+ //
+ // send the basic authentication response even before the server gives an unauthorized response
+ //
+ client.getParams().setAuthenticationPreemptive(true);
+ client.getState().setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+ new UsernamePasswordCredentials("java", "java"));
+ PostMethod req;
+ req = new PostMethod(PROXY_URL);
+ req.addParameter("url", PROTECTED_SITE_URL);
+ req.addParameter("instanceid_key", instance_id_key);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ }
+
+ /**
+ * Test that the proxy passes through the correct status code when trying to access
+ * an allowed resource without authn credentials required at the final target site
+ * @throws Exception
+ */
+ @Test
+ public void getProtectedSiteWithoutAuth() throws Exception{
+ HttpClient client = new HttpClient();
+ HttpMethod req;
+ req = new GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(401,code);
+ }
+
+ /**
+ * Test that the proxy passes along credentials to the proxied site and is granted access
+ * @throws Exception
+ */
+ @Test
+ public void getProtectedSiteWithBasicAuth() throws Exception{
+ HttpClient client = new HttpClient();
+ List<String> authPrefs = new ArrayList<String>(2);
+ authPrefs.add(AuthPolicy.DIGEST );
+ authPrefs.add(AuthPolicy.BASIC);
+ client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+ //
+ // send the basic authentication response even before the server gives an unauthorized response
+ //
+ client.getParams().setAuthenticationPreemptive(true);
+ client.getState().setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+ new UsernamePasswordCredentials("java", "java"));
+ HttpMethod req;
+ req = new GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ assertEquals(200,code);
+ }
+
+ /**
+ * Test that proxy responses contain headers from the originating site. See WOOKIE-251
+ * @throws IOException
+ */
+ @Test
+ public void getWithCustomResponseHeaders() throws IOException{
+
+ //
+ // Create a custom server
+ //
+ ProxiedServer server = new ProxiedServer(9000);
+ String testUrl = "http://localhost:9000";
+
+ //
+ // Check the server works
+ //
+ HttpClient client = new HttpClient();
+ HttpMethod req = new GetMethod(testUrl);
+
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ String header = req.getResponseHeader("CUSTOM-WOOKIE").getValue();
+ assertEquals(200,code);
+ assertEquals("hhrooar", header);
+ req.releaseConnection();
+
+ //
+ // Make a proxed request, and check we get the custom header back
+ //
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=http://localhost:9000";
+ req = new GetMethod(url);
+ client.executeMethod(req);
+ code = req.getStatusCode();
+ header = req.getResponseHeader("CUSTOM-WOOKIE").getValue();
+
+ req.releaseConnection();
+ assertEquals(200,code);
+ assertEquals("hhrooar", header);
+
+ server.stop();
+ }
+
+ /**
+ * Test that proxy responses contain headers from the originating site. See WOOKIE-251
+ * @throws IOException
+ */
+ @Test
+ public void getWithCookies() throws IOException{
+
+ //
+ // Create a custom server
+ //
+ ProxiedServer server = new ProxiedServer(9000);
+ String testUrl = "http://localhost:9000";
+
+ //
+ // Check the server works
+ //
+ HttpClient client = new HttpClient();
+ HttpMethod req = new GetMethod(testUrl);
+
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ String header = req.getResponseHeader("Set-Cookie").getValue();
+ assertEquals(200,code);
+ assertEquals("test=wookie", header);
+ req.releaseConnection();
+
+ //
+ // Make a proxed request, and check we get the custom header back
+ //
+ String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=http://localhost:9000";
+ req = new GetMethod(url);
+ client.executeMethod(req);
+ code = req.getStatusCode();
+ header = req.getResponseHeader("Set-Cookie").getValue();
+
+ req.releaseConnection();
+ assertEquals(200,code);
+ assertEquals("test=wookie", header);
+
+ //
+ // Make another proxied request, this time send the cookie
+ //
+ String cookieurl = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=http://localhost:9000";
+ req = new GetMethod(cookieurl);
+ req.addRequestHeader("Cookie", "test=wookie");
+ client.executeMethod(req);
+ code = req.getStatusCode();
+ header = req.getResponseHeader("GOT-COOKIE").getValue();
+
+ req.releaseConnection();
+ assertEquals(200,code);
+ assertEquals("true", header);
+
+ server.stop();
+ }
+
+
+ /**
+ * A subclass of NanoHTTPD for testing
+ */
+ class ProxiedServer extends NanoHTTPD{
+
+ /**
+ * @param port
+ * @throws IOException
+ */
+ public ProxiedServer(int port) throws IOException {
+ super(port);
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.wookie.tests.helpers.NanoHTTPD#serve(java.lang.String, java.lang.String, java.util.Properties, java.util.Properties, java.util.Properties)
+ */
+ @Override
+ public Response serve(String uri, String method, Properties header,
+ Properties parms, Properties files) {
+ Response response = new Response();
+ response.addHeader("CUSTOM-WOOKIE", "hhrooar");
+ response.addHeader("Set-Cookie","test=wookie");
+ String cookie = header.getProperty("cookie");
+ if (cookie != null && cookie.contains("test=wookie")){
+ response.addHeader("GOT-COOKIE", "true");
+ }
+ response.status = "200 OK";
+ return response;
+ }
+ }
+
+ /**
+ * Utility method for sending requests
+ * @param url
+ * @param method
+ * @return
+ */
+ private int send(String url, String method){
+ try {
+ HttpClient client = new HttpClient();
+ HttpMethod req;
+ if (method.equals("POST")){
+ req = new PostMethod(url);
+ } else {
+ req = new GetMethod(url);
+ }
+ client.executeMethod(req);
+ int code = req.getStatusCode();
+ req.releaseConnection();
+ return code;
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ fail("post failed");
+ return -1;
+ }
+ }
}
Added: incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java?rev=1195073&view=auto
==============================================================================
--- incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java (added)
+++ incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java Sat Oct 29 23:18:54 2011
@@ -0,0 +1,1117 @@
+package org.apache.wookie.tests.helpers;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+
+/**
+ * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java
+ *
+ * <p> NanoHTTPD version 1.24,
+ * Copyright © 2001,2005-2011 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
+ * and Copyright © 2010 Konstantinos Togias (info@ktogias.gr, http://ktogias.gr)
+ *
+ * <p><b>Features + limitations: </b><ul>
+ *
+ * <li> Only one Java file </li>
+ * <li> Java 1.1 compatible </li>
+ * <li> Released as open source, Modified BSD licence </li>
+ * <li> No fixed config files, logging, authorization etc. (Implement yourself if you need them.) </li>
+ * <li> Supports parameter parsing of GET and POST methods </li>
+ * <li> Supports both dynamic content and file serving </li>
+ * <li> Supports file upload (since version 1.2, 2010) </li>
+ * <li> Supports partial content (streaming)</li>
+ * <li> Supports ETags</li>
+ * <li> Never caches anything </li>
+ * <li> Doesn't limit bandwidth, request time or simultaneous connections </li>
+ * <li> Default code serves files and shows all HTTP parameters and headers</li>
+ * <li> File server supports directory listing, index.html and index.htm</li>
+ * <li> File server supports partial content (streaming)</li>
+ * <li> File server supports ETags</li>
+ * <li> File server does the 301 redirection trick for directories without '/'</li>
+ * <li> File server supports simple skipping for files (continue download) </li>
+ * <li> File server serves also very long files without memory overhead </li>
+ * <li> Contains a built-in list of most common mime types </li>
+ * <li> All header names are converted lowercase so they don't vary between browsers/clients </li>
+ *
+ * </ul>
+ *
+ * <p><b>Ways to use: </b><ul>
+ *
+ * <li> Run as a standalone app, serves files and shows requests</li>
+ * <li> Subclass serve() and embed to your own program </li>
+ * <li> Call serveFile() from serve() with your own base directory </li>
+ *
+ * </ul>
+ *
+ * Homepage: http://iki.fi/elonen/code/nanohttpd/
+ *
+ * See the end of the source file for distribution license
+ * (Modified BSD licence)
+ */
+public class NanoHTTPD
+{
+ // ==================================================
+ // API parts
+ // ==================================================
+
+ /**
+ * Override this to customize the server.<p>
+ *
+ * (By default, this delegates to serveFile() and allows directory listing.)
+ *
+ * @param uri Percent-decoded URI without parameters, for example "/index.cgi"
+ * @param method "GET", "POST" etc.
+ * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data.
+ * @param header Header entries, percent decoded
+ * @return HTTP response, see class Response for details
+ */
+ public Response serve( String uri, String method, Properties header, Properties parms, Properties files )
+ {
+ System.out.println( method + " '" + uri + "' " );
+
+ Enumeration e = header.propertyNames();
+ while ( e.hasMoreElements())
+ {
+ String value = (String)e.nextElement();
+ System.out.println( " HDR: '" + value + "' = '" +
+ header.getProperty( value ) + "'" );
+ }
+ e = parms.propertyNames();
+ while ( e.hasMoreElements())
+ {
+ String value = (String)e.nextElement();
+ System.out.println( " PRM: '" + value + "' = '" +
+ parms.getProperty( value ) + "'" );
+ }
+ e = files.propertyNames();
+ while ( e.hasMoreElements())
+ {
+ String value = (String)e.nextElement();
+ System.out.println( " UPLOADED: '" + value + "' = '" +
+ files.getProperty( value ) + "'" );
+ }
+
+ return serveFile( uri, header, myRootDir, true );
+ }
+
+ /**
+ * HTTP response.
+ * Return one of these from serve().
+ */
+ public class Response
+ {
+ /**
+ * Default constructor: response = HTTP_OK, data = mime = 'null'
+ */
+ public Response()
+ {
+ this.status = HTTP_OK;
+ }
+
+ /**
+ * Basic constructor.
+ */
+ public Response( String status, String mimeType, InputStream data )
+ {
+ this.status = status;
+ this.mimeType = mimeType;
+ this.data = data;
+ }
+
+ /**
+ * Convenience method that makes an InputStream out of
+ * given text.
+ */
+ public Response( String status, String mimeType, String txt )
+ {
+ this.status = status;
+ this.mimeType = mimeType;
+ try
+ {
+ this.data = new ByteArrayInputStream( txt.getBytes("UTF-8"));
+ }
+ catch ( java.io.UnsupportedEncodingException uee )
+ {
+ uee.printStackTrace();
+ }
+ }
+
+ /**
+ * Adds given line to the header.
+ */
+ public void addHeader( String name, String value )
+ {
+ header.put( name, value );
+ }
+
+ /**
+ * HTTP status code after processing, e.g. "200 OK", HTTP_OK
+ */
+ public String status;
+
+ /**
+ * MIME type of content, e.g. "text/html"
+ */
+ public String mimeType;
+
+ /**
+ * Data of the response, may be null.
+ */
+ public InputStream data;
+
+ /**
+ * Headers for the HTTP response. Use addHeader()
+ * to add lines.
+ */
+ public Properties header = new Properties();
+ }
+
+ /**
+ * Some HTTP response status codes
+ */
+ public static final String
+ HTTP_OK = "200 OK",
+ HTTP_PARTIALCONTENT = "206 Partial Content",
+ HTTP_RANGE_NOT_SATISFIABLE = "416 Requested Range Not Satisfiable",
+ HTTP_REDIRECT = "301 Moved Permanently",
+ HTTP_FORBIDDEN = "403 Forbidden",
+ HTTP_NOTFOUND = "404 Not Found",
+ HTTP_BADREQUEST = "400 Bad Request",
+ HTTP_INTERNALERROR = "500 Internal Server Error",
+ HTTP_NOTIMPLEMENTED = "501 Not Implemented";
+
+ /**
+ * Common mime types for dynamic content
+ */
+ public static final String
+ MIME_PLAINTEXT = "text/plain",
+ MIME_HTML = "text/html",
+ MIME_DEFAULT_BINARY = "application/octet-stream",
+ MIME_XML = "text/xml";
+
+ // ==================================================
+ // Socket & server code
+ // ==================================================
+ /**
+ * Starts a HTTP server to given port.<p>
+ * Throws an IOException if the socket is already in use
+ */
+ public NanoHTTPD( int port, File wwwroot ) throws IOException
+ {
+ myTcpPort = port;
+ this.myRootDir = wwwroot;
+ myServerSocket = new ServerSocket( myTcpPort );
+ myThread = new Thread( new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ while( true )
+ new HTTPSession( myServerSocket.accept());
+ }
+ catch ( IOException ioe )
+ {}
+ }
+ });
+ myThread.setDaemon( true );
+ myThread.start();
+ }
+
+ /**
+ * Starts a HTTP server to given port.<p>
+ * Throws an IOException if the socket is already in use.
+ * Starts serving files from current directory.
+ */
+ public NanoHTTPD( int port ) throws IOException
+ {
+ this( port, new File(".").getAbsoluteFile());
+ }
+
+ /**
+ * Stops the server.
+ */
+ public void stop()
+ {
+ try
+ {
+ myServerSocket.close();
+ myThread.join();
+ }
+ catch ( IOException ioe ) {}
+ catch ( InterruptedException e ) {}
+ }
+
+
+ /**
+ * Starts as a standalone file server and waits for Enter.
+ */
+ public static void main( String[] args )
+ {
+ System.out.println( "NanoHTTPD 1.24 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n" +
+ "(Command line options: [-p port] [-d root-dir] [--licence])\n" );
+
+ // Defaults
+ int port = 80;
+ File wwwroot = new File(".").getAbsoluteFile();
+
+ // Show licence if requested
+ for ( int i=0; i<args.length; ++i )
+ if(args[i].equalsIgnoreCase("-p"))
+ port = Integer.parseInt( args[i+1] );
+ else if(args[i].equalsIgnoreCase("-d"))
+ wwwroot = new File( args[i+1] ).getAbsoluteFile();
+ else if ( args[i].toLowerCase().endsWith( "licence" ))
+ {
+ System.out.println( LICENCE + "\n" );
+ break;
+ }
+
+ try
+ {
+ new NanoHTTPD( port, wwwroot );
+ }
+ catch( IOException ioe )
+ {
+ System.err.println( "Couldn't start server:\n" + ioe );
+ System.exit( -1 );
+ }
+
+ System.out.println( "Now serving files in port " + port + " from \"" + wwwroot + "\"" );
+ System.out.println( "Hit Enter to stop.\n" );
+
+ try { System.in.read(); } catch( Throwable t ) {}
+ }
+
+ /**
+ * Handles one session, i.e. parses the HTTP request
+ * and returns the response.
+ */
+ private class HTTPSession implements Runnable
+ {
+ public HTTPSession( Socket s )
+ {
+ mySocket = s;
+ Thread t = new Thread( this );
+ t.setDaemon( true );
+ t.start();
+ }
+
+ public void run()
+ {
+ try
+ {
+ InputStream is = mySocket.getInputStream();
+ if ( is == null) return;
+
+ // Read the first 8192 bytes.
+ // The full header should fit in here.
+ // Apache's default header limit is 8KB.
+ int bufsize = 8192;
+ byte[] buf = new byte[bufsize];
+ int rlen = is.read(buf, 0, bufsize);
+ if (rlen <= 0) return;
+
+ // Create a BufferedReader for parsing the header.
+ ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
+ BufferedReader hin = new BufferedReader( new InputStreamReader( hbis ));
+ Properties pre = new Properties();
+ Properties parms = new Properties();
+ Properties header = new Properties();
+ Properties files = new Properties();
+
+ // Decode the header into parms and header java properties
+ decodeHeader(hin, pre, parms, header);
+ String method = pre.getProperty("method");
+ String uri = pre.getProperty("uri");
+
+ long size = 0x7FFFFFFFFFFFFFFFl;
+ String contentLength = header.getProperty("content-length");
+ if (contentLength != null)
+ {
+ try { size = Integer.parseInt(contentLength); }
+ catch (NumberFormatException ex) {}
+ }
+
+ // We are looking for the byte separating header from body.
+ // It must be the last byte of the first two sequential new lines.
+ int splitbyte = 0;
+ boolean sbfound = false;
+ while (splitbyte < rlen)
+ {
+ if (buf[splitbyte] == '\r' && buf[++splitbyte] == '\n' && buf[++splitbyte] == '\r' && buf[++splitbyte] == '\n') {
+ sbfound = true;
+ break;
+ }
+ splitbyte++;
+ }
+ splitbyte++;
+
+ // Write the part of body already read to ByteArrayOutputStream f
+ ByteArrayOutputStream f = new ByteArrayOutputStream();
+ if (splitbyte < rlen) f.write(buf, splitbyte, rlen-splitbyte);
+
+ // While Firefox sends on the first read all the data fitting
+ // our buffer, Chrome and Opera sends only the headers even if
+ // there is data for the body. So we do some magic here to find
+ // out whether we have already consumed part of body, if we
+ // have reached the end of the data to be sent or we should
+ // expect the first byte of the body at the next read.
+ if (splitbyte < rlen)
+ size -= rlen - splitbyte +1;
+ else if (!sbfound || size == 0x7FFFFFFFFFFFFFFFl)
+ size = 0;
+
+ // Now read all the body and write it to f
+ buf = new byte[512];
+ while ( rlen >= 0 && size > 0 )
+ {
+ rlen = is.read(buf, 0, 512);
+ size -= rlen;
+ if (rlen > 0)
+ f.write(buf, 0, rlen);
+ }
+
+ // Get the raw body as a byte []
+ byte [] fbuf = f.toByteArray();
+
+ // Create a BufferedReader for easily reading it as string.
+ ByteArrayInputStream bin = new ByteArrayInputStream(fbuf);
+ BufferedReader in = new BufferedReader( new InputStreamReader(bin));
+
+ // If the method is POST, there may be parameters
+ // in data section, too, read it:
+ if ( method.equalsIgnoreCase( "POST" ))
+ {
+ String contentType = "";
+ String contentTypeHeader = header.getProperty("content-type");
+ StringTokenizer st = new StringTokenizer( contentTypeHeader , "; " );
+ if ( st.hasMoreTokens()) {
+ contentType = st.nextToken();
+ }
+
+ if (contentType.equalsIgnoreCase("multipart/form-data"))
+ {
+ // Handle multipart/form-data
+ if ( !st.hasMoreTokens())
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html" );
+ String boundaryExp = st.nextToken();
+ st = new StringTokenizer( boundaryExp , "=" );
+ if (st.countTokens() != 2)
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html" );
+ st.nextToken();
+ String boundary = st.nextToken();
+
+ decodeMultipartData(boundary, fbuf, in, parms, files);
+ }
+ else
+ {
+ // Handle application/x-www-form-urlencoded
+ String postLine = "";
+ char pbuf[] = new char[512];
+ int read = in.read(pbuf);
+ while ( read >= 0 && !postLine.endsWith("\r\n") )
+ {
+ postLine += String.valueOf(pbuf, 0, read);
+ read = in.read(pbuf);
+ }
+ postLine = postLine.trim();
+ decodeParms( postLine, parms );
+ }
+ }
+
+ // Ok, now do the serve()
+ Response r = serve( uri, method, header, parms, files );
+ if ( r == null )
+ sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." );
+ else
+ sendResponse( r.status, r.mimeType, r.header, r.data );
+
+ in.close();
+ is.close();
+ }
+ catch ( IOException ioe )
+ {
+ try
+ {
+ sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ }
+ catch ( Throwable t ) {}
+ }
+ catch ( InterruptedException ie )
+ {
+ // Thrown by sendError, ignore and exit the thread.
+ }
+ }
+
+ /**
+ * Decodes the sent headers and loads the data into
+ * java Properties' key - value pairs
+ **/
+ private void decodeHeader(BufferedReader in, Properties pre, Properties parms, Properties header)
+ throws InterruptedException
+ {
+ try {
+ // Read the request line
+ String inLine = in.readLine();
+ if (inLine == null) return;
+ StringTokenizer st = new StringTokenizer( inLine );
+ if ( !st.hasMoreTokens())
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" );
+
+ String method = st.nextToken();
+ pre.put("method", method);
+
+ if ( !st.hasMoreTokens())
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" );
+
+ String uri = st.nextToken();
+
+ // Decode parameters from the URI
+ int qmi = uri.indexOf( '?' );
+ if ( qmi >= 0 )
+ {
+ decodeParms( uri.substring( qmi+1 ), parms );
+ uri = decodePercent( uri.substring( 0, qmi ));
+ }
+ else uri = decodePercent(uri);
+
+ // If there's another token, it's protocol version,
+ // followed by HTTP headers. Ignore version but parse headers.
+ // NOTE: this now forces header names lowercase since they are
+ // case insensitive and vary by client.
+ if ( st.hasMoreTokens())
+ {
+ String line = in.readLine();
+ while ( line != null && line.trim().length() > 0 )
+ {
+ int p = line.indexOf( ':' );
+ if ( p >= 0 )
+ header.put( line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim());
+ line = in.readLine();
+ }
+ }
+
+ pre.put("uri", uri);
+ }
+ catch ( IOException ioe )
+ {
+ sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ }
+ }
+
+ /**
+ * Decodes the Multipart Body data and put it
+ * into java Properties' key - value pairs.
+ **/
+ private void decodeMultipartData(String boundary, byte[] fbuf, BufferedReader in, Properties parms, Properties files)
+ throws InterruptedException
+ {
+ try
+ {
+ int[] bpositions = getBoundaryPositions(fbuf,boundary.getBytes());
+ int boundarycount = 1;
+ String mpline = in.readLine();
+ while ( mpline != null )
+ {
+ if (mpline.indexOf(boundary) == -1)
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html" );
+ boundarycount++;
+ Properties item = new Properties();
+ mpline = in.readLine();
+ while (mpline != null && mpline.trim().length() > 0)
+ {
+ int p = mpline.indexOf( ':' );
+ if (p != -1)
+ item.put( mpline.substring(0,p).trim().toLowerCase(), mpline.substring(p+1).trim());
+ mpline = in.readLine();
+ }
+ if (mpline != null)
+ {
+ String contentDisposition = item.getProperty("content-disposition");
+ if (contentDisposition == null)
+ {
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html" );
+ }
+ StringTokenizer st = new StringTokenizer( contentDisposition , "; " );
+ Properties disposition = new Properties();
+ while ( st.hasMoreTokens())
+ {
+ String token = st.nextToken();
+ int p = token.indexOf( '=' );
+ if (p!=-1)
+ disposition.put( token.substring(0,p).trim().toLowerCase(), token.substring(p+1).trim());
+ }
+ String pname = disposition.getProperty("name");
+ pname = pname.substring(1,pname.length()-1);
+
+ String value = "";
+ if (item.getProperty("content-type") == null) {
+ while (mpline != null && mpline.indexOf(boundary) == -1)
+ {
+ mpline = in.readLine();
+ if ( mpline != null)
+ {
+ int d = mpline.indexOf(boundary);
+ if (d == -1)
+ value+=mpline;
+ else
+ value+=mpline.substring(0,d-2);
+ }
+ }
+ }
+ else
+ {
+ if (boundarycount> bpositions.length)
+ sendError( HTTP_INTERNALERROR, "Error processing request" );
+ int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount-2]);
+ String path = saveTmpFile(fbuf, offset, bpositions[boundarycount-1]-offset-4);
+ files.put(pname, path);
+ value = disposition.getProperty("filename");
+ value = value.substring(1,value.length()-1);
+ do {
+ mpline = in.readLine();
+ } while (mpline != null && mpline.indexOf(boundary) == -1);
+ }
+ parms.put(pname, value);
+ }
+ }
+ }
+ catch ( IOException ioe )
+ {
+ sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ }
+ }
+
+ /**
+ * Find the byte positions where multipart boundaries start.
+ **/
+ public int[] getBoundaryPositions(byte[] b, byte[] boundary)
+ {
+ int matchcount = 0;
+ int matchbyte = -1;
+ Vector matchbytes = new Vector();
+ for (int i=0; i<b.length; i++)
+ {
+ if (b[i] == boundary[matchcount])
+ {
+ if (matchcount == 0)
+ matchbyte = i;
+ matchcount++;
+ if (matchcount==boundary.length)
+ {
+ matchbytes.addElement(new Integer(matchbyte));
+ matchcount = 0;
+ matchbyte = -1;
+ }
+ }
+ else
+ {
+ i -= matchcount;
+ matchcount = 0;
+ matchbyte = -1;
+ }
+ }
+ int[] ret = new int[matchbytes.size()];
+ for (int i=0; i < ret.length; i++)
+ {
+ ret[i] = ((Integer)matchbytes.elementAt(i)).intValue();
+ }
+ return ret;
+ }
+
+ /**
+ * Retrieves the content of a sent file and saves it
+ * to a temporary file.
+ * The full path to the saved file is returned.
+ **/
+ private String saveTmpFile(byte[] b, int offset, int len)
+ {
+ String path = "";
+ if (len > 0)
+ {
+ String tmpdir = System.getProperty("java.io.tmpdir");
+ try {
+ File temp = File.createTempFile("NanoHTTPD", "", new File(tmpdir));
+ OutputStream fstream = new FileOutputStream(temp);
+ fstream.write(b, offset, len);
+ fstream.close();
+ path = temp.getAbsolutePath();
+ } catch (Exception e) { // Catch exception if any
+ System.err.println("Error: " + e.getMessage());
+ }
+ }
+ return path;
+ }
+
+
+ /**
+ * It returns the offset separating multipart file headers
+ * from the file's data.
+ **/
+ private int stripMultipartHeaders(byte[] b, int offset)
+ {
+ int i = 0;
+ for (i=offset; i<b.length; i++)
+ {
+ if (b[i] == '\r' && b[++i] == '\n' && b[++i] == '\r' && b[++i] == '\n')
+ break;
+ }
+ return i+1;
+ }
+
+ /**
+ * Decodes the percent encoding scheme. <br/>
+ * For example: "an+example%20string" -> "an example string"
+ */
+ private String decodePercent( String str ) throws InterruptedException
+ {
+ try
+ {
+ StringBuffer sb = new StringBuffer();
+ for( int i=0; i<str.length(); i++ )
+ {
+ char c = str.charAt( i );
+ switch ( c )
+ {
+ case '+':
+ sb.append( ' ' );
+ break;
+ case '%':
+ sb.append((char)Integer.parseInt( str.substring(i+1,i+3), 16 ));
+ i += 2;
+ break;
+ default:
+ sb.append( c );
+ break;
+ }
+ }
+ return sb.toString();
+ }
+ catch( Exception e )
+ {
+ sendError( HTTP_BADREQUEST, "BAD REQUEST: Bad percent-encoding." );
+ return null;
+ }
+ }
+
+ /**
+ * Decodes parameters in percent-encoded URI-format
+ * ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
+ * adds them to given Properties. NOTE: this doesn't support multiple
+ * identical keys due to the simplicity of Properties -- if you need multiples,
+ * you might want to replace the Properties with a Hashtable of Vectors or such.
+ */
+ private void decodeParms( String parms, Properties p )
+ throws InterruptedException
+ {
+ if ( parms == null )
+ return;
+
+ StringTokenizer st = new StringTokenizer( parms, "&" );
+ while ( st.hasMoreTokens())
+ {
+ String e = st.nextToken();
+ int sep = e.indexOf( '=' );
+ if ( sep >= 0 )
+ p.put( decodePercent( e.substring( 0, sep )).trim(),
+ decodePercent( e.substring( sep+1 )));
+ }
+ }
+
+ /**
+ * Returns an error message as a HTTP response and
+ * throws InterruptedException to stop further request processing.
+ */
+ private void sendError( String status, String msg ) throws InterruptedException
+ {
+ sendResponse( status, MIME_PLAINTEXT, null, new ByteArrayInputStream( msg.getBytes()));
+ throw new InterruptedException();
+ }
+
+ /**
+ * Sends given response to the socket.
+ */
+ private void sendResponse( String status, String mime, Properties header, InputStream data )
+ {
+ try
+ {
+ if ( status == null )
+ throw new Error( "sendResponse(): Status can't be null." );
+
+ OutputStream out = mySocket.getOutputStream();
+ PrintWriter pw = new PrintWriter( out );
+ pw.print("HTTP/1.0 " + status + " \r\n");
+
+ if ( mime != null )
+ pw.print("Content-Type: " + mime + "\r\n");
+
+ if ( header == null || header.getProperty( "Date" ) == null )
+ pw.print( "Date: " + gmtFrmt.format( new Date()) + "\r\n");
+
+ if ( header != null )
+ {
+ Enumeration e = header.keys();
+ while ( e.hasMoreElements())
+ {
+ String key = (String)e.nextElement();
+ String value = header.getProperty( key );
+ pw.print( key + ": " + value + "\r\n");
+ }
+ }
+
+ pw.print("\r\n");
+ pw.flush();
+
+ if ( data != null )
+ {
+ int maxBuff = 2048;
+ int pending = data.available(); // This is to support partial sends, see serveFile()
+ byte[] buff = new byte[Math.max(pending, maxBuff)];
+ while (pending>0)
+ {
+ int read = data.read( buff, 0, ( (pending>maxBuff) ? maxBuff : pending ));
+ if (read <= 0) break;
+ out.write( buff, 0, read );
+ pending -= read;
+ }
+ }
+ out.flush();
+ out.close();
+ if ( data != null )
+ data.close();
+ }
+ catch( IOException ioe )
+ {
+ // Couldn't write? No can do.
+ try { mySocket.close(); } catch( Throwable t ) {}
+ }
+ }
+
+ private Socket mySocket;
+ }
+
+ /**
+ * URL-encodes everything between "/"-characters.
+ * Encodes spaces as '%20' instead of '+'.
+ */
+ private String encodeUri( String uri )
+ {
+ String newUri = "";
+ StringTokenizer st = new StringTokenizer( uri, "/ ", true );
+ while ( st.hasMoreTokens())
+ {
+ String tok = st.nextToken();
+ if ( tok.equals( "/" ))
+ newUri += "/";
+ else if ( tok.equals( " " ))
+ newUri += "%20";
+ else
+ {
+ newUri += URLEncoder.encode( tok );
+ // For Java 1.4 you'll want to use this instead:
+ // try { newUri += URLEncoder.encode( tok, "UTF-8" ); } catch ( java.io.UnsupportedEncodingException uee ) {}
+ }
+ }
+ return newUri;
+ }
+
+ private int myTcpPort;
+ private final ServerSocket myServerSocket;
+ private Thread myThread;
+ private File myRootDir;
+
+ // ==================================================
+ // File server code
+ // ==================================================
+
+ /**
+ * Serves file from homeDir and its' subdirectories (only).
+ * Uses only URI, ignores all headers and HTTP parameters.
+ */
+ public Response serveFile( String uri, Properties header, File homeDir,
+ boolean allowDirectoryListing )
+ {
+ Response res = null;
+
+ // Make sure we won't die of an exception later
+ if ( !homeDir.isDirectory())
+ res = new Response( HTTP_INTERNALERROR, MIME_PLAINTEXT,
+ "INTERNAL ERRROR: serveFile(): given homeDir is not a directory." );
+
+ if ( res == null )
+ {
+ // Remove URL arguments
+ uri = uri.trim().replace( File.separatorChar, '/' );
+ if ( uri.indexOf( '?' ) >= 0 )
+ uri = uri.substring(0, uri.indexOf( '?' ));
+
+ // Prohibit getting out of current directory
+ if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 )
+ res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
+ "FORBIDDEN: Won't serve ../ for security reasons." );
+ }
+
+ File f = new File( homeDir, uri );
+ if ( res == null && !f.exists())
+ res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT,
+ "Error 404, file not found." );
+
+ // List the directory, if necessary
+ if ( res == null && f.isDirectory())
+ {
+ // Browsers get confused without '/' after the
+ // directory, send a redirect.
+ if ( !uri.endsWith( "/" ))
+ {
+ uri += "/";
+ res = new Response( HTTP_REDIRECT, MIME_HTML,
+ "<html><body>Redirected: <a href=\"" + uri + "\">" +
+ uri + "</a></body></html>");
+ res.addHeader( "Location", uri );
+ }
+
+ if ( res == null )
+ {
+ // First try index.html and index.htm
+ if ( new File( f, "index.html" ).exists())
+ f = new File( homeDir, uri + "/index.html" );
+ else if ( new File( f, "index.htm" ).exists())
+ f = new File( homeDir, uri + "/index.htm" );
+ // No index file, list the directory if it is readable
+ else if ( allowDirectoryListing && f.canRead() )
+ {
+ String[] files = f.list();
+ String msg = "<html><body><h1>Directory " + uri + "</h1><br/>";
+
+ if ( uri.length() > 1 )
+ {
+ String u = uri.substring( 0, uri.length()-1 );
+ int slash = u.lastIndexOf( '/' );
+ if ( slash >= 0 && slash < u.length())
+ msg += "<b><a href=\"" + uri.substring(0, slash+1) + "\">..</a></b><br/>";
+ }
+
+ if (files!=null)
+ {
+ for ( int i=0; i<files.length; ++i )
+ {
+ File curFile = new File( f, files[i] );
+ boolean dir = curFile.isDirectory();
+ if ( dir )
+ {
+ msg += "<b>";
+ files[i] += "/";
+ }
+
+ msg += "<a href=\"" + encodeUri( uri + files[i] ) + "\">" +
+ files[i] + "</a>";
+
+ // Show file size
+ if ( curFile.isFile())
+ {
+ long len = curFile.length();
+ msg += " <font size=2>(";
+ if ( len < 1024 )
+ msg += len + " bytes";
+ else if ( len < 1024 * 1024 )
+ msg += len/1024 + "." + (len%1024/10%100) + " KB";
+ else
+ msg += len/(1024*1024) + "." + len%(1024*1024)/10%100 + " MB";
+
+ msg += ")</font>";
+ }
+ msg += "<br/>";
+ if ( dir ) msg += "</b>";
+ }
+ }
+ msg += "</body></html>";
+ res = new Response( HTTP_OK, MIME_HTML, msg );
+ }
+ else
+ {
+ res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT,
+ "FORBIDDEN: No directory listing." );
+ }
+ }
+ }
+
+ try
+ {
+ if ( res == null )
+ {
+ // Get MIME type from file name extension, if possible
+ String mime = null;
+ int dot = f.getCanonicalPath().lastIndexOf( '.' );
+ if ( dot >= 0 )
+ mime = (String)theMimeTypes.get( f.getCanonicalPath().substring( dot + 1 ).toLowerCase());
+ if ( mime == null )
+ mime = MIME_DEFAULT_BINARY;
+
+ // Calculate etag
+ String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode());
+
+ // Support (simple) skipping:
+ long startFrom = 0;
+ long endAt = -1;
+ String range = header.getProperty( "range" );
+ if ( range != null )
+ {
+ if ( range.startsWith( "bytes=" ))
+ {
+ range = range.substring( "bytes=".length());
+ int minus = range.indexOf( '-' );
+ try {
+ if ( minus > 0 )
+ {
+ startFrom = Long.parseLong( range.substring( 0, minus ));
+ endAt = Long.parseLong( range.substring( minus+1 ));
+ }
+ }
+ catch ( NumberFormatException nfe ) {}
+ }
+ }
+
+ // Change return code and add Content-Range header when skipping is requested
+ long fileLen = f.length();
+ if (range != null && startFrom >= 0)
+ {
+ if ( startFrom >= fileLen)
+ {
+ res = new Response( HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" );
+ res.addHeader( "Content-Range", "bytes 0-0/" + fileLen);
+ res.addHeader( "ETag", etag);
+ }
+ else
+ {
+ if ( endAt < 0 )
+ endAt = fileLen-1;
+ long newLen = endAt - startFrom + 1;
+ if ( newLen < 0 ) newLen = 0;
+
+ final long dataLen = newLen;
+ FileInputStream fis = new FileInputStream( f ) {
+ public int available() throws IOException { return (int)dataLen; }
+ };
+ fis.skip( startFrom );
+
+ res = new Response( HTTP_PARTIALCONTENT, mime, fis );
+ res.addHeader( "Content-Length", "" + dataLen);
+ res.addHeader( "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
+ res.addHeader( "ETag", etag);
+ }
+ }
+ else
+ {
+ res = new Response( HTTP_OK, mime, new FileInputStream( f ));
+ res.addHeader( "Content-Length", "" + fileLen);
+ res.addHeader( "ETag", etag);
+ }
+ }
+ }
+ catch( IOException ioe )
+ {
+ res = new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." );
+ }
+
+ res.addHeader( "Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes
+ return res;
+ }
+
+ /**
+ * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
+ */
+ private static Hashtable theMimeTypes = new Hashtable();
+ static
+ {
+ StringTokenizer st = new StringTokenizer(
+ "css text/css "+
+ "htm text/html "+
+ "html text/html "+
+ "xml text/xml "+
+ "txt text/plain "+
+ "asc text/plain "+
+ "gif image/gif "+
+ "jpg image/jpeg "+
+ "jpeg image/jpeg "+
+ "png image/png "+
+ "mp3 audio/mpeg "+
+ "m3u audio/mpeg-url " +
+ "mp4 video/mp4 " +
+ "ogv video/ogg " +
+ "flv video/x-flv " +
+ "mov video/quicktime " +
+ "swf application/x-shockwave-flash " +
+ "js application/javascript "+
+ "pdf application/pdf "+
+ "doc application/msword "+
+ "ogg application/x-ogg "+
+ "zip application/octet-stream "+
+ "exe application/octet-stream "+
+ "class application/octet-stream " );
+ while ( st.hasMoreTokens())
+ theMimeTypes.put( st.nextToken(), st.nextToken());
+ }
+
+ /**
+ * GMT date formatter
+ */
+ private static java.text.SimpleDateFormat gmtFrmt;
+ static
+ {
+ gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+ gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+ }
+
+ /**
+ * The distribution licence
+ */
+ private static final String LICENCE =
+ "Copyright (C) 2001,2005-2011 by Jarno Elonen <el...@iki.fi>\n"+
+ "and Copyright (C) 2010 by Konstantinos Togias <in...@ktogias.gr>\n"+
+ "\n"+
+ "Redistribution and use in source and binary forms, with or without\n"+
+ "modification, are permitted provided that the following conditions\n"+
+ "are met:\n"+
+ "\n"+
+ "Redistributions of source code must retain the above copyright notice,\n"+
+ "this list of conditions and the following disclaimer. Redistributions in\n"+
+ "binary form must reproduce the above copyright notice, this list of\n"+
+ "conditions and the following disclaimer in the documentation and/or other\n"+
+ "materials provided with the distribution. The name of the author may not\n"+
+ "be used to endorse or promote products derived from this software without\n"+
+ "specific prior written permission. \n"+
+ " \n"+
+ "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+
+ "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+
+ "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+
+ "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+
+ "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+
+ "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+
+ "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+
+ "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+
+ "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+
+ "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
+}
+
Modified: incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java?rev=1195073&r1=1195072&r2=1195073&view=diff
==============================================================================
--- incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java (original)
+++ incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java Sat Oct 29 23:18:54 2011
@@ -14,14 +14,11 @@
package org.apache.wookie.proxy;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -30,15 +27,14 @@ import org.apache.commons.configuration.
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthPolicy;
import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
/**
@@ -48,7 +44,6 @@ public class ProxyClient {
static Logger fLogger = Logger.getLogger(ProxyClient.class.getName());
- private String fContentType = "text/plain";
private String fProxyUsername = null;
private String fProxyPassword = null;
private String fBase64Auth = null;
@@ -57,6 +52,9 @@ public class ProxyClient {
@SuppressWarnings("unchecked")
public ProxyClient(HttpServletRequest request){
+ //
+ // If the request includes authn parameters, create proxy auth headers
+ //
String proxyUserName = request.getParameter("username");
String proxyPassword = request.getParameter("password");
String base64Auth = request.getHeader("Authorization");
@@ -64,8 +62,10 @@ public class ProxyClient {
this.setProxyAuthConfig(proxyUserName, proxyPassword);
if(base64Auth != null)
this.setBase64AuthConfig(base64Auth);
+ //
+ // Filter out instructions to the proxy server from the original request parameters
+ //
filterParameters(request.getParameterMap());
- fContentType = request.getContentType();
}
private void setProxyAuthConfig(String username, String password){
@@ -78,36 +78,48 @@ public class ProxyClient {
fUseProxyAuthentication = true;
fBase64Auth = base64Auth;
}
-
- public String getCType(){
- return fContentType;
- }
-
- public String get(String url, Configuration properties) throws Exception {
- fLogger.debug("GET from " + url); //$NON-NLS-1$
- GetMethod method = new GetMethod(url);
- method.setDoAuthentication(true);
- return executeMethod(method, properties);
- }
-
- public String post(String uri, String xmlData, Configuration properties) throws Exception {
- fLogger.debug("POST to " + uri); //$NON-NLS-1$
- PostMethod method = new PostMethod(uri);
- method.setDoAuthentication(true);
-
- if(this.parameters.length > 0) {
- method.addParameters(this.parameters);
- } else {
- method.setRequestEntity(new StringRequestEntity(xmlData, "text/xml", "UTF8"));//$NON-NLS-1$ //$NON-NLS-2$
- }
-
- return executeMethod(method, properties);
+
+ /**
+ * Process a proxied GET request
+ * @param url the URL to GET
+ * @param request the original request object
+ * @param properties the servlet configuration
+ * @return a ResponseObject from the remote site
+ * @throws Exception
+ */
+ public ResponseObject get(String url, HttpServletRequest request, Configuration properties) throws Exception {
+ fLogger.debug("GET from " + url); //$NON-NLS-1$
+ GetMethod method = new GetMethod(url);
+ method.setDoAuthentication(true);
+ return executeMethod(method, request, properties);
}
+
+ /**
+ * Process a proxied POST request
+ * @param url the URL to POST
+ * @param xmlData the body of the request
+ * @param request the original request object
+ * @param properties the servlet configuration
+ * @return a ResponseObject from the remote site
+ * @throws Exception
+ */
+ public ResponseObject post(String url , HttpServletRequest request, Configuration properties) throws Exception {
+ fLogger.debug("POST to " + url); //$NON-NLS-1$
+ PostMethod method = new PostMethod(url);
+ method.setDoAuthentication(true);
+
+ if(this.parameters.length > 0) {
+ method.addParameters(this.parameters);
+ } else {
+ method.setRequestEntity(new InputStreamRequestEntity(request.getInputStream()));
+ }
+
+ return executeMethod(method, request, properties);
+ }
/**
* Processes the parameters passed through to the request,
* removing the parameters used by the proxy itself
- * @return
*/
private void filterParameters(Map<Object,Object> umap){
Map<Object, Object> map = new HashMap<Object, Object>(umap);
@@ -124,78 +136,98 @@ public class ProxyClient {
}
parameters = params.toArray(new NameValuePair[params.size()]);
}
+
+ /**
+ * Execute the request and return the components of the response
+ * @param method the method to use, e.g. POST or GET
+ * @param request the original request
+ * @param properties the configuration of the servlet
+ * @return a ResponseObject containing both the response body and all headers returned
+ * @throws Exception
+ */
+ private ResponseObject executeMethod(HttpMethod method, HttpServletRequest request, Configuration properties) throws Exception{
+ ResponseObject responseObject = new ResponseObject();
- private String executeMethod(HttpMethod method, Configuration properties) throws Exception, AuthenticationException {
- // Execute the method.
- try {
- HttpClient client = new HttpClient();
- // set the clients proxy values if needed
- ConnectionsPrefsManager.setProxySettings(client, properties);
-
- if(fUseProxyAuthentication){
- if (fBase64Auth != null) {
- method.setRequestHeader("Authorization", fBase64Auth);
- }
- else {
- List<String> authPrefs = new ArrayList<String>(2);
- authPrefs.add(AuthPolicy.DIGEST );
- authPrefs.add(AuthPolicy.BASIC);
- client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
- // send the basic authentication response even before the server gives an unauthorized response
- client.getParams().setAuthenticationPreemptive(true);
- // Pass our credentials to HttpClient
- client.getState().setCredentials(
- new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
- new UsernamePasswordCredentials(fProxyUsername, fProxyPassword));
- }
- }
-
- // Add user language to http request in order to notify server of user's language
- Locale locale = Locale.getDefault();
-
- method.setRequestHeader("Accept-Language", locale.getLanguage()); //$NON-NLS-1$
- method.setRequestHeader("Content-Type", fContentType);
- int statusCode = client.executeMethod(method);
-
- if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED) {
- Header hType = method.getResponseHeader("Content-Type");
- if (hType != null) fContentType = hType.getValue();
- // for now we are only expecting Strings
- //return method.getResponseBodyAsString();
- return readFully(new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8"));
- }
- else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED || statusCode == HttpStatus.SC_UNAUTHORIZED)
- throw new AuthenticationException("Authentication failed:"+ method.getStatusLine() + ' ' + method.getURI() + ' ' + method.getStatusText());
- else {
- throw new Exception("Method failed: " + method.getStatusLine() + ' ' + method.getURI() + ' ' + method.getStatusText()); //$NON-NLS-1$
- }
- }
- catch (IOException e) {
- throw e;
- }
- finally {
- // Release the connection.
- method.releaseConnection();
- }
+ try {
+ HttpClient client = new HttpClient();
+
+ //
+ // set the clients proxy values if needed
+ //
+ ConnectionsPrefsManager.setProxySettings(client, properties);
+
+ //
+ // Add in original request headers
+ //
+ @SuppressWarnings("unchecked")
+ Enumeration<String> headers = request.getHeaderNames();
+ while(headers.hasMoreElements()){
+ String header = (String) headers.nextElement();
+
+ //
+ // We can't use content-length headers in case we altered the original body when filtering out
+ // the proxy parameters, so exclude them when adding the headers to the request
+ //
+ if(!header.equalsIgnoreCase("Content-Length")){
+ method.addRequestHeader(header, request.getHeader(header));
+ }
+ }
+
+ //
+ // Include authentication if required
+ //
+ if(fUseProxyAuthentication){
+ if (fBase64Auth != null) {
+ method.setRequestHeader("Authorization", fBase64Auth);
+ }
+ else {
+ List<String> authPrefs = new ArrayList<String>(2);
+ authPrefs.add(AuthPolicy.DIGEST );
+ authPrefs.add(AuthPolicy.BASIC);
+ client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+
+ //
+ // send the basic authentication response even before the server gives an unauthorized response
+ //
+ client.getParams().setAuthenticationPreemptive(true);
+
+ //
+ // Pass our credentials to HttpClient
+ //
+ client.getState().setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+ new UsernamePasswordCredentials(fProxyUsername, fProxyPassword));
+ }
+ }
+
+ //
+ // Excecute request and return response
+ //
+ int statusCode = client.executeMethod(method);
+ responseObject.status = statusCode;
+ responseObject.body = IOUtils.toByteArray(method.getResponseBodyAsStream());
+ responseObject.headers = method.getResponseHeaders();
+ return responseObject;
+ }
+ catch (IOException e) {
+ throw e;
+ }
+ finally {
+
+ //
+ // Release the connection.
+ //
+ method.releaseConnection();
+ }
}
-
+
/**
- * This is supposed to be the correct way to read the response instead of using getResponseBody() - which gives a runtime warning;
- *
- * See - http://mail-archives.apache.org/mod_mbox/jakarta-httpclient-user/200411.mbox/%3c1101558111.4070.22.camel@localhost.localdomain%3e
- * @param input
- * @return
- * @throws IOException
+ * Inner class used to pass response parts back to the servlet
*/
- private String readFully(Reader input) throws IOException {
- BufferedReader bufferedReader = input instanceof BufferedReader ? (BufferedReader) input
- : new BufferedReader(input);
- StringBuffer result = new StringBuffer();
- char[] buffer = new char[4 * 1024];
- int charsRead;
- while ((charsRead = bufferedReader.read(buffer)) != -1) {
- result.append(buffer, 0, charsRead);
- }
- return result.toString();
+ class ResponseObject{
+ public int status;
+ public byte[] body;
+ public Header[] headers;
+
}
}
Modified: incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java?rev=1195073&r1=1195072&r2=1195073&view=diff
==============================================================================
--- incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java (original)
+++ incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java Sat Oct 29 23:18:54 2011
@@ -15,7 +15,6 @@
package org.apache.wookie.proxy;
import java.io.IOException;
-import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
@@ -29,6 +28,7 @@ import javax.servlet.http.HttpServletReq
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.configuration.Configuration;
+import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.auth.AuthenticationException;
import org.apache.log4j.Logger;
import org.apache.wookie.beans.IAccessRequest;
@@ -68,13 +68,17 @@ public class ProxyServlet extends HttpSe
try {
Configuration properties = (Configuration) request.getSession().getServletContext().getAttribute("properties");
+ //
// Check that the request is coming from the same domain (i.e. from a widget served by this server)
+ //
if (properties.getBoolean("widget.proxy.checkdomain") && !isSameDomain(request)){
response.sendError(HttpServletResponse.SC_FORBIDDEN,"<error>"+UNAUTHORISED_MESSAGE+"</error>");
return;
}
+ //
// Check that the request is coming from a valid widget
+ //
IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
IWidgetInstance instance = persistenceManager.findWidgetInstanceByIdKey(request.getParameter("instanceid_key"));
if(instance == null && !isDefaultGadget(request)){
@@ -82,7 +86,9 @@ public class ProxyServlet extends HttpSe
return;
}
+ //
// Create the proxy bean for the request
+ //
ProxyURLBean bean;
try {
bean = new ProxyURLBean(request);
@@ -91,24 +97,50 @@ public class ProxyServlet extends HttpSe
return;
}
+ //
// should we filter urls?
+ //
if (properties.getBoolean("widget.proxy.usewhitelist") && !isAllowed(bean.getNewUrl().toURI(), instance)){
response.sendError(HttpServletResponse.SC_FORBIDDEN,"<error>URL Blocked</error>");
fLogger.warn("URL " + bean.getNewUrl().toExternalForm() + " Blocked");
return;
}
+ //
+ // Create a ProxyClient instance for the request
+ //
ProxyClient proxyclient = new ProxyClient(request);
- PrintWriter out = response.getWriter();
+ ProxyClient.ResponseObject responseObject = null;
+
//TODO - find all the links etc & make them absolute - to make request come thru this servlet
- String output = "";
+
+ //
+ // Execute the request and populate the ResponseObject
+ //
if(httpMethod.equals("get")){
- output = proxyclient.get(bean.getNewUrl().toExternalForm(), properties);
- }else{
- output = proxyclient.post(bean.getNewUrl().toExternalForm(),getXmlData(request), properties);
+ responseObject = proxyclient.get(bean.getNewUrl().toExternalForm(), request, properties);
+ } else {
+ responseObject = proxyclient.post(bean.getNewUrl().toExternalForm(), request, properties);
+ }
+
+ //
+ // Set Status
+ //
+ response.setStatus(responseObject.status);
+
+ //
+ // Set Headers
+ //
+ for (Header header:responseObject.headers){
+ response.setHeader(header.getName(), header.getValue());
+ }
+
+ //
+ // Set Body
+ //
+ if(responseObject.body != null && responseObject.body.length > 0){
+ response.getOutputStream().write(responseObject.body);
}
- response.setContentType(proxyclient.getCType());
- out.print(output);
}
catch (Exception ex) {
try {
@@ -127,22 +159,6 @@ public class ProxyServlet extends HttpSe
}
/**
- * Gets the content of the request
- * @param request
- * @return
- * @throws IOException
- */
- private String getXmlData(HttpServletRequest request) throws IOException{
- // Note that we cannot use a Reader for this as we already
- // call getParameter() which works on an InputStream - and you
- // can only use an InputStream OR a Reader, not both.
- byte[] b = new byte[request.getContentLength()];
- request.getInputStream().read(b, 0, request.getContentLength());
- String xml = new String(b);
- return xml;
- }
-
- /**
* Checks that the request is from the same domain as this service
* @param request
* @param checkDomain
Re:svn commit: r1195073 - in /incubator/wookie/trunk:
src-tests/org/apache/wookie/tests/functional/ src-tests/org/apache/wookie/tests/helpers/
src/org/apache/wookie/proxy/
Posted by Ross Gardler <rg...@opendirective.com>.
Scott,
Thank you for fixing a major issue I thought I'd have to spend days on!
(not tested yet but I'm sure it's great)
Sent from my mobile device, please forgive errors and brevity.
On Oct 30, 2011 12:19 AM, <sc...@apache.org> wrote:
> Author: scottbw
> Date: Sat Oct 29 23:18:54 2011
> New Revision: 1195073
>
> URL: http://svn.apache.org/viewvc?rev=1195073&view=rev
> Log:
> A substantial rewrite of the proxy service to support full headers being
> sent and returned, including cookies, and for any content types to be
> returned in the response body (see WOOKIE-251). Also added more tests,
> using a NanoHTTPD test server to check that custom headers and cookies are
> preserved across proxied requests.
>
> Added:
>
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
> Modified:
>
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
> incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
> incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
>
> Modified:
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
> URL:
> http://svn.apache.org/viewvc/incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java?rev=1195073&r1=1195072&r2=1195073&view=diff
>
> ==============================================================================
> ---
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
> (original)
> +++
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ProxyTest.java
> Sat Oct 29 23:18:54 2011
> @@ -20,6 +20,7 @@ import static org.junit.Assert.fail;
> import java.io.IOException;
> import java.util.ArrayList;
> import java.util.List;
> +import java.util.Properties;
>
> import org.apache.commons.httpclient.HttpClient;
> import org.apache.commons.httpclient.HttpException;
> @@ -29,6 +30,7 @@ import org.apache.commons.httpclient.aut
> import org.apache.commons.httpclient.auth.AuthScope;
> import org.apache.commons.httpclient.methods.GetMethod;
> import org.apache.commons.httpclient.methods.PostMethod;
> +import org.apache.wookie.tests.helpers.NanoHTTPD;
> import org.junit.BeforeClass;
> import org.junit.Ignore;
> import org.junit.Test;
> @@ -39,386 +41,516 @@ import org.junit.Test;
> */
> public class ProxyTest extends AbstractControllerTest {
>
> - private static String instance_id_key;
> - private static String other_instance_id_key;
> - private static final String OTHER_WIDGET_ID = "
> http://www.getwookie.org/widgets/natter";
> - private static final String PROXY_URL =
> TEST_SERVER_LOCATION+"proxy";
> -
> - /**
> - * The valid site URL is our Apache incubator page. This is added
> by default to the global site whitelist
> - */
> - private static final String VALID_SITE_URL = "
> http://incubator.apache.org/wookie/";
> -
> - /**
> - * Returns XML for checking we get the correct content-type
> - */
> - private static final String VALID_SITE_XML_URL =
> TEST_SERVER_LOCATION+"widgets?all=true";
> -
> - /**
> - * An invalid URL
> - */
> - private static final String INVALID_SITE_URL =
> "DFASFAFEQ3FQ32145235123452";
> -
> - /**
> - * A site not allowed in any policies
> - */
> - private static final String BLOCKED_SITE_URL = "
> http://very.bad.place";
> -
> - /**
> - * The policy allowed site URL is one that the Weather widget is
> granted acccess to using WARP in the default install
> - */
> - private static final String POLICY_ALLOWED_SITE_URL = "
> http://newsrss.bbc.co.uk/weather/forecast/9/Next3DaysRSS.xml";
> - private static final String POLICY_DISALLOWED_SITE_URL = "
> http://news.bbc.co.uk";
> - private static final String PROTECTED_SITE_URL =
> TEST_SERVER_LOCATION+"admin/";
> -
> - /**
> - * Create two instances of a widget for testing
> - * @throws IOException
> - * @throws HttpException
> - */
> - @BeforeClass
> - public static void setup() throws HttpException, IOException{
> - HttpClient client = new HttpClient();
> - PostMethod post = new
> PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
> -
> post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest");
> - client.executeMethod(post);
> - String response = post.getResponseBodyAsString();
> - instance_id_key =
> response.substring(response.indexOf("<identifier>")+12,
> response.indexOf("</identifier>"));
> - post.releaseConnection();
> -
> - client = new HttpClient();
> - post = new
> PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
> -
> post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+OTHER_WIDGET_ID+"&userid=test&shareddatakey=proxytest");
> - client.executeMethod(post);
> - response = post.getResponseBodyAsString();
> - other_instance_id_key =
> response.substring(response.indexOf("<identifier>")+12,
> response.indexOf("</identifier>"));
> - post.releaseConnection();
> -
> - }
> -
> -/**
> - * Check we can access a site allowed under the global whitelist using
> our widget instance's id key
> - */
> - @Test
> - public void getValidSite(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
> - assertEquals(200,send(url,"GET"));
> - assertEquals(500,send(url,"POST")); // This URL doesn't
> support POST
> - }
> -
> - /**
> - * Check we can access a site allowed under a widget-specific
> policy using an instance's id key
> - */
> - @Test
> - public void getAllowedSite(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
> - assertEquals(200,send(url,"GET"));
> - }
> -
> - /**
> - * Check that we can't access a site which is not in the whitelist
> or WARP
> - */
> - @Test
> - public void getDisallowedSite(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_DISALLOWED_SITE_URL;
> - assertEquals(403,send(url,"GET"));
> - assertEquals(403,send(url,"POST")); // This URL doesn't
> support POST
> - }
> -
> - /**
> - * Test trying to get to a site allowed for a different widget,
> but not this one
> - */
> - @Test
> - public void getAllowedSiteWrongInstance(){
> - String url =
> PROXY_URL+"?instanceid_key="+other_instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
> - assertEquals(403,send(url,"GET"));
> - assertEquals(403,send(url,"POST"));
> - }
> -
> - /**
> - * Test we can get the content from a site with the correct
> content-type (in this case XML)
> - * @throws Exception
> - */
> - @Test
> - public void getValidSiteAndValidXMLContentType() throws Exception{
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_XML_URL;
> - HttpClient client = new HttpClient();
> - HttpMethod req = new GetMethod(url);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> -
> assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/xml"));
> - }
> -
> - /**
> - * Test we can get the content from a site with the correct
> content-type (in this case HTML)
> - * @throws Exception
> - */
> - @Test
> - public void getValidSiteAndValidHTMLContentType() throws Exception{
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
> - HttpClient client = new HttpClient();
> - HttpMethod req = new GetMethod(url);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> -
> assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/html"));
> - }
> -
> -
> -/**
> - * Test that we can't access a site with an invalid instance ID key
> - */
> - @Test
> - public void getValidSiteInvalidIdKey(){
> - String url =
> PROXY_URL+"?instanceid_key=TEST&url="+VALID_SITE_URL;
> - assertEquals(403,send(url,"GET"));
> - assertEquals(403,send(url,"POST"));
> - }
> -
> - /**
> - * Test that we can't access a site no instance id key
> - */
> - @Test
> - public void getValidSiteNoIdKey(){
> - String url = PROXY_URL+"?url="+VALID_SITE_URL;
> - assertEquals(403,send(url,"GET"));
> - assertEquals(403,send(url,"POST"));
> - }
> -
> - /**
> - * Test we can't get access to a site using an invalid URL
> - */
> - @Test
> - public void getInvalidSite(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+INVALID_SITE_URL;
> - assertEquals(400,send(url,"GET"));
> - assertEquals(400,send(url,"POST"));
> - }
> + private static String instance_id_key;
> + private static String other_instance_id_key;
> + private static final String OTHER_WIDGET_ID = "
> http://www.getwookie.org/widgets/natter";
> + private static final String PROXY_URL = TEST_SERVER_LOCATION+"proxy";
> +
> + /**
> + * The valid site URL is our Apache incubator page. This is added by
> default to the global site whitelist
> + */
> + private static final String VALID_SITE_URL = "
> http://incubator.apache.org/wookie/";
> +
> + /**
> + * Returns XML for checking we get the correct content-type
> + */
> + private static final String VALID_SITE_XML_URL =
> TEST_SERVER_LOCATION+"widgets?all=true";
> +
> + /**
> + * An invalid URL
> + */
> + private static final String INVALID_SITE_URL =
> "DFASFAFEQ3FQ32145235123452";
> +
> + /**
> + * A site not allowed in any policies
> + */
> + private static final String BLOCKED_SITE_URL = "http://very.bad.place";
> +
> + /**
> + * The policy allowed site URL is one that the Weather widget is
> granted acccess to using WARP in the default install
> + */
> + private static final String POLICY_ALLOWED_SITE_URL = "
> http://newsrss.bbc.co.uk/weather/forecast/9/Next3DaysRSS.xml";
> + private static final String POLICY_DISALLOWED_SITE_URL = "
> http://news.bbc.co.uk";
> + private static final String PROTECTED_SITE_URL =
> TEST_SERVER_LOCATION+"admin/";
> +
> + /**
> + * Create two instances of a widget for testing
> + * @throws IOException
> + * @throws HttpException
> + */
> + @BeforeClass
> + public static void setup() throws HttpException, IOException{
> + HttpClient client = new HttpClient();
> + PostMethod post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
> +
> post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest");
> + client.executeMethod(post);
> + String response = post.getResponseBodyAsString();
> + instance_id_key =
> response.substring(response.indexOf("<identifier>")+12,
> response.indexOf("</identifier>"));
> + post.releaseConnection();
> +
> + client = new HttpClient();
> + post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
> +
> post.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+OTHER_WIDGET_ID+"&userid=test&shareddatakey=proxytest");
> + client.executeMethod(post);
> + response = post.getResponseBodyAsString();
> + other_instance_id_key =
> response.substring(response.indexOf("<identifier>")+12,
> response.indexOf("</identifier>"));
> + post.releaseConnection();
> +
> + }
> +
> + /**
> + * Check we can access a site allowed under the global whitelist using
> our widget instance's id key
> + */
> + @Test
> + public void getValidSite(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
> + assertEquals(200,send(url,"GET"));
> + assertEquals(405,send(url,"POST")); // This URL doesn't support POST
> + }
> +
> + /**
> + * Check we can access a site allowed under a widget-specific policy
> using an instance's id key
> + */
> + @Test
> + public void getAllowedSite(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
> + assertEquals(200,send(url,"GET"));
> + }
> +
> + /**
> + * Check that we can't access a site which is not in the whitelist or
> WARP
> + */
> + @Test
> + public void getDisallowedSite(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+POLICY_DISALLOWED_SITE_URL;
> + assertEquals(403,send(url,"GET"));
> + assertEquals(403,send(url,"POST")); // This URL doesn't support POST
> + }
> +
> + /**
> + * Test trying to get to a site allowed for a different widget, but not
> this one
> + */
> + @Test
> + public void getAllowedSiteWrongInstance(){
> + String url =
> PROXY_URL+"?instanceid_key="+other_instance_id_key+"&url="+POLICY_ALLOWED_SITE_URL;
> + assertEquals(403,send(url,"GET"));
> + assertEquals(403,send(url,"POST"));
> + }
> +
> + /**
> + * Test we can get the content from a site with the correct
> content-type (in this case XML)
> + * @throws Exception
> + */
> + @Test
> + public void getValidSiteAndValidXMLContentType() throws Exception{
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_XML_URL;
> + HttpClient client = new HttpClient();
> + HttpMethod req = new GetMethod(url);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> +
> assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/xml"));
> + }
> +
> + /**
> + * Test we can get the content from a site with the correct
> content-type (in this case HTML)
> + * @throws Exception
> + */
> + @Test
> + public void getValidSiteAndValidHTMLContentType() throws Exception{
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL;
> + HttpClient client = new HttpClient();
> + HttpMethod req = new GetMethod(url);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> +
> assertTrue(req.getResponseHeader("Content-Type").toString().contains("text/html"));
> + }
> +
>
> - /**
> + /**
> + * Test that we can't access a site with an invalid instance ID key
> + */
> + @Test
> + public void getValidSiteInvalidIdKey(){
> + String url = PROXY_URL+"?instanceid_key=TEST&url="+VALID_SITE_URL;
> + assertEquals(403,send(url,"GET"));
> + assertEquals(403,send(url,"POST"));
> + }
> +
> + /**
> + * Test that we can't access a site no instance id key
> + */
> + @Test
> + public void getValidSiteNoIdKey(){
> + String url = PROXY_URL+"?url="+VALID_SITE_URL;
> + assertEquals(403,send(url,"GET"));
> + assertEquals(403,send(url,"POST"));
> + }
> +
> + /**
> + * Test we can't get access to a site using an invalid URL
> + */
> + @Test
> + public void getInvalidSite(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+INVALID_SITE_URL;
> + assertEquals(400,send(url,"GET"));
> + assertEquals(400,send(url,"POST"));
> + }
> +
> + /**
> * Test we can't get access to a site that isn't in either the global
> whitelist of WARP
> */
> - @Test
> - public void getBlockedSite(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+BLOCKED_SITE_URL;
> - assertEquals(403,send(url,"GET"));
> - }
> -
> - /**
> - * Tests that we can get valid returns when using querystring
> parameters in a POST request
> - * @throws Exception
> - */
> - @Test
> - public void postWithOnlyQueryStringParameters() throws Exception{
> - HttpClient client = new HttpClient();
> - List<String> authPrefs = new ArrayList<String>(2);
> - authPrefs.add(AuthPolicy.DIGEST );
> - authPrefs.add(AuthPolicy.BASIC);
> - client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> - //
> - // send the basic authentication response even before the
> server gives an unauthorized response
> - //
> - client.getParams().setAuthenticationPreemptive(true);
> - client.getState().setCredentials(
> - new AuthScope(AuthScope.ANY_HOST,
> AuthScope.ANY_PORT, AuthScope.ANY_REALM),
> - new UsernamePasswordCredentials("java",
> "java"));
> - PostMethod req;
> - req = new
> PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> - }
> -
> - /**
> - * Ignored - this was a case for handling double-encoding, but
> which is no longer valid
> - */
> - @Ignore // Not a valid use case
> - @Test
> - public void getWithEncodedParameters(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"%3Fx=1%26y=2";
> - assertEquals(200,send(url,"GET"));
> - }
> -
> - /**
> - * Test the proxy can handle a site URL that contains unencoded
> parameters including a second "?" character
> - */
> - @Test
> - public void getWithUnencodedParameters(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"?x=1&y=2";
> - assertEquals(200,send(url,"GET"));
> - }
> -
> - /**
> + @Test
> + public void getBlockedSite(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+BLOCKED_SITE_URL;
> + assertEquals(403,send(url,"GET"));
> + }
> +
> + /**
> + * Tests that we can get valid returns when using querystring
> parameters in a POST request
> + * @throws Exception
> + */
> + @Test
> + public void postWithOnlyQueryStringParameters() throws Exception{
> + HttpClient client = new HttpClient();
> + List<String> authPrefs = new ArrayList<String>(2);
> + authPrefs.add(AuthPolicy.DIGEST );
> + authPrefs.add(AuthPolicy.BASIC);
> + client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY,
> authPrefs);
> + //
> + // send the basic authentication response even before the server
> gives an unauthorized response
> + //
> + client.getParams().setAuthenticationPreemptive(true);
> + client.getState().setCredentials(
> + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT,
> AuthScope.ANY_REALM),
> + new UsernamePasswordCredentials("java", "java"));
> + PostMethod req;
> + req = new
> PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> + }
> +
> + /**
> + * Ignored - this was a case for handling double-encoding, but which is
> no longer valid
> + */
> + @Ignore // Not a valid use case
> + @Test
> + public void getWithEncodedParameters(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"%3Fx=1%26y=2";
> + assertEquals(200,send(url,"GET"));
> + }
> +
> + /**
> + * Test the proxy can handle a site URL that contains unencoded
> parameters including a second "?" character
> + */
> + @Test
> + public void getWithUnencodedParameters(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"?x=1&y=2";
> + assertEquals(200,send(url,"GET"));
> + }
> +
> + /**
> * Test the proxy can handle a site URL that contains unencoded
> parameters appended in the usual form generated
> * by JQuery
> */
> - @Test
> - public void getWithJQueryEncodedParameters(){
> - String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"&x=y";
> - assertEquals(200,send(url,"GET"));
> - assertEquals(500,send(url,"POST")); // This URL doesn't
> support POST
> - }
> -
> - /**
> - * Test that the proxy behaves as expected when a POST request
> contains a mix of querystring parameters and post parameters
> - * @throws Exception
> - */
> - @Test
> - public void postWithMixedQueryAndParameters() throws Exception{
> - HttpClient client = new HttpClient();
> - List<String> authPrefs = new ArrayList<String>(2);
> - authPrefs.add(AuthPolicy.DIGEST );
> - authPrefs.add(AuthPolicy.BASIC);
> - client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> - //
> - // send the basic authentication response even before the
> server gives an unauthorized response
> - //
> - client.getParams().setAuthenticationPreemptive(true);
> - client.getState().setCredentials(
> - new AuthScope(AuthScope.ANY_HOST,
> AuthScope.ANY_PORT, AuthScope.ANY_REALM),
> - new UsernamePasswordCredentials("java",
> "java"));
> - PostMethod req;
> - req = new
> PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key);
> - req.addParameter("url", PROTECTED_SITE_URL);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> - }
> -
> - /**
> - * This tests accessing a site passing through POST parameters
> - * @throws Exception
> - */
> - @Test
> - public void postWithPassingParameters() throws Exception{
> - String url = PROXY_URL;
> - HttpClient client = new HttpClient();
> - PostMethod post = new PostMethod(url);
> - post.addParameter("instanceid_key", instance_id_key);
> - post.addParameter("url",
> TEST_PROPERTIES_SERVICE_URL_VALID);
> - post.addParameter("api_key", API_KEY_VALID);
> - post.addParameter("widgetid", WIDGET_ID_VALID);
> - post.addParameter("userid", "test");
> - post.addParameter("is_public", "false");
> - post.addParameter("shareddatakey","proxytest");
> - post.addParameter("propertyname", "pass");
> - post.addParameter("propertyvalue","pass");
> - client.executeMethod(post);
> - int code = post.getStatusCode();
> - assertEquals(200,code);
> - post.releaseConnection();
> -
> - client = new HttpClient();
> - GetMethod get = new
> GetMethod(TEST_PROPERTIES_SERVICE_URL_VALID);
> -
> get.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest&propertyname=pass");
> - client.executeMethod(get);
> - code = get.getStatusCode();
> - assertEquals(200, code);
> - String resp = get.getResponseBodyAsString();
> - assertEquals("pass",resp);
> - get.releaseConnection();
> - }
> -
> - /**
> - * Tests that the proxy behaves correctly when using POST
> parameters only
> - * @throws Exception
> - */
> - @Test
> - public void postWithOnlyParameters() throws Exception{
> - HttpClient client = new HttpClient();
> - List<String> authPrefs = new ArrayList<String>(2);
> - authPrefs.add(AuthPolicy.DIGEST );
> - authPrefs.add(AuthPolicy.BASIC);
> - client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> - //
> - // send the basic authentication response even before the
> server gives an unauthorized response
> - //
> - client.getParams().setAuthenticationPreemptive(true);
> - client.getState().setCredentials(
> - new AuthScope(AuthScope.ANY_HOST,
> AuthScope.ANY_PORT, AuthScope.ANY_REALM),
> - new UsernamePasswordCredentials("java",
> "java"));
> - PostMethod req;
> - req = new PostMethod(PROXY_URL);
> - req.addParameter("url", PROTECTED_SITE_URL);
> - req.addParameter("instanceid_key", instance_id_key);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> - }
> -
> - /**
> - * Test that the proxy passes through the correct status code when
> trying to access
> - * an allowed resource without authn credentials required at the
> final target site
> - * @throws Exception
> - */
> - @Test
> - public void getProtectedSiteWithoutAuth() throws Exception{
> - HttpClient client = new HttpClient();
> - HttpMethod req;
> - req = new
> GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(401,code);
> - }
> -
> - /**
> - * Test that the proxy passes along credentials to the proxied
> site and is granted access
> - * @throws Exception
> - */
> - @Test
> - public void getProtectedSiteWithBasicAuth() throws Exception{
> - HttpClient client = new HttpClient();
> - List<String> authPrefs = new ArrayList<String>(2);
> - authPrefs.add(AuthPolicy.DIGEST );
> - authPrefs.add(AuthPolicy.BASIC);
> - client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> - //
> - // send the basic authentication response even before the
> server gives an unauthorized response
> - //
> - client.getParams().setAuthenticationPreemptive(true);
> - client.getState().setCredentials(
> - new AuthScope(AuthScope.ANY_HOST,
> AuthScope.ANY_PORT, AuthScope.ANY_REALM),
> - new UsernamePasswordCredentials("java",
> "java"));
> - HttpMethod req;
> - req = new
> GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - assertEquals(200,code);
> - }
> -
> - /**
> - * Utility method for sending requests
> - * @param url
> - * @param method
> - * @return
> - */
> - private int send(String url, String method){
> - try {
> - HttpClient client = new HttpClient();
> - HttpMethod req;
> - if (method.equals("POST")){
> - req = new PostMethod(url);
> - } else {
> - req = new GetMethod(url);
> - }
> - client.executeMethod(req);
> - int code = req.getStatusCode();
> - req.releaseConnection();
> - return code;
> - }
> - catch (Exception e) {
> - e.printStackTrace();
> - fail("post failed");
> - return -1;
> - }
> - }
> + @Test
> + public void getWithJQueryEncodedParameters(){
> + String url =
> PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+VALID_SITE_URL+"&x=y";
> + assertEquals(200,send(url,"GET"));
> + assertEquals(405,send(url,"POST")); // This URL doesn't support POST
> + }
> +
> + /**
> + * Test that the proxy behaves as expected when a POST request contains
> a mix of querystring parameters and post parameters
> + * @throws Exception
> + */
> + @Test
> + public void postWithMixedQueryAndParameters() throws Exception{
> + HttpClient client = new HttpClient();
> + List<String> authPrefs = new ArrayList<String>(2);
> + authPrefs.add(AuthPolicy.DIGEST );
> + authPrefs.add(AuthPolicy.BASIC);
> + client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY,
> authPrefs);
> + //
> + // send the basic authentication response even before the server
> gives an unauthorized response
> + //
> + client.getParams().setAuthenticationPreemptive(true);
> + client.getState().setCredentials(
> + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT,
> AuthScope.ANY_REALM),
> + new UsernamePasswordCredentials("java", "java"));
> + PostMethod req;
> + req = new PostMethod(PROXY_URL+"?instanceid_key="+instance_id_key);
> + req.addParameter("url", PROTECTED_SITE_URL);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> + }
> +
> + /**
> + * This tests accessing a site passing through POST parameters
> + * @throws Exception
> + */
> + @Test
> + public void postWithPassingParameters() throws Exception{
> + String url = PROXY_URL;
> + HttpClient client = new HttpClient();
> + PostMethod post = new PostMethod(url);
> + post.addParameter("instanceid_key", instance_id_key);
> + post.addParameter("url", TEST_PROPERTIES_SERVICE_URL_VALID);
> + post.addParameter("api_key", API_KEY_VALID);
> + post.addParameter("widgetid", WIDGET_ID_VALID);
> + post.addParameter("userid", "test");
> + post.addParameter("is_public", "false");
> + post.addParameter("shareddatakey","proxytest");
> + post.addParameter("propertyname", "pass");
> + post.addParameter("propertyvalue","pass");
> + client.executeMethod(post);
> + int code = post.getStatusCode();
> + assertEquals(201,code);
> + post.releaseConnection();
> +
> + client = new HttpClient();
> + GetMethod get = new GetMethod(TEST_PROPERTIES_SERVICE_URL_VALID);
> +
> get.setQueryString("api_key="+API_KEY_VALID+"&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=proxytest&propertyname=pass");
> + client.executeMethod(get);
> + code = get.getStatusCode();
> + assertEquals(200, code);
> + String resp = get.getResponseBodyAsString();
> + assertEquals("pass",resp);
> + get.releaseConnection();
> + }
> +
> + /**
> + * Tests that the proxy behaves correctly when using POST parameters
> only
> + * @throws Exception
> + */
> + @Test
> + public void postWithOnlyParameters() throws Exception{
> + HttpClient client = new HttpClient();
> + List<String> authPrefs = new ArrayList<String>(2);
> + authPrefs.add(AuthPolicy.DIGEST );
> + authPrefs.add(AuthPolicy.BASIC);
> + client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY,
> authPrefs);
> + //
> + // send the basic authentication response even before the server
> gives an unauthorized response
> + //
> + client.getParams().setAuthenticationPreemptive(true);
> + client.getState().setCredentials(
> + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT,
> AuthScope.ANY_REALM),
> + new UsernamePasswordCredentials("java", "java"));
> + PostMethod req;
> + req = new PostMethod(PROXY_URL);
> + req.addParameter("url", PROTECTED_SITE_URL);
> + req.addParameter("instanceid_key", instance_id_key);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> + }
> +
> + /**
> + * Test that the proxy passes through the correct status code when
> trying to access
> + * an allowed resource without authn credentials required at the final
> target site
> + * @throws Exception
> + */
> + @Test
> + public void getProtectedSiteWithoutAuth() throws Exception{
> + HttpClient client = new HttpClient();
> + HttpMethod req;
> + req = new
> GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(401,code);
> + }
> +
> + /**
> + * Test that the proxy passes along credentials to the proxied site and
> is granted access
> + * @throws Exception
> + */
> + @Test
> + public void getProtectedSiteWithBasicAuth() throws Exception{
> + HttpClient client = new HttpClient();
> + List<String> authPrefs = new ArrayList<String>(2);
> + authPrefs.add(AuthPolicy.DIGEST );
> + authPrefs.add(AuthPolicy.BASIC);
> + client.getParams().setParameter (AuthPolicy.AUTH_SCHEME_PRIORITY,
> authPrefs);
> + //
> + // send the basic authentication response even before the server
> gives an unauthorized response
> + //
> + client.getParams().setAuthenticationPreemptive(true);
> + client.getState().setCredentials(
> + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT,
> AuthScope.ANY_REALM),
> + new UsernamePasswordCredentials("java", "java"));
> + HttpMethod req;
> + req = new
> GetMethod(PROXY_URL+"?instanceid_key="+instance_id_key+"&url="+PROTECTED_SITE_URL);
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + assertEquals(200,code);
> + }
> +
> + /**
> + * Test that proxy responses contain headers from the originating site.
> See WOOKIE-251
> + * @throws IOException
> + */
> + @Test
> + public void getWithCustomResponseHeaders() throws IOException{
> +
> + //
> + // Create a custom server
> + //
> + ProxiedServer server = new ProxiedServer(9000);
> + String testUrl = "http://localhost:9000";
> +
> + //
> + // Check the server works
> + //
> + HttpClient client = new HttpClient();
> + HttpMethod req = new GetMethod(testUrl);
> +
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + String header = req.getResponseHeader("CUSTOM-WOOKIE").getValue();
> + assertEquals(200,code);
> + assertEquals("hhrooar", header);
> + req.releaseConnection();
> +
> + //
> + // Make a proxed request, and check we get the custom header back
> + //
> + String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=
> http://localhost:9000";
> + req = new GetMethod(url);
> + client.executeMethod(req);
> + code = req.getStatusCode();
> + header = req.getResponseHeader("CUSTOM-WOOKIE").getValue();
> +
> + req.releaseConnection();
> + assertEquals(200,code);
> + assertEquals("hhrooar", header);
> +
> + server.stop();
> + }
> +
> + /**
> + * Test that proxy responses contain headers from the originating site.
> See WOOKIE-251
> + * @throws IOException
> + */
> + @Test
> + public void getWithCookies() throws IOException{
> +
> + //
> + // Create a custom server
> + //
> + ProxiedServer server = new ProxiedServer(9000);
> + String testUrl = "http://localhost:9000";
> +
> + //
> + // Check the server works
> + //
> + HttpClient client = new HttpClient();
> + HttpMethod req = new GetMethod(testUrl);
> +
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + String header = req.getResponseHeader("Set-Cookie").getValue();
> + assertEquals(200,code);
> + assertEquals("test=wookie", header);
> + req.releaseConnection();
> +
> + //
> + // Make a proxed request, and check we get the custom header back
> + //
> + String url = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=
> http://localhost:9000";
> + req = new GetMethod(url);
> + client.executeMethod(req);
> + code = req.getStatusCode();
> + header = req.getResponseHeader("Set-Cookie").getValue();
> +
> + req.releaseConnection();
> + assertEquals(200,code);
> + assertEquals("test=wookie", header);
> +
> + //
> + // Make another proxied request, this time send the cookie
> + //
> + String cookieurl = PROXY_URL+"?instanceid_key="+instance_id_key+"&url=
> http://localhost:9000";
> + req = new GetMethod(cookieurl);
> + req.addRequestHeader("Cookie", "test=wookie");
> + client.executeMethod(req);
> + code = req.getStatusCode();
> + header = req.getResponseHeader("GOT-COOKIE").getValue();
> +
> + req.releaseConnection();
> + assertEquals(200,code);
> + assertEquals("true", header);
> +
> + server.stop();
> + }
> +
> +
> + /**
> + * A subclass of NanoHTTPD for testing
> + */
> + class ProxiedServer extends NanoHTTPD{
> +
> + /**
> + * @param port
> + * @throws IOException
> + */
> + public ProxiedServer(int port) throws IOException {
> + super(port);
> + }
> +
> + /* (non-Javadoc)
> + * @see
> org.apache.wookie.tests.helpers.NanoHTTPD#serve(java.lang.String,
> java.lang.String, java.util.Properties, java.util.Properties,
> java.util.Properties)
> + */
> + @Override
> + public Response serve(String uri, String method, Properties header,
> + Properties parms, Properties files) {
> + Response response = new Response();
> + response.addHeader("CUSTOM-WOOKIE", "hhrooar");
> + response.addHeader("Set-Cookie","test=wookie");
> + String cookie = header.getProperty("cookie");
> + if (cookie != null && cookie.contains("test=wookie")){
> + response.addHeader("GOT-COOKIE", "true");
> + }
> + response.status = "200 OK";
> + return response;
> + }
> + }
> +
> + /**
> + * Utility method for sending requests
> + * @param url
> + * @param method
> + * @return
> + */
> + private int send(String url, String method){
> + try {
> + HttpClient client = new HttpClient();
> + HttpMethod req;
> + if (method.equals("POST")){
> + req = new PostMethod(url);
> + } else {
> + req = new GetMethod(url);
> + }
> + client.executeMethod(req);
> + int code = req.getStatusCode();
> + req.releaseConnection();
> + return code;
> + }
> + catch (Exception e) {
> + e.printStackTrace();
> + fail("post failed");
> + return -1;
> + }
> + }
>
> }
>
> Added:
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
> URL:
> http://svn.apache.org/viewvc/incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java?rev=1195073&view=auto
>
> ==============================================================================
> ---
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
> (added)
> +++
> incubator/wookie/trunk/src-tests/org/apache/wookie/tests/helpers/NanoHTTPD.java
> Sat Oct 29 23:18:54 2011
> @@ -0,0 +1,1117 @@
> +package org.apache.wookie.tests.helpers;
> +
> +import java.io.BufferedReader;
> +import java.io.ByteArrayInputStream;
> +import java.io.File;
> +import java.io.FileInputStream;
> +import java.io.IOException;
> +import java.io.InputStream;
> +import java.io.InputStreamReader;
> +import java.io.OutputStream;
> +import java.io.PrintWriter;
> +import java.net.ServerSocket;
> +import java.net.Socket;
> +import java.net.URLEncoder;
> +import java.util.Date;
> +import java.util.Enumeration;
> +import java.util.Vector;
> +import java.util.Hashtable;
> +import java.util.Locale;
> +import java.util.Properties;
> +import java.util.StringTokenizer;
> +import java.util.TimeZone;
> +
> +import java.io.ByteArrayOutputStream;
> +import java.io.FileOutputStream;
> +
> +/**
> + * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in
> Java
> + *
> + * <p> NanoHTTPD version 1.24,
> + * Copyright © 2001,2005-2011 Jarno Elonen (elonen@iki.fi,
> http://iki.fi/elonen/)
> + * and Copyright © 2010 Konstantinos Togias (info@ktogias.gr,
> http://ktogias.gr)
> + *
> + * <p><b>Features + limitations: </b><ul>
> + *
> + * <li> Only one Java file </li>
> + * <li> Java 1.1 compatible </li>
> + * <li> Released as open source, Modified BSD licence </li>
> + * <li> No fixed config files, logging, authorization etc. (Implement
> yourself if you need them.) </li>
> + * <li> Supports parameter parsing of GET and POST methods </li>
> + * <li> Supports both dynamic content and file serving </li>
> + * <li> Supports file upload (since version 1.2, 2010) </li>
> + * <li> Supports partial content (streaming)</li>
> + * <li> Supports ETags</li>
> + * <li> Never caches anything </li>
> + * <li> Doesn't limit bandwidth, request time or simultaneous
> connections </li>
> + * <li> Default code serves files and shows all HTTP parameters and
> headers</li>
> + * <li> File server supports directory listing, index.html and
> index.htm</li>
> + * <li> File server supports partial content (streaming)</li>
> + * <li> File server supports ETags</li>
> + * <li> File server does the 301 redirection trick for directories
> without '/'</li>
> + * <li> File server supports simple skipping for files (continue
> download) </li>
> + * <li> File server serves also very long files without memory
> overhead </li>
> + * <li> Contains a built-in list of most common mime types </li>
> + * <li> All header names are converted lowercase so they don't vary
> between browsers/clients </li>
> + *
> + * </ul>
> + *
> + * <p><b>Ways to use: </b><ul>
> + *
> + * <li> Run as a standalone app, serves files and shows requests</li>
> + * <li> Subclass serve() and embed to your own program </li>
> + * <li> Call serveFile() from serve() with your own base directory
> </li>
> + *
> + * </ul>
> + *
> + * Homepage: http://iki.fi/elonen/code/nanohttpd/
> + *
> + * See the end of the source file for distribution license
> + * (Modified BSD licence)
> + */
> +public class NanoHTTPD
> +{
> + // ==================================================
> + // API parts
> + // ==================================================
> +
> + /**
> + * Override this to customize the server.<p>
> + *
> + * (By default, this delegates to serveFile() and allows directory
> listing.)
> + *
> + * @param uri Percent-decoded URI without parameters, for
> example "/index.cgi"
> + * @param method "GET", "POST" etc.
> + * @param parms Parsed, percent decoded parameters from URI and,
> in case of POST, data.
> + * @param header Header entries, percent decoded
> + * @return HTTP response, see class Response for details
> + */
> + public Response serve( String uri, String method, Properties
> header, Properties parms, Properties files )
> + {
> + System.out.println( method + " '" + uri + "' " );
> +
> + Enumeration e = header.propertyNames();
> + while ( e.hasMoreElements())
> + {
> + String value = (String)e.nextElement();
> + System.out.println( " HDR: '" + value + "' = '" +
> +
> header.getProperty( value ) + "'" );
> + }
> + e = parms.propertyNames();
> + while ( e.hasMoreElements())
> + {
> + String value = (String)e.nextElement();
> + System.out.println( " PRM: '" + value + "' = '" +
> +
> parms.getProperty( value ) + "'" );
> + }
> + e = files.propertyNames();
> + while ( e.hasMoreElements())
> + {
> + String value = (String)e.nextElement();
> + System.out.println( " UPLOADED: '" + value + "' =
> '" +
> +
> files.getProperty( value ) + "'" );
> + }
> +
> + return serveFile( uri, header, myRootDir, true );
> + }
> +
> + /**
> + * HTTP response.
> + * Return one of these from serve().
> + */
> + public class Response
> + {
> + /**
> + * Default constructor: response = HTTP_OK, data = mime =
> 'null'
> + */
> + public Response()
> + {
> + this.status = HTTP_OK;
> + }
> +
> + /**
> + * Basic constructor.
> + */
> + public Response( String status, String mimeType,
> InputStream data )
> + {
> + this.status = status;
> + this.mimeType = mimeType;
> + this.data = data;
> + }
> +
> + /**
> + * Convenience method that makes an InputStream out of
> + * given text.
> + */
> + public Response( String status, String mimeType, String
> txt )
> + {
> + this.status = status;
> + this.mimeType = mimeType;
> + try
> + {
> + this.data = new ByteArrayInputStream(
> txt.getBytes("UTF-8"));
> + }
> + catch ( java.io.UnsupportedEncodingException uee )
> + {
> + uee.printStackTrace();
> + }
> + }
> +
> + /**
> + * Adds given line to the header.
> + */
> + public void addHeader( String name, String value )
> + {
> + header.put( name, value );
> + }
> +
> + /**
> + * HTTP status code after processing, e.g. "200 OK",
> HTTP_OK
> + */
> + public String status;
> +
> + /**
> + * MIME type of content, e.g. "text/html"
> + */
> + public String mimeType;
> +
> + /**
> + * Data of the response, may be null.
> + */
> + public InputStream data;
> +
> + /**
> + * Headers for the HTTP response. Use addHeader()
> + * to add lines.
> + */
> + public Properties header = new Properties();
> + }
> +
> + /**
> + * Some HTTP response status codes
> + */
> + public static final String
> + HTTP_OK = "200 OK",
> + HTTP_PARTIALCONTENT = "206 Partial Content",
> + HTTP_RANGE_NOT_SATISFIABLE = "416 Requested Range Not
> Satisfiable",
> + HTTP_REDIRECT = "301 Moved Permanently",
> + HTTP_FORBIDDEN = "403 Forbidden",
> + HTTP_NOTFOUND = "404 Not Found",
> + HTTP_BADREQUEST = "400 Bad Request",
> + HTTP_INTERNALERROR = "500 Internal Server Error",
> + HTTP_NOTIMPLEMENTED = "501 Not Implemented";
> +
> + /**
> + * Common mime types for dynamic content
> + */
> + public static final String
> + MIME_PLAINTEXT = "text/plain",
> + MIME_HTML = "text/html",
> + MIME_DEFAULT_BINARY = "application/octet-stream",
> + MIME_XML = "text/xml";
> +
> + // ==================================================
> + // Socket & server code
> + // ==================================================
> + /**
> + * Starts a HTTP server to given port.<p>
> + * Throws an IOException if the socket is already in use
> + */
> + public NanoHTTPD( int port, File wwwroot ) throws IOException
> + {
> + myTcpPort = port;
> + this.myRootDir = wwwroot;
> + myServerSocket = new ServerSocket( myTcpPort );
> + myThread = new Thread( new Runnable()
> + {
> + public void run()
> + {
> + try
> + {
> + while( true )
> + new HTTPSession(
> myServerSocket.accept());
> + }
> + catch ( IOException ioe )
> + {}
> + }
> + });
> + myThread.setDaemon( true );
> + myThread.start();
> + }
> +
> + /**
> + * Starts a HTTP server to given port.<p>
> + * Throws an IOException if the socket is already in use.
> + * Starts serving files from current directory.
> + */
> + public NanoHTTPD( int port ) throws IOException
> + {
> + this( port, new File(".").getAbsoluteFile());
> + }
> +
> + /**
> + * Stops the server.
> + */
> + public void stop()
> + {
> + try
> + {
> + myServerSocket.close();
> + myThread.join();
> + }
> + catch ( IOException ioe ) {}
> + catch ( InterruptedException e ) {}
> + }
> +
> +
> + /**
> + * Starts as a standalone file server and waits for Enter.
> + */
> + public static void main( String[] args )
> + {
> + System.out.println( "NanoHTTPD 1.24 (C) 2001,2005-2011
> Jarno Elonen and (C) 2010 Konstantinos Togias\n" +
> + "(Command line
> options: [-p port] [-d root-dir] [--licence])\n" );
> +
> + // Defaults
> + int port = 80;
> + File wwwroot = new File(".").getAbsoluteFile();
> +
> + // Show licence if requested
> + for ( int i=0; i<args.length; ++i )
> + if(args[i].equalsIgnoreCase("-p"))
> + port = Integer.parseInt( args[i+1] );
> + else if(args[i].equalsIgnoreCase("-d"))
> + wwwroot = new File( args[i+1] ).getAbsoluteFile();
> + else if ( args[i].toLowerCase().endsWith( "licence" ))
> + {
> + System.out.println( LICENCE + "\n" );
> + break;
> + }
> +
> + try
> + {
> + new NanoHTTPD( port, wwwroot );
> + }
> + catch( IOException ioe )
> + {
> + System.err.println( "Couldn't start server:\n" +
> ioe );
> + System.exit( -1 );
> + }
> +
> + System.out.println( "Now serving files in port " + port +
> " from \"" + wwwroot + "\"" );
> + System.out.println( "Hit Enter to stop.\n" );
> +
> + try { System.in.read(); } catch( Throwable t ) {}
> + }
> +
> + /**
> + * Handles one session, i.e. parses the HTTP request
> + * and returns the response.
> + */
> + private class HTTPSession implements Runnable
> + {
> + public HTTPSession( Socket s )
> + {
> + mySocket = s;
> + Thread t = new Thread( this );
> + t.setDaemon( true );
> + t.start();
> + }
> +
> + public void run()
> + {
> + try
> + {
> + InputStream is = mySocket.getInputStream();
> + if ( is == null) return;
> +
> + // Read the first 8192 bytes.
> + // The full header should fit in here.
> + // Apache's default header limit is 8KB.
> + int bufsize = 8192;
> + byte[] buf = new byte[bufsize];
> + int rlen = is.read(buf, 0, bufsize);
> + if (rlen <= 0) return;
> +
> + // Create a BufferedReader for parsing the
> header.
> + ByteArrayInputStream hbis = new
> ByteArrayInputStream(buf, 0, rlen);
> + BufferedReader hin = new BufferedReader(
> new InputStreamReader( hbis ));
> + Properties pre = new Properties();
> + Properties parms = new Properties();
> + Properties header = new Properties();
> + Properties files = new Properties();
> +
> + // Decode the header into parms and header
> java properties
> + decodeHeader(hin, pre, parms, header);
> + String method = pre.getProperty("method");
> + String uri = pre.getProperty("uri");
> +
> + long size = 0x7FFFFFFFFFFFFFFFl;
> + String contentLength =
> header.getProperty("content-length");
> + if (contentLength != null)
> + {
> + try { size =
> Integer.parseInt(contentLength); }
> + catch (NumberFormatException ex) {}
> + }
> +
> + // We are looking for the byte separating
> header from body.
> + // It must be the last byte of the first
> two sequential new lines.
> + int splitbyte = 0;
> + boolean sbfound = false;
> + while (splitbyte < rlen)
> + {
> + if (buf[splitbyte] == '\r' &&
> buf[++splitbyte] == '\n' && buf[++splitbyte] == '\r' && buf[++splitbyte] ==
> '\n') {
> + sbfound = true;
> + break;
> + }
> + splitbyte++;
> + }
> + splitbyte++;
> +
> + // Write the part of body already read to
> ByteArrayOutputStream f
> + ByteArrayOutputStream f = new
> ByteArrayOutputStream();
> + if (splitbyte < rlen) f.write(buf,
> splitbyte, rlen-splitbyte);
> +
> + // While Firefox sends on the first read
> all the data fitting
> + // our buffer, Chrome and Opera sends only
> the headers even if
> + // there is data for the body. So we do
> some magic here to find
> + // out whether we have already consumed
> part of body, if we
> + // have reached the end of the data to be
> sent or we should
> + // expect the first byte of the body at
> the next read.
> + if (splitbyte < rlen)
> + size -= rlen - splitbyte +1;
> + else if (!sbfound || size ==
> 0x7FFFFFFFFFFFFFFFl)
> + size = 0;
> +
> + // Now read all the body and write it to f
> + buf = new byte[512];
> + while ( rlen >= 0 && size > 0 )
> + {
> + rlen = is.read(buf, 0, 512);
> + size -= rlen;
> + if (rlen > 0)
> + f.write(buf, 0, rlen);
> + }
> +
> + // Get the raw body as a byte []
> + byte [] fbuf = f.toByteArray();
> +
> + // Create a BufferedReader for easily
> reading it as string.
> + ByteArrayInputStream bin = new
> ByteArrayInputStream(fbuf);
> + BufferedReader in = new BufferedReader(
> new InputStreamReader(bin));
> +
> + // If the method is POST, there may be
> parameters
> + // in data section, too, read it:
> + if ( method.equalsIgnoreCase( "POST" ))
> + {
> + String contentType = "";
> + String contentTypeHeader =
> header.getProperty("content-type");
> + StringTokenizer st = new
> StringTokenizer( contentTypeHeader , "; " );
> + if ( st.hasMoreTokens()) {
> + contentType =
> st.nextToken();
> + }
> +
> + if
> (contentType.equalsIgnoreCase("multipart/form-data"))
> + {
> + // Handle
> multipart/form-data
> + if ( !st.hasMoreTokens())
> + sendError(
> HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but
> boundary missing. Usage: GET /example/file.html" );
> + String boundaryExp =
> st.nextToken();
> + st = new StringTokenizer(
> boundaryExp , "=" );
> + if (st.countTokens() != 2)
> + sendError(
> HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but
> boundary syntax error. Usage: GET /example/file.html" );
> + st.nextToken();
> + String boundary =
> st.nextToken();
> +
> +
> decodeMultipartData(boundary, fbuf, in, parms, files);
> + }
> + else
> + {
> + // Handle
> application/x-www-form-urlencoded
> + String postLine = "";
> + char pbuf[] = new
> char[512];
> + int read = in.read(pbuf);
> + while ( read >= 0 &&
> !postLine.endsWith("\r\n") )
> + {
> + postLine +=
> String.valueOf(pbuf, 0, read);
> + read =
> in.read(pbuf);
> + }
> + postLine = postLine.trim();
> + decodeParms( postLine,
> parms );
> + }
> + }
> +
> + // Ok, now do the serve()
> + Response r = serve( uri, method, header,
> parms, files );
> + if ( r == null )
> + sendError( HTTP_INTERNALERROR,
> "SERVER INTERNAL ERROR: Serve() returned a null response." );
> + else
> + sendResponse( r.status,
> r.mimeType, r.header, r.data );
> +
> + in.close();
> + is.close();
> + }
> + catch ( IOException ioe )
> + {
> + try
> + {
> + sendError( HTTP_INTERNALERROR,
> "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
> + }
> + catch ( Throwable t ) {}
> + }
> + catch ( InterruptedException ie )
> + {
> + // Thrown by sendError, ignore and exit
> the thread.
> + }
> + }
> +
> + /**
> + * Decodes the sent headers and loads the data into
> + * java Properties' key - value pairs
> + **/
> + private void decodeHeader(BufferedReader in, Properties
> pre, Properties parms, Properties header)
> + throws InterruptedException
> + {
> + try {
> + // Read the request line
> + String inLine = in.readLine();
> + if (inLine == null) return;
> + StringTokenizer st = new StringTokenizer(
> inLine );
> + if ( !st.hasMoreTokens())
> + sendError( HTTP_BADREQUEST, "BAD
> REQUEST: Syntax error. Usage: GET /example/file.html" );
> +
> + String method = st.nextToken();
> + pre.put("method", method);
> +
> + if ( !st.hasMoreTokens())
> + sendError( HTTP_BADREQUEST, "BAD
> REQUEST: Missing URI. Usage: GET /example/file.html" );
> +
> + String uri = st.nextToken();
> +
> + // Decode parameters from the URI
> + int qmi = uri.indexOf( '?' );
> + if ( qmi >= 0 )
> + {
> + decodeParms( uri.substring( qmi+1
> ), parms );
> + uri = decodePercent(
> uri.substring( 0, qmi ));
> + }
> + else uri = decodePercent(uri);
> +
> + // If there's another token, it's protocol
> version,
> + // followed by HTTP headers. Ignore
> version but parse headers.
> + // NOTE: this now forces header names
> lowercase since they are
> + // case insensitive and vary by client.
> + if ( st.hasMoreTokens())
> + {
> + String line = in.readLine();
> + while ( line != null &&
> line.trim().length() > 0 )
> + {
> + int p = line.indexOf( ':'
> );
> + if ( p >= 0 )
> + header.put(
> line.substring(0,p).trim().toLowerCase(), line.substring(p+1).trim());
> + line = in.readLine();
> + }
> + }
> +
> + pre.put("uri", uri);
> + }
> + catch ( IOException ioe )
> + {
> + sendError( HTTP_INTERNALERROR, "SERVER
> INTERNAL ERROR: IOException: " + ioe.getMessage());
> + }
> + }
> +
> + /**
> + * Decodes the Multipart Body data and put it
> + * into java Properties' key - value pairs.
> + **/
> + private void decodeMultipartData(String boundary, byte[]
> fbuf, BufferedReader in, Properties parms, Properties files)
> + throws InterruptedException
> + {
> + try
> + {
> + int[] bpositions =
> getBoundaryPositions(fbuf,boundary.getBytes());
> + int boundarycount = 1;
> + String mpline = in.readLine();
> + while ( mpline != null )
> + {
> + if (mpline.indexOf(boundary) == -1)
> + sendError(
> HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but next
> chunk does not start with boundary. Usage: GET /example/file.html" );
> + boundarycount++;
> + Properties item = new Properties();
> + mpline = in.readLine();
> + while (mpline != null &&
> mpline.trim().length() > 0)
> + {
> + int p = mpline.indexOf(
> ':' );
> + if (p != -1)
> + item.put(
> mpline.substring(0,p).trim().toLowerCase(), mpline.substring(p+1).trim());
> + mpline = in.readLine();
> + }
> + if (mpline != null)
> + {
> + String contentDisposition
> = item.getProperty("content-disposition");
> + if (contentDisposition ==
> null)
> + {
> + sendError(
> HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but no
> content-disposition info found. Usage: GET /example/file.html" );
> + }
> + StringTokenizer st = new
> StringTokenizer( contentDisposition , "; " );
> + Properties disposition =
> new Properties();
> + while ( st.hasMoreTokens())
> + {
> + String token =
> st.nextToken();
> + int p =
> token.indexOf( '=' );
> + if (p!=-1)
> +
> disposition.put( token.substring(0,p).trim().toLowerCase(),
> token.substring(p+1).trim());
> + }
> + String pname =
> disposition.getProperty("name");
> + pname =
> pname.substring(1,pname.length()-1);
> +
> + String value = "";
> + if
> (item.getProperty("content-type") == null) {
> + while (mpline !=
> null && mpline.indexOf(boundary) == -1)
> + {
> + mpline =
> in.readLine();
> + if (
> mpline != null)
> + {
> +
> int d = mpline.indexOf(boundary);
> + if
> (d == -1)
> +
> value+=mpline;
> +
> else
> +
> value+=mpline.substring(0,d-2);
> + }
> + }
> + }
> + else
> + {
> + if (boundarycount>
> bpositions.length)
> + sendError(
> HTTP_INTERNALERROR, "Error processing request" );
> + int offset =
> stripMultipartHeaders(fbuf, bpositions[boundarycount-2]);
> + String path =
> saveTmpFile(fbuf, offset, bpositions[boundarycount-1]-offset-4);
> + files.put(pname,
> path);
> + value =
> disposition.getProperty("filename");
> + value =
> value.substring(1,value.length()-1);
> + do {
> + mpline =
> in.readLine();
> + } while (mpline !=
> null && mpline.indexOf(boundary) == -1);
> + }
> + parms.put(pname, value);
> + }
> + }
> + }
> + catch ( IOException ioe )
> + {
> + sendError( HTTP_INTERNALERROR, "SERVER
> INTERNAL ERROR: IOException: " + ioe.getMessage());
> + }
> + }
> +
> + /**
> + * Find the byte positions where multipart boundaries
> start.
> + **/
> + public int[] getBoundaryPositions(byte[] b, byte[]
> boundary)
> + {
> + int matchcount = 0;
> + int matchbyte = -1;
> + Vector matchbytes = new Vector();
> + for (int i=0; i<b.length; i++)
> + {
> + if (b[i] == boundary[matchcount])
> + {
> + if (matchcount == 0)
> + matchbyte = i;
> + matchcount++;
> + if (matchcount==boundary.length)
> + {
> + matchbytes.addElement(new
> Integer(matchbyte));
> + matchcount = 0;
> + matchbyte = -1;
> + }
> + }
> + else
> + {
> + i -= matchcount;
> + matchcount = 0;
> + matchbyte = -1;
> + }
> + }
> + int[] ret = new int[matchbytes.size()];
> + for (int i=0; i < ret.length; i++)
> + {
> + ret[i] =
> ((Integer)matchbytes.elementAt(i)).intValue();
> + }
> + return ret;
> + }
> +
> + /**
> + * Retrieves the content of a sent file and saves it
> + * to a temporary file.
> + * The full path to the saved file is returned.
> + **/
> + private String saveTmpFile(byte[] b, int offset, int len)
> + {
> + String path = "";
> + if (len > 0)
> + {
> + String tmpdir =
> System.getProperty("java.io.tmpdir");
> + try {
> + File temp =
> File.createTempFile("NanoHTTPD", "", new File(tmpdir));
> + OutputStream fstream = new
> FileOutputStream(temp);
> + fstream.write(b, offset, len);
> + fstream.close();
> + path = temp.getAbsolutePath();
> + } catch (Exception e) { // Catch exception
> if any
> + System.err.println("Error: " +
> e.getMessage());
> + }
> + }
> + return path;
> + }
> +
> +
> + /**
> + * It returns the offset separating multipart file headers
> + * from the file's data.
> + **/
> + private int stripMultipartHeaders(byte[] b, int offset)
> + {
> + int i = 0;
> + for (i=offset; i<b.length; i++)
> + {
> + if (b[i] == '\r' && b[++i] == '\n' &&
> b[++i] == '\r' && b[++i] == '\n')
> + break;
> + }
> + return i+1;
> + }
> +
> + /**
> + * Decodes the percent encoding scheme. <br/>
> + * For example: "an+example%20string" -> "an example
> string"
> + */
> + private String decodePercent( String str ) throws
> InterruptedException
> + {
> + try
> + {
> + StringBuffer sb = new StringBuffer();
> + for( int i=0; i<str.length(); i++ )
> + {
> + char c = str.charAt( i );
> + switch ( c )
> + {
> + case '+':
> + sb.append( ' ' );
> + break;
> + case '%':
> +
> sb.append((char)Integer.parseInt( str.substring(i+1,i+3), 16 ));
> + i += 2;
> + break;
> + default:
> + sb.append( c );
> + break;
> + }
> + }
> + return sb.toString();
> + }
> + catch( Exception e )
> + {
> + sendError( HTTP_BADREQUEST, "BAD REQUEST:
> Bad percent-encoding." );
> + return null;
> + }
> + }
> +
> + /**
> + * Decodes parameters in percent-encoded URI-format
> + * ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
> + * adds them to given Properties. NOTE: this doesn't
> support multiple
> + * identical keys due to the simplicity of Properties --
> if you need multiples,
> + * you might want to replace the Properties with a
> Hashtable of Vectors or such.
> + */
> + private void decodeParms( String parms, Properties p )
> + throws InterruptedException
> + {
> + if ( parms == null )
> + return;
> +
> + StringTokenizer st = new StringTokenizer( parms,
> "&" );
> + while ( st.hasMoreTokens())
> + {
> + String e = st.nextToken();
> + int sep = e.indexOf( '=' );
> + if ( sep >= 0 )
> + p.put( decodePercent( e.substring(
> 0, sep )).trim(),
> + decodePercent(
> e.substring( sep+1 )));
> + }
> + }
> +
> + /**
> + * Returns an error message as a HTTP response and
> + * throws InterruptedException to stop further request
> processing.
> + */
> + private void sendError( String status, String msg ) throws
> InterruptedException
> + {
> + sendResponse( status, MIME_PLAINTEXT, null, new
> ByteArrayInputStream( msg.getBytes()));
> + throw new InterruptedException();
> + }
> +
> + /**
> + * Sends given response to the socket.
> + */
> + private void sendResponse( String status, String mime,
> Properties header, InputStream data )
> + {
> + try
> + {
> + if ( status == null )
> + throw new Error( "sendResponse():
> Status can't be null." );
> +
> + OutputStream out =
> mySocket.getOutputStream();
> + PrintWriter pw = new PrintWriter( out );
> + pw.print("HTTP/1.0 " + status + " \r\n");
> +
> + if ( mime != null )
> + pw.print("Content-Type: " + mime +
> "\r\n");
> +
> + if ( header == null || header.getProperty(
> "Date" ) == null )
> + pw.print( "Date: " +
> gmtFrmt.format( new Date()) + "\r\n");
> +
> + if ( header != null )
> + {
> + Enumeration e = header.keys();
> + while ( e.hasMoreElements())
> + {
> + String key =
> (String)e.nextElement();
> + String value =
> header.getProperty( key );
> + pw.print( key + ": " +
> value + "\r\n");
> + }
> + }
> +
> + pw.print("\r\n");
> + pw.flush();
> +
> + if ( data != null )
> + {
> + int maxBuff = 2048;
> + int pending = data.available(); //
> This is to support partial sends, see serveFile()
> + byte[] buff = new
> byte[Math.max(pending, maxBuff)];
> + while (pending>0)
> + {
> + int read = data.read(
> buff, 0, ( (pending>maxBuff) ? maxBuff : pending ));
> + if (read <= 0) break;
> + out.write( buff, 0, read );
> + pending -= read;
> + }
> + }
> + out.flush();
> + out.close();
> + if ( data != null )
> + data.close();
> + }
> + catch( IOException ioe )
> + {
> + // Couldn't write? No can do.
> + try { mySocket.close(); } catch( Throwable
> t ) {}
> + }
> + }
> +
> + private Socket mySocket;
> + }
> +
> + /**
> + * URL-encodes everything between "/"-characters.
> + * Encodes spaces as '%20' instead of '+'.
> + */
> + private String encodeUri( String uri )
> + {
> + String newUri = "";
> + StringTokenizer st = new StringTokenizer( uri, "/ ", true
> );
> + while ( st.hasMoreTokens())
> + {
> + String tok = st.nextToken();
> + if ( tok.equals( "/" ))
> + newUri += "/";
> + else if ( tok.equals( " " ))
> + newUri += "%20";
> + else
> + {
> + newUri += URLEncoder.encode( tok );
> + // For Java 1.4 you'll want to use this
> instead:
> + // try { newUri += URLEncoder.encode( tok,
> "UTF-8" ); } catch ( java.io.UnsupportedEncodingException uee ) {}
> + }
> + }
> + return newUri;
> + }
> +
> + private int myTcpPort;
> + private final ServerSocket myServerSocket;
> + private Thread myThread;
> + private File myRootDir;
> +
> + // ==================================================
> + // File server code
> + // ==================================================
> +
> + /**
> + * Serves file from homeDir and its' subdirectories (only).
> + * Uses only URI, ignores all headers and HTTP parameters.
> + */
> + public Response serveFile( String uri, Properties header, File
> homeDir,
> + boolean
> allowDirectoryListing )
> + {
> + Response res = null;
> +
> + // Make sure we won't die of an exception later
> + if ( !homeDir.isDirectory())
> + res = new Response( HTTP_INTERNALERROR,
> MIME_PLAINTEXT,
> + "INTERNAL ERRROR: serveFile(): given
> homeDir is not a directory." );
> +
> + if ( res == null )
> + {
> + // Remove URL arguments
> + uri = uri.trim().replace( File.separatorChar, '/'
> );
> + if ( uri.indexOf( '?' ) >= 0 )
> + uri = uri.substring(0, uri.indexOf( '?' ));
> +
> + // Prohibit getting out of current directory
> + if ( uri.startsWith( ".." ) || uri.endsWith( ".."
> ) || uri.indexOf( "../" ) >= 0 )
> + res = new Response( HTTP_FORBIDDEN,
> MIME_PLAINTEXT,
> + "FORBIDDEN: Won't serve ../ for
> security reasons." );
> + }
> +
> + File f = new File( homeDir, uri );
> + if ( res == null && !f.exists())
> + res = new Response( HTTP_NOTFOUND, MIME_PLAINTEXT,
> + "Error 404, file not found." );
> +
> + // List the directory, if necessary
> + if ( res == null && f.isDirectory())
> + {
> + // Browsers get confused without '/' after the
> + // directory, send a redirect.
> + if ( !uri.endsWith( "/" ))
> + {
> + uri += "/";
> + res = new Response( HTTP_REDIRECT,
> MIME_HTML,
> + "<html><body>Redirected: <a
> href=\"" + uri + "\">" +
> + uri + "</a></body></html>");
> + res.addHeader( "Location", uri );
> + }
> +
> + if ( res == null )
> + {
> + // First try index.html and index.htm
> + if ( new File( f, "index.html" ).exists())
> + f = new File( homeDir, uri +
> "/index.html" );
> + else if ( new File( f, "index.htm"
> ).exists())
> + f = new File( homeDir, uri +
> "/index.htm" );
> + // No index file, list the directory if it
> is readable
> + else if ( allowDirectoryListing &&
> f.canRead() )
> + {
> + String[] files = f.list();
> + String msg =
> "<html><body><h1>Directory " + uri + "</h1><br/>";
> +
> + if ( uri.length() > 1 )
> + {
> + String u = uri.substring(
> 0, uri.length()-1 );
> + int slash = u.lastIndexOf(
> '/' );
> + if ( slash >= 0 && slash
> < u.length())
> + msg += "<b><a
> href=\"" + uri.substring(0, slash+1) + "\">..</a></b><br/>";
> + }
> +
> + if (files!=null)
> + {
> + for ( int i=0;
> i<files.length; ++i )
> + {
> + File curFile = new
> File( f, files[i] );
> + boolean dir =
> curFile.isDirectory();
> + if ( dir )
> + {
> + msg +=
> "<b>";
> + files[i]
> += "/";
> + }
> +
> + msg += "<a
> href=\"" + encodeUri( uri + files[i] ) + "\">" +
> + files[i]
> + "</a>";
> +
> + // Show file size
> + if (
> curFile.isFile())
> + {
> + long len =
> curFile.length();
> + msg += "
> <font size=2>(";
> + if ( len <
> 1024 )
> +
> msg += len + " bytes";
> + else if (
> len < 1024 * 1024 )
> +
> msg += len/1024 + "." + (len%1024/10%100) + " KB";
> + else
> +
> msg += len/(1024*1024) + "." + len%(1024*1024)/10%100 + " MB";
> +
> + msg +=
> ")</font>";
> + }
> + msg += "<br/>";
> + if ( dir ) msg +=
> "</b>";
> + }
> + }
> + msg += "</body></html>";
> + res = new Response( HTTP_OK,
> MIME_HTML, msg );
> + }
> + else
> + {
> + res = new Response(
> HTTP_FORBIDDEN, MIME_PLAINTEXT,
> + "FORBIDDEN: No directory
> listing." );
> + }
> + }
> + }
> +
> + try
> + {
> + if ( res == null )
> + {
> + // Get MIME type from file name extension,
> if possible
> + String mime = null;
> + int dot =
> f.getCanonicalPath().lastIndexOf( '.' );
> + if ( dot >= 0 )
> + mime = (String)theMimeTypes.get(
> f.getCanonicalPath().substring( dot + 1 ).toLowerCase());
> + if ( mime == null )
> + mime = MIME_DEFAULT_BINARY;
> +
> + // Calculate etag
> + String etag =
> Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" +
> f.length()).hashCode());
> +
> + // Support (simple) skipping:
> + long startFrom = 0;
> + long endAt = -1;
> + String range = header.getProperty( "range"
> );
> + if ( range != null )
> + {
> + if ( range.startsWith( "bytes=" ))
> + {
> + range = range.substring(
> "bytes=".length());
> + int minus = range.indexOf(
> '-' );
> + try {
> + if ( minus > 0 )
> + {
> + startFrom
> = Long.parseLong( range.substring( 0, minus ));
> + endAt =
> Long.parseLong( range.substring( minus+1 ));
> + }
> + }
> + catch (
> NumberFormatException nfe ) {}
> + }
> + }
> +
> + // Change return code and add
> Content-Range header when skipping is requested
> + long fileLen = f.length();
> + if (range != null && startFrom >= 0)
> + {
> + if ( startFrom >= fileLen)
> + {
> + res = new Response(
> HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "" );
> + res.addHeader(
> "Content-Range", "bytes 0-0/" + fileLen);
> + res.addHeader( "ETag",
> etag);
> + }
> + else
> + {
> + if ( endAt < 0 )
> + endAt = fileLen-1;
> + long newLen = endAt -
> startFrom + 1;
> + if ( newLen < 0 ) newLen =
> 0;
> +
> + final long dataLen =
> newLen;
> + FileInputStream fis = new
> FileInputStream( f ) {
> + public int
> available() throws IOException { return (int)dataLen; }
> + };
> + fis.skip( startFrom );
> +
> + res = new Response(
> HTTP_PARTIALCONTENT, mime, fis );
> + res.addHeader(
> "Content-Length", "" + dataLen);
> + res.addHeader(
> "Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
> + res.addHeader( "ETag",
> etag);
> + }
> + }
> + else
> + {
> + res = new Response( HTTP_OK, mime,
> new FileInputStream( f ));
> + res.addHeader( "Content-Length",
> "" + fileLen);
> + res.addHeader( "ETag", etag);
> + }
> + }
> + }
> + catch( IOException ioe )
> + {
> + res = new Response( HTTP_FORBIDDEN,
> MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." );
> + }
> +
> + res.addHeader( "Accept-Ranges", "bytes"); // Announce that
> the file server accepts partial content requestes
> + return res;
> + }
> +
> + /**
> + * Hashtable mapping (String)FILENAME_EXTENSION ->
> (String)MIME_TYPE
> + */
> + private static Hashtable theMimeTypes = new Hashtable();
> + static
> + {
> + StringTokenizer st = new StringTokenizer(
> + "css text/css "+
> + "htm text/html "+
> + "html text/html "+
> + "xml text/xml "+
> + "txt text/plain "+
> + "asc text/plain "+
> + "gif image/gif "+
> + "jpg image/jpeg "+
> + "jpeg image/jpeg "+
> + "png image/png "+
> + "mp3 audio/mpeg "+
> + "m3u audio/mpeg-url " +
> + "mp4 video/mp4 " +
> + "ogv video/ogg " +
> + "flv video/x-flv " +
> + "mov video/quicktime " +
> + "swf application/x-shockwave-flash " +
> + "js application/javascript "+
> + "pdf application/pdf "+
> + "doc application/msword "+
> + "ogg application/x-ogg "+
> + "zip application/octet-stream "+
> + "exe application/octet-stream "+
> + "class application/octet-stream " );
> + while ( st.hasMoreTokens())
> + theMimeTypes.put( st.nextToken(), st.nextToken());
> + }
> +
> + /**
> + * GMT date formatter
> + */
> + private static java.text.SimpleDateFormat gmtFrmt;
> + static
> + {
> + gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy
> HH:mm:ss 'GMT'", Locale.US);
> + gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
> + }
> +
> + /**
> + * The distribution licence
> + */
> + private static final String LICENCE =
> + "Copyright (C) 2001,2005-2011 by Jarno Elonen <
> elonen@iki.fi>\n"+
> + "and Copyright (C) 2010 by Konstantinos Togias <
> info@ktogias.gr>\n"+
> + "\n"+
> + "Redistribution and use in source and binary forms, with
> or without\n"+
> + "modification, are permitted provided that the following
> conditions\n"+
> + "are met:\n"+
> + "\n"+
> + "Redistributions of source code must retain the above
> copyright notice,\n"+
> + "this list of conditions and the following disclaimer.
> Redistributions in\n"+
> + "binary form must reproduce the above copyright notice,
> this list of\n"+
> + "conditions and the following disclaimer in the
> documentation and/or other\n"+
> + "materials provided with the distribution. The name of the
> author may not\n"+
> + "be used to endorse or promote products derived from this
> software without\n"+
> + "specific prior written permission. \n"+
> + " \n"+
> + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
> EXPRESS OR\n"+
> + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES\n"+
> + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> ARE DISCLAIMED.\n"+
> + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
> INDIRECT,\n"+
> + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
> (INCLUDING, BUT\n"+
> + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
> SERVICES; LOSS OF USE,\n"+
> + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
> CAUSED AND ON ANY\n"+
> + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> LIABILITY, OR TORT\n"+
> + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
> OUT OF THE USE\n"+
> + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
> SUCH DAMAGE.";
> +}
> +
>
> Modified:
> incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
> URL:
> http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java?rev=1195073&r1=1195072&r2=1195073&view=diff
>
> ==============================================================================
> --- incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
> (original)
> +++ incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyClient.java
> Sat Oct 29 23:18:54 2011
> @@ -14,14 +14,11 @@
>
> package org.apache.wookie.proxy;
>
> -import java.io.BufferedReader;
> import java.io.IOException;
> -import java.io.InputStreamReader;
> -import java.io.Reader;
> import java.util.ArrayList;
> +import java.util.Enumeration;
> import java.util.HashMap;
> import java.util.List;
> -import java.util.Locale;
> import java.util.Map;
>
> import javax.servlet.http.HttpServletRequest;
> @@ -30,15 +27,14 @@ import org.apache.commons.configuration.
> import org.apache.commons.httpclient.Header;
> import org.apache.commons.httpclient.HttpClient;
> import org.apache.commons.httpclient.HttpMethod;
> -import org.apache.commons.httpclient.HttpStatus;
> import org.apache.commons.httpclient.NameValuePair;
> import org.apache.commons.httpclient.UsernamePasswordCredentials;
> import org.apache.commons.httpclient.auth.AuthPolicy;
> import org.apache.commons.httpclient.auth.AuthScope;
> -import org.apache.commons.httpclient.auth.AuthenticationException;
> import org.apache.commons.httpclient.methods.GetMethod;
> +import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
> import org.apache.commons.httpclient.methods.PostMethod;
> -import org.apache.commons.httpclient.methods.StringRequestEntity;
> +import org.apache.commons.io.IOUtils;
> import org.apache.log4j.Logger;
>
> /**
> @@ -48,7 +44,6 @@ public class ProxyClient {
>
> static Logger fLogger =
> Logger.getLogger(ProxyClient.class.getName());
>
> - private String fContentType = "text/plain";
> private String fProxyUsername = null;
> private String fProxyPassword = null;
> private String fBase64Auth = null;
> @@ -57,6 +52,9 @@ public class ProxyClient {
>
> @SuppressWarnings("unchecked")
> public ProxyClient(HttpServletRequest request){
> + //
> + // If the request includes authn parameters, create proxy auth
> headers
> + //
> String proxyUserName = request.getParameter("username");
> String proxyPassword = request.getParameter("password");
> String base64Auth = request.getHeader("Authorization");
> @@ -64,8 +62,10 @@ public class ProxyClient {
> this.setProxyAuthConfig(proxyUserName,
> proxyPassword);
> if(base64Auth != null)
> this.setBase64AuthConfig(base64Auth);
> + //
> + // Filter out instructions to the proxy server from the
> original request parameters
> + //
> filterParameters(request.getParameterMap());
> - fContentType = request.getContentType();
> }
>
> private void setProxyAuthConfig(String username, String password){
> @@ -78,36 +78,48 @@ public class ProxyClient {
> fUseProxyAuthentication = true;
> fBase64Auth = base64Auth;
> }
> -
> - public String getCType(){
> - return fContentType;
> - }
> -
> - public String get(String url, Configuration properties) throws
> Exception {
> - fLogger.debug("GET from " + url); //$NON-NLS-1$
> - GetMethod method = new GetMethod(url);
> - method.setDoAuthentication(true);
> - return executeMethod(method, properties);
> - }
> -
> - public String post(String uri, String xmlData, Configuration
> properties) throws Exception {
> - fLogger.debug("POST to " + uri); //$NON-NLS-1$
> - PostMethod method = new PostMethod(uri);
> - method.setDoAuthentication(true);
> -
> - if(this.parameters.length > 0) {
> - method.addParameters(this.parameters);
> - } else {
> - method.setRequestEntity(new
> StringRequestEntity(xmlData, "text/xml", "UTF8"));//$NON-NLS-1$
> //$NON-NLS-2$
> - }
> -
> - return executeMethod(method, properties);
> +
> + /**
> + * Process a proxied GET request
> + * @param url the URL to GET
> + * @param request the original request object
> + * @param properties the servlet configuration
> + * @return a ResponseObject from the remote site
> + * @throws Exception
> + */
> + public ResponseObject get(String url, HttpServletRequest request,
> Configuration properties) throws Exception {
> + fLogger.debug("GET from " + url); //$NON-NLS-1$
> + GetMethod method = new GetMethod(url);
> + method.setDoAuthentication(true);
> + return executeMethod(method, request, properties);
> }
> +
> + /**
> + * Process a proxied POST request
> + * @param url the URL to POST
> + * @param xmlData the body of the request
> + * @param request the original request object
> + * @param properties the servlet configuration
> + * @return a ResponseObject from the remote site
> + * @throws Exception
> + */
> + public ResponseObject post(String url , HttpServletRequest
> request, Configuration properties) throws Exception {
> + fLogger.debug("POST to " + url); //$NON-NLS-1$
> + PostMethod method = new PostMethod(url);
> + method.setDoAuthentication(true);
> +
> + if(this.parameters.length > 0) {
> + method.addParameters(this.parameters);
> + } else {
> + method.setRequestEntity(new
> InputStreamRequestEntity(request.getInputStream()));
> + }
> +
> + return executeMethod(method, request, properties);
> + }
>
> /**
> * Processes the parameters passed through to the request,
> * removing the parameters used by the proxy itself
> - * @return
> */
> private void filterParameters(Map<Object,Object> umap){
> Map<Object, Object> map = new HashMap<Object, Object>(umap);
> @@ -124,78 +136,98 @@ public class ProxyClient {
> }
> parameters = params.toArray(new
> NameValuePair[params.size()]);
> }
> +
> + /**
> + * Execute the request and return the components of the response
> + * @param method the method to use, e.g. POST or GET
> + * @param request the original request
> + * @param properties the configuration of the servlet
> + * @return a ResponseObject containing both the response body and
> all headers returned
> + * @throws Exception
> + */
> + private ResponseObject executeMethod(HttpMethod method,
> HttpServletRequest request, Configuration properties) throws Exception{
> + ResponseObject responseObject = new ResponseObject();
>
> - private String executeMethod(HttpMethod method, Configuration
> properties) throws Exception, AuthenticationException {
> - // Execute the method.
> - try {
> - HttpClient client = new HttpClient();
> - // set the clients proxy values if needed
> - ConnectionsPrefsManager.setProxySettings(client,
> properties);
> -
> - if(fUseProxyAuthentication){
> - if (fBase64Auth != null) {
> -
> method.setRequestHeader("Authorization", fBase64Auth);
> - }
> - else {
> - List<String> authPrefs = new
> ArrayList<String>(2);
> - authPrefs.add(AuthPolicy.DIGEST );
> - authPrefs.add(AuthPolicy.BASIC);
> - client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> - // send the basic authentication
> response even before the server gives an unauthorized response
> -
> client.getParams().setAuthenticationPreemptive(true);
> - // Pass our credentials to
> HttpClient
> - client.getState().setCredentials(
> - new
> AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
> - new
> UsernamePasswordCredentials(fProxyUsername, fProxyPassword));
> - }
> - }
> -
> - // Add user language to http request in order to
> notify server of user's language
> - Locale locale = Locale.getDefault();
> -
> - method.setRequestHeader("Accept-Language",
> locale.getLanguage()); //$NON-NLS-1$
> - method.setRequestHeader("Content-Type",
> fContentType);
> - int statusCode = client.executeMethod(method);
> -
> - if (statusCode == HttpStatus.SC_OK || statusCode
> == HttpStatus.SC_CREATED) {
> - Header hType =
> method.getResponseHeader("Content-Type");
> - if (hType != null) fContentType =
> hType.getValue();
> - // for now we are only expecting Strings
> - //return method.getResponseBodyAsString();
> - return readFully(new
> InputStreamReader(method.getResponseBodyAsStream(), "UTF-8"));
> - }
> - else if (statusCode ==
> HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED || statusCode ==
> HttpStatus.SC_UNAUTHORIZED)
> - throw new
> AuthenticationException("Authentication failed:"+ method.getStatusLine() +
> ' ' + method.getURI() + ' ' + method.getStatusText());
> - else {
> - throw new Exception("Method failed: " +
> method.getStatusLine() + ' ' + method.getURI() + ' ' +
> method.getStatusText()); //$NON-NLS-1$
> - }
> - }
> - catch (IOException e) {
> - throw e;
> - }
> - finally {
> - // Release the connection.
> - method.releaseConnection();
> - }
> + try {
> + HttpClient client = new HttpClient();
> +
> + //
> + // set the clients proxy values if needed
> + //
> + ConnectionsPrefsManager.setProxySettings(client, properties);
> +
> + //
> + // Add in original request headers
> + //
> + @SuppressWarnings("unchecked")
> + Enumeration<String> headers = request.getHeaderNames();
> + while(headers.hasMoreElements()){
> + String header = (String) headers.nextElement();
> +
> + //
> + // We can't use content-length headers in case we altered
> the original body when filtering out
> + // the proxy parameters, so exclude them when adding the
> headers to the request
> + //
> + if(!header.equalsIgnoreCase("Content-Length")){
> + method.addRequestHeader(header,
> request.getHeader(header));
> + }
> + }
> +
> + //
> + // Include authentication if required
> + //
> + if(fUseProxyAuthentication){
> + if (fBase64Auth != null) {
> + method.setRequestHeader("Authorization", fBase64Auth);
> + }
> + else {
> + List<String> authPrefs = new ArrayList<String>(2);
> + authPrefs.add(AuthPolicy.DIGEST );
> + authPrefs.add(AuthPolicy.BASIC);
> + client.getParams().setParameter
> (AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
> +
> + //
> + // send the basic authentication response even before the
> server gives an unauthorized response
> + //
> + client.getParams().setAuthenticationPreemptive(true);
> +
> + //
> + // Pass our credentials to HttpClient
> + //
> + client.getState().setCredentials(
> + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT,
> AuthScope.ANY_REALM),
> + new UsernamePasswordCredentials(fProxyUsername,
> fProxyPassword));
> + }
> + }
> +
> + //
> + // Excecute request and return response
> + //
> + int statusCode = client.executeMethod(method);
> + responseObject.status = statusCode;
> + responseObject.body =
> IOUtils.toByteArray(method.getResponseBodyAsStream());
> + responseObject.headers = method.getResponseHeaders();
> + return responseObject;
> + }
> + catch (IOException e) {
> + throw e;
> + }
> + finally {
> +
> + //
> + // Release the connection.
> + //
> + method.releaseConnection();
> + }
> }
> -
> +
> /**
> - * This is supposed to be the correct way to read the response
> instead of using getResponseBody() - which gives a runtime warning;
> - *
> - * See -
> http://mail-archives.apache.org/mod_mbox/jakarta-httpclient-user/200411.mbox/%3c1101558111.4070.22.camel@localhost.localdomain%3e
> - * @param input
> - * @return
> - * @throws IOException
> + * Inner class used to pass response parts back to the servlet
> */
> - private String readFully(Reader input) throws IOException {
> - BufferedReader bufferedReader = input instanceof
> BufferedReader ? (BufferedReader) input
> - : new BufferedReader(input);
> - StringBuffer result = new StringBuffer();
> - char[] buffer = new char[4 * 1024];
> - int charsRead;
> - while ((charsRead = bufferedReader.read(buffer)) != -1) {
> - result.append(buffer, 0, charsRead);
> - }
> - return result.toString();
> + class ResponseObject{
> + public int status;
> + public byte[] body;
> + public Header[] headers;
> +
> }
> }
>
> Modified:
> incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
> URL:
> http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java?rev=1195073&r1=1195072&r2=1195073&view=diff
>
> ==============================================================================
> --- incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
> (original)
> +++ incubator/wookie/trunk/src/org/apache/wookie/proxy/ProxyServlet.java
> Sat Oct 29 23:18:54 2011
> @@ -15,7 +15,6 @@
> package org.apache.wookie.proxy;
>
> import java.io.IOException;
> -import java.io.PrintWriter;
> import java.io.UnsupportedEncodingException;
> import java.net.MalformedURLException;
> import java.net.URI;
> @@ -29,6 +28,7 @@ import javax.servlet.http.HttpServletReq
> import javax.servlet.http.HttpServletResponse;
>
> import org.apache.commons.configuration.Configuration;
> +import org.apache.commons.httpclient.Header;
> import org.apache.commons.httpclient.auth.AuthenticationException;
> import org.apache.log4j.Logger;
> import org.apache.wookie.beans.IAccessRequest;
> @@ -68,13 +68,17 @@ public class ProxyServlet extends HttpSe
> try {
> Configuration properties = (Configuration)
> request.getSession().getServletContext().getAttribute("properties");
>
> + //
> // Check that the request is coming from the same
> domain (i.e. from a widget served by this server)
> + //
> if
> (properties.getBoolean("widget.proxy.checkdomain") &&
> !isSameDomain(request)){
>
> response.sendError(HttpServletResponse.SC_FORBIDDEN,"<error>"+UNAUTHORISED_MESSAGE+"</error>");
> return;
> }
>
> + //
> // Check that the request is coming from a valid
> widget
> + //
> IPersistenceManager persistenceManager =
> PersistenceManagerFactory.getPersistenceManager();
> IWidgetInstance instance =
> persistenceManager.findWidgetInstanceByIdKey(request.getParameter("instanceid_key"));
> if(instance == null && !isDefaultGadget(request)){
> @@ -82,7 +86,9 @@ public class ProxyServlet extends HttpSe
> return;
> }
>
> + //
> // Create the proxy bean for the request
> + //
> ProxyURLBean bean;
> try {
> bean = new ProxyURLBean(request);
> @@ -91,24 +97,50 @@ public class ProxyServlet extends HttpSe
> return;
> }
>
> + //
> // should we filter urls?
> + //
> if
> (properties.getBoolean("widget.proxy.usewhitelist") &&
> !isAllowed(bean.getNewUrl().toURI(), instance)){
>
> response.sendError(HttpServletResponse.SC_FORBIDDEN,"<error>URL
> Blocked</error>");
> fLogger.warn("URL " +
> bean.getNewUrl().toExternalForm() + " Blocked");
> return;
> }
>
> + //
> + // Create a ProxyClient instance for the request
> + //
> ProxyClient proxyclient = new ProxyClient(request);
> - PrintWriter out = response.getWriter();
> + ProxyClient.ResponseObject responseObject = null;
> +
> //TODO - find all the links etc & make them
> absolute - to make request come thru this servlet
> - String output = "";
> +
> + //
> + // Execute the request and populate the
> ResponseObject
> + //
> if(httpMethod.equals("get")){
> - output =
> proxyclient.get(bean.getNewUrl().toExternalForm(), properties);
> - }else{
> - output =
> proxyclient.post(bean.getNewUrl().toExternalForm(),getXmlData(request),
> properties);
> + responseObject =
> proxyclient.get(bean.getNewUrl().toExternalForm(), request, properties);
> + } else {
> + responseObject =
> proxyclient.post(bean.getNewUrl().toExternalForm(), request, properties);
> + }
> +
> + //
> + // Set Status
> + //
> + response.setStatus(responseObject.status);
> +
> + //
> + // Set Headers
> + //
> + for (Header header:responseObject.headers){
> + response.setHeader(header.getName(),
> header.getValue());
> + }
> +
> + //
> + // Set Body
> + //
> + if(responseObject.body != null &&
> responseObject.body.length > 0){
> +
> response.getOutputStream().write(responseObject.body);
> }
> - response.setContentType(proxyclient.getCType());
> - out.print(output);
> }
> catch (Exception ex) {
> try {
> @@ -127,22 +159,6 @@ public class ProxyServlet extends HttpSe
> }
>
> /**
> - * Gets the content of the request
> - * @param request
> - * @return
> - * @throws IOException
> - */
> - private String getXmlData(HttpServletRequest request) throws
> IOException{
> - // Note that we cannot use a Reader for this as we already
> - // call getParameter() which works on an InputStream - and
> you
> - // can only use an InputStream OR a Reader, not both.
> - byte[] b = new byte[request.getContentLength()];
> - request.getInputStream().read(b, 0,
> request.getContentLength());
> - String xml = new String(b);
> - return xml;
> - }
> -
> - /**
> * Checks that the request is from the same domain as this service
> * @param request
> * @param checkDomain
>
>
>