diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d64ce..1678f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Unreleased +------------------ +* oEmbed type=richの制限的なサポート + 3.0.4 / 2023-02-12 ------------------ * 不要な依存関係を除去 diff --git a/README.md b/README.md index 21c6f3d..aaf9012 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ import { summaly } from 'summaly'; summaly(url[, opts]) ``` -As Fastify plugin: +As Fastify plugin: (will listen `GET` of `/`) ```javascript @@ -60,27 +60,39 @@ interface IPlugin { A Promise of an Object that contains properties below: -※ Almost all values are nullable. player shoud not be null. +※ Almost all values are nullable. player should not be null. #### Root -| Property | Type | Description | -| :-------------- | :------- | :--------------------------------------- | -| **description** | *string* | The description of the web page | -| **icon** | *string* | The url of the icon of the web page | -| **sitename** | *string* | The name of the web site | -| **thumbnail** | *string* | The url of the thumbnail of the web page | -| **player** | *Player* | The player of the web page | -| **title** | *string* | The title of the web page | -| **url** | *string* | The url of the web page | +| Property | Type | Description | +| :-------------- | :------- | :------------------------------------------ | +| **description** | *string* | The description of the web page | +| **icon** | *string* | The url of the icon of the web page | +| **sitename** | *string* | The name of the web site | +| **thumbnail** | *string* | The url of the thumbnail of the web page | +| **oEmbed** | *OEmbedRichIframe* | The oEmbed rich iframe info of the web page | +| **player** | *Player* | The player of the web page | +| **title** | *string* | The title of the web page | +| **url** | *string* | The url of the web page | #### Player -| Property | Type | Description | -| :-------------- | :------- | :--------------------------------------- | -| **url** | *string* | The url of the player | -| **width** | *number* | The width of the player | -| **height** | *number* | The height of the player | +| Property | Type | Description | +| :-------------- | :--------- | :---------------------------------------------- | +| **url** | *string* | The url of the player | +| **width** | *number* | The width of the player | +| **height** | *number* | The height of the player | +| **allow** | *string[]* | The names of the allowed permissions for iframe | + +Currently the possible items in `allow` are: + +* `autoplay` +* `clipboard-write` +* `fullscreen` +* `encrypted-media` +* `picture-in-picture` + +See [Permissions Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Permissions_Policy) in MDN for details of them. ### Example diff --git a/built/general.d.ts b/built/general.d.ts index 95fe685..c54a068 100644 --- a/built/general.d.ts +++ b/built/general.d.ts @@ -1,4 +1,4 @@ -import * as URL from 'node:url'; -import Summary from './summary.js'; -declare const _default: (url: URL.Url, lang?: string | null) => Promise; +import { URL } from 'node:url'; +import type { default as Summary } from './summary.js'; +declare const _default: (_url: URL | string, lang?: string | null) => Promise; export default _default; diff --git a/built/general.js b/built/general.js index ac1d711..e8104be 100644 --- a/built/general.js +++ b/built/general.js @@ -1,11 +1,109 @@ -import * as URL from 'node:url'; +import { URL } from 'node:url'; import clip from './utils/clip.js'; import cleanupTitle from './utils/cleanup-title.js'; import { decode as decodeHtml } from 'html-entities'; -import { head, scpaping } from './utils/got.js'; -export default async (url, lang = null) => { +import { get, head, scpaping } from './utils/got.js'; +import * as cheerio from 'cheerio'; +/** + * Contains only the html snippet for a sanitized iframe as the thumbnail is + * mostly covered in OpenGraph instead. + * + * Width should always be 100%. + */ +async function getOEmbedPlayer($, pageUrl) { + const href = $('link[type="application/json+oembed"]').attr('href'); + if (!href) { + return null; + } + const oEmbed = await get((new URL(href, pageUrl)).href); + const body = (() => { + try { + return JSON.parse(oEmbed); + } + catch { } + })(); + if (!body || body.version !== '1.0' || !['rich', 'video'].includes(body.type)) { + // Not a well formed rich oEmbed + return null; + } + if (!body.html.startsWith('')) { + // It includes something else than an iframe + return null; + } + + const oEmbedHtml = cheerio.load(body.html); + const iframe = oEmbedHtml("iframe"); + + if (iframe.length !== 1) { + // Somehow we either have multiple iframes or none + return null; + } + + if (iframe.parents().length !== 2) { + // Should only have the body and html elements as the parents + return null; + } + + const url = iframe.attr('src'); + if (!url) { + // No src? + return null; + } + + try { + if ((new URL(url)).protocol !== 'https:') { + // Allow only HTTPS for best security + return null; + } + } catch (e) { + return null; + } + + // Height is the most important, width is okay to be null. The implementer + // should choose fixed height instead of fixed aspect ratio if width is null. + // + // For example, Spotify's embed page does not strictly follow aspect ratio + // and thus keeping the height is better than keeping the aspect ratio. + // + // Spotify gives `width: 100%, height: 152px` for iframe while `width: 456, + // height: 152` for oEmbed data, and we treat any percentages as null here. + let width: number | null = Number(iframe.attr('width') ?? body.width); + if (Number.isNaN(width)) { + width = null; + } + const height = Math.min(Number(iframe.attr('height') ?? body.height), 1024); + if (Number.isNaN(height)) { + // No proper height info + return null; + } + + // TODO: This implementation only allows basic syntax of `allow`. + // Might need to implement better later. + const safeList = [ + 'autoplay', + 'clipboard-write', + 'fullscreen', + 'encrypted-media', + 'picture-in-picture', + 'web-share', + ]; + // YouTube has these but they are almost never used. + const ignoredList = [ + 'gyroscope', + 'accelerometer', + ]; + const allowedPermissions = + (iframe.attr('allow') ?? '').split(/\s*;\s*/g) + .filter(s => s) + .filter(s => !ignoredList.includes(s)); + if (allowedPermissions.some(allow => !safeList.includes(allow))) { + // This iframe is probably too powerful to be embedded + return null; + } + + return { + url, + width, + height, + allow: allowedPermissions + } +} + +export default async (_url: URL | string, lang: string | null = null): Promise => { if (lang && !lang.match(/^[\w-]+(\s*,\s*[\w-]+)*$/)) lang = null; + const url = typeof _url === 'string' ? new URL(_url) : _url; + const res = await scpaping(url.href, { lang: lang || undefined }); const $ = res.$; const twitterCard = $('meta[property="twitter:card"]').attr('content'); @@ -32,7 +142,7 @@ export default async (url: URL.Url, lang: string | null = null): Promise { - const target = URL.resolve(url.href, path); + const target = new URL(path, url.href); try { - await head(target); + await head(target.href); return target; } catch (e) { return null; } }; - // 相対的なURL (ex. test) を絶対的 (ex. /test) に変換 - const toAbsolute = (relativeURLString: string): string => { - const relativeURL = URL.parse(relativeURLString); - const isAbsolute = relativeURL.slashes || relativeURL.path !== null && relativeURL.path[0] === '/'; + const getIcon = async () => { + return (await find(favicon)) || null; + } - // 既に絶対的なら、即座に値を返却 - if (isAbsolute) { - return relativeURLString; - } - - // スラッシュを付けて返却 - return '/' + relativeURLString; - }; - - const icon = await find(favicon) || - // 相対指定を絶対指定に変換し再試行 - await find(toAbsolute(favicon)) || - null; + const [icon, oEmbed] = await Promise.all([ + getIcon(), + getOEmbedPlayer($, url.href), + ]) // Clean up the title title = cleanupTitle(title, siteName); @@ -118,13 +217,14 @@ export default async (url: URL.Url, lang: string | null = null): Promise = } } - const _url = URL.parse(actualUrl, true); + const _url = new URL(actualUrl); // Find matching plugin const match = plugins.filter(plugin => plugin.test(_url))[0]; diff --git a/src/iplugin.ts b/src/iplugin.ts index 8ad0e1f..2aabf86 100644 --- a/src/iplugin.ts +++ b/src/iplugin.ts @@ -1,7 +1,7 @@ -import * as URL from 'node:url'; +import type { URL } from 'node:url'; import Summary from './summary.js'; export interface IPlugin { - test: (url: URL.Url) => boolean; - summarize: (url: URL.Url, lang?: string) => Promise; + test: (url: URL) => boolean; + summarize: (url: URL, lang?: string) => Promise; } diff --git a/src/plugins/amazon.ts b/src/plugins/amazon.ts index 5c3f1ee..5f10d5d 100644 --- a/src/plugins/amazon.ts +++ b/src/plugins/amazon.ts @@ -1,8 +1,8 @@ -import * as URL from 'node:url'; +import { URL } from 'node:url'; import { scpaping } from '../utils/got.js'; import summary from '../summary.js'; -export function test(url: URL.Url): boolean { +export function test(url: URL): boolean { return url.hostname === 'www.amazon.com' || url.hostname === 'www.amazon.co.jp' || url.hostname === 'www.amazon.ca' || @@ -19,7 +19,7 @@ export function test(url: URL.Url): boolean { url.hostname === 'www.amazon.au'; } -export async function summarize(url: URL.Url): Promise { +export async function summarize(url: URL): Promise { const res = await scpaping(url.href); const $ = res.$; @@ -51,8 +51,9 @@ export async function summarize(url: URL.Url): Promise { player: { url: playerUrl || null, width: playerWidth ? parseInt(playerWidth) : null, - height: playerHeight ? parseInt(playerHeight) : null + height: playerHeight ? parseInt(playerHeight) : null, + allow: playerUrl ? ['fullscreen', 'encrypted-media'] : [], }, - sitename: 'Amazon' + sitename: 'Amazon', }; } diff --git a/src/plugins/wikipedia.ts b/src/plugins/wikipedia.ts index aa42718..9249dac 100644 --- a/src/plugins/wikipedia.ts +++ b/src/plugins/wikipedia.ts @@ -1,4 +1,4 @@ -import * as URL from 'node:url'; +import { URL } from 'node:url'; import { get } from '../utils/got.js'; import debug from 'debug'; import summary from '../summary.js'; @@ -6,12 +6,12 @@ import clip from './../utils/clip.js'; const log = debug('summaly:plugins:wikipedia'); -export function test(url: URL.Url): boolean { +export function test(url: URL): boolean { if (!url.hostname) return false; return /\.wikipedia\.org$/.test(url.hostname); } -export async function summarize(url: URL.Url): Promise { +export async function summarize(url: URL): Promise { const lang = url.host ? url.host.split('.')[0] : null; const title = url.pathname ? url.pathname.split('/')[2] : null; const endpoint = `https://${lang}.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=${title}`; @@ -38,8 +38,9 @@ export async function summarize(url: URL.Url): Promise { player: { url: null, width: null, - height: null + height: null, + allow: [], }, - sitename: 'Wikipedia' + sitename: 'Wikipedia', }; } diff --git a/src/summary.ts b/src/summary.ts index 0f38301..cc580cc 100644 --- a/src/summary.ts +++ b/src/summary.ts @@ -52,4 +52,9 @@ export type Player = { * The height of the player */ height: number | null; + + /** + * The allowed permissions of the iframe + */ + allow: string[]; }; diff --git a/src/utils/got.ts b/src/utils/got.ts index 22fc052..94b8063 100644 --- a/src/utils/got.ts +++ b/src/utils/got.ts @@ -108,16 +108,17 @@ async function getResponse(args: GotOptions) { }, }); - return await receiveResponce({ req, typeFilter: args.typeFilter }); + return await receiveResponse({ req, typeFilter: args.typeFilter }); } -async function receiveResponce(args: { req: Got.CancelableRequest>, typeFilter?: RegExp }) { +async function receiveResponse(args: { req: Got.CancelableRequest>, typeFilter?: RegExp }) { const req = args.req; const maxSize = MAX_RESPONSE_SIZE; req.on('response', (res: Got.Response) => { // Check html if (args.typeFilter && !res.headers['content-type']?.match(args.typeFilter)) { + // console.warn(res.headers['content-type']); req.cancel(`Rejected by type filter ${res.headers['content-type']}`); return; } diff --git a/test/htmls/oembed-and-og-video.html b/test/htmls/oembed-and-og-video.html new file mode 100644 index 0000000..68758da --- /dev/null +++ b/test/htmls/oembed-and-og-video.html @@ -0,0 +1,3 @@ + + + diff --git a/test/htmls/oembed-and-og.html b/test/htmls/oembed-and-og.html new file mode 100644 index 0000000..d9c7791 --- /dev/null +++ b/test/htmls/oembed-and-og.html @@ -0,0 +1,3 @@ + + + diff --git a/test/htmls/oembed-nonexistent-path.html b/test/htmls/oembed-nonexistent-path.html new file mode 100644 index 0000000..50813b0 --- /dev/null +++ b/test/htmls/oembed-nonexistent-path.html @@ -0,0 +1,2 @@ + + diff --git a/test/htmls/oembed-relative.html b/test/htmls/oembed-relative.html new file mode 100644 index 0000000..f4f8ecc --- /dev/null +++ b/test/htmls/oembed-relative.html @@ -0,0 +1,2 @@ + + diff --git a/test/htmls/oembed-wrong.html b/test/htmls/oembed-wrong.html new file mode 100644 index 0000000..4327b9d --- /dev/null +++ b/test/htmls/oembed-wrong.html @@ -0,0 +1,2 @@ + + diff --git a/test/htmls/oembed.html b/test/htmls/oembed.html new file mode 100644 index 0000000..c05e03b --- /dev/null +++ b/test/htmls/oembed.html @@ -0,0 +1,2 @@ + + diff --git a/test/index.ts b/test/index.ts index 0f2506f..e1a3d2f 100644 --- a/test/index.ts +++ b/test/index.ts @@ -6,13 +6,13 @@ /* dependencies below */ -import fs from 'node:fs'; +import fs, { readdirSync } from 'node:fs'; import process from 'node:process'; import fastify from 'fastify'; import { summaly } from '../src/index.js'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import {expect, jest, test, describe, beforeEach, afterEach} from '@jest/globals'; +import { expect, jest, test, describe, beforeEach, afterEach } from '@jest/globals'; import { Agent as httpAgent } from 'node:http'; import { Agent as httpsAgent } from 'node:https'; import { StatusError } from '../src/utils/status-error.js'; @@ -213,6 +213,7 @@ describe('TwitterCard', () => { const summary = await summaly(host); expect(summary.player.url).toBe('https://example.com/embedurl'); + expect(summary.player.allow).toStrictEqual(['autoplay', 'encrypted-media', 'fullscreen']); }); test('Player detection - Pleroma:video => video', async () => { @@ -224,6 +225,7 @@ describe('TwitterCard', () => { const summary = await summaly(host); expect(summary.player.url).toBe('https://example.com/embedurl'); + expect(summary.player.allow).toStrictEqual(['autoplay', 'encrypted-media', 'fullscreen']); }); test('Player detection - Pleroma:image => image', async () => { @@ -237,3 +239,122 @@ describe('TwitterCard', () => { expect(summary.thumbnail).toBe('https://example.com/imageurl'); }); }); + +describe("oEmbed", () => { + const setUpFastify = async (oEmbedPath: string, htmlPath = 'htmls/oembed.html') => { + app = fastify(); + app.get('/', (request, reply) => { + return reply.send(fs.createReadStream(new URL(htmlPath, import.meta.url))); + }); + app.get('/oembed.json', (request, reply) => { + return reply.send(fs.createReadStream( + new URL(oEmbedPath, new URL('oembed/', import.meta.url)) + )); + }); + await app.listen({ port }); + } + + for (const filename of readdirSync(new URL('oembed/invalid', import.meta.url))) { + test(`Invalidity test: ${filename}`, async () => { + await setUpFastify(`invalid/${filename}`); + const summary = await summaly(host); + expect(summary.player.url).toBe(null); + }); + } + + test('basic properties', async () => { + await setUpFastify('oembed.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.width).toBe(500); + expect(summary.player.height).toBe(300); + }); + + test('type: video', async () => { + await setUpFastify('oembed-video.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.width).toBe(500); + expect(summary.player.height).toBe(300); + }); + + test('max height', async () => { + await setUpFastify('oembed-too-tall.json'); + const summary = await summaly(host); + expect(summary.player.height).toBe(1024); + }); + + test('children are ignored', async () => { + await setUpFastify('oembed-iframe-child.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + }); + + test('allows fullscreen', async () => { + await setUpFastify('oembed-allow-fullscreen.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.allow).toStrictEqual(['fullscreen']) + }); + + test('allows safelisted permissions', async () => { + await setUpFastify('oembed-allow-safelisted-permissions.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.allow).toStrictEqual([ + 'autoplay', 'clipboard-write', 'fullscreen', + 'encrypted-media', 'picture-in-picture', 'web-share', + ]); + }); + + test('ignores rare permissions', async () => { + await setUpFastify('oembed-ignore-rare-permissions.json'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.allow).toStrictEqual(['autoplay']); + }); + + test('oEmbed with relative path', async () => { + await setUpFastify('oembed.json', 'htmls/oembed-relative.html'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + }); + + test('oEmbed with nonexistent path', async () => { + await setUpFastify('oembed.json', 'htmls/oembed-nonexistent-path.html'); + await expect(summaly(host)).rejects.toThrow('404 Not Found'); + }); + + test('oEmbed with wrong path', async () => { + await setUpFastify('oembed.json', 'htmls/oembed-wrong-path.html'); + await expect(summaly(host)).rejects.toThrow(); + }); + + test('oEmbed with OpenGraph', async () => { + await setUpFastify('oembed.json', 'htmls/oembed-and-og.html'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.description).toBe('blobcats rule the world'); + }); + + test('Invalid oEmbed with valid OpenGraph', async () => { + await setUpFastify('invalid/oembed-insecure.json', 'htmls/oembed-and-og.html'); + const summary = await summaly(host); + expect(summary.player.url).toBe(null); + expect(summary.description).toBe('blobcats rule the world'); + }); + + test('oEmbed with og:video', async () => { + await setUpFastify('oembed.json', 'htmls/oembed-and-og-video.html'); + const summary = await summaly(host); + expect(summary.player.url).toBe('https://example.com/'); + expect(summary.player.allow).toStrictEqual([]); + }); + + test('width: 100%', async () => { + await setUpFastify('oembed-percentage-width.json'); + const summary = await summaly(host); + expect(summary.player.width).toBe(null); + expect(summary.player.height).toBe(300); + }); +}); diff --git a/test/oembed/invalid/oembed-child-iframe.json b/test/oembed/invalid/oembed-child-iframe.json new file mode 100644 index 0000000..f7bf052 --- /dev/null +++ b/test/oembed/invalid/oembed-child-iframe.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "
", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-double-iframes.json b/test/oembed/invalid/oembed-double-iframes.json new file mode 100644 index 0000000..2e5b0c8 --- /dev/null +++ b/test/oembed/invalid/oembed-double-iframes.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-future.json b/test/oembed/invalid/oembed-future.json new file mode 100644 index 0000000..db356c0 --- /dev/null +++ b/test/oembed/invalid/oembed-future.json @@ -0,0 +1,7 @@ +{ + "version": "11.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-insecure.json b/test/oembed/invalid/oembed-insecure.json new file mode 100644 index 0000000..db2ebf4 --- /dev/null +++ b/test/oembed/invalid/oembed-insecure.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-invalid-height.json b/test/oembed/invalid/oembed-invalid-height.json new file mode 100644 index 0000000..1f42210 --- /dev/null +++ b/test/oembed/invalid/oembed-invalid-height.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": "blobcat" +} diff --git a/test/oembed/invalid/oembed-no-height.json b/test/oembed/invalid/oembed-no-height.json new file mode 100644 index 0000000..0658bc2 --- /dev/null +++ b/test/oembed/invalid/oembed-no-height.json @@ -0,0 +1,6 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500 +} diff --git a/test/oembed/invalid/oembed-no-version.json b/test/oembed/invalid/oembed-no-version.json new file mode 100644 index 0000000..d09f036 --- /dev/null +++ b/test/oembed/invalid/oembed-no-version.json @@ -0,0 +1,6 @@ +{ + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-old.json b/test/oembed/invalid/oembed-old.json new file mode 100644 index 0000000..dc1c1c4 --- /dev/null +++ b/test/oembed/invalid/oembed-old.json @@ -0,0 +1,7 @@ +{ + "version": "0.1", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-photo.json b/test/oembed/invalid/oembed-photo.json new file mode 100644 index 0000000..ababfe4 --- /dev/null +++ b/test/oembed/invalid/oembed-photo.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "photo", + "url": "https://example.com/example.avif", + "width": 300, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-too-powerful.json b/test/oembed/invalid/oembed-too-powerful.json new file mode 100644 index 0000000..6456ff7 --- /dev/null +++ b/test/oembed/invalid/oembed-too-powerful.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/invalid/oembed-too-powerful2.json b/test/oembed/invalid/oembed-too-powerful2.json new file mode 100644 index 0000000..27c1ad1 --- /dev/null +++ b/test/oembed/invalid/oembed-too-powerful2.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed-allow-fullscreen.json b/test/oembed/oembed-allow-fullscreen.json new file mode 100644 index 0000000..286fbdd --- /dev/null +++ b/test/oembed/oembed-allow-fullscreen.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed-allow-safelisted-permissions.json b/test/oembed/oembed-allow-safelisted-permissions.json new file mode 100644 index 0000000..247441b --- /dev/null +++ b/test/oembed/oembed-allow-safelisted-permissions.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed-iframe-child.json b/test/oembed/oembed-iframe-child.json new file mode 100644 index 0000000..10ca745 --- /dev/null +++ b/test/oembed/oembed-iframe-child.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed-ignore-rare-permissions.json b/test/oembed/oembed-ignore-rare-permissions.json new file mode 100644 index 0000000..78e7fc8 --- /dev/null +++ b/test/oembed/oembed-ignore-rare-permissions.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed-percentage-width.json b/test/oembed/oembed-percentage-width.json new file mode 100644 index 0000000..b55c2a1 --- /dev/null +++ b/test/oembed/oembed-percentage-width.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": "100%", + "height": 300 +} diff --git a/test/oembed/oembed-too-tall.json b/test/oembed/oembed-too-tall.json new file mode 100644 index 0000000..3f857c0 --- /dev/null +++ b/test/oembed/oembed-too-tall.json @@ -0,0 +1,6 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "height": 3000 +} diff --git a/test/oembed/oembed-video.json b/test/oembed/oembed-video.json new file mode 100644 index 0000000..7537c7c --- /dev/null +++ b/test/oembed/oembed-video.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "video", + "html": "", + "width": 500, + "height": 300 +} diff --git a/test/oembed/oembed.json b/test/oembed/oembed.json new file mode 100644 index 0000000..704c37e --- /dev/null +++ b/test/oembed/oembed.json @@ -0,0 +1,7 @@ +{ + "version": "1.0", + "type": "rich", + "html": "", + "width": 500, + "height": 300 +}