Merge remote-tracking branch 'upstream/pull/2044'
authorTom Hughes <tom@compton.nu>
Mon, 5 Nov 2018 21:22:48 +0000 (21:22 +0000)
committerTom Hughes <tom@compton.nu>
Mon, 5 Nov 2018 21:22:48 +0000 (21:22 +0000)
26 files changed:
app/models/changeset_comment.rb
app/models/changeset_tag.rb
app/models/diary_comment.rb
app/models/diary_entry.rb
app/models/issue_comment.rb
app/models/message.rb
app/models/node_tag.rb
app/models/note_comment.rb
app/models/old_node_tag.rb
app/models/old_relation_tag.rb
app/models/old_way_tag.rb
app/models/redaction.rb
app/models/relation_tag.rb
app/models/report.rb
app/models/trace.rb
app/models/tracetag.rb
app/models/user.rb
app/models/user_block.rb
app/models/user_preference.rb
app/models/way_tag.rb
app/validators/characters_validator.rb [new file with mode: 0644]
app/validators/whitespace_validator.rb [new file with mode: 0644]
config/locales/en.yml
test/models/user_test.rb
test/validators/characters_validator_test.rb [new file with mode: 0644]
test/validators/whitespace_validator_test.rb [new file with mode: 0644]

index 756fda14c6e7b85adb198d89ddf002316ce9d740..a0ad6f2ea4a17dc5f3db58d3d01c1bb4d646886b 100644 (file)
@@ -28,7 +28,7 @@ class ChangesetComment < ActiveRecord::Base
   validates :changeset, :presence => true, :associated => true
   validates :author, :presence => true, :associated => true
   validates :visible, :inclusion => [true, false]
-  validates :body, :format => /\A[^\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\ufffe\uffff]*\z/
+  validates :body, :characters => true
 
   # Return the comment text
   def body
index 8d6cd45ac3bb1056aa72f8643b4dfd7c8c37ffe1..942fafb2a34d61646d2e38d9f2fd4044d34c81b5 100644 (file)
@@ -21,6 +21,6 @@ class ChangesetTag < ActiveRecord::Base
   belongs_to :changeset
 
   validates :changeset, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => :changeset_id }
 end
index 63eae3f213ee621f7016459271e46af036661a77..ade7a64ea317fd38d00dc5b5ef6d5318068bc904 100644 (file)
@@ -28,7 +28,7 @@ class DiaryComment < ActiveRecord::Base
 
   scope :visible, -> { where(:visible => true) }
 
-  validates :body, :presence => true
+  validates :body, :presence => true, :characters => true
   validates :diary_entry, :user, :associated => true
 
   after_save :spam_check
index 2a24d800296b80902fffdd33656e67447505e9a5..d6124199352481895af958f7ae5c25b7e8c02c1a 100644 (file)
@@ -37,8 +37,8 @@ class DiaryEntry < ActiveRecord::Base
 
   scope :visible, -> { where(:visible => true) }
 
-  validates :title, :body, :presence => true
-  validates :title, :length => 1..255
+  validates :title, :presence => true, :length => 1..255, :characters => true
+  validates :body, :presence => true, :characters => true
   validates :latitude, :allow_nil => true,
                        :numericality => { :greater_than_or_equal_to => -90,
                                           :less_than_or_equal_to => 90 }
index 3a5894cb3e1b4cab5ee1a91ed077164ed2faf77f..0841295e1760120335d2e16ed79f57b20a9a67c2 100644 (file)
@@ -24,7 +24,7 @@ class IssueComment < ActiveRecord::Base
   belongs_to :issue
   belongs_to :user
 
-  validates :body, :presence => true
+  validates :body, :presence => true, :characters => true
   validates :user, :presence => true
   validates :issue, :presence => true
 end
index aff628e035901e685c18515902087db8e48a088d..4e89cb2e1648dd709f3e99cd715ee8df22b99c44 100644 (file)
@@ -32,6 +32,7 @@ class Message < ActiveRecord::Base
 
   validates :title, :presence => true, :utf8 => true, :length => 1..255
   validates :body, :sent_on, :sender, :recipient, :presence => true
+  validates :title, :body, :characters => true
 
   def self.from_mail(mail, from, to)
     if mail.multipart?
