Skip to content

Commit

Permalink
Add a way to pass a meta tag max bytes size to proxy (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmalzieu authored Mar 27, 2024
1 parent 1e392c2 commit b6a5a36
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 10 deletions.
10 changes: 10 additions & 0 deletions packages/server/fixtures/frame-with-big-tag.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<html>
<head>
<meta property="fc:frame" content="vNext" />
<!-- This image tag is 2kb -->
<meta property="fc:frame:image" content="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIEZvbnQgQXdlc29tZSBGcmVlIDUuMTUuNCBieSBAZm9udGF3ZXNvbWUgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbSBMaWNlbnNlIC0gaHR0cHM6Ly9mb250YXdlc29tZS5jb20vbGljZW5zZS9mcmVlIChJY29uczogQ0MgQlkgNC4wLCBGb250czogU0lMIE9GTCAxLjEsIENvZGU6IE1JVCBMaWNlbnNlKSAtLT48cGF0aCBkPSJNMjY3LjQyOSA0ODguNTYzQzI2Mi4yODYgNTA3LjU3MyAyNDIuODU4IDUxMiAyMjQgNTEyYy0xOC44NTcgMC0zOC4yODYtNC40MjctNDMuNDI4LTIzLjQzN0MxNzIuOTI3IDQ2MC4xMzQgMTYwIDM4OC44OTggMTYwIDM1NS43NWMwLTM1LjE1NiAzMS4xNDItNDMuNzUgNjQtNDMuNzVzNjQgOC41OTQgNjQgNDMuNzVjMCAzMi45NDktMTIuODcxIDEwNC4xNzktMjAuNTcxIDEzMi44MTN6TTE1Ni44NjcgMjg4LjU1NGMtMTguNjkzLTE4LjMwOC0yOS45NTgtNDQuMTczLTI4Ljc4NC03Mi41OTkgMi4wNTQtNDkuNzI0IDQyLjM5NS04OS45NTYgOTIuMTI0LTkxLjg4MUMyNzQuODYyIDEyMS45NTggMzIwIDE2NS44MDcgMzIwIDIyMGMwIDI2LjgyNy0xMS4wNjQgNTEuMTE2LTI4Ljg2NiA2OC41NTItMi42NzUgMi42Mi0yLjQwMSA2Ljk4Ni42MjggOS4xODcgOS4zMTIgNi43NjUgMTYuNDYgMTUuMzQzIDIxLjIzNCAyNS4zNjMgMS43NDEgMy42NTQgNi40OTcgNC42NiA5LjQ0OSAxLjg5MSAyOC44MjYtMjcuMDQzIDQ2LjU1My02NS43ODMgNDUuNTExLTEwOC41NjUtMS44NTUtNzYuMjA2LTYzLjU5NS0xMzguMjA4LTEzOS43OTMtMTQwLjM2OUMxNDYuODY5IDczLjc1MyA4MCAxMzkuMjE1IDgwIDIyMGMwIDQxLjM2MSAxNy41MzIgNzguNyA0NS41NSAxMDQuOTg5IDIuOTUzIDIuNzcxIDcuNzExIDEuNzcgOS40NTMtMS44ODcgNC43NzQtMTAuMDIxIDExLjkyMy0xOC41OTggMjEuMjM1LTI1LjM2MyAzLjAyOS0yLjIgMy4zMDQtNi41NjYuNjI5LTkuMTg1ek0yMjQgMEMxMDAuMjA0IDAgMCAxMDAuMTg1IDAgMjI0YzAgODkuOTkyIDUyLjYwMiAxNjUuNjQ3IDEyNS43MzkgMjAxLjQwOCA0LjMzMyAyLjExOCA5LjI2Ny0xLjU0NCA4LjUzNS02LjMxLTIuMzgyLTE1LjUxMi00LjM0Mi0zMC45NDYtNS40MDYtNDQuMzM5LS4xNDYtMS44MzYtMS4xNDktMy40ODYtMi42NzgtNC41MTItNDcuNC0zMS44MDYtNzguNTY0LTg2LjAxNi03OC4xODctMTQ3LjM0Ny41OTItOTYuMjM3IDc5LjI5LTE3NC42NDggMTc1LjUyOS0xNzQuODk5QzMyMC43OTMgNDcuNzQ3IDQwMCAxMjYuNzk3IDQwMCAyMjRjMCA2MS45MzItMzIuMTU4IDExNi40OS04MC42NSAxNDcuODY3LS45OTkgMTQuMDM3LTMuMDY5IDMwLjU4OC01LjYyNCA0Ny4yMy0uNzMyIDQuNzY3IDQuMjAzIDguNDI5IDguNTM1IDYuMzFDMzk1LjIyNyAzODkuNzI3IDQ0OCAzMTQuMTg3IDQ0OCAyMjQgNDQ4IDEwMC4yMDUgMzQ3LjgxNSAwIDIyNCAwem0wIDE2MGMtMzUuMzQ2IDAtNjQgMjguNjU0LTY0IDY0czI4LjY1NCA2NCA2NCA2NCA2NC0yOC42NTQgNjQtNjQtMjguNjU0LTY0LTY0LTY0eiIvPjwvc3ZnPg==" />
<meta property="fc:frame:post_url" content="post-url" />
<meta property="og:image" content="test-image" />
</head>
<body />
</html>
18 changes: 10 additions & 8 deletions packages/server/src/handlers.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import type { GetMetadataResponse, PostRedirectResponse } from '@open-frames/proxy-types';

import { CORS_HEADERS } from './constants.js';
import { CORS_HEADERS, TAG_PREFIXES } from './constants.js';
import { ErrorResponse } from './errors.js';
import { extractMetaTags, getFrameInfo } from './parser.js';
import { getMimeType, getProxySafeMediaHeaders, getUrl, metaTagsToObject } from './utils.js';
import { getMaxMetaTagSize, getMimeType, getProxySafeMediaHeaders, getUrl, metaTagsToObject } from './utils.js';

export async function handleGet(req: Request) {
const url = getUrl(req);
console.log(`Processing get metadata request for ${url}`);
if (!url) {
return new Response('Missing url query param', { status: 400 });
}
const { data, headersToForward } = await downloadAndExtract(url);
const maxMetaTagSize = getMaxMetaTagSize(req);
const { data, headersToForward } = await downloadAndExtract(url, maxMetaTagSize);
const res: GetMetadataResponse = {
url,
extractedTags: metaTagsToObject(data),
Expand All @@ -34,7 +35,8 @@ export async function handlePost(req: Request) {
if (!url) {
return new Response('Missing url query param', { status: 400, headers: CORS_HEADERS });
}
const data = await postAndExtract(url, body);
const maxMetaTagSize = getMaxMetaTagSize(req);
const data = await postAndExtract(url, body, maxMetaTagSize);

const res: GetMetadataResponse = {
url,
Expand Down Expand Up @@ -95,7 +97,7 @@ export async function handleMedia(req: Request) {
});
}

export async function postAndExtract(url: string, body: unknown) {
export async function postAndExtract(url: string, body: unknown, maxMetaTagSize: number | undefined) {
const signal = AbortSignal.timeout(10000);
const response = await fetch(url, {
method: 'POST',
Expand All @@ -112,10 +114,10 @@ export async function postAndExtract(url: string, body: unknown) {
}

const text = await response.text();
return extractMetaTags(text);
return extractMetaTags(text, TAG_PREFIXES, maxMetaTagSize);
}

export async function downloadAndExtract(url: string) {
export async function downloadAndExtract(url: string, maxMetaTagSize?: number | undefined) {
const signal = AbortSignal.timeout(10000);
const response = await fetch(url, { redirect: 'follow', signal });
// TODO: Better error handling
Expand All @@ -127,7 +129,7 @@ export async function downloadAndExtract(url: string) {
// TODO: Stream response until you see </head> and then stop
const text = await response.text();

return { data: extractMetaTags(text), headersToForward };
return { data: extractMetaTags(text, TAG_PREFIXES, maxMetaTagSize), headersToForward };
}

export async function findRedirect(url: string, body: unknown): Promise<PostRedirectResponse> {
Expand Down
18 changes: 17 additions & 1 deletion packages/server/src/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ async function serveHtml(port: number) {
const testCases = [
{
file: 'github.html',
maxMetaTagSize: undefined,
expectedTags: {
'og:title': 'oven-sh/bun: Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one',
'og:image': 'https://opengraph.githubassets.com/14c49397fbfdc07e07d589d265396ddb65eda364617f14d1976937a842bb0983/oven-sh/bun',
Expand All @@ -39,6 +40,7 @@ const testCases = [
},
{
file: 'ogp.html',
maxMetaTagSize: undefined,
expectedTags: {
'og:title': 'Open Graph protocol',
'og:image': 'https://ogp.me/logo.png',
Expand All @@ -49,6 +51,7 @@ const testCases = [
},
{
file: 'minimal-frame.html',
maxMetaTagSize: undefined,
expectedTags: {
'fc:frame': EXPECTED_FRAME_FARCASTER_VERSION,
'fc:frame:image': EXPECTED_FRAME_IMAGE,
Expand All @@ -66,6 +69,7 @@ const testCases = [
},
{
file: 'minimal-open-frame.html',
maxMetaTagSize: undefined,
expectedTags: {
'of:accepts:xmtp': '1',
'of:image': EXPECTED_FRAME_IMAGE,
Expand All @@ -84,6 +88,7 @@ const testCases = [
},
{
file: 'mixed-frame.html',
maxMetaTagSize: undefined,
expectedTags: {
'fc:frame': EXPECTED_FRAME_FARCASTER_VERSION,
'fc:frame:image': `fc-${EXPECTED_FRAME_IMAGE}`,
Expand All @@ -106,6 +111,7 @@ const testCases = [
},
{
file: 'frame-with-all-fields.html',
maxMetaTagSize: undefined,
expectedTags: {
'of:version': EXPECTED_FRAME_VERSION,
'of:accepts:xmtp': EXPECTED_FRAME_XMTP_VERSION,
Expand Down Expand Up @@ -164,6 +170,16 @@ const testCases = [
},
},
},
{
file: 'frame-with-big-tag.html',
maxMetaTagSize: 1024,
expectedTags: {
// no image tag because image is 2kb
'fc:frame': EXPECTED_FRAME_FARCASTER_VERSION,
'fc:frame:post_url': EXPECTED_FRAME_POST_URL,
},
// since image tag is not valid, no frame info
},
] as const;

describe('metadata parsing', () => {
Expand All @@ -180,7 +196,7 @@ describe('metadata parsing', () => {

for (const testCase of testCases) {
test(`can extract tags from ${testCase.file}`, async () => {
const { data: metaTags } = await downloadAndExtract(`http://localhost:${PORT}/${testCase.file}`);
const { data: metaTags } = await downloadAndExtract(`http://localhost:${PORT}/${testCase.file}`, testCase.maxMetaTagSize);

const extractedTags = metaTagsToObject(metaTags);
for (const [key, value] of Object.entries(testCase.expectedTags)) {
Expand Down
7 changes: 6 additions & 1 deletion packages/server/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { load } from 'cheerio';

import { ALLOWED_ACTIONS, FRAMES_PREFIXES, TAG_PREFIXES } from './constants.js';
import type { DeepPartial } from './types.js';
import { getStrByteSize } from './utils.js';

type MetaTag = [string, string];

export function extractMetaTags(html: string, tagPrefixes = TAG_PREFIXES) {
export function extractMetaTags(html: string, tagPrefixes = TAG_PREFIXES, maxMetaTagSize: number | undefined) {
const $ = load(html);
const metaTags = $('meta');
const metaTagsArray = Array.from(metaTags);
Expand All @@ -26,6 +27,10 @@ export function extractMetaTags(html: string, tagPrefixes = TAG_PREFIXES) {
return acc;
}

if (maxMetaTagSize && getStrByteSize(content) > maxMetaTagSize) {
return acc;
}

acc.push([property, content]);

return acc;
Expand Down
20 changes: 20 additions & 0 deletions packages/server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ export function getUrl(req: Request) {
return url;
}

export function getMaxMetaTagSize(req: Request) {
const maxMetaTagSize = new URL(req.url).searchParams.get('max-meta-tag-bytes');
if (maxMetaTagSize) {
if (isPositiveInteger(maxMetaTagSize)) {
return Number(maxMetaTagSize);
} else {
throw new ErrorResponse('Could not parse max-meta-tag-length query param', 400);
}
}
return undefined;
}

export function getRequestPath(req: Request): string {
return new URL(req.url).pathname;
}
Expand Down Expand Up @@ -49,3 +61,11 @@ export function metaTagsToObject(tags: [string, string][]): Record<string, strin
{} as Record<string, string>,
);
}

export function getStrByteSize(str: string): number {
return new Blob([str]).size;
}

export function isPositiveInteger(str: string): boolean {
return Number.isInteger(Number(str)) && Number(str) > 0;
}

0 comments on commit b6a5a36

Please sign in to comment.