• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.jsoup.helper;
2 
3 import org.jspecify.annotations.Nullable;
4 
5 import java.lang.reflect.Constructor;
6 import java.net.Authenticator;
7 import java.net.HttpURLConnection;
8 import java.net.PasswordAuthentication;
9 
10 /**
11  Handles per request Authenticator-based authentication. Loads the class `org.jsoup.helper.RequestAuthHandler` if
12  per-request Authenticators are supported (Java 9+), or installs a system-wide Authenticator that delegates to a request
13  ThreadLocal.
14  */
15 class AuthenticationHandler extends Authenticator {
16     static final int MaxAttempts = 5; // max authentication attempts per request. allows for multiple auths (e.g. proxy and server) in one request, but saves otherwise 20 requests if credentials are incorrect.
17     static AuthShim handler;
18 
19     static {
20         try {
21             //noinspection unchecked
22             Class<AuthShim> perRequestClass = (Class<AuthShim>) Class.forName("org.jsoup.helper.RequestAuthHandler");
23             Constructor<AuthShim> constructor = perRequestClass.getConstructor();
24             handler = constructor.newInstance();
25         } catch (ClassNotFoundException e) {
26             handler = new GlobalHandler();
27         } catch (Exception e) {
28             throw new IllegalStateException(e);
29         }
30     }
31 
32     @Nullable RequestAuthenticator auth;
33     int attemptCount = 0;
34 
AuthenticationHandler()35     AuthenticationHandler() {}
36 
AuthenticationHandler(RequestAuthenticator auth)37     AuthenticationHandler(RequestAuthenticator auth) {
38         this.auth = auth;
39     }
40 
41     /**
42      Authentication callback, called by HttpURLConnection - either as system-wide default (Java 8) or per HttpURLConnection (Java 9+)
43      * @return credentials, or null if not attempting to auth.
44      */
getPasswordAuthentication()45     @Nullable @Override public final PasswordAuthentication getPasswordAuthentication() {
46         AuthenticationHandler delegate = handler.get(this);
47         if (delegate == null) return null; // this request has no auth handler
48         delegate.attemptCount++;
49         // if the password returned fails, Java will repeatedly retry the request with a new password auth hit (because
50         // it may be an interactive prompt, and the user could eventually get it right). But in Jsoup's context, the
51         // auth will either be correct or not, so just abandon
52         if (delegate.attemptCount > MaxAttempts)
53             return null;
54         if (delegate.auth == null)
55             return null; // detached - would have been the Global Authenticator (not a delegate)
56 
57         RequestAuthenticator.Context ctx = new RequestAuthenticator.Context(
58             this.getRequestingURL(), this.getRequestorType(), this.getRequestingPrompt());
59         return delegate.auth.authenticate(ctx);
60     }
61 
62     interface AuthShim {
enable(RequestAuthenticator auth, HttpURLConnection con)63         void enable(RequestAuthenticator auth, HttpURLConnection con);
64 
remove()65         void remove();
66 
get(AuthenticationHandler helper)67         @Nullable AuthenticationHandler get(AuthenticationHandler helper);
68     }
69 
70     /**
71      On Java 8 we install a system-wide Authenticator, which pulls the delegating Auth from a ThreadLocal pool.
72      */
73     static class GlobalHandler implements AuthShim {
74         static ThreadLocal<AuthenticationHandler> authenticators = new ThreadLocal<>();
75         static {
Authenticator.setDefault(new AuthenticationHandler())76             Authenticator.setDefault(new AuthenticationHandler());
77         }
78 
enable(RequestAuthenticator auth, HttpURLConnection con)79         @Override public void enable(RequestAuthenticator auth, HttpURLConnection con) {
80             authenticators.set(new AuthenticationHandler(auth));
81         }
82 
remove()83         @Override public void remove() {
84             authenticators.remove();
85         }
86 
get(AuthenticationHandler helper)87         @Override public AuthenticationHandler get(AuthenticationHandler helper) {
88             return authenticators.get();
89         }
90     }
91 }
92