diff --git a/helpers/errorHandlers.js b/helpers/errorHandlers.js index aa1f723d..4a825399 100644 --- a/helpers/errorHandlers.js +++ b/helpers/errorHandlers.js @@ -1,5 +1,4 @@ const logger = require('winston'); -const { postToStats } = require('../controllers/statsController.js'); module.exports = { returnErrorMessageAndStatus: function (error) { @@ -33,17 +32,15 @@ module.exports = { } return [status, message]; }, - handleRequestError: function (action, originalUrl, ip, error, res) { + handleRequestError: function (originalUrl, ip, error, res) { logger.error(`Request Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); - postToStats(action, originalUrl, ip, null, null, error); const [status, message] = module.exports.returnErrorMessageAndStatus(error); res .status(status) .render('requestError', module.exports.createErrorResponsePayload(status, message)); }, - handleApiError: function (action, originalUrl, ip, error, res) { - logger.error(`Api ${action} Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); - postToStats(action, originalUrl, ip, null, null, error); + handleApiError: function (originalUrl, ip, error, res) { + logger.error(`Api Error on ${originalUrl}`, module.exports.useObjectPropertiesIfNoKeys(error)); const [status, message] = module.exports.returnErrorMessageAndStatus(error); res .status(status) diff --git a/helpers/lbryuri.js b/helpers/lbryuri.js new file mode 100644 index 00000000..254f1607 --- /dev/null +++ b/helpers/lbryuri.js @@ -0,0 +1,90 @@ +const logger = require('winston'); +// const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); + +module.exports = { + REGEXP_INVALID_URI: /[^A-Za-z0-9-]/g, + REGEXP_ADDRESS : /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/, + CHANNEL_CHAR : '@', + parseIdentifier : function (identifier) { + logger.debug('parsing identifier:', identifier); + const componentsRegex = new RegExp( + '([^:$#/]*)' + // value (stops at the first separator or end) + '([:$#]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end) + ); + const [proto, value, modifierSeperator, modifier] = componentsRegex + .exec(identifier) + .map(match => match || null); + logger.debug(`${proto}, ${value}, ${modifierSeperator}, ${modifier}`); + + // Validate and process name + const isChannel = value.startsWith(module.exports.CHANNEL_CHAR); + const channelName = isChannel ? value : null; + let claimId; + if (isChannel) { + if (!channelName) { + throw new Error('No channel name after @.'); + } + const nameBadChars = (channelName).match(module.exports.REGEXP_INVALID_URI); + if (nameBadChars) { + throw new Error(`Invalid characters in channel name: ${nameBadChars.join(', ')}.`); + } + } else { + claimId = value; + } + + // Validate and process modifier + let channelClaimId; + if (modifierSeperator) { + if (!modifier) { + throw new Error(`No modifier provided after separator ${modifierSeperator}.`); + } + + if (modifierSeperator === ':') { + channelClaimId = modifier; + } else { + throw new Error(`The ${modifierSeperator} modifier is not currently supported.`); + } + } + return { + isChannel, + channelName, + channelClaimId, + claimId, + }; + }, + parseName: function (name) { + logger.debug('parsing name:', name); + const componentsRegex = new RegExp( + '([^:$#/.]*)' + // name (stops at the first modifier) + '([:$#.]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end) + ); + const [proto, claimName, modifierSeperator, modifier] = componentsRegex + .exec(name) + .map(match => match || null); + logger.debug(`${proto}, ${claimName}, ${modifierSeperator}, ${modifier}`); + + // Validate and process name + if (!claimName) { + throw new Error('No claim name provided before .'); + } + const nameBadChars = (claimName).match(module.exports.REGEXP_INVALID_URI); + if (nameBadChars) { + throw new Error(`Invalid characters in claim name: ${nameBadChars.join(', ')}.`); + } + // Validate and process modifier + let isServeRequest = false; + if (modifierSeperator) { + if (!modifier) { + throw new Error(`No file extension provided after separator ${modifierSeperator}.`); + } + if (modifierSeperator !== '.') { + throw new Error(`The ${modifierSeperator} modifier is not supported in the claim name`); + } + isServeRequest = true; + } + return { + claimName, + isServeRequest, + }; + }, +}; diff --git a/routes/api-routes.js b/routes/api-routes.js index 63414870..999e91e2 100644 --- a/routes/api-routes.js +++ b/routes/api-routes.js @@ -39,7 +39,7 @@ module.exports = (app) => { res.status(200).json(claimsList); }) .catch(error => { - errorHandlers.handleApiError('claim_list', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); // route to see if asset is available locally @@ -55,7 +55,7 @@ module.exports = (app) => { res.status(200).json({status: 'success', message: isLocalFileAvailable}); }) .catch(error => { - errorHandlers.handleApiError('get', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); // route to get an asset @@ -81,7 +81,7 @@ module.exports = (app) => { res.status(200).json({ status: 'success', message, completed }); }) .catch(error => { - errorHandlers.handleApiError('get', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); @@ -123,7 +123,7 @@ module.exports = (app) => { res.status(200).json(resolvedUri); }) .catch(error => { - errorHandlers.handleApiError('resolve', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); // route to run a publish request on the daemon @@ -211,7 +211,7 @@ module.exports = (app) => { }); }) .catch(error => { - errorHandlers.handleApiError('publish', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); // route to get a short claim id from long claim Id @@ -234,7 +234,7 @@ module.exports = (app) => { }) .catch(error => { logger.error('api error getting short channel id', error); - errorHandlers.handleApiError('short channel id', originalUrl, ip, error, res); + errorHandlers.handleApiError(originalUrl, ip, error, res); }); }); }; diff --git a/routes/page-routes.js b/routes/page-routes.js index ac56b889..a1a755da 100644 --- a/routes/page-routes.js +++ b/routes/page-routes.js @@ -36,7 +36,7 @@ module.exports = (app) => { }); }) .catch(error => { - errorHandlers.handleRequestError('popular', originalUrl, ip, error, res); + errorHandlers.handleRequestError(originalUrl, ip, error, res); }); }); // route to display a list of the trending images @@ -47,7 +47,7 @@ module.exports = (app) => { res.status(200).render('new', { newClaims: result }); }) .catch(error => { - errorHandlers.handleRequestError('new', originalUrl, ip, error, res); + errorHandlers.handleRequestError(originalUrl, ip, error, res); }); }); // route to send embedable video player (for twitter) diff --git a/routes/serve-routes.js b/routes/serve-routes.js index 2e61385a..5f8480b5 100644 --- a/routes/serve-routes.js +++ b/routes/serve-routes.js @@ -3,11 +3,11 @@ const { getClaimId, getChannelInfoAndContent, getLocalFileRecord } = require('.. const serveHelpers = require('../helpers/serveHelpers.js'); const { handleRequestError } = require('../helpers/errorHandlers.js'); const db = require('../models'); +const lbryuri = require('../helpers/lbryuri.js'); const SERVE = 'SERVE'; const SHOW = 'SHOW'; const SHOWLITE = 'SHOWLITE'; -const CHANNEL_CHAR = '@'; const CLAIMS_PER_PAGE = 10; const NO_CHANNEL = 'NO_CHANNEL'; const NO_CLAIM = 'NO_CLAIM'; @@ -116,7 +116,7 @@ function showChannelPageToClient (channelName, channelClaimId, originalUrl, ip, sendChannelInfoAndContentToClient(result, query, res); }) .catch(error => { - handleRequestError('serve', originalUrl, ip, error, res); + handleRequestError(originalUrl, ip, error, res); }); } @@ -211,95 +211,6 @@ function logRequestData (responseType, claimName, channelName, claimId) { logger.debug('claim id ===', claimId); } -const lbryuri = {}; - -lbryuri.REGEXP_INVALID_URI = /[^A-Za-z0-9-]/g; -lbryuri.REGEXP_ADDRESS = /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/; - -lbryuri.parseIdentifier = function (identifier) { - logger.debug('parsing identifier:', identifier); - const componentsRegex = new RegExp( - '([^:$#/]*)' + // value (stops at the first separator or end) - '([:$#]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end) - ); - const [proto, value, modifierSeperator, modifier] = componentsRegex - .exec(identifier) - .map(match => match || null); - logger.debug(`${proto}, ${value}, ${modifierSeperator}, ${modifier}`); - - // Validate and process name - const isChannel = value.startsWith(CHANNEL_CHAR); - const channelName = isChannel ? value : null; - let claimId; - if (isChannel) { - if (!channelName) { - throw new Error('No channel name after @.'); - } - const nameBadChars = (channelName).match(lbryuri.REGEXP_INVALID_URI); - if (nameBadChars) { - throw new Error(`Invalid characters in channel name: ${nameBadChars.join(', ')}.`); - } - } else { - claimId = value; - } - - // Validate and process modifier - let channelClaimId; - if (modifierSeperator) { - if (!modifier) { - throw new Error(`No modifier provided after separator ${modifierSeperator}.`); - } - - if (modifierSeperator === ':') { - channelClaimId = modifier; - } else { - throw new Error(`The ${modifierSeperator} modifier is not currently supported.`); - } - } - return { - isChannel, - channelName, - channelClaimId, - claimId, - }; -}; - -lbryuri.parseName = function (name) { - logger.debug('parsing name:', name); - const componentsRegex = new RegExp( - '([^:$#/.]*)' + // name (stops at the first modifier) - '([:$#.]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end) - ); - const [proto, claimName, modifierSeperator, modifier] = componentsRegex - .exec(name) - .map(match => match || null); - logger.debug(`${proto}, ${claimName}, ${modifierSeperator}, ${modifier}`); - - // Validate and process name - if (!claimName) { - throw new Error('No claim name provided before .'); - } - const nameBadChars = (claimName).match(lbryuri.REGEXP_INVALID_URI); - if (nameBadChars) { - throw new Error(`Invalid characters in claim name: ${nameBadChars.join(', ')}.`); - } - // Validate and process modifier - let isServeRequest = false; - if (modifierSeperator) { - if (!modifier) { - throw new Error(`No file extension provided after separator ${modifierSeperator}.`); - } - if (modifierSeperator !== '.') { - throw new Error(`The ${modifierSeperator} modifier is not supported in the claim name`); - } - isServeRequest = true; - } - return { - claimName, - isServeRequest, - }; -}; - module.exports = (app) => { // route to serve a specific asset using the channel or claim id app.get('/:identifier/:name', ({ headers, ip, originalUrl, params }, res) => { @@ -308,8 +219,7 @@ module.exports = (app) => { ({ isChannel, channelName, channelClaimId, claimId } = lbryuri.parseIdentifier(params.identifier)); ({ claimName, isServeRequest } = lbryuri.parseName(params.name)); } catch (error) { - logger.error(error); - return res.status(400).json({success: false, message: error}); + return handleRequestError(originalUrl, ip, error, res); } if (!isChannel) { [claimId, claimName] = flipClaimNameAndIdForBackwardsCompatibility(claimId, claimName); @@ -328,7 +238,7 @@ module.exports = (app) => { showOrServeAsset(responseType, fullClaimId, claimName, res); }) .catch(error => { - handleRequestError('serve', originalUrl, ip, error, res); + handleRequestError(originalUrl, ip, error, res); }); }); // route to serve the winning asset at a claim or a channel page @@ -337,8 +247,7 @@ module.exports = (app) => { try { ({ isChannel, channelName, channelClaimId } = lbryuri.parseIdentifier(params.identifier)); } catch (error) { - logger.error(error); - return res.status(400).json({success: false, message: error}); + return handleRequestError(originalUrl, ip, error, res); } if (isChannel) { // log the request data for debugging @@ -350,8 +259,7 @@ module.exports = (app) => { try { ({claimName, isServeRequest} = lbryuri.parseName(params.identifier)); } catch (error) { - logger.error(error); - return res.status(400).json({success: false, message: error}); + return handleRequestError(originalUrl, ip, error, res); } let responseType = determineResponseType(isServeRequest, headers); // log the request data for debugging @@ -365,7 +273,7 @@ module.exports = (app) => { showOrServeAsset(responseType, fullClaimId, claimName, res); }) .catch(error => { - handleRequestError(responseType, originalUrl, ip, error, res); + handleRequestError(originalUrl, ip, error, res); }); } });