Merge pull request #933 from jessopb/croppingSafe

documents and validates image resize
This commit is contained in:
jessopb 2019-02-26 03:28:32 -05:00 committed by GitHub
commit 3cd7b6080d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 54 deletions

View file

@ -261,9 +261,11 @@ Spee.ch has a few types of URL formats that return different assets from the LBR
- retrieve the controlling `LBRY` claim: - retrieve the controlling `LBRY` claim:
- https://spee.ch/`claim` - https://spee.ch/`claim`
- https://spee.ch/`claim`.`ext` (serve) - https://spee.ch/`claim`.`ext` (serve)
- https://spee.ch/`claim`.`ext`&`querystring` (serve transformed)
- retrieve a specific `LBRY` claim: - retrieve a specific `LBRY` claim:
- https://spee.ch/`claim_id`/`claim` - https://spee.ch/`claim_id`/`claim`
- https://spee.ch/`claim_id`/`claim`.`ext` (serve) - 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 - retrieve all contents for the controlling `LBRY` channel
- https://spee.ch/`@channel` - https://spee.ch/`@channel`
- a specific `LBRY` channel - a specific `LBRY` channel
@ -271,9 +273,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 - retrieve a specific claim within the controlling `LBRY` channel
- https://spee.ch/`@channel`/`claim` - https://spee.ch/`@channel`/`claim`
- https://spee.ch/`@channel`/`claim`.`ext` (serve) - https://spee.ch/`@channel`/`claim`.`ext` (serve)
- https://spee.ch/`@channel`/`claim`.`ext`&`querystring` (serve)
- retrieve a specific claim within a specific `LBRY` channel - retrieve a specific claim within a specific `LBRY` channel
- https://spee.ch/`@channel`:`channel_id`/`claim` - https://spee.ch/`@channel`:`channel_id`/`claim`
- https://spee.ch/`@channel`:`channel_id`/`claim`.`ext` (serve) - 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 ### Dependencies

View file

@ -49,6 +49,10 @@
} }
}, },
"serving": { "serving": {
"dynamicFileSizing": {
"enabled": true,
"maxDimension": 2000
},
"markdownSettings": { "markdownSettings": {
"skipHtmlMain": true, "skipHtmlMain": true,
"escapeHtmlMain": true, "escapeHtmlMain": true,
@ -83,10 +87,7 @@
"code", "code",
"html", "html",
"parsedHtml" "parsedHtml"
], ]
"disallowedTypesMain": [],
"disallowedTypesDescriptions": ["image", "html"],
"disallowedTypesExample": ["image", "html"]
}, },
"customFileExtensions": { "customFileExtensions": {
"application/x-troff-man": "man", "application/x-troff-man": "man",

View file

@ -26,7 +26,6 @@ PUBLISHING:
"primaryClaimAddress": null, - generally supplied by your lbrynet sdk "primaryClaimAddress": null, - generally supplied by your lbrynet sdk
"uploadDirectory": "/home/lbry/Uploads", - lbrynet sdk will know your uploads are here "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. "thumbnailChannel": null, - when publishing non-image content, thumbnails will go here.
"thumbnailChannelId": null, "thumbnailChannelId": null,
"additionalClaimAddresses": [], "additionalClaimAddresses": [],
@ -53,45 +52,49 @@ PUBLISHING:
SERVING: SERVING:
"markdownSettings": { "dynamicFileSizing": {
"skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~ "enabled": false, - if you choose to allow your instance to serve transform images
"escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly "maxDimension": 2000 - the maximum size you allow transform to scale
"skipHtmlDescriptions": true, - as above, for descriptions },
"escapeHtmlDescriptions": true, - as above, for descriptions "markdownSettings": {
"allowedTypesMain": [], - markdown rendered as main content "skipHtmlMain": true, - false: render html, in a somewhat unpredictable way~
"allowedTypesDescriptions": [], - markdown rendered in description in content details "escapeHtmlMain": true, - true: rather than render html, escape it and print it visibly
"allowedTypesExample": [ - here are examples of allowed types "skipHtmlDescriptions": true, - as above, for descriptions
"see react-markdown docs", `https://github.com/rexxars/react-markdown` "escapeHtmlDescriptions": true, - as above, for descriptions
"root", "allowedTypesMain": [], - markdown rendered as main content
"text", "allowedTypesDescriptions": [], - markdown rendered in description in content details
"break", "allowedTypesExample": [ - here are examples of allowed types
"paragraph", "see react-markdown docs", `https://github.com/rexxars/react-markdown`
"emphasis", "root",
"strong", "text",
"thematicBreak", "break",
"blockquote", "paragraph",
"delete", "emphasis",
"link", "strong",
"image", - you may not have a lot of control over how these are rendered "thematicBreak",
"linkReference", "blockquote",
"imageReference", "delete",
"table", "link",
"tableHead", "image", - you may not have a lot of control over how these are rendered
"tableBody", "linkReference",
"tableRow", "imageReference",
"tableCell", "table",
"list", "tableHead",
"listItem", "tableBody",
"heading", "tableRow",
"inlineCode", "tableCell",
"code", "list",
"html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs. "listItem",
"parsedHtml" "heading",
], "inlineCode",
}, "code",
"customFileExtensions": { - suggest a file extension for experimental content types you may be publishing "html", - potentially DANGEROUS, intended for `serveOnlyApproved = true` environments, includes iframes, divs.
"application/example-type": "example" "parsedHtml"
} ],
},
"customFileExtensions": { - suggest a file extension for experimental content types you may be publishing
"application/example-type": "example"
}
STARTUP: STARTUP:

View file

@ -1,5 +1,10 @@
const logger = require('winston'); const logger = require('winston');
const transformImage = require('./transformImage'); 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 serveFile = async ({ filePath, fileType }, res, originalUrl) => {
const queryObject = {}; const queryObject = {};
// TODO: replace quick/dirty try catch with better practice // 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('/')) : ''; let mediaType = fileType ? fileType.substr(0, fileType.indexOf('/')) : '';
const transform = const transform =
mediaType === 'image' && queryObject.hasOwnProperty('h') && queryObject.hasOwnProperty('w'); mediaType === 'image' &&
queryObject.hasOwnProperty('h') &&
queryObject.hasOwnProperty('w') &&
dynamicEnabled;
const sendFileOptions = { const sendFileOptions = {
headers: { headers: {
@ -32,14 +40,26 @@ const serveFile = async ({ filePath, fileType }, res, originalUrl) => {
}, },
}; };
logger.debug(`fileOptions for ${filePath}:`, sendFileOptions); logger.debug(`fileOptions for ${filePath}:`, sendFileOptions);
if (transform) { try {
logger.debug(`transforming and sending file`); 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); let xformed = await transformImage(filePath, queryObject);
res.status(200).set(sendFileOptions.headers); res.status(200).set(sendFileOptions.headers);
res.end(xformed, 'binary'); res.end(xformed, 'binary');
} else { } else {
res.status(200).sendFile(filePath, sendFileOptions); res.status(200).sendFile(filePath, sendFileOptions);
}
} catch (e) {
logger.debug(e);
} }
}; };

View file

@ -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;