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.
Files changed (198) hide show
  1. data/ChangeLog +1590 -0
  2. data/LICENSE +28 -0
  3. data/README +75 -0
  4. data/Rakefile +366 -0
  5. data/Rakefile.local +63 -0
  6. data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
  7. data/data/arrow/applets/args.rb +50 -0
  8. data/data/arrow/applets/config.rb +55 -0
  9. data/data/arrow/applets/error.rb +63 -0
  10. data/data/arrow/applets/files.rb +46 -0
  11. data/data/arrow/applets/inspect.rb +46 -0
  12. data/data/arrow/applets/nosuchapplet.rb +31 -0
  13. data/data/arrow/applets/status.rb +92 -0
  14. data/data/arrow/applets/test.rb +133 -0
  15. data/data/arrow/applets/tutorial/counter.rb +96 -0
  16. data/data/arrow/applets/tutorial/dingus.rb +67 -0
  17. data/data/arrow/applets/tutorial/hello.rb +34 -0
  18. data/data/arrow/applets/tutorial/hello2.rb +73 -0
  19. data/data/arrow/applets/tutorial/imgtext.rb +90 -0
  20. data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
  21. data/data/arrow/applets/tutorial/index.rb +36 -0
  22. data/data/arrow/applets/tutorial/logo.rb +98 -0
  23. data/data/arrow/applets/tutorial/memcache.rb +61 -0
  24. data/data/arrow/applets/tutorial/missing.rb +37 -0
  25. data/data/arrow/applets/tutorial/protected.rb +100 -0
  26. data/data/arrow/applets/tutorial/redirector.rb +52 -0
  27. data/data/arrow/applets/tutorial/rndimages.rb +159 -0
  28. data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
  29. data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
  30. data/data/arrow/applets/tutorial/superhello.rb +72 -0
  31. data/data/arrow/applets/tutorial/timeclock.rb +78 -0
  32. data/data/arrow/applets/view-applet.rb +123 -0
  33. data/data/arrow/applets/view-template.rb +85 -0
  34. data/data/arrow/applets/wiki.rb +274 -0
  35. data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
  36. data/data/arrow/templates/applet-status.tmpl +153 -0
  37. data/data/arrow/templates/args-display.tmpl +120 -0
  38. data/data/arrow/templates/config/display-table.tmpl +36 -0
  39. data/data/arrow/templates/config/display.tmpl +36 -0
  40. data/data/arrow/templates/counter-deleted.tmpl +33 -0
  41. data/data/arrow/templates/counter.tmpl +59 -0
  42. data/data/arrow/templates/dingus.tmpl +55 -0
  43. data/data/arrow/templates/enumtable.tmpl +8 -0
  44. data/data/arrow/templates/error-display.tmpl +92 -0
  45. data/data/arrow/templates/filemap.tmpl +89 -0
  46. data/data/arrow/templates/hello-world-src.tmpl +34 -0
  47. data/data/arrow/templates/hello-world.tmpl +60 -0
  48. data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
  49. data/data/arrow/templates/imgtext/form.tmpl +70 -0
  50. data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
  51. data/data/arrow/templates/imgtext/reload.tmpl +55 -0
  52. data/data/arrow/templates/inspect/display.tmpl +80 -0
  53. data/data/arrow/templates/loginform.tmpl +64 -0
  54. data/data/arrow/templates/logout.tmpl +32 -0
  55. data/data/arrow/templates/memcache/display.tmpl +41 -0
  56. data/data/arrow/templates/navbar.incl +27 -0
  57. data/data/arrow/templates/nosuchapplet.tmpl +32 -0
  58. data/data/arrow/templates/printsource.tmpl +35 -0
  59. data/data/arrow/templates/protected.tmpl +36 -0
  60. data/data/arrow/templates/rndimages.tmpl +38 -0
  61. data/data/arrow/templates/service-response.tmpl +13 -0
  62. data/data/arrow/templates/sharenotes/display.tmpl +38 -0
  63. data/data/arrow/templates/status.tmpl +120 -0
  64. data/data/arrow/templates/templateviewer.tmpl +43 -0
  65. data/data/arrow/templates/test/harness.tmpl +57 -0
  66. data/data/arrow/templates/test/list.tmpl +48 -0
  67. data/data/arrow/templates/test/problem.tmpl +42 -0
  68. data/data/arrow/templates/tutorial/index.tmpl +37 -0
  69. data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
  70. data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
  71. data/data/arrow/templates/view-applet.tmpl +40 -0
  72. data/data/arrow/templates/view-template.tmpl +83 -0
  73. data/data/arrow/templates/wiki/formerror.tmpl +47 -0
  74. data/data/arrow/templates/wiki/markup_help.incl +6 -0
  75. data/data/arrow/templates/wiki/new.tmpl +56 -0
  76. data/data/arrow/templates/wiki/new_system.tmpl +122 -0
  77. data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
  78. data/data/arrow/templates/wiki/show.tmpl +34 -0
  79. data/docs/manual/layouts/default.page +43 -0
  80. data/docs/manual/lib/api-filter.rb +81 -0
  81. data/docs/manual/lib/editorial-filter.rb +64 -0
  82. data/docs/manual/lib/examples-filter.rb +244 -0
  83. data/docs/manual/lib/links-filter.rb +117 -0
  84. data/lib/apache/fakerequest.rb +448 -0
  85. data/lib/apache/logger.rb +33 -0
  86. data/lib/arrow.rb +51 -0
  87. data/lib/arrow/acceptparam.rb +207 -0
  88. data/lib/arrow/applet.rb +725 -0
  89. data/lib/arrow/appletmixins.rb +218 -0
  90. data/lib/arrow/appletregistry.rb +590 -0
  91. data/lib/arrow/applettestcase.rb +503 -0
  92. data/lib/arrow/broker.rb +255 -0
  93. data/lib/arrow/cache.rb +176 -0
  94. data/lib/arrow/config-loaders/yaml.rb +75 -0
  95. data/lib/arrow/config.rb +615 -0
  96. data/lib/arrow/constants.rb +24 -0
  97. data/lib/arrow/cookie.rb +359 -0
  98. data/lib/arrow/cookieset.rb +108 -0
  99. data/lib/arrow/dispatcher.rb +368 -0
  100. data/lib/arrow/dispatcherloader.rb +50 -0
  101. data/lib/arrow/exceptions.rb +61 -0
  102. data/lib/arrow/fallbackhandler.rb +48 -0
  103. data/lib/arrow/formvalidator.rb +631 -0
  104. data/lib/arrow/htmltokenizer.rb +343 -0
  105. data/lib/arrow/logger.rb +488 -0
  106. data/lib/arrow/logger/apacheoutputter.rb +69 -0
  107. data/lib/arrow/logger/arrayoutputter.rb +63 -0
  108. data/lib/arrow/logger/coloroutputter.rb +111 -0
  109. data/lib/arrow/logger/fileoutputter.rb +96 -0
  110. data/lib/arrow/logger/htmloutputter.rb +54 -0
  111. data/lib/arrow/logger/outputter.rb +123 -0
  112. data/lib/arrow/mixins.rb +425 -0
  113. data/lib/arrow/monkeypatches.rb +94 -0
  114. data/lib/arrow/object.rb +117 -0
  115. data/lib/arrow/path.rb +196 -0
  116. data/lib/arrow/service.rb +447 -0
  117. data/lib/arrow/session.rb +289 -0
  118. data/lib/arrow/session/dbstore.rb +100 -0
  119. data/lib/arrow/session/filelock.rb +160 -0
  120. data/lib/arrow/session/filestore.rb +132 -0
  121. data/lib/arrow/session/id.rb +98 -0
  122. data/lib/arrow/session/lock.rb +253 -0
  123. data/lib/arrow/session/md5id.rb +42 -0
  124. data/lib/arrow/session/nulllock.rb +42 -0
  125. data/lib/arrow/session/posixlock.rb +166 -0
  126. data/lib/arrow/session/sha1id.rb +54 -0
  127. data/lib/arrow/session/store.rb +366 -0
  128. data/lib/arrow/session/usertrackid.rb +52 -0
  129. data/lib/arrow/spechelpers.rb +73 -0
  130. data/lib/arrow/template.rb +713 -0
  131. data/lib/arrow/template/attr.rb +31 -0
  132. data/lib/arrow/template/call.rb +31 -0
  133. data/lib/arrow/template/comment.rb +33 -0
  134. data/lib/arrow/template/container.rb +118 -0
  135. data/lib/arrow/template/else.rb +41 -0
  136. data/lib/arrow/template/elsif.rb +44 -0
  137. data/lib/arrow/template/escape.rb +53 -0
  138. data/lib/arrow/template/export.rb +87 -0
  139. data/lib/arrow/template/for.rb +145 -0
  140. data/lib/arrow/template/if.rb +78 -0
  141. data/lib/arrow/template/import.rb +119 -0
  142. data/lib/arrow/template/include.rb +206 -0
  143. data/lib/arrow/template/iterator.rb +208 -0
  144. data/lib/arrow/template/nodes.rb +734 -0
  145. data/lib/arrow/template/parser.rb +571 -0
  146. data/lib/arrow/template/prettyprint.rb +53 -0
  147. data/lib/arrow/template/render.rb +191 -0
  148. data/lib/arrow/template/selectlist.rb +94 -0
  149. data/lib/arrow/template/set.rb +87 -0
  150. data/lib/arrow/template/timedelta.rb +81 -0
  151. data/lib/arrow/template/unless.rb +78 -0
  152. data/lib/arrow/template/urlencode.rb +51 -0
  153. data/lib/arrow/template/yield.rb +139 -0
  154. data/lib/arrow/templatefactory.rb +125 -0
  155. data/lib/arrow/testcase.rb +567 -0
  156. data/lib/arrow/transaction.rb +608 -0
  157. data/rake/191_compat.rb +26 -0
  158. data/rake/dependencies.rb +76 -0
  159. data/rake/documentation.rb +114 -0
  160. data/rake/helpers.rb +502 -0
  161. data/rake/hg.rb +282 -0
  162. data/rake/manual.rb +787 -0
  163. data/rake/packaging.rb +129 -0
  164. data/rake/publishing.rb +278 -0
  165. data/rake/style.rb +62 -0
  166. data/rake/svn.rb +668 -0
  167. data/rake/testing.rb +187 -0
  168. data/rake/verifytask.rb +64 -0
  169. data/spec/arrow/acceptparam_spec.rb +157 -0
  170. data/spec/arrow/applet_spec.rb +575 -0
  171. data/spec/arrow/appletmixins_spec.rb +409 -0
  172. data/spec/arrow/appletregistry_spec.rb +294 -0
  173. data/spec/arrow/broker_spec.rb +153 -0
  174. data/spec/arrow/config_spec.rb +224 -0
  175. data/spec/arrow/cookieset_spec.rb +164 -0
  176. data/spec/arrow/dispatcher_spec.rb +137 -0
  177. data/spec/arrow/dispatcherloader_spec.rb +65 -0
  178. data/spec/arrow/formvalidator_spec.rb +781 -0
  179. data/spec/arrow/logger_spec.rb +346 -0
  180. data/spec/arrow/mixins_spec.rb +120 -0
  181. data/spec/arrow/service_spec.rb +645 -0
  182. data/spec/arrow/session_spec.rb +121 -0
  183. data/spec/arrow/template/iterator_spec.rb +222 -0
  184. data/spec/arrow/templatefactory_spec.rb +185 -0
  185. data/spec/arrow/transaction_spec.rb +319 -0
  186. data/spec/arrow_spec.rb +37 -0
  187. data/spec/lib/appletmatchers.rb +281 -0
  188. data/spec/lib/constants.rb +77 -0
  189. data/spec/lib/helpers.rb +41 -0
  190. data/spec/lib/matchers.rb +44 -0
  191. data/tests/cookie.tests.rb +310 -0
  192. data/tests/path.tests.rb +157 -0
  193. data/tests/session.tests.rb +111 -0
  194. data/tests/session_id.tests.rb +82 -0
  195. data/tests/session_lock.tests.rb +191 -0
  196. data/tests/session_store.tests.rb +53 -0
  197. data/tests/template.tests.rb +1360 -0
  198. 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
+