Last active
December 4, 2025 07:02
-
-
Save matsubo/c1366c36b1045484a00d0756ab082f3a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Please ensure GOOGLE_API_KEY is set. | |
| # The key can be found: https://aistudio.google.com/apps | |
| # Create test.xlsx file | |
| echo "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" | base64 -d > test.xlsx | |
| # Download PoC script | |
| curl https://gist.githubusercontent.com/matsubo/c1366c36b1045484a00d0756ab082f3a/raw/poc_mime_type_issue.py > poc_mime_type_issue.py | |
| # Execute | |
| python poc_mime_type_issue.py | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Proof of Concept: MIME Type Validation issue in uploadToFileSearchStore API | |
| This script demonstrates a server-side validation issue where the API rejects a | |
| valid MIME type for Excel files (.xlsx). | |
| Error: 400 INVALID_ARGUMENT | |
| Message: "When provided, MIME type must be in a valid type/subtype format" | |
| MIME Type Used: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | |
| Issue: The MIME type IS in valid type/subtype format, but the API incorrectly rejects it. | |
| """ | |
| from google import genai | |
| from google.genai import types | |
| import time | |
| import os | |
| from dotenv import load_dotenv | |
| # Load environment variables | |
| load_dotenv() | |
| # Get API key | |
| api_key = os.getenv('GOOGLE_API_KEY') | |
| if not api_key: | |
| raise ValueError("GOOGLE_API_KEY not found in environment variables") | |
| client = genai.Client(api_key=api_key) | |
| print("=" * 80) | |
| print("PoC: MIME Type issue in uploadToFileSearchStore API") | |
| print("=" * 80) | |
| # Preparation: Create a file search store | |
| print("\nPreparation: Creating file search store...") | |
| file_search_store = client.file_search_stores.create( | |
| config={'display_name': 'poc-mime-type-xlsx'} | |
| ) | |
| print(f" Created: {file_search_store.name}") | |
| # Step 1: Attempt to upload an Excel file with the correct MIME type | |
| print("\n1. Uploading Excel file (.xlsx) with correct MIME type...") | |
| test_file = "test.xlsx" | |
| mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" | |
| print(f" File: {test_file}") | |
| print(f" MIME Type: {mime_type}") | |
| print(f" Note: This MIME type is the official IANA-registered type for .xlsx files") | |
| try: | |
| with open(test_file, 'rb') as f: | |
| operation = client.file_search_stores.upload_to_file_search_store( | |
| file=f, | |
| file_search_store_name=file_search_store.name, | |
| config={ | |
| 'display_name': 'test.xlsx', | |
| 'mime_type': mime_type # VALID MIME TYPE | |
| } | |
| ) | |
| # Wait for completion | |
| while not operation.done: | |
| time.sleep(2) | |
| operation = client.operations.get(operation) | |
| print("\n ✓ SUCCESS: File uploaded successfully") | |
| except Exception as e: | |
| print("\n ✗ ERROR: Upload failed") | |
| print(f"\n{e}") | |
| print("\n" + "=" * 80) | |
| print("ISSUE CONFIRMED") | |
| print("=" * 80) | |
| print("The API incorrectly rejects the MIME type:") | |
| print(f" {mime_type}") | |
| print("\nThis is a valid IANA-registered MIME type in 'type/subtype' format.") | |
| print("Expected: API should accept this MIME type") | |
| print("Actual: API returns 400 INVALID_ARGUMENT") | |
| print("=" * 80) | |
| # Step 2: Try without mime_type to see if it works | |
| print("\n\n2. Testing upload WITHOUT mime_type parameter...") | |
| try: | |
| with open(test_file, 'rb') as f: | |
| operation = client.file_search_stores.upload_to_file_search_store( | |
| file=f, | |
| file_search_store_name=file_search_store.name, | |
| config={ | |
| 'display_name': 'test-without-mimetype.xlsx' | |
| # NO mime_type specified | |
| } | |
| ) | |
| # Wait for completion | |
| while not operation.done: | |
| time.sleep(2) | |
| operation = client.operations.get(operation) | |
| print(" ✓ SUCCESS: File uploaded without mime_type parameter") | |
| print("\n This confirms the issue: The API works when mime_type is omitted,") | |
| print(" but fails when the correct mime_type is provided.") | |
| except Exception as e: | |
| print(f"\n ✗ Also failed without mime_type: {e}") | |
| # Step 3: Try alternative approach - upload via File API then import | |
| print("\n\n3. Testing alternative approach: File API upload → import to store...") | |
| print(" This tests if using client.files.upload() + import_file() works better") | |
| try: | |
| # Upload file using File API | |
| with open(test_file, 'rb') as f: | |
| file_ref = client.files.upload( | |
| file=f, | |
| config=types.UploadFileConfig( | |
| display_name='test-via-file-api.xlsx', | |
| mime_type=mime_type | |
| ) | |
| ) | |
| print(f" ✓ Uploaded via File API: {file_ref.name}") | |
| # Import the uploaded file into the file search store | |
| import_op = client.file_search_stores.import_file( | |
| file_search_store_name=file_search_store.name, | |
| file_name=file_ref.name, | |
| ) | |
| print(f" File import started: {import_op.name}") | |
| print(" Waiting for import to complete", end="") | |
| while not (import_op := client.operations.get(import_op)).done: | |
| time.sleep(1) | |
| print(".", end="", flush=True) | |
| print() | |
| print(" ✓ SUCCESS: File imported successfully via File API → import_file()") | |
| print("\n This approach works! Use File API upload + import as a workaround.") | |
| except Exception as e: | |
| print(f"\n ✗ Failed with File API approach: {e}") | |
| print("\n" + "=" * 80) | |
| print("PoC Complete") | |
| print("=" * 80) | |
| print("\nSUMMARY:") | |
| print(" Step 1 (direct upload with mime_type): Expected to fail") | |
| print(" Step 2 (direct upload without mime_type): May work") | |
| print(" Step 3 (File API + import): Recommended workaround") | |
| print("=" * 80) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output
pakcage version