tempspace-cli 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tempspace_cli-1.0.0/PKG-INFO +152 -0
- tempspace_cli-1.0.0/README.md +137 -0
- tempspace_cli-1.0.0/cli/__init__.py +1 -0
- tempspace_cli-1.0.0/cli/tempspace.py +137 -0
- tempspace_cli-1.0.0/pyproject.toml +30 -0
- tempspace_cli-1.0.0/setup.cfg +4 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/PKG-INFO +152 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/SOURCES.txt +12 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/dependency_links.txt +1 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/entry_points.txt +2 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/requires.txt +4 -0
- tempspace_cli-1.0.0/tempspace_cli.egg-info/top_level.txt +1 -0
- tempspace_cli-1.0.0/tests/test_cli.py +12 -0
- tempspace_cli-1.0.0/tests/test_main.py +168 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tempspace-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A command-line tool for uploading files to Tempspace.
|
|
5
|
+
Author-email: Tempspace <tempspace@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests==2.31.0
|
|
12
|
+
Requires-Dist: tqdm==4.66.1
|
|
13
|
+
Requires-Dist: requests-toolbelt==1.0.0
|
|
14
|
+
Requires-Dist: rich==13.7.1
|
|
15
|
+
|
|
16
|
+
# 🚀 FileShare - Enhanced Terminal File Upload Service
|
|
17
|
+
|
|
18
|
+
A modern, terminal-style file sharing service with password protection, rate limiting, and QR code generation.
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
21
|
+
|
|
22
|
+
### Server
|
|
23
|
+
|
|
24
|
+
To run the web server, you can clone the repository and run it locally.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone the repository
|
|
28
|
+
git clone <your-repo>
|
|
29
|
+
cd fileshare
|
|
30
|
+
|
|
31
|
+
# Install dependencies
|
|
32
|
+
pip install -r requirements.txt
|
|
33
|
+
|
|
34
|
+
# Copy environment template
|
|
35
|
+
cp .env.example .env
|
|
36
|
+
|
|
37
|
+
# Edit .env and change admin credentials!
|
|
38
|
+
nano .env
|
|
39
|
+
|
|
40
|
+
# Run the server
|
|
41
|
+
python main.py
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Command-Line Tool (CLI)
|
|
45
|
+
|
|
46
|
+
To use the command-line tool, you can install it directly from PyPI.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install tempspace-cli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## ✨ Features
|
|
53
|
+
|
|
54
|
+
### 🔒 Security
|
|
55
|
+
- **Rate Limiting**: 10 uploads/hour, 100 downloads/hour per IP
|
|
56
|
+
- **Password Protection**: Optional password-protected uploads
|
|
57
|
+
- **Secure Admin Panel**: HTTP Basic Auth for debug endpoints
|
|
58
|
+
- **File Deduplication**: Automatic duplicate detection (SHA256)
|
|
59
|
+
- **One-Time Downloads**: Self-destructing links after first download
|
|
60
|
+
|
|
61
|
+
### 🎨 UI/UX
|
|
62
|
+
- **Modern Terminal Design**: Green-on-black terminal aesthetic
|
|
63
|
+
- **Drag & Drop**: Intuitive file upload interface
|
|
64
|
+
- **QR Code Generation**: Instant QR codes for mobile sharing
|
|
65
|
+
- **Upload History**: Local storage of recent uploads (browser)
|
|
66
|
+
- **Toast Notifications**: Non-intrusive feedback system
|
|
67
|
+
- **Real-time Progress**: Upload progress with speed indicators
|
|
68
|
+
- **Countdown Timer**: Shows time remaining until expiry
|
|
69
|
+
- **Responsive Design**: Works on mobile, tablet, and desktop
|
|
70
|
+
|
|
71
|
+
### 📦 File Management
|
|
72
|
+
- **Large File Support**: Up to 4GB per file
|
|
73
|
+
- **Image Previews**: Automatic preview for images
|
|
74
|
+
- **Multiple Expiry Options**: 1, 3, 6, 12, or 24 hours
|
|
75
|
+
- **Chunked Uploads**: Efficient streaming for large files
|
|
76
|
+
- **Automatic Cleanup**: Background task removes expired files
|
|
77
|
+
|
|
78
|
+
## 📖 Usage
|
|
79
|
+
|
|
80
|
+
### Web Interface
|
|
81
|
+
|
|
82
|
+
1. **Open** `http://localhost:8000` in your browser
|
|
83
|
+
2. **Drag & drop** a file or click to browse
|
|
84
|
+
3. **Configure** expiry time and optional password
|
|
85
|
+
4. **Upload** and share the generated link
|
|
86
|
+
5. **Scan QR code** for mobile access
|
|
87
|
+
|
|
88
|
+
### CLI Usage
|
|
89
|
+
|
|
90
|
+
Once installed, you can use the `tempspace` command.
|
|
91
|
+
|
|
92
|
+
#### Basic Upload
|
|
93
|
+
```bash
|
|
94
|
+
tempspace /path/to/document.pdf -t 6h
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Password Protected Upload
|
|
98
|
+
```bash
|
|
99
|
+
tempspace /path/to/secret.txt -t 12h -p "mypassword"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### One-Time Download
|
|
103
|
+
```bash
|
|
104
|
+
tempspace /path/to/report.pdf --one-time
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 🔧 Configuration
|
|
108
|
+
|
|
109
|
+
### Environment Variables
|
|
110
|
+
|
|
111
|
+
Create a `.env` file for the server:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Admin credentials (CHANGE THESE!)
|
|
115
|
+
ADMIN_USER=admin
|
|
116
|
+
ADMIN_PASS=your-secure-password-here
|
|
117
|
+
|
|
118
|
+
# Optional: Custom limits
|
|
119
|
+
RATE_LIMIT_UPLOADS=10
|
|
120
|
+
RATE_LIMIT_DOWNLOADS=100
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 🛡️ Admin Endpoints
|
|
124
|
+
|
|
125
|
+
Protected by HTTP Basic Auth (use credentials from `.env`).
|
|
126
|
+
|
|
127
|
+
### View Statistics
|
|
128
|
+
```bash
|
|
129
|
+
curl -u admin:password http://localhost:8000/debug/stats
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Wipe All Files
|
|
133
|
+
```bash
|
|
134
|
+
curl -X POST -u admin:password http://localhost:8000/debug/wipe
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
⚠️ **Warning**: This deletes ALL uploaded files permanently!
|
|
138
|
+
|
|
139
|
+
## 📝 License
|
|
140
|
+
|
|
141
|
+
MIT License - Feel free to use and modify!
|
|
142
|
+
|
|
143
|
+
## 🤝 Contributing
|
|
144
|
+
|
|
145
|
+
Contributions welcome! Please:
|
|
146
|
+
1. Fork the repository
|
|
147
|
+
2. Create a feature branch
|
|
148
|
+
3. Submit a pull request
|
|
149
|
+
|
|
150
|
+
## 📧 Support
|
|
151
|
+
|
|
152
|
+
For issues and questions, please open a GitHub issue.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# 🚀 FileShare - Enhanced Terminal File Upload Service
|
|
2
|
+
|
|
3
|
+
A modern, terminal-style file sharing service with password protection, rate limiting, and QR code generation.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
### Server
|
|
8
|
+
|
|
9
|
+
To run the web server, you can clone the repository and run it locally.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Clone the repository
|
|
13
|
+
git clone <your-repo>
|
|
14
|
+
cd fileshare
|
|
15
|
+
|
|
16
|
+
# Install dependencies
|
|
17
|
+
pip install -r requirements.txt
|
|
18
|
+
|
|
19
|
+
# Copy environment template
|
|
20
|
+
cp .env.example .env
|
|
21
|
+
|
|
22
|
+
# Edit .env and change admin credentials!
|
|
23
|
+
nano .env
|
|
24
|
+
|
|
25
|
+
# Run the server
|
|
26
|
+
python main.py
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Command-Line Tool (CLI)
|
|
30
|
+
|
|
31
|
+
To use the command-line tool, you can install it directly from PyPI.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install tempspace-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## ✨ Features
|
|
38
|
+
|
|
39
|
+
### 🔒 Security
|
|
40
|
+
- **Rate Limiting**: 10 uploads/hour, 100 downloads/hour per IP
|
|
41
|
+
- **Password Protection**: Optional password-protected uploads
|
|
42
|
+
- **Secure Admin Panel**: HTTP Basic Auth for debug endpoints
|
|
43
|
+
- **File Deduplication**: Automatic duplicate detection (SHA256)
|
|
44
|
+
- **One-Time Downloads**: Self-destructing links after first download
|
|
45
|
+
|
|
46
|
+
### 🎨 UI/UX
|
|
47
|
+
- **Modern Terminal Design**: Green-on-black terminal aesthetic
|
|
48
|
+
- **Drag & Drop**: Intuitive file upload interface
|
|
49
|
+
- **QR Code Generation**: Instant QR codes for mobile sharing
|
|
50
|
+
- **Upload History**: Local storage of recent uploads (browser)
|
|
51
|
+
- **Toast Notifications**: Non-intrusive feedback system
|
|
52
|
+
- **Real-time Progress**: Upload progress with speed indicators
|
|
53
|
+
- **Countdown Timer**: Shows time remaining until expiry
|
|
54
|
+
- **Responsive Design**: Works on mobile, tablet, and desktop
|
|
55
|
+
|
|
56
|
+
### 📦 File Management
|
|
57
|
+
- **Large File Support**: Up to 4GB per file
|
|
58
|
+
- **Image Previews**: Automatic preview for images
|
|
59
|
+
- **Multiple Expiry Options**: 1, 3, 6, 12, or 24 hours
|
|
60
|
+
- **Chunked Uploads**: Efficient streaming for large files
|
|
61
|
+
- **Automatic Cleanup**: Background task removes expired files
|
|
62
|
+
|
|
63
|
+
## 📖 Usage
|
|
64
|
+
|
|
65
|
+
### Web Interface
|
|
66
|
+
|
|
67
|
+
1. **Open** `http://localhost:8000` in your browser
|
|
68
|
+
2. **Drag & drop** a file or click to browse
|
|
69
|
+
3. **Configure** expiry time and optional password
|
|
70
|
+
4. **Upload** and share the generated link
|
|
71
|
+
5. **Scan QR code** for mobile access
|
|
72
|
+
|
|
73
|
+
### CLI Usage
|
|
74
|
+
|
|
75
|
+
Once installed, you can use the `tempspace` command.
|
|
76
|
+
|
|
77
|
+
#### Basic Upload
|
|
78
|
+
```bash
|
|
79
|
+
tempspace /path/to/document.pdf -t 6h
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Password Protected Upload
|
|
83
|
+
```bash
|
|
84
|
+
tempspace /path/to/secret.txt -t 12h -p "mypassword"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### One-Time Download
|
|
88
|
+
```bash
|
|
89
|
+
tempspace /path/to/report.pdf --one-time
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 🔧 Configuration
|
|
93
|
+
|
|
94
|
+
### Environment Variables
|
|
95
|
+
|
|
96
|
+
Create a `.env` file for the server:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Admin credentials (CHANGE THESE!)
|
|
100
|
+
ADMIN_USER=admin
|
|
101
|
+
ADMIN_PASS=your-secure-password-here
|
|
102
|
+
|
|
103
|
+
# Optional: Custom limits
|
|
104
|
+
RATE_LIMIT_UPLOADS=10
|
|
105
|
+
RATE_LIMIT_DOWNLOADS=100
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 🛡️ Admin Endpoints
|
|
109
|
+
|
|
110
|
+
Protected by HTTP Basic Auth (use credentials from `.env`).
|
|
111
|
+
|
|
112
|
+
### View Statistics
|
|
113
|
+
```bash
|
|
114
|
+
curl -u admin:password http://localhost:8000/debug/stats
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Wipe All Files
|
|
118
|
+
```bash
|
|
119
|
+
curl -X POST -u admin:password http://localhost:8000/debug/wipe
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
⚠️ **Warning**: This deletes ALL uploaded files permanently!
|
|
123
|
+
|
|
124
|
+
## 📝 License
|
|
125
|
+
|
|
126
|
+
MIT License - Feel free to use and modify!
|
|
127
|
+
|
|
128
|
+
## 🤝 Contributing
|
|
129
|
+
|
|
130
|
+
Contributions welcome! Please:
|
|
131
|
+
1. Fork the repository
|
|
132
|
+
2. Create a feature branch
|
|
133
|
+
3. Submit a pull request
|
|
134
|
+
|
|
135
|
+
## 📧 Support
|
|
136
|
+
|
|
137
|
+
For issues and questions, please open a GitHub issue.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import hashlib
|
|
6
|
+
import multiprocessing
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncoderMonitor
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
# Default configuration
|
|
13
|
+
DEFAULT_SERVER_URL = "https://tempspace.fly.dev/"
|
|
14
|
+
CHUNK_SIZE = 1024 * 1024 # 1MB
|
|
15
|
+
|
|
16
|
+
def parse_time(time_str: str) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Parse a time string (e.g., '7d', '24h', '360') into an integer number of hours.
|
|
19
|
+
Returns the number of hours as an integer, or None if parsing fails.
|
|
20
|
+
"""
|
|
21
|
+
time_str = time_str.lower().strip()
|
|
22
|
+
if time_str.endswith('d'):
|
|
23
|
+
try:
|
|
24
|
+
days = int(time_str[:-1])
|
|
25
|
+
return days * 24
|
|
26
|
+
except ValueError:
|
|
27
|
+
return None
|
|
28
|
+
elif time_str.endswith('h'):
|
|
29
|
+
try:
|
|
30
|
+
return int(time_str[:-1])
|
|
31
|
+
except ValueError:
|
|
32
|
+
return None
|
|
33
|
+
else:
|
|
34
|
+
try:
|
|
35
|
+
return int(time_str)
|
|
36
|
+
except ValueError:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def calculate_file_hash(filepath: str) -> str:
|
|
40
|
+
"""Calculate the SHA256 hash of a file in chunks."""
|
|
41
|
+
sha256_hash = hashlib.sha256()
|
|
42
|
+
try:
|
|
43
|
+
with open(filepath, "rb") as f:
|
|
44
|
+
for byte_block in iter(lambda: f.read(CHUNK_SIZE), b""):
|
|
45
|
+
sha256_hash.update(byte_block)
|
|
46
|
+
return sha256_hash.hexdigest()
|
|
47
|
+
except IOError as e:
|
|
48
|
+
print(f"Error reading file for hashing: {e}", file=sys.stderr)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
"""Main function to handle argument parsing and file upload."""
|
|
53
|
+
console = Console()
|
|
54
|
+
|
|
55
|
+
parser = argparse.ArgumentParser(
|
|
56
|
+
description="Upload a file to Tempspace.",
|
|
57
|
+
formatter_class=argparse.RawTextHelpFormatter
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
parser.add_argument("filepath", help="The path to the file you want to upload.")
|
|
61
|
+
parser.add_argument("-t", "--time", type=str, default='24', help="Set the file's expiration time. Examples: '24h', '7d', '360' (hours).\nDefault: '24' (24 hours).")
|
|
62
|
+
parser.add_argument("-p", "--password", type=str, help="Protect the file with a password.")
|
|
63
|
+
parser.add_argument("--one-time", action="store_true", help="The file will be deleted after the first download.")
|
|
64
|
+
parser.add_argument("--url", type=str, default=os.environ.get("TEMPSPACE_URL", DEFAULT_SERVER_URL), help=f"The URL of the Tempspace server.\nCan also be set with the TEMPSPACE_URL environment variable.\nDefault: {DEFAULT_SERVER_URL}")
|
|
65
|
+
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
|
|
68
|
+
# --- Validate Inputs ---
|
|
69
|
+
if not os.path.isfile(args.filepath):
|
|
70
|
+
console.print(f"[bold red]Error:[/] File not found at '{args.filepath}'")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
hours = parse_time(args.time)
|
|
74
|
+
if hours is None:
|
|
75
|
+
console.print(f"[bold red]Error:[/] Invalid time format '{args.time}'. Use formats like '24h', '7d', or '360'.")
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
# --- Hashing ---
|
|
79
|
+
console.print("[cyan]Calculating file hash...[/]")
|
|
80
|
+
client_hash = calculate_file_hash(args.filepath)
|
|
81
|
+
console.print(f" [cyan]- Hash:[/] {client_hash}")
|
|
82
|
+
|
|
83
|
+
# --- Prepare Upload ---
|
|
84
|
+
upload_url = f"{args.url.rstrip('/')}/upload"
|
|
85
|
+
filename = os.path.basename(args.filepath)
|
|
86
|
+
file_size = os.path.getsize(args.filepath)
|
|
87
|
+
|
|
88
|
+
fields = {
|
|
89
|
+
'hours': str(hours),
|
|
90
|
+
'one_time': str(args.one_time).lower(),
|
|
91
|
+
'client_hash': client_hash,
|
|
92
|
+
'file': (filename, open(args.filepath, 'rb'), 'application/octet-stream')
|
|
93
|
+
}
|
|
94
|
+
if args.password:
|
|
95
|
+
fields['password'] = args.password
|
|
96
|
+
|
|
97
|
+
encoder = MultipartEncoder(fields=fields)
|
|
98
|
+
response = None
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
bar_format = "{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [Speed: {rate_fmt}, ETA: {remaining}]"
|
|
102
|
+
with tqdm(total=encoder.len, unit='B', unit_scale=True, desc=f"Uploading {filename}", bar_format=bar_format) as pbar:
|
|
103
|
+
monitor = MultipartEncoderMonitor(encoder, lambda m: pbar.update(m.bytes_read - pbar.n))
|
|
104
|
+
response = requests.post(upload_url, data=monitor, headers={'Content-Type': monitor.content_type})
|
|
105
|
+
|
|
106
|
+
except FileNotFoundError:
|
|
107
|
+
console.print(f"[bold red]Error:[/] The file '{args.filepath}' was not found.")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
except requests.exceptions.RequestException as e:
|
|
110
|
+
console.print("\n[bold red]An error occurred while connecting to the server:[/]")
|
|
111
|
+
console.print(e)
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
console.print(f"\n[bold red]An unexpected error occurred:[/] {e}")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
finally:
|
|
117
|
+
# Ensure the file handle is closed
|
|
118
|
+
if 'file' in fields and not fields['file'][1].closed:
|
|
119
|
+
fields['file'][1].close()
|
|
120
|
+
|
|
121
|
+
# --- Handle Response ---
|
|
122
|
+
if response is not None:
|
|
123
|
+
if response.status_code == 200:
|
|
124
|
+
console.print("\n[bold green]Upload successful![/]")
|
|
125
|
+
console.print(Panel(response.text.strip(), title="[bold cyan]Download Link[/]", border_style="green"))
|
|
126
|
+
else:
|
|
127
|
+
console.print(f"\n[bold red]Error:[/] Upload failed with status code {response.status_code}")
|
|
128
|
+
try:
|
|
129
|
+
error_details = response.json()
|
|
130
|
+
console.print(f"[red]Server message:[/] {error_details.get('detail', 'No details provided.')}")
|
|
131
|
+
except requests.exceptions.JSONDecodeError:
|
|
132
|
+
console.print(f"[red]Server response:\n[/]{response.text}")
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
multiprocessing.freeze_support()
|
|
137
|
+
main()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools]
|
|
6
|
+
packages = ["cli"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "tempspace-cli"
|
|
10
|
+
version = "1.0.0"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name="Tempspace", email="tempspace@example.com" },
|
|
13
|
+
]
|
|
14
|
+
description = "A command-line tool for uploading files to Tempspace."
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
license = { text = "MIT License" }
|
|
17
|
+
requires-python = ">=3.7"
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"requests==2.31.0",
|
|
24
|
+
"tqdm==4.66.1",
|
|
25
|
+
"requests-toolbelt==1.0.0",
|
|
26
|
+
"rich==13.7.1",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
tempspace = "cli.tempspace:main"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tempspace-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A command-line tool for uploading files to Tempspace.
|
|
5
|
+
Author-email: Tempspace <tempspace@example.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests==2.31.0
|
|
12
|
+
Requires-Dist: tqdm==4.66.1
|
|
13
|
+
Requires-Dist: requests-toolbelt==1.0.0
|
|
14
|
+
Requires-Dist: rich==13.7.1
|
|
15
|
+
|
|
16
|
+
# 🚀 FileShare - Enhanced Terminal File Upload Service
|
|
17
|
+
|
|
18
|
+
A modern, terminal-style file sharing service with password protection, rate limiting, and QR code generation.
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
21
|
+
|
|
22
|
+
### Server
|
|
23
|
+
|
|
24
|
+
To run the web server, you can clone the repository and run it locally.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone the repository
|
|
28
|
+
git clone <your-repo>
|
|
29
|
+
cd fileshare
|
|
30
|
+
|
|
31
|
+
# Install dependencies
|
|
32
|
+
pip install -r requirements.txt
|
|
33
|
+
|
|
34
|
+
# Copy environment template
|
|
35
|
+
cp .env.example .env
|
|
36
|
+
|
|
37
|
+
# Edit .env and change admin credentials!
|
|
38
|
+
nano .env
|
|
39
|
+
|
|
40
|
+
# Run the server
|
|
41
|
+
python main.py
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Command-Line Tool (CLI)
|
|
45
|
+
|
|
46
|
+
To use the command-line tool, you can install it directly from PyPI.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install tempspace-cli
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## ✨ Features
|
|
53
|
+
|
|
54
|
+
### 🔒 Security
|
|
55
|
+
- **Rate Limiting**: 10 uploads/hour, 100 downloads/hour per IP
|
|
56
|
+
- **Password Protection**: Optional password-protected uploads
|
|
57
|
+
- **Secure Admin Panel**: HTTP Basic Auth for debug endpoints
|
|
58
|
+
- **File Deduplication**: Automatic duplicate detection (SHA256)
|
|
59
|
+
- **One-Time Downloads**: Self-destructing links after first download
|
|
60
|
+
|
|
61
|
+
### 🎨 UI/UX
|
|
62
|
+
- **Modern Terminal Design**: Green-on-black terminal aesthetic
|
|
63
|
+
- **Drag & Drop**: Intuitive file upload interface
|
|
64
|
+
- **QR Code Generation**: Instant QR codes for mobile sharing
|
|
65
|
+
- **Upload History**: Local storage of recent uploads (browser)
|
|
66
|
+
- **Toast Notifications**: Non-intrusive feedback system
|
|
67
|
+
- **Real-time Progress**: Upload progress with speed indicators
|
|
68
|
+
- **Countdown Timer**: Shows time remaining until expiry
|
|
69
|
+
- **Responsive Design**: Works on mobile, tablet, and desktop
|
|
70
|
+
|
|
71
|
+
### 📦 File Management
|
|
72
|
+
- **Large File Support**: Up to 4GB per file
|
|
73
|
+
- **Image Previews**: Automatic preview for images
|
|
74
|
+
- **Multiple Expiry Options**: 1, 3, 6, 12, or 24 hours
|
|
75
|
+
- **Chunked Uploads**: Efficient streaming for large files
|
|
76
|
+
- **Automatic Cleanup**: Background task removes expired files
|
|
77
|
+
|
|
78
|
+
## 📖 Usage
|
|
79
|
+
|
|
80
|
+
### Web Interface
|
|
81
|
+
|
|
82
|
+
1. **Open** `http://localhost:8000` in your browser
|
|
83
|
+
2. **Drag & drop** a file or click to browse
|
|
84
|
+
3. **Configure** expiry time and optional password
|
|
85
|
+
4. **Upload** and share the generated link
|
|
86
|
+
5. **Scan QR code** for mobile access
|
|
87
|
+
|
|
88
|
+
### CLI Usage
|
|
89
|
+
|
|
90
|
+
Once installed, you can use the `tempspace` command.
|
|
91
|
+
|
|
92
|
+
#### Basic Upload
|
|
93
|
+
```bash
|
|
94
|
+
tempspace /path/to/document.pdf -t 6h
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Password Protected Upload
|
|
98
|
+
```bash
|
|
99
|
+
tempspace /path/to/secret.txt -t 12h -p "mypassword"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### One-Time Download
|
|
103
|
+
```bash
|
|
104
|
+
tempspace /path/to/report.pdf --one-time
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 🔧 Configuration
|
|
108
|
+
|
|
109
|
+
### Environment Variables
|
|
110
|
+
|
|
111
|
+
Create a `.env` file for the server:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Admin credentials (CHANGE THESE!)
|
|
115
|
+
ADMIN_USER=admin
|
|
116
|
+
ADMIN_PASS=your-secure-password-here
|
|
117
|
+
|
|
118
|
+
# Optional: Custom limits
|
|
119
|
+
RATE_LIMIT_UPLOADS=10
|
|
120
|
+
RATE_LIMIT_DOWNLOADS=100
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 🛡️ Admin Endpoints
|
|
124
|
+
|
|
125
|
+
Protected by HTTP Basic Auth (use credentials from `.env`).
|
|
126
|
+
|
|
127
|
+
### View Statistics
|
|
128
|
+
```bash
|
|
129
|
+
curl -u admin:password http://localhost:8000/debug/stats
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Wipe All Files
|
|
133
|
+
```bash
|
|
134
|
+
curl -X POST -u admin:password http://localhost:8000/debug/wipe
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
⚠️ **Warning**: This deletes ALL uploaded files permanently!
|
|
138
|
+
|
|
139
|
+
## 📝 License
|
|
140
|
+
|
|
141
|
+
MIT License - Feel free to use and modify!
|
|
142
|
+
|
|
143
|
+
## 🤝 Contributing
|
|
144
|
+
|
|
145
|
+
Contributions welcome! Please:
|
|
146
|
+
1. Fork the repository
|
|
147
|
+
2. Create a feature branch
|
|
148
|
+
3. Submit a pull request
|
|
149
|
+
|
|
150
|
+
## 📧 Support
|
|
151
|
+
|
|
152
|
+
For issues and questions, please open a GitHub issue.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
cli/__init__.py
|
|
4
|
+
cli/tempspace.py
|
|
5
|
+
tempspace_cli.egg-info/PKG-INFO
|
|
6
|
+
tempspace_cli.egg-info/SOURCES.txt
|
|
7
|
+
tempspace_cli.egg-info/dependency_links.txt
|
|
8
|
+
tempspace_cli.egg-info/entry_points.txt
|
|
9
|
+
tempspace_cli.egg-info/requires.txt
|
|
10
|
+
tempspace_cli.egg-info/top_level.txt
|
|
11
|
+
tests/test_cli.py
|
|
12
|
+
tests/test_main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cli
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from cli.tempspace import parse_time
|
|
3
|
+
|
|
4
|
+
def test_parse_time():
|
|
5
|
+
assert parse_time("7d") == 168
|
|
6
|
+
assert parse_time("24h") == 24
|
|
7
|
+
assert parse_time("360") == 360
|
|
8
|
+
assert parse_time("1D") == 24
|
|
9
|
+
assert parse_time("2H") == 2
|
|
10
|
+
assert parse_time(" 7d ") == 168
|
|
11
|
+
assert parse_time("invalid") is None
|
|
12
|
+
assert parse_time("1w") is None
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fastapi.testclient import TestClient
|
|
3
|
+
from main import app, UPLOAD_DIR
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
# Create a client for testing
|
|
10
|
+
client = TestClient(app)
|
|
11
|
+
|
|
12
|
+
@pytest.fixture(scope="module", autouse=True)
|
|
13
|
+
def setup_and_teardown():
|
|
14
|
+
"""Fixture to set up a clean upload directory for tests and tear it down afterward."""
|
|
15
|
+
# Ensure a clean state before tests run
|
|
16
|
+
if UPLOAD_DIR.exists():
|
|
17
|
+
shutil.rmtree(UPLOAD_DIR)
|
|
18
|
+
UPLOAD_DIR.mkdir()
|
|
19
|
+
|
|
20
|
+
# Yield control to the tests
|
|
21
|
+
yield
|
|
22
|
+
|
|
23
|
+
# Teardown: clean up the uploads directory after all tests in the module are done
|
|
24
|
+
if UPLOAD_DIR.exists():
|
|
25
|
+
shutil.rmtree(UPLOAD_DIR)
|
|
26
|
+
|
|
27
|
+
def test_upload_file():
|
|
28
|
+
"""Test basic file upload."""
|
|
29
|
+
response = client.post(
|
|
30
|
+
"/upload",
|
|
31
|
+
files={"file": ("test_file.txt", b"hello world", "text/plain")},
|
|
32
|
+
data={"hours": "1"},
|
|
33
|
+
)
|
|
34
|
+
assert response.status_code == 200
|
|
35
|
+
data = response.json()
|
|
36
|
+
assert data["success"] is True
|
|
37
|
+
assert data["filename"] == "test_file.txt"
|
|
38
|
+
assert "url" in data
|
|
39
|
+
assert "file_id" in data
|
|
40
|
+
|
|
41
|
+
def test_upload_with_password():
|
|
42
|
+
"""Test file upload with a password."""
|
|
43
|
+
response = client.post(
|
|
44
|
+
"/upload",
|
|
45
|
+
files={"file": ("test_password.txt", b"secret content", "text/plain")},
|
|
46
|
+
data={"hours": "1", "password": "testpassword"},
|
|
47
|
+
)
|
|
48
|
+
assert response.status_code == 200
|
|
49
|
+
data = response.json()
|
|
50
|
+
assert data["success"] is True
|
|
51
|
+
assert data["password_protected"] is True
|
|
52
|
+
|
|
53
|
+
def test_download_file_no_password():
|
|
54
|
+
"""Test downloading a file that doesn't require a password."""
|
|
55
|
+
# First, upload a file
|
|
56
|
+
upload_response = client.post(
|
|
57
|
+
"/upload",
|
|
58
|
+
files={"file": ("download_test.txt", b"download content", "text/plain")},
|
|
59
|
+
data={"hours": "1"},
|
|
60
|
+
)
|
|
61
|
+
upload_data = upload_response.json()
|
|
62
|
+
file_url = upload_data["url"]
|
|
63
|
+
|
|
64
|
+
# Now, try to download it
|
|
65
|
+
# The URL contains the full host, so we need to extract the path
|
|
66
|
+
path = file_url.split("/", 3)[-1]
|
|
67
|
+
download_response = client.get(path)
|
|
68
|
+
|
|
69
|
+
assert download_response.status_code == 200
|
|
70
|
+
assert download_response.content == b"download content"
|
|
71
|
+
|
|
72
|
+
def test_download_file_with_correct_password():
|
|
73
|
+
"""Test downloading a password-protected file with the correct password."""
|
|
74
|
+
upload_response = client.post(
|
|
75
|
+
"/upload",
|
|
76
|
+
files={"file": ("protected_download.txt", b"protected", "text/plain")},
|
|
77
|
+
data={"hours": "1", "password": "supersecret"},
|
|
78
|
+
)
|
|
79
|
+
upload_data = upload_response.json()
|
|
80
|
+
path = upload_data["url"].split("/", 3)[-1]
|
|
81
|
+
|
|
82
|
+
download_response = client.get(f"{path}?password=supersecret")
|
|
83
|
+
assert download_response.status_code == 200
|
|
84
|
+
assert download_response.content == b"protected"
|
|
85
|
+
|
|
86
|
+
def test_download_file_with_incorrect_password():
|
|
87
|
+
"""Test downloading a password-protected file with an incorrect password."""
|
|
88
|
+
upload_response = client.post(
|
|
89
|
+
"/upload",
|
|
90
|
+
files={"file": ("protected_fail.txt", b"protected fail", "text/plain")},
|
|
91
|
+
data={"hours": "1", "password": "supersecret"},
|
|
92
|
+
)
|
|
93
|
+
upload_data = upload_response.json()
|
|
94
|
+
path = upload_data["url"].split("/", 3)[-1]
|
|
95
|
+
|
|
96
|
+
download_response = client.get(f"{path}?password=wrongpassword")
|
|
97
|
+
assert download_response.status_code == 403 # Forbidden
|
|
98
|
+
|
|
99
|
+
def test_one_time_download():
|
|
100
|
+
"""Test that a one-time download file is deleted after being accessed."""
|
|
101
|
+
upload_response = client.post(
|
|
102
|
+
"/upload",
|
|
103
|
+
files={"file": ("one_time.txt", b"one time content", "text/plain")},
|
|
104
|
+
data={"hours": "1", "one_time": "true"},
|
|
105
|
+
)
|
|
106
|
+
upload_data = upload_response.json()
|
|
107
|
+
path = upload_data["url"].split("/", 3)[-1]
|
|
108
|
+
|
|
109
|
+
# First download should succeed
|
|
110
|
+
first_download_response = client.get(path)
|
|
111
|
+
assert first_download_response.status_code == 200
|
|
112
|
+
|
|
113
|
+
# Let the async deletion task run
|
|
114
|
+
async def wait_for_deletion():
|
|
115
|
+
await asyncio.sleep(0.1)
|
|
116
|
+
|
|
117
|
+
asyncio.run(wait_for_deletion())
|
|
118
|
+
|
|
119
|
+
# Second download should fail
|
|
120
|
+
second_download_response = client.get(path)
|
|
121
|
+
assert second_download_response.status_code == 404 # Not Found
|
|
122
|
+
|
|
123
|
+
def test_delete_file():
|
|
124
|
+
"""Test deleting a file using the client_id."""
|
|
125
|
+
# Upload a file with a specific client_id
|
|
126
|
+
client_id = "test-client-123"
|
|
127
|
+
upload_response = client.post(
|
|
128
|
+
"/upload",
|
|
129
|
+
files={"file": ("to_be_deleted.txt", b"delete me", "text/plain")},
|
|
130
|
+
data={"hours": "1", "client_id": client_id},
|
|
131
|
+
)
|
|
132
|
+
upload_data = upload_response.json()
|
|
133
|
+
file_id = upload_data["file_id"]
|
|
134
|
+
|
|
135
|
+
# Now, delete the file with the correct client_id
|
|
136
|
+
delete_response = client.request(
|
|
137
|
+
"DELETE",
|
|
138
|
+
f"/delete/{file_id}",
|
|
139
|
+
json={"client_id": client_id},
|
|
140
|
+
)
|
|
141
|
+
assert delete_response.status_code == 200
|
|
142
|
+
assert delete_response.json()["success"] is True
|
|
143
|
+
|
|
144
|
+
# Verify the file is gone
|
|
145
|
+
path = upload_data["url"].split("/", 3)[-1]
|
|
146
|
+
get_response = client.get(path)
|
|
147
|
+
assert get_response.status_code == 404
|
|
148
|
+
|
|
149
|
+
def test_delete_file_unauthorized():
|
|
150
|
+
"""Test that deleting a file with the wrong client_id fails."""
|
|
151
|
+
# Upload a file with a specific client_id
|
|
152
|
+
owner_client_id = "owner-client"
|
|
153
|
+
upload_response = client.post(
|
|
154
|
+
"/upload",
|
|
155
|
+
files={"file": ("unauthorized_delete.txt", b"don't delete me", "text/plain")},
|
|
156
|
+
data={"hours": "1", "client_id": owner_client_id},
|
|
157
|
+
)
|
|
158
|
+
upload_data = upload_response.json()
|
|
159
|
+
file_id = upload_data["file_id"]
|
|
160
|
+
|
|
161
|
+
# Attempt to delete with a different client_id
|
|
162
|
+
attacker_client_id = "attacker-client"
|
|
163
|
+
delete_response = client.request(
|
|
164
|
+
"DELETE",
|
|
165
|
+
f"/delete/{file_id}",
|
|
166
|
+
json={"client_id": attacker_client_id},
|
|
167
|
+
)
|
|
168
|
+
assert delete_response.status_code == 403 # Forbidden
|