oembed.ts 4.0 KB

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