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

Proposal: Add RuleCondition to declarativeNetRequest that can check the headers #627

Closed
danielhjacobs opened this issue Jun 4, 2024 · 4 comments
Labels
needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time

Comments

@danielhjacobs
Copy link
Contributor

danielhjacobs commented Jun 4, 2024

This is as a substitute for webRequest.onHeadersReceived with webRequestBlocking. As a real-life example of where this would be useful, https://github.com/ruffle-rs/ruffle used to have code which caused SWF files loaded directly in the browser to be able to be opened with an internal extension page automagically with a redirect rule, but which does not work with Manifest V3. We tried replacing it with a regex filter for regexFilter: "^.*\\.s(?:wf|pl)(\\?.*|#.*|)$", but without also being able to check the Content-Type header that did not work properly since some SWF files are at URLs not ending in .swf (or .spl) and some URLs ending in .swf (or .spl) are not actually SWF files.

My suggestion would be a RuleCondition, headers, which is an object with keys matching the request header's keys and values of regular expressions for the corresponding values

@github-actions github-actions bot added needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time labels Jun 4, 2024
@danielhjacobs
Copy link
Contributor Author

danielhjacobs commented Jun 4, 2024

This is the aforementioned code:

/**
 * Returns whether the given filename ends in a known Flash extension.
 *
 * @param filename The filename to test.
 * @returns True if the filename is a Flash movie (swf or spl).
 */
function isSwfFilename(filename: string): boolean {
    let pathname = "";
    try {
        // A base URL is required if `filename` is a relative URL, but we don't need to detect the real URL origin.
        pathname = new URL(filename, "https://example.com").pathname;
    } catch (err) {
        // Some invalid filenames, like `///`, could raise a TypeError. Let's fail silently in this situation.
    }
    if (pathname && pathname.length >= 4) {
        const extension = pathname.slice(-4).toLowerCase();
        if (extension === ".swf" || extension === ".spl") {
            return true;
        }
    }
    return false;
}

/**
 * Returns whether the given MIME type is a known Flash type.
 *
 * @param mimeType The MIME type to test.
 * @param allowExtraMimes Whether extra MIME types, non-Flash related, are allowed.
 * @returns True if the MIME type is a Flash MIME type.
 */
function isSwfMimeType(mimeType: string, allowExtraMimes: boolean): boolean {
    mimeType = mimeType.toLowerCase();
    switch (mimeType) {
        case "application/x-shockwave-flash":
        case "application/futuresplash":
        case "application/x-shockwave-flash2-preview":
        case "application/vnd.adobe.flash.movie":
            return true;
        default:
            if (allowExtraMimes) {
                // Allow extra MIME types to improve detection of Flash content.
                // Extension: Some sites (e.g. swfchan.net) might (wrongly?) serve files with octet-stream.
                // Polyfill: Other sites (e.g. #11050) might use octet-stream when defining an <embed> tag.
                switch (mimeType) {
                    case "application/octet-stream":
                    case "binary/octet-stream":
                        return true;
                }
            }
    }
    return false;
}

/**
 * Returns whether the given filename and MIME type resolve as a Flash content.
 *
 * @param filename The filename to test.
 * @param mimeType The MIME type to test.
 * @returns True if the given arguments resolve as a Flash content.
 */
export function isSwfCore(filename: string, mimeType: string | null): boolean {
    const isSwfExtension = isSwfFilename(filename);
    if (!mimeType) {
        // If no MIME type is specified (null or empty string), returns whether the movie ends in a known Flash extension.
        return isSwfExtension;
    } else {
        return isSwfMimeType(mimeType, isSwfExtension);
    }
}
function isSwf(
    details:
        | chrome.webRequest.WebResponseHeadersDetails
        | browser.webRequest._OnHeadersReceivedDetails,
) {
    // TypeScript doesn't compile without this explicit type declaration.
    const headers: (
        | chrome.webRequest.HttpHeader
        | browser.webRequest._HttpHeaders
    )[] = details.responseHeaders!;
    const typeHeader = headers.find(
        ({ name }) => name.toLowerCase() === "content-type",
    );
    if (!typeHeader) {
        return false;
    }
    const mimeType = typeHeader
        .value!.toLowerCase()
        .match(/^\s*(.*?)\s*(?:;.*)?$/)![1]!;
    return isSwfCore(details.url, mimeType);
}
function onHeadersReceived(
    details:
        | chrome.webRequest.WebResponseHeadersDetails
        | browser.webRequest._OnHeadersReceivedDetails,
) {
    if (isSwf(details)) {
        const baseUrl = utils.runtime.getURL("player.html");
        return {
            redirectUrl: `${baseUrl}?url=${encodeURIComponent(details.url)}`,
        };
    }
    return undefined;
}
    (chrome || browser).webRequest.onHeadersReceived.addListener(
        onHeadersReceived,
        {
            urls: ["<all_urls>"],
            types: ["main_frame", "sub_frame"],
        },
        ["blocking", "responseHeaders"],
    );

