diff --git a/lib/core.js b/lib/core.js index e60030ad0..8c104c082 100644 --- a/lib/core.js +++ b/lib/core.js @@ -12,7 +12,7 @@ import * as htmlUtils from './html-utils.js'; import * as metaUtils from './plugins/system/meta/utils.js'; import mediaPlugin from './plugins/validators/media.js'; - import { difference, intersection } from '../utils.js'; + import { difference, intersection, normalizeQueryOptionValue } from '../utils.js'; const plugins = pluginLoader._plugins, pluginsModules = pluginLoader._pluginsModules, @@ -1509,21 +1509,6 @@ } } - function normalizeValue(value) { - if (value === 'true') { - return true; - } - if (value === 'false') { - return false; - } - if (value.match && value.match(/^\d+$/)) { - return parseInt(value); - } - if (value.match && value.match(/^(\d+)?\.\d+$/)) { - return parseFloat(value); - } - return value; - } function getQueryOptionsFromEntries(queryEntries) { const _RE = /^_.+/; @@ -1537,7 +1522,7 @@ queryEntries.forEach((value, key) => { if (key.length > 1 && _RE.test(key)) { - value = normalizeValue(value); + value = normalizeQueryOptionValue(value); var realKey = key.substr(1); result[realKey] = value; } diff --git a/modules/api/utils.js b/modules/api/utils.js index 3fcf9e3bc..a00907d09 100644 --- a/modules/api/utils.js +++ b/modules/api/utils.js @@ -1,4 +1,5 @@ import { fetchData } from "../../lib/fetch.js"; +import { normalizeQueryOptionValue } from "../../utils.js"; var _RE = /^_.+/; @@ -14,42 +15,6 @@ export function getProviderOptionsQuery(query) { return providerOptionsQuery; } -const HTML_ESCAPE_MAP = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' -}; - -function escapeHTML(value) { - if (typeof value !== "string") { - return value; - } - return value.replace(/[&<>"']/g, char => HTML_ESCAPE_MAP[char]); -} - -function normalizeValue(value) { - if (value === 'true') { - return true; - } - if (value === 'false') { - return false; - } - if (/^\d+$/.test(value)) { - return parseInt(value); - } - if (/^(\d+)?\.\d+$/.test(value)) { - return parseFloat(value); - } - if (typeof value === 'string') { - // Escape string value in case it will be used in html. - return escapeHTML(value); - } - // Return nothing if unknown type or array. - return; -} - export function getProviderOptionsFromQuery(query) { /* Convert '_option=value' to @@ -64,7 +29,7 @@ export function getProviderOptionsFromQuery(query) { for(var key in query) { if (key.length > 1 && _RE.test(key)) { - var value = normalizeValue(query[key]); + var value = normalizeQueryOptionValue(query[key]); if (typeof value !== 'undefined') { providerOptions[key] = value; } @@ -72,7 +37,7 @@ export function getProviderOptionsFromQuery(query) { } // Move `query.maxwidth` to `providerOptions.maxwidth`. - var maxWidth = normalizeValue(query['maxwidth']); + var maxWidth = normalizeQueryOptionValue(query['maxwidth']); if (maxWidth) { providerOptions.maxwidth = maxWidth; } diff --git a/plugins/domains/twitter.com/twitter.timelines.js b/plugins/domains/twitter.com/twitter.timelines.js index 0d87ef6fc..9734c2a42 100644 --- a/plugins/domains/twitter.com/twitter.timelines.js +++ b/plugins/domains/twitter.com/twitter.timelines.js @@ -1,3 +1,6 @@ +const TWITTER_LIMIT_MIN = 1; +const TWITTER_LIMIT_MAX = 20; + export default { // Embedded Like, Collection, and Moment Timelines are now retired. @@ -79,17 +82,23 @@ export default { var limit = options.getRequestOptions('twitter.limit', (/data\-(?:tweet\-)?limit=\"(\d+)\"/i.test(html) && html.match(/data\-(?:tweet\-)?limit=\"(\d+)\"/i)[1]) - || 20); + || TWITTER_LIMIT_MAX); + + limit = parseInt(limit); + // Check limit type and range. + if (!Number.isInteger(limit) || limit < TWITTER_LIMIT_MIN || limit > TWITTER_LIMIT_MAX) { + limit = TWITTER_LIMIT_MAX; + } if (/data\-(?:tweet\-)?limit=\"(\d+)\"/.test(html)) { html = html.replace(/data\-(?:tweet\-)?limit=\"\d+\"/, ''); } if (height) { - limit = 20; // `data-height` works only if there's no `data-limit`. Let's give it priority. + limit = TWITTER_LIMIT_MAX; // `data-height` works only if there's no `data-limit`. Let's give it priority. } - if (limit !== 20) { + if (limit !== TWITTER_LIMIT_MAX) { html = html.replace(/href="/, 'data-tweet-limit="' + limit + '" href="'); } @@ -111,8 +120,8 @@ export default { label: 'Include up to 20 tweets', value: limit, range: { - max: 20, - min: 1 + max: TWITTER_LIMIT_MAX, + min: TWITTER_LIMIT_MIN } }, theme: { @@ -169,4 +178,4 @@ export default { "https://twitter.com/i/lists/211796334", {skipMixins: ["domain-icon", "oembed-error"]}, {skipMethods: ["getData"]} ] -}; \ No newline at end of file +}; diff --git a/utils.js b/utils.js index b001c45fa..565e515dc 100644 --- a/utils.js +++ b/utils.js @@ -358,3 +358,39 @@ } return [...new Set(merged)]; } + + const HTML_ESCAPE_MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + + function escapeHTML(value) { + if (typeof value !== "string") { + return value; + } + return value.replace(/[&<>"']/g, char => HTML_ESCAPE_MAP[char]); + } + + export function normalizeQueryOptionValue(value) { + if (value === 'true') { + return true; + } + if (value === 'false') { + return false; + } + if (/^\d+$/.test(value)) { + return parseInt(value); + } + if (/^(\d+)?\.\d+$/.test(value)) { + return parseFloat(value); + } + if (typeof value === 'string') { + // Escape string value in case it will be used in html. + return escapeHTML(value); + } + // Return nothing if unknown type or array. + return; + }