1+ /*
2+ * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
3+ *
4+ * This program and the accompanying materials are made available under the
5+ * terms of the Eclipse Public License v. 2.0, which is available at
6+ * http://www.eclipse.org/legal/epl-2.0.
7+ *
8+ * This Source Code may also be made available under the following Secondary
9+ * Licenses when the conditions for such availability set forth in the
10+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+ * version 2 with the GNU Classpath Exception, which is available at
12+ * https://www.gnu.org/software/classpath/license.html.
13+ *
14+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+ */
16+
17+ package org .glassfish .jersey .tests .e2e .client ;
18+
19+ import com .sun .net .httpserver .HttpExchange ;
20+ import com .sun .net .httpserver .HttpHandler ;
21+ import com .sun .net .httpserver .HttpServer ;
22+
23+ import java .io .BufferedReader ;
24+ import java .io .IOException ;
25+ import java .io .InputStream ;
26+ import java .io .InputStreamReader ;
27+ import java .io .OutputStream ;
28+ import java .net .InetSocketAddress ;
29+ import java .nio .charset .StandardCharsets ;
30+ import java .nio .file .Files ;
31+ import java .nio .file .Path ;
32+ import java .nio .file .Paths ;
33+ import java .nio .file .StandardCopyOption ;
34+ import java .util .UUID ;
35+ import java .util .concurrent .Executors ;
36+
37+
38+ /**
39+ * Server for the file upload test that redirects from /submit to /upload.
40+ */
41+ class RedirectFileUploadServerTest {
42+ private static final String UPLOAD_DIRECTORY = "target/uploads" ;
43+ private static final String BOUNDARY_PREFIX = "boundary=" ;
44+ private static final Path uploadDir = Paths .get (UPLOAD_DIRECTORY );
45+
46+ private static HttpServer server ;
47+
48+
49+ static void start (int port ) throws IOException {
50+ // Create upload directory if it doesn't exist
51+ if (!Files .exists (uploadDir )) {
52+ Files .createDirectory (uploadDir );
53+ }
54+
55+ // Create HTTP server
56+ server = HttpServer .create (new InetSocketAddress (port ), 0 );
57+
58+ // Create contexts for different endpoints
59+ server .createContext ("/submit" , new SubmitHandler ());
60+ server .createContext ("/upload" , new UploadHandler ());
61+
62+ // Set executor and start server
63+ server .setExecutor (Executors .newFixedThreadPool (10 ));
64+ server .start ();
65+ System .out .println ("Server running on port " + port );
66+ }
67+
68+ public static void stop () {
69+ server .stop (0 );
70+ }
71+
72+
73+ // Handler for /submit endpoint that redirects to /upload
74+ static class SubmitHandler implements HttpHandler {
75+ @ Override
76+ public void handle (HttpExchange exchange ) throws IOException {
77+ try {
78+ if (!"POST" .equals (exchange .getRequestMethod ())) {
79+ sendResponse (exchange , 405 , "Method Not Allowed. Only POST is supported." );
80+ return ;
81+ }
82+
83+ final BufferedReader reader
84+ = new BufferedReader (new InputStreamReader (exchange .getRequestBody (), StandardCharsets .UTF_8 ));
85+ while (reader .readLine () != null ) {
86+ //discard payload - required for JDK 1.8
87+ }
88+ reader .close ();
89+
90+ // Send a 307 Temporary Redirect to /upload
91+ // This preserves the POST method and body in the redirect
92+ exchange .getResponseHeaders ().add ("Location" , "/upload" );
93+ exchange .sendResponseHeaders (307 , -1 );
94+ } finally {
95+ exchange .close ();
96+ }
97+ }
98+ }
99+
100+ // Handler for /upload endpoint that processes file uploads
101+ static class UploadHandler implements HttpHandler {
102+ @ Override
103+ public void handle (HttpExchange exchange ) throws IOException {
104+ try {
105+ if (!"POST" .equals (exchange .getRequestMethod ())) {
106+ sendResponse (exchange , 405 , "Method Not Allowed. Only POST is supported." );
107+ return ;
108+ }
109+
110+ // Check if the request contains multipart form data
111+ String contentType = exchange .getRequestHeaders ().getFirst ("Content-Type" );
112+ if (contentType == null || !contentType .startsWith ("multipart/form-data" )) {
113+ sendResponse (exchange , 400 , "Bad Request. Content type must be multipart/form-data." );
114+ return ;
115+ }
116+
117+ // Extract boundary from content type
118+ String boundary = extractBoundary (contentType );
119+ if (boundary == null ) {
120+ sendResponse (exchange , 400 , "Bad Request. Could not determine boundary." );
121+ return ;
122+ }
123+
124+ // Process the multipart request and save the file
125+ String fileName = processMultipartRequest (exchange , boundary );
126+
127+ if (fileName != null ) {
128+ sendResponse (exchange , 200 , "File uploaded successfully: " + fileName );
129+ } else {
130+ sendResponse (exchange , 400 , "Bad Request. No file found in request." );
131+ }
132+ } catch (Exception e ) {
133+ e .printStackTrace ();
134+ sendResponse (exchange , 500 , "Internal Server Error: " + e .getMessage ());
135+ } finally {
136+ exchange .close ();
137+ Files .deleteIfExists (uploadDir );
138+ }
139+ }
140+
141+ private String extractBoundary (String contentType ) {
142+ int boundaryIndex = contentType .indexOf (BOUNDARY_PREFIX );
143+ if (boundaryIndex != -1 ) {
144+ return "--" + contentType .substring (boundaryIndex + BOUNDARY_PREFIX .length ());
145+ }
146+ return null ;
147+ }
148+
149+ private String processMultipartRequest (HttpExchange exchange , String boundary ) throws IOException {
150+ InputStream requestBody = exchange .getRequestBody ();
151+ BufferedReader reader = new BufferedReader (new InputStreamReader (requestBody , StandardCharsets .UTF_8 ));
152+
153+ String line ;
154+ String fileName = null ;
155+ Path tempFile = null ;
156+ boolean isFileContent = false ;
157+
158+ // Generate a random filename for the temporary file
159+ String tempFileName = UUID .randomUUID ().toString ();
160+ tempFile = Files .createTempFile (tempFileName , ".tmp" );
161+
162+ try (OutputStream fileOut = Files .newOutputStream (tempFile )) {
163+ while ((line = reader .readLine ()) != null ) {
164+ // Check for the boundary
165+ if (line .startsWith (boundary )) {
166+ if (isFileContent ) {
167+ // We've reached the end of the file content
168+ break ;
169+ }
170+
171+ // Read the next line (Content-Disposition)
172+ line = reader .readLine ();
173+ if (line != null && line .startsWith ("Content-Type" )) {
174+ line = reader .readLine ();
175+ }
176+ if (line != null && line .contains ("filename=" )) {
177+ // Extract filename
178+ int filenameStart = line .indexOf ("filename=\" " ) + 10 ;
179+ int filenameEnd = line .indexOf ("\" " , filenameStart );
180+ fileName = line .substring (filenameStart , filenameEnd );
181+
182+ // Skip Content-Type line and empty line
183+ reader .readLine (); // Content-Type
184+ // System.out.println(reader.readLine()); // Empty line
185+ isFileContent = true ;
186+ }
187+ } else if (isFileContent ) {
188+ // If we're reading file content and this line is not a boundary,
189+ // write it to the file (append a newline unless it's the first line)
190+ fileOut .write (line .getBytes (StandardCharsets .UTF_8 ));
191+ fileOut .write ('\n' );
192+ }
193+ }
194+ }
195+
196+ // If we found a file, move it from the temp location to the uploads directory
197+ if (fileName != null && !fileName .isEmpty ()) {
198+ Path targetPath = Paths .get (UPLOAD_DIRECTORY , fileName );
199+ Files .move (tempFile , targetPath , StandardCopyOption .REPLACE_EXISTING );
200+ return fileName ;
201+ } else {
202+ // If no file was found, delete the temp file
203+ Files .deleteIfExists (tempFile );
204+ return null ;
205+ }
206+ }
207+ }
208+
209+ // Helper method to send HTTP responses
210+ private static void sendResponse (HttpExchange exchange , int statusCode , String response ) throws IOException {
211+ exchange .getResponseHeaders ().set ("Content-Type" , "text/plain; charset=UTF-8" );
212+ exchange .sendResponseHeaders (statusCode , response .length ());
213+ try (OutputStream os = exchange .getResponseBody ()) {
214+ os .write (response .getBytes (StandardCharsets .UTF_8 ));
215+ }
216+ }
217+ }
0 commit comments