-
Notifications
You must be signed in to change notification settings - Fork 0
/
fetchd.rb
139 lines (134 loc) · 3.91 KB
/
fetchd.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
require "open-uri"
require "openssl"
require "pathname"
require "readability"
require "socket"
require "uri"
class InternalServerError < StandardError; end
class BadRequest < StandardError; end
class NotFound < StandardError; end
class Forbidden < StandardError; end
port = 8888
server = TCPServer.new(port)
options = {}
ARGV.each do |arg|
case arg
when "--readable", "-r"
options[:readable] = true
when "--server", "-s"
options[:server] = true
when "--proxy", "-p"
options[:proxy] = true
when "--help", "-h"
print "Usage: fetchd [--server] [--proxy]"
exit
end
end
loop do
Thread.start(server.accept) do |client|
response_code = 200
response_size = 0
time = Time.new
begin
request = client.gets&.chomp
raise BadRequest if request.empty? || request.nil?
sock_domain, remote_port, remote_hostname, remote_ip = client.peeraddr
action, url = request.split
uri = URI.parse(url)
case uri.scheme
when "http", "https"
raise Forbidden unless options[:proxy]
begin
URI.open(url) do |file|
response = file.read
response = Readability::Document.new(response).content if options[:readable]
response_size = response.size
client.puts response
end
rescue SocketError
raise InternalServerError
rescue Errno::ECONNRESET, OpenURI::HTTPError
raise NotFound
end
when "gemini"
raise Forbidden unless options[:proxy]
host = uri.host
loop do
tcp_socket = TCPSocket.new(host, 1965)
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_NONE
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, context)
ssl_socket.hostname = host
ssl_socket.sync_close = true
ssl_socket.connect
ssl_socket.write("#{url}\r\n")
headers = ssl_socket.gets&.chomp&.split.to_a
response = ssl_socket.read
ssl_socket.close
tcp_socket.close
case headers[0]
when "31"
url = headers[1]
when "20"
response_size = response.size
client.puts response
break
else
raise NotFound
end
rescue SocketError
raise NotFound
end
when "gopher"
raise Forbidden unless options[:proxy]
host = uri.host
path = uri.path.sub(/^\/\d+/, "")
begin
socket = TCPSocket.new(host, 70)
socket.write("#{path}\r\n")
response = socket.read
response_size = response.size
client.puts response
rescue SocketError
raise NotFound
end
when nil
raise Forbidden unless options[:server]
host = uri.host
path = ".#{uri.path}"
base = Pathname.new(Dir.pwd)
begin
if base.join(path).relative_path_from(base).to_s.start_with?("..")
raise Forbidden
elsif File.directory?(path)
found = %w[index.html index.txt index.md].any? do |index|
file = base.join(path, index)
if File.exist?(file)
response = File.read(file)
response_size = response.size
client.puts response
true
end
end
raise NotFound unless found
else
response = File.read(path)
response_size = response.size
client.puts response
end
end
true
end
rescue BadRequest
response_code = 400
rescue NotFound, Errno::ENOENT
response_code = 404
rescue Forbidden
response_code = 403
rescue InternalServerError
response_code = 500
end
printf("%s - - [%s] \"%s\" %d %d\n", remote_ip, time, request, response_code, response_size)
client.close
end
end