arrow 1.0.7
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/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- metadata +339 -0
@@ -0,0 +1,608 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'arrow/mixins'
|
7
|
+
require 'arrow/exceptions'
|
8
|
+
require 'arrow/object'
|
9
|
+
require 'arrow/cookie'
|
10
|
+
require 'arrow/cookieset'
|
11
|
+
require 'arrow/session'
|
12
|
+
require 'arrow/acceptparam'
|
13
|
+
require 'arrow/constants'
|
14
|
+
|
15
|
+
|
16
|
+
# The Arrow::Transaction class, a derivative of
|
17
|
+
# Arrow::Object. Instances of this class encapsulate a transaction within a web
|
18
|
+
# application implemented using the Arrow application framework.
|
19
|
+
#
|
20
|
+
# == Authors
|
21
|
+
#
|
22
|
+
# * Michael Granger <ged@FaerieMUD.org>
|
23
|
+
#
|
24
|
+
# Please see the file LICENSE in the top-level directory for licensing details.
|
25
|
+
#
|
26
|
+
class Arrow::Transaction < Arrow::Object
|
27
|
+
extend Forwardable
|
28
|
+
include Arrow::Constants
|
29
|
+
|
30
|
+
# Regex to match the mimetypes that browsers use for sending form data
|
31
|
+
FORM_CONTENT_TYPES = %r{application/x-www-form-urlencoded|multipart/form-data}i
|
32
|
+
|
33
|
+
# A minimal HTML document for #status_doc
|
34
|
+
HTML_DOC = <<-"EOF".gsub(/^\t/, '')
|
35
|
+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
36
|
+
<html>
|
37
|
+
<head><title>%d %s</title></head>
|
38
|
+
<body><h1>%s</h1><p>%s</p></body>
|
39
|
+
</html>
|
40
|
+
EOF
|
41
|
+
|
42
|
+
# Names for redirect statuses for #status_doc
|
43
|
+
STATUS_NAME = {
|
44
|
+
300 => "Multiple Choices",
|
45
|
+
301 => "Moved Permanently",
|
46
|
+
302 => "Found",
|
47
|
+
303 => "See Other",
|
48
|
+
304 => "Not Modified",
|
49
|
+
305 => "Use Proxy",
|
50
|
+
307 => "Temporary Redirect",
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
# Methods that the transaction delegates to the underlying request
|
55
|
+
# object. If we're running inside mod_ruby, get the list from the class.
|
56
|
+
if defined?( Apache ) && defined?( Apache::Request )
|
57
|
+
DelegatedMethods = Apache::Request.instance_methods(false) - [
|
58
|
+
"inspect", "to_s"
|
59
|
+
]
|
60
|
+
|
61
|
+
# Otherwise, just use the ones that were define when this was written (2007/03/20)
|
62
|
+
else
|
63
|
+
raise "No mod_ruby loaded. Try requiring 'apache/fakerequest' " +
|
64
|
+
"to emulate the Apache environment."
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
#############################################################
|
69
|
+
### I N S T A N C E M E T H O D S
|
70
|
+
#############################################################
|
71
|
+
|
72
|
+
### Create a new Arrow::Transaction object with the specified +request+
|
73
|
+
### (an Apache::Request object), +config+ (an Arrow::Config object),
|
74
|
+
### +broker+ object (an Arrow::Broker), and +session+ (Arrow::Session)
|
75
|
+
### objects.
|
76
|
+
def initialize( request, config, broker )
|
77
|
+
@request = request
|
78
|
+
@config = config
|
79
|
+
@broker = broker
|
80
|
+
@handler_status = Apache::OK
|
81
|
+
|
82
|
+
@serial = make_transaction_serial( request )
|
83
|
+
|
84
|
+
# Stuff that may be filled in later
|
85
|
+
@session = nil # Lazily-instantiated
|
86
|
+
@applet_path = nil # Added by the broker
|
87
|
+
@vargs = nil # Filled in by the applet
|
88
|
+
@data = {}
|
89
|
+
@request_cookies = parse_cookies( request )
|
90
|
+
@cookies = Arrow::CookieSet.new()
|
91
|
+
@accepted_types = nil
|
92
|
+
|
93
|
+
# Check for a "RubyOption root_dispatcher true"
|
94
|
+
if @request.options.key?('root_dispatcher') &&
|
95
|
+
@request.options['root_dispatcher'].match( /^(true|yes|1)$/i )
|
96
|
+
self.log.debug "Dispatching from root path"
|
97
|
+
@root_dispatcher = true
|
98
|
+
else
|
99
|
+
self.log.debug "Dispatching from sub-path"
|
100
|
+
@root_dispatcher = false
|
101
|
+
end
|
102
|
+
|
103
|
+
@request.sync_header = true
|
104
|
+
super()
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
######
|
109
|
+
public
|
110
|
+
######
|
111
|
+
|
112
|
+
# Set up some delegators if running inside Apache
|
113
|
+
def_delegators :@request, *DelegatedMethods
|
114
|
+
|
115
|
+
|
116
|
+
# The Apache::Request that initiated this transaction
|
117
|
+
attr_reader :request
|
118
|
+
|
119
|
+
# The Arrow::Config object for the Arrow application that created this
|
120
|
+
# transaction.
|
121
|
+
attr_reader :config
|
122
|
+
|
123
|
+
# The Arrow::Broker that is responsible for delegating the Transaction
|
124
|
+
# to one or more Arrow::Applet objects.
|
125
|
+
attr_reader :broker
|
126
|
+
|
127
|
+
# The argument validator (a FormValidator object)
|
128
|
+
attr_accessor :vargs
|
129
|
+
|
130
|
+
# The applet portion of the path_info
|
131
|
+
attr_accessor :applet_path
|
132
|
+
|
133
|
+
# The transaction's unique id in the context of the system.
|
134
|
+
attr_reader :serial
|
135
|
+
|
136
|
+
# User-data hash. Can be used to pass data between applets in a chain.
|
137
|
+
attr_reader :data
|
138
|
+
|
139
|
+
# The Hash of Arrow::Cookies parsed from the request
|
140
|
+
attr_reader :request_cookies
|
141
|
+
|
142
|
+
# The Arrow::CookieSet that contains cookies to be added to the response
|
143
|
+
attr_reader :cookies
|
144
|
+
|
145
|
+
# The handler status code to return to Apache
|
146
|
+
attr_accessor :handler_status
|
147
|
+
|
148
|
+
|
149
|
+
### Returns a human-readable String representation of the transaction,
|
150
|
+
### suitable for debugging.
|
151
|
+
def inspect
|
152
|
+
"#<%s:0x%0x serial: %s; HTTP status: %d>" % [
|
153
|
+
self.class.name,
|
154
|
+
self.object_id * 2,
|
155
|
+
self.serial,
|
156
|
+
self.status
|
157
|
+
]
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
### Returns +true+ if a session has been created for the receiver.
|
162
|
+
def session?
|
163
|
+
@session ? true : false
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
### The session associated with the receiver (an Arrow::Session object).
|
168
|
+
def session( config={} )
|
169
|
+
@session ||= Arrow::Session.create( self, config )
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
### Returns true if the transactions response status is 2xx.
|
174
|
+
def is_success?
|
175
|
+
return nil unless self.status
|
176
|
+
return (self.status / 100) == 2
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
### Returns true if the transaction's server status will cause the
|
181
|
+
### request to be declined (i.e., not handled by Arrow)
|
182
|
+
def is_declined?
|
183
|
+
self.log.debug "Checking to see if the transaction is declined (%p)" %
|
184
|
+
[self.handler_status]
|
185
|
+
return self.handler_status == Apache::DECLINED ? true : false
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
|
190
|
+
# Apache::Request attributes under various conditions. Need to determine
|
191
|
+
# if the dispatcher is mounted on the root URI without access to the
|
192
|
+
# config. It doesn't appear to be possible, since Apache doesn't set
|
193
|
+
# path_info for handlers mounted at "/":
|
194
|
+
#
|
195
|
+
# Dispatcher mounted on "/foo"
|
196
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
197
|
+
# | Request | path_info : unparsed_uri : uri : script_name :
|
198
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
199
|
+
# |/foo | "" | "/foo" | "/foo" | "/foo" |
|
200
|
+
# |/foo/?a=b | "/" | "/foo/?a=b" | "/foo/" | "/foo" |
|
201
|
+
# |/foo/args | "/args" | "/foo/args" | "/foo/args" | "/foo" |
|
202
|
+
# |/foo/args?a=b | "/args" | "/foo/args?a=b" | "/foo/args" | "/foo" |
|
203
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
204
|
+
# Dispatcher mounted on "/":
|
205
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
206
|
+
# | Request | path_info : unparsed_uri : uri : script_name :
|
207
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
208
|
+
# | / | "" | "/" | "/" | "/" |
|
209
|
+
# | /?a=b | "" | "/?a=b" | "/" | "/" |
|
210
|
+
# | /args | "" | "/args" | "/args" | "/args" |
|
211
|
+
# | /args?a=b | "" | "/args?a=b" | "/args" | "/args" |
|
212
|
+
# +--------------+-----------+-----------------+-------------+-------------+
|
213
|
+
# Note that with the dispatcher at "/", path_info is always empty and
|
214
|
+
# #script_name is always the same as the #uri. Strange.
|
215
|
+
|
216
|
+
### Returns +true+ if the dispatcher is mounted on the root URI ("/")
|
217
|
+
def root_dispatcher?
|
218
|
+
return @root_dispatcher
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
### Returns the path operated on by the Arrow::Broker when delegating the
|
223
|
+
### transaction. Equal to the #uri minus the #app_root.
|
224
|
+
def path
|
225
|
+
path = @request.uri
|
226
|
+
uripat = Regexp.new( "^" + self.app_root )
|
227
|
+
return path.sub( uripat, '' )
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
### Return the portion of the request's URI that serves as the base URI for
|
232
|
+
### the application. All self-referential URLs created by the application
|
233
|
+
### should include this.
|
234
|
+
def app_root
|
235
|
+
return "" if self.root_dispatcher?
|
236
|
+
return @request.script_name
|
237
|
+
end
|
238
|
+
alias_method :approot, :app_root
|
239
|
+
|
240
|
+
|
241
|
+
### Returns a fully-qualified URI String to the current applet using the
|
242
|
+
### request object's server name and port.
|
243
|
+
def app_root_url
|
244
|
+
return construct_url( self.app_root )
|
245
|
+
end
|
246
|
+
alias_method :approot_url, :app_root_url
|
247
|
+
|
248
|
+
|
249
|
+
### Return an absolute uri that refers back to the applet the transaction is
|
250
|
+
### being run in
|
251
|
+
def applet
|
252
|
+
return [ self.app_root, self.applet_path ].join("/").gsub( %r{//+}, '/' )
|
253
|
+
end
|
254
|
+
deprecate_method :action, :applet
|
255
|
+
alias_method :applet_uri, :applet
|
256
|
+
|
257
|
+
|
258
|
+
### Returns a fully-qualified URI String to the current applet using the
|
259
|
+
### request object's server name and port.
|
260
|
+
def applet_url
|
261
|
+
return construct_url( self.applet )
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
### If the referer was another applet under the same Arrow instance, return
|
266
|
+
### the uri to it. If there was no 'Referer' header, or the referer wasn't
|
267
|
+
### an applet under the same Arrow instance, returns +nil+.
|
268
|
+
def referring_applet
|
269
|
+
return nil unless self.referer
|
270
|
+
uri = URI.parse( self.referer )
|
271
|
+
path = uri.path or return nil
|
272
|
+
rootRe = Regexp.new( self.app_root + "/" )
|
273
|
+
|
274
|
+
return nil unless rootRe.match( path )
|
275
|
+
subpath = path.
|
276
|
+
sub( rootRe, '' ).
|
277
|
+
split( %r{/} ).
|
278
|
+
first
|
279
|
+
|
280
|
+
return subpath
|
281
|
+
end
|
282
|
+
deprecate_method :referringApplet, :referring_applet
|
283
|
+
|
284
|
+
### If the referer was another applet under the same Arrow instance, return
|
285
|
+
### the name of the action that preceded the current one. If there was no
|
286
|
+
### 'Referer' header, or the referer wasn't an applet under the same Arrow
|
287
|
+
### instance, return +nil+.
|
288
|
+
def referring_action
|
289
|
+
return nil unless self.referer
|
290
|
+
uri = URI.parse( self.referer )
|
291
|
+
path = uri.path or return nil
|
292
|
+
appletRe = Regexp.new( self.app_root + "/\\w+/" )
|
293
|
+
|
294
|
+
return nil unless appletRe.match( path )
|
295
|
+
subpath = path.
|
296
|
+
sub( appletRe, '' ).
|
297
|
+
split( %r{/} ).
|
298
|
+
first
|
299
|
+
|
300
|
+
return subpath
|
301
|
+
end
|
302
|
+
deprecate_method :referringAction, :referring_action
|
303
|
+
|
304
|
+
|
305
|
+
#
|
306
|
+
# Header convenience methods
|
307
|
+
#
|
308
|
+
|
309
|
+
### Add a 'Set-Cookie' header to the response for each cookie that
|
310
|
+
### currently exists the transaction's cookieset.
|
311
|
+
def add_cookie_headers
|
312
|
+
self.cookies.each do |cookie|
|
313
|
+
if self.is_success?
|
314
|
+
self.log.debug "Adding 'Set-Cookie' header: %p (%p)" %
|
315
|
+
[cookie, cookie.to_s]
|
316
|
+
self.headers_out['Set-Cookie'] = cookie.to_s
|
317
|
+
else
|
318
|
+
self.log.debug "Adding 'Set-Cookie' to the error headers: %p (%p)" %
|
319
|
+
[cookie, cookie.to_s]
|
320
|
+
self.err_headers_out['Set-Cookie'] = cookie.to_s
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
### Overridden from Apache::Request to take Apache mod_proxy headers into
|
327
|
+
### account. If the 'X-Forwarded-Host' or 'X-Forwarded-Server' headers
|
328
|
+
### exist in the request, the hostname specified is used instead of the
|
329
|
+
### canonical host.
|
330
|
+
def construct_url( uri )
|
331
|
+
url = @request.construct_url( uri )
|
332
|
+
|
333
|
+
# If the request came through a proxy, rewrite the url's host to match
|
334
|
+
# the hostname the proxy is forwarding for.
|
335
|
+
if (( host = self.proxied_host ))
|
336
|
+
uriobj = URI.parse( url )
|
337
|
+
uriobj.host = host
|
338
|
+
url = uriobj.to_s
|
339
|
+
end
|
340
|
+
|
341
|
+
return url
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
### If the request came from a reverse proxy (i.e., the X-Forwarded-Host
|
346
|
+
### or X-Forwarded-Server headers are present), return the hostname that
|
347
|
+
### the proxy is forwarding for. If no proxy headers are present, return
|
348
|
+
### nil.
|
349
|
+
def proxied_host
|
350
|
+
headers = @request.headers_in
|
351
|
+
return headers['x-forwarded-host'] || headers['x-forwarded-server']
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
### Fetch the client's IP, either from proxy headers or the connection's IP.
|
356
|
+
def remote_ip
|
357
|
+
return self.headers_in['X-Forwarded-For'] || self.connection.remote_ip
|
358
|
+
end
|
359
|
+
deprecate_method :remoteIp, :remote_ip
|
360
|
+
|
361
|
+
|
362
|
+
### Get the request's referer, if any
|
363
|
+
def referer
|
364
|
+
return self.headers_in['Referer']
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
### Set the result's 'Content-Disposition' header to 'attachment' and set
|
369
|
+
### the attachment's +filename+.
|
370
|
+
def attachment=( filename )
|
371
|
+
|
372
|
+
# IE flubs attachments of any mimetype it handles directly.
|
373
|
+
if self.browser_is_ie?
|
374
|
+
self.content_type = 'application/octet-stream'
|
375
|
+
end
|
376
|
+
|
377
|
+
val = %q{attachment; filename="%s"} % [ filename ]
|
378
|
+
self.headers_out['Content-Disposition'] = val
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
### Return a URI object that is parsed from the request's URI.
|
383
|
+
def parsed_uri
|
384
|
+
return URI.parse( self.request.unparsed_uri )
|
385
|
+
end
|
386
|
+
|
387
|
+
|
388
|
+
### Return the Content-type header given in the request's headers, if any
|
389
|
+
def request_content_type
|
390
|
+
return self.headers_in['Content-type']
|
391
|
+
end
|
392
|
+
|
393
|
+
|
394
|
+
#
|
395
|
+
# HTTP content negotiation
|
396
|
+
#
|
397
|
+
|
398
|
+
### Return the contents of the 'Accept' header as an Array of Arrow::AcceptParam objects.
|
399
|
+
def accepted_types
|
400
|
+
@accepted_types ||= parse_accept_header( self.headers_in['Accept'] )
|
401
|
+
return @accepted_types
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
### Returns boolean true/false if the requestor can handle the given
|
406
|
+
### +content_type+.
|
407
|
+
def accepts?( content_type )
|
408
|
+
return self.accepted_types.find {|type| type =~ content_type } ? true : false
|
409
|
+
end
|
410
|
+
alias_method :accept?, :accepts?
|
411
|
+
|
412
|
+
|
413
|
+
### Returns boolean true/false if the requestor can handle the given
|
414
|
+
### +content_type+, not including mime wildcards.
|
415
|
+
def explicitly_accepts?( content_type )
|
416
|
+
self.accepted_types.reject { |param| param.subtype.nil? }.
|
417
|
+
find {|type| type =~ content_type } ? true : false
|
418
|
+
end
|
419
|
+
alias_method :explicitly_accept?, :explicitly_accepts?
|
420
|
+
|
421
|
+
|
422
|
+
### Returns true if the request's content-negotiation headers indicate that it can
|
423
|
+
### accept either 'text/html' or 'application/xhtml+xml'
|
424
|
+
def accepts_html?
|
425
|
+
return self.accepts?( XHTML_MIMETYPE ) || self.accepts?( HTML_MIMETYPE )
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
### Return a normalized list of acceptable types, sorted by q-value and specificity.
|
430
|
+
def normalized_accept_string
|
431
|
+
return self.accepted_types.sort.collect {|ap| ap.to_s }.join( ', ' )
|
432
|
+
end
|
433
|
+
|
434
|
+
|
435
|
+
#
|
436
|
+
# Browser detection/workarounds
|
437
|
+
#
|
438
|
+
|
439
|
+
### Returns true if the User-Agent header indicates that the remote
|
440
|
+
### browser is Internet Explorer. Useful for making the inevitable IE
|
441
|
+
### workarounds.
|
442
|
+
def browser_is_ie?
|
443
|
+
agent = self.headers_in['user-agent'] || ''
|
444
|
+
return agent =~ /MSIE/ ? true : false
|
445
|
+
end
|
446
|
+
|
447
|
+
|
448
|
+
### Execute a block if the User-Agent header indicates that the remote
|
449
|
+
### browser is Internet Explorer. Useful for making the inevitable IE
|
450
|
+
### workarounds.
|
451
|
+
def for_ie_users
|
452
|
+
yield if self.browser_is_ie?
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
### Return +true+ if the request is from XMLHttpRequest (as indicated by the
|
457
|
+
### 'X-Requested-With' header from Scriptaculous or jQuery)
|
458
|
+
def is_ajax_request?
|
459
|
+
xrw_header = self.headers_in['x-requested-with']
|
460
|
+
return true if !xrw_header.nil? && xrw_header =~ /xmlhttprequest/i
|
461
|
+
return false
|
462
|
+
end
|
463
|
+
|
464
|
+
|
465
|
+
### Return +true+ if there are HTML form parameters in the request, either in the
|
466
|
+
### query string with a GET request, or in the body of a POST with a mimetype of
|
467
|
+
### either 'application/x-www-form-urlencoded' or 'multipart/form-data'.
|
468
|
+
def form_request?
|
469
|
+
case self.request_method
|
470
|
+
when 'GET', 'HEAD', 'DELETE', 'PUT'
|
471
|
+
return (!self.parsed_uri.query.nil? ||
|
472
|
+
self.request_content_type =~ FORM_CONTENT_TYPES) ? true : false
|
473
|
+
|
474
|
+
when 'POST'
|
475
|
+
return self.request_content_type =~ FORM_CONTENT_TYPES ? true : false
|
476
|
+
|
477
|
+
else
|
478
|
+
return false
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
|
483
|
+
#
|
484
|
+
# Redirection methods
|
485
|
+
#
|
486
|
+
|
487
|
+
### Return a minimal HTML doc for representing a given status_code
|
488
|
+
def status_doc( status_code, uri=nil )
|
489
|
+
body = ''
|
490
|
+
if uri
|
491
|
+
body = %q{<a href="%s">%s</a>} % [ uri, uri ]
|
492
|
+
end
|
493
|
+
|
494
|
+
#<head><title>%d %s</title></head>
|
495
|
+
#<body><h1>%s</h1><p>%s</p></body>
|
496
|
+
return HTML_DOC % [
|
497
|
+
status_code,
|
498
|
+
STATUS_NAME[status_code],
|
499
|
+
STATUS_NAME[status_code],
|
500
|
+
body
|
501
|
+
]
|
502
|
+
end
|
503
|
+
deprecate_method :statusDoc, :status_doc
|
504
|
+
|
505
|
+
|
506
|
+
### Set the necessary fields in the request to cause the response to be a
|
507
|
+
### redirect to the given +url+ with the specified +status_code+ (302 by
|
508
|
+
### default).
|
509
|
+
def redirect( uri, status_code=Apache::HTTP_MOVED_TEMPORARILY )
|
510
|
+
self.log.debug "Redirecting to %s" % uri
|
511
|
+
self.headers_out[ 'Location' ] = uri.to_s
|
512
|
+
self.status = status_code
|
513
|
+
self.handler_status = Apache::REDIRECT
|
514
|
+
|
515
|
+
return ''
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
### Set the necessary header fields in the response to cause a
|
520
|
+
### NOT_MODIFIED response to be sent.
|
521
|
+
def not_modified
|
522
|
+
return self.redirect( uri, Apache::HTTP_NOT_MODIFIED )
|
523
|
+
end
|
524
|
+
|
525
|
+
|
526
|
+
### Set the necessary header to make the displayed page refresh to the
|
527
|
+
### specified +url+ in the given number of +seconds+.
|
528
|
+
def refresh( seconds, url=nil )
|
529
|
+
seconds = Integer( seconds )
|
530
|
+
url ||= self.construct_url( '' )
|
531
|
+
if !URI.parse( url ).absolute?
|
532
|
+
url = self.construct_url( url )
|
533
|
+
end
|
534
|
+
|
535
|
+
self.headers_out['Refresh'] = "%d;%s" % [seconds, url]
|
536
|
+
end
|
537
|
+
|
538
|
+
|
539
|
+
### Get the verson of Arrow currently running.
|
540
|
+
def arrow_version
|
541
|
+
return Arrow::VERSION
|
542
|
+
end
|
543
|
+
deprecate_method :arrowVersion, :arrow_version
|
544
|
+
|
545
|
+
|
546
|
+
#
|
547
|
+
# SSL convenience methods
|
548
|
+
#
|
549
|
+
|
550
|
+
|
551
|
+
|
552
|
+
|
553
|
+
|
554
|
+
#######
|
555
|
+
private
|
556
|
+
#######
|
557
|
+
|
558
|
+
### Make a transaction serial for the given instance.
|
559
|
+
def make_transaction_serial( request )
|
560
|
+
"%0.3f:%d:%s" % [
|
561
|
+
Time.now.to_f,
|
562
|
+
Process.pid,
|
563
|
+
request.hostname,
|
564
|
+
]
|
565
|
+
end
|
566
|
+
|
567
|
+
|
568
|
+
### Parse cookies from the specified request and return them in a Hash.
|
569
|
+
def parse_cookies( request )
|
570
|
+
hash = Arrow::Cookie.parse( request.headers_in['cookie'] )
|
571
|
+
return Arrow::CookieSet.new( hash.values )
|
572
|
+
end
|
573
|
+
|
574
|
+
|
575
|
+
### Parse the given +header+ and return a list of mimetypes in order of
|
576
|
+
### specificity and q-value, with most-specific and highest q-values sorted
|
577
|
+
### first.
|
578
|
+
def parse_accept_header( header )
|
579
|
+
rval = []
|
580
|
+
|
581
|
+
# Handle the case where there's more than one 'Accept:' header by
|
582
|
+
# forcing everything to an Array
|
583
|
+
header = [header] unless header.is_a?( Array )
|
584
|
+
|
585
|
+
# Accept = "Accept" ":"
|
586
|
+
# #( media-range [ accept-params ] )
|
587
|
+
#
|
588
|
+
# media-range = ( "*/*"
|
589
|
+
# | ( type "/" "*" )
|
590
|
+
# | ( type "/" subtype )
|
591
|
+
# ) *( ";" parameter )
|
592
|
+
# accept-params = ";" "q" "=" qvalue *( accept-extension )
|
593
|
+
# accept-extension = ";" token [ "=" ( token | quoted-string ) ]
|
594
|
+
header.compact.flatten.each do |headerval|
|
595
|
+
params = headerval.split( /\s*,\s*/ )
|
596
|
+
|
597
|
+
params.each do |param|
|
598
|
+
rval << Arrow::AcceptParam.parse( param )
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
rval << Arrow::AcceptParam.new( '*', '*' ) if rval.empty?
|
603
|
+
return rval
|
604
|
+
end
|
605
|
+
|
606
|
+
|
607
|
+
end # class Arrow::Transaction
|
608
|
+
|