X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;ds=sidebyside;f=server%2Flib%2Fclient-html.ts;h=899d80c15e4ecd3fe873f43a8ca4a4e1b7c157f0;hb=a49407524fc1212299a41d7c5330f27b36112167;hp=85fdc87545ab98324254304d9a62cfa9c8ec83fb;hpb=35f676e5d3e5e242e84ed63da2cc78117079c7cb;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 85fdc8754..899d80c15 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts @@ -1,19 +1,22 @@ -import * as express from 'express' +import express from 'express' import { readFile } from 'fs-extra' import { join } from 'path' import validator from 'validator' +import { isTestOrDevInstance } from '@server/helpers/core-utils' +import { toCompleteUUID } from '@server/helpers/custom-validators/misc' +import { mdToOneLinePlainText } from '@server/helpers/markdown' +import { ActorImageModel } from '@server/models/actor/actor-image' +import { root } from '@shared/core-utils' import { escapeHTML } from '@shared/core-utils/renderer' +import { sha256 } from '@shared/extra-utils' import { HTMLServerConfig } from '@shared/models' import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' -import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' +import { HttpStatusCode } from '../../shared/models/http/http-error-codes' import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' -import { isTestInstance, sha256 } from '../helpers/core-utils' import { logger } from '../helpers/logger' -import { mdToPlainText } from '../helpers/markdown' import { CONFIG } from '../initializers/config' import { ACCEPT_HEADERS, - ACTOR_IMAGES_SIZE, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, FILES_CONTENT_HASH, @@ -23,10 +26,11 @@ import { import { AccountModel } from '../models/account/account' import { VideoModel } from '../models/video/video' import { VideoChannelModel } from '../models/video/video-channel' -import { getActivityStreamDuration } from '../models/video/video-format-utils' import { VideoPlaylistModel } from '../models/video/video-playlist' import { MAccountActor, MChannelActor } from '../types/models' -import { getHTMLServerConfig } from './config' +import { getActivityStreamDuration } from './activitypub/activity' +import { getBiggestActorImage } from './actor-image' +import { ServerConfigManager } from './server-config-manager' type Tags = { ogType: string @@ -37,11 +41,14 @@ type Tags = { numberOfItems: number } - siteName: string - title: string + escapedSiteName: string + escapedTitle: string + escapedDescription: string + url: string originUrl: string - description: string + + disallowIndexation?: boolean embed?: { url: string @@ -78,7 +85,9 @@ class ClientHtml { return customHtml } - static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) { + static async getWatchHTMLPage (videoIdArg: string, req: express.Request, res: express.Response) { + const videoId = toCompleteUUID(videoIdArg) + // Let Angular application handle errors if (!validator.isInt(videoId) && !validator.isUUID(videoId, 4)) { res.status(HttpStatusCode.NOT_FOUND_404) @@ -95,15 +104,15 @@ class ClientHtml { res.status(HttpStatusCode.NOT_FOUND_404) return html } + const description = mdToOneLinePlainText(video.description) - let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) - customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(video.description)) + let customHtml = ClientHtml.addTitleTag(html, video.name) + customHtml = ClientHtml.addDescriptionTag(customHtml, description) const url = WEBSERVER.URL + video.getWatchStaticPath() const originUrl = video.url - const title = escapeHTML(video.name) - const siteName = escapeHTML(CONFIG.INSTANCE.NAME) - const description = mdToPlainText(video.description) + const title = video.name + const siteName = CONFIG.INSTANCE.NAME const image = { url: WEBSERVER.URL + video.getPreviewStaticPath() @@ -123,9 +132,10 @@ class ClientHtml { customHtml = ClientHtml.addTags(customHtml, { url, originUrl, - siteName, - title, - description, + escapedSiteName: escapeHTML(siteName), + escapedTitle: escapeHTML(title), + escapedDescription: escapeHTML(description), + disallowIndexation: video.privacy !== VideoPrivacy.PUBLIC, image, embed, ogType, @@ -136,7 +146,9 @@ class ClientHtml { return customHtml } - static async getWatchPlaylistHTMLPage (videoPlaylistId: string, req: express.Request, res: express.Response) { + static async getWatchPlaylistHTMLPage (videoPlaylistIdArg: string, req: express.Request, res: express.Response) { + const videoPlaylistId = toCompleteUUID(videoPlaylistIdArg) + // Let Angular application handle errors if (!validator.isInt(videoPlaylistId) && !validator.isUUID(videoPlaylistId, 4)) { res.status(HttpStatusCode.NOT_FOUND_404) @@ -154,14 +166,15 @@ class ClientHtml { return html } - let customHtml = ClientHtml.addTitleTag(html, escapeHTML(videoPlaylist.name)) - customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(videoPlaylist.description)) + const description = mdToOneLinePlainText(videoPlaylist.description) + + let customHtml = ClientHtml.addTitleTag(html, videoPlaylist.name) + customHtml = ClientHtml.addDescriptionTag(customHtml, description) - const url = videoPlaylist.getWatchUrl() + const url = WEBSERVER.URL + videoPlaylist.getWatchStaticPath() const originUrl = videoPlaylist.url - const title = escapeHTML(videoPlaylist.name) - const siteName = escapeHTML(CONFIG.INSTANCE.NAME) - const description = mdToPlainText(videoPlaylist.description) + const title = videoPlaylist.name + const siteName = CONFIG.INSTANCE.NAME const image = { url: videoPlaylist.getThumbnailUrl() @@ -183,10 +196,11 @@ class ClientHtml { customHtml = ClientHtml.addTags(customHtml, { url, originUrl, - siteName, + escapedSiteName: escapeHTML(siteName), + escapedTitle: escapeHTML(title), + escapedDescription: escapeHTML(description), + disallowIndexation: videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC, embed, - title, - description, image, list, ogType, @@ -198,20 +212,34 @@ class ClientHtml { } static async getAccountHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { - return this.getAccountOrChannelHTMLPage(() => AccountModel.loadByNameWithHost(nameWithHost), req, res) + const accountModelPromise = AccountModel.loadByNameWithHost(nameWithHost) + return this.getAccountOrChannelHTMLPage(() => accountModelPromise, req, res) } static async getVideoChannelHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { - return this.getAccountOrChannelHTMLPage(() => VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost), req, res) + const videoChannelModelPromise = VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost) + return this.getAccountOrChannelHTMLPage(() => videoChannelModelPromise, req, res) + } + + static async getActorHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { + const [ account, channel ] = await Promise.all([ + AccountModel.loadByNameWithHost(nameWithHost), + VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost) + ]) + + return this.getAccountOrChannelHTMLPage(() => Promise.resolve(account || channel), req, res) } static async getEmbedHTML () { const path = ClientHtml.getEmbedPath() - if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] + // Disable HTML cache in dev mode because webpack can regenerate JS files + if (!isTestOrDevInstance() && ClientHtml.htmlCache[path]) { + return ClientHtml.htmlCache[path] + } const buffer = await readFile(path) - const serverConfig = await getHTMLServerConfig() + const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() let html = buffer.toString() html = await ClientHtml.addAsyncPluginCSS(html) @@ -241,19 +269,21 @@ class ClientHtml { return ClientHtml.getIndexHTML(req, res) } - let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) - customHtml = ClientHtml.addDescriptionTag(customHtml, mdToPlainText(entity.description)) + const description = mdToOneLinePlainText(entity.description) + + let customHtml = ClientHtml.addTitleTag(html, entity.getDisplayName()) + customHtml = ClientHtml.addDescriptionTag(customHtml, description) const url = entity.getLocalUrl() const originUrl = entity.Actor.url - const siteName = escapeHTML(CONFIG.INSTANCE.NAME) - const title = escapeHTML(entity.getDisplayName()) - const description = mdToPlainText(entity.description) + const siteName = CONFIG.INSTANCE.NAME + const title = entity.getDisplayName() + const avatar = getBiggestActorImage(entity.Actor.Avatars) const image = { - url: entity.Actor.getAvatarUrl(), - width: ACTOR_IMAGES_SIZE.AVATARS.width, - height: ACTOR_IMAGES_SIZE.AVATARS.height + url: ActorImageModel.getImageUrl(avatar), + width: avatar?.width, + height: avatar?.height } const ogType = 'website' @@ -263,13 +293,14 @@ class ClientHtml { customHtml = ClientHtml.addTags(customHtml, { url, originUrl, - title, - siteName, - description, + escapedTitle: escapeHTML(title), + escapedSiteName: escapeHTML(siteName), + escapedDescription: escapeHTML(description), image, ogType, twitterCard, - schemaType + schemaType, + disallowIndexation: !entity.Actor.isOwned() }) return customHtml @@ -277,14 +308,13 @@ class ClientHtml { private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { const path = ClientHtml.getIndexPath(req, res, paramLang) - if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] + if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] const buffer = await readFile(path) - const serverConfig = await getHTMLServerConfig() + const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() let html = buffer.toString() - if (paramLang) html = ClientHtml.addHtmlLang(html, paramLang) html = ClientHtml.addManifestContentHash(html) html = ClientHtml.addFaviconContentHash(html) html = ClientHtml.addLogoContentHash(html) @@ -317,15 +347,16 @@ class ClientHtml { lang = req.acceptsLanguages(POSSIBLE_LOCALES) || getDefaultLocale() } - return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') - } + logger.debug( + 'Serving %s HTML language', buildFileLocale(lang), + { cookie: req.cookies?.clientLanguage, paramLang, acceptLanguage: req.headers['accept-language'] } + ) - private static getEmbedPath () { - return join(__dirname, '../../../client/dist/standalone/videos/embed.html') + return join(root(), 'client', 'dist', buildFileLocale(lang), 'index.html') } - private static addHtmlLang (htmlStringPage: string, paramLang: string) { - return htmlStringPage.replace('', ``) + private static getEmbedPath () { + return join(root(), 'client', 'dist', 'standalone', 'videos', 'embed.html') } private static addManifestContentHash (htmlStringPage: string) { @@ -344,14 +375,14 @@ class ClientHtml { let text = title || CONFIG.INSTANCE.NAME if (title) text += ` - ${CONFIG.INSTANCE.NAME}` - const titleTag = `