opentofu 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1eeac402d86ae7cc5b5f2e9be3327a60f8f3132453854ea9ee5f477462e58fc7
4
+ data.tar.gz: 54f68d912adb5e14a39ddde842ad4acf5ff3664923815138e5a30fcc8ba87881
5
+ SHA512:
6
+ metadata.gz: c74b24eada5091a0d37eb1a97b871838744e2169b36974fed374900c0a64e73acafe019751341da153f6f5e59a0b6e48e2c4aa9b1674fa65aa3573a74be44528
7
+ data.tar.gz: 8c00e948a371088cd8e87698d3884fe55d7aa8b84ac65c5a5d64d6b0a53cbf505fed91010cd4d83e53f820a1b2a6dac46a9ad5947733546404e1d992d0301bf5
data/.rubocop.yml ADDED
@@ -0,0 +1,34 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Metrics/MethodLength:
5
+ Enabled: true
6
+ Max: 18
7
+ Exclude:
8
+ - "lib/net/tofu/response.rb"
9
+
10
+ Style/ClassVars:
11
+ Enabled: true
12
+ Exclude:
13
+ - "lib/net/tofu/request.rb"
14
+
15
+ Style/Semicolon:
16
+ Enabled: true
17
+ Exclude:
18
+ - "lib/net/tofu/response.rb"
19
+
20
+ Style/SingleLineMethods:
21
+ Enabled: true
22
+ Exclude:
23
+ - "lib/net/tofu/response.rb"
24
+
25
+ Style/StringLiterals:
26
+ Enabled: true
27
+ EnforcedStyle: double_quotes
28
+
29
+ Style/StringLiteralsInInterpolation:
30
+ Enabled: true
31
+ EnforcedStyle: double_quotes
32
+
33
+ Layout/LineLength:
34
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2023-07027
9
+
10
+ First release. Still lots of improvements to be made.
11
+
12
+ ### Added
13
+
14
+ - Custom error classes for the library.
15
+ - A Request model for handling client requests.
16
+ - Handles the URI parsing logic.
17
+ - Holds a Response type.
18
+ - Does some basic error checking based on the Gemini Protocol specification.
19
+ - A Response model for handling server responses.
20
+ - Holds a Socket type.
21
+ - Does some basic error checking based on the Gemini Protocol specification.
22
+ - A Socket model for handling raw socket connections, as well as TLS security settings.
23
+ - A top-level module for making get and get_response requests.
24
+ - Added a 'trust' parameter to the Net::Tofu.get and Net::Tofu.get_response methods. This will later be used for certificate management, but it isn't in use yet.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Rory Dudley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # OpenTOFU
2
+
3
+ OpenTOFU is a certificate pinning and client library for Geminispace.
4
+ It is based off of the ['trust on first use'](https://en.wikipedia.org/wiki/Trust_on_first_use) authentication scheme.
5
+
6
+ ## Installation
7
+
8
+ To install the gem standalone:
9
+
10
+ ```sh
11
+ gem install opentofu
12
+ ```
13
+
14
+ Or to use it as part of a project, place the following line in your Gemfile, then run `bundle install` in your project directory:
15
+
16
+ ```ruby
17
+ gem "opentofu", "~> 0.1.0"
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ require "net/tofu"
24
+ ```
25
+
26
+ ## Credits
27
+
28
+ I'd like to thank Étienne Deparis, author of [ruby-net-text](https://git.umaneti.net/ruby-net-text/) for releasing their code under the MIT license so that some of it can be used in this project. In particular, `lib/ui/gemini.rb` is taken straight from that codebase. The license for it is present in the aformentioned file.
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pinecat/opentofu.
39
+
40
+ ## License
41
+
42
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ module Tofu
5
+ class Response
6
+ # Raised when a server sends an invalid header.
7
+ class InvalidHeaderError < StandardError; end
8
+
9
+ # Raised when a server sends an invalid status code.
10
+ class InvalidStatusCodeError < StandardError; end
11
+
12
+ # Raised when a server sends an invalid meta field.
13
+ class InvalidMetaError < StandardError; end
14
+
15
+ # Raised when a server sends an invalid redirect link.
16
+ class InvalidRedirectError < StandardError; end
17
+ end
18
+
19
+ class Request
20
+ # Raised when a request contains an invalid scheme.
21
+ class InvalidSchemeError < StandardError; end
22
+
23
+ # Raised when a request contains an invalid URI.
24
+ class InvalidURIError < StandardError; end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ module Tofu
5
+ # Stores a client request to a Gemini server.
6
+ class Request
7
+ SCHEME = "gemini"
8
+
9
+ MAX_URI_BYTESIZE = 1024
10
+
11
+ # @return [URI] The full URI object of the request.
12
+ attr_reader :uri
13
+
14
+ # @return [String] The request scheme (i.e. gemini://, http://).
15
+ attr_reader :scheme
16
+
17
+ # @return [String] The hostname of the request.
18
+ attr_reader :host
19
+
20
+ # @return [Integer] The server port to connect on.
21
+ attr_reader :port
22
+
23
+ # @return [String] The requested path on the host.
24
+ attr_reader :path
25
+
26
+ # @return [Array] Additional queries to send to the host.
27
+ attr_reader :queries
28
+
29
+ # @return [String] A fragment to request from the host.
30
+ attr_reader :fragment
31
+
32
+ # @return [Response] The response from the server after calling #{gets}.
33
+ attr_reader :resp
34
+
35
+ # Constructor for the request type.
36
+ # @param host [String] A host string, optionally with the gemini:// scheme.
37
+ # @param port [Integer] Optional parameter to specify the server port to connect to.
38
+ def initialize(host, port: nil)
39
+ # Keeps track of the current host for links with only paths.
40
+ @@current_host ||= ""
41
+
42
+ @host = host
43
+ @port = port unless port.nil?
44
+ determine_host
45
+ parse_head
46
+ parse_tail
47
+
48
+ puts format
49
+
50
+ # Make sure the URI isn't too large
51
+ if format.bytesize > MAX_URI_BYTESIZE
52
+ raise InvalidURIError,
53
+ "The URI is too large, should be #{MAX_URI_BYTESIZE} bytes, instead is #{format.bytesize} bytes"
54
+ end
55
+
56
+ # Create a socket
57
+ @sock = Socket.new(@host, @port)
58
+ end
59
+
60
+ # Format the URI for sending over a socket to a Gemini server.
61
+ # @return [String] The URI string appended with a carriage return and linefeed.
62
+ def format
63
+ "#{@uri}\r\n"
64
+ end
65
+
66
+ # Connect to the server and try to fetch data.
67
+ def gets
68
+ @sock.connect
69
+ @resp = @sock.gets(self)
70
+ ensure
71
+ @sock.close
72
+ end
73
+
74
+ private
75
+
76
+ # Parses the host and the path, and sets the current_host.
77
+ def determine_host
78
+ puts "current: #{@@current_host}"
79
+ @uri = URI(@host)
80
+
81
+ unless @uri.host.nil? || @uri.host.empty?
82
+ @host = @uri.host
83
+ @@current_host = @host
84
+ return
85
+ end
86
+
87
+ return if @uri.path.nil? || @uri.path.empty?
88
+ return unless @uri.host.nil? || @uri.host.empty?
89
+
90
+ if @uri.path.start_with?("/")
91
+ raise InvalidURIError, "No host specified" if @@current_host.nil? || @@current_host.empty?
92
+
93
+ unless @@current_host.nil? || @@current_host.empty?
94
+ @uri.host = @@current_host
95
+ @host = @uri.host
96
+ @@current_host = @host
97
+ end
98
+
99
+ return
100
+ end
101
+
102
+ paths = @uri.path.split("/")
103
+ puts paths
104
+
105
+ @uri.host = paths[0]
106
+ @host = @uri.host
107
+ @@current_host = @host
108
+ @uri.path = nil if paths.length == 1
109
+ return unless paths.length > 1
110
+
111
+ @uri.path = paths[1..].join("/")
112
+ @uri.path = "/#{uri.path}"
113
+ end
114
+
115
+ # Parses the scheme, the host, and the port for the request.
116
+ def parse_head
117
+ # Check if a scheme was specified, if not, default to gemini://
118
+ # Also set the port if this happens
119
+ if @uri.scheme.nil? || @uri.scheme.empty?
120
+ @uri.scheme = SCHEME
121
+ @uri.port = URI::Gemini::DEFAULT_PORT
122
+ end
123
+
124
+ # Set member parts
125
+ @scheme = @uri.scheme
126
+ @port = @uri.port
127
+
128
+ # Check if a scheme is present that isn't gemini://
129
+ return if @uri.scheme == SCHEME
130
+
131
+ raise InvalidSchemeError,
132
+ "Request uses an invalid scheme (has: #{@uri.scheme}, wants: #{SCHEME}"
133
+ end
134
+
135
+ # Parses the path, the query, and the fragment for the request.
136
+ def parse_tail
137
+ # Set path to '/' if one isn't specified
138
+ @uri.path = "/" if @uri.path.nil? || @uri.path.empty?
139
+
140
+ # Set member parts
141
+ @path = @uri.path
142
+ @queries = @uri.query.split("&") unless @uri.query.nil? || @uri.query.empty?
143
+ @fragment = @uri.fragment
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String # :nodoc:
4
+ def numerical?
5
+ to_i.to_s == self
6
+ end
7
+ end
8
+
9
+ module Net
10
+ module Tofu
11
+ # Stores a response from a Gemini server.
12
+ class Response
13
+ # Response types
14
+ INPUT = 1
15
+ SUCCESS = 2
16
+ REDIRECT = 3
17
+ TEMPORARY_FAILURE = 4
18
+ PERMANENT_FAILURE = 5
19
+ REQUEST_CERTIFICATE = 6
20
+
21
+ # Limits
22
+ MAX_META_BYTESIZE = 1024
23
+
24
+ # @return [String] The full response header from the server.
25
+ attr_reader :header
26
+
27
+ # @return [Integer] The 2-digit, server response status.
28
+ attr_reader :status
29
+
30
+ # @return [Integer] The first digit of the server response status.
31
+ attr_reader :status_maj
32
+
33
+ # @return [Integer] The second digit of the server response status.
34
+ attr_reader :status_min
35
+
36
+ # Dependent on the #{status} ->
37
+ # => 1x: (INPUT) A prompt line that should be displayed to the user.
38
+ # => 2x: (SUCCESS) A MIME media type.
39
+ # => 3x: (REDIRECT) A new URL for the requested resource.
40
+ # => 4x: (TEMP FAIL) Additional information regarding the temporary failure.
41
+ # => 5x: (PERM FAIL) Additional information regarding the temporary failure.
42
+ # => 6x: (RQST CERT) Additional information regarding the client certificate requirements.
43
+ #
44
+ # According to the specification for the Gemini Protocol, clients SHOULD close a connection to servers which send
45
+ # a meta over 1024 bytes. This library complies with this specification, although, it is conceivable that meta
46
+ # could be an arbitrarily long string.
47
+ #
48
+ # @return [String] The UTF-8 encoded message from the response header.
49
+ attr_reader :meta
50
+
51
+ # @return [String] The message body.
52
+ attr_reader :body
53
+
54
+ # Constructor for the response type.
55
+ # @param data [String] A raw Gemini server response.
56
+ def initialize(data)
57
+ @data = data
58
+ parse
59
+ end
60
+
61
+ # Get a human readable response type.
62
+ # @return [String] The response type as a human readable string.
63
+ def type
64
+ case @status_maj
65
+ when INPUT then return "Input"
66
+ when SUCCESS then return "Success"
67
+ when REDIRECT then return "Redirect"
68
+ when TEMPORARY_FAILURE then return "Temporary failure"
69
+ when PERMANENT_FAILURE then return "Permanent failure"
70
+ when REQUEST_CERTIFICATE then return "Request certificate"
71
+ end
72
+ "Unknown"
73
+ end
74
+
75
+ def input?; return true if @status_maj == INPUT; false; end
76
+ def success?; return true if @status_maj == SUCCESS; false; end
77
+ def redirect?; return true if @status_maj == REDIRECT; false; end
78
+ def temporary_failure?; return true if @status_maj == TEMPORARY_FAILURE; false; end
79
+ def permanent_failure?; return true if @status_maj == PERMANENT_FAILURE; false; end
80
+ def request_certificate?; return true if @status_maj == REQUEST_CERTIFICATE; false; end
81
+
82
+ private
83
+
84
+ # Splits up the responseinto a header and a body.
85
+ def parse
86
+ # Extract the header and parse it
87
+ a = @data.split("\n")
88
+ @header = a[0].strip
89
+ parse_header
90
+
91
+ # Remove the first element from the array,
92
+ # then populate body with the rest of the data
93
+ a.shift
94
+ @body = a.join("\n") if a.length.positive?
95
+ end
96
+
97
+ # Splits upt the header into a status code and a meta.
98
+ def parse_header
99
+ a = @header.split
100
+
101
+ # Make sure there are only one or two elements in the header
102
+ raise InvalidHeaderError, "The server did not send a header" unless a.length.positive?
103
+
104
+ # Parse the status code
105
+ @status = a[0]
106
+ parse_status
107
+
108
+ # Parse the meta
109
+ @meta = ""
110
+ @meta = a[1..].join(" ") if a.length >= 2
111
+ parse_meta
112
+ end
113
+
114
+ # Splits up the status into a major and minor, checks for invalid status codes.
115
+ def parse_status
116
+ # Make sure the status is numerical
117
+ unless @status.numerical?
118
+ raise InvalidStatusCodeError,
119
+ "The server sent a non-numerical status code: #{@status}"
120
+ end
121
+
122
+ # Allow status code to only be a single digit, or two digits (as the spec says it should be)
123
+ if @status.length == 1
124
+ @status_maj = Integer(@status)
125
+ @status_min = 0
126
+ elsif @status.length == 2
127
+ @status_maj = Integer(@status[0])
128
+ @status_min = Integer(@status[1])
129
+ else
130
+ raise InvalidStatusCodeError, "The server sent a status code that is longer than 2 digits: #{@status}"
131
+ end
132
+
133
+ # Make sure the major status code is between 1 and 6, including 1 and 6
134
+ unless @status_maj >= 1 && @status_maj <= 6
135
+ raise InvalidStatusCodeError,
136
+ "The server sent an invalid, major status code: #{@status_maj}"
137
+ end
138
+
139
+ # Make sure #{status} is an Integer
140
+ @status = Integer(@status)
141
+ end
142
+
143
+ # Checks the meta size, does extra checking depending on #{status} type.
144
+ def parse_meta
145
+ # Make sure the meta isn't too large
146
+ if @meta.bytesize > MAX_META_BYTESIZE
147
+ raise InvalidMetaError,
148
+ "The server sent a meta that was too large, should be #{MAX_META_BYTESIZE} bytes, instead is #{@meta.bytesize} bytes"
149
+ end
150
+
151
+ if @status_maj == TEMPORARY_FAILURE || @status_maj == PERMANENT_FAILURE || @status_maj == REQUEST_CERTIFICATE
152
+ return
153
+ end
154
+
155
+ # Make sure meta exists (i.e. has length)
156
+ # This satisfies the INPUT and SUCCESS
157
+ unless @meta.length.positive?
158
+ raise InvalidMetaError,
159
+ "The server sent an empty meta, should've sent a user prompt"
160
+ end
161
+
162
+ # TODO: Possibly check for valid MIME type for the SUCCESS response
163
+ return if @status_maj == INPUT || @status_maj == SUCCESS
164
+
165
+ # The meta needs a specific URI if @status_maj == REDIRECT
166
+ uri = URI(@meta)
167
+
168
+ # Make sure the URI has a scheme
169
+ raise InvalidRedirectError, "The redirect link does not have a scheme" if uri.scheme.nil? || uri.scheme.empty?
170
+
171
+ # Make sure the URI scheme is 'gemini'
172
+ return if uri.scheme == "gemini"
173
+
174
+ raise InvalidRedirectError,
175
+ "The redirect link has an invalid scheme (has: #{uri.scheme}, wants: gemini)"
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ module Tofu
5
+ # Stoes an SSLSocket for making requests and receiving data.
6
+ class Socket
7
+ # Constructor for the socket type.
8
+ # @param host [String] Hostname of the server to connect to.
9
+ # @param port [Integer] Server port to connect to (typically 1965).
10
+ def initialize(host, port)
11
+ @host = host
12
+ @port = port
13
+ @sock = OpenSSL::SSL::SSLSocket.open(@host, @port, context: generate_secure_context)
14
+ end
15
+
16
+ # Open a connection to the server.
17
+ def connect
18
+ @sock.hostname = @host
19
+ @sock.connect
20
+ end
21
+
22
+ # Try and retrieve data from a request.
23
+ def gets(req)
24
+ @sock.puts req.format
25
+
26
+ io = StringIO.new
27
+ while (line = @sock.gets)
28
+ io.puts line
29
+ end
30
+
31
+ Response.new(io.string)
32
+ ensure
33
+ io.close
34
+ end
35
+
36
+ # Close the connection with the server.
37
+ def close
38
+ @sock.close
39
+ end
40
+
41
+ private
42
+
43
+ # Configure the TLS security options to use on the socket.
44
+ def generate_secure_context
45
+ ctx = OpenSSL::SSL::SSLContext.new
46
+ ctx.verify_hostname = true
47
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
48
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
49
+ ctx.options |= OpenSSL::SSL::OP_IGNORE_UNEXPECTED_EOF
50
+ ctx
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tofu
4
+ VERSION = "0.1.0"
5
+ end
data/lib/net/tofu.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tofu/error"
4
+ require_relative "tofu/request"
5
+ require_relative "tofu/response"
6
+ require_relative "tofu/socket"
7
+ require_relative "tofu/version"
8
+
9
+ require "openssl"
10
+ require "stringio"
11
+ require "uri/gemini"
12
+
13
+ module Net
14
+ # Top level module for Geminispace requests.
15
+ module Tofu
16
+ def self.get(uri, trust: true)
17
+ req = Request.new(uri)
18
+ req.gets
19
+
20
+ return req.resp.body if req.resp.success?
21
+
22
+ req.resp.meta
23
+ end
24
+
25
+ def self.get_response(uri, trust: true)
26
+ req = Request.new(uri)
27
+ req.gets
28
+
29
+ req.resp
30
+ end
31
+ end
32
+ end
data/lib/uri/gemini.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # MIT License
4
+ #
5
+ # Copyright (c) 2020 Étienne Deparis
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in all
15
+ # copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ # SOFTWARE.
24
+
25
+ require "uri"
26
+
27
+ module URI # :nodoc:
28
+ #
29
+ # The syntax of Gemini URIs is defined in the Gemini specification,
30
+ # section 1.2.
31
+ #
32
+ # @see https://gemini.circumlunar.space/docs/specification.html
33
+ #
34
+ class Gemini < HTTP
35
+ # A Default port of 1965 for URI::Gemini.
36
+ DEFAULT_PORT = 1965
37
+
38
+ # An Array of the available components for URI::Gemini.
39
+ COMPONENT = %i[scheme host port
40
+ path query fragment].freeze
41
+ end
42
+
43
+ if respond_to? :register_scheme
44
+ # Introduced somewhere in ruby 3.0.x
45
+ register_scheme "GEMINI", Gemini
46
+ else
47
+ @@schemes["GEMINI"] = Gemini
48
+ end
49
+ end
data/sig/opentofu.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Opentofu
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opentofu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rory Dudley
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-07-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ OpenTOFU is a client and certificate pinning library for Geminispace, derived from
15
+ the 'trust on first use' authentication scheme (https://en.wikipedia.org/wiki/Trust_on_first_use).
16
+ email:
17
+ - rory.dudley@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".rubocop.yml"
23
+ - CHANGELOG.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/net/tofu.rb
28
+ - lib/net/tofu/error.rb
29
+ - lib/net/tofu/request.rb
30
+ - lib/net/tofu/response.rb
31
+ - lib/net/tofu/socket.rb
32
+ - lib/net/tofu/version.rb
33
+ - lib/uri/gemini.rb
34
+ - sig/opentofu.rbs
35
+ homepage: https://github.com/pinecat/opentofu
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ allowed_push_host: https://rubygems.org
40
+ homepage_uri: https://github.com/pinecat/opentofu
41
+ source_code_uri: https://github.com/pinecat/opentofu
42
+ changelog_uri: https://github.com/pinecat/opentofu
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 2.6.0
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.4.17
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: A Gemini client and certificate manager.
62
+ test_files: []