Skip to content

Commit 1b8334e

Browse files
fix: remove file from online generator
1 parent dc839b8 commit 1b8334e

File tree

3 files changed

+161
-33
lines changed

3 files changed

+161
-33
lines changed

modules/swagger-generator/pom.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@
146146
<configuration>
147147
<url>https://github.com/swagger-api/swagger-ui/archive/master.tar.gz</url>
148148
<unpack>true</unpack>
149-
<!--<skipCache>true</skipCache>-->
150149
<outputDirectory>${project.build.directory}</outputDirectory>
151150
</configuration>
152151
</execution>
@@ -327,6 +326,18 @@
327326
<version>${junit-version}</version>
328327
<scope>test</scope>
329328
</dependency>
329+
<dependency>
330+
<groupId>org.mockito</groupId>
331+
<artifactId>mockito-core</artifactId>
332+
<version>5.20.0</version>
333+
<scope>test</scope>
334+
</dependency>
335+
<dependency>
336+
<groupId>org.mockito</groupId>
337+
<artifactId>mockito-inline</artifactId>
338+
<version>5.2.0</version>
339+
<scope>test</scope>
340+
</dependency>
330341
</dependencies>
331342
<properties>
332343
<servlet-api-version>2.5</servlet-api-version>

modules/swagger-generator/src/main/java/io/swagger/generator/resource/SwaggerResource.java

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,43 @@
88
import io.swagger.codegen.CodegenConfig;
99
import io.swagger.codegen.CodegenType;
1010
import io.swagger.codegen.utils.SecureFileUtils;
11+
import io.swagger.generator.exception.ApiException;
1112
import io.swagger.generator.exception.BadRequestException;
1213
import io.swagger.generator.model.Generated;
1314
import io.swagger.generator.model.GeneratorInput;
1415
import io.swagger.generator.model.ResponseCode;
1516
import io.swagger.generator.online.Generator;
1617
import org.apache.commons.io.FileUtils;
1718
import org.apache.commons.lang3.StringUtils;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
1821

1922
import javax.servlet.http.HttpServletRequest;
20-
import javax.ws.rs.*;
23+
import javax.ws.rs.GET;
24+
import javax.ws.rs.POST;
25+
import javax.ws.rs.Path;
26+
import javax.ws.rs.PathParam;
27+
import javax.ws.rs.Produces;
2128
import javax.ws.rs.core.Context;
2229
import javax.ws.rs.core.MediaType;
2330
import javax.ws.rs.core.Response;
2431
import java.io.File;
25-
import java.util.*;
32+
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.HashMap;
35+
import java.util.List;
36+
import java.util.Map;
37+
import java.util.UUID;
2638

2739
@Path("/gen")
28-
@Api(value = "/gen", description = "Resource for generating swagger components")
40+
@Api(value = "/gen")
2941
@SuppressWarnings("static-method")
3042
public class SwaggerResource {
31-
static List<String> clients = new ArrayList<String>();
32-
static List<String> servers = new ArrayList<String>();
33-
private static Map<String, Generated> fileMap = new HashMap<String, Generated>();
43+
private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerResource.class);
44+
45+
static List<String> clients = new ArrayList<>();
46+
static List<String> servers = new ArrayList<>();
47+
private static Map<String, Generated> fileMap = new HashMap<>();
3448

3549
static {
3650
List<CodegenConfig> extensions = Codegen.getExtensions();
@@ -43,8 +57,8 @@ public class SwaggerResource {
4357
}
4458
}
4559

46-
Collections.sort(clients, String.CASE_INSENSITIVE_ORDER);
47-
Collections.sort(servers, String.CASE_INSENSITIVE_ORDER);
60+
clients.sort(String.CASE_INSENSITIVE_ORDER);
61+
servers.sort(String.CASE_INSENSITIVE_ORDER);
4862
}
4963

