如何在Ruby中对字符串进行URL编码

170

我该如何对像这样的字符串进行URI::encode编码:

\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a

想要以类似这样的格式获取它:

%124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A

按照RFC 1738的规定?

这是我尝试过的:

irb(main):123:0> URI::encode "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a"
ArgumentError: invalid byte sequence in UTF-8
    from /usr/local/lib/ruby/1.9.1/uri/common.rb:219:in `gsub'
    from /usr/local/lib/ruby/1.9.1/uri/common.rb:219:in `escape'
    from /usr/local/lib/ruby/1.9.1/uri/common.rb:505:in `escape'
    from (irb):123
    from /usr/local/bin/irb:12:in `<main>'

此外:

irb(main):126:0> CGI::escape "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a"
ArgumentError: invalid byte sequence in UTF-8
    from /usr/local/lib/ruby/1.9.1/cgi/util.rb:7:in `gsub'
    from /usr/local/lib/ruby/1.9.1/cgi/util.rb:7:in `escape'
    from (irb):126
    from /usr/local/bin/irb:12:in `<main>'

我在互联网上搜索了很久,但却没有找到一种方法可以做到这一点,尽管我几乎肯定前几天我毫不费力地就做到了。


1
如果使用Ruby 1.9可能会有用:http://yehudakatz.com/2010/05/05/ruby-1-9-encodings-a-primer-and-the-solution-for-rails/ - apneadiving
8个回答

203
str = "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a".force_encoding('ASCII-8BIT')
puts CGI.escape str


=> "%124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A"

2
force_encoding('binary') 可能是一个更加自我说明的选择。 - mu is too short
68
他们不再推荐使用那种方法,应该使用 CGI.escape 代替。你也可以尝试使用 URI.www_form_encodeURI.www_form_encode_component ,但我从未使用过这些。 - J-Rou
2
这里不需要 require 'open-uri'。你是不是想要 require 'uri' - pje
1
@J-Rou,CGI.escape可以转义整个URL,它不会有选择地转义查询参数。例如,如果您将'a=&!@&b=&$^'传递给CGI.escape,它将使用查询分隔符&转义整个内容,因此仅可用于查询值。我建议使用addressable gem,它更智能地处理URL。 - Alexander.Iljushkin
我需要访问远程服务器上的文件。使用CGI编码无法正常工作,但URI.encode却可以完美解决问题。 - Tashows

116

现今应该使用ERB::Util.url_encode或者CGI.escape。它们之间的主要区别在于对待空格的方式:

>> ERB::Util.url_encode("foo/bar? baz&")
=> "foo%2Fbar%3F%20baz%26"

>> CGI.escape("foo/bar? baz&")
=> "foo%2Fbar%3F+baz%26"

CGI.escape 遵循 CGI/HTML 表单规范,给出一个需要将空格转义为 +application/x-www-form-urlencoded 字符串,而 ERB::Util.url_encode 遵循 RFC 3986,要求将空格编码为 %20

查看“URI.escape 和 CGI.escape 之间的区别是什么?”以获取更多讨论。


在Ruby 2.7.3及以上版本中,这两个命令都不可用。我认为"现如今"已经过时了。 - undefined
@karatedog 他们还在!你可能需要 require 'erb' 或者 require 'cgi' - undefined

73
str = "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a"
require 'cgi'
CGI.escape(str)
# => "%124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A"

来自@J-Rou的评论


13
我最初尝试仅在文件名中而非路径中逃避特殊字符,来自完整的URL字符串。 ERB::Util.url_encode对我的用途无效:
helper.send(:url_encode, "http://example.com/?a=\11\15")
# => "http%3A%2F%2Fexample.com%2F%3Fa%3D%09%0D"

根据"为什么URI.escape()被标记为过时的,REGEXP::UNSAFE常量在哪里?"中的两个回答,似乎使用URI::RFC2396_Parser#escape比使用URI::Escape#escape更好。然而,它们在我的电脑上都表现得一样:
URI.escape("http://example.com/?a=\11\15")
# => "http://example.com/?a=%09%0D"
URI::Parser.new.escape("http://example.com/?a=\11\15")
# => "http://example.com/?a=%09%0D"

我找到了唯一的实际答案。谢谢。 - akostadinov
整个问题一团糟!感谢您为此提供了真正的解决方案。在找到这个之前,我浪费了至少一天的时间! - Russell Fulton

12

你可以使用 Addressable::URI gem 来完成这个操作:

require 'addressable/uri'   
string = '\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a'
Addressable::URI.encode_component(string, Addressable::URI::CharacterClasses::QUERY)
# "%5Cx12%5Cx34%5Cx56%5Cx78%5Cx9a%5Cxbc%5Cxde%5Cxf1%5Cx23%5Cx45%5Cx67%5Cx89%5Cxab%5Cxcd%5Cxef%5Cx12%5Cx34%5Cx56%5Cx78%5Cx9a" 

它使用比CGI.escape更现代的格式,例如,它可以将空格正确编码为%20,而不是+符号,在维基百科上的应用程序/x-www-form-urlencoded类型中可以了解更多信息。

2.1.2 :008 > CGI.escape('Hello, this is me')
 => "Hello%2C+this+is+me" 
2.1.2 :009 > Addressable::URI.encode_component('Hello, this is me', Addressable::URI::CharacterClasses::QUERY)
 => "Hello,%20this%20is%20me" 

也可以这样做: CGI.escape('Hello, this is me').gsub("+", "%20") => Hello%2C%20this%20is%20me" 如果不想使用任何宝石(gems)。 - Raccoon

8

代码:

str = "http://localhost/with spaces and spaces"
encoded = URI::encode(str)
puts encoded

结果:

http://localhost/with%20spaces%20and%20spaces

如果接收服务器比较老,可能无法很好地响应CGI.escape。这仍然是一个有效的替代方案。 - cesartalves

6

我创建了一个宝石(gem)来使URI编码更加干净易用。它为您处理二进制编码。

运行gem install uri-handler,然后使用:

require 'uri-handler'

str = "\x12\x34\x56\x78\x9a\xbc\xde\xf1\x23\x45\x67\x89\xab\xcd\xef\x12\x34\x56\x78\x9a".to_uri
# => "%124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A"

它将URI转换功能添加到String类中。您还可以传递带有可选编码字符串的参数。如果直接使用UTF-8编码失败,它默认设置为编码“binary”。


2
如果您想"编码"一个完整的URL而不必考虑手动拆分它的不同部分,我发现以下方法可以像我以前使用的URI.encode一样工作:

Original Answer翻译成"最初的回答"

URI.parse(my_url).to_s

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接