rffw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +5 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +4 -0
  4. data/README.md +60 -0
  5. data/Rakefile +41 -0
  6. data/bin/rffw +16 -0
  7. data/lib/rffw.rb +7 -0
  8. data/lib/rffw/app.rb +36 -0
  9. data/lib/rffw/app/app_handler.rb +59 -0
  10. data/lib/rffw/app/data/images/bg.png +0 -0
  11. data/lib/rffw/app/data/index.html +56 -0
  12. data/lib/rffw/app/data/javascripts/application.js +110 -0
  13. data/lib/rffw/app/data/stylesheets/style.css +338 -0
  14. data/lib/rffw/app/data/template.html +42 -0
  15. data/lib/rffw/app/db.rb +19 -0
  16. data/lib/rffw/app/description_handler.rb +12 -0
  17. data/lib/rffw/app/dir_handler.rb +36 -0
  18. data/lib/rffw/app/record.rb +43 -0
  19. data/lib/rffw/app/show_handler.rb +25 -0
  20. data/lib/rffw/app/upload_handler.rb +32 -0
  21. data/lib/rffw/app/upload_status_handler.rb +40 -0
  22. data/lib/rffw/app/urlencode_chars.data +224 -0
  23. data/lib/rffw/app/view_helpers.rb +30 -0
  24. data/lib/rffw/parser.rb +9 -0
  25. data/lib/rffw/parser/http_request.rb +137 -0
  26. data/lib/rffw/parser/http_response.rb +29 -0
  27. data/lib/rffw/parser/mime_parser.rb +46 -0
  28. data/lib/rffw/server.rb +15 -0
  29. data/lib/rffw/server/buffered_client.rb +57 -0
  30. data/lib/rffw/server/client.rb +53 -0
  31. data/lib/rffw/server/http_client.rb +49 -0
  32. data/lib/rffw/server/server.rb +46 -0
  33. data/lib/rffw/version.rb +3 -0
  34. data/rffw.db +0 -0
  35. data/rffw.gemspec +21 -0
  36. data/test/fixtures/image.jpg +0 -0
  37. data/test/fixtures/mime_image.data +0 -0
  38. data/test/fixtures/mime_image.png +0 -0
  39. data/test/fixtures/post_form_data.http +14 -0
  40. data/test/fixtures/raw_request.txt +8 -0
  41. data/test/fixtures/request.http +9 -0
  42. data/test/fixtures/upload.http +0 -0
  43. data/test/fixtures/upload_status_request.http +10 -0
  44. data/test/helper.rb +97 -0
  45. data/test/test_buffered_client.rb +50 -0
  46. data/test/test_client.rb +47 -0
  47. data/test/test_db.rb +21 -0
  48. data/test/test_http_client.rb +26 -0
  49. data/test/test_http_request.rb +52 -0
  50. data/test/test_mime_parser.rb +22 -0
  51. data/test/test_record.rb +49 -0
  52. data/test/test_upload_status_handler.rb +18 -0
  53. data/test/test_uploader_handler.rb +10 -0
  54. data/test/test_view_helpers.rb +24 -0
  55. 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
@@ -0,0 +1,9 @@
1
+
2
+
3
+ module RFFW
4
+ module Parser
5
+ require 'rffw/parser/http_request'
6
+ require 'rffw/parser/http_response'
7
+ require 'rffw/parser/mime_parser'
8
+ end
9
+ end
@@ -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
@@ -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