5064
@GET
@@ -55,20 +69,16 @@ public class SwaggerResource {
5569
notes = "A valid `fileId` is generated by the `/clients/{language}` or `/servers/{language}` POST "
5670
+ "operations. The fileId code can be used just once, after which a new `fileId` will need to "
5771
+ "be requested.", response = String.class, tags = {"clients", "servers"})
58-
public Response downloadFile(@PathParam("fileId") String fileId) throws Exception {
72+
public Response downloadFile(@PathParam("fileId") String fileId) {
5973
Generated g = fileMap.get(fileId);
60-
System.out.println("looking for fileId " + fileId);
61-
System.out.println("got filename " + g.getFilename());
62-
if (g.getFilename() != null) {
74+
LOGGER.info("Looking for fileId: {}", fileId);
75+
if (g != null && g.getFilename() != null) {
76+
LOGGER.info("Got filename: {}", g.getFilename());
6377
SecureFileUtils.validatePath(g.getFilename());
64-
File file = new java.io.File(g.getFilename());
65-
byte[] bytes = org.apache.commons.io.FileUtils.readFileToByteArray(file);
78+
final File file = new java.io.File(g.getFilename());
6679

67-
try {
68-
FileUtils.deleteDirectory(file.getParentFile());
69-
} catch (Exception e) {
70-
System.out.println("failed to delete file " + file.getAbsolutePath());
71-
}
80+
byte[] bytes = getFileBytes(file);
81+
removeFile(fileId, file);
7282

7383
return Response
7484
.ok(bytes, "application/zip")
@@ -80,6 +90,23 @@ public Response downloadFile(@PathParam("fileId") String fileId) throws Exceptio
8090
}
8191
}
8292

93+
private static byte[] getFileBytes(File file) {
94+
try {
95+
return FileUtils.readFileToByteArray(file);
96+
} catch (IOException e) {
97+
throw new IllegalStateException("Cannot read the file: " + file.getAbsolutePath(), e);
98+
}
99+
}
100+
101+
private static void removeFile(String fileId, File file) {
102+
try {
103+
FileUtils.deleteDirectory(file.getParentFile());
104+
fileMap.remove(fileId);
105+
} catch (Exception e) {
106+
LOGGER.error("Failed to delete file: {} ", file.getAbsolutePath());
107+
}
108+
}
109+
83110
@POST
84111
@Path("/clients/{language}")
85112
@ApiOperation(
@@ -90,7 +117,7 @@ public Response generateClient(
90117
@Context HttpServletRequest request,
91118
@ApiParam(value = "The target language for the client library", required = true) @PathParam("language") String language,
92119
@ApiParam(value = "Configuration for building the client library", required = true) GeneratorInput opts)
93-
throws Exception {
120+
throws ApiException {
94121

95122
String filename = Generator.generateClient(language, opts);
96123
String host = getHost(request);
@@ -101,7 +128,6 @@ public Response generateClient(
101128
g.setFilename(filename);
102129
g.setFriendlyName(language + "-client");
103130
fileMap.put(code, g);
104-
System.out.println(code + ", " + filename);
105131
String link = host + "/api/gen/download/" + code;
106132
return Response.ok().entity(new ResponseCode(code, link)).build();
107133
} else {
@@ -117,7 +143,7 @@ public Response generateClient(
117143
public Response getClientOptions(
118144
@SuppressWarnings("unused") @Context HttpServletRequest request,
119145
@ApiParam(value = "The target language for the client library", required = true) @PathParam("language") String language)
120-
throws Exception {
146+
throws ApiException {
121147

122148
Map<String, CliOption> opts = Generator.getOptions(language);
123149

@@ -136,7 +162,7 @@ public Response getClientOptions(
136162
public Response getServerOptions(
137163
@SuppressWarnings("unused") @Context HttpServletRequest request,
138164
@ApiParam(value = "The target language for the server framework", required = true) @PathParam("framework") String framework)
139-
throws Exception {
165+
throws ApiException {
140166

141167
Map<String, CliOption> opts = Generator.getOptions(framework);
142168

@@ -173,14 +199,13 @@ public Response serverOptions() {
173199
value = "Generates a server library",
174200
notes = "Accepts a `GeneratorInput` options map for spec location and generation options.",
175201
response = ResponseCode.class, tags = "servers")
176-
public Response generateServerForLanguage(@Context HttpServletRequest request, @ApiParam(
177-
value = "framework", required = true) @PathParam("framework") String framework,
178-
@ApiParam(value = "parameters", required = true) GeneratorInput opts) throws Exception {
202+
public Response generateServerForLanguage(@Context HttpServletRequest request, @ApiParam(value = "framework", required = true) @PathParam("framework") String framework,
203+
@ApiParam(value = "parameters", required = true) GeneratorInput opts) throws ApiException {
179204
if (framework == null) {
180205
throw new BadRequestException("Framework is required");
181206
}
182207
String filename = Generator.generateServer(framework, opts);
183-
System.out.println("generated name: " + filename);
208+
LOGGER.info("Generated filename: {}", filename);
184209

185210
String host = getHost(request);
186211

@@ -190,7 +215,6 @@ public Response generateServerForLanguage(@Context HttpServletRequest request, @
190215
g.setFilename(filename);
191216
g.setFriendlyName(framework + "-server");
192217
fileMap.put(code, g);
193-
System.out.println(code + ", " + filename);
194218
String link = host + "/api/gen/download/" + code;
195219
return Response.ok().entity(new ResponseCode(code, link)).build();
196220
} else {

modules/swagger-generator/src/test/java/io/swagger/generator/resource/SwaggerResourceTest.java

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,114 @@
11
package io.swagger.generator.resource;
22

3+
import io.swagger.codegen.utils.SecureFileUtils;
4+
import io.swagger.generator.model.Generated;
5+
import org.apache.commons.io.FileUtils;
6+
import org.mockito.MockedStatic;
7+
import org.mockito.Mockito;
8+
import org.testng.annotations.AfterMethod;
9+
import org.testng.annotations.BeforeMethod;
310
import org.testng.annotations.Test;
411

12+
import javax.ws.rs.core.Response;
13+
import java.io.File;
14+
import java.lang.reflect.Field;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
19+
import static org.testng.Assert.assertFalse;
20+
import static org.testng.AssertJUnit.assertEquals;
21+
522
public class SwaggerResourceTest {
623

24+
private SwaggerResource resource;
25+
private Map<String, Generated> fileMap;
26+
27+
@BeforeMethod
28+
public void setUp() throws Exception {
29+
resource = new SwaggerResource();
30+
31+
fileMap = new HashMap<>();
32+
Field fm = SwaggerResource.class.getDeclaredField("fileMap");
33+
fm.setAccessible(true);
34+
fm.set(null, fileMap);
35+
}
36+
37+
@AfterMethod
38+
public void after() {
39+
fileMap.clear();
40+
}
41+
42+
@Test
43+
public void shouldReturnSuccessWhenDownloadFileAndBadRequestAfterSecondTry() throws Exception {
44+
File dir = new File("target/testng-gen1");
45+
File zip = new File(dir, "client.zip");
46+
FileUtils.write(zip, "TESTDATA", StandardCharsets.UTF_8);
47+
48+
Generated g = new Generated();
49+
g.setFilename(zip.getAbsolutePath());
50+
g.setFriendlyName("clientX");
51+
52+
fileMap.put("123", g);
53+
54+
Response response = resource.downloadFile("123");
55+
56+
assertEquals(200, response.getStatus());
57+
assertEquals("TESTDATA", new String((byte[]) response.getEntity()));
58+
assertFalse(zip.exists(), "File should be removed after download.");
59+
assertFalse(dir.exists(), "Directory should be removed after download.");
60+
61+
Response response2 = resource.downloadFile("123");
62+
assertEquals(404, response2.getStatus());
63+
}
64+
65+
@Test
66+
public void shouldReturnNotFoundWhenFileDoesntExist() {
67+
Response response = resource.downloadFile("nope");
68+
assertEquals(404, response.getStatus());
69+
}
70+
71+
@Test(expectedExceptions = Exception.class)
72+
public void testDownloadFile_missingPhysicalFile_causes500() {
73+
Generated g = new Generated();
74+
g.setFilename("target/no_such_dir/file.zip");
75+
g.setFriendlyName("missing");
76+
77+
fileMap.put("777", g);
78+
79+
resource.downloadFile("777");
80+
}
81+
82+
@Test(expectedExceptions = Exception.class)
83+
public void shouldPathValidationFailsWhenDownloadFile() throws Exception {
84+
try (MockedStatic<SecureFileUtils> mocked = Mockito.mockStatic(SecureFileUtils.class)) {
85+
86+
mocked.when(() -> SecureFileUtils.validatePath(Mockito.anyString()))
87+
.thenThrow(new RuntimeException("Invalid path"));
88+
89+
File dir = new File("target/testng-gen2");
90+
File zip = new File(dir, "client.zip");
91+
FileUtils.write(zip, "XYZ", StandardCharsets.UTF_8);
92+
93+
Generated g = new Generated();
94+
g.setFilename(zip.getAbsolutePath());
95+
g.setFriendlyName("clientY");
96+
97+
fileMap.put("xyz", g);
98+
99+
resource.downloadFile("xyz");
100+
}
101+
}
102+
7103
@Test(expectedExceptions = SecurityException.class)
8104
public void testDownloadFileWithPathTraversal() throws Exception {
9-
SwaggerResource resource = new SwaggerResource();
10105

11106
io.swagger.generator.model.Generated generated = new io.swagger.generator.model.Generated();
12107
generated.setFilename("../../../etc/passwd");
13108

14109
java.lang.reflect.Field fileMapField = SwaggerResource.class.getDeclaredField("fileMap");
15110
fileMapField.setAccessible(true);
16-
@SuppressWarnings("unchecked")
17-
java.util.Map<String, io.swagger.generator.model.Generated> fileMap =
18-
(java.util.Map<String, io.swagger.generator.model.Generated>) fileMapField.get(null);
111+
19112
fileMap.put("test-file-id", generated);
20113

21114
try {

0 commit comments

Comments
 (0)