Handle JPX wasm fetch-response errors correctly (PR 19329 follow-up)

Currently we're not checking that the response is actually OK before getting the data, which means that rather than throwing an error we can get an empty `ArrayBuffer`.

To avoid duplicating code we can move an existing helper into `src/core/core_utils.js` and re-use it when fetching the JPX wasm-file as well.
This commit is contained in:
Jonas Jenwald 2025-01-17 01:02:39 +01:00
parent 88735d0f14
commit 6038b5a992
3 changed files with 24 additions and 17 deletions

View File

@ -103,6 +103,16 @@ function arrayBuffersToBytes(arr) {
return data; return data;
} }
async function fetchBinaryData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch file "${url}" with "${response.statusText}".`
);
}
return new Uint8Array(await response.arrayBuffer());
}
/** /**
* Get the value of an inheritable property. * Get the value of an inheritable property.
* *
@ -701,6 +711,7 @@ export {
encodeToXmlString, encodeToXmlString,
escapePDFName, escapePDFName,
escapeString, escapeString,
fetchBinaryData,
getInheritableProperty, getInheritableProperty,
getLookupTableFactory, getLookupTableFactory,
getNewAnnotationsMap, getNewAnnotationsMap,

View File

@ -32,6 +32,12 @@ import {
import { CMapFactory, IdentityCMap } from "./cmap.js"; import { CMapFactory, IdentityCMap } from "./cmap.js";
import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js"; import { Cmd, Dict, EOF, isName, Name, Ref, RefSet } from "./primitives.js";
import { ErrorFont, Font } from "./fonts.js"; import { ErrorFont, Font } from "./fonts.js";
import {
fetchBinaryData,
isNumberArray,
lookupMatrix,
lookupNormalRect,
} from "./core_utils.js";
import { import {
getEncoding, getEncoding,
MacRomanEncoding, MacRomanEncoding,
@ -51,7 +57,6 @@ import {
import { getTilingPatternIR, Pattern } from "./pattern.js"; import { getTilingPatternIR, Pattern } from "./pattern.js";
import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js"; import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js";
import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js";
import { isNumberArray, lookupMatrix, lookupNormalRect } from "./core_utils.js";
import { isPDFFunction, PDFFunctionFactory } from "./function.js"; import { isPDFFunction, PDFFunctionFactory } from "./function.js";
import { Lexer, Parser } from "./parser.js"; import { Lexer, Parser } from "./parser.js";
import { import {
@ -382,16 +387,6 @@ class PartialEvaluator {
return false; return false;
} }
async #fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch file "${url}" with "${response.statusText}".`
);
}
return new Uint8Array(await response.arrayBuffer());
}
async fetchBuiltInCMap(name) { async fetchBuiltInCMap(name) {
const cachedData = this.builtInCMapCache.get(name); const cachedData = this.builtInCMapCache.get(name);
if (cachedData) { if (cachedData) {
@ -401,7 +396,7 @@ class PartialEvaluator {
if (this.options.cMapUrl !== null) { if (this.options.cMapUrl !== null) {
// Only compressed CMaps are (currently) supported here. // Only compressed CMaps are (currently) supported here.
const cMapData = await this.#fetchData( const cMapData = await fetchBinaryData(
`${this.options.cMapUrl}${name}.bcmap` `${this.options.cMapUrl}${name}.bcmap`
); );
data = { cMapData, isCompressed: true }; data = { cMapData, isCompressed: true };
@ -437,7 +432,7 @@ class PartialEvaluator {
try { try {
if (this.options.standardFontDataUrl !== null) { if (this.options.standardFontDataUrl !== null) {
data = await this.#fetchData( data = await fetchBinaryData(
`${this.options.standardFontDataUrl}${filename}` `${this.options.standardFontDataUrl}${filename}`
); );
} else { } else {

View File

@ -14,6 +14,7 @@
*/ */
import { BaseException, warn } from "../shared/util.js"; import { BaseException, warn } from "../shared/util.js";
import { fetchBinaryData } from "./core_utils.js";
import OpenJPEG from "../../external/openjpeg/openjpeg.js"; import OpenJPEG from "../../external/openjpeg/openjpeg.js";
import { Stream } from "./stream.js"; import { Stream } from "./stream.js";
@ -44,14 +45,14 @@ class JpxImage {
} }
static async #instantiateWasm(imports, successCallback) { static async #instantiateWasm(imports, successCallback) {
const filename = "openjpeg.wasm";
try { try {
if (!this.#buffer) { if (!this.#buffer) {
if (this.#wasmUrl !== null) { if (this.#wasmUrl !== null) {
const response = await fetch(`${this.#wasmUrl}openjpeg.wasm`); this.#buffer = await fetchBinaryData(`${this.#wasmUrl}${filename}`);
this.#buffer = await response.arrayBuffer();
} else { } else {
this.#buffer = await this.#handler.sendWithPromise("FetchWasm", { this.#buffer = await this.#handler.sendWithPromise("FetchWasm", {
filename: "openjpeg.wasm", filename,
}); });
} }
} }
@ -59,7 +60,7 @@ class JpxImage {
return successCallback(results.instance); return successCallback(results.instance);
} catch (e) { } catch (e) {
this.#instantiationFailed = true; this.#instantiationFailed = true;
warn(`Cannot load openjpeg.wasm: "${e}".`); warn(`Cannot load ${filename}: "${e}".`);
return false; return false;
} finally { } finally {
this.#handler = null; this.#handler = null;