Skip to content
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

web: PluginArray, MimeType and MimeTypeArray prototype spoofing #18229

Merged
merged 2 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions web/packages/core/src/plugin-polyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ class RuffleMimeTypeArray implements MimeTypeArray {
* @param mimeType The mime type to install
*/
install(mimeType: MimeType): void {
const wrapper = new RuffleMimeType(mimeType);

const index = this.__mimeTypes.length;
this.__mimeTypes.push(mimeType);
this.__namedMimeTypes[mimeType.type] = mimeType;
this[mimeType.type] = mimeType;
this[index] = mimeType;
this.__mimeTypes.push(wrapper);
this.__namedMimeTypes[mimeType.type] = wrapper;
this[wrapper.type] = wrapper;
this[index] = wrapper;
}

item(index: number): MimeType {
Expand All @@ -66,6 +68,43 @@ class RuffleMimeTypeArray implements MimeTypeArray {
[Symbol.iterator](): IterableIterator<MimeType> {
return this.__mimeTypes[Symbol.iterator]();
}

get [Symbol.toStringTag](): string {
return "MimeTypeArray";
}
}

/**
* Replacement object for the built-in MimeType object.
* This only exists, because the built-in type is not constructable and we
* need to spoof `window.MimeType`.
*/
class RuffleMimeType implements MimeType {
private readonly __mimeType: MimeType;

constructor(mimeType: MimeType) {
this.__mimeType = mimeType;
}

get type(): string {
return this.__mimeType.type;
}

get description(): string {
return this.__mimeType.description;
}

get suffixes(): string {
return this.__mimeType.suffixes;
}

get enabledPlugin(): Plugin {
return this.__mimeType.enabledPlugin;
}

get [Symbol.toStringTag](): string {
return "MimeType";
}
}

/**
Expand Down Expand Up @@ -141,6 +180,10 @@ class RufflePluginArray implements PluginArray {
return this.__plugins[Symbol.iterator]();
}

get [Symbol.toStringTag](): string {
return "PluginArray";
}

get length(): number {
return this.__plugins.length;
}
Expand Down Expand Up @@ -205,6 +248,9 @@ export function installPlugin(plugin: RufflePlugin): void {
return;
}
if (!("install" in navigator.plugins) || !navigator.plugins["install"]) {
Object.defineProperty(window, "PluginArray", {
value: RufflePluginArray,
});
Object.defineProperty(navigator, "plugins", {
value: new RufflePluginArray(navigator.plugins),
writable: false,
Expand All @@ -218,6 +264,12 @@ export function installPlugin(plugin: RufflePlugin): void {
plugin.length > 0 &&
(!("install" in navigator.mimeTypes) || !navigator.mimeTypes["install"])
) {
Object.defineProperty(window, "MimeTypeArray", {
value: RuffleMimeTypeArray,
});
Object.defineProperty(window, "MimeType", {
value: RuffleMimeType,
});
Object.defineProperty(navigator, "mimeTypes", {
value: new RuffleMimeTypeArray(navigator.mimeTypes),
writable: false,
Expand Down
5 changes: 5 additions & 0 deletions web/packages/selfhosted/test/polyfill/spoofing/expected.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ruffle-object
data="/test_assets/example.swf"
width="500"
height="500"
></ruffle-object>
17 changes: 17 additions & 0 deletions web/packages/selfhosted/test/polyfill/spoofing/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Spoofing Test</title>
</head>

<body>
<div id="test-container">
<object
data="/test_assets/example.swf"
width="500"
height="500"
></object>
</div>
</body>
</html>
63 changes: 63 additions & 0 deletions web/packages/selfhosted/test/polyfill/spoofing/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */

import { openTest, injectRuffleAndWait } from "../../utils.js";
import { expect, use } from "chai";
import chaiHtml from "chai-html";
import fs from "fs";

use(chaiHtml);

describe("Spoofing is not easily detectable", () => {
it("loads the test", async () => {
await openTest(browser, `polyfill/spoofing`);
});

it("Polyfills", async () => {
await injectRuffleAndWait(browser);
await browser.$("<ruffle-object />").waitForExist();

const actual = await browser.$("#test-container").getHTML(false);
const expected = fs.readFileSync(
`${import.meta.dirname}/expected.html`,
"utf8",
);
expect(actual).html.to.equal(expected);
});

it("Spoofs navigator.plugins", async () => {
const names = await browser.execute(() => {
const names = [];
for (let i = 0; i < navigator.plugins.length; i++) {
names.push(navigator.plugins[i]!.name);
}
return names;
});
expect(names).to.include("Shockwave Flash");

const instance = await browser.execute(() => {
return navigator.plugins instanceof PluginArray;
});
expect(instance).be.true;
});

it("Spoofs navigator.mimeTypes", async () => {
const types = await browser.execute(() => {
const types = [];
for (let i = 0; i < navigator.mimeTypes.length; i++) {
types.push(navigator.mimeTypes[i]!.type);
}
return types;
});
expect(types).to.include("application/x-shockwave-flash");

const instance = await browser.execute(() => {
for (let i = 0; i < navigator.mimeTypes.length; i++) {
if (!(navigator.mimeTypes[i] instanceof MimeType)) {
return false;
}
}
return navigator.mimeTypes instanceof MimeTypeArray;
});
expect(instance).be.true;
});
});