ruby-net-text 0.0.3 → 0.0.7

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: cc4467da1a2124222650203e4d1f3bcbf377b2c8178d6d7a9690c1fb0003ac8a
4
- data.tar.gz: a04be63fe05981caf42cd4c64f615c06dfd9ee6352b464fa80fadb7dd87b3ecb
3
+ metadata.gz: de26ac5c1f8127c9d08a5430adc0cff34d266847327c6864baa903b5352ac041
4
+ data.tar.gz: d2725dab71f60b4d12be9ff0d948ae1a3f1bc6de9858499258aeedc11c801ae0
5
5
  SHA512:
6
- metadata.gz: ef9cab28b37134208fc2e24563e8f5138ca118d1df47c76e2ac8272dc688ee3f19f5ad6f343a1e08fc3f1359742f999d9216a802ce08d4b4acd213c906bcd5dc
7
- data.tar.gz: d142b3651f551721b022746e32c8aee4355075dd0bbd8cdb5e897444d42d695b8576561e6068547e85b435d738f14b449c28c0991c7719d0c0890c4d18ace61f
6
+ metadata.gz: 79defc399e2b1e1631e153c5994ef1547d0eb9732f0153a641ba9dbf98d35ea03c176723b7bb7052ac08a61b2069eac37e158d04e213dd4c778ecb0c39af00b4
7
+ data.tar.gz: 050fecb1c2fda499404207e5cb3d49048ff5fcb9695497f35b257dc40c02e3e71ba648ffe575e044a57e282ac43f62590d147c65404005427ae8bfeeb66848d9
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Gemini, Gopher, and Finger support for Net::* and URI::*
2
+
3
+ [![Support using Liberapay](https://img.shields.io/badge/Liberapay-Support_me-yellow?logo=liberapay)](https://liberapay.com/milouse/donate)
4
+ [![Support using Flattr](https://img.shields.io/badge/Flattr-Support_me-brightgreen?logo=flattr)](https://flattr.com/@milouse)
5
+ [![Support using Paypal](https://img.shields.io/badge/Paypal-Support_me-00457C?logo=paypal&labelColor=lightgray)](https://paypal.me/milouse)
6
+
7
+ [![Gem](https://img.shields.io/gem/v/ruby-net-text)](https://rubygems.org/gems/ruby-net-text)
8
+ [![Documentation](https://img.shields.io/badge/Documentation-ruby--net--text-CC342D?logo=rubygems)](https://www.rubydoc.info/gems/ruby-net-text/Net/Gemini)
9
+
10
+ This project aims to add connectors to well known internet text protocols
11
+ through the standard `Net::*` and `URI::*` ruby module namespaces.
12
+
13
+ ## Documentation
14
+
15
+ The code is self-documented and you can browse it on rubydoc.info:
16
+
17
+ ### Gemini
18
+
19
+ - [URI::Gemini](https://www.rubydoc.info/gems/ruby-net-text/URI/Gemini)
20
+ - [Net::Gemini](https://www.rubydoc.info/gems/ruby-net-text/Net/Gemini)
21
+
22
+ ### Gopher
23
+
24
+ - [URI::Gopher](https://www.rubydoc.info/gems/ruby-net-text/URI/Gopher)
25
+ - [Net::Gopher](https://www.rubydoc.info/gems/ruby-net-text/Net/Gopher)
26
+
27
+ ### Finger
28
+
29
+ - [URI::Finger](https://www.rubydoc.info/gems/ruby-net-text/URI/Finger)
30
+ - [Net::Finger](https://www.rubydoc.info/gems/ruby-net-text/Net/Finger)
31
+
32
+ ## Helpers
33
+
34
+ This repository also includes 2 little helpers:
35
+
36
+ - `bin/heraut`: a toy client for Gemini, Gopher and Finger. Give it a URI and
37
+ it will output the remote file.
38
+ - `bin/test_thread.rb`: a toy performance test script to run against a Gemini
39
+ server
data/lib/net/finger.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../uri/finger'
4
+ require_relative 'generic'
5
+
6
+ module Net
7
+ # == A Finger client API for Ruby.
8
+ #
9
+ # Net::Finger provides a library which can be used to build Finger
10
+ # user-agents.
11
+ #
12
+ # Net::Finger is designed to work closely with URI.
13
+ #
14
+ # == Simple Examples
15
+ #
16
+ # All examples assume you have loaded Net::Finger with:
17
+ #
18
+ # require 'net/finger'
19
+ #
20
+ # This will also require 'uri' so you don't need to require it
21
+ # separately.
22
+ #
23
+ # The Net::Finger methods in the following section do not persist
24
+ # connections.
25
+ #
26
+ # === GET by URI
27
+ #
28
+ # uri = URI('finger://skyjake.fi/jaakko')
29
+ # Net::Finger.get(uri) # => String
30
+ #
31
+ class Finger
32
+ extend TextGeneric
33
+
34
+ def self.get(string_or_uri)
35
+ uri = build_uri string_or_uri, URI::Finger
36
+ request uri, uri.name.to_s
37
+ end
38
+ end
39
+ end
@@ -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
@@ -42,7 +42,7 @@ module Net
42
42
  # - 2 bytes for <CR><LF>
43
43
  str = sock.gets($INPUT_RECORD_SEPARATOR, 1026)
44
44
  m = /\A(.*)\r\n\z/.match(str)
45
- raise GeminiBadRequest, "Malformed request: #{str.dump}" if m.nil?
45
+ raise GeminiBadRequest, "Malformed request: #{str&.dump}" if m.nil?
46
46
  new(m[1])
47
47
  end
48
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.
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ # Generic interface to be used by specific network protocol implementation.
6
+ module TextGeneric
7
+ private
8
+
9
+ def build_uri(string_or_uri, uri_class)
10
+ string_or_uri = URI(string_or_uri) if string_or_uri.is_a?(String)
11
+ return string_or_uri if string_or_uri.is_a?(uri_class)
12
+ raise ArgumentError, "uri is not a String, nor an #{uri_class.name}"
13
+ end
14
+
15
+ def request(uri, query)
16
+ sock = TCPSocket.new(uri.host, uri.port)
17
+ sock.puts query
18
+ sock.read
19
+ ensure
20
+ # Stop remaining connection, even if they should be already cut
21
+ # by the server
22
+ sock&.close
23
+ end
24
+ end
data/lib/net/gopher.rb ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../uri/gopher'
4
+ require_relative 'generic'
5
+
6
+ module Net
7
+ # == A Gopher client API for Ruby.
8
+ #
9
+ # Net::Gopher provides a library which can be used to build Gopher
10
+ # user-agents.
11
+ #
12
+ # Net::Gopher is designed to work closely with URI.
13
+ #
14
+ # == Simple Examples
15
+ #
16
+ # All examples assume you have loaded Net::Gopher with:
17
+ #
18
+ # require 'net/gopher'
19
+ #
20
+ # This will also require 'uri' so you don't need to require it
21
+ # separately.
22
+ #
23
+ # The Net::Gopher methods in the following section do not persist
24
+ # connections.
25
+ #
26
+ # === GET by URI
27
+ #
28
+ # uri = URI('gopher://thelambdalab.xyz/1/projects/elpher/')
29
+ # Net::Gopher.get(uri) # => String
30
+ #
31
+ class Gopher
32
+ extend TextGeneric
33
+
34
+ def self.get(string_or_uri)
35
+ uri = build_uri string_or_uri, URI::Gopher
36
+ request uri, "#{uri.selector}\r\n"
37
+ end
38
+ end
39
+ end
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.3
4
+ version: 0.0.7
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-12-12 00:00:00.000000000 Z
11
+ date: 2022-02-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: etienne@depar.is
@@ -17,16 +17,27 @@ extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
19
  - LICENSE
20
+ - README.md
21
+ - lib/net/finger.rb
20
22
  - lib/net/gemini.rb
23
+ - lib/net/gemini/gmi_parser.rb
24
+ - lib/net/gemini/reflow_text.rb
21
25
  - lib/net/gemini/request.rb
22
26
  - lib/net/gemini/response.rb
27
+ - lib/net/gemini/ssl.rb
28
+ - lib/net/generic.rb
29
+ - lib/net/gopher.rb
23
30
  - lib/uri/finger.rb
24
31
  - lib/uri/gemini.rb
25
32
  - lib/uri/gopher.rb
26
33
  homepage: https://git.umaneti.net/ruby-net-text/
27
34
  licenses:
28
35
  - MIT
29
- metadata: {}
36
+ metadata:
37
+ rubygems_mfa_required: 'true'
38
+ source_code_uri: https://git.umaneti.net/ruby-net-text/
39
+ documentation_uri: https://www.rubydoc.info/gems/ruby-net-text
40
+ funding_uri: https://liberapay.com/milouse
30
41
  post_install_message:
31
42
  rdoc_options: []
32
43
  require_paths:
@@ -42,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
53
  - !ruby/object:Gem::Version
43
54
  version: '0'
44
55
  requirements: []
45
- rubygems_version: 3.1.4
56
+ rubygems_version: 3.3.7
46
57
  signing_key:
47
58
  specification_version: 4
48
59
  summary: Gemini, Gopher, and Finger support for Net::* and URI::*