diff --git a/httpclient/src/main/java/org/apache/http/auth/AuthChallenge.java b/httpclient/src/main/java/org/apache/http/auth/AuthChallenge.java new file mode 100644 index 000000000..c6bbb073c --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/auth/AuthChallenge.java @@ -0,0 +1,83 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.auth; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.http.NameValuePair; +import org.apache.http.annotation.Immutable; +import org.apache.http.util.Args; + +/** + * This class represents an authentication challenge consisting of a auth scheme + * and either a single parameter or a list of name / value pairs. + * + * @since 5.0 + */ +@Immutable +public final class AuthChallenge { + + private final String scheme; + private final String value; + private final List params; + + public AuthChallenge(final String scheme, final String value, final List params) { + super(); + Args.notNull(scheme, "Auth scheme"); + this.scheme = scheme; + this.value = value; + this.params = params != null ? Collections.unmodifiableList(new ArrayList<>(params)) : null; + } + + public String getScheme() { + return scheme; + } + + public String getValue() { + return value; + } + + public List getParams() { + return params; + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + buffer.append(scheme).append(" "); + if (value != null) { + buffer.append(value); + } else if (params != null) { + buffer.append(params); + } + return buffer.toString(); + } + +} + diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/AuthChallengeParser.java b/httpclient/src/main/java/org/apache/http/impl/auth/AuthChallengeParser.java new file mode 100644 index 000000000..e64fdb01f --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/impl/auth/AuthChallengeParser.java @@ -0,0 +1,122 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.http.impl.auth; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.auth.AuthChallenge; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.message.ParserCursor; +import org.apache.http.message.TokenParser; +import org.apache.http.util.CharArrayBuffer; + +public class AuthChallengeParser { + + private final TokenParser tokenParser = TokenParser.INSTANCE; + + private final static char BLANK = ' '; + private final static char COMMA_CHAR = ','; + private final static char EQUAL_CHAR = '='; + + // IMPORTANT! + // These private static variables must be treated as immutable and never exposed outside this class + private static final BitSet TERMINATORS = TokenParser.INIT_BITSET(BLANK, EQUAL_CHAR, COMMA_CHAR); + private static final BitSet DELIMITER = TokenParser.INIT_BITSET(COMMA_CHAR); + + NameValuePair parseTokenOrParameter(final CharArrayBuffer buffer, final ParserCursor cursor) throws ParseException { + + tokenParser.skipWhiteSpace(buffer, cursor); + final String token = tokenParser.parseToken(buffer, cursor, TERMINATORS); + if (!cursor.atEnd()) { + if (buffer.charAt(cursor.getPos()) == BLANK) { + tokenParser.skipWhiteSpace(buffer, cursor); + } + if (buffer.charAt(cursor.getPos()) == EQUAL_CHAR) { + cursor.updatePos(cursor.getPos() + 1); + final String value = tokenParser.parseValue(buffer, cursor, DELIMITER); + return new BasicNameValuePair(token, value); + } + } + return new BasicNameValuePair(token, null); + } + + public List parse(final CharArrayBuffer buffer, final ParserCursor cursor) throws ParseException { + + final List list = new ArrayList<>(); + String scheme = null; + final List params = new ArrayList<>(); + while (!cursor.atEnd()) { + final NameValuePair tokenOrParameter = parseTokenOrParameter(buffer, cursor); + if (tokenOrParameter.getValue() == null && !cursor.atEnd() && buffer.charAt(cursor.getPos()) != COMMA_CHAR) { + if (scheme != null) { + if (params.isEmpty()) { + throw new ParseException("Malformed auth challenge"); + } + list.add(createAuthChallenge(scheme, params)); + params.clear(); + } + scheme = tokenOrParameter.getName(); + } else { + params.add(tokenOrParameter); + if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) != COMMA_CHAR) { + scheme = null; + } + } + if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) == COMMA_CHAR) { + cursor.updatePos(cursor.getPos() + 1); + } + } + list.add(createAuthChallenge(scheme, params)); + return list; + } + + private static AuthChallenge createAuthChallenge(final String scheme, final List params) { + if (scheme != null) { + if (params.size() == 1) { + final NameValuePair nvp = params.get(0); + if (nvp.getValue() == null) { + return new AuthChallenge(scheme, nvp.getName(), null); + } + } + return new AuthChallenge(scheme, null, params.size() > 0 ? params : null); + } else { + if (params.size() == 1) { + final NameValuePair nvp = params.get(0); + if (nvp.getValue() == null) { + return new AuthChallenge(nvp.getName(), null, null); + } + } + throw new ParseException("Malformed auth challenge"); + } + } + +} diff --git a/httpclient/src/test/java/org/apache/http/auth/TestAuthChallenge.java b/httpclient/src/test/java/org/apache/http/auth/TestAuthChallenge.java new file mode 100644 index 000000000..a2700e61f --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/auth/TestAuthChallenge.java @@ -0,0 +1,57 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.http.auth; + +import java.util.Arrays; + +import org.apache.http.message.BasicNameValuePair; +import org.junit.Assert; +import org.junit.Test; + +public class TestAuthChallenge { + + @Test + public void testAuthChallengeWithValue() { + final AuthChallenge authChallenge = new AuthChallenge("Basic", "blah", null); + Assert.assertEquals("Basic", authChallenge.getScheme()); + Assert.assertEquals("blah", authChallenge.getValue()); + Assert.assertEquals(null, authChallenge.getParams()); + Assert.assertEquals("Basic blah", authChallenge.toString()); + } + + @Test + public void testAuthChallengeWithParams() { + final AuthChallenge authChallenge = new AuthChallenge("Basic", null, + Arrays.asList(new BasicNameValuePair("blah", "this"), new BasicNameValuePair("blah", "that"))); + Assert.assertEquals("Basic", authChallenge.getScheme()); + Assert.assertEquals(null, authChallenge.getValue()); + Assert.assertNotNull(authChallenge.getParams()); + Assert.assertEquals("Basic [blah=this, blah=that]", authChallenge.toString()); + } + +} diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestAuthChallengeParser.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestAuthChallengeParser.java new file mode 100644 index 000000000..65984d9ac --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestAuthChallengeParser.java @@ -0,0 +1,298 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. 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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.impl.auth; + +import java.util.List; + +import org.apache.http.NameValuePair; +import org.apache.http.ParseException; +import org.apache.http.auth.AuthChallenge; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class TestAuthChallengeParser { + + private AuthChallengeParser parser; + + @Before + public void setUp() throws Exception { + this.parser = new AuthChallengeParser(); + } + + @Test + public void testParseBasicToken() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("blah", nvp.getName()); + Assert.assertEquals(null, nvp.getValue()); + } + + @Test + public void testParseTokenWithBlanks() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(" blah blah "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("blah", nvp.getName()); + Assert.assertEquals(null, nvp.getValue()); + } + + @Test + public void testParseTokenDelimited() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("blah,blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("blah", nvp.getName()); + Assert.assertEquals(null, nvp.getValue()); + } + + @Test + public void testParseParameterSimple() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("param=blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("param", nvp.getName()); + Assert.assertEquals("blah", nvp.getValue()); + } + + @Test + public void testParseParameterDelimited() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("param = blah , "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("param", nvp.getName()); + Assert.assertEquals("blah", nvp.getValue()); + } + + @Test + public void testParseParameterQuoted() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(" param = \" blah blah \""); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("param", nvp.getName()); + Assert.assertEquals(" blah blah ", nvp.getValue()); + } + + @Test + public void testParseParameterEscaped() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(" param = \" blah \\\"blah\\\" \""); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("param", nvp.getName()); + Assert.assertEquals(" blah \"blah\" ", nvp.getValue()); + } + + @Test + public void testParseParameterNoValue() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("param = , "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor); + Assert.assertNotNull(nvp); + Assert.assertEquals("param", nvp.getName()); + Assert.assertEquals("", nvp.getValue()); + } + + @Test + public void testParseBasicAuthChallenge() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("Basic realm=blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(1, challenges.size()); + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("Basic", challenge1.getScheme()); + Assert.assertEquals(null, challenge1.getValue()); + final List params = challenge1.getParams(); + Assert.assertNotNull(params); + Assert.assertEquals(1, params.size()); + Assert.assertEquals(new BasicNameValuePair("realm", "blah"), params.get(0)); + } + + @Test + public void testParseAuthChallengeWithBlanks() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append(" Basic realm = blah "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(1, challenges.size()); + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("Basic", challenge1.getScheme()); + Assert.assertEquals(null, challenge1.getValue()); + final List params = challenge1.getParams(); + Assert.assertNotNull(params); + Assert.assertEquals(1, params.size()); + Assert.assertEquals(new BasicNameValuePair("realm", "blah"), params.get(0)); + } + + @Test + public void testParseMultipleAuthChallenge() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("This xxxxxxxxxxxxxxxxxxxxxx, " + + "That yyyyyyyyyyyyyyyyyyyyyy "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(2, challenges.size()); + + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("This", challenge1.getScheme()); + Assert.assertEquals("xxxxxxxxxxxxxxxxxxxxxx", challenge1.getValue()); + Assert.assertNull(challenge1.getParams()); + + final AuthChallenge challenge2 = challenges.get(1); + Assert.assertEquals("That", challenge2.getScheme()); + Assert.assertEquals("yyyyyyyyyyyyyyyyyyyyyy", challenge2.getValue()); + Assert.assertNull(challenge2.getParams()); + } + + @Test + public void testParseMultipleAuthChallengeWithParams() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("Basic realm=blah, param1 = this, param2=that, " + + "Basic realm=\"\\\"yada\\\"\", this, that=,this-and-that "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(2, challenges.size()); + + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("Basic", challenge1.getScheme()); + Assert.assertEquals(null, challenge1.getValue()); + final List params1 = challenge1.getParams(); + Assert.assertNotNull(params1); + Assert.assertEquals(3, params1.size()); + Assert.assertEquals(new BasicNameValuePair("realm", "blah"), params1.get(0)); + Assert.assertEquals(new BasicNameValuePair("param1", "this"), params1.get(1)); + Assert.assertEquals(new BasicNameValuePair("param2", "that"), params1.get(2)); + + final AuthChallenge challenge2 = challenges.get(1); + Assert.assertEquals("Basic", challenge2.getScheme()); + Assert.assertEquals(null, challenge2.getValue()); + final List params2 = challenge2.getParams(); + Assert.assertNotNull(params2); + Assert.assertEquals(4, params2.size()); + Assert.assertEquals(new BasicNameValuePair("realm", "\"yada\""), params2.get(0)); + Assert.assertEquals(new BasicNameValuePair("this", null), params2.get(1)); + Assert.assertEquals(new BasicNameValuePair("that", ""), params2.get(2)); + Assert.assertEquals(new BasicNameValuePair("this-and-that", null), params2.get(3)); + } + + @Test + public void testParseEmptyAuthChallenge1() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("This"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(1, challenges.size()); + + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("This", challenge1.getScheme()); + Assert.assertEquals(null, challenge1.getValue()); + Assert.assertNull(challenge1.getParams()); + } + + @Test(expected = ParseException.class) + public void testParseMalformedAuthChallenge1() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("This , "); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + parser.parse(buffer, cursor); + } + + @Test(expected = ParseException.class) + public void testParseMalformedAuthChallenge2() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("This = that"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + parser.parse(buffer, cursor); + } + + @Test(expected = ParseException.class) + public void testParseMalformedAuthChallenge3() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("blah blah blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + parser.parse(buffer, cursor); + } + + @Test + public void testParseValidAuthChallenge1() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("blah blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(1, challenges.size()); + + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("blah", challenge1.getScheme()); + Assert.assertEquals("blah", challenge1.getValue()); + Assert.assertNull(challenge1.getParams()); + } + + @Test + public void testParseValidAuthChallenge2() throws Exception { + final CharArrayBuffer buffer = new CharArrayBuffer(64); + buffer.append("blah blah, blah"); + final ParserCursor cursor = new ParserCursor(0, buffer.length()); + final List challenges = parser.parse(buffer, cursor); + Assert.assertNotNull(challenges); + Assert.assertEquals(1, challenges.size()); + + final AuthChallenge challenge1 = challenges.get(0); + Assert.assertEquals("blah", challenge1.getScheme()); + Assert.assertEquals(null, challenge1.getValue()); + final List params1 = challenge1.getParams(); + Assert.assertNotNull(params1); + Assert.assertEquals(2, params1.size()); + Assert.assertEquals(new BasicNameValuePair("blah", null), params1.get(0)); + Assert.assertEquals(new BasicNameValuePair("blah", null), params1.get(1)); + } + +}