diff options
5 files changed, 338 insertions, 154 deletions
@@ -1,4 +1,14 @@ -2013-08-01 Andrew Azores <[email protected]> +2013-08-12 Andrew Azores <[email protected]> + * tests/test-extensions/net/sourceforge/jnlp/TinyHttpdImpl.java: refactored + * tests/test-extensions/net/sourceforge/jnlp/ServerLauncher.java: + TinyHttpdImpl constructor changed, reflecting this here + * tests/test-extensions-tests/net/sourceforge/jnlp/ServerAccessTest.java: + removed TinyHttpdImpl tests + * tests/test-extensions-tests/net/sourceforge/jnlp/TinyHttpdImplTest.java: + new unit tests for TinyHttpdImpl and moved old tests out of + ServerAccessTest + +2013-08-01 Andrew Azores <[email protected]> * .hgignore: ignore generated HTML files (from AboutDialog) @@ -22,7 +32,7 @@ should be served by test server in reproducers run and so prevent FNF exception * ChangeLog: fixed few entries below (added emty line between author and body) -2013-07-25 Andrew Azores <[email protected]> +2013-07-25 Andrew Azores <[email protected]> * netx/net/sourceforge/jnlp/about/AboutDialog.java (AboutDialog, display): removed "throws IOException" @@ -35,7 +45,7 @@ * netx/net/sourceforge/jnlp/splashscreen/impls/DefaultSplashScreens2012Commons.java: same -2013-07-22 Andrew Azores <[email protected]> +2013-07-22 Andrew Azores <[email protected]> * netx/net/sourceforge/jnlp/runtime/RhinoBasedPacEvaluator.java: (getProxiesWithoutCaching) added java.vm.name read permission to fix diff --git a/tests/test-extensions-tests/net/sourceforge/jnlp/ServerAccessTest.java b/tests/test-extensions-tests/net/sourceforge/jnlp/ServerAccessTest.java index 1b93e3e..9607045 100644 --- a/tests/test-extensions-tests/net/sourceforge/jnlp/ServerAccessTest.java +++ b/tests/test-extensions-tests/net/sourceforge/jnlp/ServerAccessTest.java @@ -39,7 +39,6 @@ package net.sourceforge.jnlp; import java.io.File; import java.io.FileInputStream; import java.net.URL; -import java.net.URLDecoder; import org.junit.Assert; import org.junit.Test; @@ -220,70 +219,6 @@ public class ServerAccessTest { Assert.assertArrayEquals(b3, bb[2]); } - private static final String[] filePathTestUrls = { - "/foo.html", - "/foo/", - "/foo/bar.jar", - "/foo/bar.jar;path_param", - "/foo/bar.jar%3Bpath_param", - "/foo/bar?query=string&red=hat" - }; - - @Test - public void urlToFilePathTest() throws Exception { - for (String url : filePathTestUrls) { - String newUrl = TinyHttpdImpl.urlToFilePath(url); - - Assert.assertFalse("File path should not contain query string: " + newUrl, newUrl.contains("?")); - Assert.assertTrue("File path should be relative: " + newUrl, newUrl.startsWith("./")); - Assert.assertFalse("File path should not contain \"/XslowX\":" + newUrl, - newUrl.toLowerCase().contains("/XslowX".toLowerCase())); - - if (url.endsWith("/")) { - Assert.assertTrue(newUrl.endsWith("/index.html")); - } - } - } - - @Test - public void urlToFilePathUrlDecodeTest() throws Exception { - // This test may fail with strange original URLs, eg those containing the substring "%253B", - // which can be decoded into "%3B", then decoded again into ';'. - - for (String url : filePathTestUrls) { - String newUrl = TinyHttpdImpl.urlToFilePath(url); - Assert.assertEquals(newUrl, URLDecoder.decode(newUrl, "UTF-8")); - } - } - - @Test - public void stripHttpPathParamTest() { - String[] testBaseUrls = { - "http://foo.com/bar", - "localhost:8080", - "https://bar.co.uk/site;para/baz?u=param1&v=param2" - }; - - String[] testJarNames = { - "jar", - "foo.jar", - "bar;baz.jar", - "nom.jar;", - "rhat.jar.pack.gz;tag" - }; - - for (String url : testBaseUrls) { - for (String jar : testJarNames) { - String newUrl = TinyHttpdImpl.stripHttpPathParams(url), - newJar = TinyHttpdImpl.stripHttpPathParams(jar), - path = newUrl + "/" + newJar; - Assert.assertTrue("Base URL should not have been modified: " + url + " => " + newUrl, newUrl.equals(url)); - Assert.assertTrue("JAR name should not be altered other than removing path param: " + jar + " => " + newJar, jar.startsWith(newJar)); - Assert.assertTrue("New path should be a substring of old path: " + path + " => " + url + "/" + jar, (url + "/" + jar).startsWith(path)); - } - } - } - private void printArrays(byte[][] bb) { System.out.println("[][] l=" + bb.length); for (int i = 0; i < bb.length; i++) { diff --git a/tests/test-extensions-tests/net/sourceforge/jnlp/TinyHttpdImplTest.java b/tests/test-extensions-tests/net/sourceforge/jnlp/TinyHttpdImplTest.java new file mode 100644 index 0000000..e7567f5 --- /dev/null +++ b/tests/test-extensions-tests/net/sourceforge/jnlp/TinyHttpdImplTest.java @@ -0,0 +1,226 @@ +package net.sourceforge.jnlp; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URLDecoder; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.Assert; +import org.junit.Test; + +public class TinyHttpdImplTest { + + private static final String HTTP_OK = "HTTP/1.0 200 OK"; + private static final String HTTP_400 = "HTTP/1.0 400 Bad Request"; + private static final String HTTP_404 = "HTTP/1.0 404 Not Found"; + private static final String HTTP_501 = "HTTP/1.0 501 Not Implemented"; + private static final String CONTENT_JNLP = "Content-Type: application/x-java-jnlp-file"; + private static final String CONTENT_HTML = "Content-Type: text/html"; + private static final String CONTENT_JAR = "Content-Type: application/x-jar"; + private static final Pattern CONTENT_LENGTH = Pattern.compile("Content-Length:([0-9]+)"); + + private static final String[] FilePathTestUrls = { + "/foo.html", + "/foo/", + "/foo/bar.jar", + "/foo/bar.jar;path_param", + "/foo/bar.jar%3Bpath_param", + "/foo/bar?query=string&red=hat" + }; + + private static BufferedReader mReader; + private static DataOutputStream mWriter; + private static TinyHttpdImpl mServer; + + static { + try { + ServerSocket sSocket = new ServerSocket(44322); + sSocket.setReuseAddress(true); + File dir = new File(System.getProperty("test.server.dir")); + Socket extSock = new Socket("localhost", 44322); + extSock.setReuseAddress(true); + mServer = new TinyHttpdImpl(extSock, dir); + + Socket socket = sSocket.accept(); + socket.setReuseAddress(true); + mReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + mWriter = new DataOutputStream(socket.getOutputStream()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + @Test + public void urlToFilePathTest() throws Exception { + for (String url : FilePathTestUrls) { + String newUrl = TinyHttpdImpl.urlToFilePath(url); + + Assert.assertFalse("File path should not contain query string: " + newUrl, newUrl.contains("?")); + Assert.assertTrue("File path should be relative: " + newUrl, newUrl.startsWith("./")); + Assert.assertFalse("File path should not contain \"/XslowX\":" + newUrl, + newUrl.toLowerCase().contains("/XslowX".toLowerCase())); + + if (url.endsWith("/")) { + Assert.assertTrue(newUrl.endsWith("/index.html")); + } + } + } + + @Test + public void urlToFilePathUrlDecodeTest() throws Exception { + // This test may fail with strange original URLs, eg those containing the substring "%253B", + // which can be decoded into "%3B", then decoded again into ';'. + + for (String url : FilePathTestUrls) { + String newUrl = TinyHttpdImpl.urlToFilePath(url); + Assert.assertEquals(newUrl, URLDecoder.decode(newUrl, "UTF-8")); + } + } + + @Test + public void stripHttpPathParamTest() { + String[] testBaseUrls = { + "http://foo.com/bar", + "localhost:8080", + "https://bar.co.uk/site;para/baz?u=param1&v=param2" + }; + + String[] testJarNames = { + "jar", + "foo.jar", + "bar;baz.jar", + "nom.jar;", + "rhat.jar.pack.gz;tag" + }; + + for (String url : testBaseUrls) { + for (String jar : testJarNames) { + String newUrl = TinyHttpdImpl.stripHttpPathParams(url), newJar = TinyHttpdImpl.stripHttpPathParams(jar), path = newUrl + "/" + newJar; + Assert.assertTrue("Base URL should not have been modified: " + url + " => " + newUrl, newUrl.equals(url)); + Assert.assertTrue("JAR name should not be altered other than removing path param: " + jar + " => " + newJar, jar.startsWith(newJar)); + Assert.assertTrue("New path should be a substring of old path: " + path + " => " + url + "/" + jar, (url + "/" + jar).startsWith(path)); + } + } + } + + private void headTestHelper(String request, String contentType) { + Matcher matcher = CONTENT_LENGTH.matcher(request); + + Assert.assertTrue("Status should have been " + HTTP_OK, request.contains(HTTP_OK)); + Assert.assertTrue("Content type should have been " + contentType, request.contains(contentType)); + Assert.assertTrue("Should have had a content length", matcher.find()); + } + + @Test + public void JnlpHeadTest() throws IOException, InterruptedException { + String head = getTinyHttpdImplResponse("HEAD", "/simpletest1.jnlp"); + headTestHelper(head, CONTENT_JNLP); + } + + @Test + public void HtmlHeadTest() throws Exception { + String head = getTinyHttpdImplResponse("HEAD", "/StripHttpPathParams.html"); + headTestHelper(head, CONTENT_HTML); + } + + @Test + public void JarHeadTest() throws Exception { + String head = getTinyHttpdImplResponse("HEAD", "/StripHttpPathParams.jar"); + headTestHelper(head, CONTENT_JAR); + } + + @Test + public void PngHeadTest() throws Exception { + // TinyHttpdImpl doesn't recognize PNG type - default content type should be HTML + String head = getTinyHttpdImplResponse("HEAD", "/netxPlugin.png"); + headTestHelper(head, CONTENT_HTML); + } + + @Test + public void SlowSendTest() throws Exception { + // This test is VERY SLOW due to the extremely slow sending speed TinyHttpdImpl uses when XslowX is specified. + // Running time will be over two minutes. + long fastStartTime = System.nanoTime(); + String req1 = getTinyHttpdImplResponse("GET", "/simpletest1.jnlp"); + long fastElapsed = System.nanoTime() - fastStartTime; + + long slowStartTime = System.nanoTime(); + String req2 = getTinyHttpdImplResponse("GET", "/XslowXsimpletest1.jnlp"); + long slowElapsed = System.nanoTime() - slowStartTime; + + Assert.assertTrue("Slow request should have returned the same data as normal request", req1.equals(req2)); + + // This isn't a very good test since as it is, getTinyHttpdImpl is slowing down its receive rate to + // deal with the reduced sending rate. It is hardcoded to be slower. + Assert.assertTrue("Slow request should have taken longer than normal request", slowElapsed > fastElapsed); + } + + @Test + public void GetTest() throws Exception { + String jnlpHead = getTinyHttpdImplResponse("HEAD", "/simpletest1.jnlp"); + String jnlpGet = getTinyHttpdImplResponse("GET", "/simpletest1.jnlp"); + + Assert.assertTrue("GET status should be " + HTTP_OK, jnlpGet.contains(HTTP_OK)); + Assert.assertTrue("GET content type should have been " + CONTENT_JNLP, jnlpGet.contains(CONTENT_JNLP)); + Assert.assertTrue("GET response should contain HEAD response", jnlpGet.contains(jnlpHead)); + Assert.assertTrue("GET response should have been longer than HEAD response", jnlpGet.length() > jnlpHead.length()); + } + + @Test + public void Error404DoesNotCauseShutdown() throws Exception { + // Pre-refactoring, 404 errors were sent after catching an IOException when trying to open the requested + // resource. However this was caught by a try/catch clause around the entire while loop, so a 404 would + // shut down the server. + String firstRequest = getTinyHttpdImplResponse("HEAD", "/no_such_file"); + String secondRequest = getTinyHttpdImplResponse("HEAD", "/simpletest1.jnlp"); + + Assert.assertTrue("First request should have been " + HTTP_404, firstRequest.trim().equals(HTTP_404)); + Assert.assertTrue("Second request should have been " + HTTP_OK, secondRequest.contains(HTTP_OK)); + } + + @Test + public void BadMethodTest() throws Exception { + String head = getTinyHttpdImplResponse("BADMETHOD", "/simpletest1.jnlp"); + + Assert.assertTrue("Status should have been " + HTTP_400, head.trim().equals(HTTP_400)); + } + + @Test + public void NotSupportingHeadRequest() throws Exception { + boolean headRequestSupport = mServer.isSupportingHeadRequest(); + mServer.setSupportingHeadRequest(false); + String head = getTinyHttpdImplResponse("HEAD", "/simpletest1.jnlp"); + + Assert.assertTrue("Status should have been " + HTTP_501, head.trim().equals(HTTP_501)); + + mServer.setSupportingHeadRequest(headRequestSupport); + } + + private String getTinyHttpdImplResponse(String requestType, String filePath) throws IOException, InterruptedException { + if (!filePath.startsWith("/")) { + filePath = "/" + filePath; + } + mWriter.writeBytes(requestType + " " + filePath + " HTTP/1.1\r\n"); + Thread.sleep(250); // Wait a while for server to be able to respond to request + + StringBuilder builder = new StringBuilder(); + while (mReader.ready()) { + // TODO: come up with a better way to deal with slow sending - this works but is hackish + if (filePath.startsWith("/XslowX")) { + Thread.sleep(2100); // Wait for next chunk to have been sent, otherwise it'll appear as if the response + // has finished being sent prematurely + } + builder.append(mReader.readLine()); + builder.append("\n"); + } + + return builder.toString(); + } + +} diff --git a/tests/test-extensions/net/sourceforge/jnlp/ServerLauncher.java b/tests/test-extensions/net/sourceforge/jnlp/ServerLauncher.java index b580bb1..08fa757 100644 --- a/tests/test-extensions/net/sourceforge/jnlp/ServerLauncher.java +++ b/tests/test-extensions/net/sourceforge/jnlp/ServerLauncher.java @@ -113,7 +113,7 @@ public class ServerLauncher implements Runnable { try { serverSocket = new ServerSocket(port); while (running) { - TinyHttpdImpl server = new TinyHttpdImpl(serverSocket.accept(), dir, port,false); + TinyHttpdImpl server = new TinyHttpdImpl(serverSocket.accept(), dir, false); server.setSupportingHeadRequest(isSupportingHeadRequest()); server.start(); } diff --git a/tests/test-extensions/net/sourceforge/jnlp/TinyHttpdImpl.java b/tests/test-extensions/net/sourceforge/jnlp/TinyHttpdImpl.java index 035b701..375019e 100644 --- a/tests/test-extensions/net/sourceforge/jnlp/TinyHttpdImpl.java +++ b/tests/test-extensions/net/sourceforge/jnlp/TinyHttpdImpl.java @@ -59,21 +59,26 @@ import java.util.StringTokenizer; */ public class TinyHttpdImpl extends Thread { - Socket c; - private final File dir; - private final int port; - private boolean canRun = true; + private static final String CRLF = "\r\n"; + private static final String HTTP_BAD_REQUEST = "HTTP/1.0 " + HttpURLConnection.HTTP_BAD_REQUEST + " Bad Request" + CRLF; + private static final String HTTP_NOT_IMPLEMENTED = "HTTP/1.0 " + HttpURLConnection.HTTP_NOT_IMPLEMENTED + " Not Implemented" + CRLF; + private static final String HTTP_NOT_FOUND = "HTTP/1.0 " + HttpURLConnection.HTTP_NOT_FOUND + " Not Found" + CRLF; + private static final String HTTP_OK = "HTTP/1.0 " + HttpURLConnection.HTTP_OK + " OK" + CRLF; private static final String XSX = "/XslowX"; + + private Socket socket; + private final File testDir; + private boolean canRun = true; private boolean supportingHeadRequest = true; - - public TinyHttpdImpl(Socket s, File f, int port) { - this(s, f, port, true); + + public TinyHttpdImpl(Socket socket, File dir) { + this(socket, dir, true); } - public TinyHttpdImpl(Socket s, File f, int port, boolean start) { - c = s; - this.dir = f; - this.port = port; - if (start){ + + public TinyHttpdImpl(Socket socket, File dir, boolean start) { + this.socket = socket; + this.testDir = dir; + if (start) { start(); } } @@ -87,92 +92,100 @@ public class TinyHttpdImpl extends Thread { } public boolean isSupportingHeadRequest() { - return supportingHeadRequest; + return this.supportingHeadRequest; } - - public int getPort() { - return port; + return this.socket.getPort(); } @Override public void run() { try { - BufferedReader i = new BufferedReader(new InputStreamReader(c.getInputStream())); - DataOutputStream o = new DataOutputStream(c.getOutputStream()); + BufferedReader reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); + DataOutputStream writer = new DataOutputStream(this.socket.getOutputStream()); try { while (canRun) { - String s = i.readLine(); - if (s.length() < 1) { + String line = reader.readLine(); + if (line.length() < 1) { break; } - boolean isGetRequest = s.startsWith("GET"); - boolean isHeadRequest = s.startsWith("HEAD"); - - if (isHeadRequest && !isSupportingHeadRequest()){ - o.writeBytes("HTTP/1.0 "+HttpURLConnection.HTTP_NOT_IMPLEMENTED+" Not Implemented\n"); + StringTokenizer t = new StringTokenizer(line, " "); + String request = t.nextToken(); + + boolean isHeadRequest = request.equals("HEAD"); + boolean isGetRequest = request.equals("GET"); + + if (isHeadRequest && !isSupportingHeadRequest()) { + ServerAccess.logOutputReprint("Received HEAD request but not supported"); + writer.writeBytes(HTTP_NOT_IMPLEMENTED); continue; } - - String request = "unknown"; - if (isGetRequest || isHeadRequest ) { - if (isGetRequest){ - request = "GET"; - } - if (isHeadRequest){ - request = "HEAD"; - } - StringTokenizer t = new StringTokenizer(s, " "); - t.nextToken(); - String op = t.nextToken(); - String p = op; - if (p.startsWith(XSX)) { - p = p.replace(XSX, "/"); - } - ServerAccess.logOutputReprint("Getting- " + request + ": " + p); - p = urlToFilePath(p); - ServerAccess.logOutputReprint("Serving- " + request + ": " + p); - File pp = new File(dir, p); - int l = (int) pp.length(); - byte[] b = new byte[l]; - FileInputStream f = new FileInputStream(pp); - f.read(b); - String content = ""; - String ct = "Content-Type: "; - if (p.toLowerCase().endsWith(".jnlp")) { - content = ct + "application/x-java-jnlp-file\n"; - } else if (p.toLowerCase().endsWith(".html")) { - content = ct + "text/html\n"; - } else if (p.toLowerCase().endsWith(".jar")) { - content = ct + "application/x-jar\n"; - } - o.writeBytes("HTTP/1.0 "+HttpURLConnection.HTTP_OK+" OK\nContent-Length:" + l + "\n" + content + "\n"); - if (isHeadRequest) { - continue; // Skip sending body - } + if (!isHeadRequest && !isGetRequest) { + ServerAccess.logOutputReprint("Received unknown request type " + request); + writer.writeBytes(HTTP_BAD_REQUEST); + continue; + } - if (op.startsWith(XSX)) { - byte[][] bb = splitArray(b, 10); + String filePath = t.nextToken(); + boolean slowSend = filePath.startsWith(XSX); + + if (slowSend) { + filePath = filePath.replace(XSX, "/"); + } + + ServerAccess.logOutputReprint("Getting- " + request + ": " + filePath); + filePath = urlToFilePath(filePath); + + File resource = new File(this.testDir, filePath); + + if (!(resource.isFile() && resource.canRead())) { + ServerAccess.logOutputReprint("Could not open file " + filePath); + writer.writeBytes(HTTP_NOT_FOUND); + continue; + } + ServerAccess.logOutputReprint("Serving- " + request + ": " + filePath); + + int resourceLength = (int) resource.length(); + byte[] buff = new byte[resourceLength]; + FileInputStream fis = new FileInputStream(resource); + fis.read(buff); + fis.close(); + + String contentType = "Content-Type: "; + if (filePath.toLowerCase().endsWith(".jnlp")) { + contentType += "application/x-java-jnlp-file"; + } else if (filePath.toLowerCase().endsWith(".jar")) { + contentType += "application/x-jar"; + } else { + contentType += "text/html"; + } + writer.writeBytes(HTTP_OK + "Content-Length:" + resourceLength + CRLF + contentType + CRLF + CRLF); + + if (isGetRequest) { + if (slowSend) { + byte[][] bb = splitArray(buff, 10); for (int j = 0; j < bb.length; j++) { Thread.sleep(2000); byte[] bs = bb[j]; - o.write(bs, 0, bs.length); + writer.write(bs, 0, bs.length); } } else { - o.write(b, 0, l); + writer.write(buff, 0, resourceLength); } } } } catch (SocketException e) { ServerAccess.logException(e, false); } catch (Exception e) { - o.writeBytes("HTTP/1.0 404 ERROR\n\n\n"); + writer.writeBytes(HTTP_NOT_FOUND); ServerAccess.logException(e, false); + } finally { + reader.close(); + writer.close(); } - o.close(); } catch (Exception e) { ServerAccess.logException(e, false); } @@ -209,7 +222,7 @@ public class TinyHttpdImpl extends Thread { } return array; } - + /** * This function transforms a request URL into a path to a file which the server * will return to the requester. @@ -218,7 +231,7 @@ public class TinyHttpdImpl extends Thread { * @throws UnsupportedEncodingException */ public static String urlToFilePath(String url) throws UnsupportedEncodingException { - url = URLDecoder.decode(url, "UTF-8"); // Decode URL encoded charaters, eg "%3B" b ecomes ';' + url = URLDecoder.decode(url, "UTF-8"); // Decode URL encoded charaters, eg "%3B" becomes ';' if (url.startsWith(XSX)) { url = url.replace(XSX, "/"); } @@ -227,7 +240,7 @@ public class TinyHttpdImpl extends Thread { if (url.endsWith("/")) { url += "index.html"; } - url = url.replace('/', File.separatorChar); // If running on Windows, replace '/' in path with "\\" + url = url.replace('/', File.separatorChar); // If running on Windows, replace '/' in path with "\\" url = stripHttpPathParams(url); return url; } @@ -239,19 +252,19 @@ public class TinyHttpdImpl extends Thread { * @return the URL with the path parameter removed */ public static String stripHttpPathParams(String url) { - if (url == null) { - return null; - } - - // If JNLP specifies JAR URL with .JAR extension (as it should), then look for any semicolons - // after this position. If one is found, remove it and any following characters. - int fileExtension = url.toUpperCase().lastIndexOf(".JAR"); - if (fileExtension != -1) { - int firstSemiColon = url.indexOf(';', fileExtension); - if (firstSemiColon != -1) { - url = url.substring(0, firstSemiColon); - } - } - return url; + if (url == null) { + return null; + } + + // If JNLP specifies JAR URL with .JAR extension (as it should), then look for any semicolons + // after this position. If one is found, remove it and any following characters. + int fileExtension = url.toUpperCase().lastIndexOf(".JAR"); + if (fileExtension != -1) { + int firstSemiColon = url.indexOf(';', fileExtension); + if (firstSemiColon != -1) { + url = url.substring(0, firstSemiColon); + } + } + return url; } } |