修复偏移问题,修复代理问题
This commit is contained in:
@@ -21,6 +21,12 @@ main_bp = Blueprint('main', __name__)
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
def get_gcode_dir():
|
||||
conf = SystemConfig.query.filter_by(key='gcode_upload_folder').first()
|
||||
if conf and conf.value and os.path.exists(conf.value):
|
||||
return conf.value
|
||||
return current_app.config['UPLOAD_FOLDER']
|
||||
|
||||
# Guest User Middleware
|
||||
@admin_bp.before_request
|
||||
def require_admin():
|
||||
@@ -39,6 +45,7 @@ def settings():
|
||||
default_support = request.form.get('default_support', 'false')
|
||||
default_support_pattern = request.form.get('default_support_pattern', 'tree')
|
||||
default_quality = request.form.get('default_quality', 'base_global_standard.inst.cfg')
|
||||
gcode_upload_folder = request.form.get('gcode_upload_folder', '').strip()
|
||||
|
||||
# update or create config entries
|
||||
config_items = [
|
||||
@@ -48,7 +55,12 @@ def settings():
|
||||
('default_infill', default_infill),
|
||||
('default_support', default_support),
|
||||
('default_support_pattern', default_support_pattern),
|
||||
('default_quality', default_quality)
|
||||
('default_quality', default_quality),
|
||||
('gcode_upload_folder', gcode_upload_folder),
|
||||
('default_guest_stl_quota_mb', request.form.get('default_guest_stl_quota_mb', '0')),
|
||||
('default_guest_gcode_quota_mb', request.form.get('default_guest_gcode_quota_mb', '0')),
|
||||
('default_user_stl_quota_mb', request.form.get('default_user_stl_quota_mb', '0')),
|
||||
('default_user_gcode_quota_mb', request.form.get('default_user_gcode_quota_mb', '0'))
|
||||
]
|
||||
for key, val in config_items:
|
||||
conf = SystemConfig.query.filter_by(key=key).first()
|
||||
@@ -69,7 +81,73 @@ def settings():
|
||||
@admin_bp.route('/users')
|
||||
def users():
|
||||
all_users = User.query.order_by(User.created_at.desc()).all()
|
||||
return render_template('admin/users.html', users=all_users)
|
||||
user_quotas = {}
|
||||
for u in all_users:
|
||||
sq = SystemConfig.query.filter_by(key=f"user_{u.id}_stl_quota_mb").first()
|
||||
gq = SystemConfig.query.filter_by(key=f"user_{u.id}_gcode_quota_mb").first()
|
||||
user_quotas[u.id] = {
|
||||
'stl': sq.value if sq else '0',
|
||||
'gcode': gq.value if gq else '0'
|
||||
}
|
||||
return render_template('admin/users.html', users=all_users, user_quotas=user_quotas)
|
||||
|
||||
@admin_bp.route('/user/add', methods=['POST'])
|
||||
def add_user():
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
is_admin = request.form.get('is_admin') == 'on'
|
||||
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash("Username already exists", "danger")
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
u = User(username=username, is_guest=False, is_admin=is_admin)
|
||||
u.password_hash = generate_password_hash(password)
|
||||
db.session.add(u)
|
||||
db.session.commit()
|
||||
|
||||
# Save quotas
|
||||
stl_quota = request.form.get('stl_quota_mb', '0')
|
||||
gcode_quota = request.form.get('gcode_quota_mb', '0')
|
||||
if stl_quota != '0':
|
||||
db.session.add(SystemConfig(key=f"user_{u.id}_stl_quota_mb", value=stl_quota))
|
||||
if gcode_quota != '0':
|
||||
db.session.add(SystemConfig(key=f"user_{u.id}_gcode_quota_mb", value=gcode_quota))
|
||||
db.session.commit()
|
||||
|
||||
flash("User created.", "success")
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
@admin_bp.route('/user/<int:user_id>/quota', methods=['POST'])
|
||||
def update_quota(user_id):
|
||||
stl_quota = request.form.get('stl_quota_mb', '0')
|
||||
gcode_quota = request.form.get('gcode_quota_mb', '0')
|
||||
|
||||
def set_conf(k, v):
|
||||
c = SystemConfig.query.filter_by(key=k).first()
|
||||
if not c:
|
||||
c = SystemConfig(key=k)
|
||||
db.session.add(c)
|
||||
c.value = str(v)
|
||||
|
||||
set_conf(f"user_{user_id}_stl_quota_mb", stl_quota)
|
||||
set_conf(f"user_{user_id}_gcode_quota_mb", gcode_quota)
|
||||
db.session.commit()
|
||||
flash("Quotas updated.", "success")
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
@admin_bp.route('/user/<int:user_id>/password', methods=['POST'])
|
||||
def reset_password(user_id):
|
||||
user = User.query.get_or_404(user_id)
|
||||
if user.is_guest:
|
||||
flash("Cannot set password for guests.", "danger")
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
pwd = request.form.get('password')
|
||||
user.password_hash = generate_password_hash(pwd)
|
||||
db.session.commit()
|
||||
flash("Password updated.", "success")
|
||||
return redirect(url_for('admin.users'))
|
||||
|
||||
@admin_bp.route('/user/<int:user_id>/delete', methods=['POST'])
|
||||
def delete_user(user_id):
|
||||
@@ -82,7 +160,7 @@ def delete_user(user_id):
|
||||
for print_file in print_files:
|
||||
stl_path = os.path.join(current_app.config['UPLOAD_FOLDER'], print_file.filename)
|
||||
gcode_filename = print_file.filename.rsplit('.', 1)[0] + '.gcode'
|
||||
gcode_path = os.path.join(current_app.config['UPLOAD_FOLDER'], gcode_filename)
|
||||
gcode_path = os.path.join(get_gcode_dir(), gcode_filename)
|
||||
proxy_path = stl_path + '.proxy.stl'
|
||||
|
||||
if os.path.exists(stl_path):
|
||||
|
||||
@@ -13,6 +13,7 @@ from app.utils.tasks import merge_and_slice_task, slice_stl_task, simplify_stl_t
|
||||
from app import i18n_dict
|
||||
# import trimesh.repair
|
||||
from app.utils.stl_simplifier import simplify_stl
|
||||
from app.routes.admin_routes import get_gcode_dir
|
||||
|
||||
|
||||
main_bp = Blueprint('main', __name__)
|
||||
@@ -21,6 +22,53 @@ main_bp = Blueprint('main', __name__)
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
|
||||
def get_quota_info(user, file_type):
|
||||
# Returns (quota_mb, current_size_bytes)
|
||||
if user.is_admin:
|
||||
quota_mb = 0.0
|
||||
else:
|
||||
conf = SystemConfig.query.filter_by(key=f"user_{user.id}_{file_type}_quota_mb").first()
|
||||
quota_mb = float(conf.value) if conf else 0.0
|
||||
|
||||
if quota_mb == 0.0:
|
||||
if user.is_guest:
|
||||
def_conf = SystemConfig.query.filter_by(key=f"default_guest_{file_type}_quota_mb").first()
|
||||
else:
|
||||
def_conf = SystemConfig.query.filter_by(key=f"default_user_{file_type}_quota_mb").first()
|
||||
quota_mb = float(def_conf.value) if def_conf else 0.0
|
||||
|
||||
user_files = PrintFile.query.filter_by(user_id=user.id).all()
|
||||
current_size = 0
|
||||
upload_dir = current_app.config.get('UPLOAD_FOLDER', 'uploads')
|
||||
gcode_dir = get_gcode_dir()
|
||||
|
||||
for pf in user_files:
|
||||
if file_type == 'stl' and not pf.original_filename.lower().endswith(('.gcode', '.gco', '.g')):
|
||||
path = os.path.join(upload_dir, pf.filename)
|
||||
if os.path.exists(path):
|
||||
current_size += os.path.getsize(path)
|
||||
elif file_type == 'gcode':
|
||||
g_filename = pf.filename.rsplit('.', 1)[0] + '.gcode'
|
||||
path = os.path.join(gcode_dir, g_filename)
|
||||
if os.path.exists(path):
|
||||
current_size += os.path.getsize(path)
|
||||
else:
|
||||
p2 = os.path.join(upload_dir, g_filename)
|
||||
if os.path.exists(p2): current_size += os.path.getsize(p2)
|
||||
|
||||
return quota_mb, current_size
|
||||
|
||||
def check_quota(user, file_type, size_bytes):
|
||||
if user.is_admin:
|
||||
return True
|
||||
quota_mb, current_size = get_quota_info(user, file_type)
|
||||
if quota_mb <= 0.0:
|
||||
return True
|
||||
if current_size + size_bytes > quota_mb * 1024 * 1024:
|
||||
return False
|
||||
return True
|
||||
|
||||
# Guest User Middleware
|
||||
@main_bp.before_app_request
|
||||
def assign_guest_cookie():
|
||||
@@ -48,8 +96,68 @@ def set_guest_cookie(response):
|
||||
# --- Main Routes ---
|
||||
|
||||
@main_bp.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
return render_template('slice/index.html')
|
||||
user_files = PrintFile.query.filter((PrintFile.user_id == current_user.id) | (current_user.is_admin)).all() if current_user.is_admin else PrintFile.query.filter_by(user_id=current_user.id).all()
|
||||
|
||||
stl_count = 0
|
||||
stl_size = 0
|
||||
gcode_count = 0
|
||||
gcode_size = 0
|
||||
|
||||
upload_dir = current_app.config.get('UPLOAD_FOLDER', 'uploads')
|
||||
gcode_dir = get_gcode_dir()
|
||||
|
||||
for f in user_files:
|
||||
is_external_gcode = f.original_filename.lower().endswith(('.gcode', '.gco', '.g'))
|
||||
|
||||
if is_external_gcode:
|
||||
gcode_count += 1
|
||||
gcode_path = os.path.join(gcode_dir, f.filename.replace('.stl', '.gcode'))
|
||||
if os.path.exists(gcode_path):
|
||||
gcode_size += os.path.getsize(gcode_path)
|
||||
else:
|
||||
stl_count += 1
|
||||
stl_path = os.path.join(upload_dir, f.filename)
|
||||
if os.path.exists(stl_path):
|
||||
stl_size += os.path.getsize(stl_path)
|
||||
|
||||
if f.status == 'sliced':
|
||||
gcode_count += 1
|
||||
gcode_filename = f.filename.rsplit('.', 1)[0] + '.gcode'
|
||||
gcode_path = os.path.join(gcode_dir, gcode_filename)
|
||||
if os.path.exists(gcode_path):
|
||||
gcode_size += os.path.getsize(gcode_path)
|
||||
else:
|
||||
gcode_fallback = os.path.join(upload_dir, gcode_filename)
|
||||
if os.path.exists(gcode_fallback):
|
||||
gcode_size += os.path.getsize(gcode_fallback)
|
||||
|
||||
def format_size(size_bytes):
|
||||
if size_bytes < 1024:
|
||||
return f"{size_bytes} B"
|
||||
elif size_bytes < 1024 * 1024:
|
||||
return f"{size_bytes / 1024:.2f} KB"
|
||||
elif size_bytes < 1024 * 1024 * 1024:
|
||||
return f"{size_bytes / (1024 * 1024):.2f} MB"
|
||||
else:
|
||||
return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"
|
||||
|
||||
|
||||
stl_quota_mb, stl_used_bytes = get_quota_info(current_user, 'stl')
|
||||
gcode_quota_mb, gcode_used_bytes = get_quota_info(current_user, 'gcode')
|
||||
|
||||
return render_template('slice/index.html',
|
||||
stl_count=stl_count,
|
||||
stl_size_str=format_size(stl_size),
|
||||
gcode_count=gcode_count,
|
||||
gcode_size_str=format_size(gcode_size),
|
||||
stl_used_bytes=stl_used_bytes,
|
||||
stl_quota_mb=stl_quota_mb,
|
||||
gcode_used_bytes=gcode_used_bytes,
|
||||
gcode_quota_mb=gcode_quota_mb,
|
||||
format_size=format_size
|
||||
)
|
||||
|
||||
@main_bp.route('/set_language/<lang>')
|
||||
def set_language(lang):
|
||||
@@ -78,6 +186,15 @@ def files():
|
||||
if file.filename == '':
|
||||
continue
|
||||
if file and file.filename.lower().endswith('.stl'):
|
||||
file.seek(0, os.SEEK_END)
|
||||
size_bytes = file.tell()
|
||||
file.seek(0, os.SEEK_SET)
|
||||
if not check_quota(current_user, 'stl', size_bytes):
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'error': 'STL Storage Quota Exceeded'}), 400
|
||||
flash('STL Storage Quota Exceeded', 'danger')
|
||||
continue
|
||||
|
||||
original_filename = file.filename # Do not use secure_filename to keep Chinese characters
|
||||
ext = os.path.splitext(original_filename)[1].lower()
|
||||
if not ext:
|
||||
@@ -223,6 +340,9 @@ def get_quality_presets():
|
||||
@main_bp.route('/plater')
|
||||
@login_required
|
||||
def plater():
|
||||
quota_mb, current_size = get_quota_info(current_user, 'gcode')
|
||||
quota_exceeded = (quota_mb > 0 and current_size >= quota_mb * 1024 * 1024)
|
||||
|
||||
w, h, hd = get_bed_dimensions()
|
||||
presets = get_quality_presets()
|
||||
|
||||
@@ -237,7 +357,7 @@ def plater():
|
||||
|
||||
user_files = PrintFile.query.filter_by(user_id=current_user.id, file_type='stl').order_by(PrintFile.created_at.desc()).all()
|
||||
models = [{'id': f.id, 'name': f.original_filename, 'status': f.status, 'url': url_for('main.serve_proxy_file', file_id=f.id), 'transform_matrix': f.transform_matrix} for f in user_files]
|
||||
return render_template('slice/plater.html', w=w, h=h, hd=hd, presets=presets, last_quality=default_quality, models=models, offset_x=offset_x, offset_y=offset_y, default_infill=default_infill, default_support=default_support, default_support_pattern=default_support_pattern)
|
||||
return render_template('slice/plater.html', w=w, h=h, hd=hd, presets=presets, last_quality=default_quality, models=models, offset_x=offset_x, offset_y=offset_y, default_infill=default_infill, default_support=default_support, default_support_pattern=default_support_pattern, quota_exceeded=quota_exceeded)
|
||||
|
||||
@main_bp.route('/file/<int:file_id>')
|
||||
@login_required
|
||||
@@ -263,6 +383,9 @@ def serve_proxy_file(file_id):
|
||||
@main_bp.route('/api/merge_and_slice', methods=['POST'])
|
||||
@login_required
|
||||
def merge_and_slice():
|
||||
quota_mb, current_size = get_quota_info(current_user, 'gcode')
|
||||
if quota_mb > 0 and current_size >= quota_mb * 1024 * 1024:
|
||||
return jsonify({'success': False, 'error': 'GCode Storage Quota Exceeded. Please delete some files first.'})
|
||||
data = request.json
|
||||
pieces = data.get('pieces', [])
|
||||
quality = data.get('quality', 'base_global_standard.inst.cfg')
|
||||
|
||||
@@ -7,6 +7,8 @@ import requests
|
||||
from urllib.parse import urlparse
|
||||
from app.models import SystemConfig, db
|
||||
from app.utils.octoprint_client import OctoPrintClient
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
|
||||
printer_bp = Blueprint('printer', __name__, url_prefix='/printer')
|
||||
|
||||
@@ -35,20 +37,69 @@ def status():
|
||||
|
||||
return render_template('printer/status.html', status=status_data, job=job_data, error=error)
|
||||
|
||||
def get_gcode_dir():
|
||||
conf = SystemConfig.query.filter_by(key='gcode_upload_folder').first()
|
||||
if conf and conf.value and os.path.exists(conf.value):
|
||||
return conf.value
|
||||
return current_app.config['UPLOAD_FOLDER']
|
||||
|
||||
@printer_bp.route('/prepare')
|
||||
@login_required
|
||||
def prepare():
|
||||
client = get_octo_client()
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
|
||||
# Query only the sliced GCode files belonging to the current user
|
||||
user_files = PrintFile.query.filter_by(user_id=current_user.id, status='sliced').order_by(PrintFile.created_at.desc()).all()
|
||||
|
||||
files = []
|
||||
error = None
|
||||
gcode_dir = get_gcode_dir()
|
||||
|
||||
client = get_octo_client()
|
||||
octo_files_dict = {}
|
||||
if client:
|
||||
try:
|
||||
res = client.get_files()
|
||||
files = res.get('files', [])
|
||||
octo_resp = client.get_files()
|
||||
for item in octo_resp.get('files', []):
|
||||
octo_files_dict[item.get('name')] = item
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
else:
|
||||
pass
|
||||
|
||||
for f in user_files:
|
||||
gcode_filename = f.filename.rsplit('.', 1)[0] + '.gcode'
|
||||
gcode_path = os.path.join(gcode_dir, gcode_filename)
|
||||
|
||||
size = 0
|
||||
if os.path.exists(gcode_path):
|
||||
size = os.path.getsize(gcode_path)
|
||||
|
||||
# Upload to OctoPrint if not found but exists locally
|
||||
if client and gcode_filename not in octo_files_dict and size > 0:
|
||||
try:
|
||||
resp = client.upload_file('local', gcode_path, gcode_filename)
|
||||
uploaded_loc = resp.get('files', {}).get('local', {})
|
||||
if gcode_filename in uploaded_loc:
|
||||
octo_files_dict[gcode_filename] = uploaded_loc[gcode_filename]
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
octo_info = octo_files_dict.get(gcode_filename, {})
|
||||
analysis = octo_info.get('gcodeAnalysis', None)
|
||||
|
||||
files.append({
|
||||
'id': f.id,
|
||||
'name': f.original_filename.rsplit('.', 1)[0] + '.gcode',
|
||||
'type': 'machinecode',
|
||||
'size': size,
|
||||
'origin': 'local',
|
||||
'path': gcode_filename,
|
||||
'gcodeAnalysis': analysis
|
||||
})
|
||||
|
||||
error = None
|
||||
if not get_octo_client():
|
||||
error = "OctoPrint is not configured."
|
||||
|
||||
return render_template('printer/prepare.html', files=files, error=error)
|
||||
|
||||
@printer_bp.route('/api/print_file', methods=['POST'])
|
||||
@@ -98,6 +149,54 @@ def api_command():
|
||||
return jsonify({"success": False, "error": str(e)})
|
||||
return jsonify({"success": False, "error": "Invalid client or command"})
|
||||
|
||||
@printer_bp.route('/api/upload_gcode', methods=['POST'])
|
||||
@login_required
|
||||
def upload_gcode():
|
||||
from app.models import PrintFile
|
||||
import os
|
||||
import uuid
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"success": False, "error": "No file part"}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({"success": False, "error": "No selected file"}), 400
|
||||
|
||||
if not file.filename.lower().endswith(('.gcode', '.gco', '.g')):
|
||||
return jsonify({"success": False, "error": "Only standard .gcode files are supported."}), 400
|
||||
|
||||
sec_name = secure_filename(file.filename)
|
||||
random_prefix = uuid.uuid4().hex[:8]
|
||||
|
||||
# We save pseudo-STL filename so that our conventional parser works (replaces .ext with .gcode)
|
||||
# i.e., "some_file.gcode" -> pseudo .stl tracker
|
||||
pseudo_stl_filename = f"{random_prefix}_{sec_name.rsplit('.', 1)[0]}.stl"
|
||||
gcode_filename = f"{random_prefix}_{sec_name.rsplit('.', 1)[0]}.gcode"
|
||||
|
||||
gcode_path = os.path.join(get_gcode_dir(), gcode_filename)
|
||||
file.save(gcode_path)
|
||||
|
||||
print_file = PrintFile(
|
||||
user_id=current_user.id,
|
||||
original_filename=file.filename, # keep original GCode name
|
||||
filename=pseudo_stl_filename,
|
||||
file_type='stl',
|
||||
status='sliced'
|
||||
)
|
||||
db.session.add(print_file)
|
||||
db.session.commit()
|
||||
|
||||
client = get_octo_client()
|
||||
if client:
|
||||
try:
|
||||
client.upload_file('local', gcode_path, gcode_filename)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return jsonify({"success": True})
|
||||
|
||||
@printer_bp.route('/octo_config', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def octo_config():
|
||||
@@ -141,9 +240,9 @@ def octo_embed():
|
||||
embed_url = url_for('printer.octo_proxy') if url and url.value else None
|
||||
return render_template('printer/octo_embed.html', embed_url=embed_url)
|
||||
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, websocket=True)
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, websocket=True, strict_slashes=False)
|
||||
@printer_bp.route('/proxy/<path:path>', websocket=True)
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||
@printer_bp.route('/proxy', defaults={'path': ''}, methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], strict_slashes=False)
|
||||
@printer_bp.route('/proxy/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
|
||||
@login_required
|
||||
def octo_proxy(path):
|
||||
@@ -159,7 +258,7 @@ def octo_proxy(path):
|
||||
# print("----- REQUEST HEADERS -----")
|
||||
# for k, v in request.headers:
|
||||
# print(f"{k}: {v}")
|
||||
# print("----- END HEADERS -----")
|
||||
# print("----- END REQUEST HEADERS -----")
|
||||
|
||||
# --- WebSocket Proxy Logic ---
|
||||
if request.headers.get('Upgrade', '').lower() == 'websocket':
|
||||
@@ -278,17 +377,26 @@ def octo_proxy(path):
|
||||
|
||||
# Build headers for reverse proxy based on nginx config reference
|
||||
parsed_base = urlparse(base_url)
|
||||
headers = {k: v for k, v in request.headers if k.lower() not in ['host', 'content-length']}
|
||||
headers = {k: v for k, v in request.headers if k.lower() not in ['host', 'content-length', 'origin', 'referer']}
|
||||
|
||||
# NGINX equivalent proxy headers
|
||||
# print(f"Proxying to: {target_url}")
|
||||
# Spoof Host, Origin, and Referer to match the backend URL completely
|
||||
# This prevents Tornado's strict Origin vs Host CSRF/CORS validation from failing
|
||||
# headers['Host'] = parsed_base.netloc
|
||||
headers['Host'] = request.host
|
||||
|
||||
if 'Origin' in request.headers:
|
||||
headers['Origin'] = base_url
|
||||
if 'Referer' in request.headers:
|
||||
headers['Referer'] = f"{base_url}/{path}"
|
||||
|
||||
headers['X-Real-IP'] = request.remote_addr
|
||||
headers['X-Real-Port'] = str(request.environ.get('REMOTE_PORT', ''))
|
||||
|
||||
forwarded_for = request.headers.get('X-Forwarded-For', '')
|
||||
headers['X-Forwarded-For'] = f"{forwarded_for}, {request.remote_addr}" if forwarded_for else request.remote_addr
|
||||
|
||||
headers['X-Forwarded-Protocol'] = request.scheme
|
||||
headers['X-Forwarded-Proto'] = request.scheme
|
||||
headers['X-Script-Name'] = "/printer/proxy"
|
||||
headers['X-Forwarded-Host'] = request.host
|
||||
headers['X-Forwarded-Port'] = str(request.environ.get('SERVER_PORT', '80'))
|
||||
@@ -299,6 +407,27 @@ def octo_proxy(path):
|
||||
if request.headers.get('Connection'):
|
||||
headers['Connection'] = request.headers.get('Connection')
|
||||
|
||||
# OctoPrint requires an X-CSRF-Token header matching the csrf_token_* cookie for POST/PUT/DELETE
|
||||
if request.method not in ['GET', 'HEAD', 'OPTIONS'] and 'X-CSRF-Token' not in request.headers:
|
||||
for cookie_name, cookie_value in request.cookies.items():
|
||||
if cookie_name.startswith('csrf_token_'):
|
||||
headers['X-CSRF-Token'] = cookie_value
|
||||
break
|
||||
|
||||
# if path == 'api/login':
|
||||
# print("----- SEND HEADERS -----")
|
||||
# for k, v in headers.items():
|
||||
# if k in request.headers.keys():
|
||||
# print(f"{k} :(from request): {request.headers[k]} (to send): {v}")
|
||||
# else:
|
||||
# print(f"{k} :(from request): None (to send): {v}")
|
||||
|
||||
# for k,v in request.headers.items():
|
||||
# if k not in headers.keys():
|
||||
# print(f"{k} :(from request): {request.headers[k]} (to send): None")
|
||||
|
||||
# print("----- END SEND HEADERS -----")
|
||||
|
||||
try:
|
||||
# proxy_connect_timeout 60s, proxy_read_timeout 600s
|
||||
resp = requests.request(
|
||||
@@ -316,7 +445,15 @@ def octo_proxy(path):
|
||||
|
||||
# Strip headers that might break the iframe or framing
|
||||
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection', 'x-frame-options', 'content-security-policy']
|
||||
response_headers = [(name, value) for (name, value) in resp.headers.items() if name.lower() not in excluded_headers]
|
||||
|
||||
# We must use raw headers to prevent requests from joining multiple Set-Cookie headers with a comma
|
||||
# Joining Set-Cookie with a comma breaks standard cookie parsing in the browser due to commas in dates
|
||||
response_headers = [(name, value) for name, value in resp.raw.headers.items()
|
||||
if name.lower() not in excluded_headers and name.lower() != 'set-cookie']
|
||||
|
||||
for cookie in resp.raw.headers.get_all('set-cookie', []):
|
||||
# ensure we preserve the proxy path override
|
||||
response_headers.append(('Set-Cookie', cookie))
|
||||
|
||||
def generate():
|
||||
for chunk in resp.iter_content(chunk_size=8192):
|
||||
|
||||
Reference in New Issue
Block a user