MultiPartReaderWriterTest.java

/*
 * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.media.multipart.internal;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.Collections;
import java.util.Set;

import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.BodyPartEntity;
import org.glassfish.jersey.media.multipart.MultiPart;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertThrows;

/**
 * Unit tests for {@link org.glassfish.jersey.media.multipart.internal.MultiPartReaderClientSide} (in the client) and
 * {@link org.glassfish.jersey.media.multipart.internal.MultiPartWriter} (in the server)
 .*/
public class MultiPartReaderWriterTest extends MultiPartJerseyTest {

    private static Path TMP_DIRECTORY;
    private static String ORIGINAL_TMP_DIRECTORY;

    @BeforeEach
    public void setUp() throws Exception {
        super.setUp();

        ORIGINAL_TMP_DIRECTORY = System.getProperty("java.io.tmpdir");

        TMP_DIRECTORY = Files.createTempDirectory(MultiPartReaderWriterTest.class.getName());
        System.setProperty("java.io.tmpdir", TMP_DIRECTORY.toString());
    }

    @AfterEach
    public void tearDown() throws Exception {
        super.tearDown();

        try {
            Files.delete(TMP_DIRECTORY);
        } finally {
            System.setProperty("java.io.tmpdir", ORIGINAL_TMP_DIRECTORY);
        }
    }

    @Override
    protected Set<Class<?>> getResourceClasses() {
        return Collections.<Class<?>>singleton(MultiPartResource.class);
    }

    @Test
    public void testZero() {
        final WebTarget target = target().path("multipart").path("zero");
        final String result = target.request("text/plain").get(String.class);
        assertEquals("Hello, world\r\n", result);
    }

    @Test
    public void testOne() {
        final WebTarget target = target().path("multipart/one");

        try {
            final MultiPart result = target.request("multipart/mixed").get(MultiPart.class);
            checkMediaType(new MediaType("multipart", "mixed"), result.getMediaType());
            assertEquals(1, result.getBodyParts().size());
            final BodyPart part = result.getBodyParts().get(0);
            checkMediaType(new MediaType("text", "plain"), part.getMediaType());
            checkEntity("This is the only segment", (BodyPartEntity) part.getEntity());

            result.getParameterizedHeaders();
            result.cleanup();
        } catch (final IOException | ParseException e) {
            e.printStackTrace(System.out);
            fail("Caught exception: " + e);
        }
    }

    @Test
    public void testETag() {
        final WebTarget target = target().path("multipart/etag");

        try {
            final MultiPart result = target.request("multipart/mixed").get(MultiPart.class);
            checkMediaType(new MediaType("multipart", "mixed"), result.getMediaType());
            assertEquals(1, result.getBodyParts().size());
            final BodyPart part = result.getBodyParts().get(0);
            checkMediaType(new MediaType("text", "plain"), part.getMediaType());
            checkEntity("This is the only segment", (BodyPartEntity) part.getEntity());
            assertEquals("\"value\"", part.getHeaders().getFirst("ETag"));

            result.getParameterizedHeaders();
            result.cleanup();
        } catch (final IOException | ParseException e) {
            e.printStackTrace(System.out);
            fail("Caught exception: " + e);
        }
    }

    @Test
    public void testTwo() {
        final WebTarget target = target().path("multipart/two");
        try {
            final MultiPart result = target.request("multipart/mixed").get(MultiPart.class);
            checkMediaType(new MediaType("multipart", "mixed"), result.getMediaType());
            assertEquals(2, result.getBodyParts().size());
            final BodyPart part1 = result.getBodyParts().get(0);
            checkMediaType(new MediaType("text", "plain"), part1.getMediaType());
            checkEntity("This is the first segment", (BodyPartEntity) part1.getEntity());
            final BodyPart part2 = result.getBodyParts().get(1);
            checkMediaType(new MediaType("text", "xml"), part2.getMediaType());
            checkEntity("<outer><inner>value</inner></outer>", (BodyPartEntity) part2.getEntity());

            result.getParameterizedHeaders();
            result.cleanup();
        } catch (final IOException | ParseException e) {
            e.printStackTrace(System.out);
            fail("Caught exception: " + e);
        }
    }

    @Test
    public void testThree() {
        final WebTarget target = target().path("multipart/three");
        try {
            final MultiPart result = target.request("multipart/mixed").get(MultiPart.class);
            checkMediaType(new MediaType("multipart", "mixed"), result.getMediaType());
            assertEquals(2, result.getBodyParts().size());
            final BodyPart part1 = result.getBodyParts().get(0);
            checkMediaType(new MediaType("text", "plain"), part1.getMediaType());
            checkEntity("This is the first segment", (BodyPartEntity) part1.getEntity());
            final BodyPart part2 = result.getBodyParts().get(1);
            checkMediaType(new MediaType("x-application", "x-format"), part2.getMediaType());
            final MultiPartBean entity = part2.getEntityAs(MultiPartBean.class);
            assertEquals("myname", entity.getName());
            assertEquals("myvalue", entity.getValue());

            result.getParameterizedHeaders();
            result.cleanup();
        } catch (final IOException | ParseException e) {
            e.printStackTrace(System.out);
            fail("Caught exception: " + e);
        }
    }

