-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#7673: Avoid CORS issues in ImageReader #7972
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7972 +/- ##
==========================================
- Coverage 72.96% 72.90% -0.06%
==========================================
Files 1295 1293 -2
Lines 40248 40253 +5
Branches 7490 7489 -1
==========================================
- Hits 29366 29347 -19
- Misses 10882 10906 +24 ☔ View full report in Codecov by Sentry. |
* | ||
* This supports all URLs, including data: and blob: (neither one of which would trigger an HTTP request) | ||
*/ | ||
export async function loadImageBinaryData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Original implementation:
pixiebrix-extension/src/bricks/readers/ImageExifReader.ts
Lines 25 to 47 in 117b5a3
// TODO: I think axios alone can handle all of this, or FileReader can handle both data and blob in a CSP-safe way | |
async function getData(img: HTMLImageElement): Promise<ArrayBuffer> { | |
// Adapted from https://github.com/exif-js/exif-js/blob/master/exif.js#L384 | |
if (/^data:/i.test(img.src)) { | |
// Data URI | |
return convertDataUrl(img.src, "ArrayBuffer"); | |
} | |
if (/^blob:/i.test(img.src)) { | |
// Object URL | |
const blob = await fetch(img.src).then(async (r) => r.blob()); | |
return blob.arrayBuffer(); | |
} | |
const response = await axios.get<ArrayBuffer>(img.src, { | |
responseType: "arraybuffer", | |
}); | |
if (response.status !== 200) { | |
throw new Error(`Error fetching image ${img.src}: ${response.statusText}`); | |
} | |
return response.data; | |
} |
Changes:
- accept a string instead of
<img>
sinceimg
is not strictly required (can't read binary from it) - throw proper error on SVG images
- drop redundant axios-based fetch
- drop TODO
* @warning The base64 encoding is not guaranteed to match the original image binary. For that, use `loadImageBinaryData` | ||
* @warning If the loaded image is not CORS-safe, a new HTTP request will be made. | ||
*/ | ||
export async function loadImageAsBase64( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous implementation:
pixiebrix-extension/src/bricks/readers/ImageReader.ts
Lines 21 to 41 in 117b5a3
// Copied from https://stackoverflow.com/questions/934012/get-image-data-url-in-javascript | |
// TODO: Replace with `getData` from ImageExifReader? | |
function getBase64Image(img: HTMLImageElement) { | |
// Create an empty canvas element | |
const canvas = document.createElement("canvas"); | |
canvas.width = img.width; | |
canvas.height = img.height; | |
// Copy the image contents to the canvas | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- 2d always exists | |
const context = canvas.getContext("2d")!; | |
context.drawImage(img, 0, 0); | |
// Get the data-URL formatted image | |
// Firefox supports PNG and JPEG. You could check img.src to | |
// guess the original format, but be aware the using "image/jpg" | |
// will re-encode the image. | |
const dataURL = canvas.toDataURL("image/png"); | |
return dataURL.replace(/^data:image\/(png|jpg);base64,/, ""); | |
} |
Changes:
- clarify expectations of function, add warnings
- if already a base64, skip conversion
- use natural image size instead of the authored
<img width height>
- detect CORS issues and re-load image anonymously
const parsed = parseDataUrl(dataURL); | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- The browser just generated it, it's safe. If it fails, then it's likely a bug in `parseDataUrl` and it should be fixed. | ||
return parsed!.encodedBody; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If parseDataUrl
does fail, it would be good to report the dataURL to DataDog to make it easier to debug. I don't think it's very likely though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait until that error shows up in the logs before we spend time on it
No loom links were found in the first post. Please add one there if you'd like to it to appear on Slack. Do not edit this comment manually. |
What does this PR do?
Demo
Repro available on the original issue
Screen.Recording.mov
Future Work
Checklist