-
-
Notifications
You must be signed in to change notification settings - Fork 427
[Backend] Add content-based file validation (magic number checks) #667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,8 @@ | |||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||
| import fitz | ||||||||||||||||||||||||||||||
| import mammoth | ||||||||||||||||||||||||||||||
| import zipfile | ||||||||||||||||||||||||||||||
| from werkzeug.utils import secure_filename | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class MCQGenerator: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -368,19 +370,63 @@ def extract_text_from_docx(self, file_path): | |||||||||||||||||||||||||||||
| return result.value | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| def process_file(self, file): | ||||||||||||||||||||||||||||||
| file_path = os.path.join(self.upload_folder, file.filename) | ||||||||||||||||||||||||||||||
| if not file.filename: | ||||||||||||||||||||||||||||||
| raise ValueError("Empty filename") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| filename = secure_filename(file.filename) | ||||||||||||||||||||||||||||||
| if not filename: | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid filename") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| target_dir = os.path.abspath(self.upload_folder) | ||||||||||||||||||||||||||||||
| file_path = os.path.abspath(os.path.join(target_dir, filename)) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if os.path.commonpath([target_dir, file_path]) != target_dir: | ||||||||||||||||||||||||||||||
| raise ValueError("Path traversal detected") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| file.save(file_path) | ||||||||||||||||||||||||||||||
| content = "" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if file.filename.endswith('.txt'): | ||||||||||||||||||||||||||||||
| with open(file_path, 'r') as f: | ||||||||||||||||||||||||||||||
| content = f.read() | ||||||||||||||||||||||||||||||
| elif file.filename.endswith('.pdf'): | ||||||||||||||||||||||||||||||
| content = self.extract_text_from_pdf(file_path) | ||||||||||||||||||||||||||||||
| elif file.filename.endswith('.docx'): | ||||||||||||||||||||||||||||||
| content = self.extract_text_from_docx(file_path) | ||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| if '.' not in filename: | ||||||||||||||||||||||||||||||
| raise ValueError("Unsupported file extension") | ||||||||||||||||||||||||||||||
| ext = filename.rsplit('.', 1)[1].lower() | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| with open(file_path, 'rb') as f: | ||||||||||||||||||||||||||||||
| header = f.read(512) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if ext == 'pdf': | ||||||||||||||||||||||||||||||
| if not header.startswith(b"%PDF"): | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid file content: PDF signature not found") | ||||||||||||||||||||||||||||||
| content = self.extract_text_from_pdf(file_path) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| elif ext == 'docx': | ||||||||||||||||||||||||||||||
| if not header.startswith(b"PK\x03\x04"): | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid file content: DOCX signature not found") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| with zipfile.ZipFile(file_path, 'r') as zf: | ||||||||||||||||||||||||||||||
| namelist = zf.namelist() | ||||||||||||||||||||||||||||||
| if '[Content_Types].xml' not in namelist or 'word/document.xml' not in namelist: | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid file content: Missing DOCX internal structures") | ||||||||||||||||||||||||||||||
| except zipfile.BadZipFile: | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid file content: Not a valid ZIP archive") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| content = self.extract_text_from_docx(file_path) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| elif ext == 'txt': | ||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||
| header.decode('utf-8') | ||||||||||||||||||||||||||||||
| except UnicodeDecodeError: | ||||||||||||||||||||||||||||||
| raise ValueError("Invalid file content: TXT must be valid UTF-8 text") | ||||||||||||||||||||||||||||||
| with open(file_path, 'r', encoding='utf-8') as f: | ||||||||||||||||||||||||||||||
| content = f.read() | ||||||||||||||||||||||||||||||
|
Comment on lines
+416
to
+422
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate the full text file, not just the first 512 bytes. Lines 407-410 decode only the header. A file with valid UTF-8 in the first 512 bytes and invalid bytes later will pass this check and then fail during Suggested fix elif ext == 'txt':
- try:
- header.decode('utf-8')
- except UnicodeDecodeError:
- raise ValueError("Invalid file content: TXT must be valid UTF-8 text")
- with open(file_path, 'r', encoding='utf-8') as f:
- content = f.read()
+ with open(file_path, 'rb') as f:
+ raw = f.read()
+ try:
+ content = raw.decode('utf-8')
+ except UnicodeDecodeError as exc:
+ raise ValueError("Invalid file content: TXT must be valid UTF-8 text") from exc📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||
| raise ValueError("Unsupported file extension") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| finally: | ||||||||||||||||||||||||||||||
| if os.path.exists(file_path): | ||||||||||||||||||||||||||||||
| os.remove(file_path) | ||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| os.remove(file_path) | ||||||||||||||||||||||||||||||
| return content | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| class QuestionGenerator: | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.