SHA256
Updated to support base64 email attachments.
This commit is contained in:
@@ -7,7 +7,7 @@ import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, request, jsonify, send_file, session, redirect, url_for
|
||||
from flask import Flask, render_template, request, jsonify, send_file, session, redirect, url_for, make_response
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import markdown
|
||||
import logging
|
||||
@@ -238,6 +238,45 @@ def rewrite_image_paths(md_content, filepath):
|
||||
md_content = re.sub(r'!\[([^\]]*)\]\(([^\)]+)\)', replace_image_path, md_content)
|
||||
return md_content
|
||||
|
||||
def rewrite_eml_file_links(md_content, filepath):
|
||||
"""Rewrite relative .eml file links to be served by Flask's /file/ route."""
|
||||
# Get the directory of the current file
|
||||
file_dir = os.path.dirname(filepath)
|
||||
|
||||
# Pattern to match markdown link syntax: [text](path)
|
||||
def replace_link_path(match):
|
||||
link_text = match.group(1)
|
||||
link_path = match.group(2)
|
||||
|
||||
# Only process .eml files
|
||||
if not link_path.lower().endswith('.eml'):
|
||||
return match.group(0)
|
||||
|
||||
# Skip absolute URLs
|
||||
if link_path.startswith(('http://', 'https://', '/file/')):
|
||||
return match.group(0)
|
||||
|
||||
# Resolve relative paths
|
||||
if link_path.startswith('/'):
|
||||
# Absolute path from content root
|
||||
resolved_path = link_path.lstrip('/')
|
||||
else:
|
||||
# Relative path - resolve it relative to the file's directory
|
||||
if file_dir:
|
||||
resolved_path = os.path.normpath(os.path.join(file_dir, link_path))
|
||||
else:
|
||||
resolved_path = link_path
|
||||
|
||||
# Ensure forward slashes for URL
|
||||
resolved_path = resolved_path.replace(os.sep, '/')
|
||||
file_url = f"/file/{resolved_path}"
|
||||
|
||||
return f"[{link_text}]({file_url})"
|
||||
|
||||
# Replace all markdown link references to .eml files
|
||||
md_content = re.sub(r'\[([^\]]+)\]\(([^\)]+\.eml)\)', replace_link_path, md_content)
|
||||
return md_content
|
||||
|
||||
def build_directory_tree():
|
||||
"""Build a hierarchical tree of markdown files organized by directory."""
|
||||
tree = {}
|
||||
@@ -370,6 +409,60 @@ def serve_image(filepath):
|
||||
logger.error(f"Error serving image {file_path}: {e}")
|
||||
return f"Error serving image: {e}", 500
|
||||
|
||||
@app.route('/file/<path:filepath>')
|
||||
def serve_file(filepath):
|
||||
"""Serve files from content directory, with base64 decoding for .eml files if needed."""
|
||||
# Security: prevent directory traversal
|
||||
if '..' in filepath or filepath.startswith('/'):
|
||||
return "Invalid path", 400
|
||||
|
||||
file_path = os.path.join(CONTENT_DIR, filepath)
|
||||
|
||||
# Ensure the file is within the content directory
|
||||
try:
|
||||
file_path = os.path.realpath(file_path)
|
||||
content_path = os.path.realpath(CONTENT_DIR)
|
||||
if not file_path.startswith(content_path):
|
||||
return "Access denied", 403
|
||||
except Exception:
|
||||
return "Invalid path", 400
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
logger.warning(f"File not found: {file_path}")
|
||||
return "File not found", 404
|
||||
|
||||
try:
|
||||
# Check if this is an .eml file
|
||||
if file_path.lower().endswith('.eml'):
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if content is base64 encoded
|
||||
# Base64 content is ASCII and contains only base64 characters
|
||||
is_base64 = False
|
||||
try:
|
||||
if content.strip() and all(c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r' for c in content):
|
||||
decoded = base64.b64decode(content, validate=True).decode('utf-8', errors='ignore')
|
||||
# If it looks like an email (has headers), use decoded version
|
||||
if 'From:' in decoded or 'To:' in decoded or 'Subject:' in decoded:
|
||||
content = decoded
|
||||
is_base64 = True
|
||||
except Exception:
|
||||
# Not valid base64, use original content
|
||||
pass
|
||||
|
||||
# Return as text/plain for viewing in browser
|
||||
response = make_response(content)
|
||||
response.headers['Content-Type'] = 'text/plain; charset=utf-8'
|
||||
response.headers['Content-Disposition'] = f'inline; filename="{os.path.basename(file_path)}"'
|
||||
return response
|
||||
else:
|
||||
# For other file types, just serve as-is
|
||||
return send_file(file_path)
|
||||
except Exception as e:
|
||||
logger.error(f"Error serving file {file_path}: {e}")
|
||||
return f"Error serving file: {e}", 500
|
||||
|
||||
@app.route('/api/search', methods=['GET'])
|
||||
def search():
|
||||
"""Search markdown files."""
|
||||
@@ -462,6 +555,9 @@ def view_file(filepath):
|
||||
# Rewrite image paths to be served by Flask
|
||||
md_content = rewrite_image_paths(md_content, filepath)
|
||||
|
||||
# Rewrite .eml file links to be served by Flask with base64 decoding
|
||||
md_content = rewrite_eml_file_links(md_content, filepath)
|
||||
|
||||
# Normalize list indentation for proper markdown parsing
|
||||
md_content = normalize_list_indentation(md_content)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user