# frozen_string_literal: true# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com># # Permission is hereby granted, free of charge, to any person obtaining a copy# of this software and associated documentation files (the "Software"), to deal# in the Software without restriction, including without limitation the rights# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell# copies of the Software, and to permit persons to whom the Software is# furnished to do so, subject to the following conditions:# # The above copyright notice and this permission notice shall be included in# all copies or substantial portions of the Software.# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN# THE SOFTWARE.moduleProtocolmoduleHTTPmoduleURL# Escapes a generic string, using percent encoding.defself.escape(string,encoding=string.encoding)string.b.gsub(/([^a-zA-Z0-9_.\-]+)/)do|m|'%'+m.unpack('H2'*m.bytesize).join('%').upcaseend.force_encoding(encoding)enddefself.unescape(string,encoding=string.encoding)string.b.gsub(/%(\h\h)/)do|hex|Integer(hex,16).chrend.force_encoding(encoding)end# According to https://tools.ietf.org/html/rfc3986#section-3.3, we escape non-pchar.NON_PCHAR=/([^a-zA-Z0-9_\-\.~!$&'()*+,;=:@\/]+)/.freeze# Escapes a pathdefself.escape_path(path)encoding=path.encodingpath.b.gsub(NON_PCHAR)do|m|'%'+m.unpack('H2'*m.bytesize).join('%').upcaseend.force_encoding(encoding)end# Encodes a hash or array into a query stringdefself.encode(value,prefix=nil)casevaluewhenArrayreturnvalue.map{|v|self.encode(v,"#{prefix}[]")}.join("&")whenHashreturnvalue.map{|k,v|self.encode(v,prefix?"#{prefix}[#{escape(k.to_s)}]":escape(k.to_s))}.reject(&:empty?).join('&')whennilreturnprefixelseraiseArgumentError,"value must be a Hash"ifprefix.nil?return"#{prefix}=#{escape(value.to_s)}"endenddefself.scan(string)# TODO Ruby 2.6 doesn't require `.each`string.split('&').eachdo|assignment|key,value=assignment.split('=',2)yieldunescape(key),unescape(value)endenddefself.split(name)name.scan(/([^\[]+)|(?:\[(.*?)\])/).flatten!.compact!enddefself.assign(keys,value,parent)top,*middle=keysmiddle.each_with_indexdo|key,index|ifkey.nil?orkey.empty?parent=(parent[top]||=Array.new)top=parent.sizeifnested=middle[index+1]andlast=parent.lasttop-=1unlesslast.include?(nested)endelseparent=(parent[top]||=Hash.new)top=keyendendparent[top]=valueend# TODO use native C extension from `Trenni::Reference`.defself.decode(string,maximum=8,symbolize_keys: false)parameters={}self.scan(string)do|name,value|keys=self.split(name)ifkeys.size>maximumraiseArgumentError,"Key length exceeded limit!"endifsymbolize_keyskeys.collect!{|key|key.empty??nil:key.to_sym}endself.assign(keys,value,parameters)endreturnparametersendendendend