index 20065b993137f8e1df485c24c81d716371c062ae..43915bc1265df1155f31dc51bf8b457620600bbe 100644 (file)
@@ -18,6 +18,6 @@ class NodeTag < ActiveRecord::Base
   belongs_to :node
 
   validates :node, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => :node_id }
 end
index 1772b3f9728aaab6502dec45b0a0f15977c678d2..f94032e1e3e785a398b6e5f1d06afe195f06d331 100644 (file)
@@ -33,8 +33,7 @@ class NoteComment < ActiveRecord::Base
   validates :visible, :inclusion => [true, false]
   validates :author, :associated => true
   validates :event, :inclusion => %w[opened closed reopened commented hidden]
-  validates :body, :length => { :maximum => 2000 },
-                   :format => /\A[^\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\ufffe\uffff]*\z/
+  validates :body, :length => { :maximum => 2000 }, :characters => true
 
   # Return the comment text
   def body
index 9e03d34f983a86553d6ca2fb3b8c7ff5d8472056..77b78751b4684a81a720ebe3069cf441a1a6fab4 100644 (file)
@@ -19,6 +19,6 @@ class OldNodeTag < ActiveRecord::Base
   belongs_to :old_node, :foreign_key => [:node_id, :version]
 
   validates :old_node, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => [:node_id, :version] }
 end
index 052b60853e9ec0f630d9b8ccd5aef59b81c89575..4a247949c48c90d298e84c4c9e6034ddaf5e4bb3 100644 (file)
@@ -19,6 +19,6 @@ class OldRelationTag < ActiveRecord::Base
   belongs_to :old_relation, :foreign_key => [:relation_id, :version]
 
   validates :old_relation, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => [:relation_id, :version] }
 end
index 8fffebc2553ab9e8c217195dd8da41c56374f787..5832f6d4fa2bad0addb1b093cfd83d53124ce051 100644 (file)
@@ -19,6 +19,6 @@ class OldWayTag < ActiveRecord::Base
   belongs_to :old_way, :foreign_key => [:way_id, :version]
 
   validates :old_way, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => [:way_id, :version] }
 end
index b835864db074b40e7c27cfd8521e0689b96aa979..e6d7487066fed12e879442d8e2cae74b9b3c1f81 100644 (file)
@@ -31,7 +31,8 @@ class Redaction < ActiveRecord::Base
   has_many :old_ways
   has_many :old_relations
 
-  validates :description, :presence => true
+  validates :title, :presence => true, :characters => true
+  validates :description, :presence => true, :characters => true
   validates :description_format, :inclusion => { :in => %w[text html markdown] }
 
   # this method overrides the AR default to provide the rich
index 5dc609987370ffbc3731e9f6864fc5b155f65f6a..151615f72b06272f4f634be0fba3eeed3c6ae9b7 100644 (file)
@@ -18,6 +18,6 @@ class RelationTag < ActiveRecord::Base
   belongs_to :relation
 
   validates :relation, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => :relation_id }
 end
index 63296a0948b9aae2a3ff7d1ac93423d9cf080e25..9bbf221df75c3cc0237044957321b6b372ed2da3 100644 (file)
@@ -27,7 +27,7 @@ class Report < ActiveRecord::Base
 
   validates :issue, :presence => true
   validates :user, :presence => true
-  validates :details, :presence => true
+  validates :details, :presence => true, :characters => true
   validates :category, :presence => true
 
   def self.categories_for(reportable)
index 5096a81aafcb7e69e9b77d8d90b3fd9f933ec545..1d0b11d4330888f980b3c69f8cf042f5ff78fdbc 100644 (file)
@@ -38,8 +38,8 @@ class Trace < ActiveRecord::Base
   scope :tagged, ->(t) { joins(:tags).where(:gpx_file_tags => { :tag => t }) }
 
   validates :user, :presence => true, :associated => true
-  validates :name, :presence => true, :length => 1..255
-  validates :description, :presence => { :on => :create }, :length => 1..255
+  validates :name, :presence => true, :length => 1..255, :characters => true
+  validates :description, :presence => { :on => :create }, :length => 1..255, :characters => true
   validates :timestamp, :presence => true
   validates :visibility, :inclusion => %w[private public trackable identifiable]
 
