diff --git a/cli/defaults/siteConfig.json b/cli/defaults/siteConfig.json index 15243fce..67e4a79f 100644 --- a/cli/defaults/siteConfig.json +++ b/cli/defaults/siteConfig.json @@ -23,7 +23,7 @@ "publishing": { "primaryClaimAddress": null, "uploadDirectory": "/home/lbry/Uploads", - "lbrynetHome": "/home/lbry", + "lbrynetHome": "/CURRENTLYUNUSED", "thumbnailChannel": null, "thumbnailChannelId": null, "additionalClaimAddresses": [], @@ -36,6 +36,17 @@ "publishingChannelWhitelist": [], "channelClaimBidAmount": "0.1", "fileClaimBidAmount": "0.01", + "fileSizeLimits": { + "image": 50000000, + "video": 50000000, + "audio": 50000000, + "text": 50000000, + "model": 50000000, + "application": 50000000, + "customByContentType": { + "application/octet-stream": 50000000 + } + }, "maxSizeImage": 10000000, "maxSizeGif": 50000000, "maxSizeVideo": 50000000 @@ -49,7 +60,19 @@ "publicDisallowedTypesMain": [] }, "customFileExtensions": { - "application/example-type": "example" + "application/x-troff-man": ".man", + "application/x-troff-me": ".me", + "application/x-mif": ".mif", + "application/x-troff-ms": ".ms", + "application/x-troff": ".roff", + "application/x-python-code": ".pyc", + "text/x-python": ".py", + "application/x-pn-realaudio": ".ram", + "application/x-sgml": ".sgm", + "model/stl": ".stl", + "image/pict": ".pct", + "text/xul": ".xul", + "text/x-go": "go" } }, "startup": { diff --git a/client/src/components/DropzonePreviewImage/index.jsx b/client/src/components/DropzonePreviewImage/index.jsx index d62abaeb..5aa6f730 100644 --- a/client/src/components/DropzonePreviewImage/index.jsx +++ b/client/src/components/DropzonePreviewImage/index.jsx @@ -5,8 +5,9 @@ class PublishPreview extends React.Component { constructor (props) { super(props); this.state = { - imgSource : '', - defaultThumbnail: '/assets/img/video_thumb_default.png', + imgSource : '', + defaultVideoThumbnail: '/assets/img/video_thumb_default.png', + defaultThumbnail : '/assets/img/Speech_Logo_Main@OG-02.jpg', }; } componentDidMount () { @@ -37,12 +38,13 @@ class PublishPreview extends React.Component { }; } setPreviewImageSource (file) { - if (file.type !== 'video/mp4') { + if (this.props.thumbnail) { + this.setPreviewImageSourceFromFile(this.props.thumbnail); + } else if (file.type.substr(0, file.type.indexOf('/')) === 'image'){ this.setPreviewImageSourceFromFile(file); + } else if (file.type === 'video'){ + this.setState({imgSource: this.state.defaultVideoThumbnail}); } else { - if (this.props.thumbnail) { - this.setPreviewImageSourceFromFile(this.props.thumbnail); - } this.setState({imgSource: this.state.defaultThumbnail}); } } diff --git a/client/src/components/FileViewer/index.jsx b/client/src/components/FileViewer/index.jsx index 0d3d4866..8cd0172e 100644 --- a/client/src/components/FileViewer/index.jsx +++ b/client/src/components/FileViewer/index.jsx @@ -1,6 +1,5 @@ import React from 'react'; import ReactMarkdown from 'react-markdown'; -// TODO: get markdown settings from siteConfig class FileViewer extends React.Component { diff --git a/client/src/containers/AssetInfo/view.jsx b/client/src/containers/AssetInfo/view.jsx index c1e6154d..70d63298 100644 --- a/client/src/containers/AssetInfo/view.jsx +++ b/client/src/containers/AssetInfo/view.jsx @@ -22,7 +22,6 @@ class AssetInfo extends React.Component { const canonicalUrl = createCanonicalLink({ asset: { ...claimData, shortId: asset.shortId }}); const assetCanonicalUrl = `${host}${canonicalUrl}`; // Todo Issue #882 centralize all this media type detection - // Todo get markdown settings from siteConfig const embedable = contentType.split('/')[0] === 'image' || contentType === 'video/mp4'; let channelCanonicalUrl; @@ -38,7 +37,7 @@ class AssetInfo extends React.Component { { description && ( } - content={
} + content={
} /> )} {editable && ( diff --git a/client/src/containers/Dropzone/view.jsx b/client/src/containers/Dropzone/view.jsx index a4a6d00e..cba685f9 100644 --- a/client/src/containers/Dropzone/view.jsx +++ b/client/src/containers/Dropzone/view.jsx @@ -1,13 +1,13 @@ import React from 'react'; -import { validateFile } from '../../utils/file'; import Memeify from '@components/Memeify'; import DropzonePreviewImage from '@components/DropzonePreviewImage'; import DropzoneDropItDisplay from '@components/DropzoneDropItDisplay'; import DropzoneInstructionsDisplay from '@components/DropzoneInstructionsDisplay'; +import validateFileForPublish from '@globalutils/validateFileForPublish'; -import { library } from '@fortawesome/fontawesome-svg-core' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { library } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEdit } from '@fortawesome/free-solid-svg-icons'; const isFacebook = (() => { @@ -29,7 +29,7 @@ class Dropzone extends React.Component { memeify : false, }; - if(props.file) { + if (props.file) { // No side effects allowed with `getDerivedStateFromProps`, so // we must use `componentDidUpdate` and `constructor` routines. // Note: `FileReader` has an `onloadend` side-effect @@ -133,7 +133,7 @@ class Dropzone extends React.Component { chooseFile (file) { if (file) { try { - validateFile(file); // validate the file's name, type, and size + validateFileForPublish(file); // validate the file's name, type, and size } catch (error) { return this.props.setFileError(error.message); } diff --git a/client/src/utils/createAssetMetaTags.js b/client/src/utils/createAssetMetaTags.js index 70bead44..e2a0b5ef 100644 --- a/client/src/utils/createAssetMetaTags.js +++ b/client/src/utils/createAssetMetaTags.js @@ -47,8 +47,6 @@ const createAssetMetaTags = asset => { const ogThumbnailContentType = determineContentTypeFromExtension(claimData.thumbnail); const ogThumbnail = claimData.thumbnail || defaultThumbnail; - console.log('asset.claimData', asset.claimData); - // {property: 'og:title'] = ogTitle}, const metaTags = { 'og:title': ogTitle, diff --git a/server/chainquery/models/ClaimModel.js b/server/chainquery/models/ClaimModel.js index 022656f9..4fcc1af0 100644 --- a/server/chainquery/models/ClaimModel.js +++ b/server/chainquery/models/ClaimModel.js @@ -1,9 +1,17 @@ const logger = require('winston'); const mime = require('mime-types'); +const { + serving: { customFileExtensions }, +} = require('@config/siteConfig'); const getterMethods = { generated_extension() { - return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg'; + logger.info('trying to generate extension', this.content_type); + if (customFileExtensions.hasOwnProperty(this.content_type)) { + return customFileExtensions[this.content_type]; + } else { + return mime.extension(this.content_type) ? mime.extension(this.content_type) : 'jpg'; + } }, }; diff --git a/server/controllers/api/claim/publish/parsePublishApiRequestFiles.js b/server/controllers/api/claim/publish/parsePublishApiRequestFiles.js index 3fdcdfc3..6979740e 100644 --- a/server/controllers/api/claim/publish/parsePublishApiRequestFiles.js +++ b/server/controllers/api/claim/publish/parsePublishApiRequestFiles.js @@ -1,5 +1,6 @@ const path = require('path'); const validateFileTypeAndSize = require('./validateFileTypeAndSize.js'); +const validateFileForPublish = require('./validateFileForPublish.js'); const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => { // make sure a file was provided @@ -40,7 +41,7 @@ const parsePublishApiRequestFiles = ({ file, thumbnail }, isUpdate) => { } // validate the file - if (file) validateFileTypeAndSize(file); + if (file) validateFileForPublish(file); // return results const obj = { fileName: file.name, diff --git a/server/controllers/api/claim/publish/validateFileForPublish.js b/server/controllers/api/claim/publish/validateFileForPublish.js new file mode 100644 index 00000000..1047c048 --- /dev/null +++ b/server/controllers/api/claim/publish/validateFileForPublish.js @@ -0,0 +1,38 @@ +const logger = require('winston'); + +const { publishing } = require('@config/siteConfig.json'); + +const { fileSizeLimits } = publishing; + +const SIZE_MB = 1000000; + +const validateFileForPublish = file => { + let contentType = file.type; + let mediaType = contentType ? contentType.substr(0, contentType.indexOf('/')) : ''; + let mediaTypeLimit = fileSizeLimits[mediaType] || false; + let customLimits = fileSizeLimits['customByContentType']; + + if (!file) { + throw new Error('no file provided'); + } + + if (/'/.test(file.name)) { + throw new Error('apostrophes are not allowed in the file name'); + } + + if (Object.keys(customLimits).includes(contentType)) { + if (file.size > customLimits[contentType]) { + throw new Error( + `Sorry, type ${contentType} is limited to ${customLimits[contentType] / SIZE_MB} MB.` + ); + } + } + if (mediaTypeLimit) { + if (file.size > mediaTypeLimit) { + throw new Error(`Sorry, type ${mediaType} is limited to ${mediaTypeLimit / SIZE_MB} MB.`); + } + } + return file; +}; + +module.exports = validateFileForPublish; diff --git a/utils/validateFileForPublish.js b/utils/validateFileForPublish.js new file mode 100644 index 00000000..ef129305 --- /dev/null +++ b/utils/validateFileForPublish.js @@ -0,0 +1,34 @@ +import { publishing } from '@config/siteConfig.json'; + +const { fileSizeLimits } = publishing; + +const SIZE_MB = 1000000; + +export default function validateFileForPublish(file) { + let contentType = file.type; + let mediaType = contentType ? contentType.substr(0, contentType.indexOf('/')) : ''; + let mediaTypeLimit = fileSizeLimits[mediaType] || false; + let customLimits = fileSizeLimits['customByContentType']; + + if (!file) { + throw new Error('no file provided'); + } + + if (/'/.test(file.name)) { + throw new Error('apostrophes are not allowed in the file name'); + } + + if (Object.keys(customLimits).includes(contentType)) { + if (file.size > customLimits[contentType]) { + throw new Error( + `Sorry, type ${contentType} is limited to ${customLimits[contentType] / SIZE_MB} MB.` + ); + } + } + if (mediaTypeLimit) { + if (file.size > mediaTypeLimit) { + throw new Error(`Sorry, type ${mediaType} is limited to ${mediaTypeLimit / SIZE_MB} MB.`); + } + } + return file; +}