templates: Fix missing quoting of attributes in HTML templates.

This fixes a bundle of issues where we were missing "" around
attributes coming from variables.  In most cases, the variables were
integers or fixed constants from the Zulip codebase (E.g. the name of
an installed integration), but in at least one case it was
user-provided data that could potentially have security impact.
This commit is contained in:
Tim Abbott
2020-05-12 22:26:30 -07:00
parent 87f7874a79
commit 4fff858aa2
11 changed files with 20 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
<div class="emoji-popover"> <div class="emoji-popover">
<div class="emoji-popover-top input-append"> <div class="emoji-popover-top input-append">
<input class="emoji-popover-filter" type="text" autofocus placeholder={{t 'Search' }} /> <input class="emoji-popover-filter" type="text" autofocus placeholder="{{t 'Search' }}" />
<i class="fa fa-search" aria-hidden="true"></i> <i class="fa fa-search" aria-hidden="true"></i>
</div> </div>
<div class="emoji-popover-category-tabs"> <div class="emoji-popover-category-tabs">

View File

@@ -1,5 +1,5 @@
{{#with emoji_dict}} {{#with emoji_dict}}
<div class="emoji-popover-emoji {{#if ../message_id }}{{#if has_reacted}} reacted {{/if}} reaction {{else}} composition {{/if}}" data-emoji-name={{name}} tabindex="0" data-emoji-id="{{../type}},{{../section}},{{../index}}"> <div class="emoji-popover-emoji {{#if ../message_id }}{{#if has_reacted}} reacted {{/if}} reaction {{else}} composition {{/if}}" data-emoji-name="{{name}}" tabindex="0" data-emoji-id="{{../type}},{{../section}},{{../index}}">
{{#if is_realm_emoji}} {{#if is_realm_emoji}}
<img src="{{url}}" class="emoji"/> <img src="{{url}}" class="emoji"/>
{{else}} {{else}}

View File

@@ -1,6 +1,6 @@
<div class='pill' data-id='{{ id }}' tabindex=0> <div class='pill' data-id='{{ id }}' tabindex=0>
{{#if has_image}} {{#if has_image}}
<img class="pill-image" src={{img_src}} /> <img class="pill-image" src="{{img_src}}" />
{{/if}} {{/if}}
<span class="pill-value">{{ display_value }}</span> <span class="pill-value">{{ display_value }}</span>
<div class="exit"> <div class="exit">

View File

@@ -1,4 +1,4 @@
<div class="{{this.class}}" aria-label="{{this.label}}" data-reaction-id={{this.local_id}}> <div class="{{this.class}}" aria-label="{{this.label}}" data-reaction-id="{{this.local_id}}">
{{#if this.emoji_alt_code}} {{#if this.emoji_alt_code}}
<div class="emoji_alt_code">&nbsp:{{this.emoji_name}}:</div> <div class="emoji_alt_code">&nbsp:{{this.emoji_name}}:</div>
{{else}} {{else}}

View File

@@ -1,5 +1,5 @@
{{#with muted_topics}} {{#with muted_topics}}
<tr data-stream-id={{stream_id}} data-stream="{{stream}}" data-topic="{{topic}}" data-date-muted="{{date_muted_str}}"> <tr data-stream-id="{{stream_id}}" data-stream="{{stream}}" data-topic="{{topic}}" data-date-muted="{{date_muted_str}}">
<td>{{stream}}</td> <td>{{stream}}</td>
<td>{{topic}}</td> <td>{{topic}}</td>
<td>{{date_muted_str}}</td> <td>{{date_muted_str}}</td>

View File

@@ -76,7 +76,7 @@
{{/each}} {{/each}}
</select> </select>
<span id="play_notification_sound"> <span id="play_notification_sound">
<i class="fa fa-play-circle" aria-label={{t "Play sound" }}></i> <i class="fa fa-play-circle" aria-label="{{t 'Play sound' }}"></i>
</span> </span>
</div> </div>

View File

@@ -50,9 +50,9 @@
<div class="input" contenteditable="false" style="display: none;"></div> <div class="input" contenteditable="false" style="display: none;"></div>
</div> </div>
{{else if this.is_link}} {{else if this.is_link}}
<a href={{this.value}} target="_blank" class="value">{{this.value}}</a> <a href="{{this.value}}" target="_blank" class="value">{{this.value}}</a>
{{else if this.is_external_account}} {{else if this.is_external_account}}
<a href={{this.link}} target="_blank" class="value">{{this.value}}</a> <a href="{{this.link}}" target="_blank" class="value">{{this.value}}</a>
{{else}} {{else}}
{{#if this.rendered_value}} {{#if this.rendered_value}}
<div class="value rendered_markdown">{{rendered_markdown this.rendered_value}}</div> <div class="value rendered_markdown">{{rendered_markdown this.rendered_value}}</div>

View File

@@ -5,7 +5,7 @@
<span class="message_sender{% if status_message %} sender_info_hover{% endif %} no-select"> <span class="message_sender{% if status_message %} sender_info_hover{% endif %} no-select">
{% if include_sender %} {% if include_sender %}
<div class="inline_profile_picture"> <div class="inline_profile_picture">
<img src={{ avatar_url }} alt="" class="no-drag"/> <img src="{{ avatar_url }}" alt="" class="no-drag"/>
</div> </div>
{% if status_message %} {% if status_message %}
<span class="sender-status"> <span class="sender-status">

View File

@@ -144,7 +144,7 @@
{% for integration in integrations_dict.values() %} {% for integration in integrations_dict.values() %}
{% if integration.is_enabled() %} {% if integration.is_enabled() %}
<div id={{ integration.name }} class="integration-instructions"> <div id="{{ integration.name }}" class="integration-instructions">
<div class="help-content"></div> <div class="help-content"></div>
<p style="font-size:11px; font-style:italic;"> <p style="font-size:11px; font-style:italic;">
Logos are trademarks of their respective owners. Logos are trademarks of their respective owners.

View File

@@ -28,7 +28,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<input id="id_team_name" class="required" type="text" <input id="id_team_name" class="required" type="text"
placeholder="Acme or Ακμή" placeholder="Acme or Ακμή"
value="{% if form.realm_name.value() %}{{ form.realm_name.value() }}{% endif %}" value="{% if form.realm_name.value() %}{{ form.realm_name.value() }}{% endif %}"
name="realm_name" maxlength={{ MAX_REALM_NAME_LENGTH }} required /> name="realm_name" maxlength="{{ MAX_REALM_NAME_LENGTH }}" required />
</div> </div>
<label for="id_team_name" class="inline-block label-title">{{ _('Organization name') }}</label> <label for="id_team_name" class="inline-block label-title">{{ _('Organization name') }}</label>
{% if form.realm_name.errors %} {% if form.realm_name.errors %}
@@ -63,7 +63,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
class="{% if root_domain_landing_page %}required{% endif %} subdomain" type="text" class="{% if root_domain_landing_page %}required{% endif %} subdomain" type="text"
placeholder="acme" placeholder="acme"
value="{% if form.realm_subdomain.value() %}{{ form.realm_subdomain.value() }}{% endif %}" value="{% if form.realm_subdomain.value() %}{{ form.realm_subdomain.value() }}{% endif %}"
name="realm_subdomain" maxlength={{ MAX_REALM_SUBDOMAIN_LENGTH }} name="realm_subdomain" maxlength="{{ MAX_REALM_SUBDOMAIN_LENGTH }}"
{% if root_domain_landing_page %}required{% endif %} /> {% if root_domain_landing_page %}required{% endif %} />
<label for="id_team_subdomain" class="realm_subdomain_label">.{{ external_host }}</label> <label for="id_team_subdomain" class="realm_subdomain_label">.{{ external_host }}</label>
<p id="id_team_subdomain_error_client" class="error help-inline text-error"></p> <p id="id_team_subdomain_error_client" class="error help-inline text-error"></p>
@@ -124,7 +124,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
{% else %} {% else %}
<input id="id_full_name" class="required" type="text" name="full_name" <input id="id_full_name" class="required" type="text" name="full_name"
value="{% if full_name %}{{ full_name }}{% elif form.full_name.value() %}{{ form.full_name.value() }}{% endif %}" value="{% if full_name %}{{ full_name }}{% elif form.full_name.value() %}{{ form.full_name.value() }}{% endif %}"
maxlength={{ MAX_NAME_LENGTH }} placeholder="{% trans %}Full name or 名前{% endtrans %}" required /> maxlength="{{ MAX_NAME_LENGTH }}" placeholder="{% trans %}Full name or 名前{% endtrans %}" required />
<label for="id_full_name" class="inline-block label-title">{{ _('Full name') }}</label> <label for="id_full_name" class="inline-block label-title">{{ _('Full name') }}</label>
{% if form.full_name.errors %} {% if form.full_name.errors %}
{% for error in form.full_name.errors %} {% for error in form.full_name.errors %}
@@ -151,7 +151,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
<div class="input-box"> <div class="input-box">
<input id="id_password" class="required" type="password" name="password" <input id="id_password" class="required" type="password" name="password"
value="{% if form.password.value() %}{{ form.password.value() }}{% endif %}" value="{% if form.password.value() %}{{ form.password.value() }}{% endif %}"
maxlength={{ MAX_PASSWORD_LENGTH }} maxlength="{{ MAX_PASSWORD_LENGTH }}"
data-min-length="{{password_min_length}}" data-min-length="{{password_min_length}}"
data-min-guesses="{{password_min_guesses}}" required /> data-min-guesses="{{password_min_guesses}}" required />
<label for="id_password" class="inline-block">{{ _('Password') }}</label> <label for="id_password" class="inline-block">{{ _('Password') }}</label>

View File

@@ -498,6 +498,12 @@ html_rules: List["Rule"] = whitespace_rules + prose_style_rules + [
'exclude': {"templates/analytics/support.html"}, 'exclude': {"templates/analytics/support.html"},
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'], 'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],
'bad_lines': ['<input placeholder="foo">']}, 'bad_lines': ['<input placeholder="foo">']},
{'pattern': '={',
# TODO: Improve the Apple auth patterns so we can remove this.
'exclude_pattern': 'appleid.cdn-apple.com/appleid/button',
'description': "Likely missing quoting in HTML attribute",
'good_lines': ['<a href="{{variable}}">'],
'bad_lines': ['<a href={{variable}}>']},
{'pattern': "placeholder='[^{]", {'pattern': "placeholder='[^{]",
'description': "`placeholder` value should be translatable.", 'description': "`placeholder` value should be translatable.",
'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'], 'good_lines': ['<input class="stream-list-filter" type="text" placeholder="{{ _(\'Search streams\') }}" />'],