1- from flask import Flask , send_from_directory
1+ from flask import Flask , send_from_directory , render_template_string , abort
22import os
33from logger import log
4+ from datetime import datetime
45
56# read version from file if exists
67version = "unknown"
1415GUNICORN_VERSION = f"{ os .getenv ('GUNICORN_VERSION' , 'Unknown' )} "
1516log .info ('Service started, version: [%s]' , GUNICORN_VERSION )
1617
18+ # HTML template for directory listing
19+ DIRECTORY_LISTING_TEMPLATE = """
20+ <!DOCTYPE html>
21+ <html>
22+ <head>
23+ <title>Directory Listing - {{ current_path }}</title>
24+ <style>
25+ body { font-family: Arial, sans-serif; margin: 20px; }
26+ .header { margin-bottom: 20px; }
27+ .item { padding: 8px; border-bottom: 1px solid #eee; }
28+ .item:hover { background-color: #f5f5f5; }
29+ .item a { text-decoration: none; color: #333; }
30+ .item a:hover { color: #007bff; }
31+ .directory { font-weight: bold; }
32+ .file-info { color: #666; float: right; }
33+ </style>
34+ </head>
35+ <body>
36+ <div class="header">
37+ <h2>Directory: {{ current_path }}</h2>
38+ {% if parent_path %}
39+ <a href="{{ parent_path }}">← Back to Parent Directory</a>
40+ {% endif %}
41+ </div>
42+ {% for item in items %}
43+ <div class="item">
44+ <span class="file-info">{{ item.size }} - {{ item.modified }}</span>
45+ <a href="{{ item.path }}" class="{{ 'directory' if item.is_dir else '' }}">
46+ {{ '📁 ' if item.is_dir else '📄 ' }}{{ item.name }}
47+ </a>
48+ </div>
49+ {% endfor %}
50+ </body>
51+ </html>
52+ """
53+
54+ def get_human_readable_size (size_in_bytes ):
55+ """Convert file size to human readable format"""
56+ for unit in ['B' , 'KB' , 'MB' , 'GB' ]:
57+ if size_in_bytes < 1024 :
58+ return f"{ size_in_bytes :.1f} { unit } "
59+ size_in_bytes /= 1024
60+ return f"{ size_in_bytes :.1f} TB"
61+
62+ def get_directory_listing (base_path , current_path ):
63+ """Generate directory listing information"""
64+ full_path = os .path .join (base_path , current_path )
65+ items = []
66+
67+ try :
68+ for item in os .listdir (full_path ):
69+ item_path = os .path .join (full_path , item )
70+ stats = os .stat (item_path )
71+ is_dir = os .path .isdir (item_path )
72+
73+ relative_path = os .path .join (current_path , item )
74+ if not relative_path .startswith ('/' ):
75+ relative_path = '/' + relative_path
76+
77+ items .append ({
78+ 'name' : item ,
79+ 'path' : f"/deb{ relative_path } " ,
80+ 'is_dir' : is_dir ,
81+ 'size' : get_human_readable_size (stats .st_size ) if not is_dir else '-' ,
82+ 'modified' : datetime .fromtimestamp (stats .st_mtime ).strftime ('%Y-%m-%d %H:%M:%S' )
83+ })
84+
85+ # Sort items: directories first, then files, both alphabetically
86+ items .sort (key = lambda x : (not x ['is_dir' ], x ['name' ].lower ()))
87+
88+ return items
89+ except Exception as e :
90+ log .error (f"Error reading directory { full_path } : { str (e )} " )
91+ return []
92+
1793@app .route ('/' )
1894def index ():
1995 return f"{ version } . { GUNICORN_VERSION } "
2096
21- @app .route ('/deb/<path:filename>' )
22- def serve_deb_repo (filename ):
97+ @app .route ('/deb/' , defaults = {'path' : '' })
98+ @app .route ('/deb/<path:path>' )
99+ def serve_deb_repo (path ):
23100 try :
24- filepath = os .path .join ('/deb' , filename )
25- if os .path .exists (filepath ):
26- log .info (f"Serving file { filepath } ." )
27- # Get the directory path and actual filename
28- directory = os .path .dirname (os .path .join ('/deb' , filename ))
29- basename = os .path .basename (filename )
30- return send_from_directory (directory , basename )
101+ base_path = '/deb'
102+ full_path = os .path .join (base_path , path )
103+
104+ # Security check: ensure path is within base directory
105+ if not os .path .abspath (full_path ).startswith (os .path .abspath (base_path )):
106+ log .error (f"Attempted path traversal: { path } " )
107+ abort (403 )
108+
109+ if os .path .isfile (full_path ):
110+ log .info (f"Serving file { full_path } " )
111+ directory = os .path .dirname (full_path )
112+ filename = os .path .basename (full_path )
113+ return send_from_directory (directory , filename )
114+ elif os .path .isdir (full_path ):
115+ log .info (f"Serving directory listing for { full_path } " )
116+ items = get_directory_listing (base_path , path )
117+ parent_path = '/deb/' + os .path .dirname (path ) if path else None
118+ return render_template_string (
119+ DIRECTORY_LISTING_TEMPLATE ,
120+ current_path = f"/deb/{ path } " ,
121+ parent_path = parent_path ,
122+ items = items
123+ )
31124 else :
32- log .error (f"File { filepath } does not exist. " )
125+ log .error (f"Path { full_path } does not exist" )
33126 abort (404 )
34127 except Exception as e :
35- log .error (f"Error serving file : { str (e )} " )
36- return "Invalid"
128+ log .error (f"Error serving path : { str (e )} " )
129+ abort ( 500 )
37130
38131if __name__ == '__main__' :
39132 log .info ('Service started, version: %s' , version )
40- app .run (debug = True )
133+ app .run (debug = True )
0 commit comments