raw 0.49.0
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.
- data/doc/CONTRIBUTORS +106 -0
- data/doc/LICENSE +32 -0
- data/doc/coding_conventions.txt +11 -0
- data/lib/raw.rb +42 -0
- data/lib/raw/adapter.rb +113 -0
- data/lib/raw/adapter/cgi.rb +41 -0
- data/lib/raw/adapter/fastcgi.rb +48 -0
- data/lib/raw/adapter/mongrel.rb +146 -0
- data/lib/raw/adapter/script.rb +94 -0
- data/lib/raw/adapter/webrick.rb +144 -0
- data/lib/raw/adapter/webrick/vcr.rb +91 -0
- data/lib/raw/cgi.rb +323 -0
- data/lib/raw/cgi/cookie.rb +47 -0
- data/lib/raw/cgi/http.rb +62 -0
- data/lib/raw/compiler.rb +138 -0
- data/lib/raw/compiler/filter/cleanup.rb +21 -0
- data/lib/raw/compiler/filter/elements.rb +166 -0
- data/lib/raw/compiler/filter/elements/element.rb +210 -0
- data/lib/raw/compiler/filter/localization.rb +23 -0
- data/lib/raw/compiler/filter/markup.rb +32 -0
- data/lib/raw/compiler/filter/morph.rb +123 -0
- data/lib/raw/compiler/filter/morph/each.rb +34 -0
- data/lib/raw/compiler/filter/morph/for.rb +11 -0
- data/lib/raw/compiler/filter/morph/if.rb +26 -0
- data/lib/raw/compiler/filter/morph/selected_if.rb +43 -0
- data/lib/raw/compiler/filter/morph/standard.rb +55 -0
- data/lib/raw/compiler/filter/morph/times.rb +27 -0
- data/lib/raw/compiler/filter/script.rb +116 -0
- data/lib/raw/compiler/filter/squeeze.rb +16 -0
- data/lib/raw/compiler/filter/static_include.rb +74 -0
- data/lib/raw/compiler/filter/template.rb +121 -0
- data/lib/raw/compiler/reloader.rb +96 -0
- data/lib/raw/context.rb +154 -0
- data/lib/raw/context/flash.rb +157 -0
- data/lib/raw/context/global.rb +88 -0
- data/lib/raw/context/request.rb +338 -0
- data/lib/raw/context/response.rb +57 -0
- data/lib/raw/context/session.rb +198 -0
- data/lib/raw/context/session/drb.rb +11 -0
- data/lib/raw/context/session/file.rb +15 -0
- data/lib/raw/context/session/memcached.rb +13 -0
- data/lib/raw/context/session/memory.rb +12 -0
- data/lib/raw/context/session/og.rb +15 -0
- data/lib/raw/context/session/pstore.rb +13 -0
- data/lib/raw/control.rb +18 -0
- data/lib/raw/control/attribute.rb +91 -0
- data/lib/raw/control/attribute/checkbox.rb +25 -0
- data/lib/raw/control/attribute/datetime.rb +21 -0
- data/lib/raw/control/attribute/file.rb +20 -0
- data/lib/raw/control/attribute/fixnum.rb +26 -0
- data/lib/raw/control/attribute/float.rb +26 -0
- data/lib/raw/control/attribute/options.rb +38 -0
- data/lib/raw/control/attribute/password.rb +16 -0
- data/lib/raw/control/attribute/text.rb +16 -0
- data/lib/raw/control/attribute/textarea.rb +16 -0
- data/lib/raw/control/none.rb +16 -0
- data/lib/raw/control/relation.rb +59 -0
- data/lib/raw/control/relation/belongs_to.rb +0 -0
- data/lib/raw/control/relation/has_many.rb +97 -0
- data/lib/raw/control/relation/joins_many.rb +0 -0
- data/lib/raw/control/relation/many_to_many.rb +0 -0
- data/lib/raw/control/relation/refers_to.rb +29 -0
- data/lib/raw/controller.rb +37 -0
- data/lib/raw/controller/publishable.rb +160 -0
- data/lib/raw/dispatcher.rb +209 -0
- data/lib/raw/dispatcher/format.rb +108 -0
- data/lib/raw/dispatcher/format/atom.rb +31 -0
- data/lib/raw/dispatcher/format/css.rb +0 -0
- data/lib/raw/dispatcher/format/html.rb +42 -0
- data/lib/raw/dispatcher/format/json.rb +31 -0
- data/lib/raw/dispatcher/format/rss.rb +33 -0
- data/lib/raw/dispatcher/format/xoxo.rb +31 -0
- data/lib/raw/dispatcher/mounter.rb +60 -0
- data/lib/raw/dispatcher/router.rb +111 -0
- data/lib/raw/errors.rb +19 -0
- data/lib/raw/helper.rb +86 -0
- data/lib/raw/helper/benchmark.rb +23 -0
- data/lib/raw/helper/buffer.rb +60 -0
- data/lib/raw/helper/cookie.rb +32 -0
- data/lib/raw/helper/debug.rb +28 -0
- data/lib/raw/helper/default.rb +16 -0
- data/lib/raw/helper/feed.rb +451 -0
- data/lib/raw/helper/form.rb +284 -0
- data/lib/raw/helper/javascript.rb +59 -0
- data/lib/raw/helper/layout.rb +40 -0
- data/lib/raw/helper/navigation.rb +87 -0
- data/lib/raw/helper/pager.rb +305 -0
- data/lib/raw/helper/table.rb +247 -0
- data/lib/raw/helper/xhtml.rb +218 -0
- data/lib/raw/helper/xml.rb +125 -0
- data/lib/raw/mixin/magick.rb +35 -0
- data/lib/raw/mixin/sweeper.rb +71 -0
- data/lib/raw/mixin/thumbnails.rb +1 -0
- data/lib/raw/mixin/webfile.rb +165 -0
- data/lib/raw/render.rb +271 -0
- data/lib/raw/render/builder.rb +26 -0
- data/lib/raw/render/caching.rb +81 -0
- data/lib/raw/render/call.rb +43 -0
- data/lib/raw/render/send_file.rb +46 -0
- data/lib/raw/render/stream.rb +39 -0
- data/lib/raw/scaffold.rb +13 -0
- data/lib/raw/scaffold/controller.rb +25 -0
- data/lib/raw/scaffold/model.rb +157 -0
- data/lib/raw/test.rb +5 -0
- data/lib/raw/test/assertions.rb +169 -0
- data/lib/raw/test/context.rb +55 -0
- data/lib/raw/test/testcase.rb +79 -0
- data/lib/raw/util/attr.rb +128 -0
- data/lib/raw/util/encode_uri.rb +149 -0
- data/lib/raw/util/html_filter.rb +538 -0
- data/lib/raw/util/markup.rb +130 -0
- data/test/glue/tc_webfile.rb +1 -0
- data/test/nitro/CONFIG.rb +3 -0
- data/test/nitro/adapter/raw_post1.bin +9 -0
- data/test/nitro/adapter/tc_webrick.rb +16 -0
- data/test/nitro/cgi/tc_cookie.rb +14 -0
- data/test/nitro/cgi/tc_request.rb +61 -0
- data/test/nitro/compiler/tc_client_morpher.rb +47 -0
- data/test/nitro/compiler/tc_compiler.rb +25 -0
- data/test/nitro/dispatcher/tc_mounter.rb +47 -0
- data/test/nitro/helper/tc_feed.rb +135 -0
- data/test/nitro/helper/tc_navbar.rb +74 -0
- data/test/nitro/helper/tc_pager.rb +35 -0
- data/test/nitro/helper/tc_table.rb +68 -0
- data/test/nitro/helper/tc_xhtml.rb +19 -0
- data/test/nitro/tc_caching.rb +19 -0
- data/test/nitro/tc_cgi.rb +222 -0
- data/test/nitro/tc_context.rb +17 -0
- data/test/nitro/tc_controller.rb +103 -0
- data/test/nitro/tc_controller_aspect.rb +32 -0
- data/test/nitro/tc_controller_params.rb +885 -0
- data/test/nitro/tc_dispatcher.rb +109 -0
- data/test/nitro/tc_element.rb +85 -0
- data/test/nitro/tc_flash.rb +59 -0
- data/test/nitro/tc_helper.rb +47 -0
- data/test/nitro/tc_render.rb +119 -0
- data/test/nitro/tc_router.rb +61 -0
- data/test/nitro/tc_server.rb +35 -0
- data/test/nitro/tc_session.rb +66 -0
- data/test/nitro/tc_template.rb +71 -0
- data/test/nitro/util/tc_encode_url.rb +87 -0
- data/test/nitro/util/tc_markup.rb +31 -0
- data/test/public/blog/another/very_litle/index.xhtml +1 -0
- data/test/public/blog/inc1.xhtml +2 -0
- data/test/public/blog/inc2.xhtml +1 -0
- data/test/public/blog/list.xhtml +9 -0
- data/test/public/dummy_mailer/registration.xhtml +5 -0
- metadata +244 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require "raw/adapter"
|
|
2
|
+
|
|
3
|
+
module Raw
|
|
4
|
+
|
|
5
|
+
# The script adapter. Useful when running in console mode, or
|
|
6
|
+
# when creating scripts for cron jobs, testing and more. Allows
|
|
7
|
+
# you to programmatically 'drive' the web application.
|
|
8
|
+
|
|
9
|
+
class ScriptAdapter < Adapter
|
|
10
|
+
include AdapterHandlerMixin
|
|
11
|
+
|
|
12
|
+
# The last generated response.
|
|
13
|
+
|
|
14
|
+
attr_accessor :response
|
|
15
|
+
|
|
16
|
+
def start(application)
|
|
17
|
+
info "This console is attached to the application context."
|
|
18
|
+
info ""
|
|
19
|
+
info "* $app points to the application"
|
|
20
|
+
info "* $srv points to the adapter"
|
|
21
|
+
info "* use get(uri), post(uri), response() to programmatically call actions"
|
|
22
|
+
info ""
|
|
23
|
+
|
|
24
|
+
$app = $application = @application = application
|
|
25
|
+
$srv = $adapter = self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Perform a programatic http request to the web app.
|
|
29
|
+
#
|
|
30
|
+
# === Examples
|
|
31
|
+
#
|
|
32
|
+
# $srv.get 'users/logout'
|
|
33
|
+
# $srv.post 'users/login', :params => { :name => 'gmosx', :password => 'pass' }
|
|
34
|
+
# $srv.post 'users/login?name=gmosx;password=pass
|
|
35
|
+
# $srv.post 'articles/view/1'
|
|
36
|
+
|
|
37
|
+
def handle(uri, options = {})
|
|
38
|
+
# Perform default rewriting rules.
|
|
39
|
+
# rewrite(req)
|
|
40
|
+
|
|
41
|
+
context = Context.new(@application)
|
|
42
|
+
|
|
43
|
+
context.get_params = options.fetch(:get_params, {})
|
|
44
|
+
context.post_params = options.fetch(:post_params, {})
|
|
45
|
+
context.headers = options.fetch(:headers, {})
|
|
46
|
+
|
|
47
|
+
context.headers["REQUEST_URI"] = uri
|
|
48
|
+
context.headers["REQUEST_METHOD"] = options.fetch(:method, :get)
|
|
49
|
+
context.headers["HTTP_COOKIE"] ||= options[:cookies]
|
|
50
|
+
|
|
51
|
+
handle_context(context)
|
|
52
|
+
|
|
53
|
+
@response = context
|
|
54
|
+
|
|
55
|
+
context.close
|
|
56
|
+
|
|
57
|
+
return context
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Perform a programmatic http get request to the web app.
|
|
61
|
+
|
|
62
|
+
def get(uri, options = {})
|
|
63
|
+
options[:method] = "get"
|
|
64
|
+
handle(uri, options)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Perform a programmatic http post request to the web app.
|
|
68
|
+
|
|
69
|
+
def post(uri, options = {})
|
|
70
|
+
options[:method] = "post"
|
|
71
|
+
handle(uri, options)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Add some convienience methods.
|
|
79
|
+
|
|
80
|
+
if ENV["NITRO_ADAPTER"] == "script"
|
|
81
|
+
|
|
82
|
+
def get(*args)
|
|
83
|
+
$srv.get(*args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def post(*args)
|
|
87
|
+
$srv.post(*args)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def response
|
|
91
|
+
$srv.response
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require "stringio"
|
|
2
|
+
require "webrick"
|
|
3
|
+
|
|
4
|
+
require "raw/adapter"
|
|
5
|
+
|
|
6
|
+
module Raw
|
|
7
|
+
|
|
8
|
+
# A Webrick Adapter for Nitro. Webrick is a pure Ruby web server
|
|
9
|
+
# included in the default Ruby distribution. The Webrick Adapter
|
|
10
|
+
# is the prefered adapter in development/debug environments. It
|
|
11
|
+
# is also extremely easy to setup.
|
|
12
|
+
#
|
|
13
|
+
# However, for live/production environments, you should prefer
|
|
14
|
+
# a more performant adapter like Mongrel or FCGI. Mongrel is the
|
|
15
|
+
# suggested adapter for production applications.
|
|
16
|
+
|
|
17
|
+
class WebrickAdapter < Adapter
|
|
18
|
+
|
|
19
|
+
class Swallow # :nodoc: all
|
|
20
|
+
def self.method_missing(*args)
|
|
21
|
+
# drink it!
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Start the adapter.
|
|
26
|
+
|
|
27
|
+
def start(app)
|
|
28
|
+
super
|
|
29
|
+
|
|
30
|
+
if RUBY_PLATFORM !~ /mswin32/
|
|
31
|
+
wblog = WEBrick::BasicLog::new("/dev/null")
|
|
32
|
+
elsif File.exist? "log"
|
|
33
|
+
wblog = WEBrick::BasicLog::new("log/access.log")
|
|
34
|
+
else
|
|
35
|
+
wblog = STDERR
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
webrick_options = app.options.dup
|
|
39
|
+
|
|
40
|
+
require "webrick/https" if webrick_options[:SSLEnable]
|
|
41
|
+
|
|
42
|
+
webrick_options.update(
|
|
43
|
+
:BindAddress => app.address,
|
|
44
|
+
:Port => app.port,
|
|
45
|
+
:DocumentRoot => app.public_dir,
|
|
46
|
+
:Logger => Swallow,
|
|
47
|
+
:AccessLog => [
|
|
48
|
+
[wblog, WEBrick::AccessLog::COMMON_LOG_FORMAT],
|
|
49
|
+
[wblog, WEBrick::AccessLog::REFERER_LOG_FORMAT]
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
trap("INT") { stop }
|
|
54
|
+
|
|
55
|
+
@webrick = WEBrick::HTTPServer.new(webrick_options)
|
|
56
|
+
@webrick.mount("/", WebrickHandler, app)
|
|
57
|
+
@webrick.start
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Stop the adapter.
|
|
61
|
+
|
|
62
|
+
def stop
|
|
63
|
+
super
|
|
64
|
+
@webrick.shutdown
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The Webrick Handler, handles an HTTP request.
|
|
70
|
+
#--
|
|
71
|
+
# TODO: add some way to prevent the display of template files
|
|
72
|
+
# if the public dir is used as the template dir.
|
|
73
|
+
#++
|
|
74
|
+
|
|
75
|
+
class WebrickHandler < WEBrick::HTTPServlet::AbstractServlet
|
|
76
|
+
include WEBrick
|
|
77
|
+
include AdapterHandlerMixin
|
|
78
|
+
|
|
79
|
+
def initialize(webrick, app)
|
|
80
|
+
@application = app
|
|
81
|
+
|
|
82
|
+
# Handles static resources. Useful when running
|
|
83
|
+
# a stand-alone webrick server.
|
|
84
|
+
|
|
85
|
+
@file_handler = WEBrick::HTTPServlet::FileHandler.new(
|
|
86
|
+
webrick, app.public_dir, app.options
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Handle a static file. Also handles cached pages. Typically
|
|
91
|
+
# *not* used in production applications.
|
|
92
|
+
|
|
93
|
+
def handle_file(req, res)
|
|
94
|
+
return false unless @application.handle_static_files
|
|
95
|
+
@file_handler.do_GET(req, res)
|
|
96
|
+
return true
|
|
97
|
+
rescue WEBrick::HTTPStatus::PartialContent, WEBrick::HTTPStatus::NotModified => err
|
|
98
|
+
res.set_error(err)
|
|
99
|
+
return true
|
|
100
|
+
rescue WEBrick::HTTPStatus::NotFound => ex
|
|
101
|
+
return false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Handle the request.
|
|
105
|
+
|
|
106
|
+
def handle(req, res)
|
|
107
|
+
# Perform default rewriting rules.
|
|
108
|
+
|
|
109
|
+
rewrite(req)
|
|
110
|
+
|
|
111
|
+
# First, try to serve a static file from disk.
|
|
112
|
+
|
|
113
|
+
return if handle_file(req, res)
|
|
114
|
+
|
|
115
|
+
# No static file found, attempt to dynamically generate
|
|
116
|
+
# a response.
|
|
117
|
+
|
|
118
|
+
context = Context.new(@application)
|
|
119
|
+
|
|
120
|
+
context.in = StringIO.new(req.body || "")
|
|
121
|
+
|
|
122
|
+
context.headers = {}
|
|
123
|
+
req.header.each { |h, v| context.headers[h.upcase] = v.first }
|
|
124
|
+
context.headers.update(req.meta_vars)
|
|
125
|
+
|
|
126
|
+
# gmosx: make compatible with fastcgi.
|
|
127
|
+
|
|
128
|
+
context.headers["REQUEST_URI"].slice!(/http:\/\/(.*?)\//)
|
|
129
|
+
context.headers["REQUEST_URI"] = '/' + context.headers['REQUEST_URI']
|
|
130
|
+
|
|
131
|
+
res.body = handle_context(context)
|
|
132
|
+
|
|
133
|
+
res.status = context.status
|
|
134
|
+
res.instance_variable_set(:@header, context.response_headers || {})
|
|
135
|
+
res.instance_variable_set(:@cookies, context.response_cookies || {})
|
|
136
|
+
|
|
137
|
+
context.close
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
alias_method :do_GET, :handle
|
|
141
|
+
alias_method :do_POST, :handle
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require "raw/adapter/webrick"
|
|
2
|
+
|
|
3
|
+
module Raw
|
|
4
|
+
|
|
5
|
+
class WebrickAdapter < Adapter
|
|
6
|
+
|
|
7
|
+
def setup(server)
|
|
8
|
+
if $record_session_filename
|
|
9
|
+
require "raw/adapter/webrick/vcr"
|
|
10
|
+
vcr_record($record_session_filename)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if $playback_session_filename
|
|
14
|
+
require "raw/adapter/webrick/vcr"
|
|
15
|
+
vcr_playback($playback_session_filename)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Enables session recording. The recorded data can be used
|
|
20
|
+
# for automatic app testing by means of the playback mode.
|
|
21
|
+
|
|
22
|
+
def vcr_record(filename = "session.yaml")
|
|
23
|
+
info "Recording application server session to '#{filename}'."
|
|
24
|
+
|
|
25
|
+
require "facets/core/file/self/create"
|
|
26
|
+
|
|
27
|
+
$record_session = []
|
|
28
|
+
$last_record_time = Time.now
|
|
29
|
+
|
|
30
|
+
Nitro::WebrickHandler.class_eval do
|
|
31
|
+
def do_GET(req, res)
|
|
32
|
+
record_context(req, res)
|
|
33
|
+
handle(req, res)
|
|
34
|
+
end
|
|
35
|
+
alias_method :do_POST, :do_GET
|
|
36
|
+
|
|
37
|
+
def record_context(req, res)
|
|
38
|
+
delta = Time.now - $last_record_time
|
|
39
|
+
$last_record_time = Time.now
|
|
40
|
+
$record_session << [delta, req, res]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
at_exit do
|
|
45
|
+
File.create(filename, YAML.dump($record_session))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Playback a recorded session. Typically used for testing.
|
|
50
|
+
|
|
51
|
+
def vcr_playback(filename = "session.yaml")
|
|
52
|
+
info "Playing back application server session from '#{filename}'."
|
|
53
|
+
|
|
54
|
+
$playback_session = YAML.load_file(filename)
|
|
55
|
+
$playback_exception_count = 0
|
|
56
|
+
|
|
57
|
+
WEBrick::HTTPServer.class_eval do
|
|
58
|
+
def start(&block)
|
|
59
|
+
run(nil)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run(sock)
|
|
63
|
+
while true
|
|
64
|
+
delta, req, res = $playback_session.shift
|
|
65
|
+
|
|
66
|
+
if delta
|
|
67
|
+
sleep(delta)
|
|
68
|
+
begin
|
|
69
|
+
service(req, res)
|
|
70
|
+
rescue Object => ex
|
|
71
|
+
$playback_exception_count += 1
|
|
72
|
+
p "---", ex
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
return
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
at_exit do
|
|
82
|
+
puts "\n\n"
|
|
83
|
+
puts "Playback raised #$playback_exception_count exceptions.\n"
|
|
84
|
+
puts "\n"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
|
data/lib/raw/cgi.rb
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "stringio"
|
|
3
|
+
|
|
4
|
+
require "facets/more/settings"
|
|
5
|
+
|
|
6
|
+
require "raw/cgi/http"
|
|
7
|
+
|
|
8
|
+
module Raw
|
|
9
|
+
|
|
10
|
+
# Nitro CGI (Common Gateway Interface) methods. Typically
|
|
11
|
+
# handles HTTP Request parsing and HTTP Response generation.
|
|
12
|
+
|
|
13
|
+
class Cgi
|
|
14
|
+
include Http
|
|
15
|
+
|
|
16
|
+
# Maximum content length allowed in requests.
|
|
17
|
+
|
|
18
|
+
setting :max_content_length, :default => (2 * 1024 * 1024), :doc => 'Maximum content length allowed in requests'
|
|
19
|
+
|
|
20
|
+
# Multipart parsing buffer size.
|
|
21
|
+
|
|
22
|
+
setting :buffer_size, :default => (10 * 1024), :doc => 'Multipart parsing buffer size'
|
|
23
|
+
|
|
24
|
+
# Process a CGI request. This is a general method reused by
|
|
25
|
+
# many adapters.
|
|
26
|
+
#--
|
|
27
|
+
# A CGI request is process-based so there is no need for Og
|
|
28
|
+
# connection cleanup.
|
|
29
|
+
#++
|
|
30
|
+
|
|
31
|
+
def self.process(server, cgi, inp, out)
|
|
32
|
+
context = Context.new(server)
|
|
33
|
+
|
|
34
|
+
unless inp.respond_to?(:rewind)
|
|
35
|
+
# The module Request#raw_body requires a rewind method,
|
|
36
|
+
# so if the input stream doesn't have one, *cough* FCgi,
|
|
37
|
+
# we convert it to a StringIO.
|
|
38
|
+
|
|
39
|
+
inp = StringIO.new(inp.read.to_s) # if read returns nil, to_s makes it ""
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context.in = inp
|
|
43
|
+
context.headers = cgi.env
|
|
44
|
+
|
|
45
|
+
#--
|
|
46
|
+
# gmosx: only handle nitro requests.
|
|
47
|
+
#++
|
|
48
|
+
# gmosx: QUERY_STRING is sometimes not populated.
|
|
49
|
+
|
|
50
|
+
if context.query_string.empty? and context.uri =~ /\?/
|
|
51
|
+
context.headers['QUERY_STRING'] = context.uri.split('?').last
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Cgi.parse_params(context)
|
|
55
|
+
Cgi.parse_cookies(context)
|
|
56
|
+
context.render(context.path)
|
|
57
|
+
|
|
58
|
+
out.print(Cgi.response_headers(context))
|
|
59
|
+
|
|
60
|
+
if context.out.is_a?(IO)
|
|
61
|
+
begin
|
|
62
|
+
while buf = context.out.read(4096)
|
|
63
|
+
out.write(buf)
|
|
64
|
+
end
|
|
65
|
+
ensure
|
|
66
|
+
context.out.close
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
out.print(context.out)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
$autoreload_dirty = false
|
|
73
|
+
|
|
74
|
+
context.close
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# push a parameter into the params hash
|
|
78
|
+
|
|
79
|
+
def self.structure_param(params, key, val)
|
|
80
|
+
if key =~ /(.+)\[(.+)\]$/ or key =~ /([^\.]+)\.(.+)$/
|
|
81
|
+
params[$1] ||= Dictionary.new
|
|
82
|
+
params[$1] = structure_param(params[$1], $2, val)
|
|
83
|
+
elsif key =~ /(.+)\[\]$/
|
|
84
|
+
params[$1] ||= Array.new
|
|
85
|
+
params[$1] << val.to_s
|
|
86
|
+
else
|
|
87
|
+
params[key] = val.nil? ? nil : val
|
|
88
|
+
end
|
|
89
|
+
return params
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns a hash with the pairs from the query
|
|
93
|
+
# string. The implicit hash construction that is done
|
|
94
|
+
# in parse_request_params is not done here.
|
|
95
|
+
|
|
96
|
+
def self.parse_query_string(query_string)
|
|
97
|
+
params = Dictionary.new
|
|
98
|
+
|
|
99
|
+
# gmosx, THINK: better return nil here?
|
|
100
|
+
return params if (query_string.nil? or query_string.empty?)
|
|
101
|
+
|
|
102
|
+
query_string.split(/[&;]/).each do |p|
|
|
103
|
+
key, val = p.split('=')
|
|
104
|
+
|
|
105
|
+
key = CGI.unescape(key) unless key.nil?
|
|
106
|
+
val = CGI.unescape(val) unless val.nil?
|
|
107
|
+
|
|
108
|
+
params = self.structure_param(params, key, val)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
return params
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Parse the HTTP_COOKIE header and returns the
|
|
115
|
+
# cookies as a key->value hash. For efficiency no
|
|
116
|
+
# cookie objects are created.
|
|
117
|
+
#
|
|
118
|
+
# [+context+]
|
|
119
|
+
# The context
|
|
120
|
+
|
|
121
|
+
def self.parse_cookies(context)
|
|
122
|
+
env = context.env
|
|
123
|
+
|
|
124
|
+
# FIXME: dont precreate?
|
|
125
|
+
context.cookies = {}
|
|
126
|
+
|
|
127
|
+
#if env.has_key?('HTTP_COOKIE') or env.has_key?('COOKIE')
|
|
128
|
+
if env['HTTP_COOKIE'] or env['COOKIE']
|
|
129
|
+
(env['HTTP_COOKIE'] or env['COOKIE']).split(/; /).each do |c|
|
|
130
|
+
key, val = c.split(/=/, 2)
|
|
131
|
+
val ||= ""
|
|
132
|
+
key = CGI.unescape(key)
|
|
133
|
+
val = val.split(/&/).collect{|v| CGI::unescape(v)}.join("\0")
|
|
134
|
+
|
|
135
|
+
if context.cookies.include?(key)
|
|
136
|
+
context.cookies[key] += "\0" + val
|
|
137
|
+
else
|
|
138
|
+
context.cookies[key] = val
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Build the response headers for the context.
|
|
145
|
+
#
|
|
146
|
+
# [+context+]
|
|
147
|
+
# The context of the response.
|
|
148
|
+
#
|
|
149
|
+
# [+proto+]
|
|
150
|
+
# If true emmit the protocol line. Useful for MOD_RUBY.
|
|
151
|
+
#--
|
|
152
|
+
# FIXME: return the correct protocol from env.
|
|
153
|
+
# TODO: Perhaps I can optimize status calc.
|
|
154
|
+
#++
|
|
155
|
+
|
|
156
|
+
def self.response_headers(context, proto = false)
|
|
157
|
+
reason = STATUS_STRINGS[context.status]
|
|
158
|
+
|
|
159
|
+
if proto
|
|
160
|
+
buf = "HTTP/1.1 #{context.status} #{reason}#{EOL}Date: #{CGI::rfc1123_date(Time.now)}#{EOL}"
|
|
161
|
+
else
|
|
162
|
+
buf = "Status: #{context.status} #{reason}#{EOL}"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
context.response_headers.each do |key, value|
|
|
166
|
+
tmp = key.gsub(/\bwww|^te$|\b\w/) { |s| s.upcase }
|
|
167
|
+
buf << "#{tmp}: #{value}" << EOL
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context.response_cookies.each do |cookie|
|
|
171
|
+
buf << "Set-Cookie: " << cookie.to_s << EOL
|
|
172
|
+
end if context.response_cookies
|
|
173
|
+
|
|
174
|
+
buf << EOL
|
|
175
|
+
|
|
176
|
+
return buf
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Initialize the request params.
|
|
180
|
+
# Handles multipart forms (in particular, forms that involve
|
|
181
|
+
# file uploads). Reads query parameters in the @params field,
|
|
182
|
+
# and cookies into @cookies.
|
|
183
|
+
|
|
184
|
+
def self.parse_params(context)
|
|
185
|
+
method = context.method
|
|
186
|
+
if (:post == method) and
|
|
187
|
+
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(context.headers['CONTENT_TYPE'])
|
|
188
|
+
boundary = $1.dup
|
|
189
|
+
context.post_params = parse_multipart(context, boundary)
|
|
190
|
+
|
|
191
|
+
# Also include the URI parameters.
|
|
192
|
+
context.get_params.update(Cgi.parse_query_string(context.query_string))
|
|
193
|
+
else
|
|
194
|
+
case method
|
|
195
|
+
when :get, :head
|
|
196
|
+
context.get_params = Cgi.parse_query_string(context.query_string)
|
|
197
|
+
when :post
|
|
198
|
+
context.in.binmode if defined?(context.in.binmode)
|
|
199
|
+
context.post_params = Cgi.parse_query_string(context.in.read(context.content_length) || '')
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Parse a multipart request.
|
|
205
|
+
# Adapted from Ruby's cgi.rb
|
|
206
|
+
#--
|
|
207
|
+
# TODO: optimize and rationalize this.
|
|
208
|
+
#++
|
|
209
|
+
|
|
210
|
+
def self.parse_multipart(context, boundary)
|
|
211
|
+
input = context.in
|
|
212
|
+
content_length = context.content_length
|
|
213
|
+
env_table = context.env
|
|
214
|
+
|
|
215
|
+
params = Hash.new()
|
|
216
|
+
boundary = "--" + boundary
|
|
217
|
+
quoted_boundary = Regexp.quote(boundary, "n")
|
|
218
|
+
buf = ""
|
|
219
|
+
boundary_end=""
|
|
220
|
+
|
|
221
|
+
# start multipart/form-data
|
|
222
|
+
input.binmode if defined? input.binmode
|
|
223
|
+
boundary_size = boundary.size + EOL.size
|
|
224
|
+
content_length -= boundary_size
|
|
225
|
+
status = input.read(boundary_size)
|
|
226
|
+
|
|
227
|
+
if nil == status
|
|
228
|
+
raise EOFError, "no content body"
|
|
229
|
+
elsif boundary + EOL != status
|
|
230
|
+
raise EOFError, "bad content body"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
loop do
|
|
234
|
+
head = nil
|
|
235
|
+
|
|
236
|
+
if 10240 < content_length
|
|
237
|
+
body = Tempfile.new("CGI")
|
|
238
|
+
else
|
|
239
|
+
begin
|
|
240
|
+
require "stringio"
|
|
241
|
+
body = StringIO.new
|
|
242
|
+
rescue LoadError
|
|
243
|
+
body = Tempfile.new("CGI")
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
body.binmode if defined? body.binmode
|
|
247
|
+
|
|
248
|
+
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
|
|
249
|
+
|
|
250
|
+
if (not head) and /#{EOL}#{EOL}/n.match(buf)
|
|
251
|
+
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
|
|
252
|
+
head = $1.dup
|
|
253
|
+
""
|
|
254
|
+
end
|
|
255
|
+
next
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
if head and ( (EOL + boundary + EOL).size < buf.size )
|
|
259
|
+
body.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
|
|
260
|
+
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
c = if Cgi.buffer_size < content_length
|
|
264
|
+
input.read(Cgi.buffer_size)
|
|
265
|
+
else
|
|
266
|
+
input.read(content_length)
|
|
267
|
+
end
|
|
268
|
+
if c.nil? || c.empty?
|
|
269
|
+
raise EOFError, "bad content body"
|
|
270
|
+
end
|
|
271
|
+
buf.concat(c)
|
|
272
|
+
content_length -= c.size
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
|
|
276
|
+
body.print $1
|
|
277
|
+
if "--" == $2
|
|
278
|
+
content_length = -1
|
|
279
|
+
end
|
|
280
|
+
boundary_end = $2.dup
|
|
281
|
+
""
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
body.rewind
|
|
285
|
+
|
|
286
|
+
/Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head)
|
|
287
|
+
|
|
288
|
+
filename = ($1 or "")
|
|
289
|
+
|
|
290
|
+
if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and
|
|
291
|
+
/Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and
|
|
292
|
+
(not /MSIE/ni.match(env_table['HTTP_USER_AGENT']))
|
|
293
|
+
filename = CGI::unescape(filename)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
/Content-Type: (.*)/ni.match(head)
|
|
297
|
+
content_type = ($1 or "")
|
|
298
|
+
|
|
299
|
+
(class << body; self; end).class_eval do
|
|
300
|
+
alias_method :local_path, :path
|
|
301
|
+
define_method(:original_filename) { filename.dup.taint }
|
|
302
|
+
define_method(:content_type) { content_type.dup.taint }
|
|
303
|
+
|
|
304
|
+
# gmosx: this hides the performance hit!!
|
|
305
|
+
define_method(:to_s) { str = read; rewind; return str}
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
|
|
309
|
+
name = $1.dup
|
|
310
|
+
|
|
311
|
+
params = self.structure_param(params, name, body)
|
|
312
|
+
|
|
313
|
+
break if buf.size == 0
|
|
314
|
+
break if content_length === -1
|
|
315
|
+
end
|
|
316
|
+
raise EOFError, "bad boundary end of body" unless boundary_end =~ /--/
|
|
317
|
+
|
|
318
|
+
return params
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
end
|