RedirectFileUploadServerTest.java

/*
 * Copyright (c) 2025 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.tests.e2e.client;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
import java.util.concurrent.Executors;


/**
 * Server for the file upload test that redirects from /submit to /upload.
 */
class RedirectFileUploadServerTest {
    private static final String UPLOAD_DIRECTORY = "target/uploads";
    private static final String BOUNDARY_PREFIX = "boundary=";
    private static final Path uploadDir = Paths.get(UPLOAD_DIRECTORY);

    private static HttpServer server;


    static void start(int port) throws IOException {
        // Create upload directory if it doesn't exist
        if (!Files.exists(uploadDir)) {
            Files.createDirectory(uploadDir);
        }

        // Create HTTP server
        server = HttpServer.create(new InetSocketAddress(port), 0);

        // Create contexts for different endpoints
        server.createContext("/submit", new SubmitHandler());
        server.createContext("/upload", new UploadHandler());

        // Set executor and start server
        server.setExecutor(Executors.newFixedThreadPool(10));
        server.start();
        System.out.println("Server running on port " + port);
    }

    public static void stop() {
        server.stop(0);
    }


    // Handler for /submit endpoint that redirects to /upload
    static class SubmitHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            try {
                if (!"POST".equals(exchange.getRequestMethod())) {
                    sendResponse(exchange, 405, "Method Not Allowed. Only POST is supported.");
                    return;
                }

                final BufferedReader reader
                        = new BufferedReader(new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8));
                while (reader.readLine() != null) {
                    //discard payload - required for JDK 1.8
                }
                reader.close();

                // Send a 307 Temporary Redirect to /upload
                // This preserves the POST method and body in the redirect
                exchange.getResponseHeaders().add("Location", "/upload");
                exchange.sendResponseHeaders(307, -1);
            } finally {
                exchange.close();
            }
        }
    }

    // Handler for /upload endpoint that processes file uploads
    static class UploadHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            try {
                if (!"POST".equals(exchange.getRequestMethod())) {
                    sendResponse(exchange, 405, "Method Not Allowed. Only POST is supported.");
                    return;
                }

                // Check if the request contains multipart form data
                String contentType = exchange.getRequestHeaders().getFirst("Content-Type");
                if (contentType == null || !contentType.startsWith("multipart/form-data")) {
                    sendResponse(exchange, 400, "Bad Request. Content type must be multipart/form-data.");
                    return;
                }

                // Extract boundary from content type
                String boundary = extractBoundary(contentType);
                if (boundary == null) {
                    sendResponse(exchange, 400, "Bad Request. Could not determine boundary.");
                    return;
                }

                // Process the multipart request and save the file
                String fileName = processMultipartRequest(exchange, boundary);

                if (fileName != null) {
                    sendResponse(exchange, 200, "File uploaded successfully: " + fileName);
                } else {
                    sendResponse(exchange, 400, "Bad Request. No file found in request.");
                }
            } catch (Exception e) {
                e.printStackTrace();
                sendResponse(exchange, 500, "Internal Server Error: " + e.getMessage());
            } finally {
                exchange.close();
                Files.deleteIfExists(uploadDir);
            }
        }

        private String extractBoundary(String contentType) {
            int boundaryIndex = contentType.indexOf(BOUNDARY_PREFIX);
            if (boundaryIndex != -1) {
                return "--" + contentType.substring(boundaryIndex + BOUNDARY_PREFIX.length());
            }
            return null;
        }

        private String processMultipartRequest(HttpExchange exchange, String boundary) throws IOException {
            InputStream requestBody = exchange.getRequestBody();
            BufferedReader reader = new BufferedReader(new InputStreamReader(requestBody, StandardCharsets.UTF_8));

            String line;
            String fileName = null;
            Path tempFile = null;
            boolean isFileContent = false;

            // Generate a random filename for the temporary file
            String tempFileName = UUID.randomUUID().toString();
            tempFile = Files.createTempFile(tempFileName, ".tmp");

            try (OutputStream fileOut = Files.newOutputStream(tempFile)) {
                while ((line = reader.readLine()) != null) {
                    // Check for the boundary
                    if (line.startsWith(boundary)) {
                        if (isFileContent) {
                            // We've reached the end of the file content
                            break;
                        }

                        // Read the next line (Content-Disposition)
                        line = reader.readLine();
                        if (line != null && line.startsWith("Content-Type")) {
                            line = reader.readLine();
                        }
                        if (line != null && line.contains("filename=")) {
                            // Extract filename
                            int filenameStart = line.indexOf("filename=\"") + 10;
                            int filenameEnd = line.indexOf("\"", filenameStart);
                            fileName = line.substring(filenameStart, filenameEnd);

                            // Skip Content-Type line and empty line
                            reader.readLine(); // Content-Type
//                            System.out.println(reader.readLine()); // Empty line
                            isFileContent = true;
                        }
                    } else if (isFileContent) {
                        // If we're reading file content and this line is not a boundary,
                        // write it to the file (append a newline unless it's the first line)
                        fileOut.write(line.getBytes(StandardCharsets.UTF_8));
                        fileOut.write('\n');
                    }
                }
            }

            // If we found a file, move it from the temp location to the uploads directory
            if (fileName != null && !fileName.isEmpty()) {
                Path targetPath = Paths.get(UPLOAD_DIRECTORY, fileName);
                Files.move(tempFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
                return fileName;
            } else {
                // If no file was found, delete the temp file
                Files.deleteIfExists(tempFile);
                return null;
            }
        }
    }

    // Helper method to send HTTP responses
    private static void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException {
        exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
        exchange.sendResponseHeaders(statusCode, response.length());
        try (OutputStream os = exchange.getResponseBody()) {
            os.write(response.getBytes(StandardCharsets.UTF_8));
        }
    }
}