2
1

oembed.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import express from 'express'
  2. import { query } from 'express-validator'
  3. import { join } from 'path'
  4. import { loadVideo } from '@server/lib/model-loaders'
  5. import { VideoPlaylistModel } from '@server/models/video/video-playlist'
  6. import { VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
  7. import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
  8. import { isTestInstance } from '../../helpers/core-utils'
  9. import { isIdOrUUIDValid, toCompleteUUID } from '../../helpers/custom-validators/misc'
  10. import { logger } from '../../helpers/logger'
  11. import { WEBSERVER } from '../../initializers/constants'
  12. import { areValidationErrors } from './shared'
  13. const playlistPaths = [
  14. join('videos', 'watch', 'playlist'),
  15. join('w', 'p')
  16. ]
  17. const videoPaths = [
  18. join('videos', 'watch'),
  19. 'w'
  20. ]
  21. function buildUrls (paths: string[]) {
  22. return paths.map(p => WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, p) + '/')
  23. }
  24. const startPlaylistURLs = buildUrls(playlistPaths)
  25. const startVideoURLs = buildUrls(videoPaths)
  26. const isURLOptions = {
  27. require_host: true,
  28. require_tld: true
  29. }
  30. // We validate 'localhost', so we don't have the top level domain
  31. if (isTestInstance()) {
  32. isURLOptions.require_tld = false
  33. }
  34. const oembedValidator = [
  35. query('url').isURL(isURLOptions).withMessage('Should have a valid url'),
  36. query('maxwidth').optional().isInt().withMessage('Should have a valid max width'),
  37. query('maxheight').optional().isInt().withMessage('Should have a valid max height'),
  38. query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'),
  39. async (req: express.Request, res: express.Response, next: express.NextFunction) => {
  40. logger.debug('Checking oembed parameters', { parameters: req.query })
  41. if (areValidationErrors(req, res)) return
  42. if (req.query.format !== undefined && req.query.format !== 'json') {
  43. return res.fail({
  44. status: HttpStatusCode.NOT_IMPLEMENTED_501,
  45. message: 'Requested format is not implemented on server.',
  46. data: {
  47. format: req.query.format
  48. }
  49. })
  50. }
  51. const url = req.query.url as string
  52. let urlPath: string
  53. try {
  54. urlPath = new URL(url).pathname
  55. } catch (err) {
  56. return res.fail({
  57. status: HttpStatusCode.BAD_REQUEST_400,
  58. message: err.message,
  59. data: {
  60. url
  61. }
  62. })
  63. }
  64. const isPlaylist = startPlaylistURLs.some(u => url.startsWith(u))
  65. const isVideo = isPlaylist ? false : startVideoURLs.some(u => url.startsWith(u))
  66. const startIsOk = isVideo || isPlaylist
  67. const parts = urlPath.split('/')
  68. if (startIsOk === false || parts.length === 0) {
  69. return res.fail({
  70. status: HttpStatusCode.BAD_REQUEST_400,
  71. message: 'Invalid url.',
  72. data: {
  73. url
  74. }
  75. })
  76. }
  77. const elementId = toCompleteUUID(parts.pop())
  78. if (isIdOrUUIDValid(elementId) === false) {
  79. return res.fail({ message: 'Invalid video or playlist id.' })
  80. }
  81. if (isVideo) {
  82. const video = await loadVideo(elementId, 'all')
  83. if (!video) {
  84. return res.fail({
  85. status: HttpStatusCode.NOT_FOUND_404,
  86. message: 'Video not found'
  87. })
  88. }
  89. if (video.privacy !== VideoPrivacy.PUBLIC) {
  90. return res.fail({
  91. status: HttpStatusCode.FORBIDDEN_403,
  92. message: 'Video is not public'
  93. })
  94. }
  95. res.locals.videoAll = video
  96. return next()
  97. }
  98. // Is playlist
  99. const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(elementId, undefined)
  100. if (!videoPlaylist) {
  101. return res.fail({
  102. status: HttpStatusCode.NOT_FOUND_404,
  103. message: 'Video playlist not found'
  104. })
  105. }
  106. if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC) {
  107. return res.fail({
  108. status: HttpStatusCode.FORBIDDEN_403,
  109. message: 'Playlist is not public'
  110. })
  111. }
  112. res.locals.videoPlaylistSummary = videoPlaylist
  113. return next()
  114. }
  115. ]
  116. // ---------------------------------------------------------------------------
  117. export {
  118. oembedValidator
  119. }