From 0f0164c63bbe505905fcc2bfb59a195f3ba86325 Mon Sep 17 00:00:00 2001 From: Adrien Dorsaz Date: Sat, 7 Jan 2023 18:04:42 +0100 Subject: [PATCH] fix multiple sharp signs in URIs rename HttpUrlValidator as UriValidator --- app/models/bookmark.rb | 17 ++------- app/models/link.rb | 16 ++------ app/validators/http_url_validator.rb | 25 ------------ app/validators/uri_validator.rb | 57 ++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 52 deletions(-) delete mode 100644 app/validators/http_url_validator.rb create mode 100644 app/validators/uri_validator.rb diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb index 75e47a37..5de551d3 100644 --- a/app/models/bookmark.rb +++ b/app/models/bookmark.rb @@ -26,22 +26,11 @@ class Bookmark < Content validates :title, presence: { message: "Le titre est obligatoire" }, length: { maximum: 100, message: "Le titre est trop long" } validates :link, presence: { message: "Vous ne pouvez pas poster un lien vide" }, - http_url: { message: "Le lien n'est pas valide" }, + uri: { message: "Le lien n'est pas valide" }, length: { maximum: 255, message: "Le lien est trop long" } - def link=(raw) - raw.strip! - return write_attribute :url, nil if raw.blank? - uri = URI.parse(raw) - # Default to HTTP link if neither scheme nor host is found - if uri.scheme.blank? && uri.host.blank? - raw = "http://#{raw}" - uri = URI.parse(raw) - end - write_attribute :link, uri.to_s - # Let raw value if error when parsed, HttpUrlValidator will manage it - rescue URI::InvalidURIError - write_attribute :link, raw + before_validation do |bookmark| + bookmark.link = UriValidator.before_validation(bookmark.link); end def create_node(attrs={}) diff --git a/app/models/link.rb b/app/models/link.rb index 52fde9b4..9b34aeae 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -26,22 +26,12 @@ class Link < ActiveRecord::Base validates :title, presence: { message: "Un lien doit obligatoirement avoir un titre" }, length: { maximum: 100, message: "Le titre est trop long" } - validates :url, http_url: { protocols: PROTOCOLS, message: "L'adresse n'est pas valide" }, + validates :url, uri: { protocols: PROTOCOLS, message: "L'adresse n'est pas valide" }, presence: { message: "Un lien doit obligatoirement avoir une adresse" }, length: { maximum: 255, message: "L’adresse est trop longue" } - def url=(raw) - raw.strip! - return write_attribute :url, nil if raw.blank? - uri = URI.parse(raw) - if uri.scheme.blank? && uri.host.blank? - raw = "http://#{raw}" - uri = URI.parse(raw) - end - write_attribute :url, uri.to_s - # Let raw value if error when parsed, HttpUrlValidator will manage it - rescue URI::InvalidURIError - write_attribute :url, raw + before_validation do |link| + link.url = UriValidator.before_validation(link.url); end ### Behaviour ### diff --git a/app/validators/http_url_validator.rb b/app/validators/http_url_validator.rb deleted file mode 100644 index d104a1a9..00000000 --- a/app/validators/http_url_validator.rb +++ /dev/null @@ -1,25 +0,0 @@ -# Validate if a value is a HTTP url -class HttpUrlValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - if value.present? && not(valid?(value, options)) - record.errors.add attribute, (options[:message] || "n'est pas une URL HTTP valide") - end - end - - private - - def valid?(value, options) - # Valid links can be parsed by URI - uri = URI.parse(value) - # Authorize protocol - if options.has_key?(:protocols) - return options[:protocols].include?(uri.scheme) - end - # Links starting with "//MY_DOMAIN" are current scheme dependent and are valid - return true if uri.scheme.nil? && uri.host == MY_DOMAIN - # All other links are valid only if scheme and host exists - return uri.scheme.present? && uri.host.present? - rescue URI::InvalidURIError - false - end -end diff --git a/app/validators/uri_validator.rb b/app/validators/uri_validator.rb new file mode 100644 index 00000000..92f02671 --- /dev/null +++ b/app/validators/uri_validator.rb @@ -0,0 +1,57 @@ +# Validate if a value is a URI +class UriValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if value.present? && not(valid?(value, options)) + record.errors.add attribute, (options[:message] || "n'est pas un lien valide") + end + end + + private + + def valid?(value, options) + # Valid links can be parsed by URI + uri = URI.parse(value) + + # Authorize only given protocol + if options.has_key?(:protocols) + return options[:protocols].include?(uri.scheme) + end + + # Links starting with "//MY_DOMAIN" are current scheme dependent and are valid + return true if uri.scheme.nil? && uri.host == MY_DOMAIN + + # All other links are valid only if scheme and host exists + return uri.scheme.present? && uri.host.present? + rescue URI::InvalidURIError + false + end + + def self.before_validation(raw, default_scheme='http://') + raw.strip! + return nil if raw.blank? + + # Automatically encodes sharp signs (#) found in URI fragment: + # RFC 3986 uses sharp to define URI fragment and requires other sharps + # to be percent encoded + fragments = raw.split("#") + if (fragments.length > 2) + raw = fragments[0] + '#' + fragments[1..-1].join('%23') + end + + uri = URI.parse(raw) + + # If user forgot to add scheme, add default + if default_scheme.present? && uri.scheme.blank? && uri.host.blank? + raw = "#{default_scheme}#{raw}" + uri = URI.parse(raw) + end + + return uri.to_s + + # Let raw value if error occured when we tried to parse it, because + # the HttpUrlValidator will manage it itself on validation + rescue URI::InvalidURIError + return raw + end + +end