如何安全地加入相对网址段?

时间:2022-02-04 07:15:00

I'm trying to find a robust method of joining partial url path segments together. Is there a quick way to do this?

我试图找到一种将部分url路径段连接在一起的可靠方法。有快速的方法吗?

I tried the following:

我尝试了以下方法:

puts URI::join('resource/', '/edit', '12?option=test')

I expect:

我预计:

resource/edit/12?option=test

But I get the error:

但我得到错误:

`merge': both URI are relative (URI::BadURIError)

I have used File.join() in the past for this but something does not seem right about using the file library for urls.

我过去曾使用过File.join(),但是对于使用url的文件库来说似乎并不正确。

9 个解决方案

#1


20  

URI's api is not neccearily great.

URI的api并不是很好。

URI::join will work only if the first one starts out as an absolute uri with protocol, and the later ones are relative in the right ways... except I try to do that and can't even get that to work.

URI :: join只有在第一个以协议开头的绝对uri时才会起作用,而后者则以正确的方式相对...除了我尝试这样做,甚至无法使其工作。

This at least doesn't error, but why is it skipping the middle component?

这至少没有错误,但为什么它会跳过中间组件?

 URI::join('http://somewhere.com/resource', './edit', '12?option=test') 

I think maybe URI just kind of sucks. It lacks significant api on instances, such as an instance #join or method to evaluate relative to a base uri, that you'd expect. It's just kinda crappy.

我认为URI可能很糟糕。它缺少对实例的重要API,例如实例#join或相对于基本uri的方法,您期望它。这有点蹩脚。

I think you're going to have to write it yourself. Or just use File.join and other File path methods, after testing all the edge cases you can think of to make sure it does what you want/expect.

我想你将不得不自己写。或者只是使用File.join和其他文件路径方法,在测试了所有可以想到的边缘情况后,确保它能达到您想要/期望的效果。

edit 9 Dec 2016 I figured out the addressable gem does it very nicely.

编辑2016年12月9日我发现可寻址的宝石非常好。

base = Addressable::URI.parse("http://example.com")
base + "foo.html"
# => #<Addressable::URI:0x3ff9964aabe4 URI:http://example.com/foo.html>

base = Addressable::URI.parse("http://example.com/path/to/file.html")
base + "relative_file.xml"
# => #<Addressable::URI:0x3ff99648bc80 URI:http://example.com/path/to/relative_file.xml>

base = Addressable::URI.parse("https://example.com/path")
base + "//newhost/somewhere.jpg"
# => #<Addressable::URI:0x3ff9960c9ebc URI:https://newhost/somewhere.jpg>

base = Addressable::URI.parse("http://example.com/path/subpath/file.html")
base + "../up-one-level.html"
=> #<Addressable::URI:0x3fe13ec5e928 URI:http://example.com/path/up-one-level.html>

#2


7  

The problem is that resource/ is relative to the current directory, but /edit refers to the top level directory due to the leading slash. It's impossible to join the two directories without already knowing for certain that edit contains resource.

问题是资源/是相对于当前目录,但/ edit是由于前导斜杠而引用*目录。在不知道编辑包含资源的情况下加入这两个目录是不可能的。

If you're looking for purely string operations, simply remove the leading or trailing slashes from all parts, then join them with / as the glue.

如果您正在寻找纯粹的字符串操作,只需从所有部分中删除前导或尾部斜杠,然后使用/作为粘合剂将它们连接起来。

#3


7  

The way to do it using URI.join is:

使用URI.join的方法是:

URI.join('http://example.com', '/foo/', 'bar')

URI.join('http://example.com','/ foo /','bar')

Pay attention to the trailing slashes. You can find the complete documentation here:

注意尾随斜杠。您可以在此处找到完整的文档:

http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-join

http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-join

#4


3  

Using File.join is not robust since it will use the OS filesystem separator, which in Windows is \ instead of /, losing portability.

使用File.join并不健壮,因为它将使用操作系统文件系统分隔符,在Windows中它是\而不是/,失去了可移植性。

As you noticed, URI::join won't combine paths with repeated slashes, so it doesn't fit the part.

正如您所注意到的,URI :: join不会将路径与重复斜杠组合在一起,因此它不适合该部件。

Turns out it doesn't require a lot of Ruby code to achieve this:

事实证明它不需要很多Ruby代码来实现这一点:

module GluePath

  def self.join(*paths, separator: '/')
    paths = paths.compact.reject(&:empty?)
    last = paths.length - 1
    paths.each_with_index.map { |path, index|
      _expand(path, index, last, separator)
    }.join
  end

  def self._expand(path, current, last, separator)
    if path.starts_with?(separator) && current != 0
      path = path[1..-1]
    end

    unless path.ends_with?(separator) || current == last
      path = [path, separator]
    end

    path
  end
end

The algorithm takes care of consecutive slashes, preserves start and end slashes, and ignores nil and empty strings.

该算法处理连续斜杠,保留开始和结束斜杠,并忽略nil和空字符串。

puts GluePath::join('resource/', '/edit', '12?option=test')

outputs

输出

resource/edit/12?option=test

#5


3  

Have uri as URI::Generic or subclass of thereof

uri作为URI :: Generic或其子类

uri.path += '/123' 

Enjoy!

请享用!

06/25/2016 UPDATE for skeptical folk

2016年6月25日对持怀疑态度的人进行更新

require 'uri'
uri = URI('http://ioffe.net/boris')
uri.path += '/123'
p uri

Outputs

输出

 <URI::HTTP:0x2341a58 URL:http://ioffe.net/boris/123>

Run me

跑我

#6


2  

Use this code:

使用此代码:

File.join('resource/', '/edit', '12?option=test').
     gsub(File::SEPARATOR, '/').
     sub(/^\//, '')
# => resource/edit/12?option=test

example with empty strings:

空字符串的示例:

File.join('', '/edit', '12?option=test').
     gsub(File::SEPARATOR, '/').
     sub(/^\//, '')
# => edit/12?option=test

Or use this if possible to use segments like resource/, edit/, 12?option=test and where http: is only a placeholder to get a valid URI. This works for me.

或者如果可能的话使用它来使用像resources /,edit /,12?option = test这样的段,其中http:只是一个占位符来获取有效的URI。这对我有用。

URI.
  join('http:', 'resource/', 'edit/', '12?option=test').
  path.
  sub(/^\//, '')
# => "resource/edit/12"

#7


0  

I improved @Maximo Mussini's script to make it works gracefully:

我改进了@Maximo Mussini的脚本,使其优雅地工作:

SmartURI.join('http://example.com/subpath', 'hello', query: { token: secret })
=> "http://example.com/subpath/hello?token=secret"

https://gist.github.com/zernel/0f10c71f5a9e044653c1a65c6c5ad697

https://gist.github.com/zernel/0f10c71f5a9e044653c1a65c6c5ad697

require 'uri'

module SmartURI
  SEPARATOR = '/'

  def self.join(*paths, query: nil)
    paths = paths.compact.reject(&:empty?)
    last = paths.length - 1
    url = paths.each_with_index.map { |path, index|
      _expand(path, index, last)
    }.join
    if query.nil?
      return url
    elsif query.is_a? Hash
      return url + "?#{URI.encode_www_form(query.to_a)}"
    else
      raise "Unexpected input type for query: #{query}, it should be a hash."
    end
  end

  def self._expand(path, current, last)
    if path.starts_with?(SEPARATOR) && current != 0
      path = path[1..-1]
    end

    unless path.ends_with?(SEPARATOR) || current == last
      path = [path, SEPARATOR]
    end

    path
  end
end

#8


0  

You can use this:

你可以用这个:

URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd')
=> #<URI::HTTP http://exemple.com/a/b/c/d>
URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd').to_s
=> "http://exemple.com/a/b/c/d"

See: http://ruby-doc.org/stdlib-2.4.1/libdoc/uri/rdoc/URI.html#method-c-join-label-Synopsis

见:http://ruby-doc.org/stdlib-2.4.1/libdoc/uri/rdoc/URI.html#method-c-join-label-Synopsis

#9


-1  

You can use File.join('resource/', '/edit', '12?option=test')

你可以使用File.join('resource /','/ edit','12?option = test')

#1


20  

URI's api is not neccearily great.

URI的api并不是很好。

URI::join will work only if the first one starts out as an absolute uri with protocol, and the later ones are relative in the right ways... except I try to do that and can't even get that to work.

URI :: join只有在第一个以协议开头的绝对uri时才会起作用,而后者则以正确的方式相对...除了我尝试这样做,甚至无法使其工作。

This at least doesn't error, but why is it skipping the middle component?

这至少没有错误,但为什么它会跳过中间组件?

 URI::join('http://somewhere.com/resource', './edit', '12?option=test') 

I think maybe URI just kind of sucks. It lacks significant api on instances, such as an instance #join or method to evaluate relative to a base uri, that you'd expect. It's just kinda crappy.

我认为URI可能很糟糕。它缺少对实例的重要API,例如实例#join或相对于基本uri的方法,您期望它。这有点蹩脚。

I think you're going to have to write it yourself. Or just use File.join and other File path methods, after testing all the edge cases you can think of to make sure it does what you want/expect.

我想你将不得不自己写。或者只是使用File.join和其他文件路径方法,在测试了所有可以想到的边缘情况后,确保它能达到您想要/期望的效果。

edit 9 Dec 2016 I figured out the addressable gem does it very nicely.

编辑2016年12月9日我发现可寻址的宝石非常好。

base = Addressable::URI.parse("http://example.com")
base + "foo.html"
# => #<Addressable::URI:0x3ff9964aabe4 URI:http://example.com/foo.html>

base = Addressable::URI.parse("http://example.com/path/to/file.html")
base + "relative_file.xml"
# => #<Addressable::URI:0x3ff99648bc80 URI:http://example.com/path/to/relative_file.xml>

base = Addressable::URI.parse("https://example.com/path")
base + "//newhost/somewhere.jpg"
# => #<Addressable::URI:0x3ff9960c9ebc URI:https://newhost/somewhere.jpg>

base = Addressable::URI.parse("http://example.com/path/subpath/file.html")
base + "../up-one-level.html"
=> #<Addressable::URI:0x3fe13ec5e928 URI:http://example.com/path/up-one-level.html>

#2


7  

The problem is that resource/ is relative to the current directory, but /edit refers to the top level directory due to the leading slash. It's impossible to join the two directories without already knowing for certain that edit contains resource.

问题是资源/是相对于当前目录,但/ edit是由于前导斜杠而引用*目录。在不知道编辑包含资源的情况下加入这两个目录是不可能的。

If you're looking for purely string operations, simply remove the leading or trailing slashes from all parts, then join them with / as the glue.

如果您正在寻找纯粹的字符串操作,只需从所有部分中删除前导或尾部斜杠,然后使用/作为粘合剂将它们连接起来。

#3


7  

The way to do it using URI.join is:

使用URI.join的方法是:

URI.join('http://example.com', '/foo/', 'bar')

URI.join('http://example.com','/ foo /','bar')

Pay attention to the trailing slashes. You can find the complete documentation here:

注意尾随斜杠。您可以在此处找到完整的文档:

http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-join

http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-join

#4


3  

Using File.join is not robust since it will use the OS filesystem separator, which in Windows is \ instead of /, losing portability.

使用File.join并不健壮,因为它将使用操作系统文件系统分隔符,在Windows中它是\而不是/,失去了可移植性。

As you noticed, URI::join won't combine paths with repeated slashes, so it doesn't fit the part.

正如您所注意到的,URI :: join不会将路径与重复斜杠组合在一起,因此它不适合该部件。

Turns out it doesn't require a lot of Ruby code to achieve this:

事实证明它不需要很多Ruby代码来实现这一点:

module GluePath

  def self.join(*paths, separator: '/')
    paths = paths.compact.reject(&:empty?)
    last = paths.length - 1
    paths.each_with_index.map { |path, index|
      _expand(path, index, last, separator)
    }.join
  end

  def self._expand(path, current, last, separator)
    if path.starts_with?(separator) && current != 0
      path = path[1..-1]
    end

    unless path.ends_with?(separator) || current == last
      path = [path, separator]
    end

    path
  end
end

The algorithm takes care of consecutive slashes, preserves start and end slashes, and ignores nil and empty strings.

该算法处理连续斜杠,保留开始和结束斜杠,并忽略nil和空字符串。

puts GluePath::join('resource/', '/edit', '12?option=test')

outputs

输出

resource/edit/12?option=test

#5


3  

Have uri as URI::Generic or subclass of thereof

uri作为URI :: Generic或其子类

uri.path += '/123' 

Enjoy!

请享用!

06/25/2016 UPDATE for skeptical folk

2016年6月25日对持怀疑态度的人进行更新

require 'uri'
uri = URI('http://ioffe.net/boris')
uri.path += '/123'
p uri

Outputs

输出

 <URI::HTTP:0x2341a58 URL:http://ioffe.net/boris/123>

Run me

跑我

#6


2  

Use this code:

使用此代码:

File.join('resource/', '/edit', '12?option=test').
     gsub(File::SEPARATOR, '/').
     sub(/^\//, '')
# => resource/edit/12?option=test

example with empty strings:

空字符串的示例:

File.join('', '/edit', '12?option=test').
     gsub(File::SEPARATOR, '/').
     sub(/^\//, '')
# => edit/12?option=test

Or use this if possible to use segments like resource/, edit/, 12?option=test and where http: is only a placeholder to get a valid URI. This works for me.

或者如果可能的话使用它来使用像resources /,edit /,12?option = test这样的段,其中http:只是一个占位符来获取有效的URI。这对我有用。

URI.
  join('http:', 'resource/', 'edit/', '12?option=test').
  path.
  sub(/^\//, '')
# => "resource/edit/12"

#7


0  

I improved @Maximo Mussini's script to make it works gracefully:

我改进了@Maximo Mussini的脚本,使其优雅地工作:

SmartURI.join('http://example.com/subpath', 'hello', query: { token: secret })
=> "http://example.com/subpath/hello?token=secret"

https://gist.github.com/zernel/0f10c71f5a9e044653c1a65c6c5ad697

https://gist.github.com/zernel/0f10c71f5a9e044653c1a65c6c5ad697

require 'uri'

module SmartURI
  SEPARATOR = '/'

  def self.join(*paths, query: nil)
    paths = paths.compact.reject(&:empty?)
    last = paths.length - 1
    url = paths.each_with_index.map { |path, index|
      _expand(path, index, last)
    }.join
    if query.nil?
      return url
    elsif query.is_a? Hash
      return url + "?#{URI.encode_www_form(query.to_a)}"
    else
      raise "Unexpected input type for query: #{query}, it should be a hash."
    end
  end

  def self._expand(path, current, last)
    if path.starts_with?(SEPARATOR) && current != 0
      path = path[1..-1]
    end

    unless path.ends_with?(SEPARATOR) || current == last
      path = [path, SEPARATOR]
    end

    path
  end
end

#8


0  

You can use this:

你可以用这个:

URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd')
=> #<URI::HTTP http://exemple.com/a/b/c/d>
URI.join('http://exemple.com', '/a/', 'b/', 'c/', 'd').to_s
=> "http://exemple.com/a/b/c/d"

See: http://ruby-doc.org/stdlib-2.4.1/libdoc/uri/rdoc/URI.html#method-c-join-label-Synopsis

见:http://ruby-doc.org/stdlib-2.4.1/libdoc/uri/rdoc/URI.html#method-c-join-label-Synopsis

#9


-1  

You can use File.join('resource/', '/edit', '12?option=test')

你可以使用File.join('resource /','/ edit','12?option = test')