index 1a4fbd39889195a4653fcc1ed9fec5be3d8e4126..84b6c6dfa8394404c3757a20145e1a77a6791c82 100644 (file)
@@ -22,5 +22,5 @@ class Tracetag < ActiveRecord::Base
   belongs_to :trace, :foreign_key => "gpx_id"
 
   validates :trace, :associated => true
-  validates :tag, :length => 1..255, :format => %r{\A[^/;.,?]*\z}
+  validates :tag, :length => 1..255, :characters => { :url_safe => true }
 end
index de5529e025bdbc042c02678a43880fd73d18ca67..27ed7b648cb3e0bb7864d37d4f4180a3b5f0ef43 100644 (file)
@@ -88,17 +88,14 @@ class User < ActiveRecord::Base
                     :default_url => "/assets/:class/:attachment/:style.png",
                     :styles => { :large => "100x100>", :small => "50x50>" }
 
-  validates :display_name, :presence => true, :allow_nil => true, :length => 3..255,
+  validates :display_name, :presence => true, :length => 3..255,
                            :exclusion => %w[new terms save confirm confirm-email go_public reset-password forgot-password suspended]
   validates :display_name, :if => proc { |u| u.display_name_changed? },
                            :uniqueness => { :case_sensitive => false }
   validates :display_name, :if => proc { |u| u.display_name_changed? },
