ruby-net-text 0.0.2 → 0.0.6
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/gmi_parser.rb +57 -0
- data/lib/net/gemini/reflow_text.rb +62 -0
- data/lib/net/gemini/request.rb +3 -4
- data/lib/net/gemini/ssl.rb +53 -0
- data/lib/net/gemini.rb +12 -7
- data/lib/uri/finger.rb +16 -2
- data/lib/uri/gemini.rb +6 -1
- data/lib/uri/gopher.rb +23 -1
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c547f919d21a6cb162b2e56553a4c7d7a935d8823036bbb97ae249417be0e1ae
|
4
|
+
data.tar.gz: 754907558f7a4db368b7f7cd7001bd5550dde2bb798804ee133dfee433945594
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '079107d052b63ac01d47b648966b875b7150d20db1787411f671ad5cf0fd911ded1924e9300885a0a17dea342d884482b2b4132c1b7ffdc9adb1417ecfead74a'
|
7
|
+
data.tar.gz: f2894b7e387968c2d0c3f1f7a00bceb18b78351f0aa392dc53b350badccb6e5a5c17ebfb150679537cb4be344b19ca98f1c9c9ce3ecaf6b4fea2146d761eb307
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gemini
|
4
|
+
# Contains specific method to parse text/gemini documents.
|
5
|
+
module GmiParser
|
6
|
+
private
|
7
|
+
|
8
|
+
def parse_meta
|
9
|
+
header = { status: @status, meta: @meta, mimetype: nil }
|
10
|
+
return header unless body_permitted?
|
11
|
+
mime = { lang: nil, charset: 'utf-8', format: nil }
|
12
|
+
raw_meta = meta.split(';').map(&:strip)
|
13
|
+
header[:mimetype] = raw_meta.shift
|
14
|
+
return header unless raw_meta.any?
|
15
|
+
raw_meta.map { |m| m.split('=') }.each do |opt|
|
16
|
+
key = opt[0].downcase.to_sym
|
17
|
+
next unless mime.has_key? key
|
18
|
+
mime[key] = opt[1].downcase
|
19
|
+
end
|
20
|
+
header.merge(mime)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_preformatted_block(line, buf)
|
24
|
+
cur_block = { meta: line[3..].chomp, content: '' }
|
25
|
+
while (line = buf.gets)
|
26
|
+
if line.start_with?('```')
|
27
|
+
@preformatted_blocks << cur_block
|
28
|
+
break
|
29
|
+
end
|
30
|
+
cur_block[:content] += line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_link(line)
|
35
|
+
m = line.strip.match(/\A=>\s*([^\s]+)(?:\s*(.+))?\z/)
|
36
|
+
return if m.nil?
|
37
|
+
begin
|
38
|
+
uri = URI(m[1])
|
39
|
+
rescue URI::InvalidURIError
|
40
|
+
return
|
41
|
+
end
|
42
|
+
uri = @uri.merge(uri) if @uri && uri.is_a?(URI::Generic)
|
43
|
+
@links << { uri: uri, label: m[2]&.chomp }
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse_body
|
47
|
+
buf = StringIO.new(@body)
|
48
|
+
while (line = buf.gets)
|
49
|
+
if line.start_with?('```')
|
50
|
+
parse_preformatted_block(line, buf)
|
51
|
+
elsif line.start_with?('=>')
|
52
|
+
parse_link(line)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gemini
|
4
|
+
# Contains specific method to correctly display Gemini texts
|
5
|
+
module ReflowText
|
6
|
+
private
|
7
|
+
|
8
|
+
def reflow_line_cut_index(line)
|
9
|
+
possible_cut = [
|
10
|
+
line.rindex(' ') || 0,
|
11
|
+
line.rindex('') || 0,
|
12
|
+
line.rindex('-') || 0
|
13
|
+
].sort
|
14
|
+
possible_cut.reverse!
|
15
|
+
possible_cut[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def reflow_line_prefix(line)
|
19
|
+
m = line.match(/\A([*#>]+ )/)
|
20
|
+
return '' unless m
|
21
|
+
# Each quote line should begin with the quote mark
|
22
|
+
return m[1] if m[1].start_with?('>')
|
23
|
+
' ' * m[1].length
|
24
|
+
end
|
25
|
+
|
26
|
+
def reflow_text_line(line, mono_block_open, length)
|
27
|
+
line.strip!
|
28
|
+
if mono_block_open || line.start_with?('=>') || line.length < length
|
29
|
+
return [line]
|
30
|
+
end
|
31
|
+
output = []
|
32
|
+
prefix = reflow_line_prefix(line)
|
33
|
+
while line.length > length
|
34
|
+
cut_line = line[0...length]
|
35
|
+
cut_index = reflow_line_cut_index(cut_line)
|
36
|
+
break if cut_index.zero? # Better do nothing for now
|
37
|
+
output << line[0...cut_index]
|
38
|
+
line = prefix + line[cut_index + 1..]
|
39
|
+
end
|
40
|
+
output << line
|
41
|
+
end
|
42
|
+
|
43
|
+
def reformat_body(length)
|
44
|
+
unless length.is_a? Integer
|
45
|
+
raise ArgumentError, "Length must be Integer, #{length} given"
|
46
|
+
end
|
47
|
+
return @body if length.zero?
|
48
|
+
new_body = []
|
49
|
+
mono_block_open = false
|
50
|
+
buf = StringIO.new(@body)
|
51
|
+
while (line = buf.gets)
|
52
|
+
if line.start_with?('```')
|
53
|
+
mono_block_open = !mono_block_open
|
54
|
+
# Don't include code block toggle lines
|
55
|
+
next
|
56
|
+
end
|
57
|
+
new_body += reflow_text_line(line, mono_block_open, length)
|
58
|
+
end
|
59
|
+
new_body.join("\n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/net/gemini/request.rb
CHANGED
@@ -23,9 +23,8 @@ module Net
|
|
23
23
|
raise GeminiBadRequest, "Request too long: #{url.dump}"
|
24
24
|
end
|
25
25
|
@uri = URI(url)
|
26
|
-
|
27
|
-
|
28
|
-
end
|
26
|
+
return if uri.is_a? URI::Gemini
|
27
|
+
raise GeminiBadRequest, "Not a Gemini URI: #{url.dump}"
|
29
28
|
end
|
30
29
|
|
31
30
|
def path
|
@@ -43,7 +42,7 @@ module Net
|
|
43
42
|
# - 2 bytes for <CR><LF>
|
44
43
|
str = sock.gets($INPUT_RECORD_SEPARATOR, 1026)
|
45
44
|
m = /\A(.*)\r\n\z/.match(str)
|
46
|
-
raise GeminiBadRequest, "Malformed request: #{str
|
45
|
+
raise GeminiBadRequest, "Malformed request: #{str&.dump}" if m.nil?
|
47
46
|
new(m[1])
|
48
47
|
end
|
49
48
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gemini
|
4
|
+
# Contains specific method to handle SSL connection
|
5
|
+
module SSL
|
6
|
+
private
|
7
|
+
|
8
|
+
def ssl_check_existing(new_cert, cert_file)
|
9
|
+
raw = File.read cert_file
|
10
|
+
saved_one = OpenSSL::X509::Certificate.new raw
|
11
|
+
return true if saved_one == new_cert
|
12
|
+
# TODO: offer some kind of recuperation
|
13
|
+
warn "#{cert_file} does not match the current host cert!"
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def ssl_verify_cb(cert)
|
18
|
+
return false unless OpenSSL::SSL.verify_certificate_identity(cert, @host)
|
19
|
+
cert_file = File.expand_path("#{@certs_path}/#{@host}.pem")
|
20
|
+
return ssl_check_existing(cert, cert_file) if File.exist?(cert_file)
|
21
|
+
FileUtils.mkdir_p(File.expand_path(@certs_path))
|
22
|
+
File.open(cert_file, 'wb') { |f| f.print cert.to_pem }
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def ssl_context
|
27
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
28
|
+
ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
29
|
+
ssl_context.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
30
|
+
ssl_context.verify_hostname = true
|
31
|
+
ssl_context.ca_file = '/etc/ssl/certs/ca-certificates.crt'
|
32
|
+
ssl_context.verify_callback = lambda do |preverify_ok, store_context|
|
33
|
+
return true if preverify_ok
|
34
|
+
ssl_verify_cb store_context.current_cert
|
35
|
+
end
|
36
|
+
ssl_context
|
37
|
+
end
|
38
|
+
|
39
|
+
def init_sockets
|
40
|
+
socket = TCPSocket.new(@host, @port)
|
41
|
+
@ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
42
|
+
# Close underlying TCP socket with SSL socket
|
43
|
+
@ssl_socket.sync_close = true
|
44
|
+
@ssl_socket.hostname = @host # SNI
|
45
|
+
@ssl_socket.connect
|
46
|
+
end
|
47
|
+
|
48
|
+
# Closes the SSL and TCP connections.
|
49
|
+
def finish
|
50
|
+
@ssl_socket.close
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/net/gemini.rb
CHANGED
@@ -16,8 +16,8 @@ module Net
|
|
16
16
|
|
17
17
|
# == A Gemini client API for Ruby.
|
18
18
|
#
|
19
|
-
# Net::Gemini provides a
|
20
|
-
#
|
19
|
+
# Net::Gemini provides a library which can be used to build Gemini
|
20
|
+
# user-agents.
|
21
21
|
# @see https://gemini.circumlunar.space/docs/specification.html
|
22
22
|
#
|
23
23
|
# Net::Gemini is designed to work closely with URI.
|
@@ -36,21 +36,20 @@ module Net
|
|
36
36
|
#
|
37
37
|
# === GET by URI
|
38
38
|
#
|
39
|
-
# uri = URI('gemini://
|
39
|
+
# uri = URI('gemini://gemini.circumlunar.space/')
|
40
40
|
# Net::Gemini.get(uri) # => String
|
41
41
|
#
|
42
42
|
# === GET with Dynamic Parameters
|
43
43
|
#
|
44
|
-
# uri = URI('gemini://
|
45
|
-
#
|
46
|
-
# uri.query = URI.encode_www_form(params)
|
44
|
+
# uri = URI('gemini://gus.guru/search')
|
45
|
+
# uri.query = URI.encode_www_form('test')
|
47
46
|
#
|
48
47
|
# res = Net::Gemini.get_response(uri)
|
49
48
|
# puts res.body if res.body_permitted?
|
50
49
|
#
|
51
50
|
# === Response Data
|
52
51
|
#
|
53
|
-
# res = Net::Gemini.get_response(URI('gemini://
|
52
|
+
# res = Net::Gemini.get_response(URI('gemini://gemini.circumlunar.space/'))
|
54
53
|
#
|
55
54
|
# # Status
|
56
55
|
# puts res.status # => '20'
|
@@ -105,6 +104,12 @@ module Net
|
|
105
104
|
res = GeminiResponse.read_new(@ssl_socket)
|
106
105
|
res.uri = uri
|
107
106
|
res.reading_body(@ssl_socket)
|
107
|
+
rescue OpenSSL::SSL::SSLError => e
|
108
|
+
msg = format(
|
109
|
+
'SSLError: %<cause>s',
|
110
|
+
cause: e.message.sub(/.*state=error: (.+)\Z/, '\1')
|
111
|
+
)
|
112
|
+
GeminiResponse.new('59', msg)
|
108
113
|
ensure
|
109
114
|
# Stop remaining connection, even if they should be already cut
|
110
115
|
# by the server
|
data/lib/uri/finger.rb
CHANGED
@@ -14,8 +14,22 @@ module URI # :nodoc:
|
|
14
14
|
DEFAULT_PORT = 79
|
15
15
|
|
16
16
|
# An Array of the available components for URI::Finger.
|
17
|
-
COMPONENT = [:scheme, :userinfo, :host, :port].freeze
|
17
|
+
COMPONENT = [:scheme, :userinfo, :host, :port, :path].freeze
|
18
|
+
|
19
|
+
def name
|
20
|
+
# Utilitary method to extract a name to query, either from the userinfo
|
21
|
+
# part or from the path.
|
22
|
+
return @user if @user
|
23
|
+
return '' unless @path
|
24
|
+
# Remove leading /
|
25
|
+
@path[1..] || ''
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
|
-
|
29
|
+
if respond_to? :register_scheme
|
30
|
+
# Introduced somewhere in ruby 3.0.x
|
31
|
+
register_scheme 'FINGER', Finger
|
32
|
+
else
|
33
|
+
@@schemes['FINGER'] = Finger
|
34
|
+
end
|
21
35
|
end
|
data/lib/uri/gemini.rb
CHANGED
@@ -18,5 +18,10 @@ module URI # :nodoc:
|
|
18
18
|
:path, :query, :fragment].freeze
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
if respond_to? :register_scheme
|
22
|
+
# Introduced somewhere in ruby 3.0.x
|
23
|
+
register_scheme 'GEMINI', Gemini
|
24
|
+
else
|
25
|
+
@@schemes['GEMINI'] = Gemini
|
26
|
+
end
|
22
27
|
end
|
data/lib/uri/gopher.rb
CHANGED
@@ -15,7 +15,29 @@ module URI # :nodoc:
|
|
15
15
|
|
16
16
|
# An Array of the available components for URI::Gopher.
|
17
17
|
COMPONENT = [:scheme, :host, :port, :path].freeze
|
18
|
+
|
19
|
+
def selector
|
20
|
+
return @selector if defined? @selector
|
21
|
+
@selector = extract_gopher_path_elements[1]
|
22
|
+
end
|
23
|
+
|
24
|
+
def item_type
|
25
|
+
return @item_type if defined? @item_type
|
26
|
+
@item_type = extract_gopher_path_elements[0]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def extract_gopher_path_elements
|
32
|
+
m = /\A\/([0-9+dfghisA-LT])(\/.*)?\Z/.match(@path)
|
33
|
+
m.captures
|
34
|
+
end
|
18
35
|
end
|
19
36
|
|
20
|
-
|
37
|
+
if respond_to? :register_scheme
|
38
|
+
# Introduced somewhere in ruby 3.0.x
|
39
|
+
register_scheme 'GOPHER', Gopher
|
40
|
+
else
|
41
|
+
@@schemes['GOPHER'] = Gopher
|
42
|
+
end
|
21
43
|
end
|
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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Étienne Deparis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: etienne@depar.is
|
@@ -18,15 +18,21 @@ extra_rdoc_files: []
|
|
18
18
|
files:
|
19
19
|
- LICENSE
|
20
20
|
- lib/net/gemini.rb
|
21
|
+
- lib/net/gemini/gmi_parser.rb
|
22
|
+
- lib/net/gemini/reflow_text.rb
|
21
23
|
- lib/net/gemini/request.rb
|
22
24
|
- lib/net/gemini/response.rb
|
25
|
+
- lib/net/gemini/ssl.rb
|
23
26
|
- lib/uri/finger.rb
|
24
27
|
- lib/uri/gemini.rb
|
25
28
|
- lib/uri/gopher.rb
|
26
29
|
homepage: https://git.umaneti.net/ruby-net-text/
|
27
30
|
licenses:
|
28
31
|
- MIT
|
29
|
-
metadata:
|
32
|
+
metadata:
|
33
|
+
rubygems_mfa_required: 'true'
|
34
|
+
source_code_uri: https://git.umaneti.net/ruby-net-text
|
35
|
+
funding_uri: https://liberapay.com/milouse
|
30
36
|
post_install_message:
|
31
37
|
rdoc_options: []
|
32
38
|
require_paths:
|
@@ -42,7 +48,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
48
|
- !ruby/object:Gem::Version
|
43
49
|
version: '0'
|
44
50
|
requirements: []
|
45
|
-
rubygems_version: 3.
|
51
|
+
rubygems_version: 3.2.32
|
46
52
|
signing_key:
|
47
53
|
specification_version: 4
|
48
54
|
summary: Gemini, Gopher, and Finger support for Net::* and URI::*
|