1 /*
   2  * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * @test
  28  * @bug 8163561
  29  * @modules java.base/sun.net.www
  30  *          jdk.incubator.httpclient
  31  * @summary Verify that Proxy-Authenticate header is correctly handled
  32  * @run main/othervm ProxyAuthTest
  33  */
  34 
  35 import java.io.BufferedWriter;
  36 import java.io.IOException;
  37 import java.io.InputStream;
  38 import java.io.OutputStream;
  39 import java.io.OutputStreamWriter;
  40 import java.io.PrintWriter;
  41 import java.net.Authenticator;
  42 import java.net.InetSocketAddress;
  43 import java.net.PasswordAuthentication;
  44 import java.net.Proxy;
  45 import java.net.ProxySelector;
  46 import java.net.ServerSocket;
  47 import java.net.Socket;
  48 import java.net.SocketAddress;
  49 import java.net.URI;
  50 import jdk.incubator.http.HttpClient;
  51 import jdk.incubator.http.HttpRequest;
  52 import jdk.incubator.http.HttpResponse;
  53 import java.util.Base64;
  54 import java.util.List;
  55 import sun.net.www.MessageHeader;
  56 import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
  57 
  58 public class ProxyAuthTest {
  59     private static final String AUTH_USER = "user";
  60     private static final String AUTH_PASSWORD = "password";
  61 
  62     public static void main(String[] args) throws Exception {
  63         try (ServerSocket ss = new ServerSocket(0)) {
  64             int port = ss.getLocalPort();
  65             MyProxy proxy = new MyProxy(ss);
  66             (new Thread(proxy)).start();
  67             System.out.println("Proxy listening port " + port);
  68 
  69             Auth auth = new Auth();
  70             InetSocketAddress paddr = new InetSocketAddress("localhost", port);
  71 
  72             URI uri = new URI("http://www.google.ie/");
  73             CountingProxySelector ps = CountingProxySelector.of(paddr);
  74             HttpClient client = HttpClient.newBuilder()
  75                                           .proxy(ps)
  76                                           .authenticator(auth)
  77                                           .build();
  78             HttpRequest req = HttpRequest.newBuilder(uri).GET().build();
  79             HttpResponse<?> resp = client.sendAsync(req, discard(null)).get();
  80             if (resp.statusCode() != 404) {
  81                 throw new RuntimeException("Unexpected status code: " + resp.statusCode());
  82             }
  83 
  84             if (auth.isCalled) {
  85                 System.out.println("Authenticator is called");
  86             } else {
  87                 throw new RuntimeException("Authenticator is not called");
  88             }
  89 
  90             if (!proxy.matched) {
  91                 throw new RuntimeException("Proxy authentication failed");
  92             }
  93             if (ps.count() > 1) {
  94                 throw new RuntimeException("CountingProxySelector. Expected 1, got " + ps.count());
  95             }
  96         }
  97     }
  98 
  99     static class Auth extends Authenticator {
 100         private volatile boolean isCalled;
 101 
 102         @Override
 103         protected PasswordAuthentication getPasswordAuthentication() {
 104             System.out.println("scheme: " + this.getRequestingScheme());
 105             isCalled = true;
 106             return new PasswordAuthentication(AUTH_USER,
 107                     AUTH_PASSWORD.toCharArray());
 108         }
 109     }
 110 
 111     /**
 112      * A Proxy Selector that wraps a ProxySelector.of(), and counts the number
 113      * of times its select method has been invoked. This can be used to ensure
 114      * that the Proxy Selector is invoked only once per HttpClient.sendXXX
 115      * invocation.
 116      */
 117     static class CountingProxySelector extends ProxySelector {
 118         private final ProxySelector proxySelector;
 119         private volatile int count; // 0
 120         private CountingProxySelector(InetSocketAddress proxyAddress) {
 121             proxySelector = ProxySelector.of(proxyAddress);
 122         }
 123 
 124         public static CountingProxySelector of(InetSocketAddress proxyAddress) {
 125             return new CountingProxySelector(proxyAddress);
 126         }
 127 
 128         int count() { return count; }
 129 
 130         @Override
 131         public List<Proxy> select(URI uri) {
 132             count++;
 133             return proxySelector.select(uri);
 134         }
 135 
 136         @Override
 137         public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
 138             proxySelector.connectFailed(uri, sa, ioe);
 139         }
 140     }
 141 
 142     static class MyProxy implements Runnable {
 143         final ServerSocket ss;
 144         private volatile boolean matched;
 145 
 146         MyProxy(ServerSocket ss) {
 147             this.ss = ss;
 148         }
 149 
 150         public void run() {
 151             for (int i = 0; i < 2; i++) {
 152                 try (Socket s = ss.accept();
 153                      InputStream in = s.getInputStream();
 154                      OutputStream os = s.getOutputStream();
 155                      BufferedWriter writer = new BufferedWriter(
 156                              new OutputStreamWriter(os));
 157                      PrintWriter out = new PrintWriter(writer);) {
 158                     MessageHeader headers = new MessageHeader(in);
 159                     System.out.println("Proxy: received " + headers);
 160 
 161                     String authInfo = headers.findValue("Proxy-Authorization");
 162                     if (authInfo != null) {
 163                         authenticate(authInfo);
 164                         out.print("HTTP/1.1 404 Not found\r\n");
 165                         out.print("\r\n");
 166                         System.out.println("Proxy: 404");
 167                         out.flush();
 168                     } else {
 169                         out.print("HTTP/1.1 407 Proxy Authorization Required\r\n");
 170                         out.print(
 171                                 "Proxy-Authenticate: Basic realm=\"a fake realm\"\r\n");
 172                         out.print("\r\n");
 173                         System.out.println("Proxy: Authorization required");
 174                         out.flush();
 175                     }
 176                 } catch (IOException x) {
 177                     System.err.println("Unexpected IOException from proxy.");
 178                     x.printStackTrace();
 179                     break;
 180                 }
 181             }
 182         }
 183 
 184         private void authenticate(String authInfo) throws IOException {
 185             try {
 186                 authInfo.trim();
 187                 int ind = authInfo.indexOf(' ');
 188                 String recvdUserPlusPass = authInfo.substring(ind + 1).trim();
 189                 // extract encoded username:passwd
 190                 String value = new String(
 191                         Base64.getMimeDecoder().decode(recvdUserPlusPass));
 192                 String userPlusPassword = AUTH_USER + ":" + AUTH_PASSWORD;
 193                 if (userPlusPassword.equals(value)) {
 194                     matched = true;
 195                     System.out.println("Proxy: client authentication successful");
 196                 } else {
 197                     System.err.println(
 198                             "Proxy: client authentication failed, expected ["
 199                                     + userPlusPassword + "], actual [" + value
 200                                     + "]");
 201                 }
 202             } catch (Exception e) {
 203                 throw new IOException(
 204                         "Proxy received invalid Proxy-Authorization value: "
 205                                 + authInfo);
 206             }
 207         }
 208     }
 209 
 210 }