-                           :format => { :with => %r{\A[^\x00-\x1f\x7f\ufffe\uffff/;.,?%#]*\z} }
-  validates :display_name, :if => proc { |u| u.display_name_changed? },
-                           :format => { :with => /\A\S/, :message => "has leading whitespace" }
-  validates :display_name, :if => proc { |u| u.display_name_changed? },
-                           :format => { :with => /\S\z/, :message => "has trailing whitespace" }
-  validates :email, :presence => true, :confirmation => true
+                           :characters => { :url_safe => true },
+                           :whitespace => { :leading => false, :trailing => false }
+  validates :email, :presence => true, :confirmation => true, :characters => true
   validates :email, :if => proc { |u| u.email_changed? },
                     :uniqueness => { :case_sensitive => false }
   validates :pass_crypt, :confirmation => true, :length => 8..255
index 9f32862af52546bfbf4542599d38916af0ca76e8..02af385a7a09389c88aa722ca8766054e3f2f29f 100644 (file)
@@ -26,6 +26,7 @@
 
 class UserBlock < ActiveRecord::Base
   validate :moderator_permissions
+  validates :reason, :characters => true
 
   belongs_to :user, :class_name => "User", :foreign_key => :user_id
   belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
index 69b0e9ddec9fc020787c76165f3323ec9cceccd7..3963bd02aeb47ade7f6b47faf15293412b63c604 100644 (file)
@@ -17,7 +17,7 @@ class UserPreference < ActiveRecord::Base
   belongs_to :user
 
   validates :user, :presence => true, :associated => true
-  validates :k, :v, :length => 1..255
+  validates :k, :v, :length => 1..255, :characters => true
 
   # Turn this Node in to an XML Node without the <osm> wrapper.
   def to_xml_node
index 8ef75f38e21698b99bb24b63e2cce6d6f0def5e1..c4df0abb53904bf9b733ef5110f698b01dd123df 100644 (file)
@@ -18,6 +18,6 @@ class WayTag < ActiveRecord::Base
   belongs_to :way
 
   validates :way, :presence => true, :associated => true
-  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }
+  validates :k, :v, :allow_blank => true, :length => { :maximum => 255 }, :characters => true
   validates :k, :uniqueness => { :scope => :way_id }
 end
diff --git a/app/validators/characters_validator.rb b/app/validators/characters_validator.rb
new file mode 100644 (file)
index 0000000..ee5b120
--- /dev/null
@@ -0,0 +1,12 @@
+class CharactersValidator < ActiveModel::EachValidator
+  INVALID_CHARS = "\x00-\x08\x0b-\x0c\x0e-\x1f\x7f\ufffe\uffff".freeze
+  INVALID_URL_CHARS = "/;.,?%#".freeze
+
+  def validate_each(record, attribute, value)
+    record.errors[attribute] << (options[:message] || I18n.t("validations.invalid_characters")) if value =~ /[#{INVALID_CHARS}]/
+
+    if options[:url_safe]
+      record.errors[attribute] << (options[:message] || I18n.t("validations.url_characters", :characters => INVALID_URL_CHARS)) if value =~ /[#{INVALID_URL_CHARS}]/
+    end
+  end
+end
diff --git a/app/validators/whitespace_validator.rb b/app/validators/whitespace_validator.rb
new file mode 100644 (file)
index 0000000..d177df9
--- /dev/null
@@ -0,0 +1,11 @@
+class WhitespaceValidator < ActiveModel::EachValidator
+  def validate_each(record, attribute, value)
+    unless options.fetch(:leading, true)
+      record.errors[attribute] << (options[:message] || I18n.t("validations.leading_whitespace")) if value =~ /\A\s/
+    end
+
+    unless options.fetch(:trailing, true)
+      record.errors[attribute] << (options[:message] || I18n.t("validations.trailing_whitespace")) if value =~ /\s\z/
+    end
+  end
+end
index 7b13a894bc860b3e09cddf1deee13f26e34b8a5a..a9370018aabba1ad6ddecff6fdd0f786c208f044 100644 (file)
@@ -2562,3 +2562,8 @@ en:
       not_empty: "Redaction is not empty. Please un-redact all versions belonging to this redaction before destroying it."
       flash: "Redaction destroyed."
       error: "There was an error destroying this redaction."
+  validations:
+    leading_whitespace: "has leading whitespace"
+    trailing_whitespace: "has trailing whitespace"
+    invalid_characters: "contains invalid characters"
+    url_characters: "contains special URL characters (%{characters})"
index 0e618cd22c86ec3058c0f699c6dfc1730a307bfc..a17c30deb5eded5191d4e99ff872696d3efc97da 100644 (file)
@@ -65,16 +65,13 @@ class UserTest < ActiveSupport::TestCase
   def test_display_name_length
     user = build(:user)
     user.display_name = "123"
-    assert user.valid?, " should allow nil display name"
+    assert user.valid?, "should allow 3 char name name"
     user.display_name = "12"
     assert_not user.valid?, "should not allow 2 char name"
     user.display_name = ""
-    assert_not user.valid?
+    assert_not user.valid?, "should not allow blank/0 char name"
     user.display_name = nil
-    # Don't understand why it isn't allowing a nil value,
-    # when the validates statements specifically allow it
-    # It appears the database does not allow null values
-    assert_not user.valid?
+    assert_not user.valid?, "should not allow nil value"
   end
 
   def test_display_name_valid
@@ -82,14 +79,15 @@ class UserTest < ActiveSupport::TestCase
     # expact are allowed
     # However, would they affect the xml planet dumps?
     ok = ["Name", "'me", "he\"", "<hr>", "*ho", "\"help\"@",
-          "vergrößern", "ルシステムにも対応します", "輕觸搖晃的遊戲"]
+          "vergrößern", "ルシステムにも対応します", "輕觸搖晃的遊戲", "space space"]
     # These need to be 3 chars in length, otherwise the length test above
     # should be used.
     bad = ["<hr/>", "test@example.com", "s/f", "aa/", "aa;", "aa.",
            "aa,", "aa?", "/;.,?", "も対応します/", "#ping",
            "foo\x1fbar", "foo\x7fbar", "foo\ufffebar", "foo\uffffbar",
            "new", "terms", "save", "confirm", "confirm-email",
-           "go_public", "reset-password", "forgot-password", "suspended"]
+           "go_public", "reset-password", "forgot-password", "suspended",
+           "trailing whitespace ", " leading whitespace"]
     ok.each do |display_name|
       user = build(:user)
       user.display_name = display_name
diff --git a/test/validators/characters_validator_test.rb b/test/validators/characters_validator_test.rb
new file mode 100644 (file)
index 0000000..e1409a5
--- /dev/null
@@ -0,0 +1,66 @@
+require "test_helper"
+
+class InvalidCharsValidatable
+  include ActiveModel::Validations
+  validates :chars, :characters => true
+  attr_accessor :chars
+end
+
+class InvalidUrlCharsValidatable
+  include ActiveModel::Validations
+  validates :chars, :characters => { :url_safe => true }
+  attr_accessor :chars
+end
+
+class CharactersValidatorTest < ActiveSupport::TestCase
+  include Rails::Dom::Testing::Assertions::SelectorAssertions
+
+  def test_with_valid_chars
+    c = InvalidCharsValidatable.new
+
+    valid = ["Name.", "'me", "he\"", "<hr>", "*ho", "\"help\"@",
+             "vergrößern", "ルシステムにも対応します", "輕觸搖晃的遊戲", "/;.,?%#"]
+
+    valid.each do |v|
+      c.chars = v
+      assert c.valid?, "'#{v}' should be valid"
+    end
+  end
+
+  def test_with_invalid_chars
+    c = InvalidCharsValidatable.new
+
+    invalid = ["\x7f<hr/>", "test@example.com\x0e-", "s/\x1ff", "aa/\ufffe",
+               "aa\x0b-,", "aa?\x08", "/;\uffff.,?", "\x00-も対応します/", "\x0c#ping",
+               "foo\x1fbar", "foo\x7fbar", "foo\ufffebar", "foo\uffffbar"]
+
+    invalid.each do |v|
+      c.chars = v
+      assert_not c.valid?, "'#{v}' should not be valid"
+    end
+  end
+
+  def test_with_valid_url_chars
+    c = InvalidUrlCharsValidatable.new
+
+    valid = ["Name", "'me", "he\"", "<hr>", "*ho", "\"help\"@",
+             "vergrößern", "ルシステムにも対応します", "輕觸搖晃的遊戲"]
+
+    valid.each do |v|
+      c.chars = v
+      assert c.valid?, "'#{v}' should be valid"
+    end
+  end
+
+  def test_with_invalid_url_chars
+    c = InvalidUrlCharsValidatable.new
+
+    invalid = ["Name.", "you;me", "he\"#", "<hr/>", "50%", "good?",
+               "vergrößern,deutsche", "ルシステムに;.も対応します", "輕觸搖/晃的遊戲", "/;.,?%#"]
+
+    invalid.each do |v|
+      c.chars = v
+      assert_not c.valid?, "'#{v}' should not be valid"
+    end
+  end
+end
diff --git a/test/validators/whitespace_validator_test.rb b/test/validators/whitespace_validator_test.rb
new file mode 100644 (file)
index 0000000..c908538
--- /dev/null
@@ -0,0 +1,61 @@
+require "test_helper"
+
+class LeadingWhitespaceValidatable
+  include ActiveModel::Validations
+  validates :string, :whitespace => { :leading => false }
+  attr_accessor :string
+end
+
+class TrailingWhitespaceValidatable
+  include ActiveModel::Validations
+  validates :string, :whitespace => { :trailing => false }
+  attr_accessor :string
+end
+
+class WhitespaceValidatorTest < ActiveSupport::TestCase
+  include Rails::Dom::Testing::Assertions::SelectorAssertions
+
+  def test_with_leading_whitespace
+    validator = LeadingWhitespaceValidatable.new
+
+    strings = [" ", " test", "  ", "\ttest"]
+
+    strings.each do |v|
+      validator.string = v
+      assert_not validator.valid?, "'#{v}' should not be valid"
+    end
+  end
+
+  def test_without_leading_whitespace
+    validator = LeadingWhitespaceValidatable.new
+
+    strings = ["test", "test ", "t est", "test\t", ".test", "_test"]
+
+    strings.each do |v|
+      validator.string = v
+      assert validator.valid?, "'#{v}' should be valid"
+    end
+  end
+
+  def test_with_trailing_whitespace
+    validator = TrailingWhitespaceValidatable.new
+
+    strings = [" ", "test ", "  ", "test\t", "_test_ "]
+
+    strings.each do |v|
+      validator.string = v
+      assert_not validator.valid?, "'#{v}' should not be valid"
+    end
+  end
+
+  def test_without_trailing_whitespace
+    validator = TrailingWhitespaceValidatable.new
+
+    strings = ["test", " test", "tes t", "\ttest", "test.", "test_"]
+
+    strings.each do |v|
+      validator.string = v
+      assert validator.valid?, "'#{v}' should be valid"
+    end
+  end
+end