View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.auth;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.io.ObjectInputStream;
33  import java.io.ObjectOutputStream;
34  import java.io.Serializable;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Objects;
38  import java.util.concurrent.ConcurrentHashMap;
39  
40  import org.apache.hc.client5.http.SchemePortResolver;
41  import org.apache.hc.client5.http.auth.AuthCache;
42  import org.apache.hc.client5.http.auth.AuthScheme;
43  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
44  import org.apache.hc.core5.annotation.Contract;
45  import org.apache.hc.core5.annotation.ThreadingBehavior;
46  import org.apache.hc.core5.http.HttpHost;
47  import org.apache.hc.core5.net.NamedEndpoint;
48  import org.apache.hc.core5.util.Args;
49  import org.apache.hc.core5.util.LangUtils;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  /**
54   * Default implementation of {@link AuthCache}. This implements
55   * expects {@link org.apache.hc.client5.http.auth.AuthScheme} to be {@link java.io.Serializable}
56   * in order to be cacheable.
57   * <p>
58   * Instances of this class are thread safe as of version 4.4.
59   * </p>
60   *
61   * @since 4.1
62   */
63  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
64  public class BasicAuthCache implements AuthCache {
65  
66      private static final Logger LOG = LoggerFactory.getLogger(BasicAuthCache.class);
67  
68      static class Key {
69  
70          final String scheme;
71          final String host;
72          final int port;
73          final String pathPrefix;
74  
75          Key(final String scheme, final String host, final int port, final String pathPrefix) {
76              Args.notBlank(scheme, "Scheme");
77              Args.notBlank(host, "Scheme");
78              this.scheme = scheme.toLowerCase(Locale.ROOT);
79              this.host = host.toLowerCase(Locale.ROOT);
80              this.port = port;
81              this.pathPrefix = pathPrefix;
82          }
83  
84          @Override
85          public boolean equals(final Object obj) {
86              if (this == obj) {
87                  return true;
88              }
89              if (obj instanceof Key) {
90                  final Key that = (Key) obj;
91                  return this.scheme.equals(that.scheme) &&
92                          this.host.equals(that.host) &&
93                          this.port == that.port &&
94                          Objects.equals(this.pathPrefix, that.pathPrefix);
95              }
96              return false;
97          }
98  
99          @Override
100         public int hashCode() {
101             int hash = LangUtils.HASH_SEED;
102             hash = LangUtils.hashCode(hash, this.scheme);
103             hash = LangUtils.hashCode(hash, this.host);
104             hash = LangUtils.hashCode(hash, this.port);
105             hash = LangUtils.hashCode(hash, this.pathPrefix);
106             return hash;
107         }
108 
109         @Override
110         public String toString() {
111             final StringBuilder buf = new StringBuilder();
112             buf.append(scheme).append("://").append(host);
113             if (port >= 0) {
114                 buf.append(":").append(port);
115             }
116             if (pathPrefix != null) {
117                 if (!pathPrefix.startsWith("/")) {
118                     buf.append("/");
119                 }
120                 buf.append(pathPrefix);
121             }
122             return buf.toString();
123         }
124     }
125 
126     private final Map<Key, byte[]> map;
127     private final SchemePortResolver schemePortResolver;
128 
129     /**
130      * Default constructor.
131      *
132      * @since 4.3
133      */
134     public BasicAuthCache(final SchemePortResolver schemePortResolver) {
135         super();
136         this.map = new ConcurrentHashMap<>();
137         this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
138     }
139 
140     public BasicAuthCache() {
141         this(null);
142     }
143 
144     private Key key(final String scheme, final NamedEndpoint authority, final String pathPrefix) {
145         return new Key(scheme, authority.getHostName(), schemePortResolver.resolve(scheme, authority), pathPrefix);
146     }
147 
148     @Override
149     public void put(final HttpHost host, final AuthScheme authScheme) {
150         put(host, null, authScheme);
151     }
152 
153     @Override
154     public AuthScheme get(final HttpHost host) {
155         return get(host, null);
156     }
157 
158     @Override
159     public void remove(final HttpHost host) {
160         remove(host, null);
161     }
162 
163     @Override
164     public void put(final HttpHost host, final String pathPrefix, final AuthScheme authScheme) {
165         Args.notNull(host, "HTTP host");
166         if (authScheme == null) {
167             return;
168         }
169         if (authScheme instanceof Serializable) {
170             try {
171                 final ByteArrayOutputStream buf = new ByteArrayOutputStream();
172                 try (final ObjectOutputStream out = new ObjectOutputStream(buf)) {
173                     out.writeObject(authScheme);
174                 }
175                 this.map.put(key(host.getSchemeName(), host, pathPrefix), buf.toByteArray());
176             } catch (final IOException ex) {
177                 if (LOG.isWarnEnabled()) {
178                     LOG.warn("Unexpected I/O error while serializing auth scheme", ex);
179                 }
180             }
181         } else {
182             if (LOG.isDebugEnabled()) {
183                 LOG.debug("Auth scheme {} is not serializable", authScheme.getClass());
184             }
185         }
186     }
187 
188     @Override
189     public AuthScheme get(final HttpHost host, final String pathPrefix) {
190         Args.notNull(host, "HTTP host");
191         final byte[] bytes = this.map.get(key(host.getSchemeName(), host, pathPrefix));
192         if (bytes != null) {
193             try {
194                 final ByteArrayInputStream buf = new ByteArrayInputStream(bytes);
195                 try (final ObjectInputStream in = new ObjectInputStream(buf)) {
196                     return (AuthScheme) in.readObject();
197                 }
198             } catch (final IOException ex) {
199                 if (LOG.isWarnEnabled()) {
200                     LOG.warn("Unexpected I/O error while de-serializing auth scheme", ex);
201                 }
202             } catch (final ClassNotFoundException ex) {
203                 if (LOG.isWarnEnabled()) {
204                     LOG.warn("Unexpected error while de-serializing auth scheme", ex);
205                 }
206             }
207         }
208         return null;
209     }
210 
211     @Override
212     public void remove(final HttpHost host, final String pathPrefix) {
213         Args.notNull(host, "HTTP host");
214         this.map.remove(key(host.getSchemeName(), host, pathPrefix));
215     }
216 
217     @Override
218     public void clear() {
219         this.map.clear();
220     }
221 
222     @Override
223     public String toString() {
224         return this.map.toString();
225     }
226 
227 }