Skip to content

Commit 0150317

Browse files
committed
fix: address code review feedback
- Sanitize Content-Disposition filename to prevent path traversal (Paths.get(filename).getFileName() strips directory components) - Add null check for response.body() to handle 204/205 empty responses - Fix regex to support quoted filenames with spaces (e.g. filename="my invoice.pdf")
1 parent e5829c4 commit 0150317

3 files changed

Lines changed: 36 additions & 9 deletions

File tree

modules/openapi-generator/src/main/resources/Java/libraries/feign/ApiResponseDecoder.mustache

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import java.io.InputStream;
1313
import java.lang.reflect.ParameterizedType;
1414
import java.lang.reflect.Type;
1515
import java.nio.file.Files;
16+
import java.nio.file.Paths;
1617
import java.nio.file.StandardCopyOption;
1718
import java.util.Collection;
1819
import java.util.Collections;
@@ -25,7 +26,7 @@ import {{modelPackage}}.ApiResponse;
2526
public class ApiResponseDecoder extends JacksonDecoder {
2627
2728
private static final Pattern FILENAME_PATTERN =
28-
Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?");
29+
Pattern.compile("filename=\"([^\"]+)\"|filename=([^\\s;]+)");
2930
3031
public ApiResponseDecoder(ObjectMapper mapper) {
3132
super(mapper);
@@ -58,6 +59,9 @@ public class ApiResponseDecoder extends JacksonDecoder {
5859

5960
private Object decodeBinary(Response response, Type type) throws IOException {
6061
Class<?> raw = Types.getRawType(type);
62+
if (response.body() == null) {
63+
return null;
64+
}
6165
if (byte[].class.isAssignableFrom(raw)) {
6266
return response.body().asInputStream().readAllBytes();
6367
}
@@ -71,8 +75,10 @@ public class ApiResponseDecoder extends JacksonDecoder {
7175
String filename = extractFilename(response);
7276
File file;
7377
if (filename != null) {
78+
// Sanitize: strip path components to prevent path traversal
79+
String safeName = Paths.get(filename).getFileName().toString();
7480
java.nio.file.Path tempDir = Files.createTempDirectory("feign-download");
75-
file = Files.createFile(tempDir.resolve(filename)).toFile();
81+
file = Files.createFile(tempDir.resolve(safeName)).toFile();
7682
tempDir.toFile().deleteOnExit();
7783
} else {
7884
file = Files.createTempFile("download-", "").toFile();
@@ -89,7 +95,10 @@ public class ApiResponseDecoder extends JacksonDecoder {
8995
if (dispositions == null) return null;
9096
for (String disposition : dispositions) {
9197
Matcher m = FILENAME_PATTERN.matcher(disposition);
92-
if (m.find()) return m.group(1);
98+
if (m.find()) {
99+
// Group 1: quoted filename (may contain spaces), Group 2: unquoted token
100+
return m.group(1) != null ? m.group(1) : m.group(2);
101+
}
93102
}
94103
return null;
95104
}

samples/client/petstore/java/feign-no-nullable/src/main/java/org/openapitools/client/ApiResponseDecoder.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.reflect.ParameterizedType;
2525
import java.lang.reflect.Type;
2626
import java.nio.file.Files;
27+
import java.nio.file.Paths;
2728
import java.nio.file.StandardCopyOption;
2829
import java.util.Collection;
2930
import java.util.Collections;
@@ -36,7 +37,7 @@
3637
public class ApiResponseDecoder extends JacksonDecoder {
3738

3839
private static final Pattern FILENAME_PATTERN =
39-
Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?");
40+
Pattern.compile("filename=\"([^\"]+)\"|filename=([^\\s;]+)");
4041

4142
public ApiResponseDecoder(ObjectMapper mapper) {
4243
super(mapper);
@@ -69,6 +70,9 @@ private boolean isBinaryType(Type type) {
6970

7071
private Object decodeBinary(Response response, Type type) throws IOException {
7172
Class<?> raw = Types.getRawType(type);
73+
if (response.body() == null) {
74+
return null;
75+
}
7276
if (byte[].class.isAssignableFrom(raw)) {
7377
return response.body().asInputStream().readAllBytes();
7478
}
@@ -82,8 +86,10 @@ private File downloadToTempFile(Response response) throws IOException {
8286
String filename = extractFilename(response);
8387
File file;
8488
if (filename != null) {
89+
// Sanitize: strip path components to prevent path traversal
90+
String safeName = Paths.get(filename).getFileName().toString();
8591
java.nio.file.Path tempDir = Files.createTempDirectory("feign-download");
86-
file = Files.createFile(tempDir.resolve(filename)).toFile();
92+
file = Files.createFile(tempDir.resolve(safeName)).toFile();
8793
tempDir.toFile().deleteOnExit();
8894
} else {
8995
file = Files.createTempFile("download-", "").toFile();
@@ -100,7 +106,10 @@ private String extractFilename(Response response) {
100106
if (dispositions == null) return null;
101107
for (String disposition : dispositions) {
102108
Matcher m = FILENAME_PATTERN.matcher(disposition);
103-
if (m.find()) return m.group(1);
109+
if (m.find()) {
110+
// Group 1: quoted filename (may contain spaces), Group 2: unquoted token
111+
return m.group(1) != null ? m.group(1) : m.group(2);
112+
}
104113
}
105114
return null;
106115
}

samples/client/petstore/java/feign/src/main/java/org/openapitools/client/ApiResponseDecoder.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.lang.reflect.ParameterizedType;
2525
import java.lang.reflect.Type;
2626
import java.nio.file.Files;
27+
import java.nio.file.Paths;
2728
import java.nio.file.StandardCopyOption;
2829
import java.util.Collection;
2930
import java.util.Collections;
@@ -36,7 +37,7 @@
3637
public class ApiResponseDecoder extends JacksonDecoder {
3738

3839
private static final Pattern FILENAME_PATTERN =
39-
Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?");
40+
Pattern.compile("filename=\"([^\"]+)\"|filename=([^\\s;]+)");
4041

4142
public ApiResponseDecoder(ObjectMapper mapper) {
4243
super(mapper);
@@ -69,6 +70,9 @@ private boolean isBinaryType(Type type) {
6970

7071
private Object decodeBinary(Response response, Type type) throws IOException {
7172
Class<?> raw = Types.getRawType(type);
73+
if (response.body() == null) {
74+
return null;
75+
}
7276
if (byte[].class.isAssignableFrom(raw)) {
7377
return response.body().asInputStream().readAllBytes();
7478
}
@@ -82,8 +86,10 @@ private File downloadToTempFile(Response response) throws IOException {
8286
String filename = extractFilename(response);
8387
File file;
8488
if (filename != null) {
89+
// Sanitize: strip path components to prevent path traversal
90+
String safeName = Paths.get(filename).getFileName().toString();
8591
java.nio.file.Path tempDir = Files.createTempDirectory("feign-download");
86-
file = Files.createFile(tempDir.resolve(filename)).toFile();
92+
file = Files.createFile(tempDir.resolve(safeName)).toFile();
8793
tempDir.toFile().deleteOnExit();
8894
} else {
8995
file = Files.createTempFile("download-", "").toFile();
@@ -100,7 +106,10 @@ private String extractFilename(Response response) {
100106
if (dispositions == null) return null;
101107
for (String disposition : dispositions) {
102108
Matcher m = FILENAME_PATTERN.matcher(disposition);
103-
if (m.find()) return m.group(1);
109+
if (m.find()) {
110+
// Group 1: quoted filename (may contain spaces), Group 2: unquoted token
111+
return m.group(1) != null ? m.group(1) : m.group(2);
112+
}
104113
}
105114
return null;
106115
}

0 commit comments

Comments
 (0)