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

feat: html blocks! #875

Merged
merged 7 commits into from May 14, 2024
Merged

Conversation

jennspencer
Copy link
Collaborator

PR App Fix RM-9019

🧰 Changes

HTML for everyone!

🧬 QA & Testing

Copy link
Collaborator

@kellyjosephprice kellyjosephprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, except for my question

Comment on lines +19 to +63
it('runs user scripts in compat mode', () => {
render(<HTMLBlock runScripts={true}>{`<script>mockFn()</script>`}</HTMLBlock>);
expect(global.mockFn).toHaveBeenCalledTimes(1);
});

it("doesn't run user scripts by default", () => {
render(<HTMLBlock>{`<script>mockFn()</script>`}</HTMLBlock>);
expect(global.mockFn).toHaveBeenCalledTimes(0);
});

it("doesn't render user scripts by default", () => {
render(<HTMLBlock>{`<script>mockFn()</script>`}</HTMLBlock>);
expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
});

it("doesn't render user scripts with weird endings", () => {
render(<HTMLBlock>{`<script>mockFn()</script foo='bar'>`}</HTMLBlock>);
expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
});

it("doesn't render user scripts with a malicious string", () => {
render(<HTMLBlock>{`<scrip<script></script>t>mockFn()</s<script></script>cript>`}</HTMLBlock>);
expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
});

it("doesn't run scripts on the server (even in compat mode)", () => {
const html = `
<h1>Hello World</h1>
<script>mockFn()</script>
`;
const elem = <HTMLBlock runScripts={true}>{html}</HTMLBlock>;
const view = renderToString(elem);
expect(elem.props.runScripts).toBe(true);
expect(view.indexOf('<script>')).toBeLessThan(0);
expect(view.indexOf('<h1>')).toBeGreaterThanOrEqual(0);
});

it('renders the html in a `<pre>` tag if safeMode={true}', async () => {
const md = '<HTMLBlock safeMode={true}>{`<button onload="alert(\'gotcha!\')"/>`}</HTMLBlock>';
const code = compile(md);
const Component = await run(code);
expect(renderToStaticMarkup(<Component />)).toMatchInlineSnapshot(
'"<pre class="html-unsafe"><code>&lt;button onload=&quot;alert(&#x27;gotcha!&#x27;)&quot;/&gt;</code></pre>"',
);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can scrap all these safety features now that all of the doc is executable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol honestly all these tests helped me dial the component in. i didn't realize i had swapped the "cleaned" vs "dirty" html until i got to the veeeery last test

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep whatever tests, but I think we can wholesale remove safeMode and runScripts

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was actually going to ask you about that -- runScripts is a component prop, but safeMode and lazyImages are both processor options/settings for the user. we still need to support these in a similar way, yeah? i tried making the components basically HOC and passing args that way, but useMDXComponents was Not Having It

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of HOC's we should probably create a context for the options?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really disappointed in myself that I didn't do that 2 years ago.

scripts.push(match[1]);
}
const cleaned = html.replace(MATCH_SCRIPT_TAGS, '');
return [cleaned, () => scripts.map(js => window.eval(js))];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like, there's no reason to strip script tags if the whole thing is getting executed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it only gets executed if you pass runScripts=true? we should probably leave it to the user to decide whether or not they wanna get h4x0rd

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTML blocks were originally a way to say, hey this code is dangerous and we know it. All other 'inlined' html would get tags and attributes sanitized. But I don't see how we could even begin to sanitize MDX.

The only thing I can think of, is if some existing page with an html block has an unknown malicious script in it, and all of sudden we gave it life?

if (typeof window !== 'undefined' && typeof runScripts === 'boolean' && runScripts) exec();
}, [runScripts, exec]);

if (safeMode) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeMode was my silly attempt to make suggested edits safer. Suggested edits could be submitted by anonymous users. And it features a preview mode, so if the submitter wasn't an admin, we wouldn't render the HTML. 🤷🏼

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that was the usage for us sure, but i'm wondering about the 1000 or downloads a week of @readme/markdown? is this new improved renderer going to be internal only or are we improving on the current one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, we can figure out the final details in another PR since it's a bit out of scope for this one 😅

@jennspencer jennspencer merged commit 1761667 into beta May 14, 2024
10 of 11 checks passed
@jennspencer jennspencer deleted the jenn/rm-9019-convert-html-blocks-to-mdx branch May 14, 2024 17:25
rafegoldberg pushed a commit that referenced this pull request May 14, 2024
## Version 6.75.0-beta.31

### ✨ New & Improved

* html blocks! ([#875](#875)) ([1761667](1761667))

<!--SKIP CI-->
@rafegoldberg
Copy link
Contributor

This PR was released!

🚀 Changes included in v6.75.0-beta.31

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

3 participants