/
utils.rb
142 lines (130 loc) · 4.18 KB
/
utils.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# frozen_string_literal: true
module Excon
module Utils
extend self
CONTROL = (0x0..0x1f).map {|c| c.chr }.join + "\x7f"
DELIMS = '<>#%"'
UNWISE = '{}|\\^[]`'
NONASCII = (0x80..0xff).map {|c| c.chr }.join
UNESCAPED = /([#{ Regexp.escape(CONTROL + ' ' + DELIMS + UNWISE + NONASCII) }])/
ESCAPED = /%([0-9a-fA-F]{2})/
def binary_encode(string)
if FORCE_ENC && string.encoding != Encoding::ASCII_8BIT
if string.frozen?
string.dup.force_encoding('BINARY')
else
string.force_encoding('BINARY')
end
else
string
end
end
def connection_uri(datum = @data)
unless datum
raise ArgumentError, '`datum` must be given unless called on a Connection'
end
if datum[:scheme] == UNIX
"#{datum[:scheme]}://#{datum[:socket]}"
else
"#{datum[:scheme]}://#{datum[:host]}#{port_string(datum)}"
end
end
# Redact sensitive info from provided data
def redact(datum)
datum = datum.dup
if datum.has_key?(:headers)
if datum[:headers].has_key?('Authorization') || datum[:headers].has_key?('Proxy-Authorization')
datum[:headers] = datum[:headers].dup
end
if datum[:headers].has_key?('Authorization')
datum[:headers]['Authorization'] = REDACTED
end
if datum[:headers].has_key?('Proxy-Authorization')
datum[:headers]['Proxy-Authorization'] = REDACTED
end
end
if datum.has_key?(:password)
datum[:password] = REDACTED
end
if datum.has_key?(:proxy) && datum[:proxy].has_key?(:password)
datum[:proxy] = datum[:proxy].dup
datum[:proxy][:password] = REDACTED
end
datum
end
def request_uri(datum)
connection_uri(datum) + datum[:path] + query_string(datum)
end
def port_string(datum)
if datum[:port].nil? || (datum[:omit_default_port] && ((datum[:scheme].casecmp('http') == 0 && datum[:port] == 80) || (datum[:scheme].casecmp('https') == 0 && datum[:port] == 443)))
''
else
':' + datum[:port].to_s
end
end
def query_string(datum)
str = String.new
case datum[:query]
when String
str << '?' << datum[:query]
when Hash
str << '?'
datum[:query].sort_by {|k,_| k.to_s }.each do |key, values|
key = CGI.escape(key.to_s)
if values.nil?
str << key << '&'
else
[values].flatten.each do |value|
str << key << '=' << CGI.escape(value.to_s) << '&'
end
end
end
str.chop! # remove trailing '&'
end
str
end
# Splits a header value +str+ according to HTTP specification.
def split_header_value(str)
return [] if str.nil?
str = str.dup.strip
str = binary_encode(str)
str.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",])+)
(?:,\s*|\Z)'xn).flatten
end
# Escapes HTTP reserved and unwise characters in +str+
def escape_uri(str)
str = str.dup
str = binary_encode(str)
str.gsub(UNESCAPED) { "%%%02X" % $1[0].ord }
end
# Unescapes HTTP reserved and unwise characters in +str+
def unescape_uri(str)
str = str.dup
str = binary_encode(str)
str.gsub(ESCAPED) { $1.hex.chr }
end
# Unescape form encoded values in +str+
def unescape_form(str)
str = str.dup
str = binary_encode(str)
str.gsub!(/\+/, ' ')
str.gsub(ESCAPED) { $1.hex.chr }
end
# Performs validation on the passed header hash and returns a string representation of the headers
def headers_hash_to_s(headers)
headers_str = String.new
headers.each do |key, values|
if key.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderKey.new(key.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
[values].flatten.each do |value|
if value.to_s.match(/[\r\n]/)
raise Excon::Errors::InvalidHeaderValue.new(value.to_s.inspect + ' contains forbidden "\r" or "\n"')
end
headers_str << key.to_s << ': ' << value.to_s << CR_NL
end
end
headers_str
end
end
end