    @Test
    public void testFour() {
        final WebTarget target = target().path("multipart/four");

        final MultiPartBean bean = new MultiPartBean("myname", "myvalue");
        final MultiPart entity = new MultiPart()
                .bodyPart("This is the first segment", new MediaType("text", "plain"))
                .bodyPart(bean, new MediaType("x-application", "x-format"));
        final String response = target.request("text/plain").put(Entity.entity(entity, "multipart/mixed"), String.class);
        if (!response.startsWith("SUCCESS:")) {
            fail("Response is '" + response + "'");
        }
    }

    @Test
    public void testFourBiz() {
        final WebTarget target = target().path("multipart/four");

        final MultiPartBean bean = new MultiPartBean("myname", "myvalue");
        final MultiPart entity = new MultiPart()
                .bodyPart("This is the first segment", new MediaType("text", "plain"))
                .bodyPart(bean, new MediaType("x-application", "x-format"));
        final String response = target.request("text/plain").header("Content-Type", "multipart/mixed")
                .put(Entity.entity(entity, "multipart/mixed"), String.class);
        if (!response.startsWith("SUCCESS:")) {
            fail("Response is '" + response + "'");
        }
    }

    /**
     * Test sending a completely empty MultiPart.
     */
    @Test
    public void testSix() {
        assertThrows(ProcessingException.class, () -> {
            target()
                    .path("multipart/six")
                    .request("text/plain")
                    .post(Entity.entity(new MultiPart(), "multipart/mixed"), String.class);
            fail("Should have thrown an exception about zero body parts");
        });
    }

    /**
     * Zero length body part.
     */
    @Test
    public void testTen() {
        final WebTarget target = target().path("multipart/ten");

        final MultiPartBean bean = new MultiPartBean("myname", "myvalue");
        final MultiPart entity = new MultiPart()
                .bodyPart(bean, new MediaType("x-application", "x-format"))
                .bodyPart("", MediaType.APPLICATION_OCTET_STREAM_TYPE);
        final String response = target.request("text/plain").put(Entity.entity(entity, "multipart/mixed"), String.class);

        if (!response.startsWith("SUCCESS:")) {
            fail("Response is '" + response + "'");
        }
    }

    /**
     * Echo back various sized body part entities, and check size/content
     */
    @Test
    public void testEleven() throws Exception {
        final String seed = "0123456789ABCDEF";
        checkEleven(seed, 0);
        checkEleven(seed, 1);
        checkEleven(seed, 10);
        checkEleven(seed, 100);
        checkEleven(seed, 1000);
        checkEleven(seed, 10000);
        checkEleven(seed, 100000);
    }

    /**
     * Echo back the multipart that was sent.
     */
    @Test
    public void testTwelve() throws Exception {
        final WebTarget target = target().path("multipart/twelve");

        final MultiPart entity = new MultiPart().bodyPart("CONTENT", MediaType.TEXT_PLAIN_TYPE);
        final MultiPart response = target.request("multipart/mixed")
                .put(Entity.entity(entity, "multipart/mixed"), MultiPart.class);
        final String actual = response.getBodyParts().get(0).getEntityAs(String.class);
        assertEquals("CONTENT", actual);
        response.cleanup();
    }

    /**
     * Call clean up explicitly.
     */
    @Test
    public void testThirteen() {
        final WebTarget target = target().path("multipart/thirteen");

        final MultiPart entity = new MultiPart()
                .bodyPart("CONTENT", MediaType.TEXT_PLAIN_TYPE);
        final String response = target.request("multipart/mixed").put(Entity.entity(entity, "multipart/mixed"), String.class);
        assertEquals("cleanup", response);
    }

    /**
     * Test for JERSEY-2515 - Jersey should not leave any temporary files after verifying that it is possible
     * to create files in java.io.tmpdir.
     */
    @Test
    @SuppressWarnings("ConstantConditions")
    public void shouldNotBeAnyTemporaryFiles() {
        // Make sure the MBR is initialized on the client-side as well.
        target().request().get();
        assertEquals(0, TMP_DIRECTORY.toFile().listFiles().length);
    }

    private void checkEntity(final String expected, final BodyPartEntity entity) throws IOException {
        // Convert the raw bytes into a String
        final InputStreamReader sr = new InputStreamReader(entity.getInputStream());
        final StringWriter sw = new StringWriter();
        while (true) {
            final int ch = sr.read();
            if (ch < 0) {
                break;
            }
            sw.append((char) ch);
        }
        // Perform the comparison
        assertEquals(expected, sw.toString());
    }

    private void checkEleven(final String seed, final int multiplier) throws Exception {
        final StringBuilder sb = new StringBuilder(seed.length() * multiplier);
        for (int i = 0; i < multiplier; i++) {
            sb.append(seed);
        }
        final String expected = sb.toString();

        final WebTarget target = target().path("multipart/eleven");

        final MultiPart entity = new MultiPart().bodyPart(expected, MediaType.TEXT_PLAIN_TYPE);
        final MultiPart response = target.request("multipart/mixed")
                .put(Entity.entity(entity, "multipart/mixed"), MultiPart.class);
        final String actual = response.getBodyParts().get(0).getEntityAs(String.class);
        assertEquals(expected.length(), actual.length(), "Length for multiplier " + multiplier);
        assertEquals(expected, actual, "Content for multiplier " + multiplier);
        response.cleanup();
    }

    private void checkMediaType(final MediaType expected, final MediaType actual) {
        assertEquals(expected.getType(), actual.getType(), "Expected MediaType=" + expected);
        assertEquals(expected.getSubtype(), actual.getSubtype(), "Expected MediaType=" + expected);
    }

}