Fix dictionary image support (#1526)

* Fix content security policy for images

* Add createBlobFromBase64Content to MediaUtil

* Update MediaLoader to use MediaUtil

* Use blob URLs when importing dictionaries

* Update VM's URL to support createObjectURL and revokeObjectURL

* Fix test
This commit is contained in:
toasted-nutbread 2021-03-14 18:41:15 -04:00 committed by GitHub
parent 52a4d874ea
commit 07df1e0117
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 62 additions and 20 deletions

View File

@ -114,7 +114,7 @@
"popup.html",
"template-renderer.html"
],
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
"content_security_policy": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
},
"variants": [
{
@ -194,7 +194,7 @@
{
"action": "set",
"path": ["content_security_policy"],
"value": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
"value": "default-src 'self'; script-src 'self' 'unsafe-eval'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
},
{
"action": "set",

View File

@ -76,6 +76,13 @@ class Image {
}
}
class Blob {
constructor(array, options) {
this._array = array;
this._options = options;
}
}
async function fetch(url2) {
const filePath = url.fileURLToPath(url2);
await Promise.resolve();
@ -89,15 +96,21 @@ async function fetch(url2) {
};
}
function atob(data) {
return Buffer.from(data, 'base64').toString('ascii');
}
class DatabaseVM extends VM {
constructor() {
super({
chrome,
Image,
Blob,
fetch,
indexedDB: global.indexedDB,
IDBKeyRange: global.IDBKeyRange,
JSZip
JSZip,
atob
});
this.context.window = this.context;
this.indexedDB = global.indexedDB;

View File

@ -19,6 +19,7 @@ const fs = require('fs');
const vm = require('vm');
const path = require('path');
const assert = require('assert');
const crypto = require('crypto');
function getContextEnvironmentRecords(context, names) {
@ -115,9 +116,9 @@ function deepStrictEqual(actual, expected) {
}
function createURLClass() {
function createURLClass(urlMap) {
const BaseURL = URL;
return function URL(url) {
const result = function URL(url) {
const u = new BaseURL(url);
this.hash = u.hash;
this.host = u.host;
@ -132,12 +133,23 @@ function createURLClass() {
this.searchParams = u.searchParams;
this.username = u.username;
};
result.createObjectURL = (object) => {
const id = crypto.randomBytes(16).toString('hex');
const url = `blob:${id}`;
urlMap.set(url, object);
return url;
};
result.revokeObjectURL = (url) => {
urlMap.delete(url);
};
return result;
}
class VM {
constructor(context={}) {
context.URL = createURLClass();
this._urlMap = new Map();
context.URL = createURLClass(this._urlMap);
this._context = vm.createContext(context);
this._assert = {
deepStrictEqual
@ -186,6 +198,10 @@ class VM {
return single ? results[0] : results;
}
getUrlObject(url) {
return this._urlMap.get(url);
}
}

View File

@ -400,7 +400,9 @@ class DictionaryImporter {
eventListeners.removeAllEventListeners();
reject(new Error('Image failed to load'));
}, false);
image.src = `data:${mediaType};base64,${content}`;
const blob = MediaUtil.createBlobFromBase64Content(content, mediaType);
const url = URL.createObjectURL(blob);
image.src = url;
});
}
}

View File

@ -15,6 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* global
* MediaUtil
*/
class MediaLoader {
constructor() {
this._token = {};
@ -82,22 +86,11 @@ class MediaLoader {
const token = this._token;
const data = (await yomichan.api.getMedia([{path, dictionaryName}]))[0];
if (token === this._token && data !== null) {
const contentArrayBuffer = this._base64ToArrayBuffer(data.content);
const blob = new Blob([contentArrayBuffer], {type: data.mediaType});
const blob = MediaUtil.createBlobFromBase64Content(data.content, data.mediaType);
const url = URL.createObjectURL(blob);
cachedData.data = data;
cachedData.url = url;
}
return cachedData;
}
_base64ToArrayBuffer(content) {
const binaryContent = window.atob(content);
const length = binaryContent.length;
const array = new Uint8Array(length);
for (let i = 0; i < length; ++i) {
array[i] = binaryContent.charCodeAt(i);
}
return array.buffer;
}
}

View File

@ -129,4 +129,20 @@ class MediaUtil {
return null;
}
}
/**
* Creates a new `Blob` object from a base64 string of content.
* @param content The binary content string encoded in base64.
* @param mediaType The type of the media.
* @returns A new `Blob` object corresponding to the specified content.
*/
static createBlobFromBase64Content(content, mediaType) {
const binaryContent = atob(content);
const length = binaryContent.length;
const array = new Uint8Array(length);
for (let i = 0; i < length; ++i) {
array[i] = binaryContent.charCodeAt(i);
}
return new Blob([array.buffer], {type: mediaType});
}
}

View File

@ -113,5 +113,5 @@
"popup.html",
"template-renderer.html"
],
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
"content_security_policy": "default-src 'self'; img-src blob: 'self'; style-src 'self' 'unsafe-inline'; media-src *; connect-src *"
}

View File

@ -123,6 +123,7 @@
<script src="/js/language/text-scanner.js"></script>
<script src="/js/media/audio-system.js"></script>
<script src="/js/media/media-loader.js"></script>
<script src="/js/media/media-util.js"></script>
<script src="/js/media/text-to-speech-audio.js"></script>
<script src="/js/script/dynamic-loader.js"></script>
<script src="/js/templates/template-renderer-proxy.js"></script>

View File

@ -107,6 +107,7 @@
<script src="/js/language/text-scanner.js"></script>
<script src="/js/media/audio-system.js"></script>
<script src="/js/media/media-loader.js"></script>
<script src="/js/media/media-util.js"></script>
<script src="/js/media/text-to-speech-audio.js"></script>
<script src="/js/script/dynamic-loader.js"></script>
<script src="/js/templates/template-renderer-proxy.js"></script>