FIX: prevents i18n helper to return a SafeString (#9191)

For convenience the i18n helper has been made returning a SafeString, but when used with other helpers, a String is expected and will cause unexpected behaviors.

This is the root cause of the initial bug fixed in d2bb127e2c

This commit is kept as it's a better security in case of unexpected behavior.
This commit is contained in:
Joffrey JAFFEUX 2020-03-12 16:50:20 +01:00 committed by GitHub
parent 59578dfc5b
commit 6102c287f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 58 additions and 57 deletions

View File

@ -15,7 +15,7 @@
{{#if backupStats.last_backup_taken_at}}
<br>
{{i18n "admin.dashboard.lastest_backup" date=(format-date backupStats.last_backup_taken_at leaveAgo="true")}}
{{html-safe (i18n "admin.dashboard.lastest_backup" date=(format-date backupStats.last_backup_taken_at leaveAgo="true"))}}
{{/if}}
</p>
</div>

View File

@ -1,7 +1,7 @@
<div class='penalty-post-controls'>
<label>
<div class='penalty-post-label'>
{{i18n 'admin.user.penalty_post_actions'}}
{{html-safe (i18n 'admin.user.penalty_post_actions')}}
</div>
</label>
{{combo-box

View File

@ -1,7 +1,7 @@
<div class='reason-controls'>
<label>
<div class='silence-reason-label'>
{{i18n 'admin.user.silence_reason_label'}}
{{html-safe (i18n 'admin.user.silence_reason_label')}}
</div>
</label>
{{text-field

View File

@ -2,9 +2,9 @@
<label>
<div class='suspend-reason-label'>
{{#if siteSettings.hide_suspension_reasons}}
{{i18n 'admin.user.suspend_reason_hidden_label'}}
{{html-safe (i18n 'admin.user.suspend_reason_hidden_label')}}
{{else}}
{{i18n 'admin.user.suspend_reason_label'}}
{{html-safe (i18n 'admin.user.suspend_reason_label')}}
{{/if}}
</div>
</label>

View File

@ -142,7 +142,7 @@
filters=trendingSearchFilters
isEnabled=logSearchQueriesEnabled
disabledLabel=trendingSearchDisabledLabel}}
{{i18n "admin.dashboard.reports.trending_search.more" basePath=basePath}}
{{html-safe (i18n "admin.dashboard.reports.trending_search.more" basePath=basePath)}}
</div>
</div>

View File

@ -23,7 +23,7 @@
{{#if showSecondary}}
<div class='embedding-secondary'>
<p>{{i18n "admin.embedding.sample"}}</p>
<p>{{html-safe (i18n "admin.embedding.sample")}}</p>
{{highlighted-code code=embeddingCode lang="html"}}
</div>

View File

@ -17,9 +17,9 @@
{{else}}
<p class="grant-count">
{{#if count}}
{{i18n "admin.badges.preview.grant_count" count=count}}
{{html-safe (i18n "admin.badges.preview.grant_count" count=count)}}
{{else}}
{{i18n "admin.badges.preview.no_grant_count"}}
{{html-safe (i18n "admin.badges.preview.no_grant_count")}}
{{/if}}
</p>

View File

@ -132,7 +132,7 @@
<div class="field">{{i18n "user.avatar.title"}}</div>
<div class="value">{{avatar model imageSize="large"}}</div>
<div class="controls">
{{i18n "admin.user.visit_profile" url=preferencesPath}}
{{html-safe (i18n "admin.user.visit_profile" url=preferencesPath)}}
</div>
</div>

View File

@ -1,7 +1,6 @@
import { registerUnbound } from "discourse-common/lib/helpers";
import { htmlSafe } from "@ember/template";
registerUnbound("i18n", (key, params) => htmlSafe(I18n.t(key, params)));
registerUnbound("i18n", (key, params) => I18n.t(key, params));
registerUnbound("i18n-yes-no", (value, params) =>
I18n.t(value ? "yes_value" : "no_value", params)
);

View File

@ -1,7 +1,7 @@
import { registerUnbound } from "discourse-common/lib/helpers";
import { emojiUnescape } from "discourse/lib/text";
import { htmlSafe } from "@ember/template";
registerUnbound(
"replace-emoji",
text => new Handlebars.SafeString(emojiUnescape(text))
);
registerUnbound("replace-emoji", text => {
return htmlSafe(emojiUnescape(text));
});

View File

@ -1,6 +1,6 @@
<div class='ac-message'>
{{#if email}}
{{i18n 'login.sent_activation_email_again' currentEmail=email}}
{{html-safe (i18n 'login.sent_activation_email_again' currentEmail=email)}}
{{else}}
{{i18n 'login.sent_activation_email_again_generic'}}
{{/if}}

View File

@ -5,7 +5,7 @@
<div id="banner-content">
{{html-safe content}}
{{#if currentUser.staff}}
<p><a href={{banner.url}}>{{i18n "banner.edit"}}</a></p>
<p><a href={{banner.url}}>{{html-safe (i18n "banner.edit")}}</a></p>
{{/if}}
</div>
</div>

View File

@ -1,7 +1,7 @@
{{#if category.isUncategorizedCategory}}
<p class="warning">
{{d-icon "exclamation-triangle"}}
{{i18n 'category.uncategorized_general_warning' settingLink=uncategorizedSiteSettingLink customizeLink=customizeTextContentLink}}
{{html-safe (i18n 'category.uncategorized_general_warning' settingLink=uncategorizedSiteSettingLink customizeLink=customizeTextContentLink)}}
</p>
{{/if}}

View File

@ -11,7 +11,7 @@
{{#each category.permissions as |p|}}
<li>
<span class="name"><span class="badge-group">{{p.group_name}}</span></span>
{{i18n "category.can"}}
{{html-safe (i18n "category.can")}}
<span class="permission">{{p.permission.description}}</span>
{{#if editingPermissions}}
<a class="remove-permission" href {{action "removePermission" p}}>{{d-icon "times-circle"}}</a>

View File

@ -14,7 +14,7 @@
{{d-button action=(action "copy") class="pull-right no-text" icon="copy"}}
{{/if}}
<h4>{{i18n "ip_lookup.title"}}</h4>
<p class='powered-by'>{{i18n "ip_lookup.powered_by"}}</p>
<p class='powered-by'>{{html-safe (i18n "ip_lookup.powered_by")}}</p>
<dl>
{{#if location}}
{{#if location.hostname}}

View File

@ -10,5 +10,5 @@
</div>
{{#if targetUser}}
<h3 class="see-all-pms-message">{{i18n "related_messages.see_all" path=searchLink username=targetUser.username}}</h3>
<h3 class="see-all-pms-message">{{html-safe (i18n "related_messages.see_all" path=searchLink username=targetUser.username)}}</h3>
{{/if}}

View File

@ -2,7 +2,7 @@
{{#if publishing}}
{{i18n "shared_drafts.publishing"}}
{{else}}
{{i18n "shared_drafts.notice" category=topic.category.name}}
{{html-safe (i18n "shared_drafts.notice" category=topic.category.name)}}
<div class='publish-field'>
<label>{{i18n "shared_drafts.destination_category"}}</label>

View File

@ -32,7 +32,7 @@
{{#if tagInfo.synonyms}}
<div class="synonyms-list">
<h3>{{i18n "tagging.synonyms"}}</h3>
<div>{{i18n "tagging.synonyms_description" base_tag_name=tagInfo.name}}</div>
<div>{{html-safe (i18n "tagging.synonyms_description" base_tag_name=tagInfo.name)}}</div>
<div class="tag-list">
{{#each tagInfo.synonyms as |tag|}}
<div class='tag-box'>

View File

@ -9,5 +9,5 @@
</span>
<span class='label'>
{{#if icon}}{{d-icon icon}}{{/if}}
{{i18n label count=value}}
{{html-safe (i18n label count=value)}}
</span>

View File

@ -1,5 +1,5 @@
{{#d-modal-body}}
{{i18n 'login.sent_activation_email_again' currentEmail=currentEmail}}
{{html-safe (i18n 'login.sent_activation_email_again' currentEmail=currentEmail)}}
{{/d-modal-body}}
{{modal-footer-close closeModal=(route-action "closeModal")}}

View File

@ -1,7 +1,7 @@
{{#d-modal-body title="user.auth_tokens.was_this_you"}}
<div>
<p>{{i18n 'user.auth_tokens.was_this_you_description'}}</p>
<p>{{i18n 'user.second_factor.extended_description'}}</p>
<p>{{html-safe (i18n 'user.second_factor.extended_description')}}</p>
</div>
<div>

View File

@ -10,11 +10,11 @@
{{else}}
<div class="avatar-choice">
{{radio-button id="system-avatar" name="avatar" value="system" selection=selected}}
<label class="radio" for="system-avatar">{{bound-avatar-template user.system_avatar_template "large"}} {{i18n 'user.change_avatar.letter_based'}}</label>
<label class="radio" for="system-avatar">{{bound-avatar-template user.system_avatar_template "large"}} {{html-safe (i18n 'user.change_avatar.letter_based')}}</label>
</div>
<div class="avatar-choice">
{{radio-button id="gravatar" name="avatar" value="gravatar" selection=selected}}
<label class="radio" for="gravatar">{{bound-avatar-template user.gravatar_avatar_template "large"}} <span>{{i18n 'user.change_avatar.gravatar' gravatarName=gravatarName gravatarBaseUrl=gravatarBaseUrl gravatarLoginUrl=gravatarLoginUrl }} {{user.email}}</span></label>
<label class="radio" for="gravatar">{{bound-avatar-template user.gravatar_avatar_template "large"}} <span>{{html-safe (i18n 'user.change_avatar.gravatar' gravatarName=gravatarName gravatarBaseUrl=gravatarBaseUrl gravatarLoginUrl=gravatarLoginUrl)}} {{user.email}}</span></label>
{{d-button action=(action "refreshGravatar")
translatedTitle=(i18n "user.change_avatar.refresh_gravatar_title" gravatarName=gravatarName)

View File

@ -50,7 +50,7 @@
{{/if}}
{{else}}
<div class="alert alert-info">{{i18n "bookmarks.no_timezone" basePath=basePath }}</div>
<div class="alert alert-info">{{html-safe (i18n "bookmarks.no_timezone" basePath=basePath)}}</div>
{{/if}}
</div>
{{/if}}

View File

@ -1,8 +1,10 @@
{{#d-modal-body class='change-ownership'}}
<span>
{{i18n (if selectedPostsUsername 'topic.change_owner.instructions' 'topic.change_owner.instructions_without_old_user')
count=selectedPostsCount
old_user=selectedPostsUsername}}
{{html-safe (i18n (if selectedPostsUsername 'topic.change_owner.instructions' 'topic.change_owner.instructions_without_old_user')
count=selectedPostsCount
old_user=selectedPostsUsername
)
}}
</span>
<form>

View File

@ -1,5 +1,5 @@
{{#d-modal-body}}
<p>{{i18n "post.controls.delete_topic_disallowed_modal"}}</p>
<p>{{html-safe (i18n "post.controls.delete_topic_disallowed_modal")}}</p>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button action=(route-action "closeModal") class="btn-primary" label="close"}}

View File

@ -6,9 +6,9 @@
<p>
{{#conditional-loading-spinner size="small" condition=loading}}
{{#if pinnedGloballyCount}}
{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}
{{html-safe (i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount)}}
{{else}}
{{i18n "topic.feature_topic.not_pinned_globally"}}
{{html-safe (i18n "topic.feature_topic.not_pinned_globally")}}
{{/if}}
{{/conditional-loading-spinner}}
</p>
@ -74,9 +74,9 @@
<p>
{{#conditional-loading-spinner size="small" condition=loading}}
{{#if pinnedGloballyCount}}
{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}
{{html-safe (i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount)}}
{{else}}
{{i18n "topic.feature_topic.not_pinned_globally"}}
{{html-safe (i18n "topic.feature_topic.not_pinned_globally")}}
{{/if}}
{{/conditional-loading-spinner}}
</p>
@ -123,9 +123,9 @@
<p>
{{#conditional-loading-spinner size="small" condition=loading}}
{{#if bannerCount}}
{{i18n "topic.feature_topic.banner_exists"}}
{{html-safe (i18n "topic.feature_topic.banner_exists")}}
{{else}}
{{i18n "topic.feature_topic.no_banner_exists"}}
{{html-safe (i18n "topic.feature_topic.no_banner_exists")}}
{{/if}}
{{/conditional-loading-spinner}}
</p>

View File

@ -17,7 +17,7 @@
{{#if canSplitTopic}}
{{#if newMessage}}
<p>{{i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount}}</p>
<p>{{html-safe (i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount)}}</p>
<form>
<label>{{i18n 'topic.move_to_new_message.message_title'}}</label>
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
@ -31,7 +31,7 @@
{{/if}}
{{#if existingMessage}}
<p>{{i18n 'topic.move_to_existing_message.instructions' count=selectedPostsCount}}</p>
<p>{{html-safe (i18n 'topic.move_to_existing_message.instructions' count=selectedPostsCount)}}</p>
<form>
{{choose-message currentTopicId=model.id selectedTopicId=selectedTopicId}}
@ -64,7 +64,7 @@
</div>
{{#if existingTopic}}
<p>{{i18n 'topic.merge_topic.instructions' count=selectedPostsCount}}</p>
<p>{{html-safe (i18n 'topic.merge_topic.instructions' count=selectedPostsCount)}}</p>
<form>
{{choose-topic currentTopicId=model.id selectedTopicId=selectedTopicId}}
</form>
@ -72,7 +72,7 @@
{{#if canSplitTopic}}
{{#if newTopic}}
<p>{{i18n 'topic.split_topic.instructions' count=selectedPostsCount}}</p>
<p>{{html-safe (i18n 'topic.split_topic.instructions' count=selectedPostsCount)}}</p>
<form>
<label>{{i18n 'topic.split_topic.topic_name'}}</label>
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
@ -93,7 +93,7 @@
{{#if canSplitTopic}}
{{#if newMessage}}
<p>{{i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount}}</p>
<p>{{html-safe (i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount)}}</p>
<form>
<label>{{i18n 'topic.move_to_new_message.message_title'}}</label>
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}

View File

@ -1,5 +1,5 @@
{{#d-modal-body}}
{{i18n 'login.not_activated' sentTo=sentTo}}
{{html-safe (i18n 'login.not_activated' sentTo=sentTo)}}
{{/d-modal-body}}
<div class="modal-footer">

View File

@ -1,7 +1,7 @@
{{#d-modal-body}}
<p>{{i18n "review.approval.description"}}</p>
<p>{{i18n "review.approval.pending_posts" count=model.pending_count}}</p>
<p>{{html-safe (i18n "review.approval.pending_posts" count=model.pending_count)}}</p>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button action=(route-action "closeModal") class="btn-primary" label="review.approval.ok"}}

View File

@ -10,7 +10,7 @@
<div class="control-group">
<div class="controls">
{{i18n 'user.second_factor.enable_security_key_description'}}
{{html-safe (i18n 'user.second_factor.enable_security_key_description')}}
</div>
</div>

View File

@ -10,7 +10,7 @@
<div class="control-group">
<div class="controls">
{{i18n 'user.second_factor.enable_description'}}
{{html-safe (i18n 'user.second_factor.enable_description')}}
</div>
</div>

View File

@ -14,7 +14,7 @@
{{/if}}
{{#if backupEnabled}}
{{i18n "user.second_factor_backup.remaining_codes" count=remainingCodes}}
{{html-safe (i18n "user.second_factor_backup.remaining_codes" count=remainingCodes)}}
{{/if}}
<div class="actions">

View File

@ -1,4 +1,4 @@
{{#d-modal-body}}
<p>{{i18n 'topics.bulk.selected' count=model.topics.length}}</p>
<p>{{html-safe (i18n 'topics.bulk.selected' count=model.topics.length)}}</p>
{{outlet "bulkOutlet"}}
{{/d-modal-body}}

View File

@ -86,7 +86,7 @@
<h2>{{i18n "user.second_factor_backup.title"}}</h2>
{{#if model.second_factor_enabled}}
{{#if model.second_factor_backup_enabled}}
{{i18n 'user.second_factor_backup.manage' count=model.second_factor_remaining_backup_codes}}
{{html-safe (i18n 'user.second_factor_backup.manage' count=model.second_factor_remaining_backup_codes)}}
{{else}}
{{i18n 'user.second_factor_backup.enable_long'}}
{{/if}}

View File

@ -9,7 +9,7 @@
</div>
{{#if siteSettings.enable_mentions}}
<div class='instructions'>
{{i18n 'user.username.short_instructions' username=model.username}}
{{html-safe (i18n 'user.username.short_instructions' username=model.username)}}
</div>
{{/if}}
</div>

View File

@ -73,7 +73,7 @@
<div class='control-group pref-mailing-list-mode'>
<label class="control-label">{{i18n 'user.mailing_list_mode.label'}}</label>
{{preference-checkbox labelKey="user.mailing_list_mode.enabled" checked=model.user_option.mailing_list_mode}}
<div class='instructions'>{{i18n 'user.mailing_list_mode.instructions'}}</div>
<div class='instructions'>{{html-safe (i18n 'user.mailing_list_mode.instructions')}}</div>
{{#if model.user_option.mailing_list_mode}}
<div class='controls controls-dropdown'>
{{combo-box

View File

@ -277,7 +277,7 @@
{{#if model.queued_posts_count}}
<div class="has-pending-posts">
<div>
{{i18n "review.topic_has_pending" count=model.queued_posts_count}}
{{html-safe (i18n "review.topic_has_pending" count=model.queued_posts_count)}}
</div>
{{#link-to 'review' (query-params topic_id=model.id type="ReviewableQueuedPost" status="pending")}}

View File

@ -109,7 +109,7 @@
{{else}}
<div class="user-invite-none">
{{#if canBulkInvite}}
{{i18n 'user.invited.bulk_invite.none'}}
{{html-safe (i18n 'user.invited.bulk_invite.none')}}
{{else}}
{{i18n 'user.invited.none'}}
{{/if}}

View File

@ -85,7 +85,7 @@
<label class="control-label">
{{i18n "discourse_local_dates.create.form.recurring_title"}}
</label>
<p>{{i18n "discourse_local_dates.create.form.recurring_description"}}</p>
<p>{{html-safe (i18n "discourse_local_dates.create.form.recurring_description")}}</p>
<div class="controls">
{{combo-box
content=recurringOptions