-
-
Notifications
You must be signed in to change notification settings - Fork 335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 1242 url preview #1247
base: main
Are you sure you want to change the base?
Issue 1242 url preview #1247
Changes from all commits
4e58d1a
b76e3ba
a5a6a38
64cbdee
c372eb8
3c7b862
9fcf510
0e1727d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { | ||
commonUrls, | ||
Features, | ||
getContactSupportUrl, | ||
getFreeCoachingCallUrl, | ||
|
@@ -24,9 +25,22 @@ import * as fse from 'fs-extra'; | |
import * as handlebars from 'handlebars'; | ||
import jsesc from 'jsesc'; | ||
import * as path from 'path'; | ||
import difference = require('lodash/difference'); | ||
import difference from 'lodash/difference'; | ||
|
||
const translate = (req: express.Request, key: string, args?: any) => req.t(`sendAppPage.${key}`, args); | ||
const { escapeExpression } = handlebars.Utils; | ||
|
||
/** | ||
* Return the translation given the key, but also ensure that the return value is HTML-escaped | ||
* in order to avoid possible script injection (that we don't need in any case). | ||
* | ||
* @param req | ||
* @param key The key of the translation (which will be prefixed by `sendAppPage`) | ||
* @param args The args to pass to the translation string (optional) | ||
*/ | ||
function translateEscaped(req: express.Request, key: string, args?: any) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This helper is giving me pause. I guess anything it is used to translate won't be picked up by https://github.com/gristlabs/grist-core/blob/main/buildtools/generate_translation_keys.js - not necessarily a problem, since you can add manually - but makes the eventual garbage collection problem harder of figuring out what keys are no longer used as code changes so they can be dropped. The escaping, the threat model here is that the translations may be tainted, we can't trust them? This does make sense given how our weblate project is run, it is pretty open. Just going to page @berhalak here for some advice on whether there might be a more systematic way to handle this threat. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can leave this helper for now. I will remove it later and prepare some cleaning mechanism at the build time. Same issue is for client, when someone uses it insecurely (like el.innerHTML = t(....)). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @berhalak Right, yeah. Thanks for your feedback, do you want me to create a separate issue for that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @fflorent, Yes please. I'll try to do it asap. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const translation = req.t(`sendAppPage.${key}`, args)?.toString(); | ||
return translation ? escapeExpression(translation) : translation; | ||
} | ||
|
||
export interface ISendAppPageOptions { | ||
path: string; // Ignored if .content is present (set to "" for clarity). | ||
|
@@ -174,8 +188,8 @@ export function makeSendAppPage({ server, staticDir, tag, testLogin, baseDomain | |
).join('\n'); | ||
const content = fileContent | ||
.replace("<!-- INSERT WARNING -->", warning) | ||
.replace("<!-- INSERT TITLE -->", getPageTitle(req, config)) | ||
.replace("<!-- INSERT META -->", getPageMetadataHtmlSnippet(config)) | ||
.replace("<!-- INSERT TITLE -->", getDocName(config) ?? escapeExpression(translateEscaped(req, 'Loading'))) | ||
.replace("<!-- INSERT META -->", getPageMetadataHtmlSnippet(req, config)) | ||
.replace("<!-- INSERT TITLE SUFFIX -->", getPageTitleSuffix(server.getGristConfig())) | ||
.replace("<!-- INSERT BASE -->", `<base href="${staticBaseUrl}">` + tagManagerSnippet) | ||
.replace("<!-- INSERT LOCALE -->", preloads) | ||
|
@@ -264,18 +278,15 @@ function configuredPageTitleSuffix() { | |
} | ||
|
||
/** | ||
* Returns a page title suitable for inserting into an HTML title element. | ||
* | ||
* Currently returns the document name if the page being requested is for a document, or | ||
* a placeholder, "Loading...", that's updated in the client once the page has loaded. | ||
* Returns the doc name. | ||
* | ||
* Note: The string returned is escaped and safe to insert into HTML. | ||
* | ||
*/ | ||
function getPageTitle(req: express.Request, config: GristLoadConfig): string { | ||
function getDocName(config: GristLoadConfig): string|null { | ||
const maybeDoc = getDocFromConfig(config); | ||
if (!maybeDoc) { return translate(req, 'Loading') + "..."; } | ||
|
||
return handlebars.Utils.escapeExpression(maybeDoc.name); | ||
return maybeDoc && escapeExpression(maybeDoc.name); | ||
} | ||
|
||
/** | ||
|
@@ -286,25 +297,30 @@ function getPageTitle(req: express.Request, config: GristLoadConfig): string { | |
* | ||
* Note: The string returned is escaped and safe to insert into HTML. | ||
*/ | ||
function getPageMetadataHtmlSnippet(config: GristLoadConfig): string { | ||
function getPageMetadataHtmlSnippet(req: express.Request, config: GristLoadConfig): string { | ||
const metadataElements: string[] = []; | ||
const maybeDoc = getDocFromConfig(config); | ||
|
||
const description = maybeDoc?.options?.description; | ||
if (description) { | ||
const content = handlebars.Utils.escapeExpression(description); | ||
metadataElements.push(`<meta name="description" content="${content}">`); | ||
metadataElements.push(`<meta property="og:description" content="${content}">`); | ||
metadataElements.push(`<meta name="twitter:description" content="${content}">`); | ||
} | ||
metadataElements.push('<meta property="og:type" content="website">'); | ||
metadataElements.push('<meta name="twitter:card" content="summary_large_image">'); | ||
|
||
const icon = maybeDoc?.options?.icon; | ||
if (icon) { | ||
const content = handlebars.Utils.escapeExpression(icon); | ||
metadataElements.push(`<meta name="thumbnail" content="${content}">`); | ||
metadataElements.push(`<meta property="og:image" content="${content}">`); | ||
metadataElements.push(`<meta name="twitter:image" content="${content}">`); | ||
} | ||
const description = maybeDoc?.options?.description ? | ||
escapeExpression(maybeDoc.options.description) : | ||
translateEscaped(req, 'og-description'); | ||
metadataElements.push(`<meta name="description" content="${description}">`); | ||
metadataElements.push(`<meta property="og:description" content="${description}">`); | ||
metadataElements.push(`<meta name="twitter:description" content="${description}">`); | ||
|
||
const image = escapeExpression(maybeDoc?.options?.icon ?? commonUrls.openGraphPreviewImage); | ||
metadataElements.push(`<meta name="thumbnail" content="${image}">`); | ||
metadataElements.push(`<meta property="og:image" content="${image}">`); | ||
metadataElements.push(`<meta name="twitter:image" content="${image}">`); | ||
|
||
const maybeDocTitle = getDocName(config); | ||
const title = maybeDocTitle ? maybeDocTitle + getPageTitleSuffix(config) : translateEscaped(req, 'og-title'); | ||
// NB: We don't generate the content of the <title> tag here. | ||
metadataElements.push(`<meta property="og:title" content="${title}">`); | ||
metadataElements.push(`<meta name="twitter:title" content="${title}">`); | ||
|
||
return metadataElements.join('\n'); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"sendAppPage": { | ||
"Loading": "Chargement" | ||
"Loading": "Chargement..." | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little concerned about GDPR for European self-hosters opening their instances publicly.
They should have a mechanism to a least offer user deactivation of such url sending data to a third party.
I'm realizing that there is a lot of urls in DINUM and ANCT instances sending IPs data to a third party in GDRP terms.
May be it could be a good Idea to open an issue about that.