mt-uv-rays 2.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ipaddress' # IP Address parser
4
+
5
+ module MTUV
6
+ def self.try_connect(tcp, handler, server, port)
7
+ if IPAddress.valid? server
8
+ tcp.finally { handler.on_close }
9
+ tcp.progress { |*data| handler.on_read(*data) }
10
+ tcp.connect server, port do
11
+ tcp.enable_nodelay
12
+ tcp.start_tls(handler.using_tls) if handler.using_tls
13
+
14
+ # on_connect could call use_tls so must come after start_tls
15
+ handler.on_connect(tcp)
16
+ tcp.start_read
17
+ end
18
+ else
19
+ tcp.reactor.lookup(server, wait: false).then(
20
+ proc { |result|
21
+ MTUV.try_connect(tcp, handler, result[0][0], port)
22
+ },
23
+ proc { |failure|
24
+ # TODO:: Log error on reactor
25
+ handler.on_close
26
+ }
27
+ )
28
+ end
29
+ end
30
+
31
+
32
+ # @abstract
33
+ class Connection
34
+ attr_reader :using_tls
35
+
36
+ def initialize
37
+ @send_queue = []
38
+ @paused = false
39
+ @using_tls = false
40
+ end
41
+
42
+ def pause
43
+ @paused = true
44
+ @transport.stop_read
45
+ end
46
+
47
+ def paused?
48
+ @paused
49
+ end
50
+
51
+ def resume
52
+ @paused = false
53
+ @transport.start_read
54
+ end
55
+
56
+ # Compatible with TCP
57
+ def close_connection(*args)
58
+ @transport.close
59
+ end
60
+
61
+ def on_read(data, *args) # user to define
62
+ end
63
+
64
+ def post_init(*args)
65
+ end
66
+ end
67
+
68
+ class TcpConnection < Connection
69
+ def write(data)
70
+ @transport.write(data, wait: :promise)
71
+ end
72
+
73
+ def close_connection(after_writing = false)
74
+ if after_writing
75
+ @transport.shutdown
76
+ else
77
+ @transport.close
78
+ end
79
+ end
80
+
81
+ def stream_file(filename, type = :raw)
82
+ file = @reactor.file(filename, File::RDONLY) do # File is open and available for reading
83
+ file.send_file(@transport, type, wait: :promise).finally do
84
+ file.close
85
+ end
86
+ end
87
+ return file
88
+ end
89
+
90
+ def keepalive(raw_time)
91
+ time = raw_time.to_i
92
+ if time.to_i <= 0
93
+ @transport.disable_keepalive
94
+ else
95
+ @transport.enable_keepalive(time)
96
+ end
97
+ end
98
+
99
+ def on_connect(transport) # user to define
100
+ end
101
+
102
+ def on_close # user to define
103
+ end
104
+ end
105
+
106
+ class InboundConnection < TcpConnection
107
+ def initialize(tcp)
108
+ super()
109
+
110
+ @reactor = tcp.reactor
111
+ @transport = tcp
112
+ @transport.finally { on_close }
113
+ @transport.progress { |*data| on_read(*data) }
114
+ end
115
+
116
+ def use_tls(args = {})
117
+ args[:server] = true
118
+
119
+ if @transport.connected
120
+ @transport.start_tls(args)
121
+ else
122
+ @using_tls = args
123
+ end
124
+ end
125
+ end
126
+
127
+ class OutboundConnection < TcpConnection
128
+
129
+ def initialize(server, port)
130
+ super()
131
+
132
+ @reactor = reactor
133
+ @server = server
134
+ @port = port
135
+ @transport = @reactor.tcp
136
+
137
+ ::MTUV.try_connect(@transport, self, @server, @port)
138
+ end
139
+
140
+ def use_tls(args = {})
141
+ args.delete(:server)
142
+
143
+ if @transport.connected
144
+ @transport.start_tls(args)
145
+ else
146
+ @using_tls = args
147
+ end
148
+ end
149
+
150
+ def reconnect(server = nil, port = nil)
151
+ @reactor = reactor
152
+
153
+ @transport = @reactor.tcp
154
+ @server = server || @server
155
+ @port = port || @port
156
+
157
+ ::MTUV.try_connect(@transport, self, @server, @port)
158
+ end
159
+ end
160
+
161
+ class DatagramConnection < Connection
162
+ def initialize(server = nil, port = nil)
163
+ super()
164
+
165
+ @reactor = reactor
166
+ @transport = @reactor.udp
167
+ @transport.progress { |*args| on_read(*args) }
168
+
169
+ if not server.nil?
170
+ server = '127.0.0.1' if server == 'localhost'
171
+ raise ArgumentError, "Invalid server address #{server}" unless IPAddress.valid?(server)
172
+ @transport.bind(server, port)
173
+ end
174
+
175
+ @transport.start_read
176
+ end
177
+
178
+ def send_datagram(data, recipient_address, recipient_port)
179
+ if IPAddress.valid? recipient_address
180
+ @transport.send recipient_address, recipient_port, data
181
+ else
182
+ # Async DNS resolution
183
+ # Note:: send here will chain the promise
184
+ @reactor.lookup(recipient_address).then do |result|
185
+ @transport.send result[0][0], recipient_port, data
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,131 @@
1
+ # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ module MTUV
5
+ module Http
6
+ module Encoding
7
+ HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
8
+ FIELD_ENCODING = "%s: %s\r\n"
9
+
10
+ def escape(s)
11
+ if defined?(EscapeUtils)
12
+ EscapeUtils.escape_url(s.to_s)
13
+ else
14
+ s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/) {
15
+ '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
16
+ }
17
+ end
18
+ end
19
+
20
+ def unescape(s)
21
+ if defined?(EscapeUtils)
22
+ EscapeUtils.unescape_url(s.to_s)
23
+ else
24
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {
25
+ [$1.delete('%')].pack('H*')
26
+ }
27
+ end
28
+ end
29
+
30
+ if ''.respond_to?(:bytesize)
31
+ def bytesize(string)
32
+ string.bytesize
33
+ end
34
+ else
35
+ def bytesize(string)
36
+ string.size
37
+ end
38
+ end
39
+
40
+ # Map all header keys to a downcased string version
41
+ def munge_header_keys(head)
42
+ head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
43
+ end
44
+
45
+
46
+ def encode_request(method, uri, query)
47
+ query = encode_query(uri, query)
48
+ String.new(HTTP_REQUEST_HEADER % [method.to_s.upcase, query])
49
+ end
50
+
51
+ def encode_query(uri, query)
52
+ encoded_query = if query.kind_of?(Hash)
53
+ query.map { |k, v| encode_param(k, v) }.join('&')
54
+ else
55
+ query.to_s
56
+ end
57
+ encoded_query.to_s.empty? ? uri : "#{uri}?#{encoded_query}"
58
+ end
59
+
60
+ # URL encodes query parameters:
61
+ # single k=v, or a URL encoded array, if v is an array of values
62
+ def encode_param(k, v)
63
+ if v.is_a?(Array)
64
+ v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
65
+ else
66
+ escape(k) + "=" + escape(v)
67
+ end
68
+ end
69
+
70
+ def form_encode_body(obj)
71
+ pairs = []
72
+ recursive = Proc.new do |h, prefix|
73
+ h.each do |k,v|
74
+ key = prefix == '' ? escape(k) : "#{prefix}[#{escape(k)}]"
75
+
76
+ if v.is_a? Array
77
+ nh = Hash.new
78
+ v.size.times { |t| nh[t] = v[t] }
79
+ recursive.call(nh, key)
80
+
81
+ elsif v.is_a? Hash
82
+ recursive.call(v, key)
83
+ else
84
+ pairs << "#{key}=#{escape(v)}"
85
+ end
86
+ end
87
+ end
88
+
89
+ recursive.call(obj, '')
90
+ return pairs.join('&')
91
+ end
92
+
93
+ # Encode a field in an HTTP header
94
+ def encode_field(k, v)
95
+ FIELD_ENCODING % [k, v]
96
+ end
97
+
98
+ # Encode basic auth in an HTTP header
99
+ # In: Array ([user, pass]) - for basic auth
100
+ # String - custom auth string (OAuth, etc)
101
+ def encode_auth(k,v)
102
+ if v.is_a? Array
103
+ FIELD_ENCODING % [k, ["Basic", Base64.strict_encode64(v.join(":"))].join(" ")]
104
+ else
105
+ encode_field(k, v)
106
+ end
107
+ end
108
+
109
+ def encode_headers(head)
110
+ head.inject(String.new) do |result, (key, value)|
111
+ # Munge keys from foo-bar-baz to Foo-Bar-Baz
112
+ key = key.split('-').map { |k| k.to_s.capitalize }.join('-')
113
+ result << case key
114
+ when 'Authorization', 'Proxy-Authorization'
115
+ encode_auth(key, value)
116
+ else
117
+ encode_field(key, value)
118
+ end
119
+ end
120
+ end
121
+
122
+ def encode_cookie(cookie)
123
+ if cookie.is_a? Hash
124
+ cookie.inject(String.new) { |result, (k, v)| result << encode_param(k, v) + ';' }
125
+ else
126
+ cookie
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/hash_with_indifferent_access'
4
+
5
+ module MTUV
6
+ module Http
7
+ class Headers < ::ActiveSupport::HashWithIndifferentAccess
8
+ # The HTTP version returned
9
+ attr_accessor :http_version
10
+
11
+ # The status code (as an integer)
12
+ attr_accessor :status
13
+
14
+ # The text after the status code
15
+ attr_accessor :reason_phrase
16
+
17
+ # Cookies at the time of the request
18
+ attr_accessor :cookies
19
+
20
+ attr_accessor :keep_alive
21
+
22
+ attr_accessor :body
23
+
24
+ def to_s
25
+ "HTTP#{http_version} #{status} - keep alive: #{keep_alive}\nheaders: #{super}\nbody: #{body}"
26
+ end
27
+
28
+ alias_method :inspect, :to_s
29
+ end
30
+
31
+ class Parser
32
+ def initialize
33
+ @parser = ::HttpParser::Parser.new(self)
34
+ @state = ::HttpParser::Parser.new_instance
35
+ @state.type = :response
36
+ @headers = nil
37
+ end
38
+
39
+
40
+ attr_reader :request
41
+
42
+
43
+ def new_request(request)
44
+ @headers = nil
45
+ @request = request
46
+ @headers_complete = false
47
+ @state.reset!
48
+ end
49
+
50
+ def received(data)
51
+ if @parser.parse(@state, data)
52
+ if @request
53
+ @request.reject(@state.error)
54
+ @request = nil
55
+ @response = nil
56
+ return true
57
+ end
58
+ end
59
+
60
+ false
61
+ end
62
+
63
+ ##
64
+ # Parser Callbacks:
65
+ def on_message_begin(parser)
66
+ @headers = Headers.new
67
+ @body = String.new
68
+ @chunked = false
69
+ @close_connection = false
70
+ end
71
+
72
+ def on_status(parser, data)
73
+ @headers.reason_phrase = data
74
+
75
+ # Different HTTP versions have different defaults
76
+ if @state.http_minor == 0
77
+ @close_connection = true
78
+ else
79
+ @close_connection = false
80
+ end
81
+ end
82
+
83
+ def on_header_field(parser, data)
84
+ @header = data
85
+ end
86
+
87
+ def on_header_value(parser, data)
88
+ case @header
89
+ when 'Set-Cookie'
90
+ @request.set_cookie(data)
91
+
92
+ when 'Connection'
93
+ # Overwrite the default
94
+ @close_connection = data == 'close'
95
+
96
+ when 'Transfer-Encoding'
97
+ # If chunked we'll buffer streaming data for notification
98
+ @chunked = data == 'chunked'
99
+
100
+ end
101
+
102
+ # Duplicate headers we'll place into an array
103
+ current = @headers[@header]
104
+ if current
105
+ arr = @headers[@header] = Array(current)
106
+ arr << data
107
+ else
108
+ @headers[@header] = data
109
+ end
110
+ end
111
+
112
+ def on_headers_complete(parser)
113
+ @headers_complete = true
114
+
115
+ # https://github.com/joyent/http-parser indicates we should extract
116
+ # this information here
117
+ @headers.http_version = @state.http_version
118
+ @headers.status = @state.http_status
119
+ @headers.cookies = @request.cookies_hash
120
+ @headers.keep_alive = !@close_connection
121
+
122
+ # User code may throw an error
123
+ # Errors will halt the processing and return a PAUSED error
124
+ @request.set_headers(@headers)
125
+ end
126
+
127
+ def on_body(parser, data)
128
+ if @request.streaming?
129
+ @request.notify(data)
130
+ else
131
+ @body << data
132
+ end
133
+ end
134
+
135
+ def on_message_complete(parser)
136
+ @headers.body = @body
137
+
138
+ if @request.resolve(@headers)
139
+ cleanup
140
+ else
141
+ req = @request
142
+ cleanup
143
+ new_request(req)
144
+ end
145
+ end
146
+
147
+ # We need to flush the response on disconnect if content-length is undefined
148
+ # As per the HTTP spec
149
+ attr_accessor :reason
150
+ def eof
151
+ return if @request.nil?
152
+
153
+ if @headers_complete && (@headers['Content-Length'].nil? || @request.method == :head)
154
+ on_message_complete(nil)
155
+ else
156
+ # Reject if this is a partial response
157
+ @request.reject(@reason || :partial_response)
158
+ cleanup
159
+ end
160
+ end
161
+
162
+
163
+ private
164
+
165
+
166
+ def cleanup
167
+ @request = nil
168
+ @body = nil
169
+ @headers = nil
170
+ @reason = nil
171
+ @headers_complete = false
172
+ end
173
+ end
174
+ end
175
+ end