/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.io.dav.http;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.auth.SVNPasswordAuthentication;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
abstract class HTTPAuthentication {
private Map<String, String> myChallengeParameters;
private String myUserName;
private char[] myPassword;
private static final String AUTH_METHODS_PROPERTY = "svnkit.http.methods";
private static final String OLD_AUTH_METHODS_PROPERTY = "javasvn.http.methods";
protected HTTPAuthentication (SVNPasswordAuthentication credentials) {
if (credentials != null) {
myUserName = credentials.getUserName();
myPassword = credentials.getPasswordValue();
}
}
protected HTTPAuthentication (String name, char[] password) {
myUserName = name;
myPassword = password;
}
protected HTTPAuthentication () {
}
public void setChallengeParameter(String name, String value) {
Map<String, String> params = getChallengeParameters();
params.put(name, value);
}
public String getChallengeParameter(String name) {
if (myChallengeParameters == null) {
return null;
}
return (String)myChallengeParameters.get(name);
}
protected Map<String, String> getChallengeParameters() {
if (myChallengeParameters == null) {
myChallengeParameters = new TreeMap<String, String>();
}
return myChallengeParameters;
}
public void setCredentials(SVNPasswordAuthentication credentials) {
if (credentials != null) {
myUserName = credentials.getUserName();
myPassword = credentials.getPasswordValue();
}
}
public String getRawUserName() {
return myUserName;
}
public String getUserName() {
return myUserName;
}
public char[] getPassword() {
return myPassword;
}
public void setUserName(String name) {
myUserName = name;
}
public void setPassword(char[] password) {
myPassword = password;
}
public static HTTPAuthentication parseAuthParameters(Collection<String> authHeaderValues, HTTPAuthentication prevResponse, String charset,
Collection<String> authTypes, ISVNAuthenticationManager authManager, int requestID) throws SVNException {
if (authHeaderValues == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE,
"Missing HTTP authorization method");
SVNErrorManager.error(err, SVNLogType.NETWORK);
}
HTTPAuthentication auth = null;
String authHeader = null;
// sort auth headers accordingly to priorities.
authHeaderValues = sortSchemes(authHeaderValues, authTypes);
for (Iterator<String> authSchemes = authHeaderValues.iterator(); authSchemes.hasNext();) {
authHeader = authSchemes.next();
String source = authHeader.trim();
// parse strings: name="value" or name=value
int index = source.indexOf(' ');
if (index <= 0) {
index = source.length();
if (!"NTLM".equalsIgnoreCase(source.substring(0, index)) && !"Negotiate".equalsIgnoreCase(source.substring(0, index))) {
continue;
}
}
String method = source.substring(0, index);
source = source.substring(index).trim();
if ("Basic".equalsIgnoreCase(method)) {
auth = new HTTPBasicAuthentication(charset);
if (source.indexOf("realm=") >= 0) {
source = source.substring(source.indexOf("realm=") + "realm=".length());
source = source.trim();
if (source.startsWith("\"")) {
source = source.substring(1);
}
if (source.endsWith("\"")) {
source = source.substring(0, source.length() - 1);
}
//parameters.put("realm", source);
auth.setChallengeParameter("realm", source);
}
break;
} else if ("Digest".equalsIgnoreCase(method)) {
auth = new HTTPDigestAuthentication(charset);
char[] chars = (source + " ").toCharArray();
int tokenIndex = 0;
boolean parsingToken = true;
String name = null;
String value;
int quotesCount = 0;
for(int i = 0; i < chars.length; i++) {
if (parsingToken) {
if (chars[i] == '=') {
name = new String(chars, tokenIndex, i - tokenIndex);
name = name.trim();
tokenIndex = i + 1;
parsingToken = false;
}
} else {
if (chars[i] == '\"') {
quotesCount = quotesCount > 0 ? 0 : 1;
} else if ( i + 1 >= chars.length || (chars[i] == ',' && quotesCount == 0)) {
value = new String(chars, tokenIndex, i - tokenIndex);
value = value.trim();
if (value.charAt(0) == '\"' && value.charAt(value.length() - 1) == '\"') {
value = value.substring(1);
value = value.substring(0, value.length() - 1);
}
//parameters.put(name, value);
auth.setChallengeParameter(name, value);
tokenIndex = i + 1;
parsingToken = true;
}
}
}
HTTPDigestAuthentication digestAuth = (HTTPDigestAuthentication)auth;
digestAuth.init();
break;
} else if ("NTLM".equalsIgnoreCase(method)) {
HTTPNTLMAuthentication ntlmAuth = null;
if (source.length() == 0) {
String ntlmImpl = System.getProperty("svnkit.http.ntlm", "java:apache");
if ("jna".equalsIgnoreCase(ntlmImpl)) {
ntlmAuth = HTTPNativeNTLMAuthentication.newInstance(charset);
if (ntlmAuth != null) {
ntlmAuth.parseChallenge(null);
}
}
if (ntlmAuth == null) {
if ("jna".equalsIgnoreCase(ntlmImpl)) {
ntlmImpl = "java";
}
if ("java:apache".equalsIgnoreCase(ntlmImpl)) {
ntlmAuth = HTTPApacheNTLMAuthentication.newInstance(charset, HTTPApacheNTLMAuthentication.APACHE_ENGINE);
} else if ("java:jcifs".equalsIgnoreCase(ntlmImpl)) {
ntlmAuth = HTTPApacheNTLMAuthentication.newInstance(charset, HTTPApacheNTLMAuthentication.JCIFS_ENGINE);
} else {
ntlmAuth = new HTTPNTLMAuthentication(charset);
}
}
ntlmAuth.setType1State();
} else {
ntlmAuth = (HTTPNTLMAuthentication)prevResponse;
ntlmAuth.parseChallenge(source);
ntlmAuth.setType3State();
}
auth = ntlmAuth;
break;
} else if ("Negotiate".equalsIgnoreCase(method)) {
HTTPNegotiateAuthentication negoAuth = null;
if (source.length() == 0) {
// Check for a custom negotiation implementation, created by the auth manager
if (authManager instanceof IHTTPNegotiateAuthenticationFactory) {
negoAuth = ((IHTTPNegotiateAuthenticationFactory) authManager).createNegotiateAuthentication(
prevResponse instanceof HTTPNegotiateAuthentication ? (HTTPNegotiateAuthentication) prevResponse : null, requestID);
} else {
if (DefaultHTTPNegotiateAuthentication.isSupported()) {
if (prevResponse instanceof DefaultHTTPNegotiateAuthentication) {
negoAuth = new DefaultHTTPNegotiateAuthentication((DefaultHTTPNegotiateAuthentication)prevResponse);
} else {
negoAuth = new DefaultHTTPNegotiateAuthentication();
try {
negoAuth.needsLogin();
} catch (Throwable th) {
// SecurityException might be thrown in case configuration file
// is missing, then consider Negotiate as not supporter
negoAuth = null;
}
}
}
}
if (negoAuth != null) {
negoAuth.respondTo(null);
}
} else {
// If this is a server response with a token, then negotiate authentication is already in progress.
// NOTE: this should never happen in practice since negotiate authentication should be completed with a
// single token. After successful authentication at the server end, the token from the server is sent
// in a Authentication-Info header.
negoAuth = (HTTPNegotiateAuthentication)prevResponse;
negoAuth.respondTo(source);
}
if (negoAuth != null) {
auth = negoAuth;
break;
}
}
}
if (auth == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE,
"HTTP authorization method ''{0}'' is not supported", authHeader);
SVNErrorManager.error(err, SVNLogType.NETWORK);
}
if (prevResponse != null) {
auth.setUserName(prevResponse.getRawUserName());
auth.setPassword(prevResponse.getPassword());
}
return auth;
}
public static boolean isSchemeSupportedByServer(String scheme, Collection<String> authHeaderValues) throws SVNException {
if (authHeaderValues == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNSUPPORTED_FEATURE, "Missing HTTP authorization method");
SVNErrorManager.error(err, SVNLogType.NETWORK);
}
String authHeader = null;
for (Iterator<String> authSchemes = authHeaderValues.iterator(); authSchemes.hasNext();) {
authHeader = authSchemes.next();
String source = authHeader.trim();
int index = source.indexOf(' ');
if (index <= 0) {
index = source.length();
}
String method = source.substring(0, index);
if (method.equalsIgnoreCase(scheme)) {
return true;
}
}
return false;
}
private static Collection<String> sortSchemes(Collection<String> authHeaders, Collection<String> authTypes) {
String priorities = System.getProperty(AUTH_METHODS_PROPERTY, System.getProperty(OLD_AUTH_METHODS_PROPERTY));
final List<String> schemes = new ArrayList<String>();
if (authTypes != null && !authTypes.isEmpty()) {
schemes.addAll(authTypes);
} else if (priorities != null && !"".equals(priorities.trim())) {
for(StringTokenizer tokens = new StringTokenizer(priorities, " ,"); tokens.hasMoreTokens();) {
String scheme = tokens.nextToken();
if (!schemes.contains(scheme)) {
schemes.add(scheme);
}
}
} else {
return authHeaders;
}
List<String> ordered = new ArrayList<String>(authHeaders);
Collections.sort(ordered, new Comparator<String>() {
public int compare(String o1, String o2) {
String header1 = o1;
String header2 = o2;
String scheme1 = getSchemeName(header1);
String scheme2 = getSchemeName(header2);
int index1 = schemes.indexOf(scheme1);
int index2 = schemes.indexOf(scheme2);
index1 = index1 < 0 ? Integer.MAX_VALUE : index1;
index2 = index2 < 0 ? Integer.MAX_VALUE : index2;
if (index1 == index2) {
return 0;
}
return index1 > index2 ? 1 : -1;
}
});
return ordered;
}
private static String getSchemeName(String header) {
String source = header.trim();
int index = source.indexOf(' ');
if (index <= 0) {
index = source.length();
}
return source.substring(0, index);
}
public abstract String getAuthenticationScheme();
public abstract String authenticate() throws SVNException;
protected static byte[] getASCIIBytes(final String data) {
return getBytes(data, "US-ASCII");
}
protected static byte[] getBytes(final String data, String charset) {
try {
return data.getBytes(charset);
} catch (UnsupportedEncodingException e) {
return data.getBytes();
}
}
protected static byte[] getBytes(final char[] data, String charset) {
return SVNEncodingUtil.getBytes(data, charset);
}
public static void clear(byte[] array) {
SVNEncodingUtil.clearArray(array);
}
public static void clear(char[] array) {
SVNEncodingUtil.clearArray(array);
}
}