diamant 0.0.8 → 0.0.10
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/bin/diamant +2 -2
- data/lib/diamant/cert_generator.rb +4 -4
- data/lib/diamant/mimefile.rb +96 -0
- data/lib/diamant/response.rb +25 -14
- data/lib/diamant/version.rb +1 -1
- data/lib/diamant.rb +16 -15
- metadata +12 -92
- data/lib/diamant/mimetype.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3aae272eff26676b737dc65ff08ea76b0b4ea1822ec4785dfa47b97f0647dbe8
|
4
|
+
data.tar.gz: dc803566505cbed2653c44ac2bc7d4d3e0767ceb4604f91f32c1523917045192
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc0d90505b8e7ef55f679c290aca6288dddd7c5b3208fbcab86dfa9501be7316ae8f425a3e5e4e5a3525d03c8765c397729231a590836bbe168783f162c3a852
|
7
|
+
data.tar.gz: b181654c75861b8a20308513b451999b63ce08b47c527ce2258de451daa16835aba9676c0f0ede70cb609eeb7b1a71f0a8f933b3c031ab39d505e4308396d532
|
data/bin/diamant
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'diamant'
|
5
|
-
require 'diamant/cert_generator'
|
6
4
|
require 'optparse'
|
5
|
+
require_relative '../lib/diamant'
|
6
|
+
require_relative '../lib/diamant/cert_generator'
|
7
7
|
|
8
8
|
options = { hostname: '127.0.0.1', port: 1965 }
|
9
9
|
OptionParser.new do |parser| # rubocop:disable Metrics/BlockLength
|
@@ -23,15 +23,15 @@ module Diamant
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def write
|
26
|
-
|
26
|
+
File.write('key.rsa', @key.to_pem)
|
27
27
|
File.chmod(0o400, 'key.rsa')
|
28
|
-
|
28
|
+
File.write('cert.pem', @cert.to_pem)
|
29
29
|
File.chmod(0o644, 'cert.pem')
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
def init_cert
|
34
|
+
def init_cert # rubocop:disable Metrics/AbcSize
|
35
35
|
@cert = OpenSSL::X509::Certificate.new
|
36
36
|
@cert.version = 3
|
37
37
|
@cert.serial = 0x0
|
@@ -40,7 +40,7 @@ module Diamant
|
|
40
40
|
@cert.public_key = @key.public_key
|
41
41
|
@cert.not_before = Time.now
|
42
42
|
# 1 years validity
|
43
|
-
@cert.not_after = @cert.not_before + 1 * 365 * 24 * 60 * 60
|
43
|
+
@cert.not_after = @cert.not_before + (1 * 365 * 24 * 60 * 60)
|
44
44
|
@cert
|
45
45
|
end
|
46
46
|
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diamant
|
4
|
+
class MimeError < StandardError; end
|
5
|
+
|
6
|
+
# MIME aware wrapper for file
|
7
|
+
class MimeFile
|
8
|
+
attr_reader :body, :category, :content_type, :extension
|
9
|
+
|
10
|
+
MIMETYPES = {
|
11
|
+
'.gemini' => 'text/gemini',
|
12
|
+
'.gmi' => 'text/gemini',
|
13
|
+
'.txt' => 'text/plain',
|
14
|
+
'.md' => 'text/markdown',
|
15
|
+
'.org' => 'text/org',
|
16
|
+
'.rss' => 'application/rss+xml',
|
17
|
+
'.atom' => 'application/atom+xml',
|
18
|
+
'.xml' => 'application/xml',
|
19
|
+
'.svg' => 'image/svg+xml',
|
20
|
+
'.bmp' => 'image/bmp',
|
21
|
+
'.png' => 'image/png',
|
22
|
+
'.jpg' => 'image/jpeg',
|
23
|
+
'.jpeg' => 'image/jpeg',
|
24
|
+
'.gif' => 'image/gif',
|
25
|
+
'.webp' => 'image/webp',
|
26
|
+
'.mp3' => 'audio/mpeg',
|
27
|
+
'.ogg' => 'audio/ogg'
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
def initialize(path)
|
31
|
+
@path = path
|
32
|
+
@body = File.read path
|
33
|
+
@extension, @content_type = extract_info
|
34
|
+
@category = classify
|
35
|
+
validate!
|
36
|
+
prepare_gem_file if @content_type == 'text/gemini'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Disable metrics on purpose for big switch
|
40
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
41
|
+
def validate
|
42
|
+
return true if @category == :text
|
43
|
+
|
44
|
+
case @content_type
|
45
|
+
when 'image/jpeg'
|
46
|
+
@body[0..1] == "\xFF\xD8"
|
47
|
+
when 'image/png'
|
48
|
+
@body[0..7] == "\x89PNG\r\n\u001A\n"
|
49
|
+
when 'image/gif'
|
50
|
+
@body[0..2] == 'GIF'
|
51
|
+
when 'image/webp'
|
52
|
+
@body[0..3] == 'RIFF' && @body[8..11] == 'WEBP'
|
53
|
+
when 'image/bmp'
|
54
|
+
@body[0..2] == 'BM'
|
55
|
+
when 'image/svg+xml', 'application/xml',
|
56
|
+
'application/rss+xml', 'application/atom+xml'
|
57
|
+
@body[0..5] == '<?xml '
|
58
|
+
when 'audio/mpeg'
|
59
|
+
@body[0..3] == 'ID3'
|
60
|
+
when 'audio/ogg'
|
61
|
+
@body[0..4] == 'OggS'
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
67
|
+
|
68
|
+
def validate!
|
69
|
+
return if validate
|
70
|
+
|
71
|
+
raise MimeError, "#{@path} does not looks like a #{@content_type} file!"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def extract_info
|
77
|
+
extension = File.extname @path
|
78
|
+
mime = MIMETYPES[extension]
|
79
|
+
raise MimeError, "#{@path} format is not supported!" if mime == ''
|
80
|
+
|
81
|
+
# Any other supported extension
|
82
|
+
[extension, mime]
|
83
|
+
end
|
84
|
+
|
85
|
+
def classify
|
86
|
+
return unless @content_type
|
87
|
+
|
88
|
+
@content_type.split('/', 2).first.to_sym
|
89
|
+
end
|
90
|
+
|
91
|
+
def prepare_gem_file
|
92
|
+
# Ensure each lines finishes with \r\n
|
93
|
+
@body = @body.each_line(chomp: true).to_a.join "\r\n"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/diamant/response.rb
CHANGED
@@ -3,9 +3,13 @@
|
|
3
3
|
require 'net/gemini/request'
|
4
4
|
require 'uri/gemini'
|
5
5
|
|
6
|
+
require_relative 'mimefile'
|
7
|
+
|
6
8
|
module Diamant
|
7
9
|
# Methods to generate requests responses
|
8
10
|
module Response
|
11
|
+
class NotFound < Net::Gemini::BadResponse; end
|
12
|
+
|
9
13
|
private
|
10
14
|
|
11
15
|
def reject_request?(sock, current_load)
|
@@ -13,6 +17,7 @@ module Diamant
|
|
13
17
|
return false if current_load < 11
|
14
18
|
# Seppuku
|
15
19
|
raise 'Server is under heavy load' if current_load > 1965
|
20
|
+
|
16
21
|
if current_load > 42
|
17
22
|
@logger.warn '41 - Too much threads...'
|
18
23
|
sock.puts "41 See you soon...\r\n"
|
@@ -24,33 +29,39 @@ module Diamant
|
|
24
29
|
true
|
25
30
|
end
|
26
31
|
|
27
|
-
def
|
32
|
+
def build_response(client)
|
28
33
|
r = Net::Gemini::Request.read_new(client)
|
29
|
-
|
34
|
+
file_path = route r.path
|
35
|
+
[r.uri, read_mime_file(file_path)]
|
36
|
+
rescue NotFound => e
|
37
|
+
@logger.warn "51 - #{e}"
|
38
|
+
[r.uri, "51 Not found!\r\n"]
|
30
39
|
rescue Net::Gemini::BadRequest, URI::InvalidURIError => e
|
31
40
|
@logger.error "59 - #{e}"
|
32
|
-
[nil,
|
41
|
+
[nil, "59\r\n"]
|
33
42
|
end
|
34
43
|
|
35
44
|
def route(path)
|
36
45
|
# In any case, remove the / prefix
|
37
|
-
route = File.expand_path path.delete_prefix('/')
|
46
|
+
route = File.expand_path path.delete_prefix('/')
|
38
47
|
# We better should use some sort of chroot...
|
39
48
|
unless route.start_with?(Dir.pwd)
|
40
|
-
|
41
|
-
return ['51 Not found!']
|
49
|
+
raise NotFound, "Attempt to get something out of public_dir: #{route}"
|
42
50
|
end
|
51
|
+
|
43
52
|
route << '/index.gmi' if File.directory?(route)
|
44
|
-
return
|
45
|
-
|
53
|
+
return route if File.exist?(route)
|
54
|
+
|
55
|
+
raise NotFound, "File #{route} does not exist"
|
46
56
|
end
|
47
57
|
|
48
|
-
def
|
49
|
-
info = Diamant::
|
50
|
-
|
51
|
-
|
52
|
-
rescue Diamant::MimeError
|
53
|
-
|
58
|
+
def read_mime_file(route)
|
59
|
+
info = Diamant::MimeFile.new route
|
60
|
+
header = "20 #{info.content_type}"
|
61
|
+
[header, info.body].join("\r\n")
|
62
|
+
rescue Diamant::MimeError => e
|
63
|
+
@logger.error "50 - #{e}"
|
64
|
+
"50 Not a supported file!\r\n"
|
54
65
|
end
|
55
66
|
end
|
56
67
|
end
|
data/lib/diamant/version.rb
CHANGED
data/lib/diamant.rb
CHANGED
@@ -6,9 +6,7 @@ require 'English'
|
|
6
6
|
require 'openssl'
|
7
7
|
require 'fileutils'
|
8
8
|
|
9
|
-
|
10
|
-
require 'diamant/mimetype'
|
11
|
-
require 'diamant/response'
|
9
|
+
require_relative 'diamant/response'
|
12
10
|
|
13
11
|
module Diamant
|
14
12
|
# Runs the server request/answer loop.
|
@@ -34,13 +32,7 @@ module Diamant
|
|
34
32
|
|
35
33
|
def main_loop(ssl_serv)
|
36
34
|
loop do
|
37
|
-
|
38
|
-
handle_client(client)
|
39
|
-
rescue Errno::ECONNRESET, Errno::ENOTCONN => e
|
40
|
-
@logger.error(e.message)
|
41
|
-
ensure
|
42
|
-
client.close
|
43
|
-
end
|
35
|
+
main_loop_tick ssl_serv.accept
|
44
36
|
rescue OpenSSL::SSL::SSLError => e
|
45
37
|
# Do not even try to answer anything as the socket cannot be
|
46
38
|
# built. This will abruptly interrupt the connection from a client
|
@@ -56,17 +48,26 @@ module Diamant
|
|
56
48
|
end
|
57
49
|
end
|
58
50
|
|
51
|
+
# Might raises some errors
|
52
|
+
def main_loop_tick(connection)
|
53
|
+
Thread.new(connection) do |client|
|
54
|
+
handle_client(client)
|
55
|
+
rescue Errno::ECONNRESET, Errno::ENOTCONN, Errno::ETIMEDOUT => e
|
56
|
+
@logger.error(e.message)
|
57
|
+
ensure
|
58
|
+
client.close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
59
62
|
def handle_client(client)
|
60
|
-
current_load = Thread.list.length - 1
|
63
|
+
current_load = Thread.list.length - 1 # Exclude main thread
|
61
64
|
return if reject_request?(client, current_load)
|
62
65
|
|
63
|
-
uri, answer =
|
66
|
+
uri, answer = build_response(client)
|
64
67
|
log_line = [current_load, client.peeraddr[3], answer[0]]
|
65
68
|
log_line << uri if uri
|
66
69
|
@logger.info log_line.join(' - ')
|
67
|
-
|
68
|
-
client.puts "#{line}\r\n"
|
69
|
-
end
|
70
|
+
client.puts answer
|
70
71
|
end
|
71
72
|
|
72
73
|
def ssl_context
|
metadata
CHANGED
@@ -1,113 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: diamant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
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: 2024-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-net-text
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.9
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0.9
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rspec
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '3.12'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '3.12'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rubocop
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.57'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.57'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubocop-performance
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.19'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.19'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rubocop-rspec
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.24'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '2.24'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: simplecov
|
85
15
|
requirement: !ruby/object:Gem::Requirement
|
86
16
|
requirements:
|
87
17
|
- - "~>"
|
88
18
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
90
|
-
type: :
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0.22'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: yard
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0.9'
|
104
|
-
type: :development
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
105
21
|
prerelease: false
|
106
22
|
version_requirements: !ruby/object:Gem::Requirement
|
107
23
|
requirements:
|
108
24
|
- - "~>"
|
109
25
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0.
|
26
|
+
version: '0.1'
|
111
27
|
description: |
|
112
28
|
Diamant is a server for the Gemini network protocol. It can only serve
|
113
29
|
static files. Internally, it uses the OpenSSL library to handle the TLS
|
@@ -122,13 +38,17 @@ files:
|
|
122
38
|
- bin/diamant
|
123
39
|
- lib/diamant.rb
|
124
40
|
- lib/diamant/cert_generator.rb
|
125
|
-
- lib/diamant/
|
41
|
+
- lib/diamant/mimefile.rb
|
126
42
|
- lib/diamant/response.rb
|
127
43
|
- lib/diamant/version.rb
|
128
44
|
homepage: https://git.umaneti.net/diamant/about/
|
129
45
|
licenses:
|
130
46
|
- WTFPL
|
131
|
-
metadata:
|
47
|
+
metadata:
|
48
|
+
rubygems_mfa_required: 'true'
|
49
|
+
source_code_uri: https://git.umaneti.net/fronde
|
50
|
+
homepage_uri: https://etienne.depar.is/fronde/
|
51
|
+
funding_uri: https://liberapay.com/milouse
|
132
52
|
post_install_message:
|
133
53
|
rdoc_options: []
|
134
54
|
require_paths:
|
@@ -144,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
64
|
- !ruby/object:Gem::Version
|
145
65
|
version: '0'
|
146
66
|
requirements: []
|
147
|
-
rubygems_version: 3.
|
67
|
+
rubygems_version: 3.5.15
|
148
68
|
signing_key:
|
149
69
|
specification_version: 4
|
150
70
|
summary: A simple Gemini server for static files.
|
data/lib/diamant/mimetype.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Diamant
|
4
|
-
class MimeError < StandardError; end
|
5
|
-
|
6
|
-
# Helper to understand what mimetype has a given file
|
7
|
-
class MimeType
|
8
|
-
attr_reader :extension, :content_type
|
9
|
-
|
10
|
-
MIMETYPES = {
|
11
|
-
'.gemini' => 'text/gemini',
|
12
|
-
'.gmi' => 'text/gemini',
|
13
|
-
'.txt' => 'text/plain',
|
14
|
-
'.md' => 'text/markdown',
|
15
|
-
'.org' => 'text/org',
|
16
|
-
'.xml' => 'application/xml',
|
17
|
-
'.png' => 'image/png',
|
18
|
-
'.jpg' => 'image/jpeg',
|
19
|
-
'.jpeg' => 'image/jpeg',
|
20
|
-
'.gif' => 'image/gif'
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
def initialize(path)
|
24
|
-
@path = path
|
25
|
-
extract_info
|
26
|
-
end
|
27
|
-
|
28
|
-
def supported?
|
29
|
-
@extension != '' && MIMETYPES.has_key?(@extension)
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def extract_info
|
35
|
-
@extension = File.extname @path
|
36
|
-
raise MimeError, "#{@path} format is not supported!" unless supported?
|
37
|
-
# Any other supported extension
|
38
|
-
@content_type = MIMETYPES[@extension]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|