/*
 * ====================================================================
 * 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
 * <http://www.apache.org/>.
 *
 */
package org.apache.hc.client5.http.impl.auth;

import java.util.List;

import org.apache.hc.client5.http.auth.AuthChallenge;
import org.apache.hc.client5.http.auth.ChallengeType;
import org.apache.hc.client5.http.auth.StandardAuthScheme;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.util.CharArrayBuffer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class TestAuthChallengeParser {

    private AuthChallengeParser parser;

    @BeforeEach
    void setUp() {
        this.parser = new AuthChallengeParser();
    }

    @Test
    void testParseTokenTerminatedByBlank() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc", parser.parseToken(buffer, cursor));
    }

    @Test
    void testParseTokenTerminatedByComma() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc, ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc", parser.parseToken(buffer, cursor));
    }

    @Test
    void testParseTokenTerminatedByEndOfStream() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc", parser.parseToken(buffer, cursor));
    }

    @Test
    void testParsePaddedToken68() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc==== ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc====", parser.parseToken(buffer, cursor));
        Assertions.assertFalse(cursor.atEnd());
        Assertions.assertEquals(' ', buffer.charAt(cursor.getPos()));
    }

    @Test
    void testParsePaddedToken68SingleEqual() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc=");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc=", parser.parseToken(buffer, cursor));
        Assertions.assertTrue(cursor.atEnd());
    }

    @Test
    void testParsePaddedToken68MultipleEquals() {
        final CharArrayBuffer buffer = new CharArrayBuffer(16);
        buffer.append("aaabbbbccc======");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc======", parser.parseToken(buffer, cursor));
        Assertions.assertTrue(cursor.atEnd());
    }

    @Test
    void testParsePaddedToken68TerminatedByComma() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc====,");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc====", parser.parseToken(buffer, cursor));
        Assertions.assertFalse(cursor.atEnd());
        Assertions.assertEquals(',', buffer.charAt(cursor.getPos()));
    }

    @Test
    void testParseTokenTerminatedByParameter() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("aaabbbbccc=blah");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertEquals("aaabbbbccc", parser.parseToken(buffer, cursor));
        Assertions.assertFalse(cursor.atEnd());
        Assertions.assertEquals('=', buffer.charAt(cursor.getPos()));
    }

    @Test
    void testParseBasicAuthChallenge() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append(StandardAuthScheme.BASIC + " realm=blah");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(1, challenges.size());
        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        final List<NameValuePair> params = challenge1.getParams();
        Assertions.assertNotNull(params);
        Assertions.assertEquals(1, params.size());
        assertNameValuePair(params.get(0), "realm", "blah");
    }

    @Test
    void testParseAuthChallengeWithBlanks() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("   " + StandardAuthScheme.BASIC + "  realm = blah   ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(1, challenges.size());
        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        final List<NameValuePair> params = challenge1.getParams();
        Assertions.assertNotNull(params);
        Assertions.assertEquals(1, params.size());
        assertNameValuePair(params.get(0), "realm", "blah");
    }

    @Test
    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<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(2, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals("This", challenge1.getSchemeName());
        Assertions.assertEquals("xxxxxxxxxxxxxxxxxxxxxx", challenge1.getValue());
        Assertions.assertNull(challenge1.getParams());

        final AuthChallenge challenge2 = challenges.get(1);
        Assertions.assertEquals("That", challenge2.getSchemeName());
        Assertions.assertEquals("yyyyyyyyyyyyyyyyyyyyyy", challenge2.getValue());
        Assertions.assertNull(challenge2.getParams());
    }

    @Test
    void testParseMultipleAuthChallengeWithParams() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append(StandardAuthScheme.BASIC + " realm=blah, param1 = this, param2=that, " +
                StandardAuthScheme.BASIC + " realm=\"\\\"yada\\\"\", this, that=,this-and-that  ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(2, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        final List<NameValuePair> params1 = challenge1.getParams();
        Assertions.assertNotNull(params1);
        Assertions.assertEquals(3, params1.size());
        assertNameValuePair(params1.get(0), "realm", "blah");
        assertNameValuePair(params1.get(1), "param1", "this");
        assertNameValuePair(params1.get(2), "param2", "that");

        final AuthChallenge challenge2 = challenges.get(1);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge2.getSchemeName());
        Assertions.assertNull(challenge2.getValue());
        final List<NameValuePair> params2 = challenge2.getParams();
        Assertions.assertNotNull(params2);
        Assertions.assertEquals(4, params2.size());
        assertNameValuePair(params2.get(0), "realm", "\"yada\"");
        assertNameValuePair(params2.get(1), "this", null);
        assertNameValuePair(params2.get(2), "that", "");
        assertNameValuePair(params2.get(3), "this-and-that", null);
    }

    @Test
    void testParseMultipleAuthChallengeWithParamsContainingComma() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append(StandardAuthScheme.BASIC + " realm=blah, param1 = \"this, param2=that\", " +
                StandardAuthScheme.BASIC + " realm=\"\\\"yada,,,,\\\"\"");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(2, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        final List<NameValuePair> params1 = challenge1.getParams();
        Assertions.assertNotNull(params1);
        Assertions.assertEquals(2, params1.size());
        assertNameValuePair(params1.get(0), "realm", "blah");
        assertNameValuePair(params1.get(1), "param1", "this, param2=that");

        final AuthChallenge challenge2 = challenges.get(1);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge2.getSchemeName());
        Assertions.assertNull(challenge2.getValue());
        final List<NameValuePair> params2 = challenge2.getParams();
        Assertions.assertNotNull(params2);
        Assertions.assertEquals(1, params2.size());
        assertNameValuePair(params2.get(0), "realm", "\"yada,,,,\"");
    }

    @Test
    void testParseEmptyAuthChallenge1() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("This");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(1, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals("This", challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        Assertions.assertNull(challenge1.getParams());
    }

    @Test
    void testParseMalformedAuthChallenge1() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("This , ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertThrows(ParseException.class, () ->
                parser.parse(ChallengeType.TARGET, buffer, cursor));
    }

    @Test
    void testParseMalformedAuthChallenge2() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("This = that");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertThrows(ParseException.class, () ->
                parser.parse(ChallengeType.TARGET, buffer, cursor));
    }

    @Test
    void testParseMalformedAuthChallenge3() {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("blah blah blah");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        Assertions.assertThrows(ParseException.class, () ->
                parser.parse(ChallengeType.TARGET, buffer, cursor));
    }

    @Test
    void testParseValidAuthChallenge1() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("blah blah");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(1, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals("blah", challenge1.getSchemeName());
        Assertions.assertEquals("blah", challenge1.getValue());
        Assertions.assertNull(challenge1.getParams());
    }

    @Test
    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<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(1, challenges.size());

        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals("blah", challenge1.getSchemeName());
        Assertions.assertNull(challenge1.getValue());
        final List<NameValuePair> params1 = challenge1.getParams();
        Assertions.assertNotNull(params1);
        Assertions.assertEquals(2, params1.size());
        assertNameValuePair(params1.get(0), "blah", null);
        assertNameValuePair(params1.get(1), "blah", null);
    }

    @Test
    void testParseParameterAndToken68AuthChallengeMix() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("scheme1 aaaa  , scheme2 aaaa==,  scheme3 aaaa=aaaa, scheme4 aaaa=");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
        Assertions.assertNotNull(challenges);
        Assertions.assertEquals(4, challenges.size());
        final AuthChallenge challenge1 = challenges.get(0);
        Assertions.assertEquals("scheme1", challenge1.getSchemeName());
        Assertions.assertEquals("aaaa", challenge1.getValue());
        Assertions.assertNull(challenge1.getParams());
        final AuthChallenge challenge2 = challenges.get(1);
        Assertions.assertEquals("scheme2", challenge2.getSchemeName());
        Assertions.assertEquals("aaaa==", challenge2.getValue());
        Assertions.assertNull(challenge2.getParams());
        final AuthChallenge challenge3 = challenges.get(2);
        Assertions.assertEquals("scheme3", challenge3.getSchemeName());
        Assertions.assertNull(challenge3.getValue());
        Assertions.assertNotNull(challenge3.getParams());
        Assertions.assertEquals(1, challenge3.getParams().size());
        assertNameValuePair(challenge3.getParams().get(0), "aaaa", "aaaa");
        final AuthChallenge challenge4 = challenges.get(3);
        Assertions.assertEquals("scheme4", challenge4.getSchemeName());
        Assertions.assertEquals("aaaa=", challenge4.getValue());
        Assertions.assertNull(challenge4.getParams());
    }

    private static void assertNameValuePair(final NameValuePair pair, final String name, final String value) {
        Assertions.assertNotNull(pair);
        Assertions.assertEquals(name, pair.getName());
        Assertions.assertEquals(value, pair.getValue());
    }

    @Test
    void testParseAuthInfo() throws Exception {
        final CharArrayBuffer buffer = new CharArrayBuffer(64);
        buffer.append("  realm = blah, this, that  , name = value ");
        final ParserCursor cursor = new ParserCursor(0, buffer.length());
        final AuthChallenge challenge = parser.parse(ChallengeType.TARGET, StandardAuthScheme.BASIC, buffer, cursor);
        Assertions.assertEquals(StandardAuthScheme.BASIC, challenge.getSchemeName());
        Assertions.assertNull(challenge.getValue());
        final List<NameValuePair> params = challenge.getParams();
        Assertions.assertNotNull(params);
        Assertions.assertEquals(4, params.size());
        assertNameValuePair(params.get(0), "realm", "blah");
        assertNameValuePair(params.get(1), "this", null);
        assertNameValuePair(params.get(2), "that", null);
        assertNameValuePair(params.get(3), "name", "value");
    }

}
