1+ #!/usr/bin/env python3
2+
3+ import json
4+ import tornado .web
5+ import sys
6+ import os
7+ import logging
8+ import mimetypes
9+ from pathlib import Path
10+
11+ # Add parent directory to path for imports
12+ sys .path .append (os .path .dirname (os .path .dirname (os .path .abspath (__file__ ))))
13+
14+ from auth .user_manager_postgres import UserManager
15+ from common .file_storage import file_storage
16+ from common .config import Config
17+ from command .resource import write_project_file
18+
19+ logger = logging .getLogger (__name__ )
20+
21+
22+ class UploadFileHandler (tornado .web .RequestHandler ):
23+ """Handle file upload requests for admin users"""
24+
25+ def initialize (self ):
26+ self .user_manager = UserManager ()
27+
28+ def set_default_headers (self ):
29+ """Set CORS headers"""
30+ self .set_header ("Access-Control-Allow-Origin" , "*" )
31+ self .set_header ("Access-Control-Allow-Methods" , "POST, OPTIONS" )
32+ self .set_header ("Access-Control-Allow-Headers" , "Content-Type, session-id" )
33+
34+ def options (self ):
35+ """Handle preflight requests"""
36+ self .set_status (204 )
37+ self .finish ()
38+
39+ def post (self ):
40+ """Handle file upload POST request"""
41+ try :
42+ # Get session ID from headers
43+ session_id = self .request .headers .get ('session-id' )
44+ if not session_id :
45+ self .set_status (401 )
46+ self .write (json .dumps ({
47+ 'success' : False ,
48+ 'error' : 'No session ID provided'
49+ }))
50+ return
51+
52+ # Validate session and get user info
53+ user_info = self .user_manager .validate_session (session_id )
54+ if not user_info :
55+ self .set_status (401 )
56+ self .write (json .dumps ({
57+ 'success' : False ,
58+ 'error' : 'Invalid session'
59+ }))
60+ return
61+
62+ # Check if user is admin (one of the specified admin accounts)
63+ admin_accounts = ['sl7927' , 'sa9082' , 'et2434' ]
64+ if user_info ['username' ] not in admin_accounts :
65+ self .set_status (403 )
66+ self .write (json .dumps ({
67+ 'success' : False ,
68+ 'error' : 'Only admin users can upload files'
69+ }))
70+ return
71+
72+ # Get form data
73+ project_name = self .get_argument ('projectName' , default = None )
74+ parent_path = self .get_argument ('parentPath' , default = '/' )
75+ filename = self .get_argument ('filename' , default = None )
76+
77+ if not project_name or not filename :
78+ self .set_status (400 )
79+ self .write (json .dumps ({
80+ 'success' : False ,
81+ 'error' : 'Missing required parameters: projectName, filename'
82+ }))
83+ return
84+
85+ # Get uploaded file
86+ if 'file' not in self .request .files :
87+ self .set_status (400 )
88+ self .write (json .dumps ({
89+ 'success' : False ,
90+ 'error' : 'No file uploaded'
91+ }))
92+ return
93+
94+ file_info = self .request .files ['file' ][0 ]
95+ file_content = file_info ['body' ]
96+
97+ # Validate file extension
98+ allowed_extensions = ['.py' , '.txt' , '.csv' , '.pdf' ]
99+ file_extension = '.' + filename .split ('.' )[- 1 ].lower ()
100+ if file_extension not in allowed_extensions :
101+ self .set_status (400 )
102+ self .write (json .dumps ({
103+ 'success' : False ,
104+ 'error' : f'File type not allowed. Supported: { ", " .join (allowed_extensions )} '
105+ }))
106+ return
107+
108+ # Validate file size (10MB limit)
109+ max_size = 10 * 1024 * 1024 # 10MB
110+ if len (file_content ) > max_size :
111+ self .set_status (400 )
112+ self .write (json .dumps ({
113+ 'success' : False ,
114+ 'error' : 'File too large. Maximum size: 10MB'
115+ }))
116+ return
117+
118+ # Clean up filename (remove any path separators)
119+ safe_filename = os .path .basename (filename )
120+
121+ # Construct the full path
122+ if parent_path == '/' :
123+ file_path = f"/{ safe_filename } "
124+ else :
125+ file_path = f"{ parent_path } /{ safe_filename } "
126+
127+ # Use the same file writing logic as the existing IDE
128+ try :
129+ # Get project path
130+ project_path = os .path .join (file_storage .ide_base , project_name )
131+ full_file_path = os .path .join (project_path , file_path .lstrip ('/' ))
132+
133+ # Ensure parent directory exists
134+ os .makedirs (os .path .dirname (full_file_path ), exist_ok = True )
135+
136+ # Determine if it's a binary file
137+ is_binary = self ._is_binary_file (file_extension )
138+
139+ if is_binary :
140+ # For binary files (like PDFs), save raw content
141+ with open (full_file_path , 'wb' ) as f :
142+ f .write (file_content )
143+ code = 0
144+ else :
145+ # For text files, decode content and use write_project_file
146+ try :
147+ content_str = file_content .decode ('utf-8' )
148+ except UnicodeDecodeError :
149+ # Try with other encodings
150+ try :
151+ content_str = file_content .decode ('latin-1' )
152+ except UnicodeDecodeError :
153+ content_str = file_content .decode ('utf-8' , errors = 'replace' )
154+
155+ # Use the existing write_project_file function
156+ code , error = write_project_file (project_path , full_file_path , content_str )
157+
158+ if code == 0 :
159+ logger .info (f"File uploaded successfully: { project_name } { file_path } by { user_info ['username' ]} " )
160+ self .write (json .dumps ({
161+ 'success' : True ,
162+ 'message' : f'File { safe_filename } uploaded successfully' ,
163+ 'path' : file_path ,
164+ 'project' : project_name
165+ }))
166+ else :
167+ raise Exception (f"Failed to save file: error code { code } " )
168+
169+ except Exception as e :
170+ logger .error (f"Error saving uploaded file: { str (e )} " )
171+ self .set_status (500 )
172+ self .write (json .dumps ({
173+ 'success' : False ,
174+ 'error' : f'Failed to save file: { str (e )} '
175+ }))
176+ return
177+
178+ except Exception as e :
179+ logger .error (f"Error in UploadFileHandler: { str (e )} " )
180+ self .set_status (500 )
181+ self .write (json .dumps ({
182+ 'success' : False ,
183+ 'error' : 'Internal server error'
184+ }))
185+
186+ def _is_binary_file (self , extension ):
187+ """Determine if a file should be treated as binary based on its extension"""
188+ binary_extensions = ['.pdf' , '.png' , '.jpg' , '.jpeg' , '.gif' , '.bmp' , '.ico' , '.zip' , '.tar' , '.gz' ]
189+ return extension .lower () in binary_extensions
0 commit comments