rffw 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.md +60 -0
- data/Rakefile +41 -0
- data/bin/rffw +16 -0
- data/lib/rffw.rb +7 -0
- data/lib/rffw/app.rb +36 -0
- data/lib/rffw/app/app_handler.rb +59 -0
- data/lib/rffw/app/data/images/bg.png +0 -0
- data/lib/rffw/app/data/index.html +56 -0
- data/lib/rffw/app/data/javascripts/application.js +110 -0
- data/lib/rffw/app/data/stylesheets/style.css +338 -0
- data/lib/rffw/app/data/template.html +42 -0
- data/lib/rffw/app/db.rb +19 -0
- data/lib/rffw/app/description_handler.rb +12 -0
- data/lib/rffw/app/dir_handler.rb +36 -0
- data/lib/rffw/app/record.rb +43 -0
- data/lib/rffw/app/show_handler.rb +25 -0
- data/lib/rffw/app/upload_handler.rb +32 -0
- data/lib/rffw/app/upload_status_handler.rb +40 -0
- data/lib/rffw/app/urlencode_chars.data +224 -0
- data/lib/rffw/app/view_helpers.rb +30 -0
- data/lib/rffw/parser.rb +9 -0
- data/lib/rffw/parser/http_request.rb +137 -0
- data/lib/rffw/parser/http_response.rb +29 -0
- data/lib/rffw/parser/mime_parser.rb +46 -0
- data/lib/rffw/server.rb +15 -0
- data/lib/rffw/server/buffered_client.rb +57 -0
- data/lib/rffw/server/client.rb +53 -0
- data/lib/rffw/server/http_client.rb +49 -0
- data/lib/rffw/server/server.rb +46 -0
- data/lib/rffw/version.rb +3 -0
- data/rffw.db +0 -0
- data/rffw.gemspec +21 -0
- data/test/fixtures/image.jpg +0 -0
- data/test/fixtures/mime_image.data +0 -0
- data/test/fixtures/mime_image.png +0 -0
- data/test/fixtures/post_form_data.http +14 -0
- data/test/fixtures/raw_request.txt +8 -0
- data/test/fixtures/request.http +9 -0
- data/test/fixtures/upload.http +0 -0
- data/test/fixtures/upload_status_request.http +10 -0
- data/test/helper.rb +97 -0
- data/test/test_buffered_client.rb +50 -0
- data/test/test_client.rb +47 -0
- data/test/test_db.rb +21 -0
- data/test/test_http_client.rb +26 -0
- data/test/test_http_request.rb +52 -0
- data/test/test_mime_parser.rb +22 -0
- data/test/test_record.rb +49 -0
- data/test/test_upload_status_handler.rb +18 -0
- data/test/test_uploader_handler.rb +10 -0
- data/test/test_view_helpers.rb +24 -0
- metadata +122 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module RFFW::App::ViewHelpers
|
2
|
+
|
3
|
+
CHARS = File.read(File.expand_path('../urlencode_chars.data',__FILE__)).split("\n")
|
4
|
+
|
5
|
+
def uri_escape(string)
|
6
|
+
string.gsub(/[^a-zA-Z0-9 ]/) do |char|
|
7
|
+
CHARS.include?(char) ? "%#{"%.2X" % (CHARS.index(char)+32)}" : char
|
8
|
+
end.tr(" ", "+")
|
9
|
+
end
|
10
|
+
|
11
|
+
def uri_unescape(string)
|
12
|
+
string.gsub(/\%([a-f0-9]{2})/i) do |char|
|
13
|
+
value = $1.to_i(16)
|
14
|
+
case value
|
15
|
+
when 32..(CHARS.size+32)
|
16
|
+
CHARS[value - 32] || char
|
17
|
+
when 13
|
18
|
+
"\n"
|
19
|
+
when 10
|
20
|
+
'' #LF
|
21
|
+
else
|
22
|
+
"#{char}"
|
23
|
+
end
|
24
|
+
end.gsub("+", " ")
|
25
|
+
end
|
26
|
+
|
27
|
+
def strip_tags(string)
|
28
|
+
string.gsub(/<(.|\n)*?>/m, '')
|
29
|
+
end
|
30
|
+
end
|
data/lib/rffw/parser.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
module RFFW::Parser
|
2
|
+
class HttpRequest
|
3
|
+
|
4
|
+
class MalformedReqest < StandardError ; end
|
5
|
+
class IncompleteRequest < StandardError ; end
|
6
|
+
class OnlyForPosts < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :headers
|
9
|
+
|
10
|
+
def initialize(raw_request)
|
11
|
+
@headers={}
|
12
|
+
@headers_are_parsed = false
|
13
|
+
scan(raw_request)
|
14
|
+
rescue IncompleteRequest
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def finish?
|
19
|
+
if get? && @headers_are_parsed
|
20
|
+
true
|
21
|
+
elsif post? && @headers_are_parsed && upload_content_lenght <= body_size
|
22
|
+
true
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
post? ? @body : @body || ""
|
30
|
+
end
|
31
|
+
|
32
|
+
def attachments
|
33
|
+
raise OnlyForPosts unless post?
|
34
|
+
MimeParser.parse(@body)
|
35
|
+
end
|
36
|
+
|
37
|
+
def method
|
38
|
+
@headers["HTTP_METHOD"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def path
|
42
|
+
uri && uri.path
|
43
|
+
end
|
44
|
+
|
45
|
+
def query
|
46
|
+
uri && uri.query || ""
|
47
|
+
end
|
48
|
+
|
49
|
+
def url
|
50
|
+
@headers["HTTP_PATH"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def uri
|
54
|
+
URI.parse(url)
|
55
|
+
rescue
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def version
|
60
|
+
@headers["HTTP_VERSION"]
|
61
|
+
end
|
62
|
+
|
63
|
+
def keep_alive?
|
64
|
+
!!(version == '1.1' && @headers["Connection"] =~ /keep-alive/)
|
65
|
+
end
|
66
|
+
|
67
|
+
def post?
|
68
|
+
method && method.downcase == "post"
|
69
|
+
end
|
70
|
+
|
71
|
+
def get?
|
72
|
+
method && method.downcase == 'get'
|
73
|
+
end
|
74
|
+
|
75
|
+
def uplad_info
|
76
|
+
@headers["Content-Length"].to_i - @body.size
|
77
|
+
end
|
78
|
+
|
79
|
+
def upload_content_lenght
|
80
|
+
raise OnlyForPosts unless post?
|
81
|
+
@headers["Content-Length"].to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
def body_size
|
85
|
+
raise OnlyForPosts unless post?
|
86
|
+
@body.size
|
87
|
+
end
|
88
|
+
|
89
|
+
def upload_progress
|
90
|
+
raise OnlyForPosts unless post?
|
91
|
+
"#{body_size}/#{upload_content_lenght}"
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def post_form_data
|
96
|
+
body.split("&").inject({}){|memo, value|
|
97
|
+
key,value = value.split("=")
|
98
|
+
memo[key] = value
|
99
|
+
memo
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def inspect
|
104
|
+
"#{method} #{path} #{query} #{version}"
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
def scan(raw)
|
109
|
+
return nil unless raw.is_a?(String)
|
110
|
+
full_scanner = StringScanner.new(raw.force_encoding("BINARY"))
|
111
|
+
|
112
|
+
headers = full_scanner.scan_until(/\r\n\r\n/) or raise IncompleteRequest
|
113
|
+
scan_headers(headers)
|
114
|
+
|
115
|
+
@body = full_scanner.rest
|
116
|
+
end
|
117
|
+
|
118
|
+
def scan_headers(headers)
|
119
|
+
scanner = StringScanner.new(headers)
|
120
|
+
|
121
|
+
@headers["HTTP_METHOD"] = scanner.scan(/\w+/) or raise MalformedReqest
|
122
|
+
scanner.scan(/\s+/)
|
123
|
+
@headers["HTTP_PATH"] = scanner.scan(/\S+/) or raise MalformedReqest
|
124
|
+
scanner.scan(/\s+HTTP\//) or raise MalformedReqest
|
125
|
+
@headers["HTTP_VERSION"] = scanner.scan(/\S+/) or raise MalformedReqest
|
126
|
+
begin
|
127
|
+
scanner.scan(/\r\n/)
|
128
|
+
break if scanner.scan(/\r\n/) || scanner.eos?
|
129
|
+
key = scanner.scan(/\A[^:]+/) or break
|
130
|
+
scanner.scan(/\:\s*/) or break
|
131
|
+
value = scanner.scan(/.*\r\n/) or break
|
132
|
+
@headers[key] = value.strip if key && value
|
133
|
+
end while (key && value)
|
134
|
+
@headers_are_parsed = true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RFFW::Parser
|
2
|
+
class HttpResponse
|
3
|
+
|
4
|
+
def initialize(message, status = 200, headers = {"Content-Type" => 'text/html'}, version = "1.1" )
|
5
|
+
@message, @status, @headers, @version = message, status, headers, version
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
headers = @headers.map{|k,v|
|
10
|
+
"#{k}: #{v}\r\n"
|
11
|
+
}.join('')
|
12
|
+
|
13
|
+
response = ""
|
14
|
+
response << "HTTP/#{@version} #{@status.to_s}\r\n"
|
15
|
+
response << "Server: rffw/#{RFFW::VERSION}\r\n"
|
16
|
+
response << "Date: #{Time.now.httpdate}\r\n"
|
17
|
+
response << headers
|
18
|
+
|
19
|
+
if @headers["Transfer-Encoding"] && @headers["Transfer-Encoding"] == "chunked"
|
20
|
+
response << "\r\n"
|
21
|
+
else
|
22
|
+
response << "Content-Length: #{@message.size}\r\n" <<
|
23
|
+
"\r\n#{@message}"
|
24
|
+
end
|
25
|
+
response
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RFFW::Parser
|
2
|
+
module MimeParser
|
3
|
+
extend self
|
4
|
+
class Attachment < Struct.new(:boundary, :header, :data, :headers)
|
5
|
+
|
6
|
+
def header=(header)
|
7
|
+
return if header.nil?
|
8
|
+
@headers = {}
|
9
|
+
header.split("\r\n").each{|l|
|
10
|
+
head = l.split(":")
|
11
|
+
@headers[head[0]] = head[1].strip
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def content_disposition
|
16
|
+
@headers.find{|k,v| k.downcase == 'content-disposition' }.last
|
17
|
+
end
|
18
|
+
|
19
|
+
def content_type
|
20
|
+
@headers.find{|k,v| k.downcase == 'content-type' }.last
|
21
|
+
end
|
22
|
+
|
23
|
+
def filename
|
24
|
+
content_disposition[/filename\=\"([^"]*)\"/i,1]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
def parse(string)
|
32
|
+
scanner = StringScanner.new(string.force_encoding("BINARY"))
|
33
|
+
attachments = []
|
34
|
+
while !scanner.eos?
|
35
|
+
attachment = Attachment.new
|
36
|
+
attachment.boundary = scanner.scan_until(/\r\n/).strip
|
37
|
+
attachment.header = scanner.scan_until(/\r\n\r\n/)
|
38
|
+
pos = scanner.pos
|
39
|
+
scanner.scan_until(/\r\n#{Regexp.escape attachment.boundary}/)
|
40
|
+
attachment.data = scanner.pre_match.to_s[pos..-1]
|
41
|
+
attachments << attachment unless attachment.data.nil?
|
42
|
+
end
|
43
|
+
attachments
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/rffw/server.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'delegate'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'time'
|
5
|
+
require 'strscan'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module RFFW
|
9
|
+
module Server
|
10
|
+
require 'rffw/server/client'
|
11
|
+
require 'rffw/server/buffered_client'
|
12
|
+
require 'rffw/server/http_client'
|
13
|
+
require 'rffw/server/server'
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RFFW::Server
|
2
|
+
class BufferedClient < Client
|
3
|
+
|
4
|
+
@@buffer_size = 1024
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
@buffer = ''
|
9
|
+
@file = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_data
|
13
|
+
data = self.read_nonblock 1024*1024*1024
|
14
|
+
if !@file && @buffer.size + data.size > @@buffer_size
|
15
|
+
@file = Tempfile.new('rffw_request')
|
16
|
+
@file.write @buffer
|
17
|
+
@file.write data
|
18
|
+
@buffer = nil
|
19
|
+
elsif @file
|
20
|
+
@file.write data
|
21
|
+
else
|
22
|
+
@buffer << data
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def usign_temp_file?
|
27
|
+
!!@file
|
28
|
+
end
|
29
|
+
|
30
|
+
def buffer_io
|
31
|
+
usign_temp_file? ? @file : @buffer
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_disconnect
|
35
|
+
clear_buffer
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def buffer
|
40
|
+
if @file
|
41
|
+
@file.rewind
|
42
|
+
@file.read
|
43
|
+
else
|
44
|
+
@buffer
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def clear_buffer
|
49
|
+
if @file
|
50
|
+
@file.close
|
51
|
+
@file.unlink
|
52
|
+
@file = nil
|
53
|
+
end
|
54
|
+
@buffer = ''
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RFFW::Server
|
2
|
+
|
3
|
+
class Client < DelegateClass(TCPSocket)
|
4
|
+
extend Enumerable
|
5
|
+
|
6
|
+
@@clients = []
|
7
|
+
|
8
|
+
def initialize(socket)
|
9
|
+
Thread.exclusive {
|
10
|
+
@@clients << self
|
11
|
+
}
|
12
|
+
super(socket)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
Thread.exclusive {
|
17
|
+
@@clients.uniq!
|
18
|
+
}
|
19
|
+
@@clients
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.each(&block)
|
23
|
+
@@clients.each(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.find(client)
|
27
|
+
@@clients.find{|c| c == client}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_by_io(client)
|
31
|
+
@@clients.find{ |c| c.to_i == client.to_i }
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.disconnect_all!
|
35
|
+
while client = Thread.exclusive{@@clients.pop}
|
36
|
+
client.on_disconnect
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def finished?
|
41
|
+
return ((closed? || eof?) ? true : false)
|
42
|
+
rescue Errno::ECONNRESET
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_disconnect
|
47
|
+
close unless closed?
|
48
|
+
Thread.exclusive{ @@clients.delete self }
|
49
|
+
end
|
50
|
+
|
51
|
+
alias disconnect! on_disconnect
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RFFW::Server
|
2
|
+
class HttpClient < BufferedClient
|
3
|
+
include RFFW::Parser
|
4
|
+
attr_reader :request
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_data
|
11
|
+
super
|
12
|
+
@request = process_request(self.buffer)
|
13
|
+
on_request(request) if @request.finish?
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_request(request)
|
17
|
+
message = "Not found"
|
18
|
+
write ["HTTP/1.1 404 Not found",
|
19
|
+
"Server: rffw/#{RFFW::VERSION}",
|
20
|
+
"Date: #{Time.now.httpdate}",
|
21
|
+
"Content-Type: text/html",
|
22
|
+
"Content-Length: #{message.size}",
|
23
|
+
"Last-Modified: #{Time.now.httpdate}",
|
24
|
+
"Connection: keep-alive",
|
25
|
+
"Accept-Ranges: bytes",
|
26
|
+
"",
|
27
|
+
"#{message}"].join("\r\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_chunk(message)
|
31
|
+
unless defined?(@headers_sent) && @headers_sent == true
|
32
|
+
write HttpResponse.new("", 200, {"Transfer-Encoding" => "chunked"},'1.1')
|
33
|
+
@headers_sent = true
|
34
|
+
end
|
35
|
+
write generate_chunk(message)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def generate_chunk(text)
|
41
|
+
"#{text.size.to_s(16).upcase}\r\n#{text}\r\n\r\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def process_request(raw_request)
|
45
|
+
HttpRequest.new(raw_request)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RFFW::Server
|
2
|
+
class Server < TCPServer
|
3
|
+
include RFFW::Server
|
4
|
+
|
5
|
+
def initialize(handler = HttpClient, port = 8080, listen = "0.0.0.0" )
|
6
|
+
@handler = handler
|
7
|
+
super(listen,port)
|
8
|
+
catch :ctrl_c do
|
9
|
+
loop do
|
10
|
+
read, write, error = Kernel.select(@handler.all << self)
|
11
|
+
read.each do |io|
|
12
|
+
io == self ? process_server : process_client(io)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def process_server
|
21
|
+
begin
|
22
|
+
socket = accept_nonblock
|
23
|
+
@handler.new(socket)
|
24
|
+
rescue Errno::EAGAIN
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_client(io)
|
29
|
+
begin
|
30
|
+
if client = @handler.find_by_io(io)
|
31
|
+
client.finished? ? client.on_disconnect : client.on_data
|
32
|
+
else
|
33
|
+
raise "Sorry. I don't know how to manage this situation"
|
34
|
+
end
|
35
|
+
rescue IOError => e
|
36
|
+
$stderr.puts "Closed Stream"
|
37
|
+
client.disconnect!
|
38
|
+
rescue => e
|
39
|
+
client.disconnect!
|
40
|
+
$stderr.puts e.inspect
|
41
|
+
$stderr.puts e.backtrace
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|