diff --git a/.github/workflows/github-actions-test.yml b/.github/workflows/github-actions-test.yml
index 7ec9814..fd80559 100644
--- a/.github/workflows/github-actions-test.yml
+++ b/.github/workflows/github-actions-test.yml
@@ -13,9 +13,9 @@ jobs:
linuxOS_build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
@@ -29,12 +29,14 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+ - name: Run tests
+ run: python -m unittest discover -s tests
MacOS_build:
runs-on: macOS-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
@@ -48,12 +50,14 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+ - name: Run tests
+ run: python -m unittest discover -s tests
Windows_build:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python 3.9
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: 3.9
- name: Install dependencies
@@ -66,4 +70,5 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-
+ - name: Run tests
+ run: python -m unittest discover -s tests
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..e3533a6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,138 @@
+# AGENTS.md
+
+This file gives future maintainers and coding agents the context needed to work on this repository safely.
+
+## Project Overview
+
+`simple_http_server` is a small Python HTTP file server with directory browsing, downloads, and browser-based file uploads. The project is intentionally lightweight and currently centers on one implementation file:
+
+- `simple_http_server.py`: request handler, upload handling, path translation, MIME detection, CLI parsing, and server startup.
+- `Dockerfile`: container entrypoint that serves `/opt/data`.
+- `.github/workflows/github-actions-test.yml`: lint-only CI across Linux, macOS, and Windows.
+- `README.md`: user-facing usage notes and project status.
+
+Treat this project as a temporary file-sharing tool for trusted environments. It should not be presented as a hardened public internet service unless authentication, upload limits, and stronger path/file validation are added.
+
+## Current Behavior
+
+- Runs with `python simple_http_server.py 8000`.
+- Accepts `--bind/-b ADDRESS`; the current default is `127.0.0.1`.
+- Serves files from the current working directory.
+- Lists directories when no `index.html` or `index.htm` exists.
+- Adds an HTML upload form to directory listings.
+- Stores uploaded files in the requested directory after sanitizing the uploaded filename.
+- Avoids overwriting existing files by appending `_` to the target filename.
+- Rejects uploads larger than 100 MiB.
+- Uses Python standard library modules only.
+
+## Important Implementation Notes
+
+- `SimpleHTTPRequestHandler` implements `GET`, `HEAD`, and `POST`.
+- `deal_post_data()` manually parses multipart upload bodies. Be careful when changing it; malformed input, missing headers, non-ASCII filenames, and large files need explicit coverage.
+- `translate_path()` maps URL paths to the current working directory and strips query/fragment components.
+- The server uses a threaded HTTP server so multiple requests can be handled concurrently.
+- The project still contains compatibility code for Python 2, but the Dockerfile and GitHub Actions use Python 3.9.
+
+## Safety And Security Priorities
+
+When making changes, prioritize these issues first:
+
+1. Sanitize uploaded filenames.
+ Use only a safe basename, reject absolute paths, reject `..`, and avoid allowing path separators inside uploaded names.
+
+2. Escape all user-controlled HTML output.
+ Directory names, file names, upload result messages, and paths should be HTML-escaped before rendering.
+
+3. Preserve safer network defaults.
+ The default bind address is `127.0.0.1`; document `0.0.0.0` as an explicit LAN/public option.
+
+4. Maintain upload limits.
+ Large or slow uploads can exhaust disk, memory, or worker capacity. The current limit is 100 MiB.
+
+5. Improve request robustness.
+ Handle missing `Content-Type`, missing `content-length`, malformed multipart bodies, and interrupted uploads without crashing the server.
+
+6. Consider concurrent serving.
+ If multi-client support is needed, use `ThreadingHTTPServer` on Python 3 and keep Python 2 compatibility decisions explicit.
+
+## Development Guidelines
+
+- Keep the project dependency-free unless there is a strong reason to add packaging or test dependencies.
+- Preserve the simple CLI experience.
+- Prefer small, focused changes over broad rewrites.
+- If Python 2 support is removed, update README, changelog, CI, and code comments in the same change.
+- If public/network-facing behavior changes, update README and SECURITY.md.
+- Do not silently change the served root directory behavior; users expect the current working directory to be served.
+- Avoid introducing platform-specific behavior without checking Linux, macOS, and Windows implications.
+
+## Testing Guidance
+
+There is no dedicated test suite yet. For any behavior change, add focused tests if possible. Useful coverage areas:
+
+- `translate_path()` rejects or neutralizes traversal attempts.
+- Directory listing escapes special characters.
+- Download responses include content type, length, and last-modified headers.
+- Upload accepts normal files.
+- Upload handles duplicate names predictably.
+- Upload rejects unsafe filenames.
+- Malformed upload requests return an error page instead of crashing.
+- CLI parsing supports default port, custom port, `--bind`, and `--version`.
+
+Before finishing a change, at minimum run:
+
+```bash
+python3 -m py_compile simple_http_server.py
+```
+
+If lint dependencies are available, also run:
+
+```bash
+flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+```
+
+For server behavior changes, manually start the server from a temporary directory and verify directory listing, download, upload, and duplicate filename handling.
+
+Run the unit tests with:
+
+```bash
+python3 -m unittest discover -s tests
+```
+
+## Documentation Maintenance
+
+Keep these files aligned:
+
+- `simple_http_server.py`: source version in `__version__`.
+- `CHANGELOG.md`: released changes.
+- `README.md`: install, run, Docker, support status, and security caveats.
+- `SECURITY.md`: supported versions and vulnerability contact.
+- `.github/workflows/github-actions-test.yml`: supported Python versions and CI checks.
+
+Known documentation drift to address in future work:
+
+- README still shows a Travis CI badge.
+- `.travis.yml` is obsolete and does not run meaningful tests.
+- `CHANGELOG.md` does not reflect the current `0.3.2` source version.
+- README says Python 2 and Python 3 are supported, while CI and Docker only exercise Python 3.9.
+
+## Release And Packaging Notes
+
+The project is not currently packaged for PyPI. If packaging is added, prefer a minimal modern setup and include:
+
+- A console script entrypoint.
+- README metadata.
+- License metadata.
+- Python version classifiers.
+- A clear decision on Python 2 support.
+
+Docker publishing is also not automated. If adding container releases, prefer GitHub Container Registry or Docker Hub with an explicit release workflow.
+
+## Good First Improvements
+
+- Add tests around path translation and upload filename safety.
+- Sanitize and escape upload result output.
+- Replace `HTTPServer` with a threaded server for Python 3.
+- Update GitHub Actions to current action versions.
+- Remove or replace Travis references.
+- Clarify safe usage in README.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f63c30a..c039421 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+
+## [0.3.3] (Unreleased)
+### Security
+
+* sanitize uploaded filenames
+* escape upload and directory listing output
+* reject malformed upload headers and uploads larger than 100 MiB
+
+### Features
+
+* default to localhost binding
+* serve requests with a threaded HTTP server
+* add unit tests for helper behavior
+
## [0.2.1] (2021-10-17)
### Features
diff --git a/Dockerfile b/Dockerfile
index 5d04803..bf7cf86 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,4 +10,4 @@ ENV PORT=8000
EXPOSE $PORT
-CMD python /opt/http_server/simple_http_server.py ${PORT}
\ No newline at end of file
+CMD python /opt/http_server/simple_http_server.py --bind 0.0.0.0 ${PORT}
diff --git a/README.md b/README.md
index 1e36aef..1f53883 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,12 @@
# simple_http_server
-[](https://travis-ci.org/freelamb/simple_http_server)
-
## Features
- ✔ simple
- ✔ upload
- ✔ download
- ✔ support python2, python3
+- ✔ Multi-threaded
## Usage
```bash
# get code
@@ -19,6 +18,9 @@ $ cd simple_http_server
# run server
$ python simple_http_server.py 8000
+# expose to another host in a trusted network
+$ python simple_http_server.py --bind 0.0.0.0 8000
+
# run as docker container
# 1.build the image('.' below refer to the root path of this project)
docker build -t freelamb/simple_http_server .
@@ -30,12 +32,17 @@ docker run
-d freelamb/simple_http_server:latest
```
+## Security
+
+This server is intended for temporary file sharing in trusted environments. The default bind address is `127.0.0.1`; use `--bind 0.0.0.0` only when you explicitly want other hosts to connect.
+
+Uploaded file names are sanitized, upload results and directory listings escape user-controlled text, and uploads larger than 100 MiB are rejected.
+
## Example

## Todo
-- [ ] support Multi-threaded
- [ ] add docker images
- [ ] add to pypi
## Contributing
diff --git a/simple_http_server.py b/simple_http_server.py
index 2c92a78..8fe0845 100644
--- a/simple_http_server.py
+++ b/simple_http_server.py
@@ -22,13 +22,14 @@
import mimetypes
import re
import signal
-from io import StringIO, BytesIO
+from io import BytesIO
if sys.version_info.major == 3:
# Python3
+ from importlib import reload
from urllib.parse import quote
from urllib.parse import unquote
- from http.server import HTTPServer
+ from http.server import ThreadingHTTPServer
from http.server import BaseHTTPRequestHandler
else:
# Python2
@@ -36,8 +37,15 @@
sys.setdefaultencoding('utf-8')
from urllib import quote
from urllib import unquote
- from BaseHTTPServer import HTTPServer
+ from BaseHTTPServer import HTTPServer as BaseHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
+ from SocketServer import ThreadingMixIn
+
+ class ThreadingHTTPServer(ThreadingMixIn, BaseHTTPServer):
+ daemon_threads = True
+
+
+MAX_UPLOAD_SIZE = 100 * 1024 * 1024
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
@@ -51,6 +59,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
"""
server_version = "simple_http_server/" + __version__
+ max_upload_size = MAX_UPLOAD_SIZE
def do_GET(self):
"""Serve a GET request."""
@@ -78,7 +87,7 @@ def do_POST(self):
f.write(b"Success:")
else:
f.write(b"Failed:")
- f.write(info.encode('utf-8'))
+ f.write(html_escape(info).encode('utf-8'))
f.write(b"
back")
f.write(b"