sinarey 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1180 @@
1
+ require 'sinatra/base'
2
+
3
+ module Sinatra
4
+
5
+ class SinareyBase
6
+ include Rack::Utils
7
+ include Helpers
8
+ include Templates
9
+
10
+ URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI
11
+
12
+ attr_accessor :app, :env, :request, :response, :params
13
+ attr_reader :template_cache
14
+
15
+ def initialize(app = nil)
16
+ super()
17
+ @app = app
18
+ @template_cache = Tilt::Cache.new
19
+ yield self if block_given?
20
+ end
21
+
22
+ # Rack call interface.
23
+ def call(env)
24
+ dup.call!(env)
25
+ end
26
+
27
+ def call!(env) # :nodoc:
28
+ @env = env
29
+ @request = Request.new(env)
30
+ @response = Response.new
31
+ @params = indifferent_params(@request.params)
32
+ template_cache.clear if settings.reload_templates
33
+ force_encoding(@params)
34
+
35
+ route = @request.path_info
36
+ route.chop! if (char=route[-1]) and char=='/' # ignore last '/' char
37
+
38
+ @response['Content-Type'] = nil
39
+ invoke { dispatch! }
40
+ invoke { error_block!(response.status) } unless @env['sinatra.error']
41
+
42
+ unless @response['Content-Type']
43
+ if Array === body and body[0].respond_to? :content_type
44
+ content_type body[0].content_type
45
+ else
46
+ content_type :html
47
+ end
48
+ end
49
+
50
+ @response.finish
51
+ end
52
+
53
+ # Access settings defined with Base.set.
54
+ def self.settings
55
+ self
56
+ end
57
+
58
+ # Access settings defined with Base.set.
59
+ def settings
60
+ self.class.settings
61
+ end
62
+
63
+ def options
64
+ warn "Sinatra::Base#options is deprecated and will be removed, " \
65
+ "use #settings instead."
66
+ settings
67
+ end
68
+
69
+ # Exit the current block, halts any further processing
70
+ # of the request, and returns the specified response.
71
+ def halt(*response)
72
+ response = response.first if response.length == 1
73
+ throw :halt, response
74
+ end
75
+
76
+ # Pass control to the next matching route.
77
+ # If there are no more matching routes, Sinatra will
78
+ # return a 404 response.
79
+ def pass(&block)
80
+ throw :pass, block
81
+ end
82
+
83
+ # Forward the request to the downstream app -- middleware only.
84
+ def forward
85
+ fail "downstream app not set" unless @app.respond_to? :call
86
+ status, headers, body = @app.call env
87
+ @response.status = status
88
+ @response.body = body
89
+ @response.headers.merge! headers
90
+ nil
91
+ end
92
+
93
+ private
94
+
95
+ # Run filters defined on the class and all superclasses.
96
+ def filter!(type, base = settings)
97
+ filter! type, base.superclass if base.superclass.respond_to?(:filters)
98
+ base.filters[type].each { |args| process_route(*args) }
99
+ end
100
+
101
+ # Run routes defined on the class and all superclasses.
102
+ def route!(base = settings, pass_block = nil)
103
+ if router = env['sinarey.router']
104
+ return mount_route!(router)
105
+ end
106
+
107
+ if turbo_route = (turbo_routes = base.turbo_routes[@request.request_method]) && (path_info = turbo_routes[@request.path_info])
108
+ turbo_route.tap do |block_id|
109
+ block = base.blocks[block_id]
110
+ returned_pass_block = process_turbo_route do |*args|
111
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
112
+ route_eval { block[*args] }
113
+ end
114
+ pass_block = returned_pass_block if returned_pass_block
115
+ end
116
+ elsif routes = base.routes[@request.request_method]
117
+ routes.each do |pattern, keys, conditions, block_id|
118
+ block = base.blocks[block_id]
119
+ returned_pass_block = process_route(pattern, keys, conditions) do |*args|
120
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
121
+ route_eval { block[*args] }
122
+ end
123
+ # don't wipe out pass_block in superclass
124
+ pass_block = returned_pass_block if returned_pass_block
125
+ end
126
+ end
127
+
128
+ # Run routes defined in superclass.
129
+ if base.superclass.respond_to?(:routes)
130
+ return route!(base.superclass, pass_block)
131
+ end
132
+
133
+ route_eval(&pass_block) if pass_block
134
+ route_missing
135
+ end
136
+
137
+ def mount_route!(options,base = settings)
138
+ type,block_id = options[:type],options[:block_id]
139
+ case type
140
+ when :turbo
141
+ block = base.blocks[block_id]
142
+ returned_pass_block = process_turbo_route do |*args|
143
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
144
+ route_eval { block[*args] }
145
+ end
146
+ pass_block = returned_pass_block if returned_pass_block
147
+ when :normal
148
+ match,keys,conditions = options[:match],options[:keys],options[:conditions]
149
+ block = base.blocks[block_id]
150
+ returned_pass_block = process_mount_route(match, keys, conditions) do |*args|
151
+ env['sinatra.route'] = block.instance_variable_get(:@route_name)
152
+ route_eval { block[*args] }
153
+ end
154
+ # don't wipe out pass_block in superclass
155
+ pass_block = returned_pass_block if returned_pass_block
156
+ end
157
+
158
+ route_eval(&pass_block) if pass_block
159
+ route_missing
160
+ end
161
+
162
+ # Run a route block and throw :halt with the result.
163
+ def route_eval
164
+ throw :halt, yield
165
+ end
166
+
167
+ # If the current request matches pattern and conditions, fill params
168
+ # with keys and call the given block.
169
+ # Revert params afterwards.
170
+ #
171
+ # Returns pass block.
172
+
173
+ def process_turbo_route(block = nil)
174
+ catch(:pass) do
175
+ block ? block[self, []] : yield(self, [])
176
+ end
177
+ end
178
+
179
+ def process_route(pattern, keys, conditions, block_id = nil, values = [])
180
+ route = @request.path_info
181
+ return unless match = pattern.match(route)
182
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
183
+
184
+ if values.any?
185
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
186
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
187
+ end
188
+
189
+ catch(:pass) do
190
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
191
+ (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
192
+ end
193
+ ensure
194
+ @params = original if original
195
+ end
196
+
197
+ def process_mount_route(match, keys, conditions, block_id = nil, values = [])
198
+ values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v }
199
+
200
+ if values.any?
201
+ original, @params = params, params.merge('splat' => [], 'captures' => values)
202
+ keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v }
203
+ end
204
+
205
+ catch(:pass) do
206
+ conditions.each { |c| throw :pass if c.bind(self).call == false }
207
+ (block_id && (block = settings.blocks[block_id])) ? block[self, values] : yield(self, values)
208
+ end
209
+ ensure
210
+ @params = original if original
211
+ end
212
+
213
+ # No matching route was found or all routes passed. The default
214
+ # implementation is to forward the request downstream when running
215
+ # as middleware (@app is non-nil); when no downstream app is set, raise
216
+ # a NotFound exception. Subclasses can override this method to perform
217
+ # custom route miss logic.
218
+ def route_missing
219
+ if @app
220
+ forward
221
+ else
222
+ raise NotFound
223
+ end
224
+ end
225
+
226
+ # Attempt to serve static files from public directory. Throws :halt when
227
+ # a matching file is found, returns nil otherwise.
228
+ def static!
229
+ return if (public_dir = settings.public_folder).nil?
230
+ path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" )
231
+ return unless File.file?(path)
232
+
233
+ env['sinatra.static_file'] = path
234
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
235
+ send_file path, :disposition => nil
236
+ end
237
+
238
+ # Enable string or symbol key access to the nested params hash.
239
+ def indifferent_params(object)
240
+ case object
241
+ when Hash
242
+ new_hash = indifferent_hash
243
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
244
+ new_hash
245
+ when Array
246
+ object.map { |item| indifferent_params(item) }
247
+ else
248
+ object
249
+ end
250
+ end
251
+
252
+ # Creates a Hash with indifferent access.
253
+ def indifferent_hash
254
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
255
+ end
256
+
257
+ # Run the block with 'throw :halt' support and apply result to the response.
258
+ def invoke
259
+ res = catch(:halt) { yield }
260
+ res = [res] if Fixnum === res or String === res
261
+ if Array === res and Fixnum === res.first
262
+ res = res.dup
263
+ status(res.shift)
264
+ body(res.pop)
265
+ headers(*res)
266
+ elsif res.respond_to? :each
267
+ body res
268
+ end
269
+ nil # avoid double setting the same response tuple twice
270
+ end
271
+
272
+ # Dispatch a request with error handling.
273
+ def dispatch!
274
+ invoke do
275
+ static! if settings.static? && (request.get? || request.head?)
276
+ filter! :before
277
+ route!
278
+ end
279
+ rescue ::Exception => boom
280
+ invoke { handle_exception!(boom) }
281
+ ensure
282
+ begin
283
+ filter! :after
284
+ rescue ::Exception => boom
285
+ invoke { handle_exception!(boom) } unless @env['sinatra.error']
286
+ end
287
+ end
288
+
289
+ # Error handling during requests.
290
+ def handle_exception!(boom)
291
+ @env['sinatra.error'] = boom
292
+
293
+ if boom.respond_to? :http_status
294
+ status(boom.http_status)
295
+ elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599
296
+ status(boom.code)
297
+ else
298
+ status(500)
299
+ end
300
+
301
+ status(500) unless status.between? 400, 599
302
+
303
+ if server_error?
304
+ dump_errors! boom if settings.dump_errors?
305
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
306
+ end
307
+
308
+ if not_found?
309
+ body '<h1>Not Found</h1>'
310
+ end
311
+
312
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
313
+ return res if res or not server_error?
314
+ raise boom if settings.raise_errors? or settings.show_exceptions?
315
+ error_block! Exception, boom
316
+ end
317
+
318
+ # Find an custom error block for the key(s) specified.
319
+ def error_block!(key, *block_params)
320
+ base = settings
321
+ while base.respond_to?(:errors)
322
+ next base = base.superclass unless args_array = base.errors[key]
323
+ args_array.reverse_each do |args|
324
+ first = args == args_array.first
325
+ args += [block_params]
326
+ resp = process_route(*args)
327
+ return resp unless resp.nil? && !first
328
+ end
329
+ end
330
+ return false unless key.respond_to? :superclass and key.superclass < Exception
331
+ error_block!(key.superclass, *block_params)
332
+ end
333
+
334
+ def dump_errors!(boom)
335
+ msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
336
+ @env['rack.errors'].puts(msg)
337
+ end
338
+
339
+ class << self
340
+ CALLERS_TO_IGNORE = [ # :nodoc:
341
+ /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code
342
+ /lib\/tilt.*\.rb$/, # all tilt code
343
+ /^\(.*\)$/, # generated code
344
+ /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks
345
+ /active_support/, # active_support require hacks
346
+ /bundler(\/runtime)?\.rb/, # bundler require hacks
347
+ /<internal:/, # internal in ruby >= 1.9.2
348
+ /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files
349
+ ]
350
+
351
+ # contrary to what the comment said previously, rubinius never supported this
352
+ if defined?(RUBY_IGNORE_CALLERS)
353
+ warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0"
354
+ CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS)
355
+ end
356
+
357
+ attr_reader :blocks, :turbo_routes, :routes, :filters, :templates, :errors
358
+
359
+ # Removes all routes, filters, middleware and extension hooks from the
360
+ # current class (not routes/filters/... defined by its superclass).
361
+ def reset!
362
+ @conditions = []
363
+ @routes = {}
364
+ @turbo_routes = {}
365
+ @blocks = {}
366
+ @filters = {:before => [], :after => []}
367
+ @errors = {}
368
+ @middleware = []
369
+ @prototype = nil
370
+ @extensions = []
371
+
372
+ if superclass.respond_to?(:templates)
373
+ @templates = Hash.new { |hash,key| superclass.templates[key] }
374
+ else
375
+ @templates = {}
376
+ end
377
+ end
378
+
379
+ # Extension modules registered on this class and all superclasses.
380
+ def extensions
381
+ if superclass.respond_to?(:extensions)
382
+ (@extensions + superclass.extensions).uniq
383
+ else
384
+ @extensions
385
+ end
386
+ end
387
+
388
+ # Middleware used in this class and all superclasses.
389
+ def middleware
390
+ if superclass.respond_to?(:middleware)
391
+ superclass.middleware + @middleware
392
+ else
393
+ @middleware
394
+ end
395
+ end
396
+
397
+ # Sets an option to the given value. If the value is a proc,
398
+ # the proc will be called every time the option is accessed.
399
+ def set(option, value = (not_set = true), ignore_setter = false, &block)
400
+ raise ArgumentError if block and !not_set
401
+ value, not_set = block, false if block
402
+
403
+ if not_set
404
+ raise ArgumentError unless option.respond_to?(:each)
405
+ option.each { |k,v| set(k, v) }
406
+ return self
407
+ end
408
+
409
+ if respond_to?("#{option}=") and not ignore_setter
410
+ return __send__("#{option}=", value)
411
+ end
412
+
413
+ setter = proc { |val| set option, val, true }
414
+ getter = proc { value }
415
+
416
+ case value
417
+ when Proc
418
+ getter = value
419
+ when Symbol, Fixnum, FalseClass, TrueClass, NilClass
420
+ getter = value.inspect
421
+ when Hash
422
+ setter = proc do |val|
423
+ val = value.merge val if Hash === val
424
+ set option, val, true
425
+ end
426
+ end
427
+
428
+ define_singleton("#{option}=", setter) if setter
429
+ define_singleton(option, getter) if getter
430
+ define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
431
+ self
432
+ end
433
+
434
+ # Same as calling `set :option, true` for each of the given options.
435
+ def enable(*opts)
436
+ opts.each { |key| set(key, true) }
437
+ end
438
+
439
+ # Same as calling `set :option, false` for each of the given options.
440
+ def disable(*opts)
441
+ opts.each { |key| set(key, false) }
442
+ end
443
+
444
+ # Define a custom error handler. Optionally takes either an Exception
445
+ # class, or an HTTP status code to specify which errors should be
446
+ # handled.
447
+ def error(*codes, &block)
448
+ args = compile! "ERROR", //, block
449
+ codes = codes.map { |c| Array(c) }.flatten
450
+ codes << Exception if codes.empty?
451
+ codes.each { |c| (@errors[c] ||= []) << args }
452
+ end
453
+
454
+ # Sugar for `error(404) { ... }`
455
+ def not_found(&block)
456
+ error(404, &block)
457
+ error(Sinatra::NotFound, &block)
458
+ end
459
+
460
+ # Define a named template. The block must return the template source.
461
+ def template(name, &block)
462
+ filename, line = caller_locations.first
463
+ templates[name] = [block, filename, line.to_i]
464
+ end
465
+
466
+ # Define the layout template. The block must return the template source.
467
+ def layout(name = :layout, &block)
468
+ template name, &block
469
+ end
470
+
471
+ # Load embedded templates from the file; uses the caller's __FILE__
472
+ # when no file is specified.
473
+ def inline_templates=(file = nil)
474
+ file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file
475
+
476
+ begin
477
+ io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file)
478
+ app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
479
+ rescue Errno::ENOENT
480
+ app, data = nil
481
+ end
482
+
483
+ if data
484
+ if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m
485
+ encoding = $2
486
+ else
487
+ encoding = settings.default_encoding
488
+ end
489
+ lines = app.count("\n") + 1
490
+ template = nil
491
+ force_encoding data, encoding
492
+ data.each_line do |line|
493
+ lines += 1
494
+ if line =~ /^@@\s*(.*\S)\s*$/
495
+ template = force_encoding('', encoding)
496
+ templates[$1.to_sym] = [template, file, lines]
497
+ elsif template
498
+ template << line
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ # Lookup or register a mime type in Rack's mime registry.
505
+ def mime_type(type, value = nil)
506
+ return type if type.nil?
507
+ return type.to_s if type.to_s.include?('/')
508
+ type = ".#{type}" unless type.to_s[0] == ?.
509
+ return Rack::Mime.mime_type(type, nil) unless value
510
+ Rack::Mime::MIME_TYPES[type] = value
511
+ end
512
+
513
+ # provides all mime types matching type, including deprecated types:
514
+ # mime_types :html # => ['text/html']
515
+ # mime_types :js # => ['application/javascript', 'text/javascript']
516
+ def mime_types(type)
517
+ type = mime_type type
518
+ type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type]
519
+ end
520
+
521
+ # Define a before filter; runs before all requests within the same
522
+ # context as route handlers and may access/modify the request and
523
+ # response.
524
+ def before(path = nil, options = {}, &block)
525
+ add_filter(:before, path, options, &block)
526
+ end
527
+
528
+ # Define an after filter; runs after all requests within the same
529
+ # context as route handlers and may access/modify the request and
530
+ # response.
531
+ def after(path = nil, options = {}, &block)
532
+ add_filter(:after, path, options, &block)
533
+ end
534
+
535
+ # add a filter
536
+ def add_filter(type, path = nil, options = {}, &block)
537
+ path, options = //, path if path.respond_to?(:each_pair)
538
+ filters[type] << compile!(type, path || //, block, options)
539
+ end
540
+
541
+ # Add a route condition. The route is considered non-matching when the
542
+ # block returns false.
543
+ def condition(name = "#{caller.first[/`.*'/]} condition", &block)
544
+ @conditions << generate_method(name, &block)
545
+ end
546
+
547
+ def public=(value)
548
+ warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead"
549
+ set(:public_folder, value)
550
+ end
551
+
552
+ def public_dir=(value)
553
+ self.public_folder = value
554
+ end
555
+
556
+ def public_dir
557
+ public_folder
558
+ end
559
+
560
+ # Defining a `GET` handler also automatically defines
561
+ # a `HEAD` handler.
562
+ def get(path, opts = {}, &block)
563
+ conditions = @conditions.dup
564
+ route('GET', path, opts, &block)
565
+
566
+ @conditions = conditions
567
+ route('HEAD', path, opts, &block)
568
+ end
569
+
570
+ def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end
571
+ def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end
572
+ def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end
573
+ def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end
574
+ def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end
575
+ def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end
576
+ def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end
577
+ def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end
578
+
579
+ # Makes the methods defined in the block and in the Modules given
580
+ # in `extensions` available to the handlers and templates
581
+ def helpers(*extensions, &block)
582
+ class_eval(&block) if block_given?
583
+ include(*extensions) if extensions.any?
584
+ end
585
+
586
+ # Register an extension. Alternatively take a block from which an
587
+ # extension will be created and registered on the fly.
588
+ def register(*extensions, &block)
589
+ extensions << Module.new(&block) if block_given?
590
+ @extensions += extensions
591
+ extensions.each do |extension|
592
+ extend extension
593
+ extension.registered(self) if extension.respond_to?(:registered)
594
+ end
595
+ end
596
+
597
+ def development?; environment == :development end
598
+ def production?; environment == :production end
599
+ def test?; environment == :test end
600
+
601
+ # Set configuration options for Sinatra and/or the app.
602
+ # Allows scoping of settings for certain environments.
603
+ def configure(*envs)
604
+ yield self if envs.empty? || envs.include?(environment.to_sym)
605
+ end
606
+
607
+ # Use the specified Rack middleware
608
+ def use(middleware, *args, &block)
609
+ @prototype = nil
610
+ @middleware << [middleware, args, block]
611
+ end
612
+
613
+ # Stop the self-hosted server if running.
614
+ def quit!
615
+ return unless running?
616
+ # Use Thin's hard #stop! if available, otherwise just #stop.
617
+ running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
618
+ $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
619
+ set :running_server, nil
620
+ set :handler_name, nil
621
+ end
622
+
623
+ alias_method :stop!, :quit!
624
+
625
+ # Run the Sinatra app as a self-hosted server using
626
+ # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call
627
+ # with the constructed handler once we have taken the stage.
628
+ def run!(options = {}, &block)
629
+ return if running?
630
+ set options
631
+ handler = detect_rack_handler
632
+ handler_name = handler.name.gsub(/.*::/, '')
633
+ server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
634
+ server_settings.merge!(:Port => port, :Host => bind)
635
+
636
+ begin
637
+ start_server(handler, server_settings, handler_name, &block)
638
+ rescue Errno::EADDRINUSE
639
+ $stderr.puts "== Someone is already performing on port #{port}!"
640
+ raise
641
+ ensure
642
+ quit!
643
+ end
644
+ end
645
+
646
+ alias_method :start!, :run!
647
+
648
+ # Check whether the self-hosted server is running or not.
649
+ def running?
650
+ running_server?
651
+ end
652
+
653
+ # The prototype instance used to process requests.
654
+ def prototype
655
+ @prototype ||= new
656
+ end
657
+
658
+ # Create a new instance without middleware in front of it.
659
+ alias new! new unless method_defined? :new!
660
+
661
+ # Create a new instance of the class fronted by its middleware
662
+ # pipeline. The object is guaranteed to respond to #call but may not be
663
+ # an instance of the class new was called on.
664
+ def new(*args, &bk)
665
+ instance = new!(*args, &bk)
666
+ Wrapper.new(build(instance).to_app, instance)
667
+ end
668
+
669
+ # Creates a Rack::Builder instance with all the middleware set up and
670
+ # the given +app+ as end point.
671
+ def build(app)
672
+ builder = Rack::Builder.new
673
+ setup_default_middleware builder
674
+ setup_middleware builder
675
+ builder.run app
676
+ builder
677
+ end
678
+
679
+ def call(env)
680
+ synchronize { prototype.call(env) }
681
+ end
682
+
683
+ # Like Kernel#caller but excluding certain magic entries and without
684
+ # line / method information; the resulting array contains filenames only.
685
+ def caller_files
686
+ cleaned_caller(1).flatten
687
+ end
688
+
689
+ # Like caller_files, but containing Arrays rather than strings with the
690
+ # first element being the file, and the second being the line.
691
+ def caller_locations
692
+ cleaned_caller 2
693
+ end
694
+
695
+ private
696
+
697
+ # Starts the server by running the Rack Handler.
698
+ def start_server(handler, server_settings, handler_name)
699
+ handler.run(self, server_settings) do |server|
700
+ unless handler_name =~ /cgi/i
701
+ $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
702
+ "on #{port} for #{environment} with backup from #{handler_name}"
703
+ end
704
+
705
+ setup_traps
706
+ set :running_server, server
707
+ set :handler_name, handler_name
708
+ server.threaded = settings.threaded if server.respond_to? :threaded=
709
+
710
+ yield server if block_given?
711
+ end
712
+ end
713
+
714
+ def setup_traps
715
+ if traps?
716
+ at_exit { quit! }
717
+
718
+ [:INT, :TERM].each do |signal|
719
+ old_handler = trap(signal) do
720
+ quit!
721
+ old_handler.call if old_handler.respond_to?(:call)
722
+ end
723
+ end
724
+
725
+ set :traps, false
726
+ end
727
+ end
728
+
729
+ # Dynamically defines a method on settings.
730
+ def define_singleton(name, content = Proc.new)
731
+ # replace with call to singleton_class once we're 1.9 only
732
+ (class << self; self; end).class_eval do
733
+ undef_method(name) if method_defined? name
734
+ String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
735
+ end
736
+ end
737
+
738
+ # Condition for matching host name. Parameter might be String or Regexp.
739
+ def host_name(pattern)
740
+ condition { pattern === request.host }
741
+ end
742
+
743
+ # Condition for matching user agent. Parameter should be Regexp.
744
+ # Will set params[:agent].
745
+ def user_agent(pattern)
746
+ condition do
747
+ if request.user_agent.to_s =~ pattern
748
+ @params[:agent] = $~[1..-1]
749
+ true
750
+ else
751
+ false
752
+ end
753
+ end
754
+ end
755
+ alias_method :agent, :user_agent
756
+
757
+ # Condition for matching mimetypes. Accepts file extensions.
758
+ def provides(*types)
759
+ types.map! { |t| mime_types(t) }
760
+ types.flatten!
761
+ condition do
762
+ if type = response['Content-Type']
763
+ types.include? type or types.include? type[/^[^;]+/]
764
+ elsif type = request.preferred_type(types)
765
+ params = (type.respond_to?(:params) ? type.params : {})
766
+ content_type(type, params)
767
+ true
768
+ else
769
+ false
770
+ end
771
+ end
772
+ end
773
+
774
+ def route(verb, path, options = {}, &block)
775
+ if path.class == String
776
+ return if path.empty?
777
+ path.chop! if (char=path[-1]) and char=='/'
778
+ end
779
+
780
+ # Because of self.options.host
781
+ host_name(options.delete(:host)) if options.key?(:host)
782
+
783
+ if /^[a-zA-Z0-9\-\/_]*$/ === path
784
+ turbo_signature = turbo_compile!(verb, path, block, options)
785
+ @turbo_routes[verb] ||= {}
786
+ @turbo_routes[verb][path] = turbo_signature
787
+ else
788
+ signature = compile!(verb, path, block, options)
789
+ (@routes[verb] ||= []) << signature
790
+ end
791
+ invoke_hook(:route_added, verb, path, block)
792
+ signature
793
+ end
794
+
795
+ def invoke_hook(name, *args)
796
+ extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
797
+ end
798
+
799
+ def generate_method(method_name, &block)
800
+ define_method(method_name, &block)
801
+ method = instance_method method_name
802
+ remove_method method_name
803
+ method
804
+ end
805
+
806
+ def turbo_compile!(verb, path, block, options = {})
807
+ method_name = "#{verb} #{path}"
808
+ unbound_method = generate_method(method_name, &block)
809
+ wrapper = block.arity != 0 ?
810
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
811
+ proc { |a,p| unbound_method.bind(a).call }
812
+ wrapper.instance_variable_set(:@route_name, method_name)
813
+ @blocks ||= {}
814
+ block_id = @blocks.size + 1
815
+ @blocks[block_id] = wrapper
816
+ block_id
817
+ end
818
+
819
+ def compile!(verb, path, block, options = {})
820
+ options.each_pair { |option, args| send(option, *args) }
821
+ method_name = "#{verb} #{path}"
822
+ unbound_method = generate_method(method_name, &block)
823
+ pattern, keys = compile path
824
+ conditions, @conditions = @conditions, []
825
+
826
+ wrapper = block.arity != 0 ?
827
+ proc { |a,p| unbound_method.bind(a).call(*p) } :
828
+ proc { |a,p| unbound_method.bind(a).call }
829
+ wrapper.instance_variable_set(:@route_name, method_name)
830
+ @blocks ||= {}
831
+ block_id = @blocks.size + 1
832
+ @blocks[block_id] = wrapper
833
+ [ pattern, keys, conditions, block_id ]
834
+ end
835
+
836
+ def compile(path)
837
+ if path.respond_to? :to_str
838
+ keys = []
839
+
840
+ # We append a / at the end if there was one.
841
+ # Reason: Splitting does not split off an empty
842
+ # string at the end if the split separator
843
+ # is at the end.
844
+ #
845
+ postfix = '/' if path =~ /\/\z/
846
+
847
+ # Split the path into pieces in between forward slashes.
848
+ #
849
+ segments = path.split('/').map! do |segment|
850
+ ignore = []
851
+
852
+ # Special character handling.
853
+ #
854
+ pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c|
855
+ ignore << escaped(c).join if c.match(/[\.@]/)
856
+ patt = encoded(c)
857
+ patt.gsub(/%[\da-fA-F]{2}/) do |match|
858
+ match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join
859
+ end
860
+ end
861
+
862
+ ignore = ignore.uniq.join
863
+
864
+ # Key handling.
865
+ #
866
+ pattern.gsub(/((:\w+)|\*)/) do |match|
867
+ if match == "*"
868
+ keys << 'splat'
869
+ "(.*?)"
870
+ else
871
+ keys << $2[1..-1]
872
+ ignore_pattern = safe_ignore(ignore)
873
+
874
+ ignore_pattern
875
+ end
876
+ end
877
+ end
878
+
879
+ # Special case handling.
880
+ #
881
+ if segment = segments.pop
882
+ if segment.match(/\[\^\\\./)
883
+ parts = segment.rpartition(/\[\^\\\./)
884
+ parts[1] = '[^'
885
+ segments << parts.join
886
+ else
887
+ segments << segment
888
+ end
889
+ end
890
+ [/\A#{segments.join('/')}#{postfix}\z/, keys]
891
+ elsif path.respond_to?(:keys) && path.respond_to?(:match)
892
+ [path, path.keys]
893
+ elsif path.respond_to?(:names) && path.respond_to?(:match)
894
+ [path, path.names]
895
+ elsif path.respond_to? :match
896
+ [path, []]
897
+ else
898
+ raise TypeError, path
899
+ end
900
+ end
901
+
902
+ def encoded(char)
903
+ enc = URI_INSTANCE.escape(char)
904
+ enc = "(?:#{escaped(char, enc).join('|')})" if enc == char
905
+ enc = "(?:#{enc}|#{encoded('+')})" if char == " "
906
+ enc
907
+ end
908
+
909
+ def escaped(char, enc = URI_INSTANCE.escape(char))
910
+ [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)]
911
+ end
912
+
913
+ def safe_ignore(ignore)
914
+ unsafe_ignore = []
915
+ ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
916
+ unsafe_ignore << hex[1..2]
917
+ ''
918
+ end
919
+ unsafe_patterns = unsafe_ignore.map! do |unsafe|
920
+ chars = unsafe.split(//).map! do |char|
921
+ if char =~ /[A-Z]/
922
+ char <<= char.tr('A-Z', 'a-z')
923
+ end
924
+ char
925
+ end
926
+
927
+ "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
928
+ end
929
+ if unsafe_patterns.length > 0
930
+ "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
931
+ else
932
+ "([^#{ignore}/?#]+)"
933
+ end
934
+ end
935
+
936
+ def setup_default_middleware(builder)
937
+ builder.use ExtendedRack
938
+ builder.use ShowExceptions if show_exceptions?
939
+ builder.use Rack::MethodOverride if method_override?
940
+ builder.use Rack::Head
941
+ setup_logging builder
942
+ setup_sessions builder
943
+ setup_protection builder
944
+ end
945
+
946
+ def setup_middleware(builder)
947
+ middleware.each { |c,a,b| builder.use(c, *a, &b) }
948
+ end
949
+
950
+ def setup_logging(builder)
951
+ if logging?
952
+ setup_common_logger(builder)
953
+ setup_custom_logger(builder)
954
+ elsif logging == false
955
+ setup_null_logger(builder)
956
+ end
957
+ end
958
+
959
+ def setup_null_logger(builder)
960
+ builder.use Rack::NullLogger
961
+ end
962
+
963
+ def setup_common_logger(builder)
964
+ builder.use Sinatra::CommonLogger
965
+ end
966
+
967
+ def setup_custom_logger(builder)
968
+ if logging.respond_to? :to_int
969
+ builder.use Rack::Logger, logging
970
+ else
971
+ builder.use Rack::Logger
972
+ end
973
+ end
974
+
975
+ def setup_protection(builder)
976
+ return unless protection?
977
+ options = Hash === protection ? protection.dup : {}
978
+ protect_session = options.fetch(:session) { sessions? }
979
+ options[:except] = Array options[:except]
980
+ options[:except] += [:session_hijacking, :remote_token] unless protect_session
981
+ options[:reaction] ||= :drop_session
982
+ builder.use Rack::Protection, options
983
+ end
984
+
985
+ def setup_sessions(builder)
986
+ return unless sessions?
987
+ options = {}
988
+ options[:secret] = session_secret if session_secret?
989
+ options.merge! sessions.to_hash if sessions.respond_to? :to_hash
990
+ builder.use Rack::Session::Cookie, options
991
+ end
992
+
993
+ def detect_rack_handler
994
+ servers = Array(server)
995
+ servers.each do |server_name|
996
+ begin
997
+ return Rack::Handler.get(server_name.to_s)
998
+ rescue LoadError, NameError
999
+ end
1000
+ end
1001
+ fail "Server handler (#{servers.join(',')}) not found."
1002
+ end
1003
+
1004
+ def inherited(subclass)
1005
+ subclass.reset!
1006
+ subclass.set :app_file, caller_files.first unless subclass.app_file?
1007
+ super
1008
+ end
1009
+
1010
+ @@mutex = Mutex.new
1011
+ def synchronize(&block)
1012
+ if lock?
1013
+ @@mutex.synchronize(&block)
1014
+ else
1015
+ yield
1016
+ end
1017
+ end
1018
+
1019
+ # used for deprecation warnings
1020
+ def warn(message)
1021
+ super message + "\n\tfrom #{cleaned_caller.first.join(':')}"
1022
+ end
1023
+
1024
+ # Like Kernel#caller but excluding certain magic entries
1025
+ def cleaned_caller(keep = 3)
1026
+ caller(1).
1027
+ map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }.
1028
+ reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } }
1029
+ end
1030
+ end
1031
+
1032
+ # Fixes encoding issues by
1033
+ # * defaulting to UTF-8
1034
+ # * casting params to Encoding.default_external
1035
+ #
1036
+ # The latter might not be necessary if Rack handles it one day.
1037
+ # Keep an eye on Rack's LH #100.
1038
+ def force_encoding(*args) settings.force_encoding(*args) end
1039
+ if defined? Encoding
1040
+ def self.force_encoding(data, encoding = default_encoding)
1041
+ return if data == settings || data.is_a?(Tempfile)
1042
+ if data.respond_to? :force_encoding
1043
+ data.force_encoding(encoding).encode!
1044
+ elsif data.respond_to? :each_value
1045
+ data.each_value { |v| force_encoding(v, encoding) }
1046
+ elsif data.respond_to? :each
1047
+ data.each { |v| force_encoding(v, encoding) }
1048
+ end
1049
+ data
1050
+ end
1051
+ else
1052
+ def self.force_encoding(data, *) data end
1053
+ end
1054
+
1055
+ reset!
1056
+
1057
+ set :environment, (ENV['RACK_ENV'] || :development).to_sym
1058
+ set :raise_errors, Proc.new { test? }
1059
+ set :dump_errors, Proc.new { !test? }
1060
+ set :show_exceptions, Proc.new { development? }
1061
+ set :sessions, false
1062
+ set :logging, false
1063
+ set :protection, true
1064
+ set :method_override, false
1065
+ set :use_code, false
1066
+ set :default_encoding, "utf-8"
1067
+ set :x_cascade, true
1068
+ set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
1069
+ settings.add_charset << /^text\//
1070
+
1071
+ # explicitly generating a session secret eagerly to play nice with preforking
1072
+ begin
1073
+ require 'securerandom'
1074
+ set :session_secret, SecureRandom.hex(64)
1075
+ rescue LoadError, NotImplementedError
1076
+ # SecureRandom raises a NotImplementedError if no random device is available
1077
+ set :session_secret, "%064x" % Kernel.rand(2**256-1)
1078
+ end
1079
+
1080
+ class << self
1081
+ alias_method :methodoverride?, :method_override?
1082
+ alias_method :methodoverride=, :method_override=
1083
+ end
1084
+
1085
+ set :run, false # start server via at-exit hook?
1086
+ set :running_server, nil
1087
+ set :handler_name, nil
1088
+ set :traps, true
1089
+ set :server, %w[HTTP webrick]
1090
+ set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' }
1091
+ set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567)
1092
+
1093
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
1094
+
1095
+ if ruby_engine == 'macruby'
1096
+ server.unshift 'control_tower'
1097
+ else
1098
+ server.unshift 'reel'
1099
+ server.unshift 'mongrel' if ruby_engine.nil?
1100
+ server.unshift 'puma' if ruby_engine != 'rbx'
1101
+ server.unshift 'thin' if ruby_engine != 'jruby'
1102
+ server.unshift 'puma' if ruby_engine == 'rbx'
1103
+ server.unshift 'trinidad' if ruby_engine == 'jruby'
1104
+ end
1105
+
1106
+ set :absolute_redirects, true
1107
+ set :prefixed_redirects, false
1108
+ set :empty_path_info, nil
1109
+
1110
+ set :app_file, nil
1111
+ set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) }
1112
+ set :views, Proc.new { root && File.join(root, 'views') }
1113
+ set :reload_templates, Proc.new { development? }
1114
+ set :lock, false
1115
+ set :threaded, true
1116
+
1117
+ set :public_folder, Proc.new { root && File.join(root, 'public') }
1118
+ set :static, Proc.new { public_folder && File.exist?(public_folder) }
1119
+ set :static_cache_control, false
1120
+
1121
+ error ::Exception do
1122
+ response.status = 500
1123
+ content_type 'text/html'
1124
+ '<h1>Internal Server Error</h1>'
1125
+ end
1126
+
1127
+ configure :development do
1128
+ get '/__sinatra__/:image.png' do
1129
+ filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
1130
+ content_type :png
1131
+ send_file filename
1132
+ end
1133
+
1134
+ error NotFound do
1135
+ content_type 'text/html'
1136
+
1137
+ if self.class == Sinatra::Application
1138
+ code = <<-RUBY.gsub(/^ {12}/, '')
1139
+ #{request.request_method.downcase} '#{request.path_info}' do
1140
+ "Hello World"
1141
+ end
1142
+ RUBY
1143
+ else
1144
+ code = <<-RUBY.gsub(/^ {12}/, '')
1145
+ class #{self.class}
1146
+ #{request.request_method.downcase} '#{request.path_info}' do
1147
+ "Hello World"
1148
+ end
1149
+ end
1150
+ RUBY
1151
+
1152
+ file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '')
1153
+ code = "# in #{file}\n#{code}" unless file.empty?
1154
+ end
1155
+
1156
+ (<<-HTML).gsub(/^ {10}/, '')
1157
+ <!DOCTYPE html>
1158
+ <html>
1159
+ <head>
1160
+ <style type="text/css">
1161
+ body { text-align:center;font-family:helvetica,arial;font-size:22px;
1162
+ color:#888;margin:20px}
1163
+ #c {margin:0 auto;width:500px;text-align:left}
1164
+ </style>
1165
+ </head>
1166
+ <body>
1167
+ <h2>Sinatra doesn&rsquo;t know this ditty.</h2>
1168
+ <img src='#{uri "/__sinatra__/404.png"}'>
1169
+ <div id="c">
1170
+ Try this:
1171
+ <pre>#{code}</pre>
1172
+ </div>
1173
+ </body>
1174
+ </html>
1175
+ HTML
1176
+ end
1177
+ end
1178
+ end
1179
+
1180
+ end