[FIX] website, *: avoid using unescaped query parameters

*: google_recaptcha, web_editor, website_blog, website_event_booth,
   website_event_exhibitor, website_event_track, website_forum,
   website_jitsi, website_sale_comparison, website_slides,
   website_livechat, website_twitter

This commit fixes improperly escaped query parameters across javascripts
of website-related modules.

See https://github.com/odoo/enterprise/pull/31711

closes odoo/odoo#118599

X-original-commit: 89be076aa51b097ada48d0046006be4d9692ac1e
Related: odoo/enterprise#39783
Signed-off-by: Bojabza Soukéina (sobo) <sobo@odoo.com>
This commit is contained in:
Benoit Socias
2023-04-18 11:33:46 +02:00
parent 5f0663b522
commit 02ed9f5e66
39 changed files with 67 additions and 64 deletions
@@ -21,7 +21,7 @@ const ReCaptcha = Class.extend({
*/
loadLibs: function () {
if (this._publicKey) {
this._recaptchaReady = loadJS(`https://www.recaptcha.net/recaptcha/api.js?render=${this._publicKey}`)
this._recaptchaReady = loadJS(`https://www.recaptcha.net/recaptcha/api.js?render=${encodeURIComponent(this._publicKey)}`)
.then(() => new Promise(resolve => window.grecaptcha.ready(() => resolve())));
return this._recaptchaReady.then(() => !!document.querySelector('.grecaptcha-badge'));
}
+2 -2
View File
@@ -48,14 +48,14 @@ files.filter(f => f.endsWith('svg')).forEach(filePath => {
repeatX: fileName.includes('repeatx'),
repeatY: fileName.includes('repeaty'),
};
shape.optionXML = `<we-button data-shape="web_editor/${shape.page}/${shape.name}" data-select-label="${shape.page} ${shape.name}"/>`;
shape.optionXML = `<we-button data-shape="web_editor/${encodeURIComponent(shape.page)}/${encodeURIComponent(shape.name)}" data-select-label="${shape.page} ${shape.name}"/>`;
if (shape.position[0] === 'stretch') {
shape.position = ['center'];
shape.size = '100% 100%';
} else {
shape.size = '100% auto';
}
shape.scss = `'${shape.page}/${shape.name}': ('position': ${shape.position[0]}, 'size': ${shape.size}, 'colors': (${shape.colors.join(', ')})${shape.repeatX ? ", 'repeat-x': true" : ""}${shape.repeatY ? ", 'repeat-y': true" : ""})`;
shape.scss = `'${encodeURIComponent(shape.page)}/${encodeURIComponent(shape.name)}': ('position': ${shape.position[0]}, 'size': ${shape.size}, 'colors': (${shape.colors.join(', ')})${shape.repeatX ? ", 'repeat-x': true" : ""}${shape.repeatY ? ", 'repeat-y': true" : ""})`;
shapes.push(shape);
});
const xml = shapes.map(shape => shape.optionXML).join('\n');
@@ -49,7 +49,7 @@ export class DocumentSelector extends FileSelector {
static async createElements(selectedMedia, { orm }) {
return Promise.all(selectedMedia.map(async attachment => {
const linkEl = document.createElement('a');
let href = `/web/content/${attachment.id}?unique=${attachment.checksum}&download=true`;
let href = `/web/content/${encodeURIComponent(attachment.id)}?unique=${encodeURIComponent(attachment.checksum)}&download=true`;
if (!attachment.public) {
let accessToken = attachment.access_token;
if (!accessToken) {
@@ -59,7 +59,7 @@ export class DocumentSelector extends FileSelector {
[attachment.id],
);
}
href += `&access_token=${accessToken}`;
href += `&access_token=${encodeURIComponent(accessToken)}`;
}
linkEl.href = href;
linkEl.title = attachment.name;
@@ -40,7 +40,7 @@ AttachmentError.template = xml `
following pages or views:</p>
<ul t-foreach="props.views" t-as="view" t-key="view.id">
<li>
<a t-att-href="'/web#model=ir.ui.view&amp;id=' + view.id">
<a t-att-href="'/web#model=ir.ui.view&amp;id=' + window.encodeURIComponent(view.id)">
<t t-esc="view.name"/>
</a>
</li>
@@ -272,7 +272,7 @@ export class ImageSelector extends FileSelector {
[attachment.id],
);
}
src += `?access_token=${accessToken}`;
src += `?access_token=${encodeURIComponent(accessToken)}`;
}
imageEl.src = src;
imageEl.alt = attachment.description || '';
@@ -89,11 +89,11 @@ export class VideoSelector extends Component {
onMounted(async () => {
await Promise.all(this.props.vimeoPreviewIds.map(async (videoId) => {
const { thumbnail_url: thumbnailSrc } = await this.http.get(`https://vimeo.com/api/oembed.json?url=http%3A//vimeo.com/${videoId}`);
const { thumbnail_url: thumbnailSrc } = await this.http.get(`https://vimeo.com/api/oembed.json?url=http%3A//vimeo.com/${encodeURIComponent(videoId)}`);
this.state.vimeoPreviews.push({
id: videoId,
thumbnailSrc,
src: `https://player.vimeo.com/video/${videoId}`
src: `https://player.vimeo.com/video/${encodeURIComponent(videoId)}`
});
}));
});
@@ -721,7 +721,7 @@ function fontToImg($editable) {
const image = document.createElement('img');
image.setAttribute('width', intrinsicWidth);
image.setAttribute('height', intrinsicHeight);
image.setAttribute('src', `/web_editor/font_to_img/${content.charCodeAt(0)}/${window.encodeURI(color)}/${window.encodeURI(bg)}/${Math.max(1, Math.round(intrinsicWidth))}x${Math.max(1, Math.round(intrinsicHeight))}`);
image.setAttribute('src', `/web_editor/font_to_img/${content.charCodeAt(0)}/${encodeURIComponent(color)}/${encodeURIComponent(bg)}/${Math.max(1, Math.round(intrinsicWidth))}x${Math.max(1, Math.round(intrinsicHeight))}`);
image.setAttribute('data-class', font.getAttribute('class'));
image.setAttribute('data-style', style);
image.setAttribute('style', style);
@@ -4397,7 +4397,7 @@ export class OdooEditor extends EventTarget {
videoElement.setAttribute('height', '315');
videoElement.setAttribute(
'src',
`https://www.youtube.com/embed/${youtubeUrl[1]}`,
`https://www.youtube.com/embed/${encodeURIComponent(youtubeUrl[1])}`,
);
videoElement.setAttribute('title', 'YouTube video player');
videoElement.setAttribute('frameborder', '0');
@@ -3751,7 +3751,7 @@ var SnippetsMenu = Widget.extend({
$content: $('<div/>', {text: sprintf(_t("Do you want to install the %s App?"), name)}).append(
$('<a/>', {
target: '_blank',
href: '/web#id=' + moduleID + '&view_type=form&model=ir.module.module&action=base.open_module_tree',
href: '/web#id=' + encodeURIComponent(moduleID) + '&view_type=form&model=ir.module.module&action=base.open_module_tree',
text: _t("More info about this app."),
class: 'ml4',
})
@@ -6128,7 +6128,7 @@ registry.ImageTools = ImageHandlerOption.extend({
const [module, directory, fileName] = shapeName.split('/');
let shape = this.shapeCache[fileName];
if (!shape) {
const shapeURL = `/${module}/static/image_shapes/${directory}/${fileName}.svg`;
const shapeURL = `/${encodeURIComponent(module)}/static/image_shapes/${encodeURIComponent(directory)}/${encodeURIComponent(fileName)}.svg`;
shape = await (await fetch(shapeURL)).text();
this.shapeCache[fileName] = shape;
}
@@ -6334,7 +6334,7 @@ registry.ImageTools = ImageHandlerOption.extend({
uiFragment.querySelectorAll('we-select-page we-button[data-set-img-shape]').forEach(btn => {
const image = document.createElement('img');
const [moduleName, directory, shapeName] = btn.dataset.setImgShape.split('/');
image.src = `/${moduleName}/static/image_shapes/${directory}/${shapeName}.svg`;
image.src = `/${encodeURIComponent(moduleName)}/static/image_shapes/${encodeURIComponent(directory)}/${encodeURIComponent(shapeName)}.svg`;
$(btn).prepend(image);
if (btn.dataset.animated) {
@@ -6411,7 +6411,7 @@ registry.ImageTools = ImageHandlerOption.extend({
// attribute.
match = match.slice(0, -12);
}
return this._loadImageInfo(`/web/image/${match}`);
return this._loadImageInfo(`/web/image/${encodeURIComponent(match)}`);
}
return _super(...arguments);
},
@@ -7163,9 +7163,9 @@ registry.BackgroundShape = SnippetOptionWidget.extend({
return `${colorName}=${encodedCol}`;
});
if (flip.length) {
searchParams.push(`flip=${flip.sort().join('')}`);
searchParams.push(`flip=${encodeURIComponent(flip.sort().join(''))}`);
}
return `/web_editor/shape/${shape}.svg?${searchParams.join('&')}`;
return `/web_editor/shape/${encodeURIComponent(shape)}.svg?${searchParams.join('&')}`;
},
/**
* Retrieves current shape data from the target's dataset.
@@ -209,7 +209,7 @@ const LinkPopoverWidget = Widget.extend({
// would need to fetch the page through the server (s2s), involving
// enduser fetching problematic pages such as illicit content.
this.$previewFaviconImg.attr({
'src': `https://www.google.com/s2/favicons?sz=16&domain=${url}`
'src': `https://www.google.com/s2/favicons?sz=16&domain=${encodeURIComponent(url)}`
}).removeClass('d-none');
this.$previewFaviconFa.addClass('d-none');
} else {
@@ -219,7 +219,7 @@ const Wysiwyg = Widget.extend({
categories: powerboxOptions.categories,
plugins: options.editorPlugins,
direction: options.direction || localization.direction || 'ltr',
collaborationClientAvatarUrl: `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${this.getSession().uid}`,
collaborationClientAvatarUrl: `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${encodeURIComponent(this.getSession().uid)}`,
renderingClasses: ['o_dirty', 'o_transform_removal', 'oe_edited_link', 'o_menu_loading'],
dropImageAsAttachment: options.dropImageAsAttachment,
foldSnippets: !!options.foldSnippets,
@@ -503,7 +503,7 @@ const Wysiwyg = Widget.extend({
}
return this._userName;
},
get_client_avatar: () => `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${this.getSession().uid}`,
get_client_avatar: () => `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${encodeURIComponent(this.getSession().uid)}`,
get_missing_steps: (params) => this.odooEditor.historyGetMissingSteps(params.requestPayload),
get_history_from_snapshot: () => this.odooEditor.historyGetSnapshotSteps(),
get_collaborative_selection: () => this.odooEditor.getCurrentCollaborativeSelection(),
@@ -964,7 +964,7 @@ const Wysiwyg = Widget.extend({
// it was modified previously, as the other modified image may be used
// elsewhere if the snippet was duplicated or was saved as a custom one.
const newAttachmentSrc = await this._rpc({
route: `/web_editor/modify_image/${el.dataset.originalId}`,
route: `/web_editor/modify_image/${encodeURIComponent(el.dataset.originalId)}`,
params: {
res_model: resModel,
res_id: parseInt(resId),
@@ -372,7 +372,7 @@ class ApplyConfiguratorScreen extends Component {
// Here the website service goToWebsite method is not used because
// the web client needs to be reloaded after the new modules have
// been installed.
window.location.replace(`/web#action=website.website_preview&website_id=${resp.website_id}&enable_editor=1&with_loader=1`);
window.location.replace(`/web#action=website.website_preview&website_id=${encodeURIComponent(resp.website_id)}&enable_editor=1&with_loader=1`);
}
}
}
@@ -634,7 +634,7 @@ export class Configurator extends Component {
}
get pathname() {
return `/website/configurator${this.state.currentStep ? `/${this.state.currentStep}` : ''}`;
return `/website/configurator${this.state.currentStep ? `/${encodeURIComponent(this.state.currentStep)}` : ''}`;
}
get storageItemName() {
@@ -65,9 +65,9 @@ export class WebsitePreview extends Component {
// URL (event if it wasn't, it wouldn't be an issue as those are
// really considered as the same domain, the user will share the
// same session and CORS errors won't be a thing in such a case)
window.location.href = `${this.websiteDomain}/web#action=website.website_preview&path=${encodedPath}&website_id=${this.websiteId}`;
window.location.href = `${encodeURI(this.websiteDomain)}/web#action=website.website_preview&path=${encodedPath}&website_id=${encodeURIComponent(this.websiteId)}`;
} else {
this.initialUrl = `/website/force/${this.websiteId}?path=${encodedPath}`;
this.initialUrl = `/website/force/${encodeURIComponent(this.websiteId)}?path=${encodedPath}`;
}
});
@@ -29,7 +29,7 @@ class ImageSelector extends Component {
this.seoContext = useState(seoContext);
const firstImageId = this.props.hasSocialDefaultImage ? 'social_default_image' : 'logo';
const firstImageSrc = `/web/image/website/${this.website.currentWebsite.id}/${firstImageId}`;
const firstImageSrc = `/web/image/website/${encodeURIComponent(this.website.currentWebsite.id)}/${firstImageId}`;
const firstImage = {
src: firstImageSrc,
active: this.areSameImages(firstImageSrc, this.seoContext.metaImage),
@@ -696,7 +696,7 @@ export class WysiwygAdapterComponent extends ComponentAdapter {
} else if (event.data.reloadWebClient) {
const currentPath = encodeURIComponent(window.location.pathname);
const websiteId = this.websiteService.currentWebsite.id;
callback = () => window.location = `/web#action=website.website_preview&website_id=${websiteId}&path=${currentPath}&enable_editor=1`;
callback = () => window.location = `/web#action=website.website_preview&website_id=${encodeURIComponent(websiteId)}&path=${currentPath}&enable_editor=1`;
} else if (event.data.action) {
callback = () => {
this.leaveEditMode({
@@ -103,7 +103,7 @@ var Dashboard = AbstractAction.extend({
on_go_to_website: function (ev) {
ev.preventDefault();
var website = this.websites.find(website => website.selected);
window.location.replace(`/web#action=website.website_preview&website_id=${website.id}`);
window.location.replace(`/web#action=website.website_preview&website_id=${encodeURIComponent(website.id)}`);
},
@@ -141,7 +141,7 @@ export const WebsiteRoot = publicRootData.PublicRoot.extend(KeyboardNavigationMi
this._gmapAPILoading = false;
return;
}
await loadJS(`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&callback=odoo_gmap_api_post_load&key=${key}`);
await loadJS(`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&callback=odoo_gmap_api_post_load&key=${encodeURIComponent(key)}`);
});
}
return this._gmapAPILoading;
@@ -173,7 +173,7 @@ export const WebsiteRoot = publicRootData.PublicRoot.extend(KeyboardNavigationMi
var $target = $(ev.currentTarget);
// retrieve the hash before the redirect
var redirect = {
lang: $target.data('url_code'),
lang: encodeURIComponent($target.data('url_code')),
url: encodeURIComponent($target.attr('href').replace(/[&?]edit_translations[^&?]+/, '')),
hash: encodeURIComponent(window.location.hash)
};
@@ -169,7 +169,7 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
*/
async _validateGMapAPIKey(key) {
try {
const response = await fetch(`https://maps.googleapis.com/maps/api/staticmap?center=belgium&size=10x10&key=${key}`);
const response = await fetch(`https://maps.googleapis.com/maps/api/staticmap?center=belgium&size=10x10&key=${encodeURIComponent(key)}`);
const isValid = (response.status === 200);
return {
isValid: isValid,
@@ -128,7 +128,7 @@ const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
}
for (const font of this.googleLocalFonts) {
const attachmentId = font.split(/\s*:\s*/)[1];
const fontURL = `/web/content/${attachmentId}`;
const fontURL = `/web/content/${encodeURIComponent(attachmentId)}`;
fontsToLoad.push(fontURL);
}
// TODO ideally, remove the <link> elements created once this widget
@@ -233,7 +233,9 @@ const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
let isValidFamily = false;
try {
const result = await fetch("https://fonts.googleapis.com/css?family=" + m[1]+':300,300i,400,400i,700,700i', {method: 'HEAD'});
// Font family is an encoded query parameter:
// "Open+Sans" needs to remain "Open+Sans".
const result = await fetch("https://fonts.googleapis.com/css?family=" + m[1] + ':300,300i,400,400i,700,700i', {method: 'HEAD'});
// Google fonts server returns a 400 status code if family is not valid.
if (result.ok) {
isValidFamily = true;
@@ -1627,7 +1629,7 @@ options.registry.company_data = options.Class.extend({
args: [session.uid, ['company_id']],
});
}).then(function (res) {
proto.__link = '/web#action=base.action_res_company_form&view_type=form&id=' + (res && res[0] && res[0].company_id[0] || 1);
proto.__link = '/web#action=base.action_res_company_form&view_type=form&id=' + encodeURIComponent(res && res[0] && res[0].company_id[0] || 1);
});
}
return Promise.all([this._super.apply(this, arguments), prom]);
@@ -193,7 +193,7 @@ export const websiteService = {
this.websiteRootInstance = undefined;
if (lang) {
invalidateSnippetCache = true;
path = `/website/lang/${lang}?r=${encodeURIComponent(path)}`;
path = `/website/lang/${encodeURIComponent(lang)}?r=${encodeURIComponent(path)}`;
}
action.doAction('website.website_preview', {
clearBreadcrumbs: true,
@@ -128,7 +128,7 @@ options.registry.SocialMedia = options.Class.extend({
const faIcon = isDbField ? `fa-${entry.media}` : 'fa-pencil';
anchorEl.querySelector('i').classList.add(faIcon);
if (isDbField) {
anchorEl.href = `/website/social/${entry.media}`;
anchorEl.href = `/website/social/${encodeURIComponent(entry.media)}`;
anchorEl.classList.add(`s_social_media_${entry.media}`);
}
}
@@ -199,7 +199,7 @@ options.registry.SocialMedia = options.Class.extend({
return {
id: generateHTMLId(),
display_name: media ? dbSocialValues[`social_${media}`] : el.getAttribute('href'),
placeholder: `https://${media || 'example'}.com/yourPage`,
placeholder: `https://${encodeURIComponent(media) || 'example'}.com/yourPage`,
undeletable: !!media,
notToggleable: !media,
selected: true,
@@ -211,13 +211,13 @@ options.registry.SocialMedia = options.Class.extend({
// Adds the DB social media links that are not in the DOM.
for (let [media, link] of Object.entries(dbSocialValues)) {
media = media.split('social_').pop();
if (!this.$target[0].querySelector(`:scope > a[href="/website/social/${media}"]`)) {
if (!this.$target[0].querySelector(`:scope > a[href="/website/social/${encodeURIComponent(media)}"]`)) {
const entryNotInDom = this.entriesNotInDom.find(entry => entry.media === media);
if (!entryNotInDom) {
this.entriesNotInDom.push({
id: generateHTMLId(),
display_name: link,
placeholder: `https://${media}.com/yourPage`,
placeholder: `https://${encodeURIComponent(media)}.com/yourPage`,
undeletable: true,
selected: false,
listPosition: listPosition++,
@@ -770,7 +770,7 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
* @param {string} action
*/
_redirectToAction: function (action) {
window.location.replace(`/web#action=${action}`);
window.location.replace(`/web#action=${encodeURIComponent(action)}`);
},
//--------------------------------------------------------------------------
@@ -21,7 +21,7 @@ export class WebsiteSwitcherSystray extends Component {
if (website.domain && !wUtils.isHTTPSorNakedDomainRedirection(website.domain, window.location.origin)) {
const { location: { pathname, search, hash } } = this.websiteService.contentWindow;
const path = pathname + search + hash;
window.location.href = `${website.domain}/web#action=website.website_preview&path=${encodeURI(path)}&website_id=${website.id}`;
window.location.href = `${encodeURI(website.domain)}/web#action=website.website_preview&path=${encodeURIComponent(path)}&website_id=${encodeURIComponent(website.id)}`;
} else {
this.websiteService.goToWebsite({ websiteId: website.id });
}
@@ -70,16 +70,16 @@ publicWidget.registry.websiteBlog = publicWidget.Widget.extend({
ev.preventDefault();
var url = '';
var $element = $(ev.currentTarget);
var blogPostTitle = encodeURIComponent($('#o_wblog_post_name').html() || '');
var articleURL = encodeURIComponent(window.location.href);
var blogPostTitle = $('#o_wblog_post_name').html() || '';
var articleURL = window.location.href;
if ($element.hasClass('o_twitter')) {
var twitterText = core._t("Amazing blog article: %s! Check it live: %s");
var tweetText = _.string.sprintf(twitterText, blogPostTitle, articleURL);
url = 'https://twitter.com/intent/tweet?tw_p=tweetbutton&text=' + tweetText;
url = 'https://twitter.com/intent/tweet?tw_p=tweetbutton&text=' + encodeURIComponent(tweetText);
} else if ($element.hasClass('o_facebook')) {
url = 'https://www.facebook.com/sharer/sharer.php?u=' + articleURL;
url = 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(articleURL);
} else if ($element.hasClass('o_linkedin')) {
url = 'https://www.linkedin.com/sharing/share-offsite/?url=' + articleURL;
url = 'https://www.linkedin.com/sharing/share-offsite/?url=' + encodeURIComponent(articleURL);
}
window.open(url, '', 'menubar=no, width=500, height=400');
},
@@ -204,7 +204,7 @@ publicWidget.registry.boothRegistration = publicWidget.Widget.extend({
if (this._isConfirmationFormValid($form)) {
const formData = new FormData($form[0]);
const response = await $.ajax({
url: `/event/${this.$el.data('eventId')}/booth/confirm`,
url: `/event/${encodeURIComponent(this.$el.data('eventId'))}/booth/confirm`,
data: formData,
processData: false,
contentType: false,
@@ -47,7 +47,7 @@ var ExhibitorConnectClosedDialog = Dialog.extend({
*/
async _fetchSponsor() {
const sponsorData = await this._rpc({
route: `/event_sponsor/${this.sponsorId}/read`
route: `/event_sponsor/${encodeURIComponent(this.sponsorId)}/read`
});
sponsorData.website_description = Markup(sponsorData.website_description);
this.sponsorData = sponsorData;
@@ -171,7 +171,7 @@ publicWidget.registry.websiteEventTrackProposalForm = publicWidget.Widget.extend
const formData = new FormData(this.$el[0]);
const response = await $.ajax({
url: `/event/${this.$el.data('eventId')}/track_proposal/post`,
url: `/event/${encodeURIComponent(this.$el.data('eventId'))}/track_proposal/post`,
data: formData,
processData: false,
contentType: false,
@@ -8,7 +8,7 @@ export class AddForumFormController extends NewContentFormController {
* @override
*/
computePath() {
return `/forum/${this.model.root.data.id}`;
return `/forum/${encodeURIComponent(this.model.root.data.id)}`;
}
}
@@ -47,7 +47,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
// welcome message action button
var forumLogin = _.string.sprintf('%s/web?redirect=%s',
window.location.origin,
escape(window.location.href)
encodeURIComponent(window.location.href)
);
$('.forum_register_url').attr('href', forumLogin);
@@ -252,7 +252,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({
const linkLabel = _t("Read the guidelines to know how to gain karma.");
notifOptions.message = Markup`
${notifOptions.message}<br/>
<a class="alert-link" href="/forum/${forumID}/faq">${linkLabel}</a>
<a class="alert-link" href="/forum/${encodeURIComponent(forumID)}/faq">${linkLabel}</a>
`;
}
}
@@ -230,7 +230,7 @@ publicWidget.registry.ChatRoom = publicWidget.Widget.extend({
_openMobileApplication: async function (roomName) {
if (config.device.isMobile) {
// we are on mobile, open the room in the application
window.location = `intent://${this.jitsiServer}/${roomName}#Intent;scheme=org.jitsi.meet;package=org.jitsi.meet;end`;
window.location = `intent://${this.jitsiServer}/${encodeURIComponent(roomName)}#Intent;scheme=org.jitsi.meet;package=org.jitsi.meet;end`;
return true;
}
return false;
@@ -7,7 +7,7 @@ patch(Persona.prototype, "website_livechat", {
get countryFlagUrl() {
const country = this.partner?.country ?? this.country;
return country
? `/base/static/img/country_flags/${country.code.toLowerCase()}.png`
? `/base/static/img/country_flags/${encodeURIComponent(country.code.toLowerCase())}.png`
: undefined;
},
get nameOrDisplayName() {
@@ -21,7 +21,7 @@ patch(ThreadService.prototype, "website_livechat", {
avatarUrl(persona, thread) {
if (persona?.type === "visitor" && thread?.id) {
return persona.partner
? `/mail/channel/${thread.id}/partner/${persona.id}/avatar_128`
? `/mail/channel/${encodeURIComponent(thread.id)}/partner/${encodeURIComponent(persona.id)}/avatar_128`
: DEFAULT_AVATAR;
}
return this._super(persona, thread);
@@ -67,8 +67,8 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
$(document.body).on('click.product_comparaison_widget', '.o_comparelist_remove', function (ev) {
self._removeFromComparelist(ev);
self.guard.exec(function() {
var new_link = '/shop/compare?products=' + self.comparelist_product_ids.toString();
window.location.href = _.isEmpty(self.comparelist_product_ids) ? '/shop' : new_link;
const newLink = '/shop/compare?products=' + encodeURIComponent(self.comparelist_product_ids);
window.location.href = _.isEmpty(self.comparelist_product_ids) ? '/shop' : newLink;
});
});
@@ -240,7 +240,8 @@ var ProductComparison = publicWidget.Widget.extend(VariantMixin, {
this.$('.o_comparelist_products').addClass('d-md-block');
if (this.comparelist_product_ids.length >=2) {
this.$('.o_comparelist_button').addClass('d-md-block');
this.$('.o_comparelist_button a').attr('href', '/shop/compare?products='+this.comparelist_product_ids.toString());
this.$('.o_comparelist_button a').attr('href',
'/shop/compare?products=' + encodeURIComponent(this.comparelist_product_ids));
}
}
},
@@ -609,7 +609,7 @@
} else if (slideData.category === 'video' && slideData.videoSourceType === 'vimeo') {
slideData.embedCode = Markup(slideData.embedCode);
} else if (slideData.category === 'infographic') {
slideData.embedUrl = `/web/image/slide.slide/${slideData.id}/image_1024`;
slideData.embedUrl = `/web/image/slide.slide/${encodeURIComponent(slideData.id)}/image_1024`;
} else if (slideData.category === 'document') {
slideData.embedUrl = $(slideData.embedCode).attr('src');
}
@@ -74,7 +74,7 @@ var CourseJoinWidget = publicWidget.Widget.extend({
url += '?fullscreen=1';
}
} else {
url = `/slides/${this.channel.channelId}`;
url = `/slides/${encodeURIComponent(this.channel.channelId)}`;
}
document.location = sprintf('/web/login?redirect=%s', encodeURIComponent(url));
},
@@ -119,7 +119,7 @@ var CourseJoinWidget = publicWidget.Widget.extend({
const message = data.error_signup_allowed ?
_t('Please <a href="/web/login?redirect=%s">login</a> or <a href="/web/signup?redirect=%s">create an account</a> to join this course') :
_t('Please <a href="/web/login?redirect=%s">login</a> to join this course');
self._popoverAlert(self.$el, sprintf(message, document.URL, document.URL));
self._popoverAlert(self.$el, sprintf(message, encodeURIComponent(document.URL), encodeURIComponent(document.URL)));
} else if (data.error === 'join_done') {
self._popoverAlert(self.$el, _t('You have already joined this channel'));
} else {
@@ -791,7 +791,7 @@
.text(_t('Mark To Do'))
.removeAttr('title')
.removeAttr('aria-disabled')
.attr('href', `/slides/slide/${slide.id}/set_uncompleted`);
.attr('href', `/slides/slide/${encodeURIComponent(slide.id)}/set_uncompleted`);
}
},
@@ -70,7 +70,7 @@ var SlideLikeWidget = publicWidget.Widget.extend({
const message = data.error_signup_allowed ?
_t('Please <a href="/web/login?redirect=%s">login</a> or <a href="/web/signup?redirect=%s">create an account</a> to vote for this lesson') :
_t('Please <a href="/web/login?redirect=%s">login</a> to vote for this lesson');
self._popoverAlert(self.$el, sprintf(message, document.URL, document.URL));
self._popoverAlert(self.$el, sprintf(message, encodeURIComponent(document.URL), encodeURIComponent(document.URL)));
} else if (data.error === 'slide_access') {
self._popoverAlert(self.$el, _t('You don\'t have access to this lesson'));
} else if (data.error === 'channel_membership_required') {
@@ -62,7 +62,7 @@ publicWidget.registry.twitter = publicWidget.Widget.extend({
.replace(
/[#]+[A-Za-z0-9_]+/g,
function (hashtag) {
return _makeLink('http://twitter.com/search?q='+hashtag.replace('#',''), hashtag);
return _makeLink('http://twitter.com/search?q=' + encodeURIComponent(hashtag.replace('#', '')), hashtag);
}
));