Merge pull request #933 from jessopb/croppingSafe
documents and validates image resize
This commit is contained in:
commit
3cd7b6080d
5 changed files with 110 additions and 54 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
24
server/utils/isValidQueryObj.js
Normal file
24
server/utils/isValidQueryObj.js
Normal 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;
|
Loading…
Reference in a new issue