Skip to content
This repository has been archived by the owner on Apr 28, 2024. It is now read-only.

Commit

Permalink
Fix a couple of tricky edge cases.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Mar 30, 2017
1 parent 063b99f commit 8af6ac2
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 20 deletions.
49 changes: 30 additions & 19 deletions lib/trenni/uri.rb
Expand Up @@ -20,21 +20,32 @@

module Trenni
class URI
def initialize(base, fragment, query)
@base = base
def initialize(path, query_string, fragment, parameters)
@path = path
@query_string = query_string
@fragment = fragment
@query = query
@parameters = parameters
end

attr :base
# The path component of the URI, e.g. /foo/bar/index.html
attr :path

# The un-parsed query string of the URI, e.g. 'x=10&y=20'
attr :query_string

# A fragment identifier, the part after the '#'
attr :fragment
attr :query

# User supplied parameters that will be appended to the query part.
attr :parameters

def append(buffer)
buffer << escape_path(@base)

if @query&.any?
buffer << query_separator << query_part
if @query_string
buffer << escape_path(@path) << '?' << query_string
buffer << '&' << query_parameters if @parameters
else
buffer << escape_path(@path)
buffer << '?' << query_parameters if @parameters
end

if @fragment
Expand All @@ -52,10 +63,12 @@ def to_str

private

# Escapes a path string, using percent encoding, but additionally ignoring "/" and substituting spaces with "+".
# According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar.
NON_PCHAR = /([^ a-zA-Z0-9\-\.~!$&'()*+,;=:@\/]+)/.freeze

def escape_path(path)
encoding = path.encoding
path.b.gsub(/([^ a-zA-Z0-9_.\-\/:]+)/) do |m|
path.b.gsub(NON_PCHAR) do |m|
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
end.tr(' ', '+').force_encoding(encoding)
end
Expand All @@ -68,18 +81,16 @@ def escape(string)
end.force_encoding(encoding)
end

def query_part
@query.map{|k,v| "#{escape(k.to_s)}=#{escape(v.to_s)}"}.join('&')
end

def query_separator
@base.include?('?') ? '&' : '?'
def query_parameters
@parameters.map{|k,v| "#{escape(k.to_s)}=#{escape(v.to_s)}"}.join('&')
end
end

def self.URI(path = '', query = nil)
# Generate a URI from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`.
def self.URI(path = '', parameters = nil)
base, fragment = path.split('#', 2)
path, query_string = base.split('?', 2)

URI.new(base, fragment, query)
URI.new(path, query_string, fragment, parameters)
end
end
2 changes: 1 addition & 1 deletion lib/trenni/version.rb
Expand Up @@ -19,5 +19,5 @@
# THE SOFTWARE.

module Trenni
VERSION = "3.1.0"
VERSION = "3.1.1"
end
13 changes: 13 additions & 0 deletions spec/trenni/uri_spec.rb
Expand Up @@ -52,4 +52,17 @@

expect(tag.to_s).to be == '<img src="image.jpg?x=10"/>'
end

describe Trenni::URI("foo?bar=10&baz=20", yes: 'no') do
it "can use existing query parameters" do
expect(subject.to_s).to be == "foo?bar=10&baz=20&yes=no"
end
end

describe Trenni::URI('foo#frag') do
it "can use existing fragment" do
expect(subject.fragment).to be == "frag"
expect(subject.to_s).to be == 'foo#frag'
end
end
end

0 comments on commit 8af6ac2

Please sign in to comment.