ruby-net-text 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|