ruby-net-text 0.0.1 → 0.0.2
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.
- checksums.yaml +4 -4
- data/lib/net/gemini.rb +18 -51
- data/lib/net/gemini/request.rb +51 -0
- data/lib/net/gemini/response.rb +6 -12
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f3d4fbd93c189d283b253e0f0a520bdaee167f09281d3c6a25c95126c07ecf9
|
4
|
+
data.tar.gz: 209152612f6f0dad44282fe17dc02d34fc6457ccc0f01bb993374ceef9491f33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6826cd8ee993dabd7210a329b4349fb059c5007ff1560914d09049dfa7276ff1590d9f5405c31a99c70202511502d30c1d68d9b66643ddbf881774e161cdac3
|
7
|
+
data.tar.gz: 7f756ac8389178ae82a8c11b0a8e3e7491d096cab8dfbd42d55345e277680656ccb64da1fb69e119417fb2de68b75a2740b7fbd60f492ef2c44df31668adcd69
|
data/lib/net/gemini.rb
CHANGED
@@ -7,7 +7,9 @@ require 'openssl'
|
|
7
7
|
require 'fileutils'
|
8
8
|
|
9
9
|
require 'uri/gemini'
|
10
|
+
require_relative 'gemini/request'
|
10
11
|
require_relative 'gemini/response'
|
12
|
+
require_relative 'gemini/ssl'
|
11
13
|
|
12
14
|
module Net
|
13
15
|
class GeminiError < StandardError; end
|
@@ -98,10 +100,11 @@ module Net
|
|
98
100
|
|
99
101
|
def request(uri)
|
100
102
|
init_sockets
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
103
|
+
req = GeminiRequest.new uri
|
104
|
+
req.write @ssl_socket
|
105
|
+
res = GeminiResponse.read_new(@ssl_socket)
|
106
|
+
res.uri = uri
|
107
|
+
res.reading_body(@ssl_socket)
|
105
108
|
ensure
|
106
109
|
# Stop remaining connection, even if they should be already cut
|
107
110
|
# by the server
|
@@ -112,14 +115,11 @@ module Net
|
|
112
115
|
raise GeminiError, 'Too many Gemini redirects' if limit.zero?
|
113
116
|
r = request(uri)
|
114
117
|
return r unless r.status[0] == '3'
|
115
|
-
old_url = uri.to_s
|
116
118
|
begin
|
117
|
-
|
118
|
-
uri.merge!(new_uri)
|
119
|
+
uri = handle_redirect(r)
|
119
120
|
rescue ArgumentError, URI::InvalidURIError
|
120
121
|
return r
|
121
122
|
end
|
122
|
-
raise GeminiError, "Redirect loop on #{uri}" if uri.to_s == old_url
|
123
123
|
warn "Redirect to #{uri}" if $VERBOSE
|
124
124
|
fetch(uri, limit - 1)
|
125
125
|
end
|
@@ -148,50 +148,17 @@ module Net
|
|
148
148
|
|
149
149
|
private
|
150
150
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
def ssl_verify_cb(cert)
|
161
|
-
domain = cert.subject.to_s.sub(/^\/CN=/, '')
|
162
|
-
return false if domain != @host
|
163
|
-
cert_file = File.expand_path("#{@certs_path}/#{domain}.pem")
|
164
|
-
return ssl_check_existing(cert, cert_file) if File.exist?(cert_file)
|
165
|
-
FileUtils.mkdir_p(File.expand_path(@certs_path))
|
166
|
-
File.open(cert_file, 'wb') { |f| f.print cert.to_pem }
|
167
|
-
true
|
168
|
-
end
|
169
|
-
|
170
|
-
def ssl_context
|
171
|
-
ssl_context = OpenSSL::SSL::SSLContext.new
|
172
|
-
ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
173
|
-
ssl_context.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
174
|
-
ssl_context.verify_hostname = true
|
175
|
-
ssl_context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
|
176
|
-
ssl_context.verify_callback = lambda do |preverify_ok, store_context|
|
177
|
-
return true if preverify_ok
|
178
|
-
ssl_verify_cb store_context.current_cert
|
179
|
-
end
|
180
|
-
ssl_context
|
181
|
-
end
|
182
|
-
|
183
|
-
def init_sockets
|
184
|
-
@socket = TCPSocket.new(@host, @port)
|
185
|
-
@ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, ssl_context)
|
186
|
-
# Close underlying TCP socket with SSL socket
|
187
|
-
@ssl_socket.sync_close = true
|
188
|
-
@ssl_socket.hostname = @host # SNI
|
189
|
-
@ssl_socket.connect
|
151
|
+
def handle_redirect(response)
|
152
|
+
uri = response.uri
|
153
|
+
old_url = uri.to_s
|
154
|
+
new_uri = URI(response.meta)
|
155
|
+
uri.merge!(new_uri)
|
156
|
+
raise GeminiError, "Redirect loop on #{uri}" if uri.to_s == old_url
|
157
|
+
@host = uri.host
|
158
|
+
@port = uri.port
|
159
|
+
uri
|
190
160
|
end
|
191
161
|
|
192
|
-
|
193
|
-
def finish
|
194
|
-
@ssl_socket.close
|
195
|
-
end
|
162
|
+
include ::Gemini::SSL
|
196
163
|
end
|
197
164
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
require 'uri/gemini'
|
6
|
+
|
7
|
+
module Net
|
8
|
+
class GeminiBadRequest < StandardError; end
|
9
|
+
|
10
|
+
#
|
11
|
+
# The syntax of Gemini Requests are defined in the Gemini
|
12
|
+
# specification, section 2.
|
13
|
+
#
|
14
|
+
# @see https://gemini.circumlunar.space/docs/specification.html
|
15
|
+
#
|
16
|
+
class GeminiRequest
|
17
|
+
attr_reader :uri
|
18
|
+
|
19
|
+
def initialize(uri_or_str)
|
20
|
+
# In any case, make some sanity check over this uri-like think
|
21
|
+
url = uri_or_str.to_s
|
22
|
+
if url.length > 1024
|
23
|
+
raise GeminiBadRequest, "Request too long: #{url.dump}"
|
24
|
+
end
|
25
|
+
@uri = URI(url)
|
26
|
+
unless uri.is_a? URI::Gemini
|
27
|
+
raise GeminiBadRequest, "Not a Gemini URI: #{url.dump}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def path
|
32
|
+
@uri.path
|
33
|
+
end
|
34
|
+
|
35
|
+
def write(sock)
|
36
|
+
sock.puts "#{@uri}\r\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def read_new(sock)
|
41
|
+
# Read up to 1026 bytes:
|
42
|
+
# - 1024 bytes max for the URL
|
43
|
+
# - 2 bytes for <CR><LF>
|
44
|
+
str = sock.gets($INPUT_RECORD_SEPARATOR, 1026)
|
45
|
+
m = /\A(.*)\r\n\z/.match(str)
|
46
|
+
raise GeminiBadRequest, "Malformed request: #{str.dump}" if m.nil?
|
47
|
+
new(m[1])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/net/gemini/response.rb
CHANGED
@@ -11,9 +11,9 @@ module Net
|
|
11
11
|
|
12
12
|
#
|
13
13
|
# The syntax of Gemini Responses are defined in the Gemini
|
14
|
-
#
|
14
|
+
# specification, section 3.
|
15
15
|
#
|
16
|
-
#
|
16
|
+
# @see https://gemini.circumlunar.space/docs/specification.html
|
17
17
|
#
|
18
18
|
class GeminiResponse
|
19
19
|
# The Gemini response <STATUS> string.
|
@@ -73,20 +73,14 @@ module Net
|
|
73
73
|
|
74
74
|
class << self
|
75
75
|
def read_new(sock)
|
76
|
-
|
77
|
-
new(code, msg)
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
|
82
|
-
def read_status_line(sock)
|
83
|
-
# Read up to 1027 bytes:
|
76
|
+
# Read up to 1029 bytes:
|
84
77
|
# - 3 bytes for code and space separator
|
85
78
|
# - 1024 bytes max for the message
|
86
|
-
|
79
|
+
# - 2 bytes for <CR><LF>
|
80
|
+
str = sock.gets($INPUT_RECORD_SEPARATOR, 1029)
|
87
81
|
m = /\A([1-6]\d) (.*)\r\n\z/.match(str)
|
88
82
|
raise GeminiBadResponse, "wrong status line: #{str.dump}" if m.nil?
|
89
|
-
m.captures
|
83
|
+
new(*m.captures)
|
90
84
|
end
|
91
85
|
end
|
92
86
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-net-text
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Étienne Deparis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: etienne@depar.is
|
@@ -18,6 +18,7 @@ extra_rdoc_files: []
|
|
18
18
|
files:
|
19
19
|
- LICENSE
|
20
20
|
- lib/net/gemini.rb
|
21
|
+
- lib/net/gemini/request.rb
|
21
22
|
- lib/net/gemini/response.rb
|
22
23
|
- lib/uri/finger.rb
|
23
24
|
- lib/uri/gemini.rb
|