From 0d04659498b79644b12dbdddbdf36ebc48bce9e8 Mon Sep 17 00:00:00 2001 From: jessopb <36554050+jessopb@users.noreply.github.com> Date: Mon, 25 Feb 2019 20:59:46 -0500 Subject: [PATCH 1/2] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1a90580a..1b85b2fd 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ For a closed, custom-hosted and branded example, check out https://lbry.theantim - `./lbrynet account_balance` gets your balance (initially 0.0) - `./lbrynet address_list` gets addresses you can use to recieve LBC - [FFmpeg](https://www.ffmpeg.org/download.html) +- [ImageMagick](https://packages.ubuntu.com/xenial/graphics/imagemagick) - Spee.ch (below) - pm2 (optional) process manager such as pm2 to run speech server.js - http proxy server e.g. caddy, nginx, or traefik, to forward 80/443 to speech port 3000 -- 2.45.2 From 4c37a6311f9b776d458226fe1ccbb7ae8c168552 Mon Sep 17 00:00:00 2001 From: jessop Date: Fri, 22 Feb 2019 22:47:08 -0500 Subject: [PATCH 2/2] documents and validates image resize --- README.md | 8 ++ cli/defaults/siteConfig.json | 9 +- docs/settings.md | 87 ++++++++++---------- server/controllers/assets/utils/serveFile.js | 36 ++++++-- server/utils/isValidQueryObj.js | 24 ++++++ 5 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 server/utils/isValidQueryObj.js diff --git a/README.md b/README.md index 1a90580a..9813a63a 100644 --- a/README.md +++ b/README.md @@ -260,9 +260,11 @@ Spee.ch has a few types of URL formats that return different assets from the LBR - retrieve the controlling `LBRY` claim: - https://spee.ch/`claim` - https://spee.ch/`claim`.`ext` (serve) + - https://spee.ch/`claim`.`ext`&`querystring` (serve transformed) - retrieve a specific `LBRY` claim: - https://spee.ch/`claim_id`/`claim` - https://spee.ch/`claim_id`/`claim`.`ext` (serve) + - https://spee.ch/`claim_id`/`claim`.`ext`&`querystring` (serve transformed) - retrieve all contents for the controlling `LBRY` channel - https://spee.ch/`@channel` - a specific `LBRY` channel @@ -270,9 +272,15 @@ Spee.ch has a few types of URL formats that return different assets from the LBR - retrieve a specific claim within the controlling `LBRY` channel - https://spee.ch/`@channel`/`claim` - https://spee.ch/`@channel`/`claim`.`ext` (serve) + - https://spee.ch/`@channel`/`claim`.`ext`&`querystring` (serve) - retrieve a specific claim within a specific `LBRY` channel - https://spee.ch/`@channel`:`channel_id`/`claim` - https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve) + - https://spee.ch/`@channel`:`channel_id`/`claim`.`ext`&`querystring` (serve) +- `querystring` can include the following transformation values separated by `&` + - h=`number` (defines height) + - w=`number` (defines width) + - t=`crop` or `stretch` (defines transformation - missing implies constrained proportions) ### Dependencies diff --git a/cli/defaults/siteConfig.json b/cli/defaults/siteConfig.json index b7ff5337..e8af29a7 100644 --- a/cli/defaults/siteConfig.json +++ b/cli/defaults/siteConfig.json @@ -49,6 +49,10 @@ } }, "serving": { + "dynamicFileSizing": { + "enabled": true, + "maxDimension": 2000 + }, "markdownSettings": { "skipHtmlMain": true, "escapeHtmlMain": true, @@ -83,10 +87,7 @@ "code", "html", "parsedHtml" - ], - "disallowedTypesMain": [], - "disallowedTypesDescriptions": ["image", "html"], - "disallowedTypesExample": ["image", "html"] + ] }, "customFileExtensions": { "application/x-troff-man": "man", diff --git a/docs/settings.md b/docs/settings.md index 1bf06aa3..57c3341b 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -26,7 +26,6 @@ PUBLISHING: "primaryClaimAddress": null, - generally supplied by your lbrynet sdk "uploadDirectory": "/home/lbry/Uploads", - lbrynet sdk will know your uploads are here - "lbrynetHome": "/home/lbry", "thumbnailChannel": null, - when publishing non-image content, thumbnails will go here. "thumbnailChannelId": null, "additionalClaimAddresses": [], @@ -50,48 +49,52 @@ PUBLISHING: "application/octet-stream": 50000000 } } - -SERVING: - "markdownSettings": { - "skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~ - "escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly - "skipHtmlDescriptions": true, - as above, for descriptions - "escapeHtmlDescriptions": true, - as above, for descriptions - "allowedTypesMain": [], - markdown rendered as main content - "allowedTypesDescriptions": [], - markdown rendered in description in content details - "allowedTypesExample": [ - here are examples of allowed types - "see react-markdown docs", `https://github.com/rexxars/react-markdown` - "root", - "text", - "break", - "paragraph", - "emphasis", - "strong", - "thematicBreak", - "blockquote", - "delete", - "link", - "image", - you may not have a lot of control over how these are rendered - "linkReference", - "imageReference", - "table", - "tableHead", - "tableBody", - "tableRow", - "tableCell", - "list", - "listItem", - "heading", - "inlineCode", - "code", - "html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs. - "parsedHtml" - ], - }, - "customFileExtensions": { - suggest a file extension for experimental content types you may be publishing - "application/example-type": "example" - } +SERVING: + + "dynamicFileSizing": { +"enabled": false, - if you choose to allow your instance to serve transform images +"maxDimension": 2000 - the maximum size you allow transform to scale +}, +"markdownSettings": { +"skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~ +"escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly +"skipHtmlDescriptions": true, - as above, for descriptions +"escapeHtmlDescriptions": true, - as above, for descriptions +"allowedTypesMain": [], - markdown rendered as main content +"allowedTypesDescriptions": [], - markdown rendered in description in content details +"allowedTypesExample": [ - here are examples of allowed types +"see react-markdown docs", `https://github.com/rexxars/react-markdown` +"root", +"text", +"break", +"paragraph", +"emphasis", +"strong", +"thematicBreak", +"blockquote", +"delete", +"link", +"image", - you may not have a lot of control over how these are rendered +"linkReference", +"imageReference", +"table", +"tableHead", +"tableBody", +"tableRow", +"tableCell", +"list", +"listItem", +"heading", +"inlineCode", +"code", +"html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs. +"parsedHtml" +], +}, +"customFileExtensions": { - suggest a file extension for experimental content types you may be publishing +"application/example-type": "example" +} STARTUP: diff --git a/server/controllers/assets/utils/serveFile.js b/server/controllers/assets/utils/serveFile.js index dce8f984..dce89b83 100644 --- a/server/controllers/assets/utils/serveFile.js +++ b/server/controllers/assets/utils/serveFile.js @@ -1,5 +1,10 @@ const logger = require('winston'); const transformImage = require('./transformImage'); +const isValidQueryObject = require('../../../utils/isValidQueryObj'); +const { + serving: { dynamicFileSizing }, +} = require('@config/siteConfig'); +const { enabled: dynamicEnabled } = dynamicFileSizing; const serveFile = async ({ filePath, fileType }, res, originalUrl) => { const queryObject = {}; // TODO: replace quick/dirty try catch with better practice @@ -21,7 +26,10 @@ const serveFile = async ({ filePath, fileType }, res, originalUrl) => { let mediaType = fileType ? fileType.substr(0, fileType.indexOf('/')) : ''; const transform = - mediaType === 'image' && queryObject.hasOwnProperty('h') && queryObject.hasOwnProperty('w'); + mediaType === 'image' && + queryObject.hasOwnProperty('h') && + queryObject.hasOwnProperty('w') && + dynamicEnabled; const sendFileOptions = { headers: { @@ -32,14 +40,26 @@ const serveFile = async ({ filePath, fileType }, res, originalUrl) => { }, }; logger.debug(`fileOptions for ${filePath}:`, sendFileOptions); - if (transform) { - logger.debug(`transforming and sending file`); + try { + if (transform) { + if (!isValidQueryObject(queryObject)) { + logger.debug(`Unacceptable querystring`, { queryObject }); + res.status(400).json({ + success: false, + message: 'Querystring may not have dimensions greater than 2000', + }); + res.end(); + } + logger.debug(`transforming and sending file`); - let xformed = await transformImage(filePath, queryObject); - res.status(200).set(sendFileOptions.headers); - res.end(xformed, 'binary'); - } else { - res.status(200).sendFile(filePath, sendFileOptions); + let xformed = await transformImage(filePath, queryObject); + res.status(200).set(sendFileOptions.headers); + res.end(xformed, 'binary'); + } else { + res.status(200).sendFile(filePath, sendFileOptions); + } + } catch (e) { + logger.debug(e); } }; diff --git a/server/utils/isValidQueryObj.js b/server/utils/isValidQueryObj.js new file mode 100644 index 00000000..cbebb337 --- /dev/null +++ b/server/utils/isValidQueryObj.js @@ -0,0 +1,24 @@ +const { + serving: { dynamicFileSizing }, +} = require('@config/siteConfig'); +const { maxDimension } = dynamicFileSizing; + +const isValidQueryObj = queryObj => { + let { + h: cHeight = null, + w: cWidth = null, + t: transform = null, + x: xOrigin = null, + y: yOrigin = null, + } = queryObj; + + return ( + ((cHeight <= maxDimension && cHeight > 0) || cHeight === null) && + ((cWidth <= maxDimension && cWidth > 0) || cWidth === null) && + (transform === null || transform === 'crop' || transform === 'stretch') && + ((xOrigin <= maxDimension && xOrigin >= 0) || xOrigin === null) && + ((yOrigin <= maxDimension && yOrigin >= 0) || yOrigin === null) + ); +}; + +module.exports = isValidQueryObj; -- 2.45.2