Skip to content

Commit f92deb2

Browse files
committed
File Read Path Manipulation Python
1 parent 2015096 commit f92deb2

File tree

15 files changed

+229
-36
lines changed

15 files changed

+229
-36
lines changed

.github/workflows/python-ci.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ jobs:
1010
build:
1111
runs-on: ubuntu-latest
1212
env:
13-
working-directory: "./Path Manipulation/Path Manipulation while File Upload/python"
13+
working-directory-fileupload: "./Path Manipulation/Path Manipulation while File Upload/python"
14+
working-directory-fileread: "./Path Manipulation/Path Manipulation while File Read/python"
1415

1516
steps:
1617
- name: Checkout code
@@ -21,8 +22,14 @@ jobs:
2122
with:
2223
python-version: '3.9'
2324

24-
- name: Install Dependencies
25-
working-directory: ${{ env.working-directory }}
25+
- name: Install Dependencies for Path Manipulation while File Upload
26+
working-directory: ${{ env.working-directory-fileupload }}
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install .
30+
31+
- name: Install Dependencies for Path Manipulation while File Read
32+
working-directory: ${{ env.working-directory-fileread }}
2633
run: |
2734
python -m pip install --upgrade pip
2835
pip install .
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
recursive-include pathmanipulation/src/templates *
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Path Manipulation
2+
This python project is to help to mitigate the path manipulation issues. You can use the logic in the [pathmanipulation.py](./securecodingexamples/fileread/pathmaniuplation/src/pathmanipulation.py) in your [Flask](https://pypi.org/project/Flask/) or [Django](https://pypi.org/project/Django/) projects or any other Python projects.
3+
4+
## Code Structure
5+
6+
[app.py](./securecodingexamples/fileread/pathmaniuplation/src/app.py) contains the Flask Code to handle the file upload logic with maximum file size.
7+
8+
[pathmanipulation.py](./securecodingexamples/fileread/pathmaniuplation/src/pathmanipulation.py) contains logic for Filename, File extension, Double extension check and null byte checks.
9+
10+
[template](./securecodingexamples/fileread/pathmaniuplation/src/templates/) directory contains the index.html as frontend for the file upload with file type check on the client side.
11+
12+
You can try to play around this by following the Installation steps, check the Usage to run the Flask app.
13+
14+
## Installation
15+
16+
Please note that this project will try to fetch the files from your ___TEMP/Uploads___ directory. You can either manually create your files in the directory, or you can navigate to [Path Manipulation while File Upload Python Project](../../Path%20Manipulation%20while%20File%20Upload/python/) and follow the installation steps and Upload the test files.
17+
18+
_TEMP : temporary Folder in your OS_
19+
_%TEMP% file in Windows_
20+
_/tmp Directory in Linux/MacOS_
21+
22+
1. Clone the repository:
23+
```sh
24+
git clone https://github.com/sahildari/secure-coding-examples
25+
cd 'Path Manipulation/Path Manipulation while File Read/python'
26+
```
27+
2. Install the package:
28+
```sh
29+
pip install .
30+
```
31+
## Usage
32+
1. Run the Flask app
33+
```sh
34+
python run.py
35+
```
36+
2. Open in Browser:
37+
```
38+
http://127.0.0.1:5000
39+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python
2+
3+
from securecodingexamples.fileread.pathmaniuplation.src.app import app
4+
5+
if __name__ == '__main__':
6+
app.run(debug=True)

Path Manipulation/Path Manipulation while File Read/python/securecodingexamples/__init__.py

Whitespace-only changes.

Path Manipulation/Path Manipulation while File Read/python/securecodingexamples/fileread/__init__.py

Whitespace-only changes.

Path Manipulation/Path Manipulation while File Read/python/securecodingexamples/fileread/pathmaniuplation/__init__.py

Whitespace-only changes.

Path Manipulation/Path Manipulation while File Read/python/securecodingexamples/fileread/pathmaniuplation/src/__init__.py

Whitespace-only changes.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env python3
2+
3+
from flask import Flask, request, jsonify, render_template, send_file
4+
import os
5+
import tempfile
6+
import logging
7+
from .pathmanipulation import is_valid_name, is_valid_extension, valid_filename
8+
9+
app = Flask(__name__)
10+
TEMPDIR = tempfile.gettempdir()
11+
LOGDIR = os.path.join(TEMPDIR + "/logs/")
12+
UPLOADDIR = os.path.join(TEMPDIR + "/Uploads/")
13+
14+
os.makedirs(LOGDIR, exist_ok=True)
15+
16+
loglocation = os.path.join(LOGDIR + "app.log")
17+
logging.basicConfig(
18+
level=logging.INFO,
19+
handlers= [
20+
logging.FileHandler(loglocation),
21+
logging.StreamHandler()
22+
]
23+
)
24+
25+
logger = logging.getLogger(__name__)
26+
27+
@app.route("/", methods=["GET"])
28+
def main():
29+
files = os.listdir(UPLOADDIR)
30+
return render_template("index.html", files=files)
31+
32+
@app.route("/download/<path:filename>", methods=["GET"])
33+
def download_file(filename: str):
34+
"""Handles file upload with validation."""
35+
try:
36+
if(filename is None or not is_valid_name(filename)):
37+
logger.error("Invalid filename")
38+
return jsonify({"error": "Invalid filename"}), 400
39+
40+
if(filename is None or not is_valid_extension(filename)):
41+
logger.error("Invalid extension")
42+
return jsonify({"error": "Invalid extension"}), 400
43+
44+
if(is_valid_extension(filename) and is_valid_name(filename)):
45+
validfilename = valid_filename(filename)
46+
logger.info(f"Valid filename: {validfilename}")
47+
downloads = os.path.join(UPLOADDIR, validfilename)
48+
logger.info("download() method started")
49+
logger.info(f"Requested file: {downloads}")
50+
return send_file(downloads, as_attachment=True), 200
51+
52+
except Exception as e:
53+
logger.error("download() method failed")
54+
logger.error(e)
55+
return jsonify({"error": "Internal Server Error"}), 500
56+
57+
if __name__ == '__main__':
58+
app.run(debug=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
3+
import re
4+
5+
FILENAME_REGEX_PATTERN = r"^[a-zA-Z0-9\-\_]+$"
6+
ALLOWED_EXTENSIONS = set(["txt", "pdf"]) # this example allows the text and pdf files
7+
8+
9+
"""This function checks if the filename is valid and doesn't contain any dot character in the name."""
10+
def is_valid_name(filename: str) -> bool:
11+
name = filename.rsplit('.', 1)[0]
12+
regex_match = re.search(FILENAME_REGEX_PATTERN, name)
13+
return True if(regex_match != None) else False
14+
15+
"""This function checks for the valid file extensions."""
16+
def is_valid_extension(filename: str) -> bool:
17+
ext = filename.rsplit(".", 1)[1]
18+
return True if (ext in ALLOWED_EXTENSIONS) else False
19+
20+
def valid_filename(filename: str) -> str:
21+
name, ext = filename.rsplit('.', 1)
22+
name = re.sub(r"\.", "", name)
23+
name = re.sub(r"\%[A-Za-z0-9]+", "", name)
24+
ext = re.sub(r"\%[A-Za-z0-9]+", "", ext)
25+
regex_match = re.search(FILENAME_REGEX_PATTERN, name)
26+
return f"{regex_match.group(0)}.{ext}" # returns the sanitized filename with the extension for upload and download

0 commit comments

Comments
 (0)