Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 15 commits
  • 66 files changed
  • 0 commit comments
  • 1 contributor
Commits on Apr 30, 2010
@jballanc jballanc Getting ready to drop in GCD improvements c0fc5bf
@jballanc jballanc Initial check-in of the GCD work 3a08e71
@jballanc jballanc Updated the README 3ea782d
@jballanc jballanc Added a TODO e90429a
@jballanc jballanc Add a HACKING.rdoc a1124af
@jballanc jballanc Added two known issues to HACKING 9c214a3
@jballanc jballanc Added parser ragel file and updated HACKING a821de6
Commits on May 01, 2010
@jballanc jballanc Don't need to be quite so complicated dac955f
Commits on May 06, 2010
@jballanc jballanc rackup_config needs to be eval'd de3d3a8
@jballanc jballanc Added some samples f56f0d7
@jballanc jballanc Added 'file' info for upload sample acd5819
@jballanc jballanc If you're code isn't beautiful, it's not worth reading 70e8439
Commits on May 07, 2010
@jballanc jballanc Thanks, Vincent! 5549c97
Commits on Jun 02, 2010
@jballanc jballanc Adding a gitignore 36d9100
Commits on Jun 18, 2010
@jballanc jballanc Vendoring Rack 9265a39
Showing with 5,888 additions and 74 deletions.
  1. +3 −0  .gitignore
  2. +74 −0 HACKING.rdoc
  3. +0 −9 README
  4. +31 −0 README.rdoc
  5. +5 −3 Rakefile
  6. +20 −0 TODO
  7. +4 −8 bin/control_tower
  8. +24 −0 control_tower.1
  9. +4 −2 ext/CTParser/CTParser.m
  10. +157 −0 ext/CTParser/http11_parser.rl
  11. +2 −4 lib/control_tower.rb
  12. +53 −48 lib/control_tower/rack_socket.rb
  13. +1 −0  lib/control_tower/server.rb
  14. +86 −0 lib/control_tower/vendor/rack.rb
  15. +22 −0 lib/control_tower/vendor/rack/adapter/camping.rb
  16. +37 −0 lib/control_tower/vendor/rack/auth/abstract/handler.rb
  17. +37 −0 lib/control_tower/vendor/rack/auth/abstract/request.rb
  18. +58 −0 lib/control_tower/vendor/rack/auth/basic.rb
  19. +124 −0 lib/control_tower/vendor/rack/auth/digest/md5.rb
  20. +51 −0 lib/control_tower/vendor/rack/auth/digest/nonce.rb
  21. +55 −0 lib/control_tower/vendor/rack/auth/digest/params.rb
  22. +40 −0 lib/control_tower/vendor/rack/auth/digest/request.rb
  23. +487 −0 lib/control_tower/vendor/rack/auth/openid.rb
  24. +63 −0 lib/control_tower/vendor/rack/builder.rb
  25. +41 −0 lib/control_tower/vendor/rack/cascade.rb
  26. +49 −0 lib/control_tower/vendor/rack/chunked.rb
  27. +52 −0 lib/control_tower/vendor/rack/commonlogger.rb
  28. +47 −0 lib/control_tower/vendor/rack/conditionalget.rb
  29. +29 −0 lib/control_tower/vendor/rack/content_length.rb
  30. +23 −0 lib/control_tower/vendor/rack/content_type.rb
  31. +96 −0 lib/control_tower/vendor/rack/deflater.rb
  32. +153 −0 lib/control_tower/vendor/rack/directory.rb
  33. +88 −0 lib/control_tower/vendor/rack/file.rb
  34. +69 −0 lib/control_tower/vendor/rack/handler.rb
  35. +61 −0 lib/control_tower/vendor/rack/handler/cgi.rb
  36. +8 −0 lib/control_tower/vendor/rack/handler/evented_mongrel.rb
  37. +90 −0 lib/control_tower/vendor/rack/handler/fastcgi.rb
  38. +63 −0 lib/control_tower/vendor/rack/handler/lsws.rb
  39. +91 −0 lib/control_tower/vendor/rack/handler/mongrel.rb
  40. +62 −0 lib/control_tower/vendor/rack/handler/scgi.rb
  41. +8 −0 lib/control_tower/vendor/rack/handler/swiftiplied_mongrel.rb
  42. +18 −0 lib/control_tower/vendor/rack/handler/thin.rb
  43. +71 −0 lib/control_tower/vendor/rack/handler/webrick.rb
  44. +19 −0 lib/control_tower/vendor/rack/head.rb
  45. +546 −0 lib/control_tower/vendor/rack/lint.rb
  46. +65 −0 lib/control_tower/vendor/rack/lobster.rb
  47. +16 −0 lib/control_tower/vendor/rack/lock.rb
  48. +27 −0 lib/control_tower/vendor/rack/methodoverride.rb
  49. +205 −0 lib/control_tower/vendor/rack/mime.rb
  50. +189 −0 lib/control_tower/vendor/rack/mock.rb
  51. +57 −0 lib/control_tower/vendor/rack/recursive.rb
  52. +109 −0 lib/control_tower/vendor/rack/reloader.rb
  53. +248 −0 lib/control_tower/vendor/rack/request.rb
  54. +149 −0 lib/control_tower/vendor/rack/response.rb
  55. +100 −0 lib/control_tower/vendor/rack/rewindable_input.rb
  56. +140 −0 lib/control_tower/vendor/rack/session/abstract/id.rb
  57. +90 −0 lib/control_tower/vendor/rack/session/cookie.rb
  58. +109 −0 lib/control_tower/vendor/rack/session/memcache.rb
  59. +100 −0 lib/control_tower/vendor/rack/session/pool.rb
  60. +349 −0 lib/control_tower/vendor/rack/showexceptions.rb
  61. +106 −0 lib/control_tower/vendor/rack/showstatus.rb
  62. +38 −0 lib/control_tower/vendor/rack/static.rb
  63. +55 −0 lib/control_tower/vendor/rack/urlmap.rb
  64. +590 −0 lib/control_tower/vendor/rack/utils.rb
  65. +17 −0 sample/file_upload.ru
  66. +7 −0 sample/simple_hello.ru
