В чем разница между URI.escape и CGI.escape?

147

В чем разница между URI.escapeи CGI.escapeи какой я должен использовать?

Том Леман
источник

Ответы:

124

Были некоторые небольшие различия, но важным моментом является то, что URI.escapeони устарели в Ruby 1.9.2 ... так что используйте CGI::escapeили ERB :: Util.url_encode .

Существует интересное обсуждение ruby-core для тех, кто интересуется, в котором также упоминаются WEBrick :: HTTPUtils.escape и WEBrick :: HTTPUtils.escape_form .

Марк-Андре Лафортун
источник
11
Просто чтобы добавить путаницу - я только что увидел комментарий на stackoverflow.com/questions/4967608/… где кто-то упомянул, что cgi escape использует '+' вместо% 20 для пробелов, и что это против 'spec' ...
Луи Сэйерс
18
альтернативное использование, ERB::Util.url_encodeкоторое правильно использует %20 для пробелов
riffraff
1
@Ernest: См .: github.com/ruby/ruby/commit/… (ответ обновлен)
Марк-Андре Лафортун
4
ruby-doc.org/stdlib-2.0.0/libdoc/uri/rdoc/URI/Escape.html . В ruby ​​2.0.0 есть модуль URI.escape. Почему это устарело?
user938363
1
@ user938363 если вы нажмете на источник шоу там, вы увидите, что он все еще помечен как устаревший.
drewish
229

Какая разница между топором и мечом, и какой мне следует использовать? Ну, это зависит от того, что вам нужно сделать.

URI.escapeдолжен был закодировать строку (URL) в так называемую « процентную кодировку ».

CGI::escapeисходит из спецификации CGI , которая описывает, как данные должны быть закодированы / декодированы между веб-сервером и приложением.

Теперь предположим, что вам нужно экранировать URI в вашем приложении. Это более конкретный вариант использования. Для этого сообщество Ruby использовалось URI.escapeгодами. Проблема URI.escapeбыла в том, что он не мог справиться со спецификацией RFC-3896.

URI.escape 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog' 
# => "http://google.com/foo?bar=at%23anchor&title=My%20Blog%20&%20Your%20Blog"

URI.escape был помечен как устаревший:

Более того, текущий URI.encode - это простой gsub. Но я думаю, что он должен разделить URI на компоненты, затем экранировать каждый компонент и, наконец, присоединиться к ним.

Таким образом, текущий URI.encode считается вредным и устаревшим. Это будет удалено или радикально изменит поведение.

Какая замена в это время?

Как я уже говорил выше, текущий URI.encode неверен на уровне спецификации. Поэтому мы не будем предоставлять точную замену. Замена зависит от варианта использования.

https://bugs.ruby-lang.org/issues/4167

К сожалению, в документации нет ни единого слова об этом, единственный способ узнать об этом - проверить исходный код или запустить скрипт с предупреждениями в подробном уровне ( -wW2) (или использовать несколько google-fu).

Некоторые предложили использовать CGI::Escapeдля параметров запроса, потому что вы не можете избежать целого URI:

CGI::escape 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
# => "http%3A%2F%2Fgoogle.com%2Ffoo%3Fbar%3Dat%23anchor%26title%3DMy+Blog+%26+Your+Blog"

CGI::escapeследует использовать только для параметров запроса, но результаты снова будут соответствовать спецификации. На самом деле наиболее распространенным вариантом использования является экранирование данных формы, например, при отправке application/x-www-form-urlencodedзапроса POST.

Также упоминается WEBrick::HTTPUtils.escapeне так много улучшений (опять же, это просто gsub, что, IMO, даже худший вариант, чем URI.escape):

WEBrick::HTTPUtils.escape 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
# => "http://google.com/foo?bar=at%23anchor&title=My%20Blog%20&%20Your%20Blog" 

Наиболее близким к спецификации является адресуемый камень:

require 'addressable/uri'
Addressable::URI.escape 'http://google.com/foo?bar=at#anchor&title=My Blog & Your Blog'
# => "http://google.com/foo?bar=at#anchor&title=My%20Blog%20&%20Your%20Blog"

Обратите внимание, что в отличие от всех предыдущих опций Addressable не экранируется #, и это ожидаемое поведение. Вы хотите сохранить #хэш в пути URI, но не в запросе URI.

Единственная оставшаяся проблема заключается в том, что мы не избежали наших параметров запроса должным образом, что приводит нас к выводу: мы не должны использовать один метод для всего URI, потому что не существует идеального решения (пока). Как видите, &не сбежал из «Мой блог и ваш блог». Нам нужно использовать другую форму экранирования для параметров запроса, где пользователи могут помещать разные символы, которые имеют особое значение в URL. Введите URL кодировать. URL-кодирование должно использоваться для каждого «подозрительного» значения запроса, аналогично тому, что ERB::Util.url_encode:

ERB::Util.url_encode "My Blod & Your Blog"
# => "My%20Blod%20%26%20Your%20Blog""

Это круто, но мы уже требовали

uri = Addressable::URI.parse("http://www.go.com/foo")
# => #<Addressable::URI:0x186feb0 URI:http://www.go.com/foo>
uri.query_values = {title: "My Blog & Your Blog"}
uri.normalize.to_s
# => "http://www.go.com/foo?title=My%20Blog%20%26%20Your%20Blog"

Вывод:

  • Не используйте URI.escapeили аналогичные
  • Используйте, CGI::escapeесли вам нужна только форма escape
  • Если вам нужно работать с URI, используйте Addressable, он предлагает кодирование URL, кодирование формы и нормализует URL.
  • Если это проект Rails, посмотрите " Как мне URL-экранировать строку в Rails? "
