package org.jboss.resteasy.test.xxe;

import static org.jboss.resteasy.utils.PortProviderUtil.generateURL;

import java.io.File;

import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit5.ArquillianExtension;
import org.jboss.logging.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.spi.HttpResponseCodes;
import org.jboss.resteasy.test.xxe.resource.ExternalParameterEntityResource;
import org.jboss.resteasy.test.xxe.resource.ExternalParameterEntityWrapper;
import org.jboss.resteasy.utils.TestUtil;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

/**
 * @tpSubChapter XXE
 * @tpChapter Integration tests
 * @tpTestCaseDetails Regression test for RESTEASY-1073.
 *                    External parameter entities should be disabled when the resteasy.document.expand.entity.references
 *                    parameter was set to false.
 *                    A remote attacker able to send XML requests to a RESTEasy endpoint could use this flaw to read files
 *                    accessible to the user
 *                    running the application server, and potentially perform other more advanced XXE attacks.
 * @tpSince RESTEasy 3.0.16
 */
@ExtendWith(ArquillianExtension.class)
@RunAsClient
public class ExternalParameterEntityTest {

    protected final Logger logger = Logger.getLogger(ExternalParameterEntityTest.class.getName());
    static ResteasyClient client;

    private static final String EXPAND = "war_expand";
    private static final String NO_EXPAND = "war_no_expand";

    private String passwdFile = new File(
            TestUtil.getResourcePath(ExternalParameterEntityTest.class, "ExternalParameterEntityPasswd")).getAbsolutePath();
    private String dtdFile = new File(
            TestUtil.getResourcePath(ExternalParameterEntityTest.class, "ExternalParameterEntity.dtd")).getAbsolutePath();

    private String request = "<!DOCTYPE foo [\r" +
            "  <!ENTITY % file SYSTEM \"" + passwdFile + "\">\r" +
            "  <!ENTITY % start \"<![CDATA[\">\r" +
            "  <!ENTITY % end \"]]>\">\r" +
            "  <!ENTITY % dtd SYSTEM \"" + dtdFile + "\">\r" +
            "%dtd;\r" +
            "]>\r" +
            "<externalParameterEntityWrapper><name>&xxe;</name></externalParameterEntityWrapper>";

    @Deployment(name = EXPAND, order = 1)
    public static Archive<?> createTestArchive1() {
        WebArchive war = TestUtil.prepareArchive(EXPAND);
        war.addClass(ExternalParameterEntityWrapper.class);
        war.addAsWebInfResource(ExternalParameterEntityTest.class.getPackage(), "ExternalParameterEntityExpandWeb.xml",
                "web.xml");
        return TestUtil.finishContainerPrepare(war, null, ExternalParameterEntityResource.class);
    }

    @Deployment(name = NO_EXPAND, order = 2)
    public static Archive<?> createTestArchive2() {
        WebArchive war = TestUtil.prepareArchive(NO_EXPAND);
        war.addClass(ExternalParameterEntityWrapper.class);
        war.addAsWebInfResource(ExternalParameterEntityTest.class.getPackage(), "ExternalParameterEntityNoExpandWeb.xml",
                "web.xml");
        return TestUtil.finishContainerPrepare(war, null, ExternalParameterEntityResource.class);
    }

    @BeforeEach
    public void init() {
        client = (ResteasyClient) ClientBuilder.newClient();
    }

    @AfterEach
    public void after() throws Exception {
        client.close();
    }

    /**
     * @tpTestDetails "resteasy.document.expand.entity.references" is set to "true"
     * @tpPassCrit Passwd file should be returned by request.
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testExternalParameterEntityExpand() throws Exception {
        logger.info(String.format("Request body: %s", this.request.replace('\r', '\n')));
        Response response = client.target(generateURL("/test", EXPAND)).request()
                .post(Entity.entity(this.request, MediaType.APPLICATION_XML));
        Assertions.assertEquals(HttpResponseCodes.SC_OK, response.getStatus());
        String entity = response.readEntity(String.class);
        logger.info(String.format("Result: \"%s\"", entity.replace('\r', '\n')));
        Assertions.assertEquals("root:x:0:0:root:/root:/bin/bash", entity.trim());
    }

    /**
     * @tpTestDetails "resteasy.document.expand.entity.references" is set to "false"
     * @tpPassCrit Passwd file should not be returned by request.
     * @tpSince RESTEasy 3.0.16
     */
    @Test
    public void testExternalParameterEntityNoExpand() throws Exception {
        logger.info(String.format("Request body: %s", this.request.replace('\r', '\n')));
        Response response = client.target(generateURL("/test", NO_EXPAND)).request()
                .post(Entity.entity(this.request, MediaType.APPLICATION_XML));
        // Expect a 500 because resteasy.document.expand.entity.references is set to false
        Assertions.assertEquals(HttpResponseCodes.SC_INTERNAL_SERVER_ERROR, response.getStatus());
        String entity = response.readEntity(String.class);
        logger.info(String.format("Result: \"%s\"", entity.replace('\r', '\n')));
        Assertions.assertEquals("", entity.trim());
    }
}
