rffw 0.0.1

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.
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