package jp.co.worksap.workspace.common.download; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URI; import java.net.UnknownHostException; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.Cleanup; import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.apache.http.Header; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpServerConnection; import org.apache.http.HttpStatus; import org.apache.http.impl.DefaultBHttpServerConnectionFactory; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.protocol.HttpProcessorBuilder; import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.protocol.HttpService; import org.apache.http.protocol.ResponseConnControl; import org.apache.http.protocol.ResponseContent; import org.apache.http.protocol.ResponseDate; import org.apache.http.protocol.ResponseServer; import org.apache.http.protocol.UriHttpRequestHandlerMapper; import org.junit.Test; import com.google.common.base.Charsets; import com.google.common.io.Closer; public class HttpDownloaderTest { private static final String EXPECTED_USER = "user"; private static final String EXPECTED_PASSWORD = "password"; private static final String WRONG_PASSWORD = "pazzword"; private static final String REALM = "realm"; private static final String NONCE = "nonce"; @Test public void testDownloadSuccessfullyViaBasicAuth() throws IOException { AuthenticationInfoProvider infoProvider = new ConstInfoProvider(EXPECTED_USER, EXPECTED_PASSWORD); @Cleanup Server server = runServer(10000, new BasicRequestHandler()); File tempFile = File.createTempFile("HttpDownloaderTest", ".txt"); tempFile.delete(); new HttpDownloader(infoProvider).download(server.getUri(), tempFile); assertThat(tempFile.exists(), is(true)); } @Test public void testDownloadSuccessfullyViaDigestAuth() throws IOException { AuthenticationInfoProvider infoProvider = new ConstInfoProvider(EXPECTED_USER, EXPECTED_PASSWORD); @Cleanup Server server = runServer(10001, new DigestRequestHandler()); File tempFile = File.createTempFile("HttpDownloaderTest", ".txt"); tempFile.delete(); new HttpDownloader(infoProvider).download(server.getUri(), tempFile); assertThat(tempFile.exists(), is(true)); } @Test(expected = IllegalArgumentException.class) public void testDownloadWithWrongPasswordViaBasicAuth() throws IOException { AuthenticationInfoProvider infoProvider = new ConstInfoProvider(EXPECTED_USER, WRONG_PASSWORD); @Cleanup Server server = runServer(10002, new BasicRequestHandler()); File tempFile = File.createTempFile("HttpDownloaderTest", ".txt"); tempFile.delete(); new HttpDownloader(infoProvider).download(server.getUri(), tempFile); assertThat(tempFile.exists(), is(false)); } @Test(expected = IllegalArgumentException.class) public void testDownloadWithWrongPasswordViaDigestAuth() throws IOException { AuthenticationInfoProvider infoProvider = new ConstInfoProvider(EXPECTED_USER, WRONG_PASSWORD); @Cleanup Server server = runServer(10003, new DigestRequestHandler()); File tempFile = File.createTempFile("HttpDownloaderTest", ".txt"); tempFile.delete(); new HttpDownloader(infoProvider).download(server.getUri(), tempFile); assertThat(tempFile.exists(), is(false)); } /** * @param port TCP port number to listen. Please change this value when you execute this method continuously, it is good to avoid port conflict. * @see http://hc.apache.org/httpcomponents-core-4.3.x/httpcore/examples/org/apache/http/examples/ElementalHttpServer.java */ private Server runServer(int port, HttpRequestHandler handler) throws UnknownHostException { HttpProcessor httpproc = HttpProcessorBuilder.create() .add(new ResponseDate()) .add(new ResponseServer("HttpDownloaderTest/1.0")) .add(new ResponseContent()) .add(new ResponseConnControl()).build(); UriHttpRequestHandlerMapper reqistry = new UriHttpRequestHandlerMapper(); reqistry.register("*", handler); HttpService service = new HttpService(httpproc, reqistry); RequestListener listener = new RequestListener(service, port); Thread thread = new Thread(listener); thread.setName("Server to test HttpDownloader"); thread.setDaemon(true); thread.start(); return new Server(URI.create("http://" + InetAddress.getLocalHost().getHostAddress() + ":" + port + "/"), listener); } private static final class BasicRequestHandler implements HttpRequestHandler { @Override public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { Header auth = request.getFirstHeader("Authorization"); if (auth == null) { response.setHeader(new BasicHeader("WWW-Authenticate", "Basic realm=\"" + REALM + "\"")); response.setStatusCode(HttpStatus.SC_UNAUTHORIZED); return; } String value = auth.getValue(); if (! value.startsWith("Basic ")) { response.setStatusCode(HttpStatus.SC_BAD_REQUEST); return; } String encodedUserAndPass = value.substring("Basic ".length()); byte[] userAndPass = Base64.decodeBase64(encodedUserAndPass.getBytes(Charsets.UTF_8)); if (new String(userAndPass, Charsets.UTF_8).equals(EXPECTED_USER + ":" + EXPECTED_PASSWORD)) { response.setStatusCode(HttpStatus.SC_OK); } else { response.setHeader(new BasicHeader("WWW-Authenticate", "Basic realm=\"" + REALM + "\"")); response.setStatusCode(HttpStatus.SC_UNAUTHORIZED); } } } private static final class DigestRequestHandler implements HttpRequestHandler { @Override public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { Header auth = request.getFirstHeader("Authorization"); if (auth == null) { response.setHeader(new BasicHeader("WWW-Authenticate", "Digest realm=\"" + REALM + "\", nonce=\"" + NONCE + "\"")); response.setStatusCode(HttpStatus.SC_UNAUTHORIZED); return; } String value = auth.getValue(); if (! value.startsWith("Digest ")) { response.setStatusCode(HttpStatus.SC_BAD_REQUEST); return; } String encodedUserAndPass = value.substring("Digest ".length()); if (encodedUserAndPass.hashCode() == -2062858579) { // it is troublesome to emulate encoding algorithm at here, we just check hashCode instead. response.setStatusCode(HttpStatus.SC_OK); } else { response.setHeader(new BasicHeader("WWW-Authenticate", "Digest realm=\"" + REALM + "\", nonce=\"" + NONCE + "\"")); response.setStatusCode(HttpStatus.SC_UNAUTHORIZED); } } } @AllArgsConstructor @Slf4j private static class RequestListener implements Runnable { @Nonnull private final HttpService service; @Nonnull private final int port; @Nonnull private final AtomicBoolean running = new AtomicBoolean(true); @Override public void run() { try { @Cleanup ServerSocket serverSocket = new ServerSocket(port, 10, InetAddress.getLocalHost()); log.info("Server is listening on: {}", serverSocket.getInetAddress().toString()); DefaultBHttpServerConnectionFactory connectionFactory = DefaultBHttpServerConnectionFactory.INSTANCE; while (running.get() && !Thread.interrupted()) { Closer closer = Closer.create(); try { Socket socket = closer.register(serverSocket.accept()); HttpServerConnection connection = closer.register(connectionFactory.createConnection(socket)); HttpContext context = new BasicHttpContext(null); service.handleRequest(connection, context); } finally { closer.close(); } } } catch (IOException | HttpException e) { throw new RuntimeException(e); } } public void stop() { running.set(false); } } @Value private static final class Server implements Closeable { @Nonnull private final URI uri; @Nonnull private final RequestListener listener; @Override public void close() { listener.stop(); } } }