Initial commit.
This commit is contained in:
commit
24da35f11e
9 changed files with 1753 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
uploads/
|
||||
server.log
|
||||
ssl-dir/*.key
|
||||
1
activate
Symbolic link
1
activate
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/home/archibaldik/progs/nas-python/.venv/naspy/bin/activate
|
||||
198
home.html
Normal file
198
home.html
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Network File Transfer</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
}
|
||||
h1 { color: #333; margin-bottom: 10px; text-align: center; }
|
||||
.subtitle { text-align: center; color: #666; margin-bottom: 30px; font-size: 14px; }
|
||||
.section { margin-bottom: 30px; }
|
||||
.section h2 { font-size: 16px; color: #667eea; margin-bottom: 15px; border-bottom: 2px solid #667eea; padding-bottom: 8px; }
|
||||
.upload-area {
|
||||
border: 2px dashed #667eea;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
background: #f8f9ff;
|
||||
}
|
||||
.upload-area:hover { background: #f0f2ff; border-color: #764ba2; }
|
||||
.upload-area.dragover { background: #e8ebff; border-color: #764ba2; }
|
||||
#fileInput { display: none; }
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
.btn:hover { background: #764ba2; }
|
||||
.btn-secondary { background: #6c757d; }
|
||||
.btn-secondary:hover { background: #5a6268; }
|
||||
.file-list {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.file-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.file-name { flex: 1; color: #333; word-break: break-all; font-size: 14px; }
|
||||
.file-actions { display: flex; gap: 8px; margin-left: 10px; }
|
||||
.btn-small { padding: 6px 12px; font-size: 12px; }
|
||||
.message {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 15px;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.message.show { display: block; }
|
||||
.message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
||||
.message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
||||
.no-files { text-align: center; color: #999; padding: 20px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📁 Network File Transfer</h1>
|
||||
<p class="subtitle">Upload and download files from your network drive</p>
|
||||
<div id="message" class="message"></div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📤 Upload Files</h2>
|
||||
<div class="upload-area" id="uploadArea">
|
||||
<p>📁 Drag and drop files here</p>
|
||||
<p style="font-size: 12px; color: #999;">or click to browse</p>
|
||||
<button class="btn btn-secondary btn-small">Choose Files</button>
|
||||
</div>
|
||||
<input type="file" id="fileInput" multiple>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📥 Download Files</h2>
|
||||
<div class="file-list" id="fileList">
|
||||
<div class="no-files">Loading files...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const uploadArea = document.getElementById('uploadArea');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const fileList = document.getElementById('fileList');
|
||||
const message = document.getElementById('message');
|
||||
|
||||
uploadArea.addEventListener('click', () => fileInput.click());
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.add('dragover');
|
||||
});
|
||||
uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover'));
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.classList.remove('dragover');
|
||||
uploadFiles(e.dataTransfer.files);
|
||||
});
|
||||
fileInput.addEventListener('change', (e) => uploadFiles(e.target.files));
|
||||
|
||||
async function uploadFiles(files) {
|
||||
for (let file of files) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
try {
|
||||
const response = await fetch('/upload.sh', { method: 'POST', body: formData });
|
||||
if (response.ok) {
|
||||
showMessage(`✓ ${file.name} uploaded`, 'success');
|
||||
loadFileList();
|
||||
} else {
|
||||
showMessage(`✗ Error uploading ${file.name}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(`✗ Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
async function loadFileList() {
|
||||
try {
|
||||
const response = await fetch('/list.sh');
|
||||
const text = await response.text();
|
||||
const files = text.trim().split('\n').filter(f => f);
|
||||
fileList.innerHTML = files.length === 0
|
||||
? '<div class="no-files">No files yet</div>'
|
||||
: files.map(file => `
|
||||
<div class="file-item">
|
||||
<span class="file-name">${file}</span>
|
||||
<div class="file-actions">
|
||||
<a href="/download.sh?file=${encodeURIComponent(file)}" class="btn btn-small">Download</a>
|
||||
<button class="btn btn-small" style="background: #dc3545;" onclick="deleteFile('${file}')">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
fileList.innerHTML = '<div class="no-files">Error loading files</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteFile(filename) {
|
||||
if (!confirm(`Delete ${filename}?`)) return;
|
||||
try {
|
||||
const response = await fetch(`/delete.sh?file=${encodeURIComponent(filename)}`, { method: 'POST' });
|
||||
if (response.ok) {
|
||||
showMessage(`✓ ${filename} deleted`, 'success');
|
||||
loadFileList();
|
||||
} else {
|
||||
showMessage(`✗ Error deleting ${filename}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage(`✗ Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
message.textContent = text;
|
||||
message.className = `message show ${type}`;
|
||||
setTimeout(() => message.classList.remove('show'), 4000);
|
||||
}
|
||||
|
||||
loadFileList();
|
||||
setInterval(loadFileList, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1041
index.html
Normal file
1041
index.html
Normal file
File diff suppressed because it is too large
Load diff
39
server.py
Normal file
39
server.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import ssl
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
import socketserver
|
||||
|
||||
with open("home.html", "r", encoding="utf-8" ) as f:
|
||||
greeting_page = f.read()
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def sql_req(self):
|
||||
data = ""
|
||||
d_type = ""
|
||||
if self.path == "/":
|
||||
d_type = "text/html"
|
||||
data = greeting_page
|
||||
return (data, d_type)
|
||||
|
||||
|
||||
def do_GET(self):
|
||||
if self.path != "/":
|
||||
self.send_response(404)
|
||||
self.end_headers()
|
||||
return
|
||||
self.send_response(200)
|
||||
self.send_header("Data type", self.sql_req()[1])
|
||||
self.end_headers()
|
||||
self.wfile.write(self.sql_req()[0].encode())
|
||||
|
||||
server = HTTPServer(("0.0.0.0", 4443), Handler)
|
||||
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.load_cert_chain(certfile="ssl-dir/server.crt",keyfile="ssl-dir/server.key")
|
||||
|
||||
httpd = socketserver.TCPServer(("0.0.0.0", 4445), Handler)
|
||||
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side = True)
|
||||
|
||||
|
||||
server.socket = ssl_context.wrap_socket(server.socket, server_side=True)
|
||||
|
||||
server.serve_forever()
|
||||
384
server_core.py
Normal file
384
server_core.py
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
HTTPS File Transfer Server for OpenWRT/Notebook
|
||||
Secure file upload, download, and management over HTTPS
|
||||
"""
|
||||
|
||||
import ssl
|
||||
import os
|
||||
import json
|
||||
import mimetypes
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
# ============================================================================
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Server settings
|
||||
HOST = "0.0.0.0"
|
||||
HTTP_PORT = 8080
|
||||
HTTPS_PORT = 4443
|
||||
|
||||
# File settings
|
||||
UPLOAD_DIR = "./uploads"
|
||||
CERT_DIR = "./ssl-dir"
|
||||
SERVER_CERT = os.path.join(CERT_DIR, "server.crt")
|
||||
SERVER_KEY = os.path.join(CERT_DIR, "server.key")
|
||||
|
||||
# Logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('server.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ============================================================================
|
||||
# Utility Functions
|
||||
# ============================================================================
|
||||
|
||||
def ensure_upload_dir():
|
||||
"""Create upload directory if it doesn't exist"""
|
||||
Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"Upload directory: {os.path.abspath(UPLOAD_DIR)}")
|
||||
|
||||
def ensure_ssl_certs():
|
||||
"""Check if SSL certificates exist"""
|
||||
if not os.path.exists(SERVER_CERT) or not os.path.exists(SERVER_KEY):
|
||||
logger.error(f"SSL certificates not found in {CERT_DIR}")
|
||||
logger.info("Generate certificates with:")
|
||||
logger.info(f" mkdir -p {CERT_DIR}")
|
||||
logger.info(f" openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \\")
|
||||
logger.info(f" -keyout {SERVER_KEY} -out {SERVER_CERT} \\")
|
||||
logger.info(f" -subj '/C=US/ST=State/L=City/O=Org/CN=localhost'")
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_file_list():
|
||||
"""Get list of files in upload directory
|
||||
Skips directories"""
|
||||
try:
|
||||
files = []
|
||||
for filename in os.listdir(UPLOAD_DIR):
|
||||
filepath = os.path.join(UPLOAD_DIR, filename)
|
||||
if os.path.isfile(filepath):
|
||||
size = os.path.getsize(filepath)
|
||||
mtime = os.path.getmtime(filepath)
|
||||
files.append({
|
||||
"name": filename,
|
||||
"size": size,
|
||||
"modified": datetime.fromtimestamp(mtime).isoformat(),
|
||||
"size_human": format_size(size)
|
||||
})
|
||||
return sorted(files, key=lambda x: x['modified'], reverse=True)
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing files: {e}")
|
||||
return []
|
||||
|
||||
def format_size(bytes_size):
|
||||
"""Convert bytes to human readable format"""
|
||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||
if bytes_size < 1024:
|
||||
return f"{bytes_size:.1f}{unit}"
|
||||
bytes_size /= 1024
|
||||
return f"{bytes_size:.1f}TB"
|
||||
|
||||
def get_content_type(filename):
|
||||
"""Get MIME type for file"""
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
return mime_type or 'application/octet-stream'
|
||||
|
||||
# ============================================================================
|
||||
# HTML/CSS/JavaScript
|
||||
# ============================================================================
|
||||
|
||||
with open("index.html", "r", encoding="utf-8") as f:
|
||||
HTML_TEMPLATE = f.read()
|
||||
# ============================================================================
|
||||
# Request Handler
|
||||
# ============================================================================
|
||||
|
||||
class FileTransferHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP request handler for file transfer operations"""
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Override logging to use our logger"""
|
||||
logger.info(f"{self.client_address[0]} - {format % args}")
|
||||
|
||||
def do_GET(self):
|
||||
"""Handle GET requests"""
|
||||
try:
|
||||
# Parse URL
|
||||
parsed_url = urllib.parse.urlparse(self.path)
|
||||
path = parsed_url.path
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
|
||||
# Route handling
|
||||
if path == "/":
|
||||
self.serve_index()
|
||||
elif path == "/api/files":
|
||||
self.serve_file_list()
|
||||
elif path == "/download":
|
||||
filename = query_params.get('file', [None])[0]
|
||||
if filename:
|
||||
self.serve_download(filename)
|
||||
else:
|
||||
self.send_error(400, "Missing file parameter")
|
||||
elif path == "/status":
|
||||
self.serve_status()
|
||||
else:
|
||||
self.send_error(404, "Not found")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GET error: {e}")
|
||||
self.send_error(500, "Internal server error")
|
||||
|
||||
def do_POST(self):
|
||||
"""Handle POST requests"""
|
||||
try:
|
||||
parsed_url = urllib.parse.urlparse(self.path)
|
||||
path = parsed_url.path
|
||||
query_params = urllib.parse.parse_qs(parsed_url.path.split('?')[0] if '?' in self.path else '')
|
||||
if path == "/upload":
|
||||
self.handle_upload()
|
||||
elif path == "/delete":
|
||||
query_params = urllib.parse.parse_qs(self.path.split('?')[1] if '?' in self.path else '')
|
||||
|
||||
filename = query_params.get('file', [None])[0]
|
||||
if filename:
|
||||
self.handle_delete(filename)
|
||||
else:
|
||||
self.send_error(400, "Missing file parameter")
|
||||
else:
|
||||
self.send_error(404, "Not found")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"POST error: {e}")
|
||||
self.send_error(500, "Internal server error")
|
||||
|
||||
def serve_index(self):
|
||||
"""Serve the main HTML page"""
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML_TEMPLATE.encode('utf-8'))
|
||||
|
||||
def serve_file_list(self):
|
||||
"""Serve JSON list of files"""
|
||||
files = get_file_list()
|
||||
response = {
|
||||
"files": files,
|
||||
"total_files": len(files),
|
||||
"upload_dir": os.path.abspath(UPLOAD_DIR)
|
||||
}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response).encode('utf-8'))
|
||||
|
||||
def serve_download(self, filename):
|
||||
"""Serve file download"""
|
||||
# Security: prevent directory traversal
|
||||
if ".." in filename or filename.startswith("/"):
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
|
||||
filepath = os.path.join(UPLOAD_DIR, filename)
|
||||
|
||||
if not os.path.exists(filepath) or not os.path.isfile(filepath):
|
||||
self.send_error(404, "File not found")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
file_size = os.path.getsize(filepath)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", get_content_type(filename))
|
||||
self.send_header("Content-Disposition", f'attachment; filename="{filename}"')
|
||||
self.send_header("Content-Length", str(file_size))
|
||||
self.end_headers()
|
||||
|
||||
# Send file in chunks
|
||||
chunk_size = 8192
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
self.wfile.write(chunk)
|
||||
|
||||
logger.info(f"Downloaded: {filename}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Download error: {e}")
|
||||
self.send_error(500, "Error downloading file")
|
||||
|
||||
def serve_status(self):
|
||||
"""Serve server status"""
|
||||
files = get_file_list()
|
||||
total_size = sum(f['size'] for f in files)
|
||||
|
||||
status = {
|
||||
"status": "online",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"total_files": len(files),
|
||||
"total_size": total_size,
|
||||
"upload_dir": UPLOAD_DIR,
|
||||
"https_port": HTTPS_PORT
|
||||
}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(status).encode()) #encode('utf-8')
|
||||
|
||||
def handle_upload(self):
|
||||
"""Handle file upload"""
|
||||
try:
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
|
||||
if content_length > 0:
|
||||
# Parse multipart form data
|
||||
content_type = self.headers.get('Content-Type', '')
|
||||
|
||||
if 'multipart/form-data' in content_type:
|
||||
# Extract boundary
|
||||
boundary = content_type.split("boundary=")[1].encode()
|
||||
|
||||
# Read data
|
||||
body = self.rfile.read(content_length)
|
||||
|
||||
# Simple multipart parser
|
||||
parts = body.split(b'--' + boundary)
|
||||
for part in parts:
|
||||
if b'filename=' in part:
|
||||
# Extract filename
|
||||
try:
|
||||
filename_start = part.find(b'filename="') + 10
|
||||
filename_end = part.find(b'"', filename_start)
|
||||
filename = part[filename_start:filename_end].decode()
|
||||
|
||||
# Extract file data
|
||||
data_start = part.find(b'\r\n\r\n') + 4
|
||||
data_end = part.rfind(b'\r\n')
|
||||
file_data = part[data_start:data_end]
|
||||
|
||||
# Save file
|
||||
filepath = os.path.join(UPLOAD_DIR, filename)
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(file_data)
|
||||
|
||||
logger.info(f"Uploaded: {filename} ({len(file_data)} bytes)")
|
||||
except Exception as e:
|
||||
logger.error(f"Upload parse error: {e}")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"status": "success"}).encode())
|
||||
else:
|
||||
self.send_error(400, "No file provided")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Upload error: {e}")
|
||||
self.send_error(500, "Upload failed")
|
||||
|
||||
def handle_delete(self, filename):
|
||||
"""Handle file deletion"""
|
||||
# Security: prevent directory traversal
|
||||
if ".." in filename or filename.startswith("/"):
|
||||
self.send_error(403, "Forbidden")
|
||||
return
|
||||
|
||||
filepath = os.path.join(UPLOAD_DIR, filename)
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.send_error(404, "File not found")
|
||||
return
|
||||
|
||||
try:
|
||||
os.remove(filepath)
|
||||
logger.info(f"Deleted: {filename}")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"status": "success"}).encode())
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Delete error: {e}")
|
||||
self.send_error(500, "Delete failed")
|
||||
|
||||
# ============================================================================
|
||||
# Server Initialization
|
||||
# ============================================================================
|
||||
|
||||
def create_https_server():
|
||||
"""Create and configure HTTPS server"""
|
||||
server = HTTPServer((HOST, HTTPS_PORT), FileTransferHandler)
|
||||
|
||||
# Create SSL context
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(certfile=SERVER_CERT, keyfile=SERVER_KEY)
|
||||
|
||||
# Optional: Improve security
|
||||
context.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
||||
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
|
||||
# Wrap socket
|
||||
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||
|
||||
return server
|
||||
|
||||
def create_http_server():
|
||||
"""Create plain HTTP server (for redirects if needed)"""
|
||||
server = HTTPServer((HOST, HTTP_PORT), FileTransferHandler)
|
||||
return server
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
print("=" * 70)
|
||||
print(" HTTPS File Transfer Server")
|
||||
print("=" * 70)
|
||||
|
||||
# Check requirements
|
||||
if not ensure_ssl_certs():
|
||||
print("\n❌ SSL certificates not found. Please generate them first.")
|
||||
return
|
||||
|
||||
ensure_upload_dir()
|
||||
|
||||
# Create servers
|
||||
https_server = create_https_server()
|
||||
|
||||
logger.info("=" * 70)
|
||||
logger.info("File Transfer Server Starting")
|
||||
logger.info("=" * 70)
|
||||
logger.info(f"📁 Upload directory: {os.path.abspath(UPLOAD_DIR)}")
|
||||
logger.info(f"🔒 HTTPS port: {HTTPS_PORT}")
|
||||
logger.info(f"📝 Log file: server.log")
|
||||
logger.info("=" * 70)
|
||||
|
||||
print(f"\n✅ Server starting...")
|
||||
print(f"📱 Access from iPhone: https://localhost:{HTTPS_PORT}")
|
||||
print(f"🌐 Access from network: https://<router-ip>:{HTTPS_PORT}")
|
||||
print(f"\n⚠️ Self-signed certificate - tap 'Continue' on first visit")
|
||||
print(f"📁 Files uploaded to: {os.path.abspath(UPLOAD_DIR)}")
|
||||
print(f"📝 Logs: server.log\n")
|
||||
|
||||
try:
|
||||
https_server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Server shutting down...")
|
||||
https_server.shutdown()
|
||||
print("\n✅ Server stopped")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
33
ssl-dir/rootCA.crt
Normal file
33
ssl-dir/rootCA.crt
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
P+A/5UbpCkkTtGLMsA==
|
||||
-----END CERTIFICATE-----
|
||||
26
ssl-dir/server.conf
Normal file
26
ssl-dir/server.conf
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
x509_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = RU
|
||||
ST = Moscow
|
||||
L = Moscow
|
||||
O = My
|
||||
CN = Afina and Artyom Ltd.
|
||||
|
||||
[v3_ca]
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always,issuer
|
||||
basicConstraints = critical,CA:true
|
||||
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
|
||||
|
||||
[v3_req]
|
||||
keyUsage = critical, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
IP.1 = 192.168.1.111
|
||||
|
||||
28
ssl-dir/server.crt
Normal file
28
ssl-dir/server.crt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIExjCCAq6gAwIBAgIUfoDCACbr/m52l9pLX+jnRtJvdK4wDQYJKoZIhvcNAQEL
|
||||
BQAwZDELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9z
|
||||
Y293MQswCQYDVQQKDAJNeTEmMCQGA1UEAwwdQWZpbmEgYW5kIEFydHlvbSBMdGQu
|
||||
IFJvb3QgQ0EwHhcNMjYwNTE0MjE0MzAzWhcNMjcwNTE0MjE0MzAzWjBcMQswCQYD
|
||||
VQQGEwJSVTEPMA0GA1UECAwGTW9zY293MQ8wDQYDVQQHDAZNb3Njb3cxCzAJBgNV
|
||||
BAoMAk15MR4wHAYDVQQDDBVBZmluYSBhbmQgQXJ0eW9tIEx0ZC4wggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1Be3jL7dmATj8tJh3ChWWj2uJvLvVZo0z
|
||||
jIktQ6d0gU2Y19UYy7ZIU/NyqrnM7E7USPm40C1YaYd5oyYGClzbARChTyylz7YN
|
||||
LduU/8z2ED3F0dcyuxKFEtSTQnhvc8FyTKKZiy0/DjAyW4rY9ROn+PdwXccpI5k0
|
||||
azNnh7A1+eKyKEVLYIEFi08fDwpZNKh7rCWLYK3UydpJMedi+/rTK8CJmD5ZVZXK
|
||||
fWadqKevEb49C7G3Ne0O38q/ILHy6fOM+ic0UQAr5CnGIRaRWoiaqRHkkGSpujS8
|
||||
04f92ZHXV3LAkUm+cUsD1qMXXxc9EukSlUvrpFvk/TNIFtFDcRfRAgMBAAGjeDB2
|
||||
MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAG
|
||||
hwTAqAFvMB0GA1UdDgQWBBSthNDeS2eunXESe5OaChg36D07FDAfBgNVHSMEGDAW
|
||||
gBQpHtv69SzHowfdbnadoGrK4gcNbjANBgkqhkiG9w0BAQsFAAOCAgEAnEvznC5J
|
||||
7h7b8xqmUK2+SzfB3FSif56Fcqx+pJJHGvtrcNIUVjfjMIbHjdL+exdD5/wGgrD5
|
||||
03Mgm0g87PNBBa9H1g+Uw5ZLGVgNkiaSoqzIzuG1XD5dRJ9GVyHqadOlyLju3CoH
|
||||
ILnbrqH68KcOS1Tnxg91jOkN2UwyZXgJefb5Vu7Qn9rzeJ6ZYv1YU6daJoXbu8A9
|
||||
vlGQkSBYYZGtlrmpzBYSRsNsRnCArij8N7g2XcuLvwwhhrlN7tVv2ywNeccsIbuL
|
||||
GiismeuVxHj2vXANAHgFhltXIGimv7ytqjPDV/7Ek5FZT/FhKK3d4w04shGwtkCx
|
||||
e0Joqj+zWk6yFPq1DblGLEUnQ9IWCwUcD2tVNY38reRRtudgH2fbH5PRbEFMGLbL
|
||||
wlGzSQaQpblhV0IVUdSf+tDso2aUorF+T3qzx4TZlcPKxmyuhiKDs8pelnSvJp1T
|
||||
Os2DvYoak87jzJ5hSXrVadJlvtOMDsam9MQCyz28U8nY5u7jxbQrVu53/uRfwRJr
|
||||
5xNK6qn4sAy/1eudHnF08IopVcoKh3GAnvlVMcx18ZtkurMpM9unSd5FtLBwOS4u
|
||||
eKMGhr/VE3fqNce66ELRIljrTUWZhs2NtuQXYYhXR2QbXNZY/ousjySiwXc2CKPj
|
||||
WNqcLqoKoi1i++JT2wsBH7lRZUx1z9DI61A=
|
||||
-----END CERTIFICATE-----
|
||||
Loading…
Add table
Add a link
Reference in a new issue