Эрнест
источник
Большое спасибо за информацию. Это конечно избавилось от некоторых предупреждений тестирования мотыги. Грабли и мотыга смотрят внизу.
Дуглас Г. Аллен
Отличное объяснение @Ernest, но проблема в том, что он не будет работать для внешних URL, которые я не пытаюсь создать (и не могу контролировать). например, сканеры, которые считывают URL-адреса с веб-страницы, а затем пытаются получить доступ к этим URL-адресам (которые необходимо кодировать перед доступом).
amit_saxena
@amit_saxena, если вы можете позволить себе иметь в Addressableкачестве одного из ваших драгоценных камней, вы можете сначала проанализировать URL, fi rubydoc.info/gems/addressable/Addressable/URI.heuristic_parse
Эрнест
Интересный! Но опять же, я не могу получить хэш параметров из исходного URL-адреса с помощью этого, который я затем закодирую, как вы описываете. Процесс в моем случае таков: я получаю внешние URL-адреса из некоторого канала -> который мне затем нужно кодировать -> перейти к http-клиенту для получения содержимого. Теперь, если я не закодирую внешние URL-адреса должным образом, клиенты HTTP на основе ruby ​​завершаются ошибкой с недопустимыми ошибками URI.
amit_saxena
@amit_saxena метод синтаксического анализа вернет экземпляр Addressable:URL, вы можете затем вызвать все методы экземпляра для него, возможно, один из них даст вам желаемые результаты: rubydoc.info/gems/addressable/Addressable/URI
Эрнест
6

CGI::escapeподходит для экранирования текстового сегмента, поэтому его можно использовать в параметрах URL-запроса (строки после '?'). Например, если вы хотите иметь параметр, содержащий символы косой черты в URL-адресе, вы сначала CGI :: экранируете эту строку, а затем вставляете ее в URL-адрес.

Однако в Rails вы, вероятно, не будете использовать его напрямую. Обычно вы используете hash.to_param, который будете использовать CGI::escapeпод капотом.


URI::escapeподходит для экранирования URL, который не был экранирован правильно. Например, некоторые сайты выводят неправильный / неэкранированный URL в своем теге привязки. Если ваша программа использует эти URL для получения большего количества ресурсов, OpenURI будет жаловаться на то, что URL недействительны. Вы должны сделать URI::escapeэто, чтобы сделать его действительным URL. Таким образом, он используется для экранирования всей строки URI, чтобы сделать его правильным. В моем слове URI :: unescape делает URL доступным для чтения человеком, а URI :: escape делает его действительным для браузеров.

Это термин моего непрофессионала, и не стесняйтесь их исправлять.

lulalala
источник
1

Разница в том, что URI.escape не работает ...

CGI.escape"/en/test?asd=qwe"
=> "%2Fen%2Ftest%3Fasd%3Dqwe"

URI.escape"/en/test?asd=qwe"
=> "/en/test?asd=qwe"
Раду Симионеску
источник
2
Вы выбрали неправильный контрольный пример. / / S,? S и = являются частью действительного URI и, следовательно, не экранированы. Другие символы, которые необходимо экранировать, особенно в строке запроса, должны быть.
Джерард ONeill
@GerardONeill Я выбрал тестовый пример именно для того, чтобы показать, как URI.escape не работает и ненадежен. Вы предлагаете, чтобы URI.escape экранировал только строку запроса? как он может сказать, когда завершено значение параметра, если я хочу закодировать & в там? может поэтому он устарел?
Раду
1
Это именно то, что я говорю. Экран URI должен проанализировать URL-адрес, отделить то, что он считает отдельными параметрами, экранировать их и соединить вместе. Даже это может быть грязно. Но он этого не делает - он просто избегает экранирования некоторых символов при экранировании остальных, что делает его неполным. Это может быть использовано для простых случаев, особенно если вы знаете, что ваши параметры не будут .. запутанными.
Джерард
0

CGI.escape предназначен для экранирования значения URL в строке запроса. Все символы, которые не попадают в ALPHA, DIGIT, '_', '-', '.' и '' набор символов экранирован.

Но это сделало бы URL неверным, так как URL должен иметь '/', ':', '?', '[', '&', '=' И ';'. Возможно больше, что я не могу думать о макушке.

URI.escape оставляет эти символы URL в покое и пытается найти ключи строки запроса и значения, которые необходимо экранировать. Однако это действительно не может зависеть, так как значения могут иметь всевозможные символы, предотвращающие легкий выход. По сути, уже слишком поздно. Но если URL-адрес может быть простым (без '&' и '=' и т. Д. В значениях), эта функция может использоваться для экранирования, возможно, нечитаемых или недопустимых символов.

В общем, всегда используйте CGI.escape для отдельных ключей и значений, прежде чем объединять их с «&» и добавлять их после «?».

Жерар ОНЕЙЛ
источник
0

CGI.escape не работал с API OpenProject. Он закодировал [] ,: а не +. Я взломал это вместе, что, кажется, работает до сих пор для API OpenProject. Но я уверен, что в нем отсутствуют некоторые .gsub. Вероятно, он почти такой же плохой, как URI.escape, но он не даст вам устаревших ошибок.

class XXX
      def self.encode(path)
        path, query = path.split("?", 2)
        return path if query.nil?
        query = CGI.escape(query).gsub("%3A", ":").gsub("%3D","=").gsub("%5B","[").gsub("%5D","]").gsub("%2C",",").gsub("+","%20")
        return [path,query].join("?")
      end
end

XXX.encode("http://test.com/some/path?query=[box: \"cart\"]")
URI.encode("http://test.com/some/path?query=[box: \"cart\"]")

Оба вывода:

=> " http://test.com/some/path?query=[box:%20%22cart%22] "
=> " http://test.com/some/path?query=[box:%20 % 22cart% 22] "

Brett
источник