Skip to content

Commit 67f9972

Browse files
committed
Java File Read Path Manipulation
1 parent cebbfa2 commit 67f9972

File tree

43 files changed

+348
-81
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+348
-81
lines changed

Path Manipulation/Path Manipulation while File Upload/java/.gitignore

Lines changed: 0 additions & 14 deletions
This file was deleted.

Path Manipulation/Path Manipulation while File Upload/java/README.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

Path Manipulation/README.md

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,48 @@ The Path Manipulation logic checks for the following:
1717
## Directory Structure for Path Manipulation
1818
```
1919
Path Manipulation
20-
├───Path Manipulation while File Read
21-
│ ├───java
22-
│ └───python
23-
│ └───securecodingexamples
24-
│ └───fileread
25-
│ └───pathmaniuplation
26-
│ └───src
27-
│ └───templates
28-
└───Path Manipulation while File Upload
29-
├───java
30-
│ └───src
31-
│ ├───main
32-
│ │ ├───java
33-
│ │ │ └───securecodingexamples
34-
│ │ │ └───fileupload
35-
│ │ │ └───pathmanipulation
36-
│ │ └───resources
37-
│ │ └───static
38-
│ └───test
39-
│ └───java
40-
│ └───securecodingexamples
41-
│ └───fileupload
42-
│ └───pathmanipulation
43-
└───python
44-
└───securecodingexamples
45-
└───fileupload
46-
└───pathmanipulation
47-
└───src
48-
└───templates
49-
```
20+
├───while File Read
21+
│ ├───java
22+
│ │ └───fileread.pathmanipulation
23+
│ │ └───src
24+
│ │ ├───main
25+
│ │ │ ├───java
26+
│ │ │ │ └───securecodingexamples
27+
│ │ │ │ └───fileread
28+
│ │ │ │ └───pathmanipulation
29+
│ │ │ └───resources
30+
│ │ │ └───templates
31+
│ │ └───test
32+
│ │ └───java
33+
│ │ └───securecodingexamples
34+
│ │ └───fileread
35+
│ │ └───pathmanipulation
36+
│ └───python
37+
│ └───securecodingexamples
38+
│ └───fileread
39+
│ └───pathmaniuplation
40+
│ └───src
41+
│ └───templates
42+
└───while File Upload
43+
├───java
44+
│ └───fileupload.pathmanipulation
45+
│ └───src
46+
│ ├───main
47+
│ │ ├───java
48+
│ │ │ └───securecodingexamples
49+
│ │ │ └───fileupload
50+
│ │ │ └───pathmanipulation
51+
│ │ └───resources
52+
│ │ └───static
53+
│ └───test
54+
│ └───java
55+
│ └───securecodingexamples
56+
│ └───fileupload
57+
│ └───pathmanipulation
58+
└───python
59+
└───securecodingexamples
60+
└───fileupload
61+
└───pathmanipulation
62+
└───src
63+
└───templates
64+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Path Manipulation while File Read or while File Download
2+
3+
This logic is helpful while you are facing the ___Path Manipulation___ issue in the File Downlaod/Read functionality.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Path Manipulation
2+
3+
This maven project is to help to mitigate the path manipulation issues. You can use the logic in the [DownloadController.java](./fileread.pathmanipulation/src/main/java/securecodingexamples/fileread/pathmanipulation/DownloadController.java) in your local [Maven](https://maven.apache.org/), [Gradle](https://gradle.org/) or other Java Applications.
4+
5+
## Code Structure
6+
7+
[HomeController](./fileread.pathmanipulation/src/main/java/securecodingexamples/fileread/pathmanipulation/HomeController.java) file serves the [index.html](./fileread.pathmanipulation/src/main/resources/templates/index.html) to serve as the Fronted for the File Upload.
8+
9+
[UploadController.java](./fileread.pathmanipulation/src/main/java/securecodingexamples/fileread/pathmanipulation/DownloadController.java) file contains the logic for the file Upload and the Filename validation, Extension Validation during the File Upload.
10+
11+
[resources/templates](./fileread.pathmanipulation/src/main/resources/templates/) Directory contains the index.html.
12+
13+
## Installation
14+
1. Clone the repository:
15+
```sh
16+
git clone https://github.com/sahildari/secure-coding-examples
17+
cd 'Path Manipulation/while File Read/java/fileread.pathmanipulation'
18+
```
19+
2. Install the package:
20+
21+
**MacOS/Linux:**
22+
```sh
23+
./mvnw clean spring-boot:run
24+
```
25+
26+
**Windows:**
27+
```sh
28+
mvnw.cmd clean spring-boot:run
29+
```
30+
3. Open in Browser:
31+
```
32+
http://127.0.0.1:8080
33+
```
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>3.4.3</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>securecodingexamples</groupId>
12+
<artifactId>fileread.pathmanipulation</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>fileread.pathmanipulation</name>
15+
<description>Path Manipulation Secure Coding Example with File Read</description>
16+
<url/>
17+
<licenses>
18+
<license/>
19+
</licenses>
20+
<developers>
21+
<developer/>
22+
</developers>
23+
<scm>
24+
<connection/>
25+
<developerConnection/>
26+
<tag/>
27+
<url/>
28+
</scm>
29+
<properties>
30+
<java.version>21</java.version>
31+
</properties>
32+
<dependencies>
33+
<dependency>
34+
<groupId>org.springframework.boot</groupId>
35+
<artifactId>spring-boot-starter-web</artifactId>
36+
</dependency>
37+
38+
<dependency>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-test</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
</dependencies>
49+
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>org.springframework.boot</groupId>
54+
<artifactId>spring-boot-maven-plugin</artifactId>
55+
</plugin>
56+
</plugins>
57+
</build>
58+
59+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package securecodingexamples.fileread.pathmanipulation;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.core.io.Resource;
6+
import org.springframework.core.io.UrlResource;
7+
import org.springframework.http.HttpHeaders;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.stereotype.Controller;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
import org.springframework.web.bind.annotation.PathVariable;
14+
import org.springframework.web.bind.annotation.ResponseBody;
15+
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
16+
17+
import java.io.File;
18+
import java.nio.file.Path;
19+
import java.nio.file.Paths;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.logging.Logger;
23+
import java.util.regex.Pattern;
24+
import java.util.stream.Collectors;
25+
26+
@SpringBootApplication
27+
@RestController
28+
public class DownloadController {
29+
30+
private static final Logger logger = Logger.getLogger(DownloadController.class.getName());
31+
private static final String TEMP_DIRECTORY = System.getProperty("java.io.tmpdir");
32+
private static final String UPLOAD_DIRECTORY = TEMP_DIRECTORY + File.separator + "Uploads";
33+
private static final String[] ALLOWED_EXTENSIONS = {"txt", "pdf"};
34+
private static final Pattern FILENAME_REGEX_PATTERN = Pattern.compile("[a-zA-Z0-9-_]+");
35+
36+
public static void main(String[] args) {
37+
SpringApplication.run(DownloadController.class, args);
38+
}
39+
40+
@RequestMapping("/download/{filename}")
41+
@ResponseBody
42+
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
43+
try {
44+
logger.info("Download request received for: " + filename);
45+
46+
if (filename==null || !isValidName(filename)) {
47+
logger.warning("Invalid filename requested");
48+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
49+
}
50+
51+
if (filename==null || !isValidExtension(filename)) {
52+
logger.warning("Invalid file extension ");
53+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
54+
}
55+
56+
if (isValidName(filename) && isValidExtension(filename)) {
57+
filename = validFilename(filename);
58+
Path filePath = Paths.get(UPLOAD_DIRECTORY).resolve(filename).normalize();
59+
Resource resource = new UrlResource(filePath.toUri());
60+
61+
if (!resource.exists() || !resource.isReadable()) {
62+
logger.warning("File not found or not readable: " + filename);
63+
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
64+
}
65+
66+
logger.info("File found: " + filename + " at " + filePath);
67+
logger.info("Downloading file: " + resource);
68+
return ResponseEntity.ok()
69+
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
70+
.body(resource);
71+
}
72+
logger.warning("Invalid filename requested");
73+
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
74+
}
75+
76+
catch (Exception e) {
77+
logger.severe("Error downloading file: " + e.getMessage());
78+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
79+
}
80+
}
81+
82+
private static boolean isValidName(String filename) {
83+
String name = filename.split("\\.", 2)[0];
84+
return name.matches(FILENAME_REGEX_PATTERN.pattern());
85+
}
86+
87+
private static String validFilename(String filename) {
88+
String name = filename.split("\\.", 2)[0];
89+
String extension = filename.split("\\.", 2)[1];
90+
return name + "." + extension;
91+
}
92+
93+
private static boolean isValidExtension(String filename) {
94+
String extension = filename.split("\\.", 2)[1];
95+
for (String ext : ALLOWED_EXTENSIONS) {
96+
if (ext.equals(extension)) {
97+
return true;
98+
}
99+
}
100+
return false;
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package securecodingexamples.fileread.pathmanipulation;
2+
3+
import java.io.File;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.stream.Collectors;
7+
8+
import org.springframework.stereotype.Controller;
9+
import org.springframework.ui.Model;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
12+
@Controller
13+
public class HomeController {
14+
private static final String TEMP_DIRECTORY = System.getProperty("java.io.tmpdir");
15+
private static final String UPLOAD_DIRECTORY = TEMP_DIRECTORY + File.separator + "Uploads";
16+
17+
@GetMapping("/")
18+
public String listFiles(Model model) {
19+
File folder = new File(UPLOAD_DIRECTORY);
20+
if (!folder.exists()) {
21+
folder.mkdirs();
22+
}
23+
24+
List<String> files = Arrays.stream(folder.listFiles())
25+
.filter(File::isFile)
26+
.map(File::getName)
27+
.collect(Collectors.toList());
28+
29+
model.addAttribute("files", files);
30+
return "index"; // Renders index.html
31+
}
32+
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
spring.application.name=fileread.pathmanipulation
2+
logging.file.path=${java.io.tmpdir}/log
3+
logging.file=application.log
4+
logging.level.org.springframework=INFO
5+
spring.thymeleaf.prefix=classpath:/templates/
6+
spring.thymeleaf.suffix=.html
7+
spring.web.resources.static-locations=classpath:/static/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Download Files</title>
7+
</head>
8+
<body>
9+
<h1>Files Available for Download</h1>
10+
<ul>
11+
<li th:each="file : ${files}">
12+
<a th:href="@{download/{filename}(filename=${file})}" th:text="${file}"></a>
13+
</li>
14+
</ul>
15+
</body>
16+
</html>

0 commit comments

Comments
 (0)