diff --git a/server/controllers/api/channel/data/index.js b/server/controllers/api/channel/data/index.js index ad678d78..b52022f8 100644 --- a/server/controllers/api/channel/data/index.js +++ b/server/controllers/api/channel/data/index.js @@ -1,6 +1,7 @@ -const { getChannelData } = require('../../../utils/serveHelpers.js'); const { handleErrorResponse } = require('../../../utils/errorHandlers.js'); +const getChannelData = require('./getChannelData.js'); + const NO_CHANNEL = 'NO_CHANNEL'; /* diff --git a/server/controllers/api/claim/get/addGetResultsToFileData.js b/server/controllers/api/claim/get/addGetResultsToFileData.js new file mode 100644 index 00000000..3fe3cc43 --- /dev/null +++ b/server/controllers/api/claim/get/addGetResultsToFileData.js @@ -0,0 +1,5 @@ +module.exports = (fileInfo, getResult) => { + fileInfo.fileName = getResult.file_name; + fileInfo.filePath = getResult.download_path; + return fileInfo; +}; diff --git a/server/controllers/api/claim/get/createFileData.js b/server/controllers/api/claim/get/createFileData.js new file mode 100644 index 00000000..71d295ab --- /dev/null +++ b/server/controllers/api/claim/get/createFileData.js @@ -0,0 +1,13 @@ +module.exports = ({ name, claimId, outpoint, height, address, nsfw, contentType }) => { + return { + name, + claimId, + outpoint, + height, + address, + fileName: '', + filePath: '', + fileType: contentType, + nsfw, + }; +}; diff --git a/server/controllers/api/claim/get/index.js b/server/controllers/api/claim/get/index.js index d54fcc87..1a8757e8 100644 --- a/server/controllers/api/claim/get/index.js +++ b/server/controllers/api/claim/get/index.js @@ -1,5 +1,6 @@ const { getClaim } = require('../../../../lbrynet'); -const { addGetResultsToFileData, createFileData } = require('../../../utils/file.js'); +const addGetResultsToFileData = require('./addGetResultsToFileData.js'); +const createFileData = require('./createFileData.js'); const { handleErrorResponse } = require('../../../utils/errorHandlers.js'); const db = require('../../../../models'); diff --git a/server/controllers/api/claim/longId/index.js b/server/controllers/api/claim/longId/index.js index b84ded1e..f246fd0c 100644 --- a/server/controllers/api/claim/longId/index.js +++ b/server/controllers/api/claim/longId/index.js @@ -1,4 +1,4 @@ -const { getClaimId } = require('../../../utils/serveHelpers.js'); +const getClaimId = require('../../../utils/getClaimId.js'); const { handleErrorResponse } = require('../../../utils/errorHandlers.js'); const NO_CHANNEL = 'NO_CHANNEL'; diff --git a/server/controllers/assets/serveByClaim/index.js b/server/controllers/assets/serveByClaim/index.js index 51d9a500..a561754e 100644 --- a/server/controllers/assets/serveByClaim/index.js +++ b/server/controllers/assets/serveByClaim/index.js @@ -1,11 +1,12 @@ const { sendGAServeEvent } = require('../../../utils/googleAnalytics'); -const { - determineResponseType, - logRequestData, - getClaimIdAndServeAsset, -} = require('../../utils/serve.js'); -const lbryUri = require('../../utils/lbryUri.js'); const handleShowRender = require('../../../render/build/handleShowRender.js'); + +const lbryUri = require('../utils/lbryUri.js'); + +const determineResponseType = require('../utils/determineResponseType.js'); +const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js'); +const logRequestData = require('../utils/logRequestData.js'); + const SERVE = 'SERVE'; /* diff --git a/server/controllers/assets/serveByIdentifierAndClaim/index.js b/server/controllers/assets/serveByIdentifierAndClaim/index.js index 954421d9..2fbdb7bb 100644 --- a/server/controllers/assets/serveByIdentifierAndClaim/index.js +++ b/server/controllers/assets/serveByIdentifierAndClaim/index.js @@ -1,13 +1,13 @@ const { sendGAServeEvent } = require('../../../utils/googleAnalytics'); -const { - determineResponseType, - flipClaimNameAndIdForBackwardsCompatibility, - logRequestData, - getClaimIdAndServeAsset, -} = require('../../utils/serve.js'); -const lbryUri = require('../../utils/lbryUri.js'); const handleShowRender = require('../../../render/build/handleShowRender.js'); +const lbryUri = require('../utils/lbryUri.js'); + +const determineResponseType = require('../utils/determineResponseType.js'); +const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js'); +const flipClaimNameAndIdForBackwardsCompatibility = require('../utils/flipClaimNameAndIdForBackwardsCompatibility.js'); +const logRequestData = require('../utils/logRequestData.js'); + const SERVE = 'SERVE'; /* diff --git a/server/controllers/assets/utils/determineResponseType.js b/server/controllers/assets/utils/determineResponseType.js new file mode 100644 index 00000000..f32a0c5c --- /dev/null +++ b/server/controllers/assets/utils/determineResponseType.js @@ -0,0 +1,37 @@ +const logger = require('winston'); + +const SERVE = 'SERVE'; +const SHOW = 'SHOW'; + +function clientAcceptsHtml ({accept}) { + return accept && accept.match(/text\/html/); +}; + +function requestIsFromBrowser (headers) { + return headers['user-agent'] && headers['user-agent'].match(/Mozilla/); +}; + +function clientWantsAsset ({accept, range}) { + const imageIsWanted = accept && accept.match(/image\/.*/) && !accept.match(/text\/html/) && !accept.match(/text\/\*/); + const videoIsWanted = accept && range; + return imageIsWanted || videoIsWanted; +}; + +const determineResponseType = (hasFileExtension, headers) => { + let responseType; + if (hasFileExtension) { + responseType = SERVE; // assume a serve request if file extension is present + if (clientAcceptsHtml(headers)) { // if the request comes from a browser, change it to a show request + responseType = SHOW; + } + } else { + responseType = SHOW; + if (clientWantsAsset(headers) && requestIsFromBrowser(headers)) { // this is in case someone embeds a show url + logger.debug('Show request came from browser but wants an image/video. Changing response to serve...'); + responseType = SERVE; + } + } + return responseType; +}; + +module.exports = determineResponseType; diff --git a/server/controllers/assets/utils/flipClaimNameAndIdForBackwardsCompatibility.js b/server/controllers/assets/utils/flipClaimNameAndIdForBackwardsCompatibility.js new file mode 100644 index 00000000..47ebab32 --- /dev/null +++ b/server/controllers/assets/utils/flipClaimNameAndIdForBackwardsCompatibility.js @@ -0,0 +1,23 @@ +function isValidClaimId (claimId) { + return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId)); +}; + +function isValidShortId (claimId) { + return claimId.length === 1; // it should really evaluate the short url itself +}; + +function isValidShortIdOrClaimId (input) { + return (isValidClaimId(input) || isValidShortId(input)); +}; + +const flipClaimNameAndIdForBackwardsCompatibility = (identifier, name) => { + // this is a patch for backwards compatability with '/name/claim_id' url format + if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) { + const tempName = name; + name = identifier; + identifier = tempName; + } + return [identifier, name]; +}; + +module.exports = flipClaimNameAndIdForBackwardsCompatibility; diff --git a/server/controllers/assets/utils/getClaimIdAndServeAsset.js b/server/controllers/assets/utils/getClaimIdAndServeAsset.js new file mode 100644 index 00000000..ab0e3ce3 --- /dev/null +++ b/server/controllers/assets/utils/getClaimIdAndServeAsset.js @@ -0,0 +1,51 @@ +const logger = require('winston'); +const getClaimId = require('../../utils/getClaimId.js'); +const getLocalFileRecord = require('../../utils/getLocalFileRecord.js'); +const { handleErrorResponse } = require('../../utils/errorHandlers.js'); + +const NO_FILE = 'NO_FILE'; +const NO_CHANNEL = 'NO_CHANNEL'; +const NO_CLAIM = 'NO_CLAIM'; + +function serveAssetToClient (claimId, name, res) { + return getLocalFileRecord(claimId, name) + .then(fileRecord => { + // check that a local record was found + if (fileRecord === NO_FILE) { + return res.status(307).redirect(`/api/claim/get/${name}/${claimId}`); + } + // serve the file + const {filePath, fileType} = fileRecord; + logger.verbose(`serving file: ${filePath}`); + const sendFileOptions = { + headers: { + 'X-Content-Type-Options': 'nosniff', + 'Content-Type' : fileType || 'image/jpeg', + }, + }; + res.status(200).sendFile(filePath, sendFileOptions); + }) + .catch(error => { + throw error; + }); +}; + +const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId, originalUrl, ip, res) => { + // get the claim Id and then serve the asset + getClaimId(channelName, channelClaimId, claimName, claimId) + .then(fullClaimId => { + if (fullClaimId === NO_CLAIM) { + return res.status(404).json({success: false, message: 'no claim id could be found'}); + } else if (fullClaimId === NO_CHANNEL) { + return res.status(404).json({success: false, message: 'no channel id could be found'}); + } + serveAssetToClient(fullClaimId, claimName, res); + // postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success'); + }) + .catch(error => { + handleErrorResponse(originalUrl, ip, error, res); + // postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'fail'); + }); +}; + +module.exports = getClaimIdAndServeAsset; diff --git a/server/controllers/utils/lbryUri.js b/server/controllers/assets/utils/lbryUri.js similarity index 100% rename from server/controllers/utils/lbryUri.js rename to server/controllers/assets/utils/lbryUri.js diff --git a/server/controllers/assets/utils/logRequestData.js b/server/controllers/assets/utils/logRequestData.js new file mode 100644 index 00000000..dde074c2 --- /dev/null +++ b/server/controllers/assets/utils/logRequestData.js @@ -0,0 +1,10 @@ +const logger = require('winston'); + +const logRequestData = (responseType, claimName, channelName, claimId) => { + logger.debug('responseType ===', responseType); + logger.debug('claim name === ', claimName); + logger.debug('channel name ===', channelName); + logger.debug('claim id ===', claimId); +}; + +module.exports = logRequestData; diff --git a/server/controllers/utils/file.js b/server/controllers/utils/file.js deleted file mode 100644 index b786f8e8..00000000 --- a/server/controllers/utils/file.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - addGetResultsToFileData (fileInfo, getResult) { - fileInfo.fileName = getResult.file_name; - fileInfo.filePath = getResult.download_path; - return fileInfo; - }, - createFileData ({ name, claimId, outpoint, height, address, nsfw, contentType }) { - return { - name, - claimId, - outpoint, - height, - address, - fileName: '', - filePath: '', - fileType: contentType, - nsfw, - }; - }, -}; diff --git a/server/controllers/utils/getClaimId.js b/server/controllers/utils/getClaimId.js new file mode 100644 index 00000000..4e6c5a29 --- /dev/null +++ b/server/controllers/utils/getClaimId.js @@ -0,0 +1,57 @@ +const logger = require('winston'); + +const db = require('../../models'); + +const NO_CHANNEL = 'NO_CHANNEL'; +const NO_CLAIM = 'NO_CLAIM'; + +const getClaimIdByClaim = (claimName, claimId) => { + logger.debug(`getClaimIdByClaim(${claimName}, ${claimId})`); + return new Promise((resolve, reject) => { + db.Claim.getLongClaimId(claimName, claimId) + .then(longClaimId => { + if (!longClaimId) { + resolve(NO_CLAIM); + } + resolve(longClaimId); + }) + .catch(error => { + reject(error); + }); + }); +}; + +const getClaimIdByChannel = (channelName, channelClaimId, claimName) => { + logger.debug(`getClaimIdByChannel(${channelName}, ${channelClaimId}, ${claimName})`); + return new Promise((resolve, reject) => { + db.Certificate.getLongChannelId(channelName, channelClaimId) // 1. get the long channel id + .then(longChannelId => { + if (!longChannelId) { + return [null, null]; + } + return Promise.all([longChannelId, db.Claim.getClaimIdByLongChannelId(longChannelId, claimName)]); // 2. get the long claim id + }) + .then(([longChannelId, longClaimId]) => { + if (!longChannelId) { + return resolve(NO_CHANNEL); + } + if (!longClaimId) { + return resolve(NO_CLAIM); + } + resolve(longClaimId); + }) + .catch(error => { + reject(error); + }); + }); +}; + +const getClaimId = (channelName, channelClaimId, name, claimId) => { + if (channelName) { + return getClaimIdByChannel(channelName, channelClaimId, name); + } else { + return getClaimIdByClaim(name, claimId); + } +}; + +module.exports = getClaimId; diff --git a/server/controllers/utils/getLocalFileRecord.js b/server/controllers/utils/getLocalFileRecord.js new file mode 100644 index 00000000..3d2016d1 --- /dev/null +++ b/server/controllers/utils/getLocalFileRecord.js @@ -0,0 +1,15 @@ +const db = require('../../models'); + +const NO_FILE = 'NO_FILE'; + +const getLocalFileRecord = (claimId, name) => { + return db.File.findOne({where: {claimId, name}}) + .then(file => { + if (!file) { + return NO_FILE; + } + return file.dataValues; + }); +}; + +module.exports = getLocalFileRecord; diff --git a/server/controllers/utils/serve.js b/server/controllers/utils/serve.js deleted file mode 100644 index f9fb7f8b..00000000 --- a/server/controllers/utils/serve.js +++ /dev/null @@ -1,109 +0,0 @@ -const logger = require('winston'); -const { getClaimId, getLocalFileRecord } = require('./serveHelpers.js'); -const { handleErrorResponse } = require('./errorHandlers.js'); - -const SERVE = 'SERVE'; -const SHOW = 'SHOW'; -const NO_FILE = 'NO_FILE'; -const NO_CHANNEL = 'NO_CHANNEL'; -const NO_CLAIM = 'NO_CLAIM'; - -function clientAcceptsHtml ({accept}) { - return accept && accept.match(/text\/html/); -}; - -function requestIsFromBrowser (headers) { - return headers['user-agent'] && headers['user-agent'].match(/Mozilla/); -}; - -function clientWantsAsset ({accept, range}) { - const imageIsWanted = accept && accept.match(/image\/.*/) && !accept.match(/text\/html/) && !accept.match(/text\/\*/); - const videoIsWanted = accept && range; - return imageIsWanted || videoIsWanted; -}; - -function isValidClaimId (claimId) { - return ((claimId.length === 40) && !/[^A-Za-z0-9]/g.test(claimId)); -}; - -function isValidShortId (claimId) { - return claimId.length === 1; // it should really evaluate the short url itself -}; - -function isValidShortIdOrClaimId (input) { - return (isValidClaimId(input) || isValidShortId(input)); -}; - -function serveAssetToClient (claimId, name, res) { - return getLocalFileRecord(claimId, name) - .then(fileRecord => { - // check that a local record was found - if (fileRecord === NO_FILE) { - return res.status(307).redirect(`/api/claim/get/${name}/${claimId}`); - } - // serve the file - const {filePath, fileType} = fileRecord; - logger.verbose(`serving file: ${filePath}`); - const sendFileOptions = { - headers: { - 'X-Content-Type-Options': 'nosniff', - 'Content-Type' : fileType || 'image/jpeg', - }, - }; - res.status(200).sendFile(filePath, sendFileOptions); - }) - .catch(error => { - throw error; - }); -}; - -module.exports = { - getClaimIdAndServeAsset (channelName, channelClaimId, claimName, claimId, originalUrl, ip, res) { - // get the claim Id and then serve the asset - getClaimId(channelName, channelClaimId, claimName, claimId) - .then(fullClaimId => { - if (fullClaimId === NO_CLAIM) { - return res.status(404).json({success: false, message: 'no claim id could be found'}); - } else if (fullClaimId === NO_CHANNEL) { - return res.status(404).json({success: false, message: 'no channel id could be found'}); - } - serveAssetToClient(fullClaimId, claimName, res); - // postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'success'); - }) - .catch(error => { - handleErrorResponse(originalUrl, ip, error, res); - // postToStats(responseType, originalUrl, ip, claimName, fullClaimId, 'fail'); - }); - }, - determineResponseType (hasFileExtension, headers) { - let responseType; - if (hasFileExtension) { - responseType = SERVE; // assume a serve request if file extension is present - if (clientAcceptsHtml(headers)) { // if the request comes from a browser, change it to a show request - responseType = SHOW; - } - } else { - responseType = SHOW; - if (clientWantsAsset(headers) && requestIsFromBrowser(headers)) { // this is in case someone embeds a show url - logger.debug('Show request came from browser but wants an image/video. Changing response to serve...'); - responseType = SERVE; - } - } - return responseType; - }, - flipClaimNameAndIdForBackwardsCompatibility (identifier, name) { - // this is a patch for backwards compatability with '/name/claim_id' url format - if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) { - const tempName = name; - name = identifier; - identifier = tempName; - } - return [identifier, name]; - }, - logRequestData (responseType, claimName, channelName, claimId) { - logger.debug('responseType ===', responseType); - logger.debug('claim name === ', claimName); - logger.debug('channel name ===', channelName); - logger.debug('claim id ===', claimId); - }, -}; diff --git a/server/controllers/utils/serveHelpers.js b/server/controllers/utils/serveHelpers.js deleted file mode 100644 index 68b46335..00000000 --- a/server/controllers/utils/serveHelpers.js +++ /dev/null @@ -1,64 +0,0 @@ -const db = require('../../models'); -const logger = require('winston'); - -const NO_CHANNEL = 'NO_CHANNEL'; -const NO_CLAIM = 'NO_CLAIM'; -const NO_FILE = 'NO_FILE'; - -module.exports = { - getClaimId (channelName, channelClaimId, name, claimId) { - if (channelName) { - return module.exports.getClaimIdByChannel(channelName, channelClaimId, name); - } else { - return module.exports.getClaimIdByClaim(name, claimId); - } - }, - getClaimIdByClaim (claimName, claimId) { - logger.debug(`getClaimIdByClaim(${claimName}, ${claimId})`); - return new Promise((resolve, reject) => { - db.Claim.getLongClaimId(claimName, claimId) - .then(longClaimId => { - if (!longClaimId) { - resolve(NO_CLAIM); - } - resolve(longClaimId); - }) - .catch(error => { - reject(error); - }); - }); - }, - getClaimIdByChannel (channelName, channelClaimId, claimName) { - logger.debug(`getClaimIdByChannel(${channelName}, ${channelClaimId}, ${claimName})`); - return new Promise((resolve, reject) => { - db.Certificate.getLongChannelId(channelName, channelClaimId) // 1. get the long channel id - .then(longChannelId => { - if (!longChannelId) { - return [null, null]; - } - return Promise.all([longChannelId, db.Claim.getClaimIdByLongChannelId(longChannelId, claimName)]); // 2. get the long claim id - }) - .then(([longChannelId, longClaimId]) => { - if (!longChannelId) { - return resolve(NO_CHANNEL); - } - if (!longClaimId) { - return resolve(NO_CLAIM); - } - resolve(longClaimId); - }) - .catch(error => { - reject(error); - }); - }); - }, - getLocalFileRecord (claimId, name) { - return db.File.findOne({where: {claimId, name}}) - .then(file => { - if (!file) { - return NO_FILE; - } - return file.dataValues; - }); - }, -};