blob: 4d27b22fdd14a4e7865e7f7dcfd8ac73d9048541 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.abdera2.common.http;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.abdera2.common.text.Codec;
import static org.apache.abdera2.common.text.CharUtils.unquote;
import static org.apache.abdera2.common.text.CharUtils.quotedIfNotToken;
import static org.apache.abdera2.common.text.CharUtils.isToken;
import static org.apache.abdera2.common.text.CharUtils.quoted;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
/**
* Implementation of the HTTP Challenge/Credentials Construct. This is helpful when
* using custom authentication mechanisms with an HTTP Request. It provides
* a simple means of working with the WWW-Authenticate and Proxy-Authenticate
* headers.
**/
public class Authentication implements Iterable<String>, Serializable {
private static final long serialVersionUID = 7237004814934751226L;
public final static String BASIC = "basic";
public final static String DIGEST = "digest";
private final static String TOKEN = "[\\!\\#\\$\\%\\&\\'\\*\\+\\-\\.\\^\\_\\`\\|\\~a-zA-Z0-9]+";
private final static String B64 = "([a-zA-Z0-9\\-\\.\\_\\~\\+\\/]+\\=*)";
private final static String PARAM = TOKEN+"\\s*={1}\\s*(?:(?:\"[^\"]+\")|(?:"+TOKEN+"))";
private final static String PARAMS = "(" + PARAM + "(?:\\s*,\\s*"+PARAM+")*)";
private final static String B64orPARAM = "(?:" + PARAMS + "|" + B64 + ")";
private final static String PATTERN = "("+TOKEN+")(?:\\s+" + B64orPARAM + ")?";
private final static Pattern pattern =
Pattern.compile(PATTERN);
private final static Pattern param =
Pattern.compile("("+PARAM+")");
private final static Set<String> ALWAYS =
new HashSet<String>();
static {
ALWAYS.add("domain");
ALWAYS.add("nonce");
ALWAYS.add("opaque");
ALWAYS.add("qop");
ALWAYS.add("realm");
}
public static void alwaysQuote(String name) {
if (name == null)
throw new IllegalArgumentException();
ALWAYS.add(name);
}
public static Iterable<Authentication> parse(String challenge) {
List<Authentication> challenges = new ArrayList<Authentication>();
Matcher matcher = pattern.matcher(challenge);
while (matcher.find()) {
String scheme = matcher.group(1);
String params = matcher.group(2);
String b64token = matcher.group(3);
Authentication chal =
new Authentication(scheme,b64token);
if (params != null) {
Matcher mparams = param.matcher(params);
while(mparams.find()) {
String p = mparams.group(1);
String[] ps = p.split("\\s*=\\s*", 2);
String name = ps[0];
if (name.charAt(name.length()-1)=='*')
name = name.substring(0,name.length()-1);
chal.add(name, Codec.decode(unquote(ps[1])));
}
}
challenges.add(chal);
}
return challenges;
}
private final String scheme;
private final String b64token;
private final Map<String,String> params =
new HashMap<String,String>();
private final Set<String> quoted =
new HashSet<String>();
public Authentication(String scheme) {
this(scheme,null);
}
public Authentication(String scheme, String b64token) {
if (scheme == null)
throw new IllegalArgumentException();
this.scheme = scheme.toLowerCase(Locale.US);
this.b64token = b64token;
}
private void add(String name, String value) {
params.put(name,value);
}
public String getScheme() {
return scheme;
}
public String getBase64Token() {
return b64token;
}
public String getParam(String name) {
return params.get(name);
}
private static boolean is_always_quoted(String name) {
return name != null ? ALWAYS.contains(name) : false;
}
public void setParam(String name, String val) {
if (val == null)
params.remove(name);
else
params.put(name, val);
}
public void setQuotedParam(String name, String val) {
setParam(name,val);
quoted.add(name);
}
public void setQuotedParam(String name) {
quoted.add(name);
}
public boolean hasParam(String name) {
return params.containsKey(name);
}
public Iterator<String> iterator() {
return params.keySet().iterator();
}
private boolean isquoted(String param) {
return quoted.contains(param);
}
public String toString() {
StringBuilder buf = new StringBuilder(scheme);
if (b64token != null || params.size() > 0)
buf.append(' ');
if (b64token != null)
buf.append(b64token);
else {
boolean first = true;
for (String param : this) {
if (!first) {
buf.append(',').append(' ');
} else first = false;
String val = getParam(param);
buf.append(param);
boolean always = is_always_quoted(param) || isquoted(param);
if (!isToken(val) && !always) {
buf.append('*')
.append('=')
.append(Codec.encode(val,Codec.STAR));
} else {
buf.append('=')
.append(always?quoted(val,true):quotedIfNotToken(val));
}
}
}
return buf.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((b64token == null) ? 0 : b64token.hashCode());
result = prime * result + ((params == null) ? 0 : params.hashCode());
result = prime * result + ((scheme == null) ? 0 : scheme.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Authentication other = (Authentication) obj;
if (b64token == null) {
if (other.b64token != null)
return false;
} else if (!b64token.equals(other.b64token))
return false;
if (params == null) {
if (other.params != null)
return false;
} else if (!params.equals(other.params))
return false;
if (scheme == null) {
if (other.scheme != null)
return false;
} else if (!scheme.equals(other.scheme))
return false;
return true;
}
public static Authentication basic(String userid, String password) {
String b64token =
StringUtils.newStringUsAscii(
Base64.encodeBase64(
bytes(userid,":",password)));
return new Authentication(BASIC, b64token);
}
private static byte[] bytes(String val, String... vals) {
if (val == null) return new byte[0];
ByteArrayOutputStream out =
new ByteArrayOutputStream();
try {
out.write(StringUtils.getBytesUtf8(val));
for (String v : vals)
out.write(StringUtils.getBytesUtf8(v));
} catch (Throwable t) {}
return out.toByteArray();
}
public static String toString(Authentication auth, Authentication... auths) {
if (auth == null) return null;
StringBuilder buf = new StringBuilder();
buf.append(auth.toString());
for (Authentication a : auths)
buf.append(", ").append(a.toString());
return buf.toString();
}
public static String toString(Iterable<Authentication> auths) {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (Authentication auth : auths) {
if (!first) buf.append(", ");
else first = !first;
buf.append(auth.toString());
}
return buf.toString();
}
}