/* * @(#)AuthorizationModule.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; import java.net.ProtocolException; import java.util.Hashtable; /** * This module handles authentication requests. Authentication info is * preemptively sent if any suitable candidate info is available. If a * request returns with an appropriate status (401 or 407) then the * necessary info is sought from the AuthenticationInfo class. * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär */ class AuthorizationModule implements HTTPClientModule { /** This holds the current Proxy-Authorization-Info for each HTTPConnection */ private static Hashtable proxy_cntxt_list = new Hashtable(); /** a list of deferred authorization retries (used with Response.retryRequest()) */ private static Hashtable deferred_auth_list = new Hashtable(); /** counters for challenge and auth-info lists */ private int auth_lst_idx, prxy_lst_idx, auth_scm_idx, prxy_scm_idx; /** the last auth info sent, if any */ private AuthorizationInfo auth_sent; private AuthorizationInfo prxy_sent; /** is the info in auth_sent a preemtive guess or the result of a 4xx */ private boolean auth_from_4xx; private boolean prxy_from_4xx; /** guard against bugs on both our side and the server side */ private int num_tries; /** used for deferred authoriation retries */ private Request saved_req; private Response saved_resp; // Constructors /** * Initialize counters for challenge and auth-info lists. */ AuthorizationModule() { auth_lst_idx = 0; prxy_lst_idx = 0; auth_scm_idx = 0; prxy_scm_idx = 0; auth_sent = null; prxy_sent = null; auth_from_4xx = false; prxy_from_4xx = false; num_tries = 0; saved_req = null; saved_resp = null; } // Methods /** * Invoked by the HTTPClient. */ public int requestHandler(Request req, Response[] resp) throws IOException, AuthSchemeNotImplException { HTTPConnection con = req.getConnection(); AuthorizationHandler auth_handler = AuthorizationInfo.getAuthHandler(); AuthorizationInfo guess; NVPair[] hdrs = req.getHeaders(); int rem_idx = -1; // check for retries HttpOutputStream out = req.getStream(); if (out != null && deferred_auth_list.get(out) != null) { copyFrom((AuthorizationModule) deferred_auth_list.remove(out)); req.copyFrom(saved_req); Log.write(Log.AUTH, "AuthM: Handling deferred auth challenge"); handle_auth_challenge(req, saved_resp); if (auth_sent != null) Log.write(Log.AUTH, "AuthM: Sending request with " + "Authorization '" + auth_sent + "'"); else Log.write(Log.AUTH, "AuthM: Sending request with " + "Proxy-Authorization '" + prxy_sent + "'"); return REQ_RESTART; } // Preemptively send proxy authorization info Proxy: if (con.getProxyHost() != null && !prxy_from_4xx) { // first remove any Proxy-Auth header that still may be around for (int idx=0; idx= 0) { System.arraycopy(hdrs, rem_idx+1, hdrs, rem_idx, hdrs.length-rem_idx-1); hdrs = Util.resizeArray(hdrs, hdrs.length-1); req.setHeaders(hdrs); } // Preemptively send authorization info rem_idx = -1; Auth: if (!auth_from_4xx) { // first remove any Auth header that still may be around for (int idx=0; idx= 0) { System.arraycopy(hdrs, rem_idx+1, hdrs, rem_idx, hdrs.length-rem_idx-1); hdrs = Util.resizeArray(hdrs, hdrs.length-1); req.setHeaders(hdrs); } return REQ_CONTINUE; } /** * Invoked by the HTTPClient. */ public void responsePhase1Handler(Response resp, RoRequest req) throws IOException { /* If auth info successful update path list. Note: if we * preemptively sent auth info we don't actually know if * it was necessary. Therefore we don't update the path * list in this case; this prevents it from being * contaminated. If the info was necessary, then the next * time we access this resource we will again guess the * same info and send it. */ if (resp.getStatusCode() != 401 && resp.getStatusCode() != 407) { if (auth_sent != null && auth_from_4xx) { try { AuthorizationInfo.getAuthorization(auth_sent, req, resp, false).addPath(req.getRequestURI()); } catch (AuthSchemeNotImplException asnie) { /* shouldn't happen */ } } // reset guard if not an auth challenge num_tries = 0; } auth_from_4xx = false; prxy_from_4xx = false; if (resp.getHeader("WWW-Authenticate") == null) { auth_lst_idx = 0; auth_scm_idx = 0; } if (resp.getHeader("Proxy-Authenticate") == null) { prxy_lst_idx = 0; prxy_scm_idx = 0; } } /** * Invoked by the HTTPClient. */ public int responsePhase2Handler(Response resp, Request req) throws IOException, AuthSchemeNotImplException { // Let the AuthHandler handle any Authentication headers. AuthorizationHandler h = AuthorizationInfo.getAuthHandler(); if (h != null) h.handleAuthHeaders(resp, req, auth_sent, prxy_sent); // handle 401 and 407 response codes int sts = resp.getStatusCode(); switch(sts) { case 401: // Unauthorized case 407: // Proxy Authentication Required // guard against infinite retries due to bugs num_tries++; if (num_tries > 10) throw new ProtocolException("Bug in authorization handling: server refused the given info 10 times"); // defer handling if a stream was used if (req.getStream() != null) { if (!HTTPConnection.deferStreamed) { Log.write(Log.AUTH, "AuthM: status " + sts + " not handled - request has " + "an output stream"); return RSP_CONTINUE; } saved_req = (Request) req.clone(); saved_resp = (Response) resp.clone(); deferred_auth_list.put(req.getStream(), this); req.getStream().reset(); resp.setRetryRequest(true); Log.write(Log.AUTH, "AuthM: Handling of status " + sts + " deferred because an " + "output stream was used"); return RSP_CONTINUE; } // handle the challenge Log.write(Log.AUTH, "AuthM: Handling status: " + sts + " " + resp.getReasonLine()); handle_auth_challenge(req, resp); // check for valid challenge if (auth_sent != null || prxy_sent != null) { try { resp.getInputStream().close(); } catch (IOException ioe) { } if (auth_sent != null) Log.write(Log.AUTH, "AuthM: Resending request " + "with Authorization '" + auth_sent + "'"); else Log.write(Log.AUTH, "AuthM: Resending request " + "with Proxy-Authorization '" + prxy_sent + "'"); return RSP_REQUEST; } if (req.getStream() != null) Log.write(Log.AUTH, "AuthM: status " + sts + " not " + "handled - request has an output " + "stream"); else Log.write(Log.AUTH, "AuthM: No Auth Info found - " + "status " + sts + " not handled"); return RSP_CONTINUE; default: return RSP_CONTINUE; } } /** * Invoked by the HTTPClient. */ public void responsePhase3Handler(Response resp, RoRequest req) { } /** * Invoked by the HTTPClient. */ public void trailerHandler(Response resp, RoRequest req) throws IOException { // Let the AuthHandler handle any Authentication headers. AuthorizationHandler h = AuthorizationInfo.getAuthHandler(); if (h != null) h.handleAuthTrailers(resp, req, auth_sent, prxy_sent); } /** * */ private void handle_auth_challenge(Request req, Response resp) throws AuthSchemeNotImplException, IOException { // handle WWW-Authenticate int[] idx_arr = { auth_lst_idx, // hack to pass by ref auth_scm_idx}; auth_sent = setAuthHeaders(resp.getHeader("WWW-Authenticate"), req, resp, "Authorization", idx_arr, auth_sent); if (auth_sent != null) { auth_from_4xx = true; auth_lst_idx = idx_arr[0]; auth_scm_idx = idx_arr[1]; } else { auth_lst_idx = 0; auth_scm_idx = 0; } // handle Proxy-Authenticate idx_arr[0] = prxy_lst_idx; // hack to pass by ref idx_arr[1] = prxy_scm_idx; prxy_sent = setAuthHeaders(resp.getHeader("Proxy-Authenticate"), req, resp, "Proxy-Authorization", idx_arr, prxy_sent); if (prxy_sent != null) { prxy_from_4xx = true; prxy_lst_idx = idx_arr[0]; prxy_scm_idx = idx_arr[1]; } else { prxy_lst_idx = 0; prxy_scm_idx = 0; } if (prxy_sent != null) { HTTPConnection con = req.getConnection(); Util.getList(proxy_cntxt_list, con.getContext()) .put(con.getProxyHost()+":"+con.getProxyPort(), prxy_sent); } // check for headers if (auth_sent == null && prxy_sent == null && resp.getHeader("WWW-Authenticate") == null && resp.getHeader("Proxy-Authenticate") == null) { if (resp.getStatusCode() == 401) throw new ProtocolException("Missing WWW-Authenticate header"); else throw new ProtocolException("Missing Proxy-Authenticate header"); } } /** * Handles authentication requests and sets the authorization headers. * It tries to retrieve the neccessary parameters from AuthorizationInfo, * and failing that calls the AuthHandler. Handles multiple authentication * headers. * * @param auth_str the authentication header field returned by the server. * @param req the Request used * @param resp the full Response received * @param header the header name to use in the new headers array. * @param idx_arr an array of indicies holding the state of where we * are when handling multiple authorization headers. * @param prev the previous auth info sent, or null if none * @return the new credentials, or null if none found * @exception ProtocolException if auth_str is null. * @exception AuthSchemeNotImplException if thrown by the AuthHandler. * @exception IOException if thrown by the AuthHandler. */ private AuthorizationInfo setAuthHeaders(String auth_str, Request req, RoResponse resp, String header, int[] idx_arr, AuthorizationInfo prev) throws ProtocolException, AuthSchemeNotImplException, IOException { if (auth_str == null) return null; // get the list of challenges the server sent AuthorizationInfo[] challenges = AuthorizationInfo.parseAuthString(auth_str, req, resp); if (Log.isEnabled(Log.AUTH)) { Log.write(Log.AUTH, "AuthM: parsed " + challenges.length + " challenges:"); for (int idx=0; idx= challenges.length) idx_arr[1] = 0; try { credentials = AuthorizationInfo.queryAuthHandler( challenges[idx_arr[1]], req, resp); break; } catch (AuthSchemeNotImplException asnie) { if (idx == challenges.length-1) throw asnie; } finally { idx_arr[1]++; } } } // if we still don't have any credentials then give up if (credentials == null) return null; // find auth info int auth_idx; NVPair[] hdrs = req.getHeaders(); for (auth_idx=0; auth_idx