ruby-net-text 0.0.4 → 0.0.5
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/ssl.rb +53 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf06a0fb93555a7705a8ab92e7380dcc98134721b24a1f578251667b0a7a973a
|
4
|
+
data.tar.gz: c96bc45c9d21310f93a2b9f77ed6194d1539741b58e9ba1d713f3e68779e7505
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 586165ce218f116cb6a2214a4deff76fc250a241b8ab2a92e91a6caa42d8202a38b1b2262b583a7444a1d260023716b4527191de800f90ea83ff226d6821284a
|
7
|
+
data.tar.gz: 982663ef31a62ddca0941eef34b8e227670d97b0c6674116a5b5927f5547f8e1c6e2d6df23b972cece6a9ea07263e543c5f806e0c0bc0488b7bb52f4df80b84e
|
@@ -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
|
@@ -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
|
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.5
|
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: 2021-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: etienne@depar.is
|
@@ -18,8 +18,11 @@ 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
|
@@ -42,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
45
|
- !ruby/object:Gem::Version
|
43
46
|
version: '0'
|
44
47
|
requirements: []
|
45
|
-
rubygems_version: 3.
|
48
|
+
rubygems_version: 3.2.15
|
46
49
|
signing_key:
|
47
50
|
specification_version: 4
|
48
51
|
summary: Gemini, Gopher, and Finger support for Net::* and URI::*
|