View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.proxy.handlers.http;
21  
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.mina.core.filterchain.IoFilter.NextFilter;
27  import org.apache.mina.proxy.ProxyAuthException;
28  import org.apache.mina.proxy.session.ProxyIoSession;
29  import org.apache.mina.proxy.utils.StringUtilities;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * HttpSmartProxyHandler.java - HTTP proxy handler that automatically handles forwarding a request 
35   * to the appropriate authentication mechanism logic handler.
36   * 
37   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
38   * @since MINA 2.0.0-M3
39   */
40  public class HttpSmartProxyHandler extends AbstractHttpLogicHandler {
41      private static final Logger logger = LoggerFactory.getLogger(HttpSmartProxyHandler.class);
42  
43      /**
44       * Has the HTTP proxy request been sent ?
45       */
46      private boolean requestSent = false;
47  
48      /**
49       * The automatically selected http authentication logic handler. 
50       */
51      private AbstractAuthLogicHandler authHandler;
52  
53      /**
54       * Creates a new HttpSmartProxyHandler instance
55       * 
56       * @param proxyIoSession The Prowxy IoSession
57       */
58      public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) {
59          super(proxyIoSession);
60      }
61  
62      /**
63       * Performs the handshake processing.
64       * 
65       * @param nextFilter the next filter
66       */
67      @Override
68      public void doHandshake(final NextFilter nextFilter) throws ProxyAuthException {
69          logger.debug(" doHandshake()");
70  
71          if (authHandler != null) {
72              authHandler.doHandshake(nextFilter);
73          } else {
74              if (requestSent) {
75                  // Safety check
76                  throw new ProxyAuthException("Authentication request already sent");
77              }
78  
79              logger.debug("  sending HTTP request");
80  
81              // Compute request headers
82              HttpProxyRequest./../../../org/apache/mina/proxy/handlers/http/HttpProxyRequest.html#HttpProxyRequest">HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession().getRequest();
83              Map<String, List<String>> headers = req.getHeaders() != null ? req.getHeaders()
84                      : new HashMap<String, List<String>>();
85  
86              AbstractAuthLogicHandler.addKeepAliveHeaders(headers);
87              req.setHeaders(headers);
88  
89              // Write request to the proxy
90              writeRequest(nextFilter, req);
91              requestSent = true;
92          }
93      }
94  
95      /**
96       * Automatic selection of the authentication algorithm. If <code>preferedOrder</code> is set then
97       * algorithms are selected from the list order otherwise the algorithm tries to select the most 
98       * secured algorithm available first.
99       * 
100      * @param response the proxy response
101      */
102     private void autoSelectAuthHandler(final HttpProxyResponse response) throws ProxyAuthException {
103         // Get the Proxy-Authenticate header
104         List<String> values = response.getHeaders().get("Proxy-Authenticate");
105         ProxyIoSession proxyIoSession = getProxyIoSession();
106 
107         if (values == null || values.isEmpty()) {
108             authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
109 
110         } else if (getProxyIoSession().getPreferedOrder() == null) {
111             // No preference order set for auth mechanisms
112             int method = -1;
113 
114             // Test which auth mechanism to use. First found is the first used
115             // that's why we test in a decreasing security quality order.
116             for (String proxyAuthHeader : values) {
117                 proxyAuthHeader = proxyAuthHeader.toLowerCase();
118 
119                 if (proxyAuthHeader.contains("ntlm")) {
120                     method = HttpAuthenticationMethods.NTLM.getId();
121                     break;
122                 } else if (proxyAuthHeader.contains("digest") && method != HttpAuthenticationMethods.NTLM.getId()) {
123                     method = HttpAuthenticationMethods.DIGEST.getId();
124                 } else if (proxyAuthHeader.contains("basic") && method == -1) {
125                     method = HttpAuthenticationMethods.BASIC.getId();
126                 }
127             }
128 
129             if (method != -1) {
130                 try {
131                     authHandler = HttpAuthenticationMethods.getNewHandler(method, proxyIoSession);
132                 } catch (Exception ex) {
133                     logger.debug("Following exception occured:", ex);
134                 }
135             }
136 
137             if (authHandler == null) {
138                 authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
139             }
140 
141         } else {
142             for (HttpAuthenticationMethods method : proxyIoSession.getPreferedOrder()) {
143                 if (authHandler != null) {
144                     break;
145                 }
146 
147                 if (method == HttpAuthenticationMethods.NO_AUTH) {
148                     authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
149                     break;
150                 }
151 
152                 for (String proxyAuthHeader : values) {
153                     proxyAuthHeader = proxyAuthHeader.toLowerCase();
154 
155                     try {
156                         // test which auth mechanism to use
157                         if (proxyAuthHeader.contains("basic") && method == HttpAuthenticationMethods.BASIC) {
158                             authHandler = HttpAuthenticationMethods.BASIC.getNewHandler(proxyIoSession);
159                             break;
160                         } else if (proxyAuthHeader.contains("digest") && method == HttpAuthenticationMethods.DIGEST) {
161                             authHandler = HttpAuthenticationMethods.DIGEST.getNewHandler(proxyIoSession);
162                             break;
163                         } else if (proxyAuthHeader.contains("ntlm") && method == HttpAuthenticationMethods.NTLM) {
164                             authHandler = HttpAuthenticationMethods.NTLM.getNewHandler(proxyIoSession);
165                             break;
166                         }
167                     } catch (Exception ex) {
168                         logger.debug("Following exception occured:", ex);
169                     }
170                 }
171             }
172 
173         }
174 
175         if (authHandler == null) {
176             throw new ProxyAuthException("Unknown authentication mechanism(s): " + values);
177         }
178     }
179 
180     /**
181      * Handle a HTTP response from the proxy server.
182      * 
183      * @param response The proxy response.
184      */
185     @Override
186     public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException {
187         if (!isHandshakeComplete()
188                 && ("close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(response.getHeaders(),
189                         "Proxy-Connection")) || "close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(
190                         response.getHeaders(), "Connection")))) {
191             getProxyIoSession().setReconnectionNeeded(true);
192         }
193 
194         if (response.getStatusCode() == 407) {
195             if (authHandler == null) {
196                 autoSelectAuthHandler(response);
197             }
198             authHandler.handleResponse(response);
199         } else {
200             throw new ProxyAuthException("Error: unexpected response code " + response.getStatusLine()
201                     + " received from proxy.");
202         }
203     }
204 }