Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix multiple sharp signs in URIs #356

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/bookmarks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def create
@bookmark.node = Node.new(user_id: current_user.id, cc_licensed: false)
@bookmark.node.preview_tags = params[:tags]
@bookmark.valid?
flash.now[:alert] = "Votre lien semble invalide. Le confimez‑vous ?" unless @bookmark.link =~ /\A#{URI::regexp(['http', 'https'])}\z/
flash.now[:alert] = "Votre lien est invalide, veuillez le corriger." unless @bookmark.errors[:link].empty?
render :new
end
end
Expand Down
21 changes: 7 additions & 14 deletions app/models/bookmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,15 @@ 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: { protocols: ["http", "https"], 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

after_validation do |bookmark|
bookmark.link = UriValidator.after_validation(bookmark.link);
end

def create_node(attrs={})
Expand Down
20 changes: 7 additions & 13 deletions app/models/link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,16 @@ 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

after_validation do |link|
link.url = UriValidator.after_validation(link.url);
end

### Behaviour ###
Expand Down
16 changes: 13 additions & 3 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,25 @@ class User < ActiveRecord::Base
has_many :taggings, -> { includes(:tag) }, dependent: :destroy
has_many :tags, -> { distinct }, through: :taggings

validates_format_of :homesite, message: "L’adresse du site Web personnel n’est pas valide", with: URI::regexp(%w(http https)), allow_blank: true
validates :homesite, length: { maximum: 100, message: "L’adresse du site Web personnel est trop longue" }
validates :homesite, uri: { protocols: ["http", "https"], message: "L’adresse du site Web personnel n’est pas valide" },
length: { maximum: 100, message: "L’adresse du site Web personnel est trop longue" }
validates :name, length: { maximum: 40, message: "Le nom affiché est trop long" }
validates :jabber_id, length: { maximum: 32, message: "L’adresse XMPP est trop longue" }
validates :mastodon_url, http_url: { protocols: ["https"], message: "L’adresse du compte Mastodon n’est pas valide" },
validates :mastodon_url, uri: { protocols: ["https"], message: "L’adresse du compte Mastodon n’est pas valide" },
length: { maximum: 255, message: "L’adresse du compte Mastodon est trop longue" }
validates :signature, length: { maximum: 255, message: "La signature est trop longue" }
validates :custom_avatar_url, length: { maximum: 255, message: "L’adresse de l’avatar est trop longue" }

before_validation do |user|
user.homesite = UriValidator.before_validation(user.homesite)
user.mastodon_url = UriValidator.before_validation(user.mastodon_url)
end

after_validation do |user|
user.homesite = UriValidator.after_validation(user.homesite)
user.mastodon_url = UriValidator.after_validation(user.mastodon_url)
end

def self.collective
find_by(name: "Collectif")
end
Expand Down
25 changes: 0 additions & 25 deletions app/validators/http_url_validator.rb

This file was deleted.

68 changes: 68 additions & 0 deletions app/validators/uri_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 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 UriValidator will manage it itself on validation
rescue URI::InvalidURIError
return raw
end

def self.after_validation(raw)
# Decodes sharp signs (#) found in URI fragment to keep visual match with
# the user input
fragments = raw.split("#")
if (fragments.length == 2)
raw = fragments[0] + '#' + fragments[1].gsub('%23', '#')
end

return raw
end

end