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

时间:2012-01-17 19:49:54

标签: ruby uri

我正在尝试找到一种将部分url路径段连接在一起的强大方法。有快速的方法吗?

我尝试了以下内容:

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

我期待:

resource/edit/12?option=test

但我收到错误:

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

我过去曾使用过File.join(),但是对于使用网址文件库来说似乎不对。

11 个答案:

答案 0 :(得分:21)

URI的api并不是很好。

URI :: join只有在第一个以协议开头的绝对uri时才有效,而后者以正确的方式相对...除了我尝试这样做,甚至不能得到它工作。

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

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

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

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

编辑 2016年12月9日我发现addressable宝石非常好。

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>

答案 1 :(得分:8)

问题是resource/是相对于当前目录的,但/edit由于前导斜杠而引用顶级目录。如果不确定edit包含resource,则无法加入这两个目录。

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

答案 2 :(得分:7)

使用URI.join的方法是:

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

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

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

答案 3 :(得分:4)

将uri作为URI::Generic或其子类

uri.path += '/123' 

享受!

对于持怀疑态度的人来说,

06/25/2016更新

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

输出

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

Run me

答案 4 :(得分:3)

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

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

事实证明,它不需要很多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

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

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

输出

resource/edit/12?option=test

答案 5 :(得分:2)

使用此代码:

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

空字符串示例:

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

如果可能,请使用此功能来使用resource/edit/12?option=test等字段,其中http:只是一个占位符来获取有效的URI。这对我有用。

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

答案 6 :(得分:1)

未优化的解决方案。请注意,它不考虑查询参数。它只处理路径。

class URL
  def self.join(*str)
    str.map { |path|
      new_path = path
      # Check the first character
      if path[0] == "/"
        new_path = new_path[1..-1]
      end

      # Check the last character
      if path[-1] != "/"
        new_path += "/"
      end

      new_path
    }.join
  end
end

答案 7 :(得分:0)

我改进了@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

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)

您可以使用:

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"

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

答案 9 :(得分:0)

我对URI::join的理解是,它像Web浏览器一样。

要对其进行评估,请将您的心理网络浏览器指向第一个参数,并不断单击链接,直到浏览到最后一个参数。

例如URI::join('http://example.com/resource/', '/edit', '12?option=test'),您将像这样浏览:

如果第一个链接是/edit/(带有斜杠)或/edit/foo,则下一个链接将相对于/edit/而不是/

此页面可能比我能更好地解释它:Why is URI.join so counterintuitive?

答案 10 :(得分:-1)

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