ruby-net-text 0.0.2 → 0.0.6
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/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::*
|