module ZMachine module HttpEncoding HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n" FIELD_ENCODING = "%s: %s\r\n" def escape(s) if defined?(EscapeUtils) EscapeUtils.escape_url(s.to_s) else s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) { '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase } end end def unescape(s) if defined?(EscapeUtils) EscapeUtils.unescape_url(s.to_s) else s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) { [$1.delete('%')].pack('H*') } end end if ''.respond_to?(:bytesize) def bytesize(string) string.bytesize end else def bytesize(string) string.size end end # Map all header keys to a downcased string version def munge_header_keys(head) head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h } end def encode_host if @req.uri.port == 80 || @req.uri.port == 443 return @req.uri.host else @req.uri.host + ":#{@req.uri.port}" end end def encode_request(method, uri, query, proxy) query = encode_query(uri, query) # Non CONNECT proxies require that you provide the full request # uri in request header, as opposed to a relative path. query = uri.join(query) if proxy HTTP_REQUEST_HEADER % [method.to_s.upcase, query] end def encode_query(uri, query) encoded_query = if query.kind_of?(Hash) query.map { |k, v| encode_param(k, v) }.join('&') else query.to_s end if uri && !uri.query.to_s.empty? encoded_query = [encoded_query, uri.query].reject {|part| part.empty?}.join("&") end encoded_query.to_s.empty? ? uri.path : "#{uri.path}?#{encoded_query}" end # URL encodes query parameters: # single k=v, or a URL encoded array, if v is an array of values def encode_param(k, v) if v.is_a?(Array) v.map { |e| escape(k) + "[]=" + escape(e) }.join("&") else escape(k) + "=" + escape(v) end end def form_encode_body(obj) pairs = [] recursive = Proc.new do |h, prefix| h.each do |k,v| key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]" if v.is_a? Array nh = Hash.new v.size.times { |t| nh[t] = v[t] } recursive.call(nh, key) elsif v.is_a? Hash recursive.call(v, key) else pairs << "#{key}=#{escape(v)}" end end end recursive.call(obj, '') return pairs.join('&') end # Encode a field in an HTTP header def encode_field(k, v) FIELD_ENCODING % [k, v] end # Encode basic auth in an HTTP header # In: Array ([user, pass]) - for basic auth # String - custom auth string (OAuth, etc) def encode_auth(k,v) if v.is_a? Array FIELD_ENCODING % [k, ["Basic", Base64.encode64(v.join(":")).split.join].join(" ")] else encode_field(k,v) end end def encode_headers(head) head.inject('') do |result, (key, value)| # Munge keys from foo-bar-baz to Foo-Bar-Baz key = key.split('-').map { |k| k.to_s.capitalize }.join('-') result << case key when 'Authorization', 'Proxy-Authorization' encode_auth(key, value) else encode_field(key, value) end end end def encode_cookie(cookie) if cookie.is_a? Hash cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" } else cookie end end end end