View
3  .gitignore
@@ -0,0 +1,3 @@
+*.bundle
+*.o
+pkg
View
74 HACKING.rdoc
@@ -0,0 +1,74 @@
+== Hacking Control Tower
+
+Control Tower is still in very early development. It is being developed as part
+of the MacRuby project, so be sure to familiarize yourself with MacRuby's
+HACKING.rdoc, as all guidelines there apply here as well. If you have any ideas
+or suggestions for improvements, please communicate them with the MacRuby
+developer's list at <macruby-devel@lists.macosforge.org>. You can also find more
+information at the MacRuby website (http://www.macruby.org/).
+
+
+== CAUTION! AVERTISSEMENT! VOORZICHTIG! 注意!
+
+DO NOT EDIT http11_parser.c! THIS FILE CONTAINS MACHINE GENERATED CODE.
+N'EDITEZ PAS http11_parser.c! Ce fichier a été généré automatiquement.
+WIJZIG http11_parser.c NIET! Dit bestand bevat MACHINE gegenereerde code.
+http11_parser.cを編集しないでください!このファイルが機械生成されいます。
+
+If you really must, you can recreate http11_parser.c from http11_parser.rl using
+Ragel (not included). It would also be acceptable to replace the parser all at
+once, but editing it is not likely to ever be a good idea.
+
+
+== Sample Code
+
+There are two samples that you can run to explore ControlTower's behavior on GET
+and POST requests located in the 'sample' directory. To use these samples, first
+build and install the Control Tower gem:
+
+> rake gem
+> sudo macgem install pkg/control_tower-0.1-universal-darwin-10.gem
+
+Then, to try a GET request, start the 'simple_hello.ru' rack-up config like so:
+
+> control_tower -R sample/simple_hello.ru
+
+and test it with a utility such as curl like so:
+
+> curl http://localhost:8080/
+
+This should reply with a very traditional string and a read-out of the rack
+environment generated by your request. To try a POST request, start the
+'file_upload.ru' config as above, then use curl (or similar tool) to send a post
+with some file content like so:
+
+> curl -F "file=@README.rdoc" http://localhost:8080/
+
+This command tells curl to send the file as a form parameter, and the reply
+should contain the content of the rack 'params' variable constructed from this
+parameter. In particular, when the parameter is named 'file', the
+'file_upload.ru' sample will return the contents of the file.
+
+
+== Debugging
+
+=== Environment variables
+
+Currently, there is only one environment variable specifically for debugging
+Control Tower:
+
+* CT_DEBUG: This will turn on debug logging until we get a better logger.
+
+
+== Known Issues
+
+* Error compiling Regular Expression in Rack::Request
+ Workaround: Modify line 150 in rack/request.rb like so
+- form_vars.sub!(/\0\z/, '')
++ form_vars.slice!(-1) if form_vars[-1] == "\0"
+
+* Problem with Sinatra > 1.0 using Rack.release
+ Workaround: Modify line 39 in sinatra/base.rb like so
+- if Rack.release < '1.1'
++ if Rack.version < '1.1'
+
View
9 README
@@ -1,9 +0,0 @@
-Control Tower
-
-Copyright (c) 2009-2010, Apple Inc
-Author: Joshua Ballanco
-
-SYNOPSIS
-Control Tower is a Web application server for Rack-based Ruby applications. It
-is (or will be) composed of three major components: a networking layer, an HTTP
-parser, and a Rack interface.
View
31 README.rdoc
@@ -0,0 +1,31 @@
+== Control Tower
+Control Tower is a web application server for Rack-based MacRuby applications based on Apple's Grand Central Dispatch libraries.
+
+It is composed of three major components: A Grand Central Dispatch based networking layer, the Mongrel HTTP parser, and Rack web
+application interface. It is currently very much a work in progress!
+
+=== Installation
+From the root directory of the project, run:
+
+ $ rake package
+ $ sudo macgem install pkg/control_tower-0.1-universal-darwin-10.gem
+
+=== Usage
+There are currently only 4 supported command line options:
+
+* -R <rackup_config.ru> : Where you specify the Rackup config file to run
+* -h <hostname> : Hostname for the server (Control Tower will only respond to requests to this host)
+* -p <port> : Port # for the server
+* -c : Use this to enable serving requests to a GCD concurrent queue
+
+=== License
+Control Tower is covered by the Ruby license. See COPYING for more details.
+
+=== Credits
+Control Tower's parser was stolen Thin which stole it from Mongrel (http://mongrel.rubyforge.org) originially written by Zed Shaw.
+Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw <zedshaw at zedshaw dot com> You can redistribute it and/or
+modify it under the terms of the GPL.
+
+Thin is copyright Marc-Andre Cournoyer <macournoyer@gmail.com>
+
+Control Tower is copyright (c) 2009-2010, Apple Inc
View
8 Rakefile
@@ -14,15 +14,17 @@ GEM_SPEC = Gem::Specification.new do |spec|
details).
DESCRIPTION
spec.version = CT_VERSION
- spec.add_runtime_dependency 'rack', '>= 1.0.1'
spec.files = %w(
lib/control_tower.rb
lib/control_tower/rack_socket.rb
lib/control_tower/server.rb
- lib/rack/handler/control_tower.rb
lib/CTParser.bundle
bin/control_tower
- )
+ lib/rack/handler/control_tower.rb
+ lib/control_tower/vendor
+ lib/control_tower/vendor/rack
+ lib/control_tower/vendor/rack.rb
+ ) + Dir.glob('lib/control_tower/vendor/rack/**/*')
spec.executable = 'control_tower'
end
View
20 TODO
@@ -0,0 +1,20 @@
+For 0.1:
+
+[ ] Logging!
+ [ ] An ASL-based Logger class
+ [ ] Request logging (e.g. appache_access.log)
+ [ ] Error logging (e.g. appache_error.log)
+ [ ] Debug logging
+[ ] Testing!
+ [ ] Parser test cases
+ [ ] GET test cases
+ [ ] POST test cases
+ [ ] Concurrency testing
+[ ] Handle broken request pipes
+[ ] Don't reset peer connections
+[ ] Protect against malformed Content-Length in headers
+
+For future:
+
+[ ] Improve body loading
+[ ] Fully-async file upload
View
12 bin/control_tower
@@ -9,7 +9,8 @@ require 'optparse'
@options = {
:rackup => './config.ru',
:port => '8080',
- :host => 'localhost'
+ :host => 'localhost',
+ :concurrent => false
}
OptionParser.new do |opts|
@@ -25,7 +26,7 @@ OptionParser.new do |opts|
@options[:host] = host
end
- opts.on("-c", "--[no]-concurrency", "Handle requests concurrently") do |concurrent|
+ opts.on("-c", "--[no-]concurrency", "Handle requests concurrently") do |concurrent|
@options[:concurrent] = concurrent
end
end.parse!
@@ -35,16 +36,11 @@ unless File.exist? File.expand_path(@options[:rackup])
exit 1
end
-unless File.exist? File.expand_path(@options[:rackup])
- puts "We only know how to deal with Rack-up configs for now"
- exit 1
-end
-
# Under construction...everything is development!
ENV['RACK_ENV'] = 'development'
rackup_config = File.read(File.expand_path(@options[:rackup]))
-app = eval("Rack::Builder.new {( #{rackup_config}\n )}.to_app", TOPLEVEL_BINDING)
+app = eval("Rack::Builder.new { #{rackup_config} }").to_app
# Let's get to business!
server = ControlTower::Server.new(app, @options)
View
24 control_tower.1
@@ -0,0 +1,24 @@
+.Dd May 20, 2010
+.Dt CONTROL_TOWER 1
+.Os
+.Sh NAME
+.Nm control_tower
+.Nd Web application server for Rack-based Ruby applications
+.Sh SYNOPSIS
+.Nm control_tower
+.Fl R Ar file
+.Op Fl p Ar server_port
+.Op Fl h Ar hostname
+.Op Fl c
+.Sh OPTIONS
+Control Tower accepts the following command line options:
+.Bl -tag -width 6n
+.It Fl R Ar file , Fl -rackup Ar file
+Rack-up Configuration File
+.It Fl p Ar server_port , Fl -port Ar server_port
+Port on which to run the server
+.It Fl h Ar hostname , Fl -host Ar hostname
+Hostname for the server
+.It Fl c , Fl -[no]-concurrency
+Handle requests concurrently
+.El
View
6 ext/CTParser/CTParser.m
@@ -81,7 +81,8 @@ void header_done(void *env, const char *at, size_t length)
if (colon_pos.location != NSNotFound) {
serverName = [hostString substringToIndex:colon_pos.location];
serverPort = [hostString substringFromIndex:(colon_pos.location + 1)];
- } else {
+ }
+ else {
serverName = [NSString stringWithString:hostString];
serverPort = @"80";
}
@@ -104,7 +105,8 @@ void header_done(void *env, const char *at, size_t length)
NSMutableString *body = [environment objectForKey:@"rack.input"];
if (body != nil) {
[body appendString:[[NSString alloc] initWithBytes:at length:length encoding:NSASCIIStringEncoding]];
- } else {
+ }
+ else {
NSLog(@"Hmm...you seem to have body data but no where to put it. That's probably an error.");
}
View
157 ext/CTParser/http11_parser.rl
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2005 Zed A. Shaw
+ * You can redistribute it and/or modify it under the same terms as Ruby.
+ */
+#include "parser.h"
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#define LEN(AT, FPC) (FPC - buffer - parser->AT)
+#define MARK(M,FPC) (parser->M = (FPC) - buffer)
+#define PTR_TO(F) (buffer + parser->F)
+
+/** Machine **/
+
+%%{
+
+ machine http_parser;
+
+ action mark {MARK(mark, fpc); }
+
+
+ action start_field { MARK(field_start, fpc); }
+ action write_field {
+ parser->field_len = LEN(field_start, fpc);
+ }
+
+ action start_value { MARK(mark, fpc); }
+ action write_value {
+ if (parser->http_field != NULL) {
+ parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
+ }
+ }
+ action request_method {
+ if (parser->request_method != NULL) {
+ parser->request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
+ }
+ }
+ action request_uri {
+ if (parser->request_uri != NULL) {
+ parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
+ }
+ }
+ action fragment {
+ if (parser->fragment != NULL) {
+ parser->fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
+ }
+ }
+
+ action start_query {MARK(query_start, fpc); }
+ action query_string {
+ if (parser->query_string != NULL) {
+ parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
+ }
+ }
+
+ action http_version {
+ if (parser->http_version != NULL) {
+ parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
+ }
+ }
+
+ action request_path {
+ if (parser->request_path != NULL) {
+ parser->request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
+ }
+ }
+
+ action done {
+ parser->body_start = fpc - buffer + 1;
+ if (parser->header_done != NULL) {
+ parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
+ }
+ fbreak;
+ }
+
+ include http_parser_common "common.rl";
+
+}%%
+
+/** Data **/
+%% write data;
+
+int thin_http_parser_init(http_parser *parser) {
+ int cs = 0;
+ %% write init;
+ parser->cs = cs;
+ parser->body_start = 0;
+ parser->content_len = 0;
+ parser->mark = 0;
+ parser->nread = 0;
+ parser->field_len = 0;
+ parser->field_start = 0;
+
+ return(1);
+}
+
+
+/** exec **/
+size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) {
+ const char *p, *pe;
+ int cs = parser->cs;
+
+ assert(off <= len && "offset past end of buffer");
+
+ p = buffer+off;
+ pe = buffer+len;
+
+ assert(*pe == '\0' && "pointer does not end on NUL");
+ assert(pe - p == len - off && "pointers aren't same distance");
+
+
+ %% write exec;
+
+ parser->cs = cs;
+ parser->nread += p - (buffer + off);
+
+ assert(p <= pe && "buffer overflow after parsing execute");
+ assert(parser->nread <= len && "nread longer than length");
+ assert(parser->body_start <= len && "body starts after buffer end");
+ assert(parser->mark < len && "mark is after buffer end");
+ assert(parser->field_len <= len && "field has length longer than whole buffer");
+ assert(parser->field_start < len && "field starts after buffer end");
+
+ if(parser->body_start) {
+ /* final \r\n combo encountered so stop right here */
+ parser->nread++;
+ }
+
+ return(parser->nread);
+}
+
+int thin_http_parser_finish(http_parser *parser)
+{
+ int cs = parser->cs;
+
+
+ parser->cs = cs;
+
+ if (thin_http_parser_has_error(parser) ) {
+ return -1;
+ } else if (thin_http_parser_is_finished(parser) ) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int thin_http_parser_has_error(http_parser *parser) {
+ return parser->cs == http_parser_error;
+}
+
+int thin_http_parser_is_finished(http_parser *parser) {
+ return parser->cs == http_parser_first_final;
+}
View
6 lib/control_tower.rb
@@ -1,11 +1,9 @@
# This file is covered by the Ruby license. See COPYING for more details.
# Copyright (C) 2009-2010, Apple Inc. All rights reserved.
-require 'rubygems'
-require 'rack'
require 'socket'
require 'tempfile'
+$: << File.join(File.dirname(__FILE__), 'control_tower', 'vendor')
+require 'rack'
require File.join(File.dirname(__FILE__), 'control_tower', 'rack_socket')
require File.join(File.dirname(__FILE__), 'control_tower', 'server')
-
-
View
101 lib/control_tower/rack_socket.rb
@@ -5,52 +5,49 @@
module ControlTower
class RackSocket
- READ_SIZE = 16 * 1024
- RACK_VERSION = 'rack.version'.freeze
VERSION = [1,0].freeze
def initialize(host, port, server, concurrent)
@server = server
@socket = TCPServer.new(host, port)
@status = :closed # Start closed and give the server time to start
- prepare_environment
+ @concurrent = concurrent
- #if concurrent
- # @env['rack.multithread'] = true
- # @request_queue = Dispatch::Queue.concurrent
- #else
- # @env['rack.multithread'] = false
- # @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue')
- #end
- #@request_group = Dispatch::Group.new
+ if @concurrent
+ @request_queue = Dispatch::Queue.concurrent
+ $stdout.puts "Caution: Wake turbulance! Heavy landing on parallel runway."
+ else
+ @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue')
+ end
+ @request_group = Dispatch::Group.new
end
def open
@status = :open
while (@status == :open)
connection = @socket.accept
+ $stderr.puts "Got a connection: #{connection}(fd:#{connection.to_i})" if ENV['CT_DEBUG']
- # TODO -- Concurrency doesn't quite work yet...
- #@request_group.dispatch(@request_queue) do
- req_data = parse!(connection, prepare_environment)
- data = @server.handle_request(req_data)
- data.each do |chunk|
- connection.write chunk
+ @request_queue.async(@request_group) do
+ parse!(connection, prepare_environment) do |env|
+ response = @server.handle_request(env)
+ response.each do |chunk|
+ connection.write chunk
+ end
end
- connection.close
- #end
+ end
end
end
def close
- @status = :close
+ @status = :closed
# You get 30 seconds to empty the request queue and get outa here!
Dispatch::Source.timer(30, 0, 1, Dispatch::Queue.concurrent) do
puts "Timed out waiting for connections to close"
exit 1
end
- #@request_group.wait
+ @request_group.wait
@socket.close
end
@@ -60,43 +57,51 @@ def close
def prepare_environment
{ 'rack.errors' => $stderr,
'rack.input' => '',
- 'rack.multiprocess' => false, # No multiprocess, yet...probably never
+ 'rack.multiprocess' => false, # No multiprocess, yet...possibly never
'rack.run_once' => false,
- RACK_VERSION => VERSION }
+ 'rack.multithread' => @concurrent ? true : false,
+ 'rack.version' => VERSION }
end
- def parse!(connection, env)
+ def parse!(connection, env, &block)
+ connection_queue = Dispatch::Queue.new('com.apple.ControlTower.connection_queue')
parser = ::CTParser.new
data = ""
- headers_done = false
+ parsing_headers = true
content_length = 0
- while (!headers_done || env['rack.input'].bytesize < content_length) do
- select([connection], nil, nil, 1)
- if headers_done
- begin
- data = connection.readpartial(READ_SIZE)
- env['rack.input'] << data
- rescue EOFError
- break
- end
- else
- data << connection.readpartial(READ_SIZE)
- nread = parser.parseData(data, forEnvironment: env, startingAt: nread)
- if parser.finished
- headers_done = true
- content_length = env['CONTENT_LENGTH'].to_i
+ Dispatch::Source.new(Dispatch::Source::READ, connection, 0, connection_queue) do |source|
+ $stderr.puts "#{source.data} bytes incoming" if ENV['CT_DEBUG']
+ begin
+ if parsing_headers
+ $stderr.puts "Parsing headers..." if ENV['CT_DEBUG']
+ data << connection.readpartial(source.data)
+ nread = parser.parseData(data, forEnvironment: env, startingAt: nread)
+ if parser.finished
+ parsing_headers = false
+ $stderr.puts "Headers done! Content-Length: #{env['CONTENT_LENGTH']}" if ENV['CT_DEBUG']
+ content_length = env['CONTENT_LENGTH'].to_i
+ end
+ else
+ $stderr.puts "Reading body" if ENV['CT_DEBUG']
+ env['rack.input'] << connection.readpartial(source.data)
end
+ rescue EOFError
+ content_length = env['rack.input'].bytesize
end
- end
- # Rack says "Make that a StringIO!"
- body = Tempfile.new('control-tower-request-body-')
- body << env['rack.input']
- body.rewind
- env['rack.input'] = body
- # Returning what we've got...
- return env
+ $stderr.puts "Input Length: #{env['rack.input'].bytesize}, Content-Length: #{content_length}" if ENV['CT_DEBUG']
+ unless parsing_headers || env['rack.input'].bytesize < content_length
+ # Rack says "Make that a IO!"
+ body = Tempfile.new('control-tower-request-body-')
+ body << env['rack.input']
+ body.rewind
+ env['rack.input'] = body
+ block.call(env)
+ $stderr.puts "All done. Canceling source for #{source.handle}(fd:#{source.handle.to_i})" if ENV['CT_DEBUG']
+ source.cancel!
+ end
+ end
end
end
end
View
1  lib/control_tower/server.rb
@@ -20,6 +20,7 @@ def start
end
def handle_request(env)
+ env
wrap_output(*@app.call(env))
end
View
86 lib/control_tower/vendor/rack.rb
@@ -0,0 +1,86 @@
+# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
+#
+# Rack is freely distributable under the terms of an MIT-style license.
+# See COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+# The Rack main module, serving as a namespace for all core Rack
+# modules and classes.
+#
+# All modules meant for use in your application are <tt>autoload</tt>ed here,
+# so it should be enough just to <tt>require rack.rb</tt> in your code.
+
+module Rack
+ # The Rack protocol version number implemented.
+ VERSION = [1,0]
+
+ # Return the Rack protocol version as a dotted string.
+ def self.version
+ VERSION.join(".")
+ end
+
+ # Return the Rack release as a dotted string.
+ def self.release
+ "1.0"
+ end
+
+ autoload :Builder, "rack/builder"
+ autoload :Cascade, "rack/cascade"
+ autoload :Chunked, "rack/chunked"
+ autoload :CommonLogger, "rack/commonlogger"
+ autoload :ConditionalGet, "rack/conditionalget"
+ autoload :ContentLength, "rack/content_length"
+ autoload :ContentType, "rack/content_type"
+ autoload :File, "rack/file"
+ autoload :Deflater, "rack/deflater"
+ autoload :Directory, "rack/directory"
+ autoload :ForwardRequest, "rack/recursive"
+ autoload :Handler, "rack/handler"
+ autoload :Head, "rack/head"
+ autoload :Lint, "rack/lint"
+ autoload :Lock, "rack/lock"
+ autoload :MethodOverride, "rack/methodoverride"
+ autoload :Mime, "rack/mime"
+ autoload :Recursive, "rack/recursive"
+ autoload :Reloader, "rack/reloader"
+ autoload :ShowExceptions, "rack/showexceptions"
+ autoload :ShowStatus, "rack/showstatus"
+ autoload :Static, "rack/static"
+ autoload :URLMap, "rack/urlmap"
+ autoload :Utils, "rack/utils"
+
+ autoload :MockRequest, "rack/mock"
+ autoload :MockResponse, "rack/mock"
+
+ autoload :Request, "rack/request"
+ autoload :Response, "rack/response"
+
+ module Auth
+ autoload :Basic, "rack/auth/basic"
+ autoload :AbstractRequest, "rack/auth/abstract/request"
+ autoload :AbstractHandler, "rack/auth/abstract/handler"
+ autoload :OpenID, "rack/auth/openid"
+ module Digest
+ autoload :MD5, "rack/auth/digest/md5"
+ autoload :Nonce, "rack/auth/digest/nonce"
+ autoload :Params, "rack/auth/digest/params"
+ autoload :Request, "rack/auth/digest/request"
+ end
+ end
+
+ module Session
+ autoload :Cookie, "rack/session/cookie"
+ autoload :Pool, "rack/session/pool"
+ autoload :Memcache, "rack/session/memcache"
+ end
+
+ # *Adapters* connect Rack with third party web frameworks.
+ #
+ # Rack includes an adapter for Camping, see README for other
+ # frameworks supporting Rack in their code bases.
+ #
+ # Refer to the submodules for framework-specific calling details.
+
+ module Adapter
+ autoload :Camping, "rack/adapter/camping"
+ end
+end
View
22 lib/control_tower/vendor/rack/adapter/camping.rb
@@ -0,0 +1,22 @@
+module Rack
+ module Adapter
+ class Camping
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["PATH_INFO"] ||= ""
+ env["SCRIPT_NAME"] ||= ""
+ controller = @app.run(env['rack.input'], env)
+ h = controller.headers
+ h.each_pair do |k,v|
+ if v.kind_of? URI
+ h[k] = v.to_s
+ end
+ end
+ [controller.status, controller.headers, [controller.body.to_s]]
+ end
+ end
+ end
+end
View
37 lib/control_tower/vendor/rack/auth/abstract/handler.rb
@@ -0,0 +1,37 @@
+module Rack
+ module Auth
+ # Rack::Auth::AbstractHandler implements common authentication functionality.
+ #
+ # +realm+ should be set for all handlers.
+
+ class AbstractHandler
+
+ attr_accessor :realm
+
+ def initialize(app, realm=nil, &authenticator)
+ @app, @realm, @authenticator = app, realm, authenticator
+ end
+
+
+ private
+
+ def unauthorized(www_authenticate = challenge)
+ return [ 401,
+ { 'Content-Type' => 'text/plain',
+ 'Content-Length' => '0',
+ 'WWW-Authenticate' => www_authenticate.to_s },
+ []
+ ]
+ end
+
+ def bad_request
+ return [ 400,
+ { 'Content-Type' => 'text/plain',
+ 'Content-Length' => '0' },
+ []
+ ]
+ end
+
+ end
+ end
+end
View
37 lib/control_tower/vendor/rack/auth/abstract/request.rb
@@ -0,0 +1,37 @@
+module Rack
+ module Auth
+ class AbstractRequest
+
+ def initialize(env)
+ @env = env
+ end
+
+ def provided?
+ !authorization_key.nil?
+ end
+
+ def parts
+ @parts ||= @env[authorization_key].split(' ', 2)
+ end
+
+ def scheme
+ @scheme ||= parts.first.downcase.to_sym
+ end
+
+ def params
+ @params ||= parts.last
+ end
+
+
+ private
+
+ AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
+
+ def authorization_key
+ @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) }
+ end
+
+ end
+
+ end
+end
View
58 lib/control_tower/vendor/rack/auth/basic.rb
@@ -0,0 +1,58 @@
+require 'rack/auth/abstract/handler'
+require 'rack/auth/abstract/request'
+
+module Rack
+ module Auth
+ # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
+ #
+ # Initialize with the Rack application that you want protecting,
+ # and a block that checks if a username and password pair are valid.
+ #
+ # See also: <tt>example/protectedlobster.rb</tt>
+
+ class Basic < AbstractHandler
+
+ def call(env)
+ auth = Basic::Request.new(env)
+
+ return unauthorized unless auth.provided?
+
+ return bad_request unless auth.basic?
+
+ if valid?(auth)
+ env['REMOTE_USER'] = auth.username
+
+ return @app.call(env)
+ end
+
+ unauthorized
+ end
+
+
+ private
+
+ def challenge
+ 'Basic realm="%s"' % realm
+ end
+
+ def valid?(auth)
+ @authenticator.call(*auth.credentials)
+ end
+
+ class Request < Auth::AbstractRequest
+ def basic?
+ :basic == scheme
+ end
+
+ def credentials
+ @credentials ||= params.unpack("m*").first.split(/:/, 2)
+ end
+
+ def username
+ credentials.first
+ end
+ end
+
+ end
+ end
+end
View
124 lib/control_tower/vendor/rack/auth/digest/md5.rb
@@ -0,0 +1,124 @@
+require 'rack/auth/abstract/handler'
+require 'rack/auth/digest/request'
+require 'rack/auth/digest/params'
+require 'rack/auth/digest/nonce'
+require 'digest/md5'
+
+module Rack
+ module Auth
+ module Digest
+ # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
+ # HTTP Digest Authentication, as per RFC 2617.
+ #
+ # Initialize with the [Rack] application that you want protecting,
+ # and a block that looks up a plaintext password for a given username.
+ #
+ # +opaque+ needs to be set to a constant base64/hexadecimal string.
+ #
+ class MD5 < AbstractHandler
+
+ attr_accessor :opaque
+
+ attr_writer :passwords_hashed
+
+ def initialize(*args)
+ super
+ @passwords_hashed = nil
+ end
+
+ def passwords_hashed?
+ !!@passwords_hashed
+ end
+
+ def call(env)
+ auth = Request.new(env)
+
+ unless auth.provided?
+ return unauthorized
+ end
+
+ if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
+ return bad_request
+ end
+
+ if valid?(auth)
+ if auth.nonce.stale?
+ return unauthorized(challenge(:stale => true))
+ else
+ env['REMOTE_USER'] = auth.username
+
+ return @app.call(env)
+ end
+ end
+
+ unauthorized
+ end
+
+
+ private
+
+ QOP = 'auth'.freeze
+
+ def params(hash = {})
+ Params.new do |params|
+ params['realm'] = realm
+ params['nonce'] = Nonce.new.to_s
+ params['opaque'] = H(opaque)
+ params['qop'] = QOP
+
+ hash.each { |k, v| params[k] = v }
+ end
+ end
+
+ def challenge(hash = {})
+ "Digest #{params(hash)}"
+ end
+
+ def valid?(auth)
+ valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
+ end
+
+ def valid_qop?(auth)
+ QOP == auth.qop
+ end
+
+ def valid_opaque?(auth)
+ H(opaque) == auth.opaque
+ end
+
+ def valid_nonce?(auth)
+ auth.nonce.valid?
+ end
+
+ def valid_digest?(auth)
+ digest(auth, @authenticator.call(auth.username)) == auth.response
+ end
+
+ def md5(data)
+ ::Digest::MD5.hexdigest(data)
+ end
+
+ alias :H :md5
+
+ def KD(secret, data)
+ H([secret, data] * ':')
+ end
+
+ def A1(auth, password)
+ [ auth.username, auth.realm, password ] * ':'
+ end
+
+ def A2(auth)
+ [ auth.method, auth.uri ] * ':'
+ end
+
+ def digest(auth, password)
+ password_hash = passwords_hashed? ? password : H(A1(auth, password))
+
+ KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':')
+ end
+
+ end
+ end
+ end
+end
View
51 lib/control_tower/vendor/rack/auth/digest/nonce.rb
@@ -0,0 +1,51 @@
+require 'digest/md5'
+
+module Rack
+ module Auth
+ module Digest
+ # Rack::Auth::Digest::Nonce is the default nonce generator for the
+ # Rack::Auth::Digest::MD5 authentication handler.
+ #
+ # +private_key+ needs to set to a constant string.
+ #
+ # +time_limit+ can be optionally set to an integer (number of seconds),
+ # to limit the validity of the generated nonces.
+
+ class Nonce
+
+ class << self
+ attr_accessor :private_key, :time_limit
+ end
+
+ def self.parse(string)
+ new(*string.unpack("m*").first.split(' ', 2))
+ end
+
+ def initialize(timestamp = Time.now, given_digest = nil)
+ @timestamp, @given_digest = timestamp.to_i, given_digest
+ end
+
+ def to_s
+ [([ @timestamp, digest ] * ' ')].pack("m*").strip
+ end
+
+ def digest
+ ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':')
+ end
+
+ def valid?
+ digest == @given_digest
+ end
+
+ def stale?
+ !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
+ end
+
+ def fresh?
+ !stale?
+ end
+
+ end
+ end
+ end
+end
View
55 lib/control_tower/vendor/rack/auth/digest/params.rb
@@ -0,0 +1,55 @@
+module Rack
+ module Auth
+ module Digest
+ class Params < Hash
+
+ def self.parse(str)
+ split_header_value(str).inject(new) do |header, param|
+ k, v = param.split('=', 2)
+ header[k] = dequote(v)
+ header
+ end
+ end
+
+ def self.dequote(str) # From WEBrick::HTTPUtils
+ ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
+ ret.gsub!(/\\(.)/, "\\1")
+ ret
+ end
+
+ def self.split_header_value(str)
+ str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
+ end
+
+ def initialize
+ super
+
+ yield self if block_given?
+ end
+
+ def [](k)
+ super k.to_s
+ end
+
+ def []=(k, v)
+ super k.to_s, v.to_s
+ end
+
+ UNQUOTED = ['qop', 'nc', 'stale']
+
+ def to_s
+ inject([]) do |parts, (k, v)|
+ parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
+ parts
+ end.join(', ')
+ end
+
+ def quote(str) # From WEBrick::HTTPUtils
+ '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
+ end
+
+ end
+ end
+ end
+end
+
View
40 lib/control_tower/vendor/rack/auth/digest/request.rb
@@ -0,0 +1,40 @@
+require 'rack/auth/abstract/request'
+require 'rack/auth/digest/params'
+require 'rack/auth/digest/nonce'
+
+module Rack
+ module Auth
+ module Digest
+ class Request < Auth::AbstractRequest
+
+ def method
+ @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
+ end
+
+ def digest?
+ :digest == scheme
+ end
+
+ def correct_uri?
+ (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri
+ end
+
+ def nonce
+ @nonce ||= Nonce.parse(params['nonce'])
+ end
+
+ def params
+ @params ||= Params.parse(parts.last)
+ end
+
+ def method_missing(sym)
+ if params.has_key? key = sym.to_s
+ return params[key]
+ end
+ super
+ end
+
+ end
+ end
+ end
+end
View
487 lib/control_tower/vendor/rack/auth/openid.rb
@@ -0,0 +1,487 @@
+# AUTHOR: Scytrin dai Kinthra <scytrin@gmail.com>; blink#ruby-lang@irc.freenode.net
+
+gem 'ruby-openid', '~> 2' if defined? Gem
+require 'rack/request'
+require 'rack/utils'
+require 'rack/auth/abstract/handler'
+
+require 'uri'
+require 'openid'
+require 'openid/extension'
+require 'openid/store/memory'
+
+module Rack
+ class Request
+ def openid_request
+ @env['rack.auth.openid.request']
+ end
+
+ def openid_response
+ @env['rack.auth.openid.response']
+ end
+ end
+
+ module Auth
+
+ # Rack::Auth::OpenID provides a simple method for setting up an OpenID
+ # Consumer. It requires the ruby-openid library from janrain to operate,
+ # as well as a rack method of session management.
+ #
+ # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
+ #
+ # The OpenID specifications can be found at
+ # http://openid.net/specs/openid-authentication-1_1.html
+ # and
+ # http://openid.net/specs/openid-authentication-2_0.html. Documentation
+ # for published OpenID extensions and related topics can be found at
+ # http://openid.net/developers/specs/.
+ #
+ # It is recommended to read through the OpenID spec, as well as
+ # ruby-openid's documentation, to understand what exactly goes on. However
+ # a setup as simple as the presented examples is enough to provide
+ # Consumer functionality.
+ #
+ # This library strongly intends to utilize the OpenID 2.0 features of the
+ # ruby-openid library, which provides OpenID 1.0 compatiblity.
+ #
+ # NOTE: Due to the amount of data that this library stores in the
+ # session, Rack::Session::Cookie may fault.
+ #
+ # == Examples
+ #
+ # simple_oid = OpenID.new('http://mysite.com/')
+ #
+ # return_oid = OpenID.new('http://mysite.com/', {
+ # :return_to => 'http://mysite.com/openid'
+ # })
+ #
+ # complex_oid = OpenID.new('http://mysite.com/',
+ # :immediate => true,
+ # :extensions => {
+ # ::OpenID::SReg => [['email'],['nickname']]
+ # }
+ # )
+ #
+ # = Advanced
+ #
+ # Most of the functionality of this library is encapsulated such that
+ # expansion and overriding functions isn't difficult nor tricky.
+ # Alternately, to avoid opening up singleton objects or subclassing, a
+ # wrapper rack middleware can be composed to act upon Auth::OpenID's
+ # responses. See #check and #finish for locations of pertinent data.
+ #
+ # == Responses
+ #
+ # To change the responses that Auth::OpenID returns, override the methods
+ # #redirect, #bad_request, #unauthorized, #access_denied, and
+ # #foreign_server_failure.
+ #
+ # Additionally #confirm_post_params is used when the URI would exceed
+ # length limits on a GET request when doing the initial verification
+ # request.
+ #
+ # == Processing
+ #
+ # To change methods of processing completed transactions, override the
+ # methods #success, #setup_needed, #cancel, and #failure. Please ensure
+ # the returned object is a rack compatible response.
+ #
+ # The first argument is an OpenID::Response, the second is a
+ # Rack::Request of the current request, the last is the hash used in
+ # ruby-openid handling, which can be found manually at
+ # env['rack.session'][:openid].
+ #
+ # This is useful if you wanted to expand the processing done, such as
+ # setting up user accounts.
+ #
+ # oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
+ # def oid_app.success oid, request, session
+ # user = Models::User[oid.identity_url]
+ # user ||= Models::User.create_from_openid oid
+ # request['rack.session'][:user] = user.id
+ # redirect MyApp.site_home
+ # end
+ #
+ # site_map['/openid'] = oid_app
+ # map = Rack::URLMap.new site_map
+ # ...
+
+ class OpenID
+ # Raised if an incompatible session is being used.
+ class NoSession < RuntimeError; end
+ # Raised if an extension not matching specifications is provided.
+ class BadExtension < RuntimeError; end
+ # Possible statuses returned from consumer responses. See definitions
+ # in the ruby-openid library.
+ ValidStatus = [
+ ::OpenID::Consumer::SUCCESS,
+ ::OpenID::Consumer::FAILURE,
+ ::OpenID::Consumer::CANCEL,
+ ::OpenID::Consumer::SETUP_NEEDED
+ ]
+
+ # The first argument is the realm, identifying the site they are trusting
+ # with their identity. This is required, also treated as the trust_root
+ # in OpenID 1.x exchanges.
+ #
+ # The lits of acceptable options include :return_to, :session_key,
+ # :openid_param, :store, :immediate, :extensions.
+ #
+ # <tt>:return_to</tt> defines the url to return to after the client
+ # authenticates with the openid service provider. This url should point
+ # to where Rack::Auth::OpenID is mounted. If unprovided, the url of
+ # the current request is used.
+ #
+ # <tt>:session_key</tt> defines the key to the session hash in the env.
+ # The default is 'rack.session'.
+ #
+ # <tt>:openid_param</tt> defines at what key in the request parameters to
+ # find the identifier to resolve. As per the 2.0 spec, the default is
+ # 'openid_identifier'.
+ #
+ # <tt>:store</tt> defined what OpenID Store to use for persistant
+ # information. By default a Store::Memory is used.
+ #
+ # <tt>:immediate</tt> as true will make initial requests to be of an
+ # immediate type. This is false by default. See OpenID specification
+ # documentation.
+ #
+ # <tt>:extensions</tt> should be a hash of openid extension
+ # implementations. The key should be the extension module, the value
+ # should be an array of arguments for extension::Request.new().
+ # The hash is iterated over and passed to #add_extension for processing.
+ # Please see #add_extension for further documentation.
+
+ def initialize(realm, options={})
+ realm = URI(realm)
+ raise ArgumentError, "Invalid realm: #{realm}" \
+ unless realm.absolute? \
+ and realm.fragment.nil? \
+ and realm.scheme =~ /^https?$/ \
+ and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
+ realm.path = '/' if realm.path.empty?
+ @realm = realm.to_s
+
+ if ruri = options[:return_to]
+ ruri = URI(ruri)
+ raise ArgumentError, "Invalid return_to: #{ruri}" \
+ unless ruri.absolute? \
+ and ruri.scheme =~ /^https?$/ \
+ and ruri.fragment.nil?
+ raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
+ unless self.within_realm?(ruri)
+ @return_to = ruri.to_s
+ end
+
+ @session_key = options[:session_key] || 'rack.session'
+ @openid_param = options[:openid_param] || 'openid_identifier'
+ @store = options[:store] || ::OpenID::Store::Memory.new
+ @immediate = !!options[:immediate]
+
+ @extensions = {}
+ if extensions = options[:extensions]
+ extensions.each do |ext, args|
+ add_extension(ext, *args)
+ end
+ end
+
+ # Undocumented, semi-experimental
+ @anonymous = !!options[:anonymous]
+ end
+
+ attr_reader :realm, :return_to, :session_key, :openid_param, :store,
+ :immediate, :extensions
+
+ # Sets up and uses session data at <tt>:openid</tt> within the session.
+ # Errors in this setup will raise a NoSession exception.
+ #
+ # If the parameter 'openid.mode' is set, which implies a followup from
+ # the openid server, processing is passed to #finish and the result is
+ # returned. However, if there is no appropriate openid information in the
+ # session, a 400 error is returned.
+ #
+ # If the parameter specified by <tt>options[:openid_param]</tt> is
+ # present, processing is passed to #check and the result is returned.
+ #
+ # If neither of these conditions are met, #bad_request is called.
+
+ def call(env)
+ env['rack.auth.openid'] = self
+ env_session = env[@session_key]
+ unless env_session and env_session.is_a?(Hash)
+ raise NoSession, 'No compatible session.'
+ end
+ # let us work in our own namespace...
+ session = (env_session[:openid] ||= {})
+ unless session and session.is_a?(Hash)
+ raise NoSession, 'Incompatible openid session.'
+ end
+
+ request = Rack::Request.new(env)
+ consumer = ::OpenID::Consumer.new(session, @store)
+
+ if mode = request.GET['openid.mode']
+ finish(consumer, session, request)
+ elsif request.GET[@openid_param]
+ check(consumer, session, request)
+ else
+ bad_request
+ end
+ end
+
+ # As the first part of OpenID consumer action, #check retrieves the data
+ # required for completion.
+ #
+ # If all parameters fit within the max length of a URI, a 303 redirect
+ # will be returned. Otherwise #confirm_post_params will be called.
+ #
+ # Any messages from OpenID's request are logged to env['rack.errors']
+ #
+ # <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
+ # instance.
+ #
+ # <tt>session[:openid_param]</tt> is set to the openid identifier
+ # provided by the user.
+ #
+ # <tt>session[:return_to]</tt> is set to the return_to uri given to the
+ # identity provider.
+
+ def check(consumer, session, req)
+ oid = consumer.begin(req.GET[@openid_param], @anonymous)
+ req.env['rack.auth.openid.request'] = oid
+ req.env['rack.errors'].puts(oid.message)
+ p oid if $DEBUG
+
+ ## Extension support
+ extensions.each do |ext,args|
+ oid.add_extension(ext::Request.new(*args))
+ end
+
+ session[:openid_param] = req.GET[openid_param]
+ return_to_uri = return_to ? return_to : req.url
+ session[:return_to] = return_to_uri
+ immediate = session.key?(:setup_needed) ? false : immediate
+
+ if oid.send_redirect?(realm, return_to_uri, immediate)
+ redirect(oid.redirect_url(realm, return_to_uri, immediate))
+ else
+ confirm_post_params(oid, realm, return_to_uri, immediate)
+ end
+ rescue ::OpenID::DiscoveryFailure => e
+ # thrown from inside OpenID::Consumer#begin by yadis stuff
+ req.env['rack.errors'].puts( [e.message, *e.backtrace]*"\n" )
+ return foreign_server_failure
+ end
+
+ # This is the final portion of authentication.
+ # If successful, a redirect to the realm is be returned.
+ # Data gathered from extensions are stored in session[:openid] with the
+ # extension's namespace uri as the key.
+ #
+ # Any messages from OpenID's response are logged to env['rack.errors']
+ #
+ # <tt>env['rack.auth.openid.response']</tt> will contain the openid
+ # response.
+
+ def finish(consumer, session, req)
+ oid = consumer.complete(req.GET, req.url)
+ req.env['rack.auth.openid.response'] = oid
+ req.env['rack.errors'].puts(oid.message)
+ p oid if $DEBUG
+
+ if ValidStatus.include?(oid.status)
+ __send__(oid.status, oid, req, session)
+ else
+ invalid_status(oid, req, session)
+ end
+ end
+
+ # The first argument should be the main extension module.
+ # The extension module should contain the constants:
+ # * class Request, should have OpenID::Extension as an ancestor
+ # * class Response, should have OpenID::Extension as an ancestor
+ # * string NS_URI, which defining the namespace of the extension
+ #
+ # All trailing arguments will be passed to extension::Request.new in
+ # #check.
+ # The openid response will be passed to
+ # extension::Response#from_success_response, oid#get_extension_args will
+ # be called on the result to attain the gathered data.
+ #
+ # This method returns the key at which the response data will be found in
+ # the session, which is the namespace uri by default.
+
+ def add_extension(ext, *args)
+ raise BadExtension unless valid_extension?(ext)
+ extensions[ext] = args
+ return ext::NS_URI
+ end
+
+ # Checks the validitity, in the context of usage, of a submitted
+ # extension.
+
+ def valid_extension?(ext)
+ if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
+ raise ArgumentError, 'Extension is missing constants.'
+ elsif not ext::Response.respond_to?(:from_success_response)
+ raise ArgumentError, 'Response is missing required method.'
+ end
+ return true
+ rescue
+ return false
+ end
+
+ # Checks the provided uri to ensure it'd be considered within the realm.
+ # is currently not compatible with wildcard realms.
+
+ def within_realm? uri
+ uri = URI.parse(uri.to_s)
+ realm = URI.parse(self.realm)
+ return false unless uri.absolute?
+ return false unless uri.path[0, realm.path.size] == realm.path
+ return false unless uri.host == realm.host or realm.host[/^\*\./]
+ # for wildcard support, is awkward with URI limitations
+ realm_match = Regexp.escape(realm.host).
+ sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
+ return false unless uri.host.match(realm_match)
+ return true
+ end
+
+ alias_method :include?, :within_realm?
+
+ protected
+
+ # Returns an html form page for posting to an Identity Provider if the
+ # GET request would exceed the upper URI length limit.
+
+ def confirm_post_params(oid, realm, return_to, immediate)
+ response = Rack::Response.new '<html>'+
+ '<head><title>Confirm...</title></head>'+
+ '<body>'+oid.form_markup(realm, return_to, immediate)+'</body>'+
+ '</html>'
+ response.finish
+ end
+
+ # Returns a 303 redirect with the destination of that provided by the
+ # argument.
+
+ def redirect(uri)
+ [ 303, {'Content-Type'=>'text/plain', 'Content-Length'=>'0',
+ 'Location' => uri},
+ [] ]
+ end
+
+ # Returns an empty 400 response.
+
+ def bad_request
+ [ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
+ [''] ]
+ end
+
+ # Returns a basic unauthorized 401 response.
+
+ def unauthorized
+ [ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
+ ['Unauthorized.'] ]
+ end
+
+ # Returns a basic access denied 403 response.
+
+ def access_denied
+ [ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
+ ['Access denied.'] ]
+ end
+
+ # Returns a 503 response to be used if communication with the remote
+ # OpenID server fails.
+
+ def foreign_server_failure
+ [ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
+ ['Foreign server failure.'] ]
+ end
+
+ private
+
+ # Called to complete processing on a successful transaction.
+ # Within the openid session, :openid_identity and :openid_identifier are
+ # set to the user friendly and the standard representation of the
+ # validated identity. All other data in the openid session is cleared.
+
+ def success(oid, request, session)
+ session.clear
+ session[:openid_identity] = oid.display_identifier
+ session[:openid_identifier] = oid.identity_url
+ extensions.keys.each do |ext|
+ label = ext.name[/[^:]+$/].downcase
+ response = ext::Response.from_success_response(oid)
+ session[label] = response.data
+ end
+ redirect(realm)
+ end
+
+ # Called if the Identity Provider indicates further setup by the user is
+ # required.
+ # The identifier is retrived from the openid session at :openid_param.
+ # And :setup_needed is set to true to prevent looping.
+
+ def setup_needed(oid, request, session)
+ identifier = session[:openid_param]
+ session[:setup_needed] = true
+ redirect(req.script_name + '?' + openid_param + '=' + identifier)
+ end
+
+ # Called if the user indicates they wish to cancel identification.
+ # Data within openid session is cleared.
+
+ def cancel(oid, request, session)
+ session.clear
+ access_denied
+ end
+
+ # Called if the Identity Provider indicates the user is unable to confirm
+ # their identity. Data within the openid session is left alone, in case
+ # of swarm auth attacks.
+
+ def failure(oid, request, session)
+ unauthorized
+ end
+
+ # To be called if there is no method for handling the OpenID response
+ # status.
+
+ def invalid_status(oid, request, session)
+ msg = 'Invalid status returned by the OpenID authorization reponse.'
+ [ 500,
+ {'Content-Type'=>'text/plain','Content-Length'=>msg.length.to_s},
+ [msg] ]
+ end
+ end
+
+ # A class developed out of the request to use OpenID as an authentication
+ # middleware. The request will be sent to the OpenID instance unless the
+ # block evaluates to true. For example in rackup, you can use it as such:
+ #
+ # use Rack::Session::Pool
+ # use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
+ # env['rack.session'][:authkey] == a_string
+ # end
+ # run RackApp
+ #
+ # Or simply:
+ #
+ # app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
+
+ class OpenIDAuth < Rack::Auth::AbstractHandler
+ attr_reader :oid
+ def initialize(app, realm, options={}, &auth)
+ @oid = OpenID.new(realm, options)
+ super(app, &auth)
+ end
+
+ def call(env)
+ to = @authenticator.call(env) ? @app : @oid
+ to.call(env)
+ end
+ end
+ end
+end
View
63 lib/control_tower/vendor/rack/builder.rb
@@ -0,0 +1,63 @@
+module Rack
+ # Rack::Builder implements a small DSL to iteratively construct Rack
+ # applications.
+ #
+ # Example:
+ #
+ # app = Rack::Builder.new {
+ # use Rack::CommonLogger
+ # use Rack::ShowExceptions
+ # map "/lobster" do
+ # use Rack::Lint
+ # run Rack::Lobster.new
+ # end
+ # }
+ #
+ # Or
+ #
+ # app = Rack::Builder.app do
+ # use Rack::CommonLogger
+ # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] }
+ # end
+ #
+ # +use+ adds a middleware to the stack, +run+ dispatches to an application.
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
+
+ class Builder
+ def initialize(&block)
+ @ins = []
+ instance_eval(&block) if block_given?
+ end
+
+ def self.app(&block)
+ self.new(&block).to_app
+ end
+
+ def use(middleware, *args, &block)
+ @ins << lambda { |app| middleware.new(app, *args, &block) }
+ end
+
+ def run(app)
+ @ins << app #lambda { |nothing| app }
+ end
+
+ def map(path, &block)
+ if @ins.last.kind_of? Hash
+ @ins.last[path] = self.class.new(&block).to_app
+ else
+ @ins << {}
+ map(path, &block)
+ end
+ end
+
+ def to_app
+ @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
+ inner_app = @ins.last
+ @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
+ end
+
+ def call(env)
+ to_app.call(env)
+ end
+ end
+end
View
41 lib/control_tower/vendor/rack/cascade.rb
@@ -0,0 +1,41 @@
+module Rack
+ # Rack::Cascade tries an request on several apps, and returns the
+ # first response that is not 404 (or in a list of configurable
+ # status codes).
+
+ class Cascade
+ NotFound = [404, {}, []]
+
+ attr_reader :apps
+
+ def initialize(apps, catch=404)
+ @apps = []; @has_app = {}
+ apps.each { |app| add app }
+
+ @catch = {}
+ [*catch].each { |status| @catch[status] = true }
+ end
+
+ def call(env)
+ result = NotFound
+
+ @apps.each do |app|
+ result = app.call(env)
+ break unless @catch.include?(result[0].to_i)
+ end
+
+ result
+ end
+
+ def add app
+ @has_app[app] = true
+ @apps << app
+ end
+
+ def include? app
+ @has_app.include? app
+ end
+
+ alias_method :<<, :add
+ end
+end
View
49 lib/control_tower/vendor/rack/chunked.rb
@@ -0,0 +1,49 @@
+require 'rack/utils'
+
+module Rack
+
+ # Middleware that applies chunked transfer encoding to response bodies
+ # when the response does not include a Content-Length header.
+ class Chunked
+ include Rack::Utils
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers = HeaderHash.new(headers)
+
+ if env['HTTP_VERSION'] == 'HTTP/1.0' ||
+ STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
+ headers['Content-Length'] ||
+ headers['Transfer-Encoding']
+ [status, headers.to_hash, body]
+ else
+ dup.chunk(status, headers, body)
+ end
+ end
+
+ def chunk(status, headers, body)
+ @body = body
+ headers.delete('Content-Length')
+ headers['Transfer-Encoding'] = 'chunked'
+ [status, headers.to_hash, self]
+ end
+
+ def each
+ term = "\r\n"
+ @body.each do |chunk|
+ size = bytesize(chunk)
+ next if size == 0
+ yield [size.to_s(16), term, chunk, term].join
+ end
+ yield ["0", term, "", term].join
+ end
+
+ def close
+ @body.close if @body.respond_to?(:close)
+ end
+ end
+end
View
52 lib/control_tower/vendor/rack/commonlogger.rb
@@ -0,0 +1,52 @@
+module Rack
+ # Rack::CommonLogger forwards every request to an +app+ given, and
+ # logs a line in the Apache common log format to the +logger+, or
+ # rack.errors by default.
+ class CommonLogger
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
+ FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
+
+ def initialize(app, logger=nil)
+ @app = app
+ @logger = logger
+ end
+
+ def call(env)
+ began_at = Time.now
+ status, header, body = @app.call(env)
+ log(env, status, header, began_at)
+ [status, header, body]
+ end
+
+ private
+
+ def log(env, status, header, began_at)
+ now = Time.now
+ length = extract_content_length(header)
+
+ logger = @logger || env['rack.errors']
+ logger.write FORMAT % [
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
+ env["REMOTE_USER"] || "-",
+ now.strftime("%d/%b/%Y %H:%M:%S"),
+ env["REQUEST_METHOD"],
+ env["PATH_INFO"],
+ env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
+ env["HTTP_VERSION"],
+ status.to_s[0..3],
+ length,
+ now - began_at ]
+ end
+
+ def extract_content_length(headers)
+ headers.each do |key, value|
+ if key.downcase == 'content-length'
+ return value.to_s == '0' ? '-' : value
+ end
+ end
+ '-'
+ end
+ end
+end
View
47 lib/control_tower/vendor/rack/conditionalget.rb
@@ -0,0 +1,47 @@
+require 'rack/utils'
+
+module Rack
+
+ # Middleware that enables conditional GET using If-None-Match and
+ # If-Modified-Since. The application should set either or both of the
+ # Last-Modified or Etag response headers according to RFC 2616. When
+ # either of the conditions is met, the response body is set to be zero
+ # length and the response status is set to 304 Not Modified.
+ #
+ # Applications that defer response body generation until the body's each
+ # message is received will avoid response body generation completely when
+ # a conditional GET matches.
+ #
+ # Adapted from Michael Klishin's Merb implementation:
+ # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb
+ class ConditionalGet
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD'])
+
+ status, headers, body = @app.call(env)
+ headers = Utils::HeaderHash.new(headers)
+ if etag_matches?(env, headers) || modified_since?(env, headers)
+ status = 304
+ headers.delete('Content-Type')
+ headers.delete('Content-Length')
+ body = []
+ end
+ [status, headers, body]
+ end
+
+ private
+ def etag_matches?(env, headers)
+ etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH']
+ end
+
+ def modified_since?(env, headers)
+ last_modified = headers['Last-Modified'] and
+ last_modified == env['HTTP_IF_MODIFIED_SINCE']
+ end
+ end
+
+end
View
29 lib/control_tower/vendor/rack/content_length.rb
@@ -0,0 +1,29 @@
+require 'rack/utils'
+
+module Rack
+ # Sets the Content-Length header on responses with fixed-length bodies.
+ class ContentLength
+ include Rack::Utils
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers = HeaderHash.new(headers)
+
+ if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
+ !headers['Content-Length'] &&
+ !headers['Transfer-Encoding'] &&
+ (body.respond_to?(:to_ary) || body.respond_to?(:to_str))
+
+ body = [body] if body.respond_to?(:to_str) # rack 0.4 compat
+ length = body.to_ary.inject(0) { |len, part| len + bytesize(part) }
+ headers['Content-Length'] = length.to_s
+ end
+
+ [status, headers, body]
+ end
+ end
+end
View
23 lib/control_tower/vendor/rack/content_type.rb
@@ -0,0 +1,23 @@
+require 'rack/utils'
+
+module Rack
+
+ # Sets the Content-Type header on responses which don't have one.
+ #
+ # Builder Usage:
+ # use Rack::ContentType, "text/plain"
+ #
+ # When no content type argument is provided, "text/html" is assumed.
+ class ContentType
+ def initialize(app, content_type = "text/html")
+ @app, @content_type = app, content_type
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers = Utils::HeaderHash.new(headers)
+ headers['Content-Type'] ||= @content_type
+ [status, headers.to_hash, body]
+ end
+ end
+end
View
96 lib/control_tower/vendor/rack/deflater.rb
@@ -0,0 +1,96 @@
+require "zlib"
+require "stringio"
+require "time" # for Time.httpdate
+require 'rack/utils'
+
+module Rack
+ class Deflater
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers = Utils::HeaderHash.new(headers)
+
+ # Skip compressing empty entity body responses and responses with
+ # no-transform set.
+ if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
+ headers['Cache-Control'].to_s =~ /\bno-transform\b/
+ return [status, headers, body]
+ end
+
+ request = Request.new(env)
+
+ encoding = Utils.select_best_encoding(%w(gzip deflate identity),
+ request.accept_encoding)
+
+ # Set the Vary HTTP header.
+ vary = headers["Vary"].to_s.split(",").map { |v| v.strip }
+ unless vary.include?("*") || vary.include?("Accept-Encoding")
+ headers["Vary"] = vary.push("Accept-Encoding").join(",")
+ end
+
+ case encoding
+ when "gzip"
+ headers['Content-Encoding'] = "gzip"
+ headers.delete('Content-Length')
+ mtime = headers.key?("Last-Modified") ?
+ Time.httpdate(headers["Last-Modified"]) : Time.now
+ [status, headers, GzipStream.new(body, mtime)]
+ when "deflate"
+ headers['Content-Encoding'] = "deflate"
+ headers.delete('Content-Length')
+ [status, headers, DeflateStream.new(body)]
+ when "identity"
+ [status, headers, body]
+ when nil
+ message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
+ [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
+ end
+ end
+
+ class GzipStream
+ def initialize(body, mtime)
+ @body = body
+ @mtime = mtime
+ end
+
+ def each(&block)
+ @writer = block
+ gzip =::Zlib::GzipWriter.new(self)
+ gzip.mtime = @mtime
+ @body.each { |part| gzip.write(part) }
+ @body.close if @body.respond_to?(:close)
+ gzip.close
+ @writer = nil
+ end
+
+ def write(data)
+ @writer.call(data)
+ end
+ end
+
+ class DeflateStream
+ DEFLATE_ARGS = [
+ Zlib::DEFAULT_COMPRESSION,
+ # drop the zlib header which causes both Safari and IE to choke
+ -Zlib::MAX_WBITS,
+ Zlib::DEF_MEM_LEVEL,
+ Zlib::DEFAULT_STRATEGY
+ ]
+
+ def initialize(body)
+ @body = body
+ end
+
+ def each
+ deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
+ @body.each { |part| yield deflater.deflate(part) }
+ @body.close if @body.respond_to?(:close)
+ yield deflater.finish
+ nil
+ end
+ end
+ end
+end
View
153 lib/control_tower/vendor/rack/directory.rb
@@ -0,0 +1,153 @@
+require 'time'
+require 'rack/utils'
+require 'rack/mime'
+
+module Rack
+ # Rack::Directory serves entries below the +root+ given, according to the
+ # path info of the Rack request. If a directory is found, the file's contents
+ # will be presented in an html based index. If a file is found, the env will
+ # be passed to the specified +app+.
+ #
+ # If +app+ is not specified, a Rack::File of the same +root+ will be used.
+
+ class Directory
+ DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
+ DIR_PAGE = <<-PAGE
+<html><head>
+ <title>%s</title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <style type='text/css'>
+table { width:100%%; }
+.name { text-align:left; }
+.size, .mtime { text-align:right; }
+.type { width:11em; }
+.mtime { width:15em; }
+ </style>
+</head><body>
+<h1>%s</h1>
+<hr />
+<table>
+ <tr>
+ <th class='name'>Name</th>
+ <th class='size'>Size</th>
+ <th class='type'>Type</th>
+ <th class='mtime'>Last Modified</th>
+ </tr>
+%s
+</table>
+<hr />
+</body></html>
+ PAGE
+
+ attr_reader :files
+ attr_accessor :root, :path
+
+ def initialize(root, app=nil)
+ @root = F.expand_path(root)
+ @app = app || Rack::File.new(@root)
+ end
+
+ def call(env)
+ dup._call(env)
+ end
+
+ F = ::File
+
+ def _call(env)
+ @env = env
+ @script_name = env['SCRIPT_NAME']
+ @path_info = Utils.unescape(env['PATH_INFO'])
+
+ if forbidden = check_forbidden
+ forbidden
+ else
+ @path = F.join(@root, @path_info)
+ list_path
+ end
+ end
+
+ def check_forbidden
+ return unless @path_info.include? ".."
+
+ body = "Forbidden\n"
+ size = Rack::Utils.bytesize(body)
+ return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
+ end
+
+ def list_directory
+ @files = [['../','Parent Directory','','','']]
+ glob = F.join(@path, '*')
+
+ Dir[glob].sort.each do |node|
+ stat = stat(node)
+ next unless stat
+ basename = F.basename(node)
+ ext = F.extname(node)
+
+ url = F.join(@script_name, @path_info, basename)
+ size = stat.size
+ type = stat.directory? ? 'directory' : Mime.mime_type(ext)
+ size = stat.directory? ? '-' : filesize_format(size)
+ mtime = stat.mtime.httpdate
+ url << '/' if stat.directory?
+ basename << '/' if stat.directory?
+
+ @files << [ url, basename, size, type, mtime ]
+ end
+
+ return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ]
+ end
+
+ def stat(node, max = 10)
+ F.stat(node)
+ rescue Errno::ENOENT, Errno::ELOOP
+ return nil
+ end
+
+ # TODO: add correct response if not readable, not sure if 404 is the best
+ # option
+ def list_path
+ @stat = F.stat(@path)
+
+ if @stat.readable?
+ return @app.call(@env) if @stat.file?
+ return list_directory if @stat.directory?
+ else
+ raise Errno::ENOENT, 'No such file or directory'
+ end
+
+ rescue Errno::ENOENT, Errno::ELOOP
+ return entity_not_found
+ end
+
+ def entity_not_found
+ body = "Entity not found: #{@path_info}\n"
+ size = Rack::Utils.bytesize(body)
+ return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
+ end
+
+ def each
+ show_path = @path.sub(/^#{@root}/,'')
+ files = @files.map{|f| DIR_FILE % f }*"\n"
+ page = DIR_PAGE % [ show_path, show_path , files ]
+ page.each_line{|l| yield l }
+ end
+
+ # Stolen from Ramaze
+
+ FILESIZE_FORMAT = [
+ ['%.1fT', 1 << 40],
+ ['%.1fG', 1 << 30],
+ ['%.1fM', 1 << 20],
+ ['%.1fK', 1 << 10],
+ ]
+
+ def filesize_format(int)
+ FILESIZE_FORMAT.each do |format, size|
+ return format % (int.to_f / size) if int >= size
+ end
+
+ int.to_s + 'B'
+ end
+ end
+end
View
88 lib/control_tower/vendor/rack/file.rb
@@ -0,0 +1,88 @@
+require 'time'
+require 'rack/utils'
+require 'rack/mime'
+
+module Rack
+ # Rack::File serves files below the +root+ given, according to the
+ # path info of the Rack request.
+ #
+ # Handlers can detect if bodies are a Rack::File, and use mechanisms
+ # like sendfile on the +path+.
+
+ class File
+ attr_accessor :root
+ attr_accessor :path
+
+ alias :to_path :path
+
+ def initialize(root)
+ @root = root
+ end
+
+ def call(env)
+ dup._call(env)
+ end
+
+ F = ::File
+
+ def _call(env)
+ @path_info = Utils.unescape(env["PATH_INFO"])
+ return forbidden if @path_info.include? ".."
+
+ @path = F.join(@root, @path_info)
+
+ begin
+ if F.file?(@path) && F.readable?(@path)
+ serving
+ else
+ raise Errno::EPERM
+ end
+ rescue SystemCallError
+ not_found
+ end
+ end
+
+ def forbidden
+ body = "Forbidden\n"
+ [403, {"Content-Type" => "text/plain",
+ "Content-Length" => body.size.to_s},
+ [body]]
+ end
+
+ # NOTE:
+ # We check via File::size? whether this file provides size info
+ # via stat (e.g. /proc files often don't), otherwise we have to
+ # figure it out by reading the whole file into memory. And while
+ # we're at it we also use this as body then.
+
+ def serving
+ if size = F.size?(@path)
+ body = self
+ else
+ body = [F.read(@path)]
+ size = Utils.bytesize(body.first)
+ end
+
+ [200, {
+ "Last-Modified" => F.mtime(@path).httpdate,
+ "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'),
+ "Content-Length" => size.to_s
+ }, body]
+ end
+
+ def not_found
+ body = "File not found: #{@path_info}\n"
+ [404, {"Content-Type" => "text/plain",
+ "Content-Length" => body.size.to_s},
+ [body]]
+ end
+
+ def each
+ F.open(@path, "rb") { |file|
+ while part = file.read(8192)
+ yield part
+ end
+ }
+ end
+ end
+end
View
69 lib/control_tower/vendor/rack/handler.rb
@@ -0,0 +1,69 @@
+module Rack
+ # *Handlers* connect web servers with Rack.
+ #
+ # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
+ # and LiteSpeed.
+ #
+ # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
+ # A second optional hash can be passed to include server-specific
+ # configuration.
+ module Handler
+ def self.get(server)
+ return unless server
+ server = server.to_s
+
+ if klass = @handlers[server]
+ obj = Object
+ klass.split("::").each { |x| obj = obj.const_get(x) }
+ obj
+ else
+ try_require('rack/handler', server)
+ const_get(server)
+ end
+ end
+
+ # Transforms server-name constants to their canonical form as filenames,
+ # then tries to require them but silences the LoadError if not found
+ #
+ # Naming convention:
+ #
+ # Foo # => 'foo'
+ # FooBar # => 'foo_bar.rb'
+ # FooBAR # => 'foobar.rb'
+ # FOObar # => 'foobar.rb'
+ # FOOBAR # => 'foobar.rb'
+ # FooBarBaz # => 'foo_bar_baz.rb'
+ def self.try_require(prefix, const_name)
+ file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
+ gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
+