@l1yp/file-viewer 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/FileViewer.js +2 -0
- package/dist/FileViewer.js.map +1 -0
- package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/AppxViewer.js +2 -0
- package/dist/components/AppxViewer.js.map +1 -0
- package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ArchiveBrowser.js +2 -0
- package/dist/components/ArchiveBrowser.js.map +1 -0
- package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/AudioViewer.js +2 -0
- package/dist/components/AudioViewer.js.map +1 -0
- package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/BinObjBrowser.js +2 -0
- package/dist/components/BinObjBrowser.js.map +1 -0
- package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/CertViewer.js +2 -0
- package/dist/components/CertViewer.js.map +1 -0
- package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ClassViewer.js +2 -0
- package/dist/components/ClassViewer.js.map +1 -0
- package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/CodeBlock.js +2 -0
- package/dist/components/CodeBlock.js.map +1 -0
- package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/CodeView.js +2 -0
- package/dist/components/CodeView.js.map +1 -0
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/CsvViewer.js +2 -0
- package/dist/components/CsvViewer.js.map +1 -0
- package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DicomViewer.js +2 -0
- package/dist/components/DicomViewer.js.map +1 -0
- package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DocxNavigationPane.js +9 -0
- package/dist/components/DocxNavigationPane.js.map +1 -0
- package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +98 -0
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DocxViewer.js +3 -1
- package/dist/components/DocxViewer.js.map +1 -0
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +72 -37
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DrawioViewer.js +2 -0
- package/dist/components/DrawioViewer.js.map +1 -0
- package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DxfViewer.js +2 -0
- package/dist/components/DxfViewer.js.map +1 -0
- package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/EbookViewer.js +2 -0
- package/dist/components/EbookViewer.js.map +1 -0
- package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/EmlViewer.js +2 -0
- package/dist/components/EmlViewer.js.map +1 -0
- package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/EplViewer.js +2 -0
- package/dist/components/EplViewer.js.map +1 -0
- package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/FileViewerBody.js +3 -1
- package/dist/components/FileViewerBody.js.map +1 -0
- package/dist/components/FileViewerBody.vue.d.ts +5 -0
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +250 -247
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/FileViewerPanel.js +2 -0
- package/dist/components/FileViewerPanel.js.map +1 -0
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/FontViewer.js +2 -0
- package/dist/components/FontViewer.js.map +1 -0
- package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/FrontmatterCard.js +2 -0
- package/dist/components/FrontmatterCard.js.map +1 -0
- package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/GeoViewer.js +2 -0
- package/dist/components/GeoViewer.js.map +1 -0
- package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/HarViewer.js +2 -0
- package/dist/components/HarViewer.js.map +1 -0
- package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/HexViewer.js +2 -0
- package/dist/components/HexViewer.js.map +1 -0
- package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/HprofViewer.js +2 -0
- package/dist/components/HprofViewer.js.map +1 -0
- package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ImageLightbox.js +2 -0
- package/dist/components/ImageLightbox.js.map +1 -0
- package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/InlineText.js +2 -0
- package/dist/components/InlineText.js.map +1 -0
- package/dist/components/JksViewer.js +2 -0
- package/dist/components/JksViewer.js.map +1 -0
- package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/JsonlViewer.js +2 -0
- package/dist/components/JsonlViewer.js.map +1 -0
- package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/LogViewer.js +2 -0
- package/dist/components/LogViewer.js.map +1 -0
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/LrcViewer.js +2 -0
- package/dist/components/LrcViewer.js.map +1 -0
- package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/MarkdownTable.js +2 -0
- package/dist/components/MarkdownTable.js.map +1 -0
- package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/MarkdownText.js +2 -0
- package/dist/components/MarkdownText.js.map +1 -0
- package/dist/components/MhtViewer.js +2 -0
- package/dist/components/MhtViewer.js.map +1 -0
- package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/MindMapView.js +2 -0
- package/dist/components/MindMapView.js.map +1 -0
- package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/MissingDependency.js +2 -0
- package/dist/components/MissingDependency.js.map +1 -0
- package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ModelViewer.js +2 -0
- package/dist/components/ModelViewer.js.map +1 -0
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/MsiViewer.js +2 -0
- package/dist/components/MsiViewer.js.map +1 -0
- package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/NotebookViewer.js +2 -0
- package/dist/components/NotebookViewer.js.map +1 -0
- package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PdfViewer.js +2 -0
- package/dist/components/PdfViewer.js.map +1 -0
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PptxNavigationPane.js +9 -0
- package/dist/components/PptxNavigationPane.js.map +1 -0
- package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +120 -0
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PptxViewer.js +3 -1
- package/dist/components/PptxViewer.js.map +1 -0
- package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +71 -39
- package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/PsdViewer.js +2 -0
- package/dist/components/PsdViewer.js.map +1 -0
- package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/QmcAudioViewer.js +2 -0
- package/dist/components/QmcAudioViewer.js.map +1 -0
- package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/RasterViewer.js +2 -0
- package/dist/components/RasterViewer.js.map +1 -0
- package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/RtfViewer.js +2 -0
- package/dist/components/RtfViewer.js.map +1 -0
- package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/SevenZipBrowser.js +2 -0
- package/dist/components/SevenZipBrowser.js.map +1 -0
- package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/SqliteViewer.js +2 -0
- package/dist/components/SqliteViewer.js.map +1 -0
- package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/SubtitleViewer.js +2 -0
- package/dist/components/SubtitleViewer.js.map +1 -0
- package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxChart.js +2 -0
- package/dist/components/XlsxChart.js.map +1 -0
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxViewer.js +3 -1
- package/dist/components/XlsxViewer.js.map +1 -0
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +301 -304
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XmindViewer.js +2 -0
- package/dist/components/XmindViewer.js.map +1 -0
- package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ZipBrowser.js +3 -1
- package/dist/components/ZipBrowser.js.map +1 -0
- package/dist/components/ZipBrowser.vue.d.ts +3 -1
- package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +154 -122
- package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ZoomImage.js +2 -0
- package/dist/components/ZoomImage.js.map +1 -0
- package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplCodeBlock.js +2 -0
- package/dist/components/epl/EplCodeBlock.js.map +1 -0
- package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplDoc.js +2 -0
- package/dist/components/epl/EplDoc.js.map +1 -0
- package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplMethod.js +2 -0
- package/dist/components/epl/EplMethod.js.map +1 -0
- package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplResource.js +2 -0
- package/dist/components/epl/EplResource.js.map +1 -0
- package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplTree.js +2 -0
- package/dist/components/epl/EplTree.js.map +1 -0
- package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/EplVarTable.js +2 -0
- package/dist/components/epl/EplVarTable.js.map +1 -0
- package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/epl/numbering.js +2 -0
- package/dist/components/epl/numbering.js.map +1 -0
- package/dist/components/icons/Icon.js +2 -0
- package/dist/components/icons/Icon.js.map +1 -0
- package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
- package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/icons/iconPaths.js +2 -0
- package/dist/components/icons/iconPaths.js.map +1 -0
- package/dist/components/optionalViewer.js +2 -0
- package/dist/components/optionalViewer.js.map +1 -0
- package/dist/components/ui/ClampBlock.js +2 -0
- package/dist/components/ui/ClampBlock.js.map +1 -0
- package/dist/composables/useElementNavigation.d.ts +21 -0
- package/dist/composables/useElementNavigation.js +63 -0
- package/dist/composables/useElementNavigation.js.map +1 -0
- package/dist/composables/useFileViewer.js +2 -0
- package/dist/composables/useFileViewer.js.map +1 -0
- package/dist/composables/useLightbox.js +2 -0
- package/dist/composables/useLightbox.js.map +1 -0
- package/dist/composables/useLogRules.js +2 -0
- package/dist/composables/useLogRules.js.map +1 -0
- package/dist/lib/aes.js +2 -0
- package/dist/lib/aes.js.map +1 -0
- package/dist/lib/appx.js +2 -0
- package/dist/lib/appx.js.map +1 -0
- package/dist/lib/archiveHtml.d.ts +49 -0
- package/dist/lib/archiveHtml.js +377 -0
- package/dist/lib/archiveHtml.js.map +1 -0
- package/dist/lib/asn1.js +2 -0
- package/dist/lib/asn1.js.map +1 -0
- package/dist/lib/binobj.js +2 -0
- package/dist/lib/binobj.js.map +1 -0
- package/dist/lib/brotli.js +2 -0
- package/dist/lib/brotli.js.map +1 -0
- package/dist/lib/bzip2.js +2 -0
- package/dist/lib/bzip2.js.map +1 -0
- package/dist/lib/classfile.js +2 -0
- package/dist/lib/classfile.js.map +1 -0
- package/dist/lib/codePreview.js +2 -0
- package/dist/lib/codePreview.js.map +1 -0
- package/dist/lib/codefold.js +2 -0
- package/dist/lib/codefold.js.map +1 -0
- package/dist/lib/codeformat.js +2 -0
- package/dist/lib/codeformat.js.map +1 -0
- package/dist/lib/csv.js +2 -0
- package/dist/lib/csv.js.map +1 -0
- package/dist/lib/des.js +2 -0
- package/dist/lib/des.js.map +1 -0
- package/dist/lib/diagram.js +2 -0
- package/dist/lib/diagram.js.map +1 -0
- package/dist/lib/dicom.js +2 -0
- package/dist/lib/dicom.js.map +1 -0
- package/dist/lib/dicomCodecs.js +2 -0
- package/dist/lib/dicomCodecs.js.map +1 -0
- package/dist/lib/dicomRender.js +2 -0
- package/dist/lib/dicomRender.js.map +1 -0
- package/dist/lib/docxNavigation.d.ts +31 -0
- package/dist/lib/docxNavigation.js +65 -0
- package/dist/lib/docxNavigation.js.map +1 -0
- package/dist/lib/dxf.js +2 -0
- package/dist/lib/dxf.js.map +1 -0
- package/dist/lib/eml.js +2 -0
- package/dist/lib/eml.js.map +1 -0
- package/dist/lib/encoding.js +2 -0
- package/dist/lib/encoding.js.map +1 -0
- package/dist/lib/epl/code/codeDataParser.js +2 -0
- package/dist/lib/epl/code/codeDataParser.js.map +1 -0
- package/dist/lib/epl/code/codeLines.js +2 -0
- package/dist/lib/epl/code/codeLines.js.map +1 -0
- package/dist/lib/epl/code/textCode.js +2 -0
- package/dist/lib/epl/code/textCode.js.map +1 -0
- package/dist/lib/epl/eplDocument.js +2 -0
- package/dist/lib/epl/eplDocument.js.map +1 -0
- package/dist/lib/epl/imageList.js +2 -0
- package/dist/lib/epl/imageList.js.map +1 -0
- package/dist/lib/epl/index.js +2 -0
- package/dist/lib/epl/index.js.map +1 -0
- package/dist/lib/epl/io/binaryReader.js +2 -0
- package/dist/lib/epl/io/binaryReader.js.map +1 -0
- package/dist/lib/epl/io/encoding.js +2 -0
- package/dist/lib/epl/io/encoding.js.map +1 -0
- package/dist/lib/epl/model/eplSystemId.js +2 -0
- package/dist/lib/epl/model/eplSystemId.js.map +1 -0
- package/dist/lib/epl/names/eplLibData.js +2 -0
- package/dist/lib/epl/names/eplLibData.js.map +1 -0
- package/dist/lib/epl/names/idToNameMap.js +2 -0
- package/dist/lib/epl/names/idToNameMap.js.map +1 -0
- package/dist/lib/epl/names/libNameProvider.js +2 -0
- package/dist/lib/epl/names/libNameProvider.js.map +1 -0
- package/dist/lib/epl/program.js +2 -0
- package/dist/lib/epl/program.js.map +1 -0
- package/dist/lib/epl/projectFileReader.js +2 -0
- package/dist/lib/epl/projectFileReader.js.map +1 -0
- package/dist/lib/fb2.js +2 -0
- package/dist/lib/fb2.js.map +1 -0
- package/dist/lib/fileType.js +2 -0
- package/dist/lib/fileType.js.map +1 -0
- package/dist/lib/fonts.js +2 -0
- package/dist/lib/fonts.js.map +1 -0
- package/dist/lib/format.js +2 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/frontmatter.js +2 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/geo.js +2 -0
- package/dist/lib/geo.js.map +1 -0
- package/dist/lib/har.js +2 -0
- package/dist/lib/har.js.map +1 -0
- package/dist/lib/highlight.js +2 -0
- package/dist/lib/highlight.js.map +1 -0
- package/dist/lib/highlightLangs.js +2 -0
- package/dist/lib/highlightLangs.js.map +1 -0
- package/dist/lib/hprof.js +2 -0
- package/dist/lib/hprof.js.map +1 -0
- package/dist/lib/ipynb.js +2 -0
- package/dist/lib/ipynb.js.map +1 -0
- package/dist/lib/jks.js +2 -0
- package/dist/lib/jks.js.map +1 -0
- package/dist/lib/jsonl.js +2 -0
- package/dist/lib/jsonl.js.map +1 -0
- package/dist/lib/lrc.js +2 -0
- package/dist/lib/lrc.js.map +1 -0
- package/dist/lib/lzma.js +2 -0
- package/dist/lib/lzma.js.map +1 -0
- package/dist/lib/markdown/blocks.js +2 -0
- package/dist/lib/markdown/blocks.js.map +1 -0
- package/dist/lib/markdown/inline.js +2 -0
- package/dist/lib/markdown/inline.js.map +1 -0
- package/dist/lib/markdown/math.js +2 -0
- package/dist/lib/markdown/math.js.map +1 -0
- package/dist/lib/markdown/segmentize.js +2 -0
- package/dist/lib/markdown/segmentize.js.map +1 -0
- package/dist/lib/markdown/styles.js +2 -0
- package/dist/lib/markdown/styles.js.map +1 -0
- package/dist/lib/mht.js +2 -0
- package/dist/lib/mht.js.map +1 -0
- package/dist/lib/midi.js +2 -0
- package/dist/lib/midi.js.map +1 -0
- package/dist/lib/mime.js +2 -0
- package/dist/lib/mime.js.map +1 -0
- package/dist/lib/mindmap.js +2 -0
- package/dist/lib/mindmap.js.map +1 -0
- package/dist/lib/mobi.js +2 -0
- package/dist/lib/mobi.js.map +1 -0
- package/dist/lib/msi.js +2 -0
- package/dist/lib/msi.js.map +1 -0
- package/dist/lib/optionalDep.js +2 -0
- package/dist/lib/optionalDep.js.map +1 -0
- package/dist/lib/pkcs12.js +2 -0
- package/dist/lib/pkcs12.js.map +1 -0
- package/dist/lib/pptxNavigation.d.ts +7 -0
- package/dist/lib/pptxNavigation.js +29 -0
- package/dist/lib/pptxNavigation.js.map +1 -0
- package/dist/lib/qmc.js +2 -0
- package/dist/lib/qmc.js.map +1 -0
- package/dist/lib/rar.js +2 -0
- package/dist/lib/rar.js.map +1 -0
- package/dist/lib/rawPreview.js +2 -0
- package/dist/lib/rawPreview.js.map +1 -0
- package/dist/lib/rc2.js +2 -0
- package/dist/lib/rc2.js.map +1 -0
- package/dist/lib/resolveInput.js +2 -0
- package/dist/lib/resolveInput.js.map +1 -0
- package/dist/lib/rtf.js +2 -0
- package/dist/lib/rtf.js.map +1 -0
- package/dist/lib/sevenZip.js +2 -0
- package/dist/lib/sevenZip.js.map +1 -0
- package/dist/lib/sha1.js +2 -0
- package/dist/lib/sha1.js.map +1 -0
- package/dist/lib/sha256.js +2 -0
- package/dist/lib/sha256.js.map +1 -0
- package/dist/lib/speechAudio.js +2 -0
- package/dist/lib/speechAudio.js.map +1 -0
- package/dist/lib/sqlite.js +2 -0
- package/dist/lib/sqlite.js.map +1 -0
- package/dist/lib/subtitle.js +2 -0
- package/dist/lib/subtitle.js.map +1 -0
- package/dist/lib/tarball.js +2 -0
- package/dist/lib/tarball.js.map +1 -0
- package/dist/lib/viewerFormats.js +2 -0
- package/dist/lib/viewerFormats.js.map +1 -0
- package/dist/lib/viewerKind.js +2 -0
- package/dist/lib/viewerKind.js.map +1 -0
- package/dist/lib/x509.js +2 -0
- package/dist/lib/x509.js.map +1 -0
- package/dist/lib/xlsx.js +2 -0
- package/dist/lib/xlsx.js.map +1 -0
- package/dist/lib/xlsxCharts.js +2 -0
- package/dist/lib/xlsxCharts.js.map +1 -0
- package/dist/lib/xmind.js +2 -0
- package/dist/lib/xmind.js.map +1 -0
- package/dist/lib/zip.js +2 -0
- package/dist/lib/zip.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","names":[],"sources":["../../src/lib/mime.ts"],"sourcesContent":["/**\n * Hand-rolled MIME core (RFC 822 / 2045 / 2047 / 2231) shared by the side-viewer's MIME-container previews.\n *\n * Zero-dependency, in the spirit of the project's other parsers (lib/zip.ts, lib/binobj.ts). Both an `.eml`\n * email (lib/eml.ts) and a `.mht`/`.mhtml` web archive (lib/mht.ts) are the SAME on the wire: RFC 822 headers\n * + a body that may be a tree of multipart entities, each with its own Content-Type / Content-Transfer-\n * Encoding / charset. This module owns that generic machinery — parse the container into an {@link Entity}\n * tree, transfer-decode (base64 / quoted-printable) and charset-decode (TextDecoder) each leaf — and the two\n * format-specific modules flatten the tree into their own render-ready shapes.\n *\n * Bytes vs. text: the transport is treated as a 1:1 binary string (each byte → one char in [0,255]) so we\n * can split on boundaries / unfold headers with plain string ops while keeping leaf bytes exact. Per-part we\n * then transfer-decode and charset-decode to recover real text / bytes. Real attachments / resources are\n * virtually always base64/QP (7-bit safe), so the binary string round-trips losslessly even for non-UTF-8.\n */\n\n// Charset labels TextDecoder rejects (or that aren't registered) → a label it accepts. ASCII is a UTF-8\n// subset, so decoding it as UTF-8 is lossless; the cp1252/ansi spellings map to the registered name.\nconst CHARSET_ALIAS: Record<string, string> = {\n 'us-ascii': 'utf-8',\n ascii: 'utf-8',\n 'ansi_x3.4-1968': 'utf-8',\n 'iso-ir-6': 'utf-8',\n ansi: 'windows-1252',\n cp1252: 'windows-1252',\n}\n\n/** Pack bytes into a 1:1 binary string (each byte → one char in [0,255]); chunked to bound arg count. */\nexport function bytesToBinary(bytes: Uint8Array): string {\n let out = ''\n const CHUNK = 0x8000\n for (let i = 0; i < bytes.length; i += CHUNK) {\n out += String.fromCharCode(...bytes.subarray(i, i + CHUNK))\n }\n return out\n}\n\n/** Inverse of {@link bytesToBinary}: a binary string back to its exact bytes. */\nexport function binaryToBytes(s: string): Uint8Array {\n const out = new Uint8Array(s.length)\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 0xff\n return out\n}\n\n/** Decode base64 (tolerant of embedded whitespace/newlines) to bytes. */\nexport function base64ToBytes(b64: string): Uint8Array {\n const clean = b64.replace(/[^A-Za-z0-9+/=]/g, '')\n return binaryToBytes(atob(clean))\n}\n\n/** Decode bytes with a (possibly messy) charset label, falling back to UTF-8 then a raw binary string. */\nexport function decodeCharset(bytes: Uint8Array, charset: string): string {\n // RFC 2047 charsets may carry a `*language` suffix (e.g. `utf-8*en`) — strip it before labelling.\n let label = (charset || 'utf-8').trim().toLowerCase().split('*')[0]\n label = CHARSET_ALIAS[label] ?? label\n try {\n return new TextDecoder(label, { fatal: false }).decode(bytes)\n } catch {\n try {\n return new TextDecoder('utf-8', { fatal: false }).decode(bytes)\n } catch {\n return bytesToBinary(bytes)\n }\n }\n}\n\n/** Decode quoted-printable text (soft line-breaks dropped, `=XX` → byte) to a binary string. */\nexport function decodeQuotedPrintable(s: string): string {\n return s\n .replace(/=\\r?\\n/g, '')\n .replace(/=([0-9A-Fa-f]{2})/g, (_, h: string) => String.fromCharCode(parseInt(h, 16)))\n}\n\n/**\n * Decode RFC 2047 encoded-words in a header value (`=?charset?B|Q?text?=`). Adjacent encoded words\n * separated only by whitespace are concatenated with the whitespace removed (§6.2).\n */\nexport function decodeWords(input: string): string {\n if (!input || !input.includes('=?')) return input\n const collapsed = input.replace(/\\?=\\s+=\\?/g, '?==?')\n return collapsed.replace(\n /=\\?([^?]+)\\?([BbQq])\\?([^?]*)\\?=/g,\n (whole, charset: string, enc: string, data: string) => {\n try {\n let bytes: Uint8Array\n if (enc.toUpperCase() === 'B') {\n bytes = base64ToBytes(data)\n } else {\n const q = data\n .replace(/_/g, ' ')\n .replace(/=([0-9A-Fa-f]{2})/g, (_, h: string) => String.fromCharCode(parseInt(h, 16)))\n bytes = binaryToBytes(q)\n }\n return decodeCharset(bytes, charset)\n } catch {\n return whole\n }\n },\n )\n}\n\nexport interface HeaderField {\n name: string\n value: string\n}\n\n/** Split a raw entity (binary string) into its header block and body at the first blank line. */\nexport function splitHeadersBody(raw: string): [string, string] {\n const m = raw.match(/\\r?\\n\\r?\\n/)\n if (!m || m.index === undefined) return [raw, '']\n return [raw.slice(0, m.index), raw.slice(m.index + m[0].length)]\n}\n\n/** Parse a header block into ordered fields, unfolding continuation (leading-whitespace) lines. */\nexport function parseHeaders(block: string): HeaderField[] {\n const out: HeaderField[] = []\n for (const line of block.split(/\\r?\\n/)) {\n if (/^[ \\t]/.test(line) && out.length) {\n // Folded continuation: unfolding removes the CRLF; collapse the indent to a single space.\n out[out.length - 1].value += ' ' + line.replace(/^[ \\t]+/, '')\n } else {\n const ci = line.indexOf(':')\n if (ci > 0) out.push({ name: line.slice(0, ci).trim(), value: line.slice(ci + 1).trim() })\n }\n }\n return out\n}\n\n/** First header value (case-insensitive), '' when absent. */\nexport function getHeader(headers: HeaderField[], name: string): string {\n const lc = name.toLowerCase()\n return headers.find((h) => h.name.toLowerCase() === lc)?.value ?? ''\n}\n\nexport interface ContentType {\n type: string\n params: Record<string, string>\n}\n\n/** Split a structured header value on `;`, ignoring separators inside quotes. */\nexport function splitParams(value: string): string[] {\n const out: string[] = []\n let cur = ''\n let inQ = false\n for (const c of value) {\n if (c === '\"') {\n inQ = !inQ\n cur += c\n } else if (c === ';' && !inQ) {\n out.push(cur)\n cur = ''\n } else {\n cur += c\n }\n }\n if (cur.trim()) out.push(cur)\n return out\n}\n\n/** Parse a Content-Type / Content-Disposition value into its type token + lowercased params. */\nexport function parseTyped(value: string): ContentType {\n const parts = splitParams(value)\n const type = (parts.shift() ?? '').trim().toLowerCase()\n const params: Record<string, string> = {}\n for (const p of parts) {\n const eq = p.indexOf('=')\n if (eq < 0) continue\n const k = p.slice(0, eq).trim().toLowerCase()\n let v = p.slice(eq + 1).trim()\n if (v.startsWith('\"') && v.endsWith('\"')) v = v.slice(1, -1)\n params[k] = v\n }\n return { type, params }\n}\n\n/** Percent-decode an RFC 2231 extended value's encoded bytes with the given charset. */\nfunction percentDecode(s: string, charset: string): string {\n const bytes: number[] = []\n for (let i = 0; i < s.length; i++) {\n if (s[i] === '%' && i + 2 < s.length) {\n bytes.push(parseInt(s.slice(i + 1, i + 3), 16))\n i += 2\n } else {\n bytes.push(s.charCodeAt(i) & 0xff)\n }\n }\n return decodeCharset(Uint8Array.from(bytes), charset || 'utf-8')\n}\n\n/** Decode a single RFC 2231 extended value (`charset'lang'percent-encoded`). */\nfunction decodeExtValue(v: string): string {\n const m = v.match(/^([^']*)'[^']*'(.*)$/)\n return m ? percentDecode(m[2], m[1]) : percentDecode(v, '')\n}\n\n/**\n * Resolve a parameter (`filename` / `name`) across its RFC 2231 spellings: a plain quoted value (with\n * RFC 2047 encoded-words decoded), a single extended `name*=`, or `name*0*`/`name*1*` continuations.\n */\nexport function paramValue(params: Record<string, string>, base: string): string {\n if (params[base + '*'] !== undefined) return decodeExtValue(params[base + '*'])\n if (params[base] !== undefined) return decodeWords(params[base])\n if (params[`${base}*0`] !== undefined || params[`${base}*0*`] !== undefined) {\n let s = ''\n let cs = ''\n for (let i = 0; ; i++) {\n const ext = params[`${base}*${i}*`]\n const plain = params[`${base}*${i}`]\n if (ext !== undefined) {\n if (i === 0) {\n const m = ext.match(/^([^']*)'[^']*'(.*)$/)\n if (m) {\n cs = m[1]\n s += percentDecode(m[2], cs)\n } else {\n s += percentDecode(ext, cs)\n }\n } else {\n s += percentDecode(ext, cs)\n }\n } else if (plain !== undefined) {\n s += plain\n } else {\n break\n }\n }\n return s\n }\n return ''\n}\n\n/** Transfer-decode a leaf body (binary string) per its Content-Transfer-Encoding into exact bytes. */\nexport function decodeBody(bodyBin: string, encoding: string): Uint8Array {\n const enc = (encoding || '').trim().toLowerCase()\n if (enc === 'base64') return base64ToBytes(bodyBin)\n if (enc === 'quoted-printable') return binaryToBytes(decodeQuotedPrintable(bodyBin))\n // 7bit / 8bit / binary / none → the bytes are already literal.\n return binaryToBytes(bodyBin)\n}\n\n/**\n * Split a multipart body (binary string) into its part substrings (headers + body each). Boundary\n * delimiter lines are `--boundary` at a line start; the closing one is `--boundary--`. The CRLF preceding\n * a delimiter belongs to the boundary, so it's excluded from the preceding part.\n */\nexport function splitMultipart(bodyBin: string, boundary: string): string[] {\n const esc = boundary.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const re = new RegExp(`(?:^|\\\\r\\\\n|\\\\n)--${esc}(--)?[ \\\\t]*(?:\\\\r\\\\n|\\\\n|$)`, 'g')\n const parts: string[] = []\n let m: RegExpExecArray | null\n let contentStart = -1\n while ((m = re.exec(bodyBin)) !== null) {\n if (contentStart >= 0) parts.push(bodyBin.slice(contentStart, m.index))\n if (m[1] === '--') break // closing delimiter\n contentStart = re.lastIndex\n }\n return parts\n}\n\n/** One MIME entity: a leaf (with decoded {@link bodyBytes}) or a multipart node (with child {@link parts}). */\nexport interface Entity {\n contentType: string\n charset: string\n encoding: string\n disposition: string\n filename: string\n /** Content-ID without the angle brackets, '' if none — the key a `cid:` ref resolves to (email inline). */\n contentId: string\n /** Content-Location URI, '' if none — the key a web archive's resource references resolve to (MHT). */\n contentLocation: string\n /** Raw Content-Type params (lowercased), e.g. `boundary` / `start` / `type` — for MHT root selection. */\n ctParams: Record<string, string>\n isMultipart: boolean\n parts: Entity[]\n bodyBytes: Uint8Array | null\n}\n\n/** Recursively parse one MIME entity (binary string) into the {@link Entity} tree. */\nexport function parseEntity(rawBin: string, depth = 0): Entity {\n const [head, body] = splitHeadersBody(rawBin)\n const headers = parseHeaders(head)\n const ct = parseTyped(getHeader(headers, 'content-type') || 'text/plain')\n const type = ct.type || 'text/plain'\n const cd = parseTyped(getHeader(headers, 'content-disposition'))\n let contentId = getHeader(headers, 'content-id').trim()\n if (contentId.startsWith('<') && contentId.endsWith('>')) contentId = contentId.slice(1, -1)\n\n const ent: Entity = {\n contentType: type,\n charset: ct.params.charset || '',\n encoding: getHeader(headers, 'content-transfer-encoding'),\n disposition: cd.type || '',\n filename: paramValue(cd.params, 'filename') || paramValue(ct.params, 'name') || '',\n contentId,\n // Content-Location is a bare URI (no angle brackets) — just trim.\n contentLocation: getHeader(headers, 'content-location').trim(),\n ctParams: ct.params,\n isMultipart: false,\n parts: [],\n bodyBytes: null,\n }\n\n if (type.startsWith('multipart/') && ct.params.boundary && depth < 20) {\n ent.isMultipart = true\n for (const sub of splitMultipart(body, ct.params.boundary)) {\n ent.parts.push(parseEntity(sub, depth + 1))\n }\n } else {\n ent.bodyBytes = decodeBody(body, ent.encoding)\n }\n return ent\n}\n\n/** Depth-first collect every leaf (non-multipart) entity. */\nexport function collectLeaves(ent: Entity, out: Entity[]): void {\n if (ent.isMultipart) {\n for (const p of ent.parts) collectLeaves(p, out)\n } else {\n out.push(ent)\n }\n}\n"],"mappings":"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"}
|
package/dist/lib/mindmap.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mindmap.js","names":[],"sources":["../../src/lib/mindmap.ts"],"sourcesContent":["/**\n * Parser for outline / mind-map files into a common tree, shared by the side viewer's MindMapView.\n *\n * Two input formats, both XML, both fundamentally a tree:\n * - OPML (.opml): `<opml><head><title/></head><body><outline text=… _note=… url=…>…</outline></body></opml>`,\n * `<outline>` nests arbitrarily. Used for RSS feed lists, outlines, and some mind-map exports.\n * - FreeMind / Freeplane (.mm): `<map><node TEXT=… LINK=… COLOR=… FOLDED=…>…</node></map>`, `<node>` nests;\n * a node's label can also live in `<richcontent TYPE=\"NODE\">…html…</richcontent>`, notes in `TYPE=\"NOTE\"`.\n *\n * We parse with the browser DOMParser (guarded for SSR — the viewer is client-only, but stay defensive) and\n * normalize both into {@link MindNode}. Node ids are assigned during parse and are stable for the lifetime of a\n * parsed tree (the viewer caches the parse by file text), so they key the collapse/expand state.\n *\n * `.mm` is ALSO the Objective-C++ source extension, so callers must content-sniff with {@link looksLikeMindMap}\n * before treating a `.mm` as a mind map — an Objective-C++ `.mm` is not XML and would (rightly) fail this gate.\n */\n\nexport interface MindNode {\n /** Stable id within one parsed tree — keys collapse/expand state. */\n id: number\n /** Plain-text label (markup/whitespace flattened). */\n text: string\n /** Optional note (OPML `_note`, FreeMind `<richcontent TYPE=\"NOTE\">`) — shown as a hover tooltip. */\n note?: string\n /** Optional hyperlink (OPML `url`/`htmlUrl`/`xmlUrl`, FreeMind `LINK`) — rendered as an open-in-new-tab glyph. */\n link?: string\n /** Explicit text color (FreeMind `COLOR`), `#rgb`/`#rrggbb` only. */\n color?: string\n /** Explicit background color (FreeMind `BACKGROUND_COLOR`), `#rgb`/`#rrggbb` only. */\n bg?: string\n /** Authored initial fold state (FreeMind `FOLDED=\"true\"`) — the viewer seeds collapse from this. */\n foldedInit?: boolean\n children: MindNode[]\n}\n\nexport interface MindMap {\n format: 'opml' | 'freemind' | 'xmind'\n title: string\n root: MindNode\n /** Total node count (incl. root), for the viewer's summary line. */\n count: number\n}\n\n/** Lowercased child elements with the given local tag name. */\nfunction childrenByTag(el: Element, tag: string): Element[] {\n const out: Element[] = []\n for (const c of Array.from(el.children)) {\n if (c.tagName.toLowerCase() === tag) out.push(c)\n }\n return out\n}\n\n/** First child element with the given local tag name (optionally matching a `TYPE` attribute, case-insensitive). */\nfunction firstChildByTag(el: Element, tag: string, type?: string): Element | null {\n for (const c of Array.from(el.children)) {\n if (c.tagName.toLowerCase() !== tag) continue\n if (type && (attr(c, 'TYPE') || '').toLowerCase() !== type.toLowerCase()) continue\n return c\n }\n return null\n}\n\n/** Read the first present attribute among `names`, falling back to a case-insensitive scan (FreeMind uses UPPER). */\nfunction attr(el: Element, ...names: string[]): string | null {\n for (const n of names) {\n const v = el.getAttribute(n)\n if (v != null) return v\n }\n const lowered = names.map((n) => n.toLowerCase())\n for (const a of Array.from(el.attributes)) {\n if (lowered.includes(a.name.toLowerCase())) return a.value\n }\n return null\n}\n\n/** Collapse all whitespace (incl. newlines from flattened markup) to single spaces. */\nfunction flatten(s: string | null | undefined): string {\n return (s || '').replace(/\\s+/g, ' ').trim()\n}\n\n/** Accept only simple hex colors; ignore named/rgba() values we can't safely inline. */\nfunction normColor(c: string | null): string | undefined {\n if (!c) return undefined\n return /^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(c.trim()) ? c.trim() : undefined\n}\n\nfunction makeIdGen(): () => number {\n let n = 0\n return () => n++\n}\n\nfunction parseOpmlOutline(el: Element, id: () => number): MindNode {\n // `text` is the OPML 2.0 attribute; `title` is the older fallback. Both may carry HTML — flatten to text.\n const raw = attr(el, 'text', 'title') ?? ''\n const text = flatten(raw.includes('<') ? stripTags(raw) : raw)\n const note = flatten(attr(el, '_note', 'note') ?? '')\n const link = attr(el, 'url', 'htmlUrl', 'xmlUrl') ?? undefined\n return {\n id: id(),\n text,\n note: note || undefined,\n link: link || undefined,\n children: childrenByTag(el, 'outline').map((c) => parseOpmlOutline(c, id)),\n }\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, ' ')\n}\n\nfunction parseFreemindNode(el: Element, id: () => number): MindNode {\n let text = attr(el, 'TEXT')\n if (text == null) {\n // Rich (HTML) node label — DOMParser already decoded entities, so textContent is the readable text.\n const rc = firstChildByTag(el, 'richcontent', 'NODE') ?? firstChildByTag(el, 'richcontent')\n text = rc ? rc.textContent : ''\n }\n const noteEl = firstChildByTag(el, 'richcontent', 'NOTE')\n return {\n id: id(),\n text: flatten(text),\n note: noteEl ? flatten(noteEl.textContent) || undefined : undefined,\n link: attr(el, 'LINK') ?? undefined,\n color: normColor(attr(el, 'COLOR')),\n bg: normColor(attr(el, 'BACKGROUND_COLOR')),\n foldedInit: (attr(el, 'FOLDED') || '').toLowerCase() === 'true',\n children: childrenByTag(el, 'node').map((c) => parseFreemindNode(c, id)),\n }\n}\n\nfunction countNodes(n: MindNode): number {\n let c = 1\n for (const k of n.children) c += countNodes(k)\n return c\n}\n\n/**\n * Parse OPML or FreeMind XML into a {@link MindMap}. `kind` picks the parser, but the actual root element wins\n * if it disagrees (so a mislabeled file still parses). Throws on malformed XML or an unrecognized root.\n */\nexport function parseMindMap(text: string, kind: 'opml' | 'mm', fallbackTitle = ''): MindMap {\n if (typeof DOMParser === 'undefined') {\n // SSR / no-DOM fallback: an empty tree rather than a crash (the real parse runs client-side).\n return {\n format: kind === 'opml' ? 'opml' : 'freemind',\n title: fallbackTitle,\n root: { id: 0, text: fallbackTitle, children: [] },\n count: 1,\n }\n }\n const doc = new DOMParser().parseFromString(text, 'application/xml')\n if (doc.querySelector('parsererror')) throw new Error('XML 解析失败')\n\n const id = makeIdGen()\n const opml = doc.querySelector('opml')\n const map = doc.querySelector('map')\n\n if (opml || (kind === 'opml' && !map)) {\n const root = opml ?? doc.documentElement\n const titleEl = root.querySelector('head > title') ?? doc.querySelector('head > title')\n const title = flatten(titleEl?.textContent) || fallbackTitle || '大纲'\n const body = root.querySelector('body') ?? doc.querySelector('body')\n const tops = body ? childrenByTag(body, 'outline') : []\n // A single titleless top outline becomes the root itself; otherwise wrap the tops under a title node.\n let rootNode: MindNode\n if (!flatten(titleEl?.textContent) && tops.length === 1) {\n rootNode = parseOpmlOutline(tops[0], id)\n } else {\n rootNode = { id: id(), text: title, children: tops.map((t) => parseOpmlOutline(t, id)) }\n }\n return {\n format: 'opml',\n title: rootNode.text || title,\n root: rootNode,\n count: countNodes(rootNode),\n }\n }\n\n if (map) {\n const rootEl = childrenByTag(map, 'node')[0]\n const rootNode = rootEl\n ? parseFreemindNode(rootEl, id)\n : { id: id(), text: fallbackTitle || '思维导图', children: [] }\n return {\n format: 'freemind',\n title: rootNode.text || fallbackTitle || '思维导图',\n root: rootNode,\n count: countNodes(rootNode),\n }\n }\n\n throw new Error('无法识别的文件结构(既非 OPML 也非 FreeMind)')\n}\n\n/**\n * Cheap content sniff to confirm a file really is a mind map before showing the diagram. `.opml` is unambiguous;\n * `.mm` is gated because the extension is shared with Objective-C++ — a non-XML `.mm` fails here and falls back\n * to plain highlighted source. Only the file head is inspected.\n */\nexport function looksLikeMindMap(text: string, ext: string): boolean {\n const head = (text || '').slice(0, 4096)\n if (ext === 'opml') return /<opml[\\s>]/i.test(head)\n if (ext === 'mm') return /<map[\\s>]/i.test(head) && /<node[\\s>]/i.test(head)\n return false\n}\n"],"mappings":"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"}
|
package/dist/lib/mobi.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobi.js","names":[],"sources":["../../src/lib/mobi.ts"],"sourcesContent":["/**\n * Hand-rolled, zero-dependency BEST-EFFORT MOBI / AZW3 text extractor for the side viewer.\n *\n * Kindle formats are a Palm Database (PDB) holding a MOBI/KF8 header plus text records. This parses the PDB,\n * reads the PalmDOC/MOBI header, strips each text record's trailing-data entries, decompresses (PalmDOC LZ77,\n * or uncompressed), and concatenates the result into the book's HTML — which the viewer shows in a sandboxed\n * iframe. It is intentionally PARTIAL: HUFF/CDIC-compressed books (compression 17480) and DRM'd books can't\n * be read here, and inline images (`<img recindex>`) are dropped (we don't resolve image records). Such files\n * should be downloaded and opened in a real reader.\n */\n\nexport interface ParsedMobi {\n title: string\n html: string // the extracted book HTML (already a fragment; the viewer wraps + sandboxes it)\n}\n\n// Backward variable-width int from the last ≤4 bytes of data[0..upTo) — the trailing-entry size encoding.\nfunction trailingEntrySize(data: Uint8Array, upTo: number): number {\n let num = 0\n for (let i = Math.max(0, upTo - 4); i < upTo; i++) {\n const v = data[i]\n if (v & 0x80) num = 0\n num = (num << 7) | (v & 0x7f)\n }\n return num\n}\n\n// Total trailing bytes appended to a text record, per the MOBI \"extra data flags\".\nfunction trailingSize(data: Uint8Array, flags: number): number {\n let num = 0\n let test = flags >> 1\n while (test) {\n if (test & 1) num += trailingEntrySize(data, data.length - num)\n test >>= 1\n }\n if (flags & 1) num += (data[data.length - num - 1] & 0x3) + 1\n return num\n}\n\n// PalmDOC LZ77 decompression (compression type 2).\nfunction palmDocDecompress(data: Uint8Array): Uint8Array {\n const out: number[] = []\n let i = 0\n const n = data.length\n while (i < n) {\n const b = data[i++]\n if (b === 0) {\n out.push(0)\n } else if (b <= 8) {\n for (let j = 0; j < b && i < n; j++) out.push(data[i++]) // 1..8: copy next b literal bytes\n } else if (b <= 0x7f) {\n out.push(b) // literal byte\n } else if (b <= 0xbf) {\n // 0x80..0xBF: LZ77 back-reference (2 bytes)\n const pair = ((b << 8) | data[i++]) & 0x3fff\n const dist = pair >> 3\n const len = (pair & 7) + 3\n let pos = out.length - dist\n if (pos < 0) break\n for (let j = 0; j < len; j++) out.push(out[pos++])\n } else {\n // 0xC0..0xFF: a space followed by (byte ^ 0x80)\n out.push(0x20, b ^ 0x80)\n }\n }\n return Uint8Array.from(out)\n}\n\nfunction decodeText(bytes: Uint8Array, encoding: number): string {\n const label = encoding === 65001 ? 'utf-8' : 'windows-1252'\n try {\n return new TextDecoder(label).decode(bytes)\n } catch {\n return new TextDecoder('utf-8').decode(bytes)\n }\n}\n\nexport function parseMobi(buf: ArrayBuffer): ParsedMobi {\n const dv = new DataView(buf)\n if (buf.byteLength < 80) throw new Error('文件过小,不是有效的 MOBI')\n\n // PDB record table.\n const numRecords = dv.getUint16(76)\n if (!numRecords) throw new Error('MOBI 没有记录')\n const offsets: number[] = []\n for (let i = 0; i < numRecords; i++) offsets.push(dv.getUint32(78 + i * 8))\n offsets.push(buf.byteLength)\n const record = (i: number): Uint8Array =>\n new Uint8Array(buf, offsets[i], offsets[i + 1] - offsets[i])\n\n // Record 0: PalmDOC header (16 bytes) + MOBI header.\n const rec0 = record(0)\n const r0 = new DataView(rec0.buffer, rec0.byteOffset, rec0.byteLength)\n const compression = r0.getUint16(0)\n const textRecordCount = r0.getUint16(8)\n if (compression === 17480) {\n throw new Error('该 MOBI 使用 HUFF/CDIC 压缩,暂不支持浏览器内解析(请下载后用阅读器打开)')\n }\n\n // MOBI header fields (offsets relative to record 0).\n const hasMobiHeader =\n rec0.length > 20 && String.fromCharCode(rec0[16], rec0[17], rec0[18], rec0[19]) === 'MOBI'\n const encoding = hasMobiHeader ? r0.getUint32(28) : 1252\n const extraFlags = hasMobiHeader && rec0.length > 244 ? r0.getUint16(242) : 0\n\n // Book title from the MOBI full-name field.\n let title = ''\n if (hasMobiHeader && rec0.length >= 92) {\n const nameOff = r0.getUint32(84)\n const nameLen = r0.getUint32(88)\n if (nameOff + nameLen <= rec0.length) {\n title = decodeText(rec0.subarray(nameOff, nameOff + nameLen), encoding).trim()\n }\n }\n\n // Decompress text records 1..textRecordCount.\n const chunks: string[] = []\n const last = Math.min(textRecordCount, numRecords - 1)\n for (let i = 1; i <= last; i++) {\n let rec = record(i)\n const trail = extraFlags ? trailingSize(rec, extraFlags) : 0\n if (trail > 0 && trail < rec.length) rec = rec.subarray(0, rec.length - trail)\n const bytes = compression === 2 ? palmDocDecompress(rec) : rec\n chunks.push(decodeText(bytes, encoding))\n }\n\n let html = chunks.join('')\n if (!html.trim()) throw new Error('未能从该 MOBI 提取正文')\n // Drop image references we can't resolve (mobi <img recindex=…> → no src), to avoid broken icons.\n html = html.replace(/<img[^>]*recindex[^>]*>/gi, '')\n return { title, html }\n}\n"],"mappings":"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"}
|
package/dist/lib/msi.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"msi.js","names":[],"sources":["../../src/lib/msi.ts"],"sourcesContent":["/**\n * Minimal in-browser MSI (Windows Installer) reader — no dependency, no WASM.\n *\n * An `.msi` is NOT a PE executable (that's `.exe`/`.dll` — see lib/binobj.ts), and NOT a zip (that's\n * `.appx`/`.msix` — see lib/appx.ts). It's an OLE2 **Compound File** (a.k.a. structured storage / CFBF):\n * a mini-filesystem of \"streams\" inside one file (the same container legacy `.doc`/`.xls`/`.msg` use). The\n * install metadata we surface lives in two of those streams:\n * - the standard **SummaryInformation** property set (`\\x05SummaryInformation`): title / subject / author /\n * comments, the target platform+language template, the package (revision) code, the authoring tool, dates.\n * - the **Property table** (ProductName / ProductVersion / ProductCode / UpgradeCode / Manufacturer / …),\n * spread across the MSI string pool (`_StringData` + `_StringPool`) and the `Property` table stream.\n *\n * We parse only what a \"what is this installer?\" view needs — never the embedded cabinet / payload files.\n * The logic is validated byte-for-byte against real installers using the Windows `WindowsInstaller.Installer`\n * COM automation as an oracle (the Property table + SummaryInformation match it exactly).\n *\n * Used by MsiViewer (the side viewer's MSI mode): the bytes come from the same download endpoint as any\n * other file, are parsed entirely client-side, and never leave the browser.\n */\n\nexport interface MsiMeta {\n label: string\n value: string\n}\nexport interface MsiProperty {\n name: string\n value: string\n}\n\nexport interface MsiInfo {\n /** Resolved product identity from the Property table (any field may be absent if the table is unreadable). */\n productName?: string\n productVersion?: string\n manufacturer?: string\n productCode?: string\n upgradeCode?: string\n /** Install language: a human label when the LCID is recognized (with the raw id), else the raw id. */\n language?: string\n /** Full Property table (name → value), in stored order. Empty if the table couldn't be decoded. */\n properties: MsiProperty[]\n /** SummaryInformation rows, pre-labeled (Chinese), in display order. */\n summary: MsiMeta[]\n /** Decoded names of the database tables present — a quick \"shape of the installer\" cue. */\n tables: string[]\n /** Non-fatal notes (e.g. the Property table was unreadable but SummaryInformation succeeded). */\n notes: string[]\n}\n\n/** Thrown when the bytes aren't an OLE compound file at all (so the viewer can show a clear message). */\nexport class NotMsiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'NotMsiError'\n }\n}\n\n// ── Compound File (CFBF / MS-CFB) reader ────────────────────────────────────────────────────────────\nconst CFB_SIG = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]\nconst ENDOFCHAIN = 0xfffffffe\nconst FREESECT = 0xffffffff\n\ninterface CfbEntry {\n /** Raw UTF-16 directory name (null-stripped) — MSI table names are still mangled (see decodeStreamName). */\n name: string\n /** 1 = storage, 2 = stream, 5 = root storage. */\n type: number\n start: number\n size: number\n}\n\nclass Cfb {\n private dv: DataView\n private buf: ArrayBuffer\n private sectorSize: number\n private miniSectorSize: number\n private miniCutoff: number\n private fat: number[] = []\n private miniFat: number[] = []\n private miniStream: Uint8Array = new Uint8Array(0)\n readonly entries: CfbEntry[] = []\n\n constructor(buf: ArrayBuffer) {\n this.buf = buf\n const dv = new DataView(buf)\n this.dv = dv\n if (buf.byteLength < 512) throw new NotMsiError('文件过小,不是有效的复合文件')\n for (let i = 0; i < 8; i++) {\n if (dv.getUint8(i) !== CFB_SIG[i]) {\n throw new NotMsiError('不是 OLE 复合文件(缺少 D0CF11E0 魔数)')\n }\n }\n const major = dv.getUint16(0x1a, true)\n this.sectorSize = 1 << dv.getUint16(0x1e, true)\n this.miniSectorSize = 1 << dv.getUint16(0x20, true)\n const firstDir = dv.getUint32(0x30, true)\n this.miniCutoff = dv.getUint32(0x38, true)\n const firstMiniFat = dv.getUint32(0x3c, true)\n const firstDifat = dv.getUint32(0x44, true)\n const per = this.sectorSize / 4\n\n // DIFAT (109 entries in the header, then any chained DIFAT sectors) → list of FAT sectors → FAT array.\n const difat: number[] = []\n for (let i = 0; i < 109; i++) {\n const v = dv.getUint32(0x4c + i * 4, true)\n if (v !== FREESECT && v !== ENDOFCHAIN) difat.push(v)\n }\n let ds = firstDifat\n let guard = 0\n while (ds !== ENDOFCHAIN && ds !== FREESECT && guard++ < 1_000_000) {\n const base = this.sectorOffset(ds)\n for (let i = 0; i < per - 1; i++) {\n const v = dv.getUint32(base + i * 4, true)\n if (v !== FREESECT) difat.push(v)\n }\n ds = dv.getUint32(base + (per - 1) * 4, true)\n }\n for (const fs of difat) {\n const base = this.sectorOffset(fs)\n for (let i = 0; i < per; i++) this.fat.push(dv.getUint32(base + i * 4, true))\n }\n\n // Directory: a chain of 128-byte entries starting at firstDir.\n const dirBytes = this.readFatStream(firstDir, this.chain(firstDir).length * this.sectorSize)\n const ddv = new DataView(dirBytes.buffer, dirBytes.byteOffset, dirBytes.byteLength)\n for (let o = 0; o + 128 <= dirBytes.length; o += 128) {\n const type = ddv.getUint8(o + 66)\n if (type === 0) continue // unallocated entry\n const nameLen = ddv.getUint16(o + 64, true) // bytes incl. null terminator\n let name = ''\n for (let c = 0; c + 1 < nameLen; c += 2) {\n const code = ddv.getUint16(o + c, true)\n if (code === 0) break\n name += String.fromCharCode(code)\n }\n const start = ddv.getUint32(o + 116, true)\n // v3 stores the size in 4 bytes (the high 4 may be garbage); v4 uses the full 8.\n const size =\n major <= 3 ? ddv.getUint32(o + 120, true) : Number(ddv.getBigUint64(o + 120, true))\n this.entries.push({ name, type, start, size })\n }\n\n // Mini stream (held by the root entry, in the regular FAT) + the MiniFAT (chained from firstMiniFat).\n const root = this.entries.find((e) => e.type === 5)\n if (root) this.miniStream = this.readFatStream(root.start, root.size)\n for (const s of this.chain(firstMiniFat)) {\n const base = this.sectorOffset(s)\n for (let i = 0; i < per; i++) this.miniFat.push(dv.getUint32(base + i * 4, true))\n }\n }\n\n private sectorOffset(s: number): number {\n // Sector S starts at (S+1)*sectorSize: the 512-byte header is padded to a full sector, so for v3\n // (512-byte) this is 512 + S*512, and for v4 (4096-byte) it's 4096 + S*4096 (NOT 512 + S*4096).\n return (s + 1) * this.sectorSize\n }\n /** Follow a FAT chain to a list of sector indices (bounded against corruption). */\n private chain(start: number): number[] {\n const out: number[] = []\n let s = start\n let guard = 0\n while (s !== ENDOFCHAIN && s !== FREESECT && s < this.fat.length && guard++ < 10_000_000) {\n out.push(s)\n s = this.fat[s]\n }\n return out\n }\n /** Copy one big-FAT sector, clamped to the buffer (a truncated file yields short data, not a throw). */\n private sectorBytes(s: number): Uint8Array {\n const off = this.sectorOffset(s)\n const end = Math.min(this.buf.byteLength, off + this.sectorSize)\n const out = new Uint8Array(this.sectorSize)\n if (off < this.buf.byteLength) out.set(new Uint8Array(this.buf, off, end - off))\n return out\n }\n private readFatStream(start: number, size: number): Uint8Array {\n const ch = this.chain(start)\n const out = new Uint8Array(ch.length * this.sectorSize)\n for (let i = 0; i < ch.length; i++) out.set(this.sectorBytes(ch[i]), i * this.sectorSize)\n return out.subarray(0, size)\n }\n private readMiniStream(start: number, size: number): Uint8Array {\n // The stream occupies exactly `need` mini-sectors; read only that many even if the chain runs longer\n // (some writers leave trailing/looping links past the stream end — copying them would overflow `out`).\n const need = Math.ceil(size / this.miniSectorSize)\n const out = new Uint8Array(need * this.miniSectorSize)\n let s = start\n let i = 0\n while (s !== ENDOFCHAIN && s !== FREESECT && i < need) {\n const off = s * this.miniSectorSize\n out.set(this.miniStream.subarray(off, off + this.miniSectorSize), i * this.miniSectorSize)\n i++\n s = this.miniFat[s]\n }\n return out.subarray(0, size)\n }\n /** The decompressed bytes of one directory entry's stream (small streams live in the mini stream). */\n stream(e: CfbEntry): Uint8Array {\n if (e.size === 0) return new Uint8Array(0)\n if (e.type === 5 || e.size >= this.miniCutoff) return this.readFatStream(e.start, e.size)\n return this.readMiniStream(e.start, e.size)\n }\n}\n\n// ── MSI stream-name (un)mangling ────────────────────────────────────────────────────────────────────\n// MSI encodes table/stream names into a private Unicode range using a 64-char alphabet (Wine's\n// decode_streamname). A leading 0x4840 wide-char marks \"this is a database table\".\nfunction mime2utf(x: number): string {\n if (x < 10) return String.fromCharCode(x + 48) // 0-9\n if (x < 36) return String.fromCharCode(x - 10 + 65) // A-Z\n if (x < 62) return String.fromCharCode(x - 36 + 97) // a-z\n return x === 62 ? '.' : '_'\n}\nfunction decodeStreamName(raw: string): { name: string; isTable: boolean } {\n let out = ''\n let isTable = false\n for (let i = 0; i < raw.length; i++) {\n const ch = raw.charCodeAt(i)\n if (ch >= 0x3800 && ch < 0x4800) {\n const v = ch - 0x3800 // two 6-bit characters packed into one wide-char\n out += mime2utf(v & 0x3f) + mime2utf((v >> 6) & 0x3f)\n } else if (ch >= 0x4800 && ch < 0x4840) {\n out += mime2utf(ch - 0x4800) // a single character\n } else if (ch === 0x4840) {\n isTable = true // table marker (prefix); contributes nothing to the name\n } else {\n out += String.fromCharCode(ch) // literal (ASCII names, the \\x05 property-set prefix, …)\n }\n }\n return { name: out, isTable }\n}\n\n// ── SummaryInformation (OLE property set, MS-OLEPS) ─────────────────────────────────────────────────\ninterface PropVal {\n type: number\n value: string | number\n}\n\nfunction parsePropertySet(bytes: Uint8Array): { codepage: number; props: Map<number, PropVal> } {\n const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n // 28-byte set header + one 20-byte (FMTID + offset) section locator; the section offset sits at byte 44.\n const sectionOff = v.getUint32(44, true)\n const numProps = v.getUint32(sectionOff + 4, true)\n const rows: { pid: number; abs: number }[] = []\n for (let i = 0; i < numProps; i++) {\n const pid = v.getUint32(sectionOff + 8 + i * 8, true)\n const off = v.getUint32(sectionOff + 8 + i * 8 + 4, true)\n rows.push({ pid, abs: sectionOff + off })\n }\n // The codepage (PID 1) governs how the LPSTR string properties decode — read it first.\n let codepage = 1252\n const cpRow = rows.find((r) => r.pid === 1)\n if (cpRow && v.getUint32(cpRow.abs, true) === 2) {\n let cp = v.getInt16(cpRow.abs + 4, true)\n if (cp < 0) cp += 65536 // 65001 (UTF-8) overflows a signed 16-bit read\n codepage = cp\n }\n const dec = new TextDecoder(cpLabel(codepage))\n const props = new Map<number, PropVal>()\n for (const { pid, abs } of rows) {\n const type = v.getUint32(abs, true)\n if (type === 2) props.set(pid, { type, value: v.getInt16(abs + 4, true) })\n else if (type === 3) props.set(pid, { type, value: v.getInt32(abs + 4, true) })\n else if (type === 30) {\n // LPSTR: codepage bytes, NUL-terminated — cut at the first NUL (avoids a control-char regex).\n let raw = bytes.subarray(abs + 8, abs + 8 + v.getUint32(abs + 4, true))\n const nul = raw.indexOf(0)\n if (nul >= 0) raw = raw.subarray(0, nul)\n props.set(pid, { type, value: dec.decode(raw) })\n } else if (type === 64) {\n props.set(pid, {\n type,\n value: formatFiletime(v.getUint32(abs + 4, true), v.getUint32(abs + 8, true)),\n })\n }\n }\n return { codepage, props }\n}\n\nfunction buildSummary(props: Map<number, PropVal>): MsiMeta[] {\n const out: MsiMeta[] = []\n const push = (pid: number, label: string, fmt?: (v: string | number) => string): void => {\n const p = props.get(pid)\n if (p == null) return\n const value = fmt ? fmt(p.value) : String(p.value)\n if (value) out.push({ label, value })\n }\n push(3, '主题', String) // PID_SUBJECT — usually the product/package name\n push(4, '作者 / 厂商', String) // PID_AUTHOR\n push(6, '备注', String) // PID_COMMENTS — description\n push(2, '标题', String) // PID_TITLE\n push(5, '关键字', String) // PID_KEYWORDS\n push(7, '平台 / 语言', (v) => formatTemplate(String(v))) // PID_TEMPLATE\n push(9, '包代码', String) // PID_REVNUMBER — the package (revision) code GUID\n push(18, '制作工具', String) // PID_APPNAME — authoring application\n push(12, '创建时间', String) // PID_CREATE_DTM\n push(13, '上次保存', String) // PID_LASTSAVE_DTM\n push(14, '架构版本', (v) => formatSchema(Number(v))) // PID_PAGECOUNT — min installer version\n push(15, '源类型', (v) => formatWordCount(Number(v))) // PID_WORDCOUNT — source flags\n push(19, '安全', (v) => formatSecurity(Number(v))) // PID_SECURITY\n return out\n}\n\n// ── MSI string pool + Property table ────────────────────────────────────────────────────────────────\n// `_StringPool` is an array of 4-byte slots: [length: u16][refcount: u16] (length FIRST — validated against\n// the COM oracle). Slot 0 carries the codepage (+ a long-string-refs flag in the high bit). `_StringData` is\n// every string concatenated in index order; string i lives at the running offset, `length` bytes long.\nfunction buildStringTable(\n pool: Uint8Array,\n data: Uint8Array,\n fallbackCp: number,\n): { strings: string[]; longRefs: boolean } {\n const pdv = new DataView(pool.buffer, pool.byteOffset, pool.byteLength)\n const cpRaw = pdv.getUint16(0, true) | (pdv.getUint16(2, true) << 16)\n const longRefs = (cpRaw & 0x80000000) !== 0 // ⇒ 3-byte string references in table data\n let cp = cpRaw & 0x7fffffff\n if (cp === 0) cp = fallbackCp || 1252 // codepage 0 = system ANSI; fall back to the SummaryInfo codepage\n const dec = new TextDecoder(cpLabel(cp))\n const strings: string[] = ['']\n let off = 0\n const count = Math.floor(pool.length / 4)\n for (let i = 1; i < count; i++) {\n const len = pdv.getUint16(i * 4, true)\n if (len === 0) {\n strings.push('')\n continue\n }\n strings.push(dec.decode(data.subarray(off, off + len)))\n off += len\n }\n return { strings, longRefs }\n}\n\n// The Property table is two string columns (Property name, Value) stored column-major: all the name refs,\n// then all the value refs. Each ref is a 2-byte (or 3-byte, with long string refs) index into the pool.\nfunction decodePropertyTable(\n tbl: Uint8Array,\n st: { strings: string[]; longRefs: boolean },\n): MsiProperty[] {\n const refSize = st.longRefs ? 3 : 2\n const dv = new DataView(tbl.buffer, tbl.byteOffset, tbl.byteLength)\n const rows = Math.floor(tbl.length / (refSize * 2))\n const ref = (idx: number): number => {\n const o = idx * refSize\n let v = dv.getUint16(o, true)\n if (refSize === 3) v += dv.getUint8(o + 2) << 16\n return v\n }\n const out: MsiProperty[] = []\n for (let r = 0; r < rows; r++) {\n const name = st.strings[ref(r)] ?? ''\n const value = st.strings[ref(rows + r)] ?? ''\n if (name) out.push({ name, value })\n }\n return out\n}\n\n// ── orchestrator ────────────────────────────────────────────────────────────────────────────────────\nexport function parseMsi(buf: ArrayBuffer): MsiInfo {\n const cfb = new Cfb(buf) // throws NotMsiError if the bytes aren't a compound file\n const decoded = cfb.entries.map((e) => ({ e, ...decodeStreamName(e.name) }))\n const notes: string[] = []\n\n const tables = decoded\n .filter((d) => d.isTable)\n .map((d) => d.name)\n .sort((a, b) => a.localeCompare(b))\n\n // SummaryInformation (standard, near-universal in MSIs).\n let summary: MsiMeta[] = []\n let siCodepage = 0\n const siD = decoded.find((d) => d.name === '\u0005SummaryInformation')\n if (siD) {\n try {\n const ps = parsePropertySet(cfb.stream(siD.e))\n siCodepage = ps.codepage\n summary = buildSummary(ps.props)\n } catch (e) {\n notes.push('SummaryInformation 解析失败:' + errMsg(e))\n }\n }\n\n // Property table (best-effort: a malformed string pool never blanks the summary above).\n let properties: MsiProperty[] = []\n const sd = decoded.find((d) => d.name === '_StringData')\n const sp = decoded.find((d) => d.name === '_StringPool')\n const pr = decoded.find((d) => d.name === 'Property')\n if (sd && sp && pr) {\n try {\n const st = buildStringTable(cfb.stream(sp.e), cfb.stream(sd.e), siCodepage)\n properties = decodePropertyTable(cfb.stream(pr.e), st)\n } catch (e) {\n notes.push('Property 表解析失败:' + errMsg(e))\n }\n }\n\n const map = new Map(properties.map((p) => [p.name, p.value]))\n const langId = map.get('ProductLanguage')\n const info: MsiInfo = {\n productName: map.get('ProductName'),\n productVersion: map.get('ProductVersion'),\n manufacturer: map.get('Manufacturer'),\n productCode: map.get('ProductCode'),\n upgradeCode: map.get('UpgradeCode'),\n language: langId ? formatLanguage(langId) : undefined,\n properties,\n summary,\n tables,\n notes,\n }\n if (!properties.length && !summary.length) {\n notes.push('未找到 MSI 摘要信息或属性表(可能是别的复合文件,而非 MSI 数据库)')\n }\n return info\n}\n\n// ── formatting helpers ──────────────────────────────────────────────────────────────────────────────\nfunction errMsg(e: unknown): string {\n return e instanceof Error ? e.message : String(e)\n}\n\nfunction cpLabel(cp: number): string {\n switch (cp) {\n case 65001:\n return 'utf-8'\n case 1200:\n return 'utf-16le'\n case 1201:\n return 'utf-16be'\n case 932:\n return 'shift_jis'\n case 936:\n return 'gbk'\n case 949:\n return 'euc-kr'\n case 950:\n return 'big5'\n case 1250:\n return 'windows-1250'\n case 1251:\n return 'windows-1251'\n case 1253:\n return 'windows-1253'\n case 1254:\n return 'windows-1254'\n case 1255:\n return 'windows-1255'\n case 1256:\n return 'windows-1256'\n case 1257:\n return 'windows-1257'\n case 1258:\n return 'windows-1258'\n default:\n return 'windows-1252' // 1252 and the system-ANSI (0) fallback\n }\n}\n\nfunction formatFiletime(lo: number, hi: number): string {\n const ft = hi * 4294967296 + lo\n if (!ft) return ''\n const ms = ft / 10000 - 11644473600000\n const d = new Date(ms)\n if (Number.isNaN(d.getTime())) return ''\n const p = (n: number): string => String(n).padStart(2, '0')\n return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC`\n}\n\n// MSI \"Template\" = \"<platform>;<langid>[,<langid>…]\" e.g. \"x64;1033\". Surface the platform + language names.\nfunction formatTemplate(t: string): string {\n const [platform, langs] = t.split(';')\n const names = (langs ?? '')\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((id) => formatLanguage(id))\n const parts: string[] = []\n if (platform && platform.trim()) parts.push(platform.trim())\n if (names.length) parts.push(names.join(', '))\n return parts.join(' · ') || t\n}\n\n// PID_PAGECOUNT for an install package = the minimum Windows Installer version (e.g. 200 ⇒ 2.00, 500 ⇒ 5.00).\nfunction formatSchema(n: number): string {\n if (!n) return ''\n return `${Math.floor(n / 100)}.${String(n % 100).padStart(2, '0')}`\n}\n\n// PID_WORDCOUNT = source flags bitfield.\nfunction formatWordCount(n: number): string {\n const flags: string[] = []\n flags.push(n & 0x2 ? '压缩 (cabinet)' : '未压缩')\n if (n & 0x1) flags.push('短文件名')\n if (n & 0x4) flags.push('管理映像')\n if (n & 0x8) flags.push('从源运行')\n return flags.join(' · ')\n}\n\nfunction formatSecurity(n: number): string {\n switch (n) {\n case 0:\n return '无限制'\n case 1:\n return '建议只读'\n case 2:\n return '强制只读'\n case 4:\n return '已锁定'\n default:\n return String(n)\n }\n}\n\n// Common Windows LCIDs → Chinese language names (best-effort; unknown ids fall back to the raw number).\nconst LCID_NAMES: Record<number, string> = {\n 0: '语言中性',\n 1025: '阿拉伯语',\n 1028: '中文(繁体)',\n 1029: '捷克语',\n 1030: '丹麦语',\n 1031: '德语',\n 1032: '希腊语',\n 1033: '英语(美国)',\n 1034: '西班牙语',\n 1035: '芬兰语',\n 1036: '法语',\n 1037: '希伯来语',\n 1038: '匈牙利语',\n 1040: '意大利语',\n 1041: '日语',\n 1042: '韩语',\n 1043: '荷兰语',\n 1044: '挪威语',\n 1045: '波兰语',\n 1046: '葡萄牙语(巴西)',\n 1049: '俄语',\n 1051: '斯洛伐克语',\n 1053: '瑞典语',\n 1054: '泰语',\n 1055: '土耳其语',\n 1057: '印尼语',\n 1058: '乌克兰语',\n 1060: '斯洛文尼亚语',\n 1066: '越南语',\n 2052: '中文(简体)',\n 2057: '英语(英国)',\n 2070: '葡萄牙语',\n 3076: '中文(香港)',\n 3082: '西班牙语(西班牙)',\n 4100: '中文(新加坡)',\n}\nfunction formatLanguage(id: string): string {\n const n = Number(id)\n const name = LCID_NAMES[n]\n return name ? `${name} (${id})` : id\n}\n"],"mappings":"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"}
|
package/dist/lib/optionalDep.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optionalDep.js","names":[],"sources":["../../src/lib/optionalDep.ts"],"sourcesContent":["import { shallowRef, type ShallowRef } from 'vue'\n\nexport type OptionalDepStatus = 'loading' | 'ready' | 'missing' | 'error'\n\nexport interface OptionalDep<T> {\n status: ShallowRef<OptionalDepStatus>\n mod: ShallowRef<T | null>\n error: ShallowRef<unknown>\n}\n\n/**\n * Does this error mean \"the module isn't installed / couldn't be loaded\" (→ tell the user to install it),\n * as opposed to a real error thrown from INSIDE the module once it loaded? We match the bundler/browser\n * shapes for an unresolved dynamic import. Heuristic by necessity — err on \"missing\" only for these.\n *\n * Exported so viewers that `import()` their optional engine at runtime (geo/raster/psd/ebook) can decide,\n * in their own catch, whether to show the {@link MissingDependency} card vs a generic error.\n */\nexport function isMissingModule(e: unknown): boolean {\n const msg = e instanceof Error ? e.message : String(e)\n return /Failed to (fetch|resolve) dynamically imported module|Cannot find module|Importing a module script failed|ERR_MODULE_NOT_FOUND|Failed to load module/i.test(\n msg,\n )\n}\n\n/**\n * Load an OPTIONAL peer dependency at runtime (see docs/file-viewer-package-design.md §4). Because the dep is\n * `external` in the library build, it resolves from the CONSUMER's node_modules:\n * - present → `ready`, `mod` holds the module;\n * - absent → `missing` — the viewer renders a {@link MissingDependency} card telling the user to install it;\n * - throws → `error` (a genuine failure inside the module after it loaded).\n *\n * Usage: `const dep = loadOptionalDep(() => import('pdfjs-dist'))`, then branch on `dep.status.value`.\n */\nexport function loadOptionalDep<T = unknown>(importer: () => Promise<T>): OptionalDep<T> {\n const status = shallowRef<OptionalDepStatus>('loading')\n const mod = shallowRef<T | null>(null)\n const error = shallowRef<unknown>(null)\n importer().then(\n (m) => {\n mod.value = m\n status.value = 'ready'\n },\n (e) => {\n error.value = e\n status.value = isMissingModule(e) ? 'missing' : 'error'\n },\n )\n return { status, mod, error }\n}\n"],"mappings":";;AAkBA,SAAgB,EAAgB,GAAqB;CACnD,IAAM,IAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;CACrD,OAAO,wJAAwJ,KAC7J,CACF;AACF;AAWA,SAAgB,EAA6B,GAA4C;CACvF,IAAM,IAAS,EAA8B,SAAS,GAChD,IAAM,EAAqB,IAAI,GAC/B,IAAQ,EAAoB,IAAI;CAWtC,OAVA,EAAS,CAAC,CAAC,MACR,MAAM;EAEL,AADA,EAAI,QAAQ,GACZ,EAAO,QAAQ;CACjB,IACC,MAAM;EAEL,AADA,EAAM,QAAQ,GACd,EAAO,QAAQ,EAAgB,CAAC,IAAI,YAAY;CAClD,CACF,GACO;EAAE;EAAQ;EAAK;CAAM;AAC9B"}
|
package/dist/lib/pkcs12.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkcs12.js","names":[],"sources":["../../src/lib/pkcs12.ts"],"sourcesContent":["/**\n * Zero-dependency PKCS#12 (PFX) reader — for the keystore viewer (JksViewer.vue), so `.p12`/`.pfx` and the very\n * common `.jks`-that-is-actually-PKCS#12 (keytool defaults to PKCS12 since JDK 9) open with their certificates\n * and private keys instead of just an opaque blob.\n *\n * PKCS#12 (RFC 7292) is a PKCS#7 container whose bags are ALL encrypted (unlike JKS, where certs are plaintext),\n * so nothing shows without the password. This module parses the PFX / AuthenticatedSafe / SafeBag structure,\n * verifies the integrity MAC (which doubles as the password check), and decrypts the bags:\n * - legacy PBE1 (PKCS#12 KDF, SHA-1): `pbeWithSHA1And3-KeyTripleDES` (3DES, see lib/des.ts),\n * `pbeWithSHA1And{40,128}BitRC2` (RC2, see lib/rc2.ts), and the RC4 variants;\n * - modern PBES2: PBKDF2 (HMAC-SHA1/SHA256) + AES-CBC (see lib/aes.ts) or 3DES.\n * The MAC digest supports SHA-1 / SHA-256. Anything outside this set is reported (not decrypted) rather than\n * failing the whole file. READ-ONLY: no signature/trust verification. WebCrypto ships no 3DES/RC2, hence the\n * hand-rolled ciphers.\n */\n\nimport { decodeAsn1, oidToString, TAG, type Asn1Node } from './asn1'\nimport { describePrivateKey } from './x509'\nimport { Sha1 } from './sha1'\nimport { sha256 } from './sha256'\nimport { tripleDesCbcDecrypt } from './des'\nimport { rc2CbcDecrypt } from './rc2'\nimport { aesCbcDecrypt } from './aes'\n\n// ── OIDs ──────────────────────────────────────────────────────────────────────────────────────────\nconst PKCS7_DATA = '1.2.840.113549.1.7.1'\nconst PKCS7_SIGNED_DATA = '1.2.840.113549.1.7.2'\nconst PKCS7_ENCRYPTED_DATA = '1.2.840.113549.1.7.6'\nconst CERT_BAG = '1.2.840.113549.1.12.10.1.3'\nconst KEY_BAG = '1.2.840.113549.1.12.10.1.1'\nconst SHROUDED_KEY_BAG = '1.2.840.113549.1.12.10.1.2'\nconst X509_CERT = '1.2.840.113549.1.9.22.1'\nconst ATTR_FRIENDLY_NAME = '1.2.840.113549.1.9.20'\nconst ATTR_LOCAL_KEY_ID = '1.2.840.113549.1.9.21'\nconst PBES2 = '1.2.840.113549.1.5.13'\nconst PBKDF2 = '1.2.840.113549.1.5.12'\nconst DES_EDE3_CBC = '1.2.840.113549.3.7'\nconst HMAC_SHA1 = '1.2.840.113549.2.7'\nconst HMAC_SHA256 = '1.2.840.113549.2.9'\nconst OID_SHA1 = '1.3.14.3.2.26'\nconst OID_SHA256 = '2.16.840.1.101.3.4.2.1'\nconst AES_CBC: Record<string, number> = {\n '2.16.840.1.101.3.4.1.2': 16, // AES-128-CBC\n '2.16.840.1.101.3.4.1.22': 24, // AES-192-CBC\n '2.16.840.1.101.3.4.1.42': 32, // AES-256-CBC\n}\n// PBE1 (PKCS#12 KDF) schemes → key length, IV length (0 = stream cipher RC4), RC2 effective bits, cipher tag.\ninterface Pbe1 {\n keyLen: number\n ivLen: number\n effBits?: number\n cipher: '3des' | 'rc2' | 'rc4'\n}\nconst PBE1: Record<string, Pbe1> = {\n '1.2.840.113549.1.12.1.1': { keyLen: 16, ivLen: 0, cipher: 'rc4' },\n '1.2.840.113549.1.12.1.2': { keyLen: 5, ivLen: 0, cipher: 'rc4' },\n '1.2.840.113549.1.12.1.3': { keyLen: 24, ivLen: 8, cipher: '3des' },\n '1.2.840.113549.1.12.1.4': { keyLen: 16, ivLen: 8, cipher: '3des' },\n '1.2.840.113549.1.12.1.5': { keyLen: 16, ivLen: 8, effBits: 128, cipher: 'rc2' },\n '1.2.840.113549.1.12.1.6': { keyLen: 5, ivLen: 8, effBits: 40, cipher: 'rc2' },\n}\nconst PBE_NAMES: Record<string, string> = {\n '1.2.840.113549.1.12.1.1': 'PBE-SHA1-RC4-128',\n '1.2.840.113549.1.12.1.2': 'PBE-SHA1-RC4-40',\n '1.2.840.113549.1.12.1.3': 'PBE-SHA1-3DES',\n '1.2.840.113549.1.12.1.4': 'PBE-SHA1-2DES',\n '1.2.840.113549.1.12.1.5': 'PBE-SHA1-RC2-128',\n '1.2.840.113549.1.12.1.6': 'PBE-SHA1-RC2-40',\n [PBES2]: 'PBES2',\n}\n\n// ── small byte helpers ──────────────────────────────────────────────────────────────────────────\nfunction concat(...arrs: Uint8Array[]): Uint8Array {\n let n = 0\n for (const a of arrs) n += a.length\n const out = new Uint8Array(n)\n let p = 0\n for (const a of arrs) {\n out.set(a, p)\n p += a.length\n }\n return out\n}\nfunction bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false\n return true\n}\nfunction intValue(node: Asn1Node): number {\n let v = 0\n for (const b of node.content) v = v * 256 + b\n return v\n}\nfunction bytesToHex(b: Uint8Array): string {\n let s = ''\n for (const x of b) s += x.toString(16).padStart(2, '0')\n return s\n}\n\n// ── hashes / HMAC / KDFs ─────────────────────────────────────────────────────────────────────────\ninterface HashSpec {\n u: number // output bytes\n v: number // block bytes\n hash: (x: Uint8Array) => Uint8Array\n}\nconst SHA1: HashSpec = { u: 20, v: 64, hash: (x) => new Sha1().update(x).digest() }\nconst SHA256: HashSpec = { u: 32, v: 64, hash: (x) => sha256(x) }\n\nfunction hmac(spec: HashSpec, key: Uint8Array, msg: Uint8Array): Uint8Array {\n let k = key.length > spec.v ? spec.hash(key) : key\n const ipad = new Uint8Array(spec.v)\n const opad = new Uint8Array(spec.v)\n ipad.set(k)\n opad.set(k)\n for (let i = 0; i < spec.v; i++) {\n ipad[i] ^= 0x36\n opad[i] ^= 0x5c\n }\n return spec.hash(concat(opad, spec.hash(concat(ipad, msg))))\n}\n\nfunction pbkdf2(\n spec: HashSpec,\n pw: Uint8Array,\n salt: Uint8Array,\n iter: number,\n dkLen: number,\n): Uint8Array {\n const out = new Uint8Array(Math.ceil(dkLen / spec.u) * spec.u)\n const blocks = out.length / spec.u\n for (let i = 1; i <= blocks; i++) {\n const idx = new Uint8Array([(i >>> 24) & 0xff, (i >>> 16) & 0xff, (i >>> 8) & 0xff, i & 0xff])\n let u = hmac(spec, pw, concat(salt, idx))\n const t = u.slice()\n for (let it = 1; it < iter; it++) {\n u = hmac(spec, pw, u)\n for (let k = 0; k < spec.u; k++) t[k] ^= u[k]\n }\n out.set(t, (i - 1) * spec.u)\n }\n return out.subarray(0, dkLen)\n}\n\n/** Repeat `src` cyclically to a length that is a multiple of `v` (empty stays empty). */\nfunction fillToBlock(src: Uint8Array, v: number): Uint8Array {\n if (src.length === 0) return new Uint8Array(0)\n const len = v * Math.ceil(src.length / v)\n const out = new Uint8Array(len)\n for (let i = 0; i < len; i++) out[i] = src[i % src.length]\n return out\n}\n\n/** The PKCS#12 key-derivation function (RFC 7292 Appendix B.2). `id`: 1=key, 2=IV, 3=MAC. */\nfunction pkcs12Kdf(\n spec: HashSpec,\n pw: Uint8Array,\n salt: Uint8Array,\n iter: number,\n id: number,\n n: number,\n): Uint8Array {\n const { u, v } = spec\n const D = new Uint8Array(v).fill(id)\n const I = concat(fillToBlock(salt, v), fillToBlock(pw, v))\n const out = new Uint8Array(n)\n let pos = 0\n while (pos < n) {\n let A = spec.hash(concat(D, I))\n for (let it = 1; it < iter; it++) A = spec.hash(A)\n out.set(A.subarray(0, Math.min(u, n - pos)), pos)\n pos += u\n if (pos < n) {\n const B = fillToBlock(A, v) // v bytes (A is ≤ v)\n for (let b = 0; b < I.length; b += v) {\n let carry = 1\n for (let k = v - 1; k >= 0; k--) {\n const sum = I[b + k] + B[k] + carry\n I[b + k] = sum & 0xff\n carry = sum >> 8\n }\n }\n }\n }\n return out\n}\n\n/** Password → BMPString bytes (UTF-16BE + a trailing U+0000), the form the PKCS#12 KDF hashes. */\nfunction bmpPassword(password: string): Uint8Array {\n const out = new Uint8Array(password.length * 2 + 2)\n for (let i = 0; i < password.length; i++) {\n const c = password.charCodeAt(i)\n out[2 * i] = (c >>> 8) & 0xff\n out[2 * i + 1] = c & 0xff\n }\n return out // last two bytes stay 0 (the terminator)\n}\n\nfunction rc4(key: Uint8Array, data: Uint8Array): Uint8Array {\n const s = new Uint8Array(256)\n for (let i = 0; i < 256; i++) s[i] = i\n let j = 0\n for (let i = 0; i < 256; i++) {\n j = (j + s[i] + key[i % key.length]) & 0xff\n const t = s[i]\n s[i] = s[j]\n s[j] = t\n }\n const out = new Uint8Array(data.length)\n let a = 0\n let b = 0\n for (let k = 0; k < data.length; k++) {\n a = (a + 1) & 0xff\n b = (b + s[a]) & 0xff\n const t = s[a]\n s[a] = s[b]\n s[b] = t\n out[k] = data[k] ^ s[(s[a] + s[b]) & 0xff]\n }\n return out\n}\n\n/** Strip PKCS#7 padding for a given block size; null if the padding is invalid (⇒ wrong password). */\nfunction pkcs7Unpad(data: Uint8Array, blockSize: number): Uint8Array | null {\n if (data.length === 0 || data.length % blockSize !== 0) return null\n const p = data[data.length - 1]\n if (p < 1 || p > blockSize) return null\n for (let i = data.length - p; i < data.length; i++) if (data[i] !== p) return null\n return data.subarray(0, data.length - p)\n}\n\nclass Pkcs12Unsupported extends Error {}\n\nfunction oidOf(algId: Asn1Node): string {\n return oidToString(algId.children[0].content)\n}\n\n// ── ASN.1 navigation helpers ─────────────────────────────────────────────────────────────────────\n/** A `[n] EXPLICIT` wrapper's single inner value. */\nfunction explicit(node: Asn1Node): Asn1Node {\n return node.children.length ? node.children[0] : decodeAsn1(node.content)[0]\n}\n/** The bytes of an OCTET STRING (primitive → content; BER-constructed → concatenated segments). */\nfunction octetContent(node: Asn1Node): Uint8Array {\n if (!node.constructed) return node.content\n return concat(...node.children.map((c) => octetContent(c)))\n}\n\n/**\n * Decrypt one PBE-encrypted blob (an encryptedData content, or a shrouded key bag), stripping PKCS#7 padding.\n * Returns null on invalid padding (wrong password); throws {@link Pkcs12Unsupported} for an algorithm we don't\n * implement. Handles legacy PBE1 (PKCS#12 KDF) and modern PBES2 (PBKDF2 + AES/3DES).\n */\nfunction decryptPbe(algId: Asn1Node, ct: Uint8Array, password: string): Uint8Array | null {\n const oid = oidOf(algId)\n const params = algId.children[1]\n\n const pbe1 = PBE1[oid]\n if (pbe1) {\n const salt = params.children[0].content\n const iter = intValue(params.children[1])\n const bmp = bmpPassword(password)\n const key = pkcs12Kdf(SHA1, bmp, salt, iter, 1, pbe1.keyLen)\n if (pbe1.cipher === 'rc4') return rc4(key, ct) // stream cipher: no padding\n const iv = pkcs12Kdf(SHA1, bmp, salt, iter, 2, pbe1.ivLen)\n const raw =\n pbe1.cipher === '3des'\n ? tripleDesCbcDecrypt(key, iv, ct)\n : rc2CbcDecrypt(key, iv, ct, pbe1.effBits)\n return pkcs7Unpad(raw, 8)\n }\n\n if (oid === PBES2) {\n // PBES2-params ::= SEQUENCE { keyDerivationFunc AlgorithmIdentifier, encryptionScheme AlgorithmIdentifier }\n const kdf = params.children[0]\n if (oidOf(kdf) !== PBKDF2) throw new Pkcs12Unsupported('PBES2 KDF ' + oidOf(kdf))\n const kdfParams = kdf.children[1]\n const salt = kdfParams.children[0].content\n const iter = intValue(kdfParams.children[1])\n let keyLenHint = 0\n let prf: HashSpec = SHA1\n for (let i = 2; i < kdfParams.children.length; i++) {\n const c = kdfParams.children[i]\n if (c.tagNumber === TAG.INTEGER) keyLenHint = intValue(c)\n else if (c.tagNumber === TAG.SEQUENCE) {\n const p = oidToString(c.children[0].content)\n prf =\n p === HMAC_SHA256\n ? SHA256\n : p === HMAC_SHA1\n ? SHA1\n : (() => {\n throw new Pkcs12Unsupported('PBKDF2 PRF ' + p)\n })()\n }\n }\n const enc = params.children[1]\n const encOid = oidOf(enc)\n const encParams = enc.children[1]\n if (AES_CBC[encOid]) {\n const keyLen = AES_CBC[encOid]\n const iv = encParams.content\n const key = pbkdf2(prf, new TextEncoder().encode(password), salt, iter, keyLenHint || keyLen)\n return pkcs7Unpad(aesCbcDecrypt(key, iv, ct), 16)\n }\n if (encOid === DES_EDE3_CBC) {\n const iv = encParams.content\n const key = pbkdf2(prf, new TextEncoder().encode(password), salt, iter, keyLenHint || 24)\n return pkcs7Unpad(tripleDesCbcDecrypt(key, iv, ct), 8)\n }\n throw new Pkcs12Unsupported('PBES2 cipher ' + encOid)\n }\n\n throw new Pkcs12Unsupported(PBE_NAMES[oid] ?? oid)\n}\n\n// ── public API ────────────────────────────────────────────────────────────────────────────────────\nexport interface P12Cert {\n der: Uint8Array\n friendlyName?: string\n localKeyId?: string\n}\nexport interface P12Key {\n pkcs8: Uint8Array\n algorithm: string\n bits?: number\n curve?: string\n friendlyName?: string\n localKeyId?: string\n}\nexport interface Pkcs12Result {\n macPresent: boolean\n /** true = MAC verified (password correct); false→never returned (becomes wrong-password); null = no/unsupported MAC. */\n macVerified: boolean | null\n macAlgorithm?: string\n certs: P12Cert[]\n keys: P12Key[]\n /** Human names of the PBE algorithms encountered (for display). */\n encryptionAlgos: string[]\n /** Non-fatal notes (e.g. a bag encrypted with an unsupported algorithm was skipped). */\n warnings: string[]\n}\nexport type Pkcs12Open =\n | { ok: true; result: Pkcs12Result }\n | { ok: false; reason: 'wrong-password' | 'unsupported' | 'malformed'; detail?: string }\n\n/** Does this look like a PKCS#12 PFX (SEQUENCE { version 3, ContentInfo … })? Cheap structural sniff. */\nexport function looksLikePkcs12(bytes: Uint8Array): boolean {\n try {\n const pfx = decodeAsn1(bytes)[0]\n if (!pfx || pfx.tagNumber !== TAG.SEQUENCE) return false\n const ver = pfx.children[0]\n if (!ver || ver.tagNumber !== TAG.INTEGER || intValue(ver) !== 3) return false\n const t = oidToString(pfx.children[1].children[0].content)\n return t === PKCS7_DATA || t === PKCS7_SIGNED_DATA\n } catch {\n return false\n }\n}\n\nfunction digestName(oid: string): string {\n return oid === OID_SHA1 ? 'HMAC-SHA1' : oid === OID_SHA256 ? 'HMAC-SHA256' : oid\n}\n\nfunction parseAttrs(setNode: Asn1Node | undefined): { friendlyName?: string; localKeyId?: string } {\n const out: { friendlyName?: string; localKeyId?: string } = {}\n if (!setNode) return out\n for (const attr of setNode.children) {\n const id = oidToString(attr.children[0].content)\n const val = attr.children[1]?.children[0]\n if (!val) continue\n if (id === ATTR_FRIENDLY_NAME) {\n try {\n out.friendlyName = new TextDecoder('utf-16be').decode(val.content)\n } catch {\n /* leave unset */\n }\n } else if (id === ATTR_LOCAL_KEY_ID) {\n out.localKeyId = bytesToHex(val.content)\n }\n }\n return out\n}\n\n/**\n * Parse + decrypt a PKCS#12 keystore with `password`. Certificates and private keys come back decrypted; the\n * MAC (when SHA-1/SHA-256) is verified and doubles as the password check. Returns a structured error for a\n * wrong password / unsupported algorithm / malformed file rather than throwing.\n */\nexport function readPkcs12(buf: ArrayBuffer, password: string): Pkcs12Open {\n try {\n const bytes = new Uint8Array(buf)\n const pfx = decodeAsn1(bytes)[0]\n if (!pfx || pfx.tagNumber !== TAG.SEQUENCE) return { ok: false, reason: 'malformed' }\n const authSafe = pfx.children[1]\n if (!authSafe || oidToString(authSafe.children[0].content) !== PKCS7_DATA) {\n return {\n ok: false,\n reason: 'unsupported',\n detail: '仅支持口令完整性(pkcs7-data)的 PKCS#12',\n }\n }\n const authContent = octetContent(explicit(authSafe.children[1])) // AuthenticatedSafe DER = MAC message\n\n // MAC (integrity + password check)\n let macPresent = false\n let macVerified: boolean | null = null\n let macAlgorithm: string | undefined\n if (pfx.children.length >= 3) {\n macPresent = true\n const macData = pfx.children[2]\n const digestInfo = macData.children[0]\n const digestAlgOid = oidToString(digestInfo.children[0].children[0].content)\n const storedMac = digestInfo.children[1].content\n const macSalt = macData.children[1].content\n const macIter = macData.children[2] ? intValue(macData.children[2]) : 1\n macAlgorithm = digestName(digestAlgOid)\n const spec = digestAlgOid === OID_SHA1 ? SHA1 : digestAlgOid === OID_SHA256 ? SHA256 : null\n if (spec) {\n const key = pkcs12Kdf(spec, bmpPassword(password), macSalt, macIter, 3, spec.u)\n if (!bytesEqual(hmac(spec, key, authContent), storedMac)) {\n return { ok: false, reason: 'wrong-password' }\n }\n macVerified = true\n }\n }\n\n const certs: P12Cert[] = []\n const keys: P12Key[] = []\n const encryptionAlgos = new Set<string>()\n const warnings: string[] = []\n let sawWrongPw = false\n\n const processBags = (bagNodes: Asn1Node[]): void => {\n for (const bag of bagNodes) {\n const bagId = oidToString(bag.children[0].content)\n const inner = explicit(bag.children[1])\n const attrs = parseAttrs(bag.children[2])\n if (bagId === CERT_BAG) {\n if (oidToString(inner.children[0].content) === X509_CERT) {\n certs.push({ der: octetContent(explicit(inner.children[1])).slice(), ...attrs })\n }\n } else if (bagId === KEY_BAG) {\n const pkcs8 = nodeTlv(inner)\n keys.push({ pkcs8, ...describePrivateKey(pkcs8), ...attrs })\n } else if (bagId === SHROUDED_KEY_BAG) {\n const algId = inner.children[0]\n encryptionAlgos.add(PBE_NAMES[oidOf(algId)] ?? oidOf(algId))\n let pk: Uint8Array | null\n try {\n pk = decryptPbe(algId, inner.children[1].content, password)\n } catch (e) {\n warnings.push(`私钥加密方式不支持:${e instanceof Error ? e.message : String(e)}`)\n continue\n }\n if (!pk) {\n sawWrongPw = true\n continue\n }\n keys.push({ pkcs8: pk.slice(), ...describePrivateKey(pk), ...attrs })\n }\n }\n }\n\n const authSafeSeq = decodeAsn1(authContent)[0]\n for (const ci of authSafeSeq.children) {\n const ciType = oidToString(ci.children[0].content)\n if (ciType === PKCS7_DATA) {\n const safe = decodeAsn1(octetContent(explicit(ci.children[1])))[0]\n if (safe) processBags(safe.children)\n } else if (ciType === PKCS7_ENCRYPTED_DATA) {\n // EncryptedData ::= SEQUENCE { version, EncryptedContentInfo { contentType, algId, [0] IMPLICIT OCTET STRING } }\n const eci = explicit(ci.children[1]).children[1]\n const algId = eci.children[1]\n encryptionAlgos.add(PBE_NAMES[oidOf(algId)] ?? oidOf(algId))\n const ctNode = eci.children[2]\n const ct = ctNode.constructed ? octetContent(ctNode) : ctNode.content\n let pt: Uint8Array | null\n try {\n pt = decryptPbe(algId, ct, password)\n } catch (e) {\n warnings.push(`证书加密方式不支持:${e instanceof Error ? e.message : String(e)}`)\n continue\n }\n if (!pt) {\n sawWrongPw = true\n continue\n }\n const safe = decodeAsn1(pt)[0]\n if (safe) processBags(safe.children)\n }\n }\n\n // A decrypt that produced invalid padding, with nothing recovered, means the password was wrong (when the\n // MAC wasn't available as the definitive check).\n if (sawWrongPw && !certs.length && !keys.length && macVerified !== true) {\n return { ok: false, reason: 'wrong-password' }\n }\n return {\n ok: true,\n result: {\n macPresent,\n macVerified,\n macAlgorithm,\n certs,\n keys,\n encryptionAlgos: [...encryptionAlgos],\n warnings,\n },\n }\n } catch (e) {\n return { ok: false, reason: 'malformed', detail: e instanceof Error ? e.message : String(e) }\n }\n}\n\n/** Re-encode a decoded node as its full DER TLV (definite length, tag number < 31 — true for our bag values). */\nfunction nodeTlv(node: Asn1Node): Uint8Array {\n const first = (node.tagClass << 6) | (node.constructed ? 0x20 : 0) | node.tagNumber\n const n = node.content.length\n const lenBytes: number[] = n < 0x80 ? [n] : []\n if (n >= 0x80) {\n const tmp: number[] = []\n let x = n\n while (x > 0) {\n tmp.unshift(x & 0xff)\n x >>= 8\n }\n lenBytes.push(0x80 | tmp.length, ...tmp)\n }\n return concat(new Uint8Array([first, ...lenBytes]), node.content)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PresentationData } from '@aiden0z/pptx-renderer';
|
|
2
|
+
export interface PptxNavigationItem {
|
|
3
|
+
index: number;
|
|
4
|
+
title: string;
|
|
5
|
+
}
|
|
6
|
+
/** Derive one concise label per slide from the renderer's parsed text model. */
|
|
7
|
+
export declare function buildPptxNavigation(presentation: PresentationData): PptxNavigationItem[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildTextIndex as e } from "@aiden0z/pptx-renderer";
|
|
2
|
+
//#region src/lib/pptxNavigation.ts
|
|
3
|
+
function t(e) {
|
|
4
|
+
let t = e.replace(/\s+/g, " ").trim();
|
|
5
|
+
return t.length > 72 ? `${t.slice(0, 69)}…` : t;
|
|
6
|
+
}
|
|
7
|
+
function n(e) {
|
|
8
|
+
let n = t(e.text);
|
|
9
|
+
return !!n && !/^(?:slide\s*)?\d+$/i.test(n);
|
|
10
|
+
}
|
|
11
|
+
function r(r) {
|
|
12
|
+
let i = /* @__PURE__ */ new Map();
|
|
13
|
+
for (let t of e(r)) {
|
|
14
|
+
if (!n(t)) continue;
|
|
15
|
+
let e = i.get(t.slideIndex) ?? [];
|
|
16
|
+
e.push(t), i.set(t.slideIndex, e);
|
|
17
|
+
}
|
|
18
|
+
return r.slides.map((e, n) => {
|
|
19
|
+
let a = (i.get(n) ?? []).sort((e, t) => e.bounds.y - t.bounds.y || e.bounds.x - t.bounds.x);
|
|
20
|
+
return {
|
|
21
|
+
index: n,
|
|
22
|
+
title: t((a.find((e) => e.bounds.y < r.height * .4) ?? a[0])?.text ?? "") || `幻灯片 ${n + 1}`
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { r as buildPptxNavigation };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=pptxNavigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pptxNavigation.js","names":[],"sources":["../../src/lib/pptxNavigation.ts"],"sourcesContent":["import { buildTextIndex, type PresentationData, type TextIndexEntry } from '@aiden0z/pptx-renderer'\n\nexport interface PptxNavigationItem {\n index: number\n title: string\n}\n\nfunction cleanTitle(value: string): string {\n const text = value.replace(/\\s+/g, ' ').trim()\n return text.length > 72 ? `${text.slice(0, 69)}…` : text\n}\n\nfunction isUsefulTitle(entry: TextIndexEntry): boolean {\n const text = cleanTitle(entry.text)\n return !!text && !/^(?:slide\\s*)?\\d+$/i.test(text)\n}\n\n/** Derive one concise label per slide from the renderer's parsed text model. */\nexport function buildPptxNavigation(presentation: PresentationData): PptxNavigationItem[] {\n const bySlide = new Map<number, TextIndexEntry[]>()\n for (const entry of buildTextIndex(presentation)) {\n if (!isUsefulTitle(entry)) continue\n const group = bySlide.get(entry.slideIndex) ?? []\n group.push(entry)\n bySlide.set(entry.slideIndex, group)\n }\n\n return presentation.slides.map((_slide, index) => {\n const candidates = (bySlide.get(index) ?? []).sort(\n (a, b) => a.bounds.y - b.bounds.y || a.bounds.x - b.bounds.x,\n )\n const upperTitle = candidates.find((entry) => entry.bounds.y < presentation.height * 0.4)\n const title = cleanTitle((upperTitle ?? candidates[0])?.text ?? '')\n return { index, title: title || `幻灯片 ${index + 1}` }\n })\n}\n"],"mappings":";;AAOA,SAAS,EAAW,GAAuB;CACzC,IAAM,IAAO,EAAM,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;CAC7C,OAAO,EAAK,SAAS,KAAK,GAAG,EAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACtD;AAEA,SAAS,EAAc,GAAgC;CACrD,IAAM,IAAO,EAAW,EAAM,IAAI;CAClC,OAAO,CAAC,CAAC,KAAQ,CAAC,sBAAsB,KAAK,CAAI;AACnD;AAGA,SAAgB,EAAoB,GAAsD;CACxF,IAAM,oBAAU,IAAI,IAA8B;CAClD,KAAK,IAAM,KAAS,EAAe,CAAY,GAAG;EAChD,IAAI,CAAC,EAAc,CAAK,GAAG;EAC3B,IAAM,IAAQ,EAAQ,IAAI,EAAM,UAAU,KAAK,CAAC;EAEhD,AADA,EAAM,KAAK,CAAK,GAChB,EAAQ,IAAI,EAAM,YAAY,CAAK;CACrC;CAEA,OAAO,EAAa,OAAO,KAAK,GAAQ,MAAU;EAChD,IAAM,KAAc,EAAQ,IAAI,CAAK,KAAK,CAAC,EAAA,CAAG,MAC3C,GAAG,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,EAAE,OAAO,IAAI,EAAE,OAAO,CAC7D;EAGA,OAAO;GAAE;GAAO,OADF,GADK,EAAW,MAAM,MAAU,EAAM,OAAO,IAAI,EAAa,SAAS,EAC3D,KAAc,EAAW,GAAA,EAAK,QAAQ,EACzC,KAAS,OAAO,IAAQ;EAAI;CACrD,CAAC;AACH"}
|
package/dist/lib/qmc.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qmc.js","names":[],"sources":["../../src/lib/qmc.ts"],"sourcesContent":["/**\n * QQ 音乐加密音频(QMCv2)的纯前端解密器 —— 零依赖,照搬 unlock-music / parakeet-crypto 的算法。\n *\n * QQ 音乐客户端下载的「会员歌曲」是被加密的,扩展名形如 `.mflac`(FLAC)、`.mgg`(OGG/Vorbis)、\n * `.qmcflac`/`.qmc0` 等。文件主体是用一段「文件密钥」做的逐字节流加密,密钥本身(EKey)以 base64\n * 形式追加在文件末尾。解出原始密钥后即可还原成标准的 FLAC/OGG/MP3 字节,丢给 <audio> 直接播放。\n *\n * 整条链路(均在浏览器内完成,本函数纯计算、不联网):\n * 1. {@link parseFooter} 解析文件尾,取出 base64 的 EKey 与尾部长度(PC v1 / Android QTag 两种容器)。\n * 2. {@link decryptEKey} 还原文件密钥:base64 解码 → 可选的 EncV2 两轮 TEA → EncV1(8 字节明文头 +\n * 其余走 TC-TEA/CBC 解密,TEA 密钥由「simple key」与密钥头交织而成)。\n * 3. 按密钥长度选择流密码:>300 字节用 {@link rc4Decrypt}(分段 RC4 + 跳表),否则用 {@link mapDecrypt}\n * (把密钥摊成 128 字节查找表逐字节异或)。\n * 4. 去掉尾部,得到明文音频,再用文件头 magic 判定真实格式(fLaC / OggS / ID3·MP3 / ftyp·M4A / WAV)。\n *\n * 这套算法已用本仓库的真实样例逐字节验证:`.mflac` → 合法 FLAC、`.mgg` → 合法 OGG/Vorbis(ffprobe 可解)。\n * 解密是 XOR 流密码,与「加密」同构;这里只做解密。详见 https://git.unlock-music.dev/um/web 。\n */\n\n// ───────────────────────── TC-TEA(CBC,QQ 私有填充)─────────────────────────\n// 标准 TEA 分组(8 字节、大端、16 轮),外层是腾讯私有的 CBC 变体 + 「PadLen|Padding|Salt|Body|Zero」填充。\nconst TEA_ROUNDS = 16\nconst TEA_DELTA = 0x9e3779b9\nconst u32 = (x: number): number => x >>> 0\nconst add32 = (a: number, b: number): number => u32(a + b)\nconst sub32 = (a: number, b: number): number => u32(a - b)\n\nfunction readU32BE(b: Uint8Array, o: number): number {\n return u32((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3])\n}\nfunction writeU32BE(b: Uint8Array, o: number, v: number): void {\n b[o] = (v >>> 24) & 0xff\n b[o + 1] = (v >>> 16) & 0xff\n b[o + 2] = (v >>> 8) & 0xff\n b[o + 3] = v & 0xff\n}\n// ((v<<4)+k1) ^ (sum+v) ^ ((v>>5)+k2) —— TEA 单轮的一半,加减都按 u32 回绕。\nfunction teaRound(v: number, sum: number, k1: number, k2: number): number {\n return u32(add32(u32(v << 4), k1) ^ add32(sum, v) ^ add32(v >>> 5, k2))\n}\nfunction teaEcbDecrypt(block: Uint8Array, off: number, k: [number, number, number, number]): void {\n let y = readU32BE(block, off)\n let z = readU32BE(block, off + 4)\n let sum = u32(TEA_DELTA * TEA_ROUNDS)\n for (let i = 0; i < TEA_ROUNDS; i++) {\n z = sub32(z, teaRound(y, sum, k[2], k[3]))\n y = sub32(y, teaRound(z, sum, k[0], k[1]))\n sum = sub32(sum, TEA_DELTA)\n }\n writeU32BE(block, off, y)\n writeU32BE(block, off + 4, z)\n}\n\nconst TEA_SALT_LEN = 2\nconst TEA_ZERO_LEN = 7\nconst TEA_FIXED_PADDING = 1 + TEA_SALT_LEN + TEA_ZERO_LEN\n\n/**\n * TC-TEA/CBC 解密。`key` 必须是 16 字节(4×u32 大端)。失败(长度不合法 / 尾部校验位非零)返回 null。\n * 明文布局:`PadLen(1) | Padding(0-7) | Salt(2) | Body | Zero(7)`,PadLen 取自首字节低 3 位。\n */\nfunction teaCbcDecrypt(encrypted: Uint8Array, key: Uint8Array): Uint8Array | null {\n if (key.length < 16) return null\n const k: [number, number, number, number] = [\n readU32BE(key, 0),\n readU32BE(key, 4),\n readU32BE(key, 8),\n readU32BE(key, 12),\n ]\n const len = encrypted.length\n if (len < TEA_FIXED_PADDING || len % 8 !== 0) return null\n const buf = encrypted.slice()\n teaEcbDecrypt(buf, 0, k)\n for (let i = 8; i < len; i += 8) {\n for (let j = 0; j < 8; j++) buf[i + j] ^= buf[i - 8 + j] // XOR 前一明文块\n teaEcbDecrypt(buf, i, k)\n }\n for (let i = 8; i < len; i++) buf[i] ^= encrypted[i - 8] // 收尾:XOR 前一密文块\n const padSize = buf[0] & 0x7\n const start = 1 + padSize + TEA_SALT_LEN\n const end = len - TEA_ZERO_LEN\n if (start > end) return null\n for (let i = end; i < len; i++) if (buf[i] !== 0) return null // 末 7 字节必须全 0\n return buf.slice(start, end)\n}\n\n// ───────────────────────── EKey 解密 ─────────────────────────\n// EncV2 前缀:base64(\"QQMusic EncV2,Key:\")。命中则先用两把固定 TEA 密钥各解一轮,再走 EncV1。\nconst EKEY_V2_PREFIX = 'UVFNdXNpYyBFbmNWMixLZXk6'\nconst ENC_V2_KEY_1 = strBytes('386ZJY!@#*$%^&)(')\nconst ENC_V2_KEY_2 = strBytes('**#!(#$%&^a1cZ,T')\nconst EKEY_SEED = 106 // simple key 的起始种子(QMC 固定值)\nconst MAX_EKEY_LEN = 0x500\n\nfunction strBytes(s: string): Uint8Array {\n const out = new Uint8Array(s.length)\n for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 0xff\n return out\n}\n// Tolerant base64 → bytes: drop anything outside the base64 alphabet (whitespace, the NUL padding the inner\n// EncV2 key carries, …) and re-pad, so a slightly-dirty string decodes instead of throwing in atob().\nfunction base64ToBytes(s: string): Uint8Array {\n const clean = s.replace(/[^A-Za-z0-9+/]/g, '')\n const padded = clean + '='.repeat((4 - (clean.length % 4)) % 4)\n const bin = atob(padded)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out\n}\n\n// simple key:8 字节,第 i 个 = ⌊|tan(106 + i*0.1)| * 100⌋(用 double,与 C++ 原版一致)。\nfunction makeSimpleKey(): Uint8Array {\n const out = new Uint8Array(8)\n for (let i = 0; i < 8; i++)\n out[i] = Math.trunc(Math.abs(Math.tan(EKEY_SEED + i * 0.1)) * 100) & 0xff\n return out\n}\nconst SIMPLE_KEY = makeSimpleKey()\n\n// EncV1:前 8 字节是明文密钥头,TEA 密钥由 simple key 与该头逐字节交织(simple[0],head[0],simple[1],…),\n// 其余字节走 TC-TEA/CBC 解密,结果 = 头 + 明文。\nfunction ekeyDecryptV1(raw: Uint8Array): Uint8Array | null {\n if (raw.length < 8) return null\n const header = raw.slice(0, 8)\n const cipher = raw.slice(8)\n const teaKey = new Uint8Array(16)\n for (let i = 0; i < 8; i++) {\n teaKey[i * 2] = SIMPLE_KEY[i]\n teaKey[i * 2 + 1] = header[i]\n }\n const plain = teaCbcDecrypt(cipher, teaKey)\n if (!plain) return null\n const out = new Uint8Array(8 + plain.length)\n out.set(header, 0)\n out.set(plain, 8)\n return out\n}\n\n/** 把文件尾的 base64 EKey 字符串还原成文件密钥;失败返回 null。 */\nexport function decryptEKey(ekey: string): Uint8Array | null {\n const trimmed = ekey.replace(/\\s+/g, '')\n if (trimmed.startsWith(EKEY_V2_PREFIX)) {\n // EncV2:base64 前缀对应 18 字节明文前缀(24 个 base64 字符正好 3 的倍数,分段解码等价),故直接切串。\n let k: Uint8Array | null = base64ToBytes(trimmed.slice(EKEY_V2_PREFIX.length))\n k = teaCbcDecrypt(k, ENC_V2_KEY_1)\n if (!k) return null\n k = teaCbcDecrypt(k, ENC_V2_KEY_2)\n if (!k) return null\n // 结果是 EncV1 ekey 的 base64 文本(可能带尾部 0,base64ToBytes 会自动忽略非字母表字符)。\n let s = ''\n for (const b of k) s += String.fromCharCode(b)\n return ekeyDecryptV1(base64ToBytes(s))\n }\n return ekeyDecryptV1(base64ToBytes(trimmed))\n}\n\n// ───────────────────────── 文件尾解析 ─────────────────────────\nexport interface QmcFooter {\n /** base64 的 EKey 字符串。 */\n ekey: string\n /** 需要从文件末尾去掉的字节数(密钥块 + 长度/标记)。 */\n tailLen: number\n}\n\nconst td = new TextDecoder('latin1')\n\n/**\n * 解析 QMC 文件尾,取出 EKey。支持两种容器:\n * - **PC v1**:末 4 字节小端 = EKey 长度,其前即 EKey(去尾部 0)。\n * - **Android QTag**:末 4 字节为 ASCII \"QTag\",再往前 4 字节大端为 payload 长度,payload 为\n * `ekey,resourceId,version` 逗号分隔。\n * 解析不出(或像无嵌入密钥的 STag/musicex)返回 null。\n */\nexport function parseFooter(buf: Uint8Array): QmcFooter | null {\n if (buf.length < 8) return null\n // QTag\n if (td.decode(buf.subarray(buf.length - 4)) === 'QTag') {\n const payloadLen = readU32BE(buf, buf.length - 8)\n const tailLen = payloadLen + 8\n if (tailLen > buf.length) return null\n const payload = td.decode(buf.subarray(buf.length - tailLen, buf.length - 8))\n const ekey = payload.split(',')[0]\n return ekey ? { ekey, tailLen } : null\n }\n // PC v1\n const keyLen = u32(\n buf[buf.length - 4] |\n (buf[buf.length - 3] << 8) |\n (buf[buf.length - 2] << 16) |\n (buf[buf.length - 1] << 24),\n )\n if (keyLen === 0 || keyLen > MAX_EKEY_LEN || keyLen + 4 > buf.length) return null\n let end = buf.length - 4\n let start = end - keyLen\n while (end > start && buf[end - 1] === 0) end-- // 去尾部 0\n const ekey = td.decode(buf.subarray(start, end))\n // 粗校验:EKey 应为 base64 字符集\n if (!/^[A-Za-z0-9+/=]+$/.test(ekey)) return null\n return { ekey, tailLen: keyLen + 4 }\n}\n\n// ───────────────────────── Map 流密码(短密钥)─────────────────────────\n// 把文件密钥摊成 128 字节查找表,再逐字节 `byte ^= table[ f(offset) ]`。\nfunction qmc2KeyToTable(fileKey: Uint8Array): Uint8Array {\n const table = new Uint8Array(128)\n const n = fileKey.length\n if (n === 0) return table\n const longKey = new Uint8Array(n)\n let shift = 4\n for (let i = 0; i < n; i++) {\n const v = fileKey[i]\n longKey[i] = (((v << shift) & 0xff) | (v >>> shift)) & 0xff\n shift = (shift + 1) & 0x7\n }\n for (let i = 0; i < 128; i++) table[i] = longKey[(i * i + 0x1162e) % n]\n return table\n}\nfunction mapDecrypt(audio: Uint8Array, fileKey: Uint8Array): void {\n const table = qmc2KeyToTable(fileKey)\n for (let i = 0; i < audio.length; i++) {\n let idx = i\n if (idx > 0x7fff) idx %= 0x7fff\n audio[i] ^= table[idx & 0x7f]\n }\n}\n\n// ───────────────────────── RC4 流密码(长密钥)─────────────────────────\nconst RC4_FIRST_SEG = 0x80\nconst RC4_OTHER_SEG = 0x1400\nconst RC4_KEY_STREAM_LEN = 0x1ff + RC4_OTHER_SEG\n\nfunction rc4KeyStream(key: Uint8Array, outLen: number): Uint8Array {\n const n = key.length\n const s = new Uint8Array(n)\n for (let i = 0; i < n; i++) s[i] = i & 0xff\n let j = 0\n for (let i = 0; i < n; i++) {\n j = (j + s[i] + key[i % n]) % n\n const t = s[i]\n s[i] = s[j]\n s[j] = t\n }\n const out = new Uint8Array(outLen)\n let i = 0\n j = 0\n for (let k = 0; k < outLen; k++) {\n i = (i + 1) % n\n j = (j + s[i]) % n\n const t = s[i]\n s[i] = s[j]\n s[j] = t\n out[k] = s[(s[i] + s[j]) % n]\n }\n return out\n}\nfunction calcKeyHash(key: Uint8Array): number {\n let hash = 1\n for (let i = 0; i < key.length; i++) {\n const v = key[i]\n if (v === 0) continue\n const next = Math.imul(hash, v) >>> 0\n if (next <= hash >>> 0) break\n hash = next\n }\n return hash >>> 0\n}\nfunction rc4Decrypt(audio: Uint8Array, key: Uint8Array): void {\n const keyLen = key.length\n const keyHash = calcKeyHash(key)\n const ks = rc4KeyStream(key, RC4_KEY_STREAM_LEN)\n // 分段密钥:⌊keyHash / (seed*(id+1)) * 100⌋(double)。\n const segKey = (id: number, seed: number): number =>\n seed === 0 ? 0 : Math.floor((keyHash / (seed * (id + 1))) * 100)\n\n let offset = 0\n let pos = 0\n const total = audio.length\n // 段 1(前 0x80 字节):每字节用按位置算出的密钥下标异或。\n if (offset < RC4_FIRST_SEG) {\n const len = Math.min(total - pos, RC4_FIRST_SEG - offset)\n let i = offset\n for (let p = 0; p < len; p++) {\n audio[pos + p] ^= key[segKey(i, key[i % keyLen]) % keyLen]\n i++\n }\n offset += len\n pos += len\n }\n // 对齐到 0x1400 段边界的零头。\n if (offset % RC4_OTHER_SEG !== 0) {\n const len = Math.min(total - pos, RC4_OTHER_SEG - (offset % RC4_OTHER_SEG))\n encodeOther(audio, pos, len, offset)\n offset += len\n pos += len\n }\n // 其余按 0x1400 整段处理。\n while (pos < total) {\n const len = Math.min(total - pos, RC4_OTHER_SEG)\n encodeOther(audio, pos, len, offset)\n offset += len\n pos += len\n }\n\n function encodeOther(buf: Uint8Array, start: number, len: number, off: number): void {\n const segIdx = Math.floor(off / RC4_OTHER_SEG)\n const segOff = off % RC4_OTHER_SEG\n const sk = segKey(segIdx, key[segIdx % keyLen])\n const ksBase = (sk % 512) + segOff // sk & 0x1FF\n for (let p = 0; p < len; p++) buf[start + p] ^= ks[ksBase + p]\n }\n}\n\n// ───────────────────────── 顶层 ─────────────────────────\nexport interface QmcDecrypted {\n data: Uint8Array\n /** 探测出的真实格式:flac / ogg / mp3 / m4a / wav / bin。 */\n format: string\n /** 对应的 MIME,用于 <audio> 的 Blob。 */\n mime: string\n /** 建议的下载扩展名(不含点)。 */\n ext: string\n}\n\nfunction detectFormat(b: Uint8Array): { format: string; mime: string; ext: string } {\n const ascii = (o: number, s: string): boolean => {\n for (let i = 0; i < s.length; i++) if (b[o + i] !== s.charCodeAt(i)) return false\n return true\n }\n if (ascii(0, 'fLaC')) return { format: 'flac', mime: 'audio/flac', ext: 'flac' }\n if (ascii(0, 'OggS')) return { format: 'ogg', mime: 'audio/ogg', ext: 'ogg' }\n if (ascii(0, 'ID3') || (b[0] === 0xff && (b[1] & 0xe0) === 0xe0))\n return { format: 'mp3', mime: 'audio/mpeg', ext: 'mp3' }\n if (b.length > 11 && ascii(4, 'ftyp')) return { format: 'm4a', mime: 'audio/mp4', ext: 'm4a' }\n if (ascii(0, 'RIFF') && ascii(8, 'WAVE')) return { format: 'wav', mime: 'audio/wav', ext: 'wav' }\n return { format: 'bin', mime: 'application/octet-stream', ext: 'bin' }\n}\n\nexport class QmcError extends Error {}\n\n/**\n * 解密一个 QMC 加密音频。`input` 是整个文件字节。成功返回明文音频 + 探测出的格式信息;\n * 解析/解密失败抛 {@link QmcError}(调用方据此给出友好提示)。\n */\nexport function decryptQmc(input: ArrayBuffer | Uint8Array): QmcDecrypted {\n const buf = input instanceof Uint8Array ? input : new Uint8Array(input)\n const footer = parseFooter(buf)\n if (!footer)\n throw new QmcError(\n '未能解析文件尾部的密钥(可能不是 QQ 音乐加密文件,或为需要外部密钥的 STag 格式)',\n )\n const fileKey = decryptEKey(footer.ekey)\n if (!fileKey || fileKey.length === 0) throw new QmcError('EKey 解密失败')\n const audio = buf.slice(0, buf.length - footer.tailLen)\n if (fileKey.length > 300) rc4Decrypt(audio, fileKey)\n else mapDecrypt(audio, fileKey)\n const fmt = detectFormat(audio)\n if (fmt.format === 'bin') throw new QmcError('解密结果不是可识别的音频格式(密钥或格式不支持)')\n return { data: audio, ...fmt }\n}\n"],"mappings":";AAqBA,IAAM,IAAa,IACb,IAAY,YACZ,KAAO,MAAsB,MAAM,GACnC,KAAS,GAAW,MAAsB,EAAI,IAAI,CAAC,GACnD,KAAS,GAAW,MAAsB,EAAI,IAAI,CAAC;AAEzD,SAAS,EAAU,GAAe,GAAmB;CACnD,OAAO,EAAK,EAAE,MAAM,KAAO,EAAE,IAAI,MAAM,KAAO,EAAE,IAAI,MAAM,IAAK,EAAE,IAAI,EAAE;AACzE;AACA,SAAS,EAAW,GAAe,GAAW,GAAiB;CAI7D,AAHA,EAAE,KAAM,MAAM,KAAM,KACpB,EAAE,IAAI,KAAM,MAAM,KAAM,KACxB,EAAE,IAAI,KAAM,MAAM,IAAK,KACvB,EAAE,IAAI,KAAK,IAAI;AACjB;AAEA,SAAS,EAAS,GAAW,GAAa,GAAY,GAAoB;CACxE,OAAO,EAAI,EAAM,EAAI,KAAK,CAAC,GAAG,CAAE,IAAI,EAAM,GAAK,CAAC,IAAI,EAAM,MAAM,GAAG,CAAE,CAAC;AACxE;AACA,SAAS,EAAc,GAAmB,GAAa,GAA2C;CAChG,IAAI,IAAI,EAAU,GAAO,CAAG,GACxB,IAAI,EAAU,GAAO,IAAM,CAAC,GAC5B,IAAM,EAAI,IAAY,CAAU;CACpC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAY,KAG9B,AAFA,IAAI,EAAM,GAAG,EAAS,GAAG,GAAK,EAAE,IAAI,EAAE,EAAE,CAAC,GACzC,IAAI,EAAM,GAAG,EAAS,GAAG,GAAK,EAAE,IAAI,EAAE,EAAE,CAAC,GACzC,IAAM,EAAM,GAAK,CAAS;CAG5B,AADA,EAAW,GAAO,GAAK,CAAC,GACxB,EAAW,GAAO,IAAM,GAAG,CAAC;AAC9B;AAEA,IAAM,IAAe,GACf,IAAe,GACf,IAAoB;AAM1B,SAAS,EAAc,GAAuB,GAAoC;CAChF,IAAI,EAAI,SAAS,IAAI,OAAO;CAC5B,IAAM,IAAsC;EAC1C,EAAU,GAAK,CAAC;EAChB,EAAU,GAAK,CAAC;EAChB,EAAU,GAAK,CAAC;EAChB,EAAU,GAAK,EAAE;CACnB,GACM,IAAM,EAAU;CACtB,IAAI,IAAM,KAAqB,IAAM,KAAM,GAAG,OAAO;CACrD,IAAM,IAAM,EAAU,MAAM;CAC5B,EAAc,GAAK,GAAG,CAAC;CACvB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK,GAAG;EAC/B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,EAAI,IAAI,MAAM,EAAI,IAAI,IAAI;EACtD,EAAc,GAAK,GAAG,CAAC;CACzB;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK,EAAI,MAAM,EAAU,IAAI;CAEtD,IAAM,IAAQ,KADE,EAAI,KAAK,KACG,GACtB,IAAM,IAAM;CAClB,IAAI,IAAQ,GAAK,OAAO;CACxB,KAAK,IAAI,IAAI,GAAK,IAAI,GAAK,KAAK,IAAI,EAAI,OAAO,GAAG,OAAO;CACzD,OAAO,EAAI,MAAM,GAAO,CAAG;AAC7B;AAIA,IAAM,IAAiB,4BACjB,IAAe,EAAS,kBAAkB,GAC1C,IAAe,EAAS,kBAAkB,GAC1C,IAAY,KACZ,IAAe;AAErB,SAAS,EAAS,GAAuB;CACvC,IAAM,IAAM,IAAI,WAAW,EAAE,MAAM;CACnC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,EAAI,KAAK,EAAE,WAAW,CAAC,IAAI;CAC9D,OAAO;AACT;AAGA,SAAS,EAAc,GAAuB;CAC5C,IAAM,IAAQ,EAAE,QAAQ,mBAAmB,EAAE,GACvC,IAAS,IAAQ,IAAI,QAAQ,IAAK,EAAM,SAAS,KAAM,CAAC,GACxD,IAAM,KAAK,CAAM,GACjB,IAAM,IAAI,WAAW,EAAI,MAAM;CACrC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK,EAAI,KAAK,EAAI,WAAW,CAAC;CAC9D,OAAO;AACT;AAGA,SAAS,IAA4B;CACnC,IAAM,IAAM,IAAI,WAAW,CAAC;CAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KACrB,EAAI,KAAK,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,IAAY,IAAI,EAAG,CAAC,IAAI,GAAG,IAAI;CACvE,OAAO;AACT;AACA,IAAM,IAAa,EAAc;AAIjC,SAAS,EAAc,GAAoC;CACzD,IAAI,EAAI,SAAS,GAAG,OAAO;CAC3B,IAAM,IAAS,EAAI,MAAM,GAAG,CAAC,GACvB,IAAS,EAAI,MAAM,CAAC,GACpB,IAAS,IAAI,WAAW,EAAE;CAChC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAErB,AADA,EAAO,IAAI,KAAK,EAAW,IAC3B,EAAO,IAAI,IAAI,KAAK,EAAO;CAE7B,IAAM,IAAQ,EAAc,GAAQ,CAAM;CAC1C,IAAI,CAAC,GAAO,OAAO;CACnB,IAAM,IAAM,IAAI,WAAW,IAAI,EAAM,MAAM;CAG3C,OAFA,EAAI,IAAI,GAAQ,CAAC,GACjB,EAAI,IAAI,GAAO,CAAC,GACT;AACT;AAGA,SAAgB,EAAY,GAAiC;CAC3D,IAAM,IAAU,EAAK,QAAQ,QAAQ,EAAE;CACvC,IAAI,EAAQ,WAAW,CAAc,GAAG;EAEtC,IAAI,IAAuB,EAAc,EAAQ,MAAM,EAAqB,CAAC;EAI7E,IAHA,IAAI,EAAc,GAAG,CAAY,GAC7B,CAAC,MACL,IAAI,EAAc,GAAG,CAAY,GAC7B,CAAC,IAAG,OAAO;EAEf,IAAI,IAAI;EACR,KAAK,IAAM,KAAK,GAAG,KAAK,OAAO,aAAa,CAAC;EAC7C,OAAO,EAAc,EAAc,CAAC,CAAC;CACvC;CACA,OAAO,EAAc,EAAc,CAAO,CAAC;AAC7C;AAUA,IAAM,IAAK,IAAI,YAAY,QAAQ;AASnC,SAAgB,EAAY,GAAmC;CAC7D,IAAI,EAAI,SAAS,GAAG,OAAO;CAE3B,IAAI,EAAG,OAAO,EAAI,SAAS,EAAI,SAAS,CAAC,CAAC,MAAM,QAAQ;EAEtD,IAAM,IADa,EAAU,GAAK,EAAI,SAAS,CAC/B,IAAa;EAC7B,IAAI,IAAU,EAAI,QAAQ,OAAO;EAEjC,IAAM,IADU,EAAG,OAAO,EAAI,SAAS,EAAI,SAAS,GAAS,EAAI,SAAS,CAAC,CAC9D,CAAA,CAAQ,MAAM,GAAG,CAAC,CAAC;EAChC,OAAO,IAAO;GAAE;GAAM;EAAQ,IAAI;CACpC;CAEA,IAAM,IAAS,EACb,EAAI,EAAI,SAAS,KACd,EAAI,EAAI,SAAS,MAAM,IACvB,EAAI,EAAI,SAAS,MAAM,KACvB,EAAI,EAAI,SAAS,MAAM,EAC5B;CACA,IAAI,MAAW,KAAK,IAAS,KAAgB,IAAS,IAAI,EAAI,QAAQ,OAAO;CAC7E,IAAI,IAAM,EAAI,SAAS,GACnB,IAAQ,IAAM;CAClB,OAAO,IAAM,KAAS,EAAI,IAAM,OAAO,IAAG;CAC1C,IAAM,IAAO,EAAG,OAAO,EAAI,SAAS,GAAO,CAAG,CAAC;CAG/C,OADK,oBAAoB,KAAK,CAAI,IAC3B;EAAE;EAAM,SAAS,IAAS;CAAE,IADS;AAE9C;AAIA,SAAS,EAAe,GAAiC;CACvD,IAAM,IAAQ,IAAI,WAAW,GAAG,GAC1B,IAAI,EAAQ;CAClB,IAAI,MAAM,GAAG,OAAO;CACpB,IAAM,IAAU,IAAI,WAAW,CAAC,GAC5B,IAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,IAAM,IAAI,EAAQ;EAElB,AADA,EAAQ,MAAQ,KAAK,IAAS,MAAS,MAAM,KAAU,KACvD,IAAS,IAAQ,IAAK;CACxB;CACA,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK,EAAM,KAAK,GAAS,IAAI,IAAI,SAAW;CACrE,OAAO;AACT;AACA,SAAS,EAAW,GAAmB,GAA2B;CAChE,IAAM,IAAQ,EAAe,CAAO;CACpC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK;EACrC,IAAI,IAAM;EAEV,AADI,IAAM,UAAQ,KAAO,QACzB,EAAM,MAAM,EAAM,IAAM;CAC1B;AACF;AAGA,IAAM,IAAgB,KAChB,IAAgB,MAChB,IAAqB;AAE3B,SAAS,EAAa,GAAiB,GAA4B;CACjE,IAAM,IAAI,EAAI,QACR,IAAI,IAAI,WAAW,CAAC;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,KAAK,IAAI;CACvC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,KAAK,IAAI,EAAE,KAAK,EAAI,IAAI,MAAM;EAC9B,IAAM,IAAI,EAAE;EAEZ,AADA,EAAE,KAAK,EAAE,IACT,EAAE,KAAK;CACT;CACA,IAAM,IAAM,IAAI,WAAW,CAAM,GAC7B,IAAI;CACR,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,GAAQ,KAAK;EAE/B,AADA,KAAK,IAAI,KAAK,GACd,KAAK,IAAI,EAAE,MAAM;EACjB,IAAM,IAAI,EAAE;EAGZ,AAFA,EAAE,KAAK,EAAE,IACT,EAAE,KAAK,GACP,EAAI,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM;CAC7B;CACA,OAAO;AACT;AACA,SAAS,EAAY,GAAyB;CAC5C,IAAI,IAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK;EACnC,IAAM,IAAI,EAAI;EACd,IAAI,MAAM,GAAG;EACb,IAAM,IAAO,KAAK,KAAK,GAAM,CAAC,MAAM;EACpC,IAAI,KAAQ,MAAS,GAAG;EACxB,IAAO;CACT;CACA,OAAO,MAAS;AAClB;AACA,SAAS,EAAW,GAAmB,GAAuB;CAC5D,IAAM,IAAS,EAAI,QACb,IAAU,EAAY,CAAG,GACzB,IAAK,EAAa,GAAK,CAAkB,GAEzC,KAAU,GAAY,MAC1B,MAAS,IAAI,IAAI,KAAK,MAAO,KAAW,KAAQ,IAAK,MAAO,GAAG,GAE7D,IAAS,GACT,IAAM,GACJ,IAAQ,EAAM;CAEpB,IAAI,IAAS,GAAe;EAC1B,IAAM,IAAM,KAAK,IAAI,IAAQ,GAAK,IAAgB,CAAM,GACpD,IAAI;EACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAEvB,AADA,EAAM,IAAM,MAAM,EAAI,EAAO,GAAG,EAAI,IAAI,EAAO,IAAI,IACnD;EAGF,AADA,KAAU,GACV,KAAO;CACT;CAEA,IAAI,IAAS,MAAkB,GAAG;EAChC,IAAM,IAAM,KAAK,IAAI,IAAQ,GAAK,IAAiB,IAAS,CAAc;EAG1E,AAFA,EAAY,GAAO,GAAK,GAAK,CAAM,GACnC,KAAU,GACV,KAAO;CACT;CAEA,OAAO,IAAM,IAAO;EAClB,IAAM,IAAM,KAAK,IAAI,IAAQ,GAAK,CAAa;EAG/C,AAFA,EAAY,GAAO,GAAK,GAAK,CAAM,GACnC,KAAU,GACV,KAAO;CACT;CAEA,SAAS,EAAY,GAAiB,GAAe,GAAa,GAAmB;EACnF,IAAM,IAAS,KAAK,MAAM,IAAM,CAAa,GACvC,IAAS,IAAM,GAEf,IADK,EAAO,GAAQ,EAAI,IAAS,EACvB,IAAK,MAAO;EAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAK,KAAK,EAAI,IAAQ,MAAM,EAAG,IAAS;CAC9D;AACF;AAaA,SAAS,EAAa,GAA8D;CAClF,IAAM,KAAS,GAAW,MAAuB;EAC/C,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,EAAE,IAAI,OAAO,EAAE,WAAW,CAAC,GAAG,OAAO;EAC5E,OAAO;CACT;CAOA,OANI,EAAM,GAAG,MAAM,IAAU;EAAE,QAAQ;EAAQ,MAAM;EAAc,KAAK;CAAO,IAC3E,EAAM,GAAG,MAAM,IAAU;EAAE,QAAQ;EAAO,MAAM;EAAa,KAAK;CAAM,IACxE,EAAM,GAAG,KAAK,KAAM,EAAE,OAAO,QAAS,EAAE,KAAK,QAAU,MAClD;EAAE,QAAQ;EAAO,MAAM;EAAc,KAAK;CAAM,IACrD,EAAE,SAAS,MAAM,EAAM,GAAG,MAAM,IAAU;EAAE,QAAQ;EAAO,MAAM;EAAa,KAAK;CAAM,IACzF,EAAM,GAAG,MAAM,KAAK,EAAM,GAAG,MAAM,IAAU;EAAE,QAAQ;EAAO,MAAM;EAAa,KAAK;CAAM,IACzF;EAAE,QAAQ;EAAO,MAAM;EAA4B,KAAK;CAAM;AACvE;AAEA,IAAa,IAAb,cAA8B,MAAM,CAAC;AAMrC,SAAgB,EAAW,GAA+C;CACxE,IAAM,IAAM,aAAiB,aAAa,IAAQ,IAAI,WAAW,CAAK,GAChE,IAAS,EAAY,CAAG;CAC9B,IAAI,CAAC,GACH,MAAM,IAAI,EACR,+CACF;CACF,IAAM,IAAU,EAAY,EAAO,IAAI;CACvC,IAAI,CAAC,KAAW,EAAQ,WAAW,GAAG,MAAM,IAAI,EAAS,WAAW;CACpE,IAAM,IAAQ,EAAI,MAAM,GAAG,EAAI,SAAS,EAAO,OAAO;CACtD,AAAI,EAAQ,SAAS,MAAK,EAAW,GAAO,CAAO,IAC9C,EAAW,GAAO,CAAO;CAC9B,IAAM,IAAM,EAAa,CAAK;CAC9B,IAAI,EAAI,WAAW,OAAO,MAAM,IAAI,EAAS,0BAA0B;CACvE,OAAO;EAAE,MAAM;EAAO,GAAG;CAAI;AAC/B"}
|
package/dist/lib/rar.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rar.js","names":[],"sources":["../../src/lib/rar.ts"],"sourcesContent":["/**\n * In-browser RAR reader, wrapping the WASM unrar port (node-unrar-js).\n *\n * RAR is the one archive format in this viewer we don't hand-roll: RAR3/RAR5 decompression is a large,\n * proprietary LZ+filter pipeline, so — unlike zip/7z/tar — we lean on the upstream `unrar` C sources\n * compiled to WebAssembly. The ESM build (`node-unrar-js/esm`) is browser-targeted; we feed it the wasm\n * binary fetched as a same-origin asset (Vite emits `unrar.wasm` via the `?url` import) so nothing loads\n * from a CDN. The module + wasm are pulled in lazily (this file is dynamic-imported by ArchiveBrowser),\n * keeping the ~200KB wasm out of the main bundle until a `.rar` is actually opened.\n *\n * Exposed through the shared {@link OpenedArchive} interface like the other archives. The upstream bytes are\n * decoded entirely client-side; the WASM sandbox never reaches the network or filesystem.\n */\n\nimport { createExtractorFromData, type FileHeader } from 'node-unrar-js'\n// Vite emits the browser-build wasm as a hashed, same-origin asset and gives us its URL.\nimport unrarWasmUrl from 'node-unrar-js/esm/js/unrar.wasm?url'\nimport type { OpenedArchive } from '@/lib/archive'\n\nlet wasmBinary: ArrayBuffer | null = null\n\n/** Fetch (and cache) the unrar wasm binary. */\nasync function getWasm(): Promise<ArrayBuffer> {\n if (!wasmBinary) {\n const res = await fetch(unrarWasmUrl)\n if (!res.ok) throw new Error(`无法加载 unrar.wasm(HTTP ${res.status})`)\n wasmBinary = await res.arrayBuffer()\n }\n return wasmBinary\n}\n\n/** Open a RAR archive: list its entries, extracting each file's bytes on demand via the WASM extractor. */\nexport async function openRar(buf: ArrayBuffer): Promise<OpenedArchive> {\n const extractor = await createExtractorFromData({ wasmBinary: await getWasm(), data: buf })\n // getFileList() / extract() return one-shot generators — materialize the headers up front.\n const headers: FileHeader[] = [...extractor.getFileList().fileHeaders]\n const entries = headers.map((h, i) => {\n const path = h.name.replace(/\\\\/g, '/').replace(/\\/+$/, '')\n return {\n path,\n name: path.split('/').pop() ?? path,\n size: h.flags.directory ? 0 : h.unpSize,\n isDir: h.flags.directory,\n ref: i,\n }\n })\n return {\n entries,\n extract(e) {\n const h = headers[e.ref]\n if (!h || h.flags.directory) return new Uint8Array(0)\n if (h.flags.encrypted) throw new Error('该条目已加密,无法读取')\n // Re-run extraction for just this file (the extractor decompresses predecessors itself for solid archives).\n const files = [...extractor.extract({ files: [h.name] }).files]\n const file = files.find((f) => f.fileHeader.name === h.name) ?? files[0]\n if (!file?.extraction) throw new Error('解压失败(该条目无数据)')\n return file.extraction\n },\n }\n}\n"],"mappings":";;;AAmBA,IAAI,IAAiC;AAGrC,eAAe,IAAgC;CAC7C,IAAI,CAAC,GAAY;EACf,IAAM,IAAM,MAAM,MAAM,CAAY;EACpC,IAAI,CAAC,EAAI,IAAI,MAAU,MAAM,wBAAwB,EAAI,OAAO,EAAE;EAClE,IAAa,MAAM,EAAI,YAAY;CACrC;CACA,OAAO;AACT;AAGA,eAAsB,EAAQ,GAA0C;CACtE,IAAM,IAAY,MAAM,EAAwB;EAAE,YAAY,MAAM,EAAQ;EAAG,MAAM;CAAI,CAAC,GAEpF,IAAwB,CAAC,GAAG,EAAU,YAAY,CAAC,CAAC,WAAW;CAWrE,OAAO;EACL,SAXc,EAAQ,KAAK,GAAG,MAAM;GACpC,IAAM,IAAO,EAAE,KAAK,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE;GAC1D,OAAO;IACL;IACA,MAAM,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;IAC/B,MAAM,EAAE,MAAM,YAAY,IAAI,EAAE;IAChC,OAAO,EAAE,MAAM;IACf,KAAK;GACP;EACF,CAEE;EACA,QAAQ,GAAG;GACT,IAAM,IAAI,EAAQ,EAAE;GACpB,IAAI,CAAC,KAAK,EAAE,MAAM,WAAW,OAAO,IAAI,WAAY;GACpD,IAAI,EAAE,MAAM,WAAW,MAAU,MAAM,aAAa;GAEpD,IAAM,IAAQ,CAAC,GAAG,EAAU,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GACxD,IAAO,EAAM,MAAM,MAAM,EAAE,WAAW,SAAS,EAAE,IAAI,KAAK,EAAM;GACtE,IAAI,CAAC,GAAM,YAAY,MAAU,MAAM,cAAc;GACrD,OAAO,EAAK;EACd;CACF;AACF"}
|
package/dist/lib/rawPreview.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rawPreview.js","names":[],"sources":["../../src/lib/rawPreview.ts"],"sourcesContent":["/**\n * Best-effort camera-RAW preview extraction (zero-dependency).\n *\n * Decoding a raw sensor file (CR2/NEF/ARW/DNG/…) into an image requires a full demosaicing pipeline that's\n * out of scope for the browser. But virtually every RAW embeds a ready-to-view JPEG preview (often full-res).\n * This walks the file's JPEG streams properly — skipping each marker segment by its length, including the\n * EXIF thumbnail nested in APP1, so it finds true SOI…EOI boundaries rather than cutting at the first stray\n * `FF D9` — and returns the LARGEST embedded JPEG (the main preview). Imperfect by nature; a file with no\n * embedded JPEG yields null.\n */\n\n// Given an SOI at `start`, walk markers to the real EOI; return the index just past EOI, or -1 if unterminated.\nfunction jpegEnd(b: Uint8Array, start: number): number {\n let i = start + 2 // past the SOI (FF D8)\n const n = b.length\n while (i < n - 1) {\n if (b[i] !== 0xff) {\n i++\n continue\n }\n const marker = b[i + 1]\n if (marker === 0xff) {\n i++ // fill byte\n continue\n }\n if (marker === 0xd9) return i + 2 // EOI\n // Standalone markers with no length payload: TEM (0x01) and RSTn (0xD0–0xD7).\n if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {\n i += 2\n continue\n }\n if (i + 3 >= n) return -1\n const len = (b[i + 2] << 8) | b[i + 3]\n if (len < 2) return -1\n if (marker === 0xda) {\n // Start of Scan: skip the header, then scan entropy-coded data for the next real marker (not FF00/RSTn).\n i += 2 + len\n while (i < n - 1) {\n if (b[i] === 0xff) {\n const next = b[i + 1]\n if (next !== 0x00 && !(next >= 0xd0 && next <= 0xd7)) break\n }\n i++\n }\n continue\n }\n i += 2 + len // ordinary segment (APPn, DQT, SOFn, …) — skip by length\n }\n return -1\n}\n\n/** Return the bytes of the largest embedded JPEG in `buf` (the RAW's main preview), or null if none. */\nexport function extractLargestJpeg(buf: ArrayBuffer): Uint8Array | null {\n const b = new Uint8Array(buf)\n const n = b.length\n let best: { start: number; end: number } | null = null\n let i = 0\n while (i < n - 1) {\n if (b[i] === 0xff && b[i + 1] === 0xd8) {\n const end = jpegEnd(b, i)\n if (end > i) {\n if (!best || end - i > best.end - best.start) best = { start: i, end }\n i = end // jump past this JPEG so nested streams aren't double-counted\n continue\n }\n }\n i++\n }\n return best ? b.subarray(best.start, best.end) : null\n}\n"],"mappings":";AAYA,SAAS,EAAQ,GAAe,GAAuB;CACrD,IAAI,IAAI,IAAQ,GACV,IAAI,EAAE;CACZ,OAAO,IAAI,IAAI,IAAG;EAChB,IAAI,EAAE,OAAO,KAAM;GACjB;GACA;EACF;EACA,IAAM,IAAS,EAAE,IAAI;EACrB,IAAI,MAAW,KAAM;GACnB;GACA;EACF;EACA,IAAI,MAAW,KAAM,OAAO,IAAI;EAEhC,IAAI,MAAW,KAAS,KAAU,OAAQ,KAAU,KAAO;GACzD,KAAK;GACL;EACF;EACA,IAAI,IAAI,KAAK,GAAG,OAAO;EACvB,IAAM,IAAO,EAAE,IAAI,MAAM,IAAK,EAAE,IAAI;EACpC,IAAI,IAAM,GAAG,OAAO;EACpB,IAAI,MAAW,KAAM;GAGnB,KADA,KAAK,IAAI,GACF,IAAI,IAAI,IAAG;IAChB,IAAI,EAAE,OAAO,KAAM;KACjB,IAAM,IAAO,EAAE,IAAI;KACnB,IAAI,MAAS,KAAQ,EAAE,KAAQ,OAAQ,KAAQ,MAAO;IACxD;IACA;GACF;GACA;EACF;EACA,KAAK,IAAI;CACX;CACA,OAAO;AACT;AAGA,SAAgB,EAAmB,GAAqC;CACtE,IAAM,IAAI,IAAI,WAAW,CAAG,GACtB,IAAI,EAAE,QACR,IAA8C,MAC9C,IAAI;CACR,OAAO,IAAI,IAAI,IAAG;EAChB,IAAI,EAAE,OAAO,OAAQ,EAAE,IAAI,OAAO,KAAM;GACtC,IAAM,IAAM,EAAQ,GAAG,CAAC;GACxB,IAAI,IAAM,GAAG;IAEX,CADI,CAAC,KAAQ,IAAM,IAAI,EAAK,MAAM,EAAK,WAAO,IAAO;KAAE,OAAO;KAAG;IAAI,IACrE,IAAI;IACJ;GACF;EACF;EACA;CACF;CACA,OAAO,IAAO,EAAE,SAAS,EAAK,OAAO,EAAK,GAAG,IAAI;AACnD"}
|
package/dist/lib/rc2.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc2.js","names":[],"sources":["../../src/lib/rc2.ts"],"sourcesContent":["/**\n * Compact RC2 (RFC 2268) CBC decryption — no dependency, no WASM.\n *\n * Needed for legacy PKCS#12 keystores (see lib/pkcs12.ts): certificate bags are commonly encrypted with\n * `pbeWithSHA1And40BitRC2-CBC` (or the 128-bit variant), and WebCrypto ships no RC2. Decrypt-only. RC2 is\n * obsolete — used here ONLY to read existing keystores. Blocks are 8 bytes; words are 16-bit little-endian.\n */\n\n// PITABLE — the fixed 256-byte permutation from RFC 2268 §2.\n// prettier-ignore\nconst PITABLE = new Uint8Array([\n 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d,\n 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2,\n 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32,\n 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82,\n 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc,\n 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26,\n 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03,\n 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7,\n 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a,\n 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec,\n 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39,\n 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31,\n 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9,\n 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9,\n 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e,\n 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad,\n])\n\n/** Expand a key into the 64-word (16-bit) RC2 schedule, limited to `effectiveBits` of effective strength. */\nfunction rc2KeySchedule(key: Uint8Array, effectiveBits: number): Uint16Array {\n const L = new Uint8Array(128)\n const T = key.length\n L.set(key.subarray(0, T))\n const T1 = effectiveBits\n const T8 = (T1 + 7) >> 3\n const TM = 0xff >> ((8 * T8 - T1) & 7) // = 255 mod 2^(8 + T1 - 8*T8)\n for (let i = T; i < 128; i++) L[i] = PITABLE[(L[i - 1] + L[i - T]) & 0xff]\n L[128 - T8] = PITABLE[L[128 - T8] & TM]\n for (let i = 127 - T8; i >= 0; i--) L[i] = PITABLE[L[i + 1] ^ L[i + T8]]\n const K = new Uint16Array(64)\n for (let i = 0; i < 64; i++) K[i] = L[2 * i] | (L[2 * i + 1] << 8)\n return K\n}\n\nconst rotr16 = (x: number, n: number): number => ((x >>> n) | (x << (16 - n))) & 0xffff\n\n/** Decrypt one 8-byte block (little-endian words) in place, returning [R0,R1,R2,R3]. */\nfunction rc2DecryptBlock(r: number[], K: Uint16Array): void {\n let [r0, r1, r2, r3] = r\n let j = 63\n const rMix = (): void => {\n r3 = rotr16(r3, 5)\n r3 = (r3 - K[j--] - (r2 & r1) - (~r2 & r0)) & 0xffff\n r2 = rotr16(r2, 3)\n r2 = (r2 - K[j--] - (r1 & r0) - (~r1 & r3)) & 0xffff\n r1 = rotr16(r1, 2)\n r1 = (r1 - K[j--] - (r0 & r3) - (~r0 & r2)) & 0xffff\n r0 = rotr16(r0, 1)\n r0 = (r0 - K[j--] - (r3 & r2) - (~r3 & r1)) & 0xffff\n }\n const rMash = (): void => {\n r3 = (r3 - K[r2 & 63]) & 0xffff\n r2 = (r2 - K[r1 & 63]) & 0xffff\n r1 = (r1 - K[r0 & 63]) & 0xffff\n r0 = (r0 - K[r3 & 63]) & 0xffff\n }\n for (let i = 0; i < 5; i++) rMix()\n rMash()\n for (let i = 0; i < 6; i++) rMix()\n rMash()\n for (let i = 0; i < 5; i++) rMix()\n r[0] = r0\n r[1] = r1\n r[2] = r2\n r[3] = r3\n}\n\n/**\n * RC2-CBC decrypt, no padding removal (the caller strips PKCS#7). `iv` is 8 bytes; `data` must be a whole\n * number of 8-byte blocks. `effectiveBits` defaults to the key length in bits (PKCS#12 40-bit RC2 passes 40).\n */\nexport function rc2CbcDecrypt(\n key: Uint8Array,\n iv: Uint8Array,\n data: Uint8Array,\n effectiveBits = key.length * 8,\n): Uint8Array {\n const K = rc2KeySchedule(key, effectiveBits)\n const n = data.length - (data.length % 8)\n const out = new Uint8Array(n)\n const prev = new Uint8Array(8)\n prev.set(iv.subarray(0, 8))\n const r = [0, 0, 0, 0]\n for (let off = 0; off < n; off += 8) {\n r[0] = data[off] | (data[off + 1] << 8)\n r[1] = data[off + 2] | (data[off + 3] << 8)\n r[2] = data[off + 4] | (data[off + 5] << 8)\n r[3] = data[off + 6] | (data[off + 7] << 8)\n rc2DecryptBlock(r, K)\n out[off] = (r[0] & 0xff) ^ prev[0]\n out[off + 1] = ((r[0] >> 8) & 0xff) ^ prev[1]\n out[off + 2] = (r[1] & 0xff) ^ prev[2]\n out[off + 3] = ((r[1] >> 8) & 0xff) ^ prev[3]\n out[off + 4] = (r[2] & 0xff) ^ prev[4]\n out[off + 5] = ((r[2] >> 8) & 0xff) ^ prev[5]\n out[off + 6] = (r[3] & 0xff) ^ prev[6]\n out[off + 7] = ((r[3] >> 8) & 0xff) ^ prev[7]\n prev.set(data.subarray(off, off + 8))\n }\n return out\n}\n"],"mappings":";AAUA,IAAM,IAAU,IAAI,WAAW;CAC7B;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM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"}
|
package/dist/lib/resolveInput.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveInput.js","names":[],"sources":["../../src/lib/resolveInput.ts"],"sourcesContent":["import type { ViewerInput } from '../types'\n\n/** A normalized source: always a fetchable URL, plus optional pre-decoded text; `dispose()` frees blob: URLs. */\nexport interface ResolvedSource {\n name: string\n mime: string | null\n size?: number\n /** Always fetchable: the real `src`, or a `blob:` URL for blob/text/load/pre-fetched sources. */\n url: string\n /** Present when the input carried decoded text (text-backed viewers use it directly — no fetch). */\n text?: string\n /** Release any `blob:` URL this created. No-op for a pass-through `src`. */\n dispose(): void\n}\n\nconst noop = (): void => {}\n\nfunction toBlob(bytes: Blob | ArrayBuffer, mime: string | null): Blob {\n if (bytes instanceof Blob) return bytes\n return new Blob([bytes], mime ? { type: mime } : undefined)\n}\n\n/**\n * Normalize a {@link ViewerInput} into a fetchable source (see docs/file-viewer-package-design.md §2).\n *\n * Async because AUTHENTICATED sources (`fetchConfig`/`load`) are pulled ONCE here and turned into a `blob:`\n * URL — so the self-fetching sub-viewers keep calling plain `fetch(url)` with no auth knowledge, and the\n * credentials live in exactly one place. A plain public `src` is passed through unchanged (streaming stays\n * intact for the big-file viewers). `blob`/`text` become object URLs. Precedence: text > blob > load > src.\n */\nexport async function resolveInput(input: ViewerInput): Promise<ResolvedSource> {\n const name = input.name\n const mime = input.mime && input.mime.trim() ? input.mime : null\n const base = { name, mime, size: input.size }\n\n // text: hand it straight to text-backed viewers; also expose a blob: URL so self-fetching viewers work.\n if (input.text != null) {\n const url = URL.createObjectURL(new Blob([input.text], { type: mime ?? 'text/plain' }))\n return { ...base, url, text: input.text, dispose: () => URL.revokeObjectURL(url) }\n }\n\n // blob: object URL straight from the in-memory bytes.\n if (input.blob) {\n const url = URL.createObjectURL(input.blob)\n return { ...base, url, dispose: () => URL.revokeObjectURL(url) }\n }\n\n // load: custom loader → bytes → object URL (auth handled inside the consumer's function).\n if (input.load) {\n const url = URL.createObjectURL(toBlob(await input.load(), mime))\n return { ...base, url, dispose: () => URL.revokeObjectURL(url) }\n }\n\n // src + fetchConfig: authenticated fetch ONCE here → object URL (sub-viewers fetch the blob, no auth needed).\n if (input.src && input.fetchConfig) {\n const res = await fetch(input.src, input.fetchConfig)\n if (!res.ok) throw new Error(`HTTP ${res.status} fetching \"${name}\"`)\n const url = URL.createObjectURL(await res.blob())\n return { ...base, url, dispose: () => URL.revokeObjectURL(url) }\n }\n\n // Plain public src: pass through (streaming preserved for the big-file viewers).\n if (input.src) return { ...base, url: input.src, dispose: noop }\n\n throw new Error(`ViewerInput for \"${name}\" has no data source (src / blob / text / load)`)\n}\n"],"mappings":";AAeA,IAAM,UAAmB,CAAC;AAE1B,SAAS,EAAO,GAA2B,GAA2B;CAEpE,OADI,aAAiB,OAAa,IAC3B,IAAI,KAAK,CAAC,CAAK,GAAG,IAAO,EAAE,MAAM,EAAK,IAAI,KAAA,CAAS;AAC5D;AAUA,eAAsB,EAAa,GAA6C;CAC9E,IAAM,IAAO,EAAM,MACb,IAAO,EAAM,QAAQ,EAAM,KAAK,KAAK,IAAI,EAAM,OAAO,MACtD,IAAO;EAAE;EAAM;EAAM,MAAM,EAAM;CAAK;CAG5C,IAAI,EAAM,QAAQ,MAAM;EACtB,IAAM,IAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,EAAM,IAAI,GAAG,EAAE,MAAM,KAAQ,aAAa,CAAC,CAAC;EACtF,OAAO;GAAE,GAAG;GAAM;GAAK,MAAM,EAAM;GAAM,eAAe,IAAI,gBAAgB,CAAG;EAAE;CACnF;CAGA,IAAI,EAAM,MAAM;EACd,IAAM,IAAM,IAAI,gBAAgB,EAAM,IAAI;EAC1C,OAAO;GAAE,GAAG;GAAM;GAAK,eAAe,IAAI,gBAAgB,CAAG;EAAE;CACjE;CAGA,IAAI,EAAM,MAAM;EACd,IAAM,IAAM,IAAI,gBAAgB,EAAO,MAAM,EAAM,KAAK,GAAG,CAAI,CAAC;EAChE,OAAO;GAAE,GAAG;GAAM;GAAK,eAAe,IAAI,gBAAgB,CAAG;EAAE;CACjE;CAGA,IAAI,EAAM,OAAO,EAAM,aAAa;EAClC,IAAM,IAAM,MAAM,MAAM,EAAM,KAAK,EAAM,WAAW;EACpD,IAAI,CAAC,EAAI,IAAI,MAAU,MAAM,QAAQ,EAAI,OAAO,aAAa,EAAK,EAAE;EACpE,IAAM,IAAM,IAAI,gBAAgB,MAAM,EAAI,KAAK,CAAC;EAChD,OAAO;GAAE,GAAG;GAAM;GAAK,eAAe,IAAI,gBAAgB,CAAG;EAAE;CACjE;CAGA,IAAI,EAAM,KAAK,OAAO;EAAE,GAAG;EAAM,KAAK,EAAM;EAAK,SAAS;CAAK;CAE/D,MAAU,MAAM,oBAAoB,EAAK,gDAAgD;AAC3F"}
|