diamant 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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