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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f3d4fbd93c189d283b253e0f0a520bdaee167f09281d3c6a25c95126c07ecf9
4
- data.tar.gz: 209152612f6f0dad44282fe17dc02d34fc6457ccc0f01bb993374ceef9491f33
3
+ metadata.gz: c547f919d21a6cb162b2e56553a4c7d7a935d8823036bbb97ae249417be0e1ae
4
+ data.tar.gz: 754907558f7a4db368b7f7cd7001bd5550dde2bb798804ee133dfee433945594
5
5
  SHA512:
6
- metadata.gz: e6826cd8ee993dabd7210a329b4349fb059c5007ff1560914d09049dfa7276ff1590d9f5405c31a99c70202511502d30c1d68d9b66643ddbf881774e161cdac3
7
- data.tar.gz: 7f756ac8389178ae82a8c11b0a8e3e7491d096cab8dfbd42d55345e277680656ccb64da1fb69e119417fb2de68b75a2740b7fbd60f492ef2c44df31668adcd69
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
@@ -23,9 +23,8 @@ module Net
23
23
  raise GeminiBadRequest, "Request too long: #{url.dump}"
24
24
  end
25
25
  @uri = URI(url)
26
- unless uri.is_a? URI::Gemini
27
- raise GeminiBadRequest, "Not a Gemini URI: #{url.dump}"
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.dump}" if m.nil?
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 rich library which can be used to build
20
- # Gemini user-agents.
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://example.com/index.html?count=10')
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://example.com/index.html')
45
- # params = { :limit => 10, :page => 3 }
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://exemple.com/home'))
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
- @@schemes['FINGER'] = Finger
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
- @@schemes['GEMINI'] = Gemini
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
- @@schemes['GOPHER'] = Gopher
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.2
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: 2020-11-17 00:00:00.000000000 Z
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.1.4
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::*