From ab22c8cad4e8b62cf4e2e4fc30f955df9f31596a Mon Sep 17 00:00:00 2001
From: Eric Berry <cavneb@gmail.com>
Date: Thu, 30 Nov 2017 09:04:41 -0700
Subject: [PATCH] FIX: Infinite loop when poll step is zero (#5380)

* Fix infinite loop when poll step is zero

* Add test for step minimum and for breaking test

* Remove trailing spaces (eslint)

* Remove extra space (eslint)

* Removed test call .twice
---
 .../controllers/poll-ui-builder.js.es6        | 23 +++++++++++++++++--
 .../templates/modal/poll-ui-builder.hbs       |  2 ++
 .../lib/discourse-markdown/poll.js.es6        |  5 ++++
 plugins/poll/config/locales/client.en.yml     |  1 +
 .../controllers/poll-ui-builder-test.js.es6   |  4 ++++
 5 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
index 6458c65028c..3a822f139aa 100644
--- a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
+++ b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
@@ -87,7 +87,10 @@ export default Ember.Controller.extend({
     if (isMultiple) {
       return this._comboboxOptions(pollMinInt + 1, count + 1);
     } else if (isNumber) {
-      const pollStepInt = parseInt(pollStep) || 1;
+      let pollStepInt = parseInt(pollStep, 10);
+      if (pollStepInt < 1) {
+        pollStepInt = 1;
+      }
       return this._comboboxOptions(pollMinInt + 1, pollMinInt + (this.siteSettings.poll_maximum_options * pollStepInt));
     }
   },
@@ -109,10 +112,15 @@ export default Ember.Controller.extend({
       pollHeader += ` name=poll${match.length + 1}`;
     };
 
+    let step = pollStep;
+    if (step < 1) {
+      step = 1;
+    }
+
     if (pollType) pollHeader += ` type=${pollType}`;
     if (pollMin && showMinMax) pollHeader += ` min=${pollMin}`;
     if (pollMax) pollHeader += ` max=${pollMax}`;
-    if (isNumber) pollHeader += ` step=${pollStep}`;
+    if (isNumber) pollHeader += ` step=${step}`;
     if (publicPoll) pollHeader += ' public=true';
     pollHeader += ']';
     output += `${pollHeader}\n`;
@@ -143,6 +151,17 @@ export default Ember.Controller.extend({
     return InputValidation.create(options);
   },
 
+  @computed("pollStep")
+  minStepValueValidation(pollStep) {
+    let options = { ok: true };
+
+    if (pollStep < 1) {
+      options = { failed: true, reason: I18n.t("poll.ui_builder.help.min_step_value") };
+    }
+
+    return InputValidation.create(options);
+  },
+
   @computed("disableInsert")
   minNumOfOptionsValidation(disableInsert) {
     let options = { ok: true };
diff --git a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
index 81a25f4b04b..376fff2a461 100644
--- a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
+++ b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
@@ -32,7 +32,9 @@
           {{input type='number'
                   value=pollStep
                   valueAttribute="value"
+                  min="1"
                   class="poll-options-step"}}
+          {{input-tip validation=minStepValueValidation}}
         </div>
       {{/if}}
     {{/if}}
diff --git a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
index 1a4ee5060ce..442f2a0d242 100644
--- a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
+++ b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
@@ -133,6 +133,11 @@ const rule = {
     let max = parseInt(attrs["max"], 10);
     let step = parseInt(attrs["step"], 10);
 
+    // infinite loop if step < 1
+    if (step < 1) {
+      step = 1;
+    }
+
     let header = [];
 
     let token = new state.Token('poll_open', 'div', 1);
diff --git a/plugins/poll/config/locales/client.en.yml b/plugins/poll/config/locales/client.en.yml
index f2d8cb01d25..86bae641924 100644
--- a/plugins/poll/config/locales/client.en.yml
+++ b/plugins/poll/config/locales/client.en.yml
@@ -75,6 +75,7 @@ en:
         help:
           options_count: Enter at least 2 options
           invalid_values: Minimum value must be smaller than the maximum value.
+          min_step_value: The minimum step value is 1
         poll_type:
           label: Type
           regular: Single Choice
diff --git a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6 b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
index b81d4984181..babab76ae76 100644
--- a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
+++ b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
@@ -196,6 +196,10 @@ test("number pollOutput", function(assert) {
   controller.set("publicPoll", true);
 
   assert.equal(controller.get("pollOutput"), "[poll type=number min=1 max=20 step=2 public=true]\n[/poll]", "it should return the right output");
+
+  controller.set("pollStep", 0);
+
+  assert.equal(controller.get("pollOutput"), "[poll type=number min=1 max=20 step=1 public=true]\n[/poll]", "it should return the right output");
 });
 
 test("regular pollOutput", function(assert) {