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 &copy; 2001,2005-2011 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
+ * and Copyright &copy; 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 += " &nbsp;<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 &copy; 2001,2005-2011 Jarno Elonen (elonen@iki.fi,
> http://iki.fi/elonen/)
> + * and Copyright &copy; 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 += "
> &nbsp;<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
>
>
>