pdftract/tests/fixtures/security/generate_embedded_js.py
jedarden 34c6a3719d feat(bf-2ypn2): add Phase 7 profiles exit gate fixtures
Create test fixture directories and integration tests for Phase 7 profile
validation exit gate:

Invalid profiles (tests/fixtures/profiles/invalid/):
- unknown-key.yaml: unrecognized extraction tuning key
- bad-combinator.yaml: malformed match combinators (scalar not list)
- secret-key.yaml: forbidden api_key (PROFILE_SECRETS_FORBIDDEN)
- malformed.yaml: unclosed bracket (YAML syntax error)

Valid profiles (tests/fixtures/profiles/valid/):
- minimal.yaml: smallest valid profile (name, description, priority only)
- invoice-minimal.yaml: simplified invoice with match and extraction

Resolution fixtures (tests/fixtures/profiles/resolution/):
- custom-invoice.yaml: priority 100 overrides built-in (priority 50)
- README.md: documents resolution priority order

Integration tests (tests/integration/advanced/profiles.rs):
- test_invalid_profiles_rejected: validates all 4 invalid profiles fail
- test_valid_profiles_accepted: validates all 2 valid profiles pass
- test_profile_resolution_order: tests --profile-dir override behavior
- test_invalid_fixture_error_types: validates specific error messages

Acceptance criteria:
✓ pdftract profiles validate rejects all 4 invalid files
✓ pdftract profiles validate accepts all 2 valid files
✓ Profile resolution order test passes

Closes bf-2ypn2
2026-07-04 23:56:32 -04:00

170 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Generate embedded-js.pdf fixture for TH-04 JavaScript detection testing.
This creates a minimal PDF with 3 JavaScript actions at different locations:
1. Catalog /OpenAction -> /JS containing app.alert("pwn")
2. Page 0 /AA -> /O (open action) -> /JS containing a second alert
3. Page 1 annotation /A -> /JS containing a third snippet
Generated PDF is valid and can be parsed by pdftract, but the JavaScript
is NEVER executed - only detected and reported.
"""
import struct
def write_pdf():
"""Generate a minimal PDF with embedded JavaScript actions."""
# PDF version 1.4
pdf_header = b"%PDF-1.4\n\n"
# Object 0: Pages root (dummy, will be overwritten)
# Object 1: Page 0 (with /AA /O JavaScript)
obj1 = """1 0 obj
<<
/Type/Page
/MediaBox[0 0 612 792]
/Parent 0 0 R
/Resources<<
/Font<<
/F1<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>
>>
>>
/Contents 2 0 R
/AA<<
/O<</S/JavaScript/JS(app.alert('page_open'))>>
>>
>>
endobj
"""
# Object 2: Content stream for Page 0 (simple text)
obj2 = """2 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
100 700 Td
(Page 0) Tj
ET
endstream
endobj
"""
# Object 3: Page 1 (with annotation containing JavaScript)
obj3 = """3 0 obj
<<
/Type/Page
/MediaBox[0 0 612 792]
/Parent 0 0 R
/Resources<<
/Font<<
/F1<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>
>>
>>
/Contents 4 0 R
/Annots[5 0 R]
>>
endobj
"""
# Object 4: Content stream for Page 1
obj4 = """4 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
100 700 Td
(Page 1) Tj
ET
endstream
endobj
"""
# Object 5: Annotation on Page 1 with JavaScript action
obj5 = """5 0 obj
<<
/Type/Annot
/Subtype/Link
/Rect[100 600 200 620]
/A<</S/JavaScript/JS(app.alert('annot_action'))>>
>>
endobj
"""
# Object 6: Pages catalog
obj6 = """6 0 obj
<<
/Type/Pages
/Count 2
/Kids[1 0 R 3 0 R]
>>
endobj
"""
# Object 7: Catalog with /OpenAction JavaScript
obj7 = """7 0 obj
<<
/Type/Catalog
/Pages 6 0 R
/OpenAction<</S/JavaScript/JS(app.alert("pwn"))>>
>>
endobj
"""
# Combine all objects
pdf_content = obj1 + obj2 + obj3 + obj4 + obj5 + obj6 + obj7
# Calculate offsets for xref table
offsets = []
current_offset = len(pdf_header)
# Split into individual objects to calculate offsets
objects = [obj1, obj2, obj3, obj4, obj5, obj6, obj7]
for obj in objects:
offsets.append(current_offset)
current_offset += len(obj.encode('latin1'))
# Build xref table
xref = f"xref\n0 8\n0000000000 65535 f \n"
for i, offset in enumerate(offsets, start=1):
xref += f"{offset:010d} 00000 n \n"
# Trailer
trailer = f"""trailer
<<
/Size 8
/Root 7 0 R
>>
startxref
{current_offset}
%%
%%EOF
"""
# Write complete PDF
complete_pdf = pdf_header + pdf_content.encode('latin1') + xref.encode('latin1') + trailer.encode('latin1')
with open("tests/fixtures/security/embedded-js.pdf", "wb") as f:
f.write(complete_pdf)
print("Generated tests/fixtures/security/embedded-js.pdf")
print("JavaScript actions:")
print(" 1. Catalog /OpenAction: app.alert(\"pwn\")")
print(" 2. Page 0 /AA /O: app.alert('page_open')")
print(" 3. Page 1 annotation /A: app.alert('annot_action')")
if __name__ == "__main__":
write_pdf()