The right check here for declarativeNetRequest would be checking if either the Content-Type equals, case insensitively, one of "application/x-shockwave-flash", "application/futuresplash", "application/x-shockwave-flash2-preview", "application/vnd.adobe.flash.movie" or the URL ends in .swf or .spl and the Content-Type header is not specified or case insensitively matches "binary/octet-stream" or "application/octet-stream".

@danielhjacobs danielhjacobs changed the title Proposal: Add RuleCondition to declartiveNetRequest that can do everything done by webRequest.onHeadersReceived with webRequestBlocking Proposal: Add RuleCondition to declartiveNetRequest that can do check the headers as done by webRequest.onHeadersReceived with webRequestBlocking Jun 4, 2024
@danielhjacobs
Copy link
Contributor Author

danielhjacobs commented Jun 4, 2024

If there was a condition to check the headers, that could be done with the following rules:

        const rules = [
            {
                id: 1,
                action: {
                    type: (chrome || browser).declarativeNetRequest.RuleActionType.REDIRECT,
                    redirect: { regexSubstitution: (chrome || browser).runtime.getURL("/player.html") + "#\\0" }
                },
                condition: {
                    regexFilter: "^.*$",
                    headers: {
                        "Content-Type": "^application\/(?:x-shockwave-flash|futuresplash|x-shockwave-flash2-preview|vnd\.adobe\.flash\.movie)$"
                    },
                    resourceTypes: [
                        (chrome || browser).declarativeNetRequest.ResourceType.MAIN_FRAME
                    ]
                }
            },
            {
                id: 2,
                action: {
                    type: (chrome || browser).declarativeNetRequest.RuleActionType.REDIRECT,
                    redirect: { regexSubstitution: (chrome || browser).runtime.getURL("/player.html") + "#\\0" }
                },
                condition: {
                    regexFilter: "^.*\\.s(?:wf|pl)(\\?.*|#.*|)$",
                    headers: {
                        "Content-Type": "^(?:(?:application|binary)\/octet-stream|)$"
                    },
                    resourceTypes: [
                        (chrome || browser).declarativeNetRequest.ResourceType.MAIN_FRAME
                    ]
                }
            }
        ];

@danielhjacobs danielhjacobs changed the title Proposal: Add RuleCondition to declartiveNetRequest that can do check the headers as done by webRequest.onHeadersReceived with webRequestBlocking Proposal: Add RuleCondition to declartiveNetRequest that can check the headers as done by webRequest.onHeadersReceived with webRequestBlocking Jun 4, 2024
@danielhjacobs danielhjacobs changed the title Proposal: Add RuleCondition to declartiveNetRequest that can check the headers as done by webRequest.onHeadersReceived with webRequestBlocking Proposal: Add RuleCondition to declartiveNetRequest that can get the headers Jun 4, 2024
@danielhjacobs danielhjacobs changed the title Proposal: Add RuleCondition to declartiveNetRequest that can get the headers Proposal: Add RuleCondition to declarativeNetRequest that can get the headers Jun 4, 2024
@danielhjacobs danielhjacobs changed the title Proposal: Add RuleCondition to declarativeNetRequest that can get the headers Proposal: Add RuleCondition to declarativeNetRequest that can check the headers Jun 4, 2024
@tophf
Copy link

tophf commented Jun 5, 2024

Sounds like #460 and it's already implemented in Chrome Canary, see the internal documentation (it'll be published in readable form once the API goes stable, I guess).

@danielhjacobs
Copy link
Contributor Author

Yeah, I suppose it sounds like this can be done with the following rules according to that documentation.

                    regexFilter: "^.*$",
                    responseHeaders: [
                        { header: "content-type", values: [ "application/x-shockwave-flash", "application/futuresplash", "application/x-shockwave-flash2-preview", "application/vnd.adobe.flash.movie" ] }
                    ],
                    regexFilter: "^.*\\.s(?:wf|pl)(\\?.*|#.*|)$",
                    responseHeaders: [
                        { header: "content-type", values: [ "application/octet-stream", "application/binary-stream", "" ] }
                    ],

@danielhjacobs danielhjacobs closed this as not planned Won't fix, can't repro, duplicate, stale Jun 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time
Projects
None yet
Development

No branches or pull requests

2 participants