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          if (LOGGER.isDebugEnabled()) {
70              LOGGER.debug(" doHandshake()");
71          }
72  
73          if (authHandler != null) {
74              authHandler.doHandshake(nextFilter);
75          } else {
76              if (requestSent) {
77                  // Safety check
78                  throw new ProxyAuthException("Authentication request already sent");
79              }
80  
81              if (LOGGER.isDebugEnabled()) {
82                  LOGGER.debug("  sending HTTP request");
83              }
84  
85              // Compute request headers
86              HttpProxyRequest./../../../org/apache/mina/proxy/handlers/http/HttpProxyRequest.html#HttpProxyRequest">HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession().getRequest();
87              Map<String, List<String>> headers = req.getHeaders() != null ? req.getHeaders()
88                      : new HashMap<String, List<String>>();
89  
90              AbstractAuthLogicHandler.addKeepAliveHeaders(headers);
91              req.setHeaders(headers);
92  
93              // Write request to the proxy
94              writeRequest(nextFilter, req);
95              requestSent = true;
96          }
97      }
98  
99      /**
100      * Automatic selection of the authentication algorithm. If <code>preferedOrder</code> is set then
101      * algorithms are selected from the list order otherwise the algorithm tries to select the most 
102      * secured algorithm available first.
103      * 
104      * @param response the proxy response
105      */
106     private void autoSelectAuthHandler(final HttpProxyResponse response) throws ProxyAuthException {
107         // Get the Proxy-Authenticate header
108         List<String> values = response.getHeaders().get("Proxy-Authenticate");
109         ProxyIoSession proxyIoSession = getProxyIoSession();
110 
111         if (values == null || values.isEmpty()) {
112             authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
113 
114         } else if (getProxyIoSession().getPreferedOrder() == null) {
115             // No preference order set for auth mechanisms
116             int method = -1;
117 
118             // Test which auth mechanism to use. First found is the first used
119             // that's why we test in a decreasing security quality order.
120             for (String proxyAuthHeader : values) {
121                 proxyAuthHeader = proxyAuthHeader.toLowerCase();
122 
123                 if (proxyAuthHeader.contains("ntlm")) {
124                     method = HttpAuthenticationMethods.NTLM.getId();
125                     break;
126                 } else if (proxyAuthHeader.contains("digest") && method != HttpAuthenticationMethods.NTLM.getId()) {
127                     method = HttpAuthenticationMethods.DIGEST.getId();
128                 } else if (proxyAuthHeader.contains("basic") && method == -1) {
129                     method = HttpAuthenticationMethods.BASIC.getId();
130                 }
131             }
132 
133             if (method != -1) {
134                 try {
135                     authHandler = HttpAuthenticationMethods.getNewHandler(method, proxyIoSession);
136                 } catch (Exception ex) {
137                     if (LOGGER.isDebugEnabled()) {
138                         LOGGER.debug("Following exception occured:", ex);
139                     }
140                 }
141             }
142 
143             if (authHandler == null) {
144                 authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
145             }
146 
147         } else {
148             for (HttpAuthenticationMethods method : proxyIoSession.getPreferedOrder()) {
149                 if (authHandler != null) {
150                     break;
151                 }
152 
153                 if (method == HttpAuthenticationMethods.NO_AUTH) {
154                     authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession);
155                     break;
156                 }
157 
158                 for (String proxyAuthHeader : values) {
159                     proxyAuthHeader = proxyAuthHeader.toLowerCase();
160 
161                     try {
162                         // test which auth mechanism to use
163                         if (proxyAuthHeader.contains("basic") && method == HttpAuthenticationMethods.BASIC) {
164                             authHandler = HttpAuthenticationMethods.BASIC.getNewHandler(proxyIoSession);
165                             break;
166                         } else if (proxyAuthHeader.contains("digest") && method == HttpAuthenticationMethods.DIGEST) {
167                             authHandler = HttpAuthenticationMethods.DIGEST.getNewHandler(proxyIoSession);
168                             break;
169                         } else if (proxyAuthHeader.contains("ntlm") && method == HttpAuthenticationMethods.NTLM) {
170                             authHandler = HttpAuthenticationMethods.NTLM.getNewHandler(proxyIoSession);
171                             break;
172                         }
173                     } catch (Exception ex) {
174                         if (LOGGER.isDebugEnabled()) {
175                             LOGGER.debug("Following exception occured:", ex);
176                         }
177                     }
178                 }
179             }
180 
181         }
182 
183         if (authHandler == null) {
184             throw new ProxyAuthException("Unknown authentication mechanism(s): " + values);
185         }
186     }
187 
188     /**
189      * Handle a HTTP response from the proxy server.
190      * 
191      * @param response The proxy response.
192      */
193     @Override
194     public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException {
195         if (!isHandshakeComplete()
196                 && ("close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(response.getHeaders(),
197                         "Proxy-Connection")) || "close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(
198                         response.getHeaders(), "Connection")))) {
199             getProxyIoSession().setReconnectionNeeded(true);
200         }
201 
202         if (response.getStatusCode() == 407) {
203             if (authHandler == null) {
204                 autoSelectAuthHandler(response);
205             }
206             authHandler.handleResponse(response);
207         } else {
208             throw new ProxyAuthException("Error: unexpected response code " + response.getStatusLine()
209                     + " received from proxy.");
210         }
211     }
212 }