diamant 0.0.9 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f4a7116de355c0b04280914d27758f287116298d2cde5a36e311382352342b1
4
- data.tar.gz: 04a5cd38b4596383a220bd553b6e6d1a9360310810156d009f8cad569db9bd01
3
+ metadata.gz: 3aae272eff26676b737dc65ff08ea76b0b4ea1822ec4785dfa47b97f0647dbe8
4
+ data.tar.gz: dc803566505cbed2653c44ac2bc7d4d3e0767ceb4604f91f32c1523917045192
5
5
  SHA512:
6
- metadata.gz: e948fce49088c1b5483a18dd4c4dfccfbaefb7b160d4a9ce0fc880a0bf4487c6ded0eda9a3b58de9ad74fe5a05f3922c790716d26789db0a18621381fe6acefe
7
- data.tar.gz: 1113f9c27f505edcc445b116a3d9dbb900c681a5305b48655d72faee52714a1ca7c4b3b71a7123b80e8f93468ecfcfa88afd5f455e2fc4222adef6e73070fa61
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
@@ -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
@@ -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)
@@ -25,34 +29,39 @@ module Diamant
25
29
  true
26
30
  end
27
31
 
28
- def read_file(client)
32
+ def build_response(client)
29
33
  r = Net::Gemini::Request.read_new(client)
30
- [r.uri, route(r.path)]
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"]
31
39
  rescue Net::Gemini::BadRequest, URI::InvalidURIError => e
32
40
  @logger.error "59 - #{e}"
33
- [nil, ["59\r\n"]]
41
+ [nil, "59\r\n"]
34
42
  end
35
43
 
36
44
  def route(path)
37
45
  # In any case, remove the / prefix
38
- route = File.expand_path path.delete_prefix('/'), Dir.pwd
46
+ route = File.expand_path path.delete_prefix('/')
39
47
  # We better should use some sort of chroot...
40
48
  unless route.start_with?(Dir.pwd)
41
- @logger.warn "Bad attempt to get something out of public_dir: #{route}"
42
- return ['51 Not found!']
49
+ raise NotFound, "Attempt to get something out of public_dir: #{route}"
43
50
  end
51
+
44
52
  route << '/index.gmi' if File.directory?(route)
45
- return ['51 Not found!'] unless File.exist?(route)
53
+ return route if File.exist?(route)
46
54
 
47
- build_response route
55
+ raise NotFound, "File #{route} does not exist"
48
56
  end
49
57
 
50
- def build_response(route)
51
- info = Diamant::MimeType.new(route)
52
- answer = File.readlines route, chomp: true
53
- answer.prepend "20 #{info.content_type}"
54
- rescue Diamant::MimeError
55
- ['50 Not a supported file!']
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"
56
65
  end
57
66
  end
58
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Diamant
4
- VERSION = '0.0.9'
4
+ VERSION = '0.0.10'
5
5
  end
data/lib/diamant.rb CHANGED
@@ -6,9 +6,7 @@ require 'English'
6
6
  require 'openssl'
7
7
  require 'fileutils'
8
8
 
9
- require 'diamant/version'
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.
@@ -62,16 +60,14 @@ module Diamant
62
60
  end
63
61
 
64
62
  def handle_client(client)
65
- current_load = Thread.list.length - 1
63
+ current_load = Thread.list.length - 1 # Exclude main thread
66
64
  return if reject_request?(client, current_load)
67
65
 
68
- uri, answer = read_file(client)
66
+ uri, answer = build_response(client)
69
67
  log_line = [current_load, client.peeraddr[3], answer[0]]
70
68
  log_line << uri if uri
71
69
  @logger.info log_line.join(' - ')
72
- answer.each do |line|
73
- client.puts "#{line}\r\n"
74
- end
70
+ client.puts answer
75
71
  end
76
72
 
77
73
  def ssl_context
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diamant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
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: 2024-07-13 00:00:00.000000000 Z
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
@@ -38,7 +38,7 @@ files:
38
38
  - bin/diamant
39
39
  - lib/diamant.rb
40
40
  - lib/diamant/cert_generator.rb
41
- - lib/diamant/mimetype.rb
41
+ - lib/diamant/mimefile.rb
42
42
  - lib/diamant/response.rb
43
43
  - lib/diamant/version.rb
44
44
  homepage: https://git.umaneti.net/diamant/about/
@@ -1,42 +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
-
38
- # Any other supported extension
39
- @content_type = MIMETYPES[@extension]
40
- end
41
- end
42
- end