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