diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 5ed0435ff..62412cd62 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts @@ -74,7 +74,7 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', activityPubClientRouter.get('/videos/watch/:id', executeIfActivityPub, - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)), asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), asyncMiddleware(videoController) ) diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts index 23706767a..46e76ac6b 100644 --- a/server/controllers/api/overviews.ts +++ b/server/controllers/api/overviews.ts @@ -11,7 +11,7 @@ import * as memoizee from 'memoizee' const overviewsRouter = express.Router() overviewsRouter.get('/videos', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS)), asyncMiddleware(getVideosOverview) ) diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts index 951b98209..3616c074d 100644 --- a/server/controllers/api/server/stats.ts +++ b/server/controllers/api/server/stats.ts @@ -14,7 +14,7 @@ import { CONFIG } from '../../../initializers/config' const statsRouter = express.Router() statsRouter.get('/stats', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.STATS)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.STATS)), asyncMiddleware(getStats) ) diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts index f3e778b04..63280dabb 100644 --- a/server/controllers/bots.ts +++ b/server/controllers/bots.ts @@ -14,7 +14,7 @@ const botsRouter = express.Router() // Special route that add OpenGraph and oEmbed tags // Do not use a template engine for a so little thing botsRouter.use('/sitemap.xml', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.SITEMAP)), asyncMiddleware(getSitemap) ) diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index fa6c7ac71..72628dffb 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -6,7 +6,9 @@ import { setDefaultSort, videoCommentsFeedsValidator, videoFeedsValidator, - videosSortValidator + videosSortValidator, + feedsFormatValidator, + setFeedFormatContentType } from '../middlewares' import { VideoModel } from '../models/video/video' import * as Feed from 'pfeed' @@ -18,7 +20,13 @@ import { CONFIG } from '../initializers/config' const feedsRouter = express.Router() feedsRouter.get('/feeds/video-comments.:format', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)), + feedsFormatValidator, + setFeedFormatContentType, + asyncMiddleware(cacheRoute({ + headerBlacklist: [ + 'Content-Type' + ] + })(ROUTE_CACHE_LIFETIME.FEEDS)), asyncMiddleware(videoCommentsFeedsValidator), asyncMiddleware(generateVideoCommentsFeed) ) @@ -26,7 +34,13 @@ feedsRouter.get('/feeds/video-comments.:format', feedsRouter.get('/feeds/videos.:format', videosSortValidator, setDefaultSort, - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.FEEDS)), + feedsFormatValidator, + setFeedFormatContentType, + asyncMiddleware(cacheRoute({ + headerBlacklist: [ + 'Content-Type' + ] + })(ROUTE_CACHE_LIFETIME.FEEDS)), commonVideosFiltersValidator, asyncMiddleware(videoFeedsValidator), asyncMiddleware(generateVideoFeed) @@ -224,26 +238,21 @@ function sendFeed (feed, req: express.Request, res: express.Response) { const format = req.params.format if (format === 'atom' || format === 'atom1') { - res.set('Content-Type', 'application/atom+xml') return res.send(feed.atom1()).end() } if (format === 'json' || format === 'json1') { - res.set('Content-Type', 'application/json') return res.send(feed.json1()).end() } if (format === 'rss' || format === 'rss2') { - res.set('Content-Type', 'application/rss+xml') return res.send(feed.rss2()).end() } // We're in the ambiguous '.xml' case and we look at the format query parameter if (req.query.format === 'atom' || req.query.format === 'atom1') { - res.set('Content-Type', 'application/atom+xml') return res.send(feed.atom1()).end() } - res.set('Content-Type', 'application/rss+xml') return res.send(feed.rss2()).end() } diff --git a/server/controllers/static.ts b/server/controllers/static.ts index f86a0cb5b..a4bb3a4d9 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts @@ -112,7 +112,7 @@ staticRouter.use( // robots.txt service staticRouter.get('/robots.txt', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ROBOTS)), (_, res: express.Response) => { res.type('text/plain') return res.send(CONFIG.INSTANCE.ROBOTS) @@ -127,7 +127,7 @@ staticRouter.get('/security.txt', ) staticRouter.get('/.well-known/security.txt', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.SECURITYTXT)), (_, res: express.Response) => { res.type('text/plain') return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT) @@ -136,7 +136,7 @@ staticRouter.get('/.well-known/security.txt', // nodeinfo service staticRouter.use('/.well-known/nodeinfo', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.NODEINFO)), (_, res: express.Response) => { return res.json({ links: [ @@ -149,13 +149,13 @@ staticRouter.use('/.well-known/nodeinfo', } ) staticRouter.use('/nodeinfo/:version.json', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.NODEINFO)), asyncMiddleware(generateNodeinfo) ) // dnt-policy.txt service (see https://www.eff.org/dnt-policy) staticRouter.use('/.well-known/dnt-policy.txt', - asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY)), + asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.DNT_POLICY)), (_, res: express.Response) => { res.type('text/plain') diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts index ef8611875..cb24d9e0e 100644 --- a/server/middlewares/cache.ts +++ b/server/middlewares/cache.ts @@ -4,12 +4,18 @@ import * as apicache from 'apicache' // Ensure Redis is initialized Redis.Instance.init() -const options = { +const defaultOptions = { redisClient: Redis.Instance.getClient(), - appendKey: () => Redis.Instance.getPrefix() + appendKey: () => Redis.Instance.getPrefix(), + statusCodes: { + exclude: [ 404, 403 ] + } } -const cacheRoute = apicache.options(options).middleware +const cacheRoute = (extraOptions = {}) => apicache.options({ + ...defaultOptions, + ...extraOptions +}).middleware // --------------------------------------------------------------------------- diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 1bef9891b..29f6c87be 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts @@ -12,9 +12,37 @@ import { doesVideoChannelNameWithHostExist } from '../../helpers/middlewares' -const videoFeedsValidator = [ +const feedsFormatValidator = [ param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), - query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), + query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)') +] + +function setFeedFormatContentType (req: express.Request, res: express.Response, next: express.NextFunction) { + const format = req.query.format || req.params.format || 'rss' + + let acceptableContentTypes: string[] + if (format === 'atom' || format === 'atom1') { + acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml'] + } else if (format === 'json' || format === 'json1') { + acceptableContentTypes = ['application/json'] + } else if (format === 'rss' || format === 'rss2') { + acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml'] + } else { + acceptableContentTypes = ['application/xml', 'text/xml'] + } + + if (req.accepts(acceptableContentTypes)) { + res.set('Content-Type', req.accepts(acceptableContentTypes) as string) + } else { + return res.status(406).send({ + message: `You should accept at least one of the following content-types: ${acceptableContentTypes.join(', ')}` + }).end() + } + + return next() +} + +const videoFeedsValidator = [ query('accountId').optional().custom(isIdValid), query('accountName').optional(), query('videoChannelId').optional().custom(isIdValid), @@ -35,8 +63,6 @@ const videoFeedsValidator = [ ] const videoCommentsFeedsValidator = [ - param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), - query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), query('videoId').optional().custom(isIdOrUUIDValid), async (req: express.Request, res: express.Response, next: express.NextFunction) => { @@ -53,6 +79,8 @@ const videoCommentsFeedsValidator = [ // --------------------------------------------------------------------------- export { + feedsFormatValidator, + setFeedFormatContentType, videoFeedsValidator, videoCommentsFeedsValidator }