├── .gitignore ├── COPYING ├── GPL ├── HACKING.rdoc ├── README.rdoc ├── Rakefile ├── TODO ├── bin └── control_tower ├── control_tower.1 ├── ext └── CTParser │ ├── CTParser.h │ ├── CTParser.m │ ├── extconf.rb │ ├── http11_parser.c │ └── http11_parser.h ├── lib ├── control_tower.rb ├── control_tower │ ├── rack_socket.rb │ ├── server.rb │ └── vendor │ │ ├── rack.rb │ │ └── rack │ │ ├── auth │ │ ├── abstract │ │ │ ├── handler.rb │ │ │ └── request.rb │ │ ├── basic.rb │ │ └── digest │ │ │ ├── md5.rb │ │ │ ├── nonce.rb │ │ │ ├── params.rb │ │ │ └── request.rb │ │ ├── builder.rb │ │ ├── cascade.rb │ │ ├── chunked.rb │ │ ├── commonlogger.rb │ │ ├── conditionalget.rb │ │ ├── config.rb │ │ ├── content_length.rb │ │ ├── content_type.rb │ │ ├── deflater.rb │ │ ├── directory.rb │ │ ├── etag.rb │ │ ├── file.rb │ │ ├── handler.rb │ │ ├── handler │ │ ├── cgi.rb │ │ ├── evented_mongrel.rb │ │ ├── fastcgi.rb │ │ ├── lsws.rb │ │ ├── mongrel.rb │ │ ├── scgi.rb │ │ ├── swiftiplied_mongrel.rb │ │ ├── thin.rb │ │ └── webrick.rb │ │ ├── head.rb │ │ ├── lint.rb │ │ ├── lobster.rb │ │ ├── lock.rb │ │ ├── logger.rb │ │ ├── methodoverride.rb │ │ ├── mime.rb │ │ ├── mock.rb │ │ ├── nulllogger.rb │ │ ├── recursive.rb │ │ ├── reloader.rb │ │ ├── request.rb │ │ ├── response.rb │ │ ├── rewindable_input.rb │ │ ├── runtime.rb │ │ ├── sendfile.rb │ │ ├── server.rb │ │ ├── session │ │ ├── abstract │ │ │ └── id.rb │ │ ├── cookie.rb │ │ ├── memcache.rb │ │ └── pool.rb │ │ ├── showexceptions.rb │ │ ├── showstatus.rb │ │ ├── static.rb │ │ ├── urlmap.rb │ │ └── utils.rb └── rack │ ├── handler │ └── control_tower.rb │ └── session │ └── gcd.rb ├── sample ├── README.rdoc ├── example_file.txt ├── file_upload.ru ├── gcd_sessions.ru ├── photo_search.ru ├── simple_hello.ru ├── sinatra_hello.ru └── x_sendfile.ru └── spec └── ctparser_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | *.o 3 | pkg 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | ControlTower is copyrighted free software by Apple Inc. 2 | You can redistribute it and/or modify it under either the terms of the GPL 3 | version 2 (see the file GPL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a) distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /GPL: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /HACKING.rdoc: -------------------------------------------------------------------------------- 1 | == Hacking Control Tower 2 | 3 | Control Tower is still a very young project. It is being developed as part of 4 | the MacRuby project, so be sure to familiarize yourself with MacRuby's 5 | HACKING.rdoc, as all guidelines there apply here as well. If you have any ideas 6 | or suggestions for improvements, please communicate them with the MacRuby 7 | developer's list at . You can also find more 8 | information at the MacRuby website (http://www.macruby.org/). 9 | 10 | 11 | === CAUTION! AVERTISSEMENT! VOORZICHTIG! 注意! 12 | 13 | DO NOT EDIT http11_parser.c! THIS FILE CONTAINS MACHINE GENERATED CODE. 14 | N'EDITEZ PAS http11_parser.c! Ce fichier a été généré automatiquement. 15 | WIJZIG http11_parser.c NIET! Dit bestand bevat MACHINE gegenereerde code. 16 | http11_parser.cを編集しないでください!このファイルが機械生成されいます。 17 | 18 | If you really must, you can recreate http11_parser.c from http11_parser.rl using 19 | Ragel (not included). It would also be acceptable to replace the parser all at 20 | once, but editing it is not likely to ever be a good idea. 21 | 22 | 23 | === Project Structure 24 | 25 | The three components of ControlTower are: 26 | 27 | 1. The Mongrel parser. The Ragel-generated C file is wrapped by an objective C 28 | class and the whole thing is collected as an extension in ext/CTParser. 29 | 30 | 2. The GCD based socket handling code. This is located in rack_socket.rb. 31 | Primarily, the while loop located in the #open method on RackSocket. After 32 | each connection is made, the connection is handed off to the GCD queue 33 | (either serial or concurrent) for processing. 34 | 35 | 3. Interaction with the Rack app is coordinated by the Server object in 36 | server.rb. Actually, the Server object handles the options hash and 37 | coordinates opening and closing of the socket as well. 38 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == ControlTower 2 | 3 | ControlTower is a web application server for Rack-based MacRuby applications 4 | based on Apple's Grand Central Dispatch libraries. 5 | 6 | It is composed of three major components: A Grand Central Dispatch based 7 | networking layer, the Mongrel HTTP parser, and a Rack web application interface. 8 | 9 | 10 | === Installation 11 | 12 | ControlTower can be installed as a gem using the `macgem` command. To build and 13 | install from source, go to the root directory of the project and run: 14 | 15 | $ rake package 16 | $ sudo macgem install pkg/control_tower-1.0-universal-darwin-10.gem 17 | 18 | 19 | === Usage 20 | 21 | There are currently only 4 supported command line options: 22 | 23 | * -R : Where you specify the Rack-up config to run 24 | * -a : Address to bind server to (default: 0.0.0.0) 25 | * -p : Port on which to run the server (default: 3000) 26 | * -c : Enable serving requests on a concurrent GCD queue 27 | 28 | 29 | === Sample 30 | 31 | There are a collection of Rack-up configs located in the "sample" directory that 32 | demonstrate some features of ControlTower and MacRuby. See the README in that 33 | directory for more information. 34 | 35 | 36 | === License 37 | 38 | All the files in this distribution are covered under the Ruby's license (see the 39 | file COPYING). 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake/gempackagetask' 3 | 4 | CT_VERSION = '1.0' 5 | 6 | GEM_SPEC = Gem::Specification.new do |spec| 7 | spec.name = 'control_tower' 8 | spec.summary = 'A Rack-based HTTP server for MacRuby' 9 | spec.description = <<-DESCRIPTION 10 | Control Tower is a Rack-based HTTP server designed to work with MacRuby. It can 11 | be used by calling to its Rack::Handler class, or by running the control_tower 12 | executable with a Rackup configuration file (see the control tower help for more 13 | details). 14 | DESCRIPTION 15 | spec.author = 'MacRuby Team' 16 | spec.email = 'macruby-devel@lists.macosforge.org' 17 | spec.homepage = 'http://www.macruby.org' 18 | spec.version = CT_VERSION 19 | spec.files = %w( lib/control_tower.rb 20 | lib/control_tower/rack_socket.rb 21 | lib/control_tower/server.rb 22 | lib/rack/handler/control_tower.rb 23 | lib/rack/session/gcd.rb 24 | bin/control_tower 25 | ext/CTParser/http11_parser.h 26 | ext/CTParser/http11_parser.c 27 | ext/CTParser/CTParser.h 28 | ext/CTParser/CTParser.m 29 | ext/CTParser/extconf.rb ) 30 | spec.extensions << 'ext/CTParser/extconf.rb' 31 | spec.executable = 'control_tower' 32 | spec.add_dependency('rack', '= 1.2.1') 33 | end 34 | 35 | verbose(true) 36 | 37 | desc "Same as all" 38 | task :default => :all 39 | 40 | desc "Build everything" 41 | task :all => ['build', 'gem'] 42 | 43 | desc "Build CTParser" 44 | task :build do 45 | gcc = RbConfig::CONFIG['CC'] 46 | cflags = RbConfig::CONFIG['CFLAGS'] + ' ' + RbConfig::CONFIG['ARCH_FLAG'] 47 | cflags.sub!(/-O./, '-O3') 48 | cflags << " -Wall" 49 | 50 | Dir.chdir('ext/CTParser') do 51 | sh "#{gcc} #{cflags} -fobjc-gc CTParser.m -c -o CTParser.o" 52 | sh "#{gcc} #{cflags} http11_parser.c -c -o http11_parser.o" 53 | sh "#{RbConfig::CONFIG['LDSHARED']} CTParser.o http11_parser.o -o CTParser.bundle" 54 | end 55 | end 56 | 57 | desc "Clean packages and extensions" 58 | task :clean do 59 | sh "rm -rf pkg ext/CTParser/*.o ext/CTParser/*.bundle lib/*.bundle" 60 | end 61 | 62 | desc "Install as a standard library" 63 | task :stdlib_install => [:build] do 64 | prefix = (ENV['DESTDIR'] || '') 65 | dest = File.join(prefix, RbConfig::CONFIG['sitelibdir']) 66 | mkdir_p(dest) 67 | sh "ditto lib \"#{dest}\"" 68 | dest = File.join(prefix, RbConfig::CONFIG['sitearchdir']) 69 | mkdir_p(dest) 70 | sh "cp ext/CTParser/CTParser.bundle \"#{dest}\"" 71 | end 72 | 73 | file 'ext/CTParser/CTParser.bundle' => 'build' 74 | 75 | file 'lib/CTParser.bundle' => ['ext/CTParser/CTParser.bundle'] do 76 | FileUtils.cp('ext/CTParser/CTParser.bundle', 'lib/CTParser.bundle') 77 | end 78 | 79 | Rake::GemPackageTask.new(GEM_SPEC) do |pkg| 80 | pkg.need_zip = false 81 | pkg.need_tar = true 82 | end 83 | 84 | desc "Run Control Tower" 85 | task :run do 86 | sh "macruby -I./lib -I./ext/CTParser bin/control_tower" 87 | end 88 | 89 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | For 0.1: 2 | 3 | [ ] Logging! 4 | [ ] An ASL-based Logger class (?) 5 | [ ] Request logging (maybe? this is also handled by Rack::CommonLogger) 6 | [ ] Debug logging 7 | [ ] Benchmark logging 8 | [ ] Testing! 9 | [ ] Parser test cases 10 | [ ] GET test cases 11 | [ ] POST test cases 12 | [ ] Concurrency testing 13 | [X] Handle broken request pipes 14 | [X] Don't reset peer connections 15 | [X] Handle multibyte characters in headers 16 | [X] Improve body loading 17 | [X] Be smarter about using Tempfiles vs StringIO for request body 18 | 19 | For beyond: 20 | [ ] Daemonizable! 21 | [ ] Accept local requests only 22 | [ ] Keep alive (this one is tough...probably post 1.0) 23 | -------------------------------------------------------------------------------- /bin/control_tower: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env macruby 2 | # This file is covered by the Ruby license. See COPYING for more details. 3 | # Copyright (C) 2009-2010, Apple Inc. All rights reserved. 4 | 5 | require 'control_tower' 6 | require 'optparse' 7 | 8 | # Some default values 9 | @options = { 10 | :rackup => './config.ru', 11 | :port => '3000', 12 | :host => '0.0.0.0', 13 | :concurrent => false 14 | } 15 | 16 | OptionParser.new do |opts| 17 | opts.on("-R", "--rackup FILE", "Rack-up Configuration File") do |rackup| 18 | @options[:rackup] = rackup 19 | end 20 | 21 | opts.on("-p", "--port PORT", Integer, "Port on which to run the server (default: 3000) (0 gets the socket from launchd and ignores --address)") do |port| 22 | @options[:port] = port 23 | end 24 | 25 | opts.on("-a", "--address HOST", "Address to bind server to (default: 0.0.0.0)") do |host| 26 | @options[:host] = host 27 | end 28 | 29 | opts.on("-c", "--[no-]concurrency", "Handle requests concurrently") do |concurrent| 30 | @options[:concurrent] = concurrent 31 | end 32 | end.parse! 33 | 34 | unless File.exist?(@options[:rackup]) 35 | $stderr.puts "Rack-up config file `#{@options[:rackup]}' doesn't exist!" 36 | exit 1 37 | end 38 | 39 | # Under construction...everything is development! 40 | ENV['RACK_ENV'] = 'development' 41 | 42 | rackup_config = File.read(@options[:rackup]) 43 | app = eval("Rack::Builder.new { #{rackup_config} }").to_app 44 | 45 | # Let's get to business! 46 | server = ControlTower::Server.new(app, @options) 47 | if server 48 | if @options[:port] != 0 49 | puts "Control Tower is listening on #{@options[:host]}:#{@options[:port]}" 50 | else 51 | puts "Control Tower is receiving socket connections from launchd" 52 | end 53 | server.start 54 | else 55 | puts "Mayday! Mayday! Eject! Eject!\n#{$!}" 56 | exit 1 57 | end 58 | -------------------------------------------------------------------------------- /control_tower.1: -------------------------------------------------------------------------------- 1 | .Dd May 20, 2010 2 | .Dt CONTROL_TOWER 1 3 | .Os 4 | .Sh NAME 5 | .Nm control_tower 6 | .Nd Web application server for Rack-based Ruby applications 7 | .Sh SYNOPSIS 8 | .Nm control_tower 9 | .Fl R Ar file 10 | .Op Fl p Ar server_port 11 | .Op Fl h Ar hostname 12 | .Op Fl c 13 | .Sh OPTIONS 14 | Control Tower accepts the following command line options: 15 | .Bl -tag -width 6n 16 | .It Fl R Ar file , Fl -rackup Ar file 17 | Rack-up Configuration File 18 | .It Fl p Ar server_port , Fl -port Ar server_port 19 | Port on which to run the server 20 | .It Fl h Ar hostname , Fl -host Ar hostname 21 | Hostname for the server 22 | .It Fl c , Fl -[no]-concurrency 23 | Handle requests concurrently 24 | .El 25 | -------------------------------------------------------------------------------- /ext/CTParser/CTParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is covered by the Ruby license. See COPYING for more details. 3 | * Copyright (C) 2009-2010, Apple Inc. All rights reserved. 4 | */ 5 | 6 | #include "http11_parser.h" 7 | #import 8 | 9 | #define SERVER_SOFTWARE @"Control Tower v1.0" 10 | 11 | @interface CTParser : NSObject 12 | { 13 | http_parser *_parser; 14 | } 15 | 16 | - (id)init; 17 | - (void)reset; 18 | 19 | - (NSNumber *)parseData:(NSData *)dataBuf forEnvironment:(NSDictionary *)env startingAt:(NSNumber *)startingPos; 20 | - (NSNumber *)parseData:(NSData *)dataBuf forEnvironment:(NSDictionary *)env; 21 | 22 | - (BOOL)errorCond; 23 | - (BOOL)finished; 24 | - (NSNumber *)nread; 25 | 26 | - (void)finalize; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ext/CTParser/CTParser.m: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is covered by the Ruby license. See COPYING for more details. 3 | * Copyright (C) 2009-2010, Apple Inc. All rights reserved. 4 | */ 5 | 6 | #import "CTParser.h" 7 | 8 | #pragma mark Parser Callbacks 9 | 10 | #define DEF_MAX_LENGTH(N, val) const size_t MAX_##N##_LENGTH = val 11 | 12 | #define VALIDATE_MAX_LENGTH(len, N) \ 13 | if (len > MAX_##N##_LENGTH) { \ 14 | [NSException raise:@"ParserFieldLengthError" \ 15 | format:@"HTTP element " # N " is longer than the " \ 16 | # len " character allowed length."]; \ 17 | } 18 | 19 | #define PARSE_FIELD(field) \ 20 | static void \ 21 | parse_##field(void *env, const char *at, size_t length) \ 22 | { \ 23 | VALIDATE_MAX_LENGTH(length, field) \ 24 | NSString *val = [[NSString alloc] initWithBytes:at length:length \ 25 | encoding:NSUTF8StringEncoding]; \ 26 | [(NSMutableDictionary *)env setObject:val forKey:@"" #field]; \ 27 | } 28 | 29 | // Max field lengths 30 | DEF_MAX_LENGTH(FIELD_NAME, 256); 31 | DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024); 32 | DEF_MAX_LENGTH(REQUEST_METHOD, 256); 33 | DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12); 34 | DEF_MAX_LENGTH(FRAGMENT, 1024); 35 | DEF_MAX_LENGTH(PATH_INFO, 1024); 36 | DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); 37 | DEF_MAX_LENGTH(HTTP_VERSION, 256); 38 | DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); 39 | 40 | static void 41 | parse_HTTP_FIELD(void *env, const char *field, size_t flen, const char *value, 42 | size_t vlen) 43 | { 44 | VALIDATE_MAX_LENGTH(flen, FIELD_NAME); 45 | VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE); 46 | 47 | NSString *val = [[NSString alloc] initWithBytes:value length:vlen 48 | encoding:NSUTF8StringEncoding]; 49 | 50 | NSString *key; 51 | if (strncmp(field, "HOST", 4) == 0) { 52 | key = @"HTTP_HOST"; 53 | } 54 | else if (strncmp(field, "REFERER", 4) == 0) { 55 | key = @"HTTP_REFERER"; 56 | } 57 | else if (strncmp(field, "CACHE_CONTROL", 4) == 0) { 58 | key = @"HTTP_CACHE_CONTROL"; 59 | } 60 | else if (strncmp(field, "COOKIE", 4) == 0) { 61 | key = @"HTTP_COOKIE"; 62 | } 63 | else if (strncmp(field, "CONNECTION", 4) == 0) { 64 | key = @"HTTP_CONNECTION"; 65 | } 66 | else { 67 | key = [@"HTTP_" stringByAppendingString:[[NSString alloc] 68 | initWithBytes:field length:flen encoding:NSUTF8StringEncoding]]; 69 | } 70 | [(NSMutableDictionary *)env setObject:val forKey:key]; 71 | } 72 | 73 | // Parsing callback functions 74 | PARSE_FIELD(REQUEST_METHOD); 75 | PARSE_FIELD(REQUEST_URI); 76 | PARSE_FIELD(FRAGMENT); 77 | PARSE_FIELD(PATH_INFO); 78 | PARSE_FIELD(QUERY_STRING); 79 | PARSE_FIELD(HTTP_VERSION); 80 | 81 | static void 82 | header_done(void *env, const char *at, size_t length) 83 | { 84 | NSMutableDictionary *environment = (NSMutableDictionary *)env; 85 | NSString *contentLength = [environment objectForKey:@"HTTP_CONTENT_LENGTH"]; 86 | if (contentLength != nil) { 87 | [environment setObject:contentLength forKey:@"CONTENT_LENGTH"]; 88 | } 89 | 90 | NSString *contentType = [environment objectForKey:@"HTTP_CONTENT_TYPE"]; 91 | if (contentType != nil) { 92 | [environment setObject:contentType forKey:@"CONTENT_TYPE"]; 93 | } 94 | 95 | [environment setObject:@"CGI/1.2" forKey:@"GATEWAY_INTERFACE"]; 96 | 97 | NSString *hostString = [environment objectForKey:@"HTTP_HOST"]; 98 | NSString *serverName = nil; 99 | NSString *serverPort = nil; 100 | if (hostString != nil) { 101 | NSRange colon_pos = [hostString rangeOfString:@":"]; 102 | if (colon_pos.location != NSNotFound) { 103 | serverName = [hostString substringToIndex:colon_pos.location]; 104 | serverPort = [hostString substringFromIndex:colon_pos.location+1]; 105 | } 106 | else { 107 | serverName = [NSString stringWithString:hostString]; 108 | serverPort = @"80"; 109 | } 110 | [environment setObject:serverName forKey:@"SERVER_NAME"]; 111 | [environment setObject:serverPort forKey:@"SERVER_PORT"]; 112 | } 113 | 114 | [environment setObject:@"HTTP/1.1" forKey:@"SERVER_PROTOCOL"]; 115 | [environment setObject:SERVER_SOFTWARE forKey:@"SERVER_SOFTWARE"]; 116 | [environment setObject:@"" forKey:@"SCRIPT_NAME"]; 117 | 118 | // We don't do tls yet 119 | [environment setObject:@"http" forKey:@"rack.url_scheme"]; 120 | 121 | // To satisfy Rack specs... 122 | if ([environment objectForKey:@"QUERY_STRING"] == nil) { 123 | [environment setObject:@"" forKey:@"QUERY_STRING"]; 124 | } 125 | 126 | // If we've been given any part of the body, put it here 127 | NSMutableData *body = [[NSMutableData alloc] init]; 128 | [body appendData:[NSData dataWithBytes:at length:length]]; 129 | [environment setObject:body forKey:@"rack.input"]; 130 | } 131 | 132 | @implementation CTParser 133 | 134 | - (id)init 135 | { 136 | self = [super init]; 137 | if (self != nil) { 138 | _parser = malloc(sizeof(http_parser)); 139 | assert(_parser != NULL); 140 | 141 | // Setup the callbacks 142 | _parser->http_field = parse_HTTP_FIELD; 143 | _parser->request_method = parse_REQUEST_METHOD; 144 | _parser->request_uri = parse_REQUEST_URI; 145 | _parser->fragment = parse_FRAGMENT; 146 | _parser->request_path = parse_PATH_INFO; 147 | _parser->query_string = parse_QUERY_STRING; 148 | _parser->http_version = parse_HTTP_VERSION; 149 | _parser->header_done = header_done; 150 | 151 | http_parser_init(_parser); 152 | } 153 | return self; 154 | } 155 | 156 | - (void)reset 157 | { 158 | http_parser_init(_parser); 159 | } 160 | 161 | - (NSNumber *)parseData:(NSData *)dataBuf 162 | forEnvironment:(NSMutableDictionary *)env 163 | startingAt:(NSNumber *)startingPos 164 | { 165 | const char *data = [dataBuf bytes]; 166 | size_t length = [dataBuf length] - 1; // ignore \0 sentinel 167 | size_t offset = [startingPos unsignedLongValue]; 168 | _parser->data = env; 169 | 170 | http_parser_execute(_parser, data, length, offset); 171 | if (http_parser_has_error(_parser)) { 172 | [NSException raise:@"CTParserError" 173 | format:@"Invalid HTTP format, parsing failed."]; 174 | } 175 | 176 | NSNumber *headerLength = [NSNumber numberWithUnsignedLong:_parser->nread]; 177 | VALIDATE_MAX_LENGTH([headerLength unsignedLongValue], HEADER); 178 | return headerLength; 179 | } 180 | 181 | - (NSNumber *)parseData:(NSData *)dataBuf forEnvironment:(NSDictionary *)env 182 | { 183 | return [self parseData:dataBuf forEnvironment:env startingAt:0]; 184 | } 185 | 186 | - (BOOL)errorCond 187 | { 188 | return http_parser_has_error(_parser); 189 | } 190 | 191 | - (BOOL)finished 192 | { 193 | return http_parser_is_finished(_parser); 194 | } 195 | 196 | - (NSNumber *)nread 197 | { 198 | return [NSNumber numberWithInt:_parser->nread]; 199 | } 200 | 201 | - (void)finalize 202 | { 203 | if (_parser != NULL) { 204 | free(_parser); 205 | _parser = NULL; 206 | } 207 | [super finalize]; 208 | } 209 | 210 | @end 211 | 212 | void 213 | Init_CTParser(void) 214 | { 215 | // Do nothing. This function is required by the MacRuby runtime when this 216 | // file is compiled as a C extension bundle. 217 | } 218 | -------------------------------------------------------------------------------- /ext/CTParser/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | $CFLAGS << ' -fobjc-gc -g ' 3 | create_makefile("CTParser") 4 | -------------------------------------------------------------------------------- /ext/CTParser/http11_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2005 Zed A. Shaw 3 | * You can redistribute it and/or modify it under the same terms as Ruby. 4 | */ 5 | 6 | #ifndef http11_parser_h 7 | #define http11_parser_h 8 | 9 | #include 10 | 11 | #if defined(_WIN32) 12 | #include 13 | #endif 14 | 15 | typedef void (*element_cb)(void *data, const char *at, size_t length); 16 | typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen); 17 | 18 | typedef struct http_parser { 19 | int cs; 20 | size_t body_start; 21 | int content_len; 22 | size_t nread; 23 | size_t mark; 24 | size_t field_start; 25 | size_t field_len; 26 | size_t query_start; 27 | 28 | void *data; 29 | 30 | field_cb http_field; 31 | element_cb request_method; 32 | element_cb request_uri; 33 | element_cb fragment; 34 | element_cb request_path; 35 | element_cb query_string; 36 | element_cb http_version; 37 | element_cb header_done; 38 | 39 | } http_parser; 40 | 41 | int http_parser_init(http_parser *parser); 42 | int http_parser_finish(http_parser *parser); 43 | size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off); 44 | int http_parser_has_error(http_parser *parser); 45 | int http_parser_is_finished(http_parser *parser); 46 | 47 | #define http_parser_nread(parser) (parser)->nread 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /lib/control_tower.rb: -------------------------------------------------------------------------------- 1 | # This file is covered by the Ruby license. See COPYING for more details. 2 | # Copyright (C) 2009-2010, Apple Inc. All rights reserved. 3 | 4 | require 'socket' 5 | require 'tempfile' 6 | $: << File.join(File.dirname(__FILE__), 'control_tower', 'vendor') 7 | require 'rack' 8 | require File.join(File.dirname(__FILE__), 'control_tower', 'rack_socket') 9 | require File.join(File.dirname(__FILE__), 'control_tower', 'server') 10 | -------------------------------------------------------------------------------- /lib/control_tower/rack_socket.rb: -------------------------------------------------------------------------------- 1 | # This file is covered by the Ruby license. See COPYING for more details. 2 | # Copyright (C) 2009-2011, Apple Inc. All rights reserved. 3 | 4 | framework 'Foundation' 5 | require 'CTParser' 6 | require 'stringio' 7 | 8 | CTParser # Making sure the Objective-C class is pre-loaded 9 | 10 | module ControlTower 11 | class RackSocket 12 | VERSION = [1,0].freeze 13 | QUIESCING_MSG = 'Resource limit reached. Redirecting until server quits (will auto-restart).' 14 | 15 | def log(msg, prepend_newline=false) 16 | time = Time.now 17 | tnum = Thread.current.inspect 18 | @log_queue.async(@log_group) do 19 | $stderr.puts "CTLOG::---------" if prepend_newline 20 | $stderr.puts "CTLOG::#{Process.pid}#{tnum} (#{time.strftime("%Y-%m-%d %H:%M:%S")}) #{msg}" 21 | end 22 | end 23 | 24 | def rsize 25 | `ps -o rss= -p #{Process.pid}`.to_i / 1024.0 26 | end 27 | 28 | def initialize(host, port, server, concurrent) 29 | @log_queue = Dispatch::Queue.new('log_queue') 30 | @log_group = Dispatch::Group.new 31 | 32 | @under_launchd = (port == 0) 33 | @mem_high_water_mark = ENV['CT_MEM_BOUNCE_MB'].to_i || -1 if @under_launchd # in megabytes; negative instructs to not bounce 34 | log "STARTING with pid=#{Process.pid}" + (@mem_high_water_mark ? " and memory bounce point=#{@mem_high_water_mark} MB" : "") 35 | 36 | if @under_launchd 37 | # Hash used for to determine if server is idle. Old entries are cleared by a background timer thread (see below). 38 | @auth_sessions = {} 39 | 40 | # remove old sessions (not used for 60 seconds) from @auth_sessions, because there is no way for ControlTower to determine that a session is done 41 | clear_old_auth_sessions_interval_seconds = 60 42 | Dispatch::Source.timer(clear_old_auth_sessions_interval_seconds, clear_old_auth_sessions_interval_seconds, 5, Dispatch::Queue.concurrent) do 43 | @now = Time.now 44 | log "Checking for old auth sessions to remove" 45 | @auth_sessions.delete_if { |session_id, last_used| 46 | if @now-last_used > clear_old_auth_sessions_interval_seconds 47 | log "Removing old auth session: #{session_id}" 48 | true 49 | else 50 | false 51 | end 52 | } 53 | # if idle, check high water mark for memory usage 54 | if @auth_sessions.empty? 55 | mem_used = rsize 56 | if @mem_high_water_mark && mem_used >= @mem_high_water_mark 57 | @status = :closed 58 | log "MEMORY THRESHOLD EXCEEDED: Flaggging to not accept new connections; waiting for existing connections (if any) to finish" 59 | @request_group.wait 60 | log "MEMORY THRESHOLD EXCEEDED: All existing connections done." 61 | sleep 5 # give time for more authn'd requests to come in (might happen due to race condition between accepting a connection and adding its session_id to @auth_sessions) 62 | if @auth_sessions.empty? # double-check to see if the race condition was exploited 63 | log "MEMORY THRESHOLD EXCEEDED: Server is confirmed idle; exiting pid=#{Process.pid}." 64 | exit # We're idle, so just quit now 65 | else 66 | log "Opening back up... a request snuck in." 67 | @status = :open # @auth_sessions isn't empty, which means there are current active sessions. Let them finish (and as a side-effect possibly accepting new connections). 68 | sleep 1 69 | end 70 | else 71 | log "empty session list, but not at memory threshold yet (used=#{mem_used}, threshold=#{@mem_high_water_mark})" 72 | end 73 | end 74 | end 75 | 76 | log "setup authnd session clearing timer" 77 | end 78 | 79 | @app = server.app 80 | if @under_launchd 81 | @socket = Socket.for_fd($stdin.fileno) # launchd sockets 82 | else 83 | @socket = TCPServer.new(host, port) 84 | @socket.listen(50) 85 | end 86 | @status = :closed # Start closed and give the server time to start <------ IS THIS IMPORTANT? Try it w/ the suicide version. 87 | 88 | log "socket setup" 89 | 90 | if concurrent 91 | @multithread = true 92 | @request_queue = Dispatch::Queue.concurrent 93 | puts "Control Tower is operating in concurrent mode." 94 | else 95 | @multithread = false 96 | @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue') 97 | puts "Control Tower is operating in serial mode." 98 | end 99 | @request_group = Dispatch::Group.new 100 | 101 | log "initialization complete." 102 | end 103 | 104 | def open 105 | log "opening..." 106 | 107 | @status = :open 108 | while (@status == :open) 109 | 110 | log "Control Tower: waiting for connection..." 111 | connection, remote_addrinfo_str = @socket.accept 112 | 113 | # -------------- PROCESS REQUEST ASYNCHRONOUSLY ---------------- 114 | 115 | @request_queue.async(@request_group) do 116 | remote_port, remote_ip = Socket.unpack_sockaddr_in(remote_addrinfo_str) if remote_addrinfo_str 117 | log "** new request received at #{Time.new} from #{remote_ip}:#{remote_port}", true 118 | 119 | env = { 'rack.errors' => $stderr, 120 | 'rack.multiprocess' => false, 121 | 'rack.multithread' => @multithread, 122 | 'rack.run_once' => false, 123 | 'rack.version' => VERSION } 124 | resp = nil 125 | x_sendfile_header = 'X-Sendfile' 126 | x_sendfile = nil 127 | log "** done setting rack env" 128 | begin 129 | log "** about to parse request" 130 | request_data = parse!(connection, env) 131 | log "** done parsing request" #: request_data=#{request_data.inspect}" 132 | # log "** env[]=#{env}" 133 | if request_data 134 | request_data['REMOTE_ADDR'] = remote_ip 135 | log "** about to app.call()" 136 | status, headers, body = @app.call(request_data) 137 | log "** app.call() is done"#; handling response, body=#{body.inspect}" 138 | 139 | # If there's an X-Sendfile header, we'll use sendfile(2) 140 | if headers.has_key?(x_sendfile_header) 141 | x_sendfile = headers[x_sendfile_header] 142 | x_sendfile = ::File.open(x_sendfile, 'r') unless x_sendfile.kind_of? IO 143 | x_sendfile_size = x_sendfile.stat.size 144 | headers.delete(x_sendfile_header) 145 | headers['Content-Length'] = x_sendfile_size 146 | end 147 | 148 | @auth_sessions[env['rack.session'].session_id] = Time.now if @under_launchd 149 | log "Added/updated session_id (#{env['rack.session'].session_id}) to @auth_sessions" if @under_launchd 150 | 151 | # Unless somebody's already set it for us (or we don't need it), set the Content-Length 152 | unless (status == -1 || 153 | (status >= 100 and status <= 199) || 154 | status == 204 || 155 | status == 304 || 156 | headers.has_key?('Content-Length')) 157 | headers['Content-Length'] = if body.respond_to?(:each) 158 | size = 0 159 | body.each { |x| size += x.bytesize } 160 | size 161 | else 162 | body.bytesize 163 | end 164 | end 165 | 166 | # TODO -- We don't handle keep-alive connections yet 167 | headers['Connection'] = 'close' 168 | 169 | resp = "HTTP/1.1 #{status}\r\n" 170 | headers.each do |header, value| 171 | resp << "#{header}: #{value}\r\n" 172 | end 173 | resp << "\r\n" 174 | 175 | # Start writing the response 176 | connection.write resp 177 | 178 | # Write the body 179 | if x_sendfile 180 | connection.sendfile(x_sendfile, 0, x_sendfile_size) 181 | elsif body.respond_to?(:each) 182 | body.each do |chunk| 183 | connection.write chunk 184 | end 185 | else 186 | connection.write body 187 | end 188 | 189 | else 190 | log "Error: No request data received!" 191 | end 192 | rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL 193 | log "Error: Connection terminated!" 194 | rescue Object => e 195 | if resp.nil? && !connection.closed? 196 | connection.write "HTTP/1.1 500\r\n\r\n" 197 | end 198 | log "Error: Problem transmitting data -- #{e.inspect}" 199 | $stderr.puts e.backtrace.join("\n") 200 | ensure 201 | # We should clean up after our tempfile, if we used one. 202 | input = env['rack.input'] 203 | input.unlink if input.class == Tempfile 204 | connection.close rescue nil 205 | end 206 | end 207 | end # while :open 208 | end 209 | 210 | def close 211 | puts "Received shutdown signal. Waiting for current requests to complete..." 212 | @status = :close 213 | 214 | # 60 seconds to empty the request queue 215 | Dispatch::Source.timer(60, 0, 1, Dispatch::Queue.concurrent) do 216 | puts "Timed out waiting for connections to close. Stopping server with pid=#{Process.pid}." 217 | exit 218 | end 219 | 220 | @request_group.wait 221 | 222 | puts "All requests completed. Stopping server with pid=#{Process.pid}." 223 | exit 224 | end 225 | 226 | private 227 | 228 | def parse!(connection, env) 229 | parser = Thread.current[:http_parser] ||= CTParser.new 230 | parser.reset 231 | data = NSMutableData.alloc.init 232 | data.increaseLengthBy(1) # add sentinel 233 | parsing_headers = true # Parse headers first 234 | nread = 0 235 | content_length = 0 236 | content_uploaded = 0 237 | connection_handle = NSFileHandle.alloc.initWithFileDescriptor(connection.fileno) 238 | 239 | while (parsing_headers || content_uploaded < content_length) do 240 | # Read the availableData on the socket and give up if there's nothing 241 | incoming_bytes = connection_handle.availableData 242 | return nil if incoming_bytes.length == 0 243 | 244 | # Until the headers are done being parsed, we'll parse them 245 | if parsing_headers 246 | data.setLength(data.length - 1) # Remove sentinel 247 | data.appendData(incoming_bytes) 248 | data.increaseLengthBy(1) # Add sentinel 249 | nread = parser.parseData(data, forEnvironment: env, startingAt: nread) 250 | if parser.finished == 1 251 | parsing_headers = false # We're done, now on to receiving the body 252 | content_length = env['CONTENT_LENGTH'].to_i 253 | content_uploaded = env['rack.input'].length 254 | end 255 | else # Done parsing headers, now just collect request body: 256 | content_uploaded += incoming_bytes.length 257 | env['rack.input'].appendData(incoming_bytes) 258 | end 259 | end 260 | 261 | if content_length > 1024 * 1024 262 | body_file = Tempfile.new('control-tower-request-body-') 263 | NSFileHandle.alloc.initWithFileDescriptor(body_file.fileno).writeData(env['rack.input']) 264 | body_file.rewind 265 | env['rack.input'] = body_file 266 | else 267 | env['rack.input'] = StringIO.new(env['rack.input'], IO::RDONLY) 268 | end 269 | # Returning what we've got... 270 | return env 271 | end 272 | end 273 | end 274 | -------------------------------------------------------------------------------- /lib/control_tower/server.rb: -------------------------------------------------------------------------------- 1 | # This file is covered by the Ruby license. See COPYING for more details. 2 | # Copyright (C) 2009-2010, Apple Inc. All rights reserved. 3 | 4 | module ControlTower 5 | class Server 6 | attr_reader :app 7 | 8 | def initialize(app, options) 9 | @app = app 10 | parse_options(options) 11 | @socket = RackSocket.new(@host, @port, self, @concurrent) 12 | end 13 | 14 | def start 15 | trap 'INT' do 16 | @socket.close 17 | exit 18 | end 19 | 20 | # Ok, let the server do it's thing 21 | @socket.open 22 | end 23 | 24 | private 25 | 26 | def parse_options(opt) 27 | @port = opt[:port].to_i 28 | @host = opt[:host] || `hostname`.chomp 29 | @concurrent = opt[:concurrent] 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack.rb: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen 2 | # 3 | # Rack is freely distributable under the terms of an MIT-style license. 4 | # See COPYING or http://www.opensource.org/licenses/mit-license.php. 5 | 6 | # The Rack main module, serving as a namespace for all core Rack 7 | # modules and classes. 8 | # 9 | # All modules meant for use in your application are autoloaded here, 10 | # so it should be enough just to require rack.rb in your code. 11 | 12 | module Rack 13 | # The Rack protocol version number implemented. 14 | VERSION = [1,1] 15 | 16 | # Return the Rack protocol version as a dotted string. 17 | def self.version 18 | VERSION.join(".") 19 | end 20 | 21 | # Return the Rack release as a dotted string. 22 | def self.release 23 | "1.2" 24 | end 25 | 26 | autoload :Builder, "rack/builder" 27 | autoload :Cascade, "rack/cascade" 28 | autoload :Chunked, "rack/chunked" 29 | autoload :CommonLogger, "rack/commonlogger" 30 | autoload :ConditionalGet, "rack/conditionalget" 31 | autoload :Config, "rack/config" 32 | autoload :ContentLength, "rack/content_length" 33 | autoload :ContentType, "rack/content_type" 34 | autoload :ETag, "rack/etag" 35 | autoload :File, "rack/file" 36 | autoload :Deflater, "rack/deflater" 37 | autoload :Directory, "rack/directory" 38 | autoload :ForwardRequest, "rack/recursive" 39 | autoload :Handler, "rack/handler" 40 | autoload :Head, "rack/head" 41 | autoload :Lint, "rack/lint" 42 | autoload :Lock, "rack/lock" 43 | autoload :Logger, "rack/logger" 44 | autoload :MethodOverride, "rack/methodoverride" 45 | autoload :Mime, "rack/mime" 46 | autoload :NullLogger, "rack/nulllogger" 47 | autoload :Recursive, "rack/recursive" 48 | autoload :Reloader, "rack/reloader" 49 | autoload :Runtime, "rack/runtime" 50 | autoload :Sendfile, "rack/sendfile" 51 | autoload :Server, "rack/server" 52 | autoload :ShowExceptions, "rack/showexceptions" 53 | autoload :ShowStatus, "rack/showstatus" 54 | autoload :Static, "rack/static" 55 | autoload :URLMap, "rack/urlmap" 56 | autoload :Utils, "rack/utils" 57 | 58 | autoload :MockRequest, "rack/mock" 59 | autoload :MockResponse, "rack/mock" 60 | 61 | autoload :Request, "rack/request" 62 | autoload :Response, "rack/response" 63 | 64 | module Auth 65 | autoload :Basic, "rack/auth/basic" 66 | autoload :AbstractRequest, "rack/auth/abstract/request" 67 | autoload :AbstractHandler, "rack/auth/abstract/handler" 68 | module Digest 69 | autoload :MD5, "rack/auth/digest/md5" 70 | autoload :Nonce, "rack/auth/digest/nonce" 71 | autoload :Params, "rack/auth/digest/params" 72 | autoload :Request, "rack/auth/digest/request" 73 | end 74 | end 75 | 76 | module Session 77 | autoload :Cookie, "rack/session/cookie" 78 | autoload :Pool, "rack/session/pool" 79 | autoload :Memcache, "rack/session/memcache" 80 | end 81 | end -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/abstract/handler.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | module Auth 3 | # Rack::Auth::AbstractHandler implements common authentication functionality. 4 | # 5 | # +realm+ should be set for all handlers. 6 | 7 | class AbstractHandler 8 | 9 | attr_accessor :realm 10 | 11 | def initialize(app, realm=nil, &authenticator) 12 | @app, @realm, @authenticator = app, realm, authenticator 13 | end 14 | 15 | 16 | private 17 | 18 | def unauthorized(www_authenticate = challenge) 19 | return [ 401, 20 | { 'Content-Type' => 'text/plain', 21 | 'Content-Length' => '0', 22 | 'WWW-Authenticate' => www_authenticate.to_s }, 23 | [] 24 | ] 25 | end 26 | 27 | def bad_request 28 | return [ 400, 29 | { 'Content-Type' => 'text/plain', 30 | 'Content-Length' => '0' }, 31 | [] 32 | ] 33 | end 34 | 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/abstract/request.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | module Auth 3 | class AbstractRequest 4 | 5 | def initialize(env) 6 | @env = env 7 | end 8 | 9 | def provided? 10 | !authorization_key.nil? 11 | end 12 | 13 | def parts 14 | @parts ||= @env[authorization_key].split(' ', 2) 15 | end 16 | 17 | def scheme 18 | @scheme ||= parts.first.downcase.to_sym 19 | end 20 | 21 | def params 22 | @params ||= parts.last 23 | end 24 | 25 | 26 | private 27 | 28 | AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] 29 | 30 | def authorization_key 31 | @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } 32 | end 33 | 34 | end 35 | 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/basic.rb: -------------------------------------------------------------------------------- 1 | require 'rack/auth/abstract/handler' 2 | require 'rack/auth/abstract/request' 3 | 4 | module Rack 5 | module Auth 6 | # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. 7 | # 8 | # Initialize with the Rack application that you want protecting, 9 | # and a block that checks if a username and password pair are valid. 10 | # 11 | # See also: example/protectedlobster.rb 12 | 13 | class Basic < AbstractHandler 14 | 15 | def call(env) 16 | auth = Basic::Request.new(env) 17 | 18 | return unauthorized unless auth.provided? 19 | 20 | return bad_request unless auth.basic? 21 | 22 | if valid?(auth) 23 | env['REMOTE_USER'] = auth.username 24 | 25 | return @app.call(env) 26 | end 27 | 28 | unauthorized 29 | end 30 | 31 | 32 | private 33 | 34 | def challenge 35 | 'Basic realm="%s"' % realm 36 | end 37 | 38 | def valid?(auth) 39 | @authenticator.call(*auth.credentials) 40 | end 41 | 42 | class Request < Auth::AbstractRequest 43 | def basic? 44 | :basic == scheme 45 | end 46 | 47 | def credentials 48 | @credentials ||= params.unpack("m*").first.split(/:/, 2) 49 | end 50 | 51 | def username 52 | credentials.first 53 | end 54 | end 55 | 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/digest/md5.rb: -------------------------------------------------------------------------------- 1 | require 'rack/auth/abstract/handler' 2 | require 'rack/auth/digest/request' 3 | require 'rack/auth/digest/params' 4 | require 'rack/auth/digest/nonce' 5 | require 'digest/md5' 6 | 7 | module Rack 8 | module Auth 9 | module Digest 10 | # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of 11 | # HTTP Digest Authentication, as per RFC 2617. 12 | # 13 | # Initialize with the [Rack] application that you want protecting, 14 | # and a block that looks up a plaintext password for a given username. 15 | # 16 | # +opaque+ needs to be set to a constant base64/hexadecimal string. 17 | # 18 | class MD5 < AbstractHandler 19 | 20 | attr_accessor :opaque 21 | 22 | attr_writer :passwords_hashed 23 | 24 | def initialize(*args) 25 | super 26 | @passwords_hashed = nil 27 | end 28 | 29 | def passwords_hashed? 30 | !!@passwords_hashed 31 | end 32 | 33 | def call(env) 34 | auth = Request.new(env) 35 | 36 | unless auth.provided? 37 | return unauthorized 38 | end 39 | 40 | if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) 41 | return bad_request 42 | end 43 | 44 | if valid?(auth) 45 | if auth.nonce.stale? 46 | return unauthorized(challenge(:stale => true)) 47 | else 48 | env['REMOTE_USER'] = auth.username 49 | 50 | return @app.call(env) 51 | end 52 | end 53 | 54 | unauthorized 55 | end 56 | 57 | 58 | private 59 | 60 | QOP = 'auth'.freeze 61 | 62 | def params(hash = {}) 63 | Params.new do |params| 64 | params['realm'] = realm 65 | params['nonce'] = Nonce.new.to_s 66 | params['opaque'] = H(opaque) 67 | params['qop'] = QOP 68 | 69 | hash.each { |k, v| params[k] = v } 70 | end 71 | end 72 | 73 | def challenge(hash = {}) 74 | "Digest #{params(hash)}" 75 | end 76 | 77 | def valid?(auth) 78 | valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) 79 | end 80 | 81 | def valid_qop?(auth) 82 | QOP == auth.qop 83 | end 84 | 85 | def valid_opaque?(auth) 86 | H(opaque) == auth.opaque 87 | end 88 | 89 | def valid_nonce?(auth) 90 | auth.nonce.valid? 91 | end 92 | 93 | def valid_digest?(auth) 94 | digest(auth, @authenticator.call(auth.username)) == auth.response 95 | end 96 | 97 | def md5(data) 98 | ::Digest::MD5.hexdigest(data) 99 | end 100 | 101 | alias :H :md5 102 | 103 | def KD(secret, data) 104 | H([secret, data] * ':') 105 | end 106 | 107 | def A1(auth, password) 108 | [ auth.username, auth.realm, password ] * ':' 109 | end 110 | 111 | def A2(auth) 112 | [ auth.method, auth.uri ] * ':' 113 | end 114 | 115 | def digest(auth, password) 116 | password_hash = passwords_hashed? ? password : H(A1(auth, password)) 117 | 118 | KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':') 119 | end 120 | 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/digest/nonce.rb: -------------------------------------------------------------------------------- 1 | require 'digest/md5' 2 | 3 | module Rack 4 | module Auth 5 | module Digest 6 | # Rack::Auth::Digest::Nonce is the default nonce generator for the 7 | # Rack::Auth::Digest::MD5 authentication handler. 8 | # 9 | # +private_key+ needs to set to a constant string. 10 | # 11 | # +time_limit+ can be optionally set to an integer (number of seconds), 12 | # to limit the validity of the generated nonces. 13 | 14 | class Nonce 15 | 16 | class << self 17 | attr_accessor :private_key, :time_limit 18 | end 19 | 20 | def self.parse(string) 21 | new(*string.unpack("m*").first.split(' ', 2)) 22 | end 23 | 24 | def initialize(timestamp = Time.now, given_digest = nil) 25 | @timestamp, @given_digest = timestamp.to_i, given_digest 26 | end 27 | 28 | def to_s 29 | [([ @timestamp, digest ] * ' ')].pack("m*").strip 30 | end 31 | 32 | def digest 33 | ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':') 34 | end 35 | 36 | def valid? 37 | digest == @given_digest 38 | end 39 | 40 | def stale? 41 | !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit 42 | end 43 | 44 | def fresh? 45 | !stale? 46 | end 47 | 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/digest/params.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | module Auth 3 | module Digest 4 | class Params < Hash 5 | 6 | def self.parse(str) 7 | split_header_value(str).inject(new) do |header, param| 8 | k, v = param.split('=', 2) 9 | header[k] = dequote(v) 10 | header 11 | end 12 | end 13 | 14 | def self.dequote(str) # From WEBrick::HTTPUtils 15 | ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup 16 | ret.gsub!(/\\(.)/, "\\1") 17 | ret 18 | end 19 | 20 | def self.split_header_value(str) 21 | str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] } 22 | end 23 | 24 | def initialize 25 | super 26 | 27 | yield self if block_given? 28 | end 29 | 30 | def [](k) 31 | super k.to_s 32 | end 33 | 34 | def []=(k, v) 35 | super k.to_s, v.to_s 36 | end 37 | 38 | UNQUOTED = ['nc', 'stale'] 39 | 40 | def to_s 41 | inject([]) do |parts, (k, v)| 42 | parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v)) 43 | parts 44 | end.join(', ') 45 | end 46 | 47 | def quote(str) # From WEBrick::HTTPUtils 48 | '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' 49 | end 50 | 51 | end 52 | end 53 | end 54 | end 55 | 56 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/auth/digest/request.rb: -------------------------------------------------------------------------------- 1 | require 'rack/auth/abstract/request' 2 | require 'rack/auth/digest/params' 3 | require 'rack/auth/digest/nonce' 4 | 5 | module Rack 6 | module Auth 7 | module Digest 8 | class Request < Auth::AbstractRequest 9 | 10 | def method 11 | @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD'] 12 | end 13 | 14 | def digest? 15 | :digest == scheme 16 | end 17 | 18 | def correct_uri? 19 | (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri 20 | end 21 | 22 | def nonce 23 | @nonce ||= Nonce.parse(params['nonce']) 24 | end 25 | 26 | def params 27 | @params ||= Params.parse(parts.last) 28 | end 29 | 30 | def method_missing(sym) 31 | if params.has_key? key = sym.to_s 32 | return params[key] 33 | end 34 | super 35 | end 36 | 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/builder.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Rack::Builder implements a small DSL to iteratively construct Rack 3 | # applications. 4 | # 5 | # Example: 6 | # 7 | # app = Rack::Builder.new { 8 | # use Rack::CommonLogger 9 | # use Rack::ShowExceptions 10 | # map "/lobster" do 11 | # use Rack::Lint 12 | # run Rack::Lobster.new 13 | # end 14 | # } 15 | # 16 | # Or 17 | # 18 | # app = Rack::Builder.app do 19 | # use Rack::CommonLogger 20 | # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] } 21 | # end 22 | # 23 | # +use+ adds a middleware to the stack, +run+ dispatches to an application. 24 | # You can use +map+ to construct a Rack::URLMap in a convenient way. 25 | 26 | class Builder 27 | def self.parse_file(config, opts = Server::Options.new) 28 | options = {} 29 | if config =~ /\.ru$/ 30 | cfgfile = ::File.read(config) 31 | if cfgfile[/^#\\(.*)/] && opts 32 | options = opts.parse! $1.split(/\s+/) 33 | end 34 | cfgfile.sub!(/^__END__\n.*/, '') 35 | app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", 36 | TOPLEVEL_BINDING, config 37 | else 38 | require config 39 | app = Object.const_get(::File.basename(config, '.rb').capitalize) 40 | end 41 | return app, options 42 | end 43 | 44 | def initialize(&block) 45 | @ins = [] 46 | instance_eval(&block) if block_given? 47 | end 48 | 49 | def self.app(&block) 50 | self.new(&block).to_app 51 | end 52 | 53 | def use(middleware, *args, &block) 54 | @ins << lambda { |app| middleware.new(app, *args, &block) } 55 | end 56 | 57 | def run(app) 58 | @ins << app #lambda { |nothing| app } 59 | end 60 | 61 | def map(path, &block) 62 | if @ins.last.kind_of? Hash 63 | @ins.last[path] = self.class.new(&block).to_app 64 | else 65 | @ins << {} 66 | map(path, &block) 67 | end 68 | end 69 | 70 | def to_app 71 | @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last 72 | inner_app = @ins.last 73 | @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) } 74 | end 75 | 76 | def call(env) 77 | to_app.call(env) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/cascade.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Rack::Cascade tries an request on several apps, and returns the 3 | # first response that is not 404 (or in a list of configurable 4 | # status codes). 5 | 6 | class Cascade 7 | NotFound = [404, {}, []] 8 | 9 | attr_reader :apps 10 | 11 | def initialize(apps, catch=404) 12 | @apps = []; @has_app = {} 13 | apps.each { |app| add app } 14 | 15 | @catch = {} 16 | [*catch].each { |status| @catch[status] = true } 17 | end 18 | 19 | def call(env) 20 | result = NotFound 21 | 22 | @apps.each do |app| 23 | result = app.call(env) 24 | break unless @catch.include?(result[0].to_i) 25 | end 26 | 27 | result 28 | end 29 | 30 | def add app 31 | @has_app[app] = true 32 | @apps << app 33 | end 34 | 35 | def include? app 36 | @has_app.include? app 37 | end 38 | 39 | alias_method :<<, :add 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/chunked.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Rack 4 | 5 | # Middleware that applies chunked transfer encoding to response bodies 6 | # when the response does not include a Content-Length header. 7 | class Chunked 8 | include Rack::Utils 9 | 10 | def initialize(app) 11 | @app = app 12 | end 13 | 14 | def call(env) 15 | status, headers, body = @app.call(env) 16 | headers = HeaderHash.new(headers) 17 | 18 | if env['HTTP_VERSION'] == 'HTTP/1.0' || 19 | STATUS_WITH_NO_ENTITY_BODY.include?(status) || 20 | headers['Content-Length'] || 21 | headers['Transfer-Encoding'] 22 | [status, headers, body] 23 | else 24 | dup.chunk(status, headers, body) 25 | end 26 | end 27 | 28 | def chunk(status, headers, body) 29 | @body = body 30 | headers.delete('Content-Length') 31 | headers['Transfer-Encoding'] = 'chunked' 32 | [status, headers, self] 33 | end 34 | 35 | def each 36 | term = "\r\n" 37 | @body.each do |chunk| 38 | size = bytesize(chunk) 39 | next if size == 0 40 | yield [size.to_s(16), term, chunk, term].join 41 | end 42 | yield ["0", term, "", term].join 43 | end 44 | 45 | def close 46 | @body.close if @body.respond_to?(:close) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/commonlogger.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Rack::CommonLogger forwards every request to an +app+ given, and 3 | # logs a line in the Apache common log format to the +logger+, or 4 | # rack.errors by default. 5 | class CommonLogger 6 | # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common 7 | # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 - 8 | # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % 9 | FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} 10 | 11 | def initialize(app, logger=nil) 12 | @app = app 13 | @logger = logger 14 | end 15 | 16 | def call(env) 17 | began_at = Time.now 18 | status, header, body = @app.call(env) 19 | header = Utils::HeaderHash.new(header) 20 | log(env, status, header, began_at) 21 | [status, header, body] 22 | end 23 | 24 | private 25 | 26 | def log(env, status, header, began_at) 27 | now = Time.now 28 | length = extract_content_length(header) 29 | 30 | logger = @logger || env['rack.errors'] 31 | logger.write FORMAT % [ 32 | env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", 33 | env["REMOTE_USER"] || "-", 34 | now.strftime("%d/%b/%Y %H:%M:%S"), 35 | env["REQUEST_METHOD"], 36 | env["PATH_INFO"], 37 | env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"], 38 | env["HTTP_VERSION"], 39 | status.to_s[0..3], 40 | length, 41 | now - began_at ] 42 | end 43 | 44 | def extract_content_length(headers) 45 | value = headers['Content-Length'] or return '-' 46 | value.to_s == '0' ? '-' : value 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/conditionalget.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Rack 4 | 5 | # Middleware that enables conditional GET using If-None-Match and 6 | # If-Modified-Since. The application should set either or both of the 7 | # Last-Modified or Etag response headers according to RFC 2616. When 8 | # either of the conditions is met, the response body is set to be zero 9 | # length and the response status is set to 304 Not Modified. 10 | # 11 | # Applications that defer response body generation until the body's each 12 | # message is received will avoid response body generation completely when 13 | # a conditional GET matches. 14 | # 15 | # Adapted from Michael Klishin's Merb implementation: 16 | # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb 17 | class ConditionalGet 18 | def initialize(app) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD']) 24 | 25 | status, headers, body = @app.call(env) 26 | headers = Utils::HeaderHash.new(headers) 27 | if etag_matches?(env, headers) || modified_since?(env, headers) 28 | status = 304 29 | headers.delete('Content-Type') 30 | headers.delete('Content-Length') 31 | body = [] 32 | end 33 | [status, headers, body] 34 | end 35 | 36 | private 37 | def etag_matches?(env, headers) 38 | etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH'] 39 | end 40 | 41 | def modified_since?(env, headers) 42 | last_modified = headers['Last-Modified'] and 43 | last_modified == env['HTTP_IF_MODIFIED_SINCE'] 44 | end 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/config.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Rack::Config modifies the environment using the block given during 3 | # initialization. 4 | class Config 5 | def initialize(app, &block) 6 | @app = app 7 | @block = block 8 | end 9 | 10 | def call(env) 11 | @block.call(env) 12 | @app.call(env) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/content_length.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Rack 4 | # Sets the Content-Length header on responses with fixed-length bodies. 5 | class ContentLength 6 | include Rack::Utils 7 | 8 | def initialize(app) 9 | @app = app 10 | end 11 | 12 | def call(env) 13 | status, headers, body = @app.call(env) 14 | headers = HeaderHash.new(headers) 15 | 16 | if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) && 17 | !headers['Content-Length'] && 18 | !headers['Transfer-Encoding'] && 19 | (body.respond_to?(:to_ary) || body.respond_to?(:to_str)) 20 | 21 | body = [body] if body.respond_to?(:to_str) # rack 0.4 compat 22 | length = body.to_ary.inject(0) { |len, part| len + bytesize(part) } 23 | headers['Content-Length'] = length.to_s 24 | end 25 | 26 | [status, headers, body] 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/content_type.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Rack 4 | 5 | # Sets the Content-Type header on responses which don't have one. 6 | # 7 | # Builder Usage: 8 | # use Rack::ContentType, "text/plain" 9 | # 10 | # When no content type argument is provided, "text/html" is assumed. 11 | class ContentType 12 | def initialize(app, content_type = "text/html") 13 | @app, @content_type = app, content_type 14 | end 15 | 16 | def call(env) 17 | status, headers, body = @app.call(env) 18 | headers = Utils::HeaderHash.new(headers) 19 | headers['Content-Type'] ||= @content_type 20 | [status, headers, body] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/deflater.rb: -------------------------------------------------------------------------------- 1 | require "zlib" 2 | require "stringio" 3 | require "time" # for Time.httpdate 4 | require 'rack/utils' 5 | 6 | module Rack 7 | class Deflater 8 | def initialize(app) 9 | @app = app 10 | end 11 | 12 | def call(env) 13 | status, headers, body = @app.call(env) 14 | headers = Utils::HeaderHash.new(headers) 15 | 16 | # Skip compressing empty entity body responses and responses with 17 | # no-transform set. 18 | if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || 19 | headers['Cache-Control'].to_s =~ /\bno-transform\b/ 20 | return [status, headers, body] 21 | end 22 | 23 | request = Request.new(env) 24 | 25 | encoding = Utils.select_best_encoding(%w(gzip deflate identity), 26 | request.accept_encoding) 27 | 28 | # Set the Vary HTTP header. 29 | vary = headers["Vary"].to_s.split(",").map { |v| v.strip } 30 | unless vary.include?("*") || vary.include?("Accept-Encoding") 31 | headers["Vary"] = vary.push("Accept-Encoding").join(",") 32 | end 33 | 34 | case encoding 35 | when "gzip" 36 | headers['Content-Encoding'] = "gzip" 37 | headers.delete('Content-Length') 38 | mtime = headers.key?("Last-Modified") ? 39 | Time.httpdate(headers["Last-Modified"]) : Time.now 40 | [status, headers, GzipStream.new(body, mtime)] 41 | when "deflate" 42 | headers['Content-Encoding'] = "deflate" 43 | headers.delete('Content-Length') 44 | [status, headers, DeflateStream.new(body)] 45 | when "identity" 46 | [status, headers, body] 47 | when nil 48 | message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." 49 | [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]] 50 | end 51 | end 52 | 53 | class GzipStream 54 | def initialize(body, mtime) 55 | @body = body 56 | @mtime = mtime 57 | end 58 | 59 | def each(&block) 60 | @writer = block 61 | gzip =::Zlib::GzipWriter.new(self) 62 | gzip.mtime = @mtime 63 | @body.each { |part| gzip.write(part) } 64 | @body.close if @body.respond_to?(:close) 65 | gzip.close 66 | @writer = nil 67 | end 68 | 69 | def write(data) 70 | @writer.call(data) 71 | end 72 | end 73 | 74 | class DeflateStream 75 | DEFLATE_ARGS = [ 76 | Zlib::DEFAULT_COMPRESSION, 77 | # drop the zlib header which causes both Safari and IE to choke 78 | -Zlib::MAX_WBITS, 79 | Zlib::DEF_MEM_LEVEL, 80 | Zlib::DEFAULT_STRATEGY 81 | ] 82 | 83 | def initialize(body) 84 | @body = body 85 | end 86 | 87 | def each 88 | deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS) 89 | @body.each { |part| yield deflater.deflate(part) } 90 | @body.close if @body.respond_to?(:close) 91 | yield deflater.finish 92 | nil 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/directory.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'rack/utils' 3 | require 'rack/mime' 4 | 5 | module Rack 6 | # Rack::Directory serves entries below the +root+ given, according to the 7 | # path info of the Rack request. If a directory is found, the file's contents 8 | # will be presented in an html based index. If a file is found, the env will 9 | # be passed to the specified +app+. 10 | # 11 | # If +app+ is not specified, a Rack::File of the same +root+ will be used. 12 | 13 | class Directory 14 | DIR_FILE = "%s%s%s%s" 15 | DIR_PAGE = <<-PAGE 16 | 17 | %s 18 | 19 | 26 | 27 |

%s

28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | %s 37 |
NameSizeTypeLast Modified
38 |
39 | 40 | PAGE 41 | 42 | attr_reader :files 43 | attr_accessor :root, :path 44 | 45 | def initialize(root, app=nil) 46 | @root = F.expand_path(root) 47 | @app = app || Rack::File.new(@root) 48 | end 49 | 50 | def call(env) 51 | dup._call(env) 52 | end 53 | 54 | F = ::File 55 | 56 | def _call(env) 57 | @env = env 58 | @script_name = env['SCRIPT_NAME'] 59 | @path_info = Utils.unescape(env['PATH_INFO']) 60 | 61 | if forbidden = check_forbidden 62 | forbidden 63 | else 64 | @path = F.join(@root, @path_info) 65 | list_path 66 | end 67 | end 68 | 69 | def check_forbidden 70 | return unless @path_info.include? ".." 71 | 72 | body = "Forbidden\n" 73 | size = Rack::Utils.bytesize(body) 74 | return [403, {"Content-Type" => "text/plain", 75 | "Content-Length" => size.to_s, 76 | "X-Cascade" => "pass"}, [body]] 77 | end 78 | 79 | def list_directory 80 | @files = [['../','Parent Directory','','','']] 81 | glob = F.join(@path, '*') 82 | 83 | Dir[glob].sort.each do |node| 84 | stat = stat(node) 85 | next unless stat 86 | basename = F.basename(node) 87 | ext = F.extname(node) 88 | 89 | url = F.join(@script_name, @path_info, basename) 90 | size = stat.size 91 | type = stat.directory? ? 'directory' : Mime.mime_type(ext) 92 | size = stat.directory? ? '-' : filesize_format(size) 93 | mtime = stat.mtime.httpdate 94 | url << '/' if stat.directory? 95 | basename << '/' if stat.directory? 96 | 97 | @files << [ url, basename, size, type, mtime ] 98 | end 99 | 100 | return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ] 101 | end 102 | 103 | def stat(node, max = 10) 104 | F.stat(node) 105 | rescue Errno::ENOENT, Errno::ELOOP 106 | return nil 107 | end 108 | 109 | # TODO: add correct response if not readable, not sure if 404 is the best 110 | # option 111 | def list_path 112 | @stat = F.stat(@path) 113 | 114 | if @stat.readable? 115 | return @app.call(@env) if @stat.file? 116 | return list_directory if @stat.directory? 117 | else 118 | raise Errno::ENOENT, 'No such file or directory' 119 | end 120 | 121 | rescue Errno::ENOENT, Errno::ELOOP 122 | return entity_not_found 123 | end 124 | 125 | def entity_not_found 126 | body = "Entity not found: #{@path_info}\n" 127 | size = Rack::Utils.bytesize(body) 128 | return [404, {"Content-Type" => "text/plain", 129 | "Content-Length" => size.to_s, 130 | "X-Cascade" => "pass"}, [body]] 131 | end 132 | 133 | def each 134 | show_path = @path.sub(/^#{@root}/,'') 135 | files = @files.map{|f| DIR_FILE % f }*"\n" 136 | page = DIR_PAGE % [ show_path, show_path , files ] 137 | page.each_line{|l| yield l } 138 | end 139 | 140 | # Stolen from Ramaze 141 | 142 | FILESIZE_FORMAT = [ 143 | ['%.1fT', 1 << 40], 144 | ['%.1fG', 1 << 30], 145 | ['%.1fM', 1 << 20], 146 | ['%.1fK', 1 << 10], 147 | ] 148 | 149 | def filesize_format(int) 150 | FILESIZE_FORMAT.each do |format, size| 151 | return format % (int.to_f / size) if int >= size 152 | end 153 | 154 | int.to_s + 'B' 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/etag.rb: -------------------------------------------------------------------------------- 1 | require 'digest/md5' 2 | 3 | module Rack 4 | # Automatically sets the ETag header on all String bodies 5 | class ETag 6 | def initialize(app) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | status, headers, body = @app.call(env) 12 | 13 | if !headers.has_key?('ETag') 14 | digest, body = digest_body(body) 15 | headers['ETag'] = %("#{digest}") 16 | end 17 | 18 | [status, headers, body] 19 | end 20 | 21 | private 22 | def digest_body(body) 23 | digest = Digest::MD5.new 24 | parts = [] 25 | body.each do |part| 26 | digest << part 27 | parts << part 28 | end 29 | [digest.hexdigest, parts] 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/file.rb: -------------------------------------------------------------------------------- 1 | require 'time' 2 | require 'rack/utils' 3 | require 'rack/mime' 4 | 5 | module Rack 6 | # Rack::File serves files below the +root+ directory given, according to the 7 | # path info of the Rack request. 8 | # e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file 9 | # as http://localhost:9292/passwd 10 | # 11 | # Handlers can detect if bodies are a Rack::File, and use mechanisms 12 | # like sendfile on the +path+. 13 | 14 | class File 15 | attr_accessor :root 16 | attr_accessor :path 17 | 18 | alias :to_path :path 19 | 20 | def initialize(root) 21 | @root = root 22 | end 23 | 24 | def call(env) 25 | dup._call(env) 26 | end 27 | 28 | F = ::File 29 | 30 | def _call(env) 31 | @path_info = Utils.unescape(env["PATH_INFO"]) 32 | return forbidden if @path_info.include? ".." 33 | 34 | @path = F.join(@root, @path_info) 35 | 36 | begin 37 | if F.file?(@path) && F.readable?(@path) 38 | serving 39 | else 40 | raise Errno::EPERM 41 | end 42 | rescue SystemCallError 43 | not_found 44 | end 45 | end 46 | 47 | def forbidden 48 | body = "Forbidden\n" 49 | [403, {"Content-Type" => "text/plain", 50 | "Content-Length" => body.size.to_s, 51 | "X-Cascade" => "pass"}, 52 | [body]] 53 | end 54 | 55 | # NOTE: 56 | # We check via File::size? whether this file provides size info 57 | # via stat (e.g. /proc files often don't), otherwise we have to 58 | # figure it out by reading the whole file into memory. And while 59 | # we're at it we also use this as body then. 60 | 61 | def serving 62 | if size = F.size?(@path) 63 | body = self 64 | else 65 | body = [F.read(@path)] 66 | size = Utils.bytesize(body.first) 67 | end 68 | 69 | [200, { 70 | "Last-Modified" => F.mtime(@path).httpdate, 71 | "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'), 72 | "Content-Length" => size.to_s 73 | }, body] 74 | end 75 | 76 | def not_found 77 | body = "File not found: #{@path_info}\n" 78 | [404, {"Content-Type" => "text/plain", 79 | "Content-Length" => body.size.to_s, 80 | "X-Cascade" => "pass"}, 81 | [body]] 82 | end 83 | 84 | def each 85 | F.open(@path, "rb") { |file| 86 | while part = file.read(8192) 87 | yield part 88 | end 89 | } 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # *Handlers* connect web servers with Rack. 3 | # 4 | # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI 5 | # and LiteSpeed. 6 | # 7 | # Handlers usually are activated by calling MyHandler.run(myapp). 8 | # A second optional hash can be passed to include server-specific 9 | # configuration. 10 | module Handler 11 | def self.get(server) 12 | return unless server 13 | server = server.to_s 14 | 15 | if klass = @handlers[server] 16 | obj = Object 17 | klass.split("::").each { |x| obj = obj.const_get(x) } 18 | obj 19 | else 20 | try_require('rack/handler', server) 21 | const_get(server) 22 | end 23 | end 24 | 25 | def self.default(options = {}) 26 | # Guess. 27 | if ENV.include?("PHP_FCGI_CHILDREN") 28 | # We already speak FastCGI 29 | options.delete :File 30 | options.delete :Port 31 | 32 | Rack::Handler::FastCGI 33 | elsif ENV.include?("REQUEST_METHOD") 34 | Rack::Handler::CGI 35 | else 36 | begin 37 | Rack::Handler::Mongrel 38 | rescue LoadError => e 39 | Rack::Handler::WEBrick 40 | end 41 | end 42 | end 43 | 44 | # Transforms server-name constants to their canonical form as filenames, 45 | # then tries to require them but silences the LoadError if not found 46 | # 47 | # Naming convention: 48 | # 49 | # Foo # => 'foo' 50 | # FooBar # => 'foo_bar.rb' 51 | # FooBAR # => 'foobar.rb' 52 | # FOObar # => 'foobar.rb' 53 | # FOOBAR # => 'foobar.rb' 54 | # FooBarBaz # => 'foo_bar_baz.rb' 55 | def self.try_require(prefix, const_name) 56 | file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }. 57 | gsub(/[A-Z]+[^A-Z]/, '_\&').downcase 58 | 59 | require(::File.join(prefix, file)) 60 | rescue LoadError 61 | end 62 | 63 | def self.register(server, klass) 64 | @handlers ||= {} 65 | @handlers[server] = klass 66 | end 67 | 68 | autoload :CGI, "rack/handler/cgi" 69 | autoload :FastCGI, "rack/handler/fastcgi" 70 | autoload :Mongrel, "rack/handler/mongrel" 71 | autoload :EventedMongrel, "rack/handler/evented_mongrel" 72 | autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel" 73 | autoload :WEBrick, "rack/handler/webrick" 74 | autoload :LSWS, "rack/handler/lsws" 75 | autoload :SCGI, "rack/handler/scgi" 76 | autoload :Thin, "rack/handler/thin" 77 | 78 | register 'cgi', 'Rack::Handler::CGI' 79 | register 'fastcgi', 'Rack::Handler::FastCGI' 80 | register 'mongrel', 'Rack::Handler::Mongrel' 81 | register 'emongrel', 'Rack::Handler::EventedMongrel' 82 | register 'smongrel', 'Rack::Handler::SwiftipliedMongrel' 83 | register 'webrick', 'Rack::Handler::WEBrick' 84 | register 'lsws', 'Rack::Handler::LSWS' 85 | register 'scgi', 'Rack::Handler::SCGI' 86 | register 'thin', 'Rack::Handler::Thin' 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/cgi.rb: -------------------------------------------------------------------------------- 1 | require 'rack/content_length' 2 | require 'rack/rewindable_input' 3 | 4 | module Rack 5 | module Handler 6 | class CGI 7 | def self.run(app, options=nil) 8 | $stdin.binmode 9 | serve app 10 | end 11 | 12 | def self.serve(app) 13 | app = ContentLength.new(app) 14 | 15 | env = ENV.to_hash 16 | env.delete "HTTP_CONTENT_LENGTH" 17 | 18 | env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" 19 | 20 | env.update({"rack.version" => Rack::VERSION, 21 | "rack.input" => Rack::RewindableInput.new($stdin), 22 | "rack.errors" => $stderr, 23 | 24 | "rack.multithread" => false, 25 | "rack.multiprocess" => true, 26 | "rack.run_once" => true, 27 | 28 | "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" 29 | }) 30 | 31 | env["QUERY_STRING"] ||= "" 32 | env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] 33 | env["REQUEST_PATH"] ||= "/" 34 | 35 | status, headers, body = app.call(env) 36 | begin 37 | send_headers status, headers 38 | send_body body 39 | ensure 40 | body.close if body.respond_to? :close 41 | end 42 | end 43 | 44 | def self.send_headers(status, headers) 45 | $stdout.print "Status: #{status}\r\n" 46 | headers.each { |k, vs| 47 | vs.split("\n").each { |v| 48 | $stdout.print "#{k}: #{v}\r\n" 49 | } 50 | } 51 | $stdout.print "\r\n" 52 | $stdout.flush 53 | end 54 | 55 | def self.send_body(body) 56 | body.each { |part| 57 | $stdout.print part 58 | $stdout.flush 59 | } 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/evented_mongrel.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/evented_mongrel' 2 | 3 | module Rack 4 | module Handler 5 | class EventedMongrel < Handler::Mongrel 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/fastcgi.rb: -------------------------------------------------------------------------------- 1 | require 'fcgi' 2 | require 'socket' 3 | require 'rack/content_length' 4 | require 'rack/rewindable_input' 5 | 6 | if defined? FCGI::Stream 7 | class FCGI::Stream 8 | alias _rack_read_without_buffer read 9 | 10 | def read(n, buffer=nil) 11 | buf = _rack_read_without_buffer n 12 | buffer.replace(buf.to_s) if buffer 13 | buf 14 | end 15 | end 16 | end 17 | 18 | module Rack 19 | module Handler 20 | class FastCGI 21 | def self.run(app, options={}) 22 | file = options[:File] and STDIN.reopen(UNIXServer.new(file)) 23 | port = options[:Port] and STDIN.reopen(TCPServer.new(port)) 24 | FCGI.each { |request| 25 | serve request, app 26 | } 27 | end 28 | 29 | def self.serve(request, app) 30 | app = Rack::ContentLength.new(app) 31 | 32 | env = request.env 33 | env.delete "HTTP_CONTENT_LENGTH" 34 | 35 | env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" 36 | 37 | rack_input = RewindableInput.new(request.in) 38 | 39 | env.update({"rack.version" => Rack::VERSION, 40 | "rack.input" => rack_input, 41 | "rack.errors" => request.err, 42 | 43 | "rack.multithread" => false, 44 | "rack.multiprocess" => true, 45 | "rack.run_once" => false, 46 | 47 | "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" 48 | }) 49 | 50 | env["QUERY_STRING"] ||= "" 51 | env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] 52 | env["REQUEST_PATH"] ||= "/" 53 | env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == "" 54 | env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == "" 55 | 56 | begin 57 | status, headers, body = app.call(env) 58 | begin 59 | send_headers request.out, status, headers 60 | send_body request.out, body 61 | ensure 62 | body.close if body.respond_to? :close 63 | end 64 | ensure 65 | rack_input.close 66 | request.finish 67 | end 68 | end 69 | 70 | def self.send_headers(out, status, headers) 71 | out.print "Status: #{status}\r\n" 72 | headers.each { |k, vs| 73 | vs.split("\n").each { |v| 74 | out.print "#{k}: #{v}\r\n" 75 | } 76 | } 77 | out.print "\r\n" 78 | out.flush 79 | end 80 | 81 | def self.send_body(out, body) 82 | body.each { |part| 83 | out.print part 84 | out.flush 85 | } 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/lsws.rb: -------------------------------------------------------------------------------- 1 | require 'lsapi' 2 | require 'rack/content_length' 3 | require 'rack/rewindable_input' 4 | 5 | module Rack 6 | module Handler 7 | class LSWS 8 | def self.run(app, options=nil) 9 | while LSAPI.accept != nil 10 | serve app 11 | end 12 | end 13 | def self.serve(app) 14 | app = Rack::ContentLength.new(app) 15 | 16 | env = ENV.to_hash 17 | env.delete "HTTP_CONTENT_LENGTH" 18 | env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" 19 | 20 | rack_input = RewindableInput.new($stdin.read.to_s) 21 | 22 | env.update( 23 | "rack.version" => Rack::VERSION, 24 | "rack.input" => rack_input, 25 | "rack.errors" => $stderr, 26 | "rack.multithread" => false, 27 | "rack.multiprocess" => true, 28 | "rack.run_once" => false, 29 | "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" 30 | ) 31 | 32 | env["QUERY_STRING"] ||= "" 33 | env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] 34 | env["REQUEST_PATH"] ||= "/" 35 | status, headers, body = app.call(env) 36 | begin 37 | send_headers status, headers 38 | send_body body 39 | ensure 40 | body.close if body.respond_to? :close 41 | end 42 | ensure 43 | rack_input.close 44 | end 45 | def self.send_headers(status, headers) 46 | print "Status: #{status}\r\n" 47 | headers.each { |k, vs| 48 | vs.split("\n").each { |v| 49 | print "#{k}: #{v}\r\n" 50 | } 51 | } 52 | print "\r\n" 53 | STDOUT.flush 54 | end 55 | def self.send_body(body) 56 | body.each { |part| 57 | print part 58 | STDOUT.flush 59 | } 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/mongrel.rb: -------------------------------------------------------------------------------- 1 | require 'mongrel' 2 | require 'stringio' 3 | require 'rack/content_length' 4 | require 'rack/chunked' 5 | 6 | module Rack 7 | module Handler 8 | class Mongrel < ::Mongrel::HttpHandler 9 | def self.run(app, options={}) 10 | server = ::Mongrel::HttpServer.new( 11 | options[:Host] || '0.0.0.0', 12 | options[:Port] || 8080, 13 | options[:num_processors] || 950, 14 | options[:throttle] || 0, 15 | options[:timeout] || 60) 16 | # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. 17 | # Use is similar to #run, replacing the app argument with a hash of 18 | # { path=>app, ... } or an instance of Rack::URLMap. 19 | if options[:map] 20 | if app.is_a? Hash 21 | app.each do |path, appl| 22 | path = '/'+path unless path[0] == ?/ 23 | server.register(path, Rack::Handler::Mongrel.new(appl)) 24 | end 25 | elsif app.is_a? URLMap 26 | app.instance_variable_get(:@mapping).each do |(host, path, appl)| 27 | next if !host.nil? && !options[:Host].nil? && options[:Host] != host 28 | path = '/'+path unless path[0] == ?/ 29 | server.register(path, Rack::Handler::Mongrel.new(appl)) 30 | end 31 | else 32 | raise ArgumentError, "first argument should be a Hash or URLMap" 33 | end 34 | else 35 | server.register('/', Rack::Handler::Mongrel.new(app)) 36 | end 37 | yield server if block_given? 38 | server.run.join 39 | end 40 | 41 | def initialize(app) 42 | @app = Rack::Chunked.new(Rack::ContentLength.new(app)) 43 | end 44 | 45 | def process(request, response) 46 | env = {}.replace(request.params) 47 | env.delete "HTTP_CONTENT_TYPE" 48 | env.delete "HTTP_CONTENT_LENGTH" 49 | 50 | env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" 51 | 52 | rack_input = request.body || StringIO.new('') 53 | rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) 54 | 55 | env.update({"rack.version" => Rack::VERSION, 56 | "rack.input" => rack_input, 57 | "rack.errors" => $stderr, 58 | 59 | "rack.multithread" => true, 60 | "rack.multiprocess" => false, # ??? 61 | "rack.run_once" => false, 62 | 63 | "rack.url_scheme" => "http", 64 | }) 65 | env["QUERY_STRING"] ||= "" 66 | 67 | status, headers, body = @app.call(env) 68 | 69 | begin 70 | response.status = status.to_i 71 | response.send_status(nil) 72 | 73 | headers.each { |k, vs| 74 | vs.split("\n").each { |v| 75 | response.header[k] = v 76 | } 77 | } 78 | response.send_header 79 | 80 | body.each { |part| 81 | response.write part 82 | response.socket.flush 83 | } 84 | ensure 85 | body.close if body.respond_to? :close 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/scgi.rb: -------------------------------------------------------------------------------- 1 | require 'scgi' 2 | require 'stringio' 3 | require 'rack/content_length' 4 | require 'rack/chunked' 5 | 6 | module Rack 7 | module Handler 8 | class SCGI < ::SCGI::Processor 9 | attr_accessor :app 10 | 11 | def self.run(app, options=nil) 12 | new(options.merge(:app=>app, 13 | :host=>options[:Host], 14 | :port=>options[:Port], 15 | :socket=>options[:Socket])).listen 16 | end 17 | 18 | def initialize(settings = {}) 19 | @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) 20 | super(settings) 21 | end 22 | 23 | def process_request(request, input_body, socket) 24 | env = {}.replace(request) 25 | env.delete "HTTP_CONTENT_TYPE" 26 | env.delete "HTTP_CONTENT_LENGTH" 27 | env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2) 28 | env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] 29 | env["PATH_INFO"] = env["REQUEST_PATH"] 30 | env["QUERY_STRING"] ||= "" 31 | env["SCRIPT_NAME"] = "" 32 | 33 | rack_input = StringIO.new(input_body) 34 | rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) 35 | 36 | env.update({"rack.version" => Rack::VERSION, 37 | "rack.input" => rack_input, 38 | "rack.errors" => $stderr, 39 | "rack.multithread" => true, 40 | "rack.multiprocess" => true, 41 | "rack.run_once" => false, 42 | 43 | "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" 44 | }) 45 | status, headers, body = app.call(env) 46 | begin 47 | socket.write("Status: #{status}\r\n") 48 | headers.each do |k, vs| 49 | vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")} 50 | end 51 | socket.write("\r\n") 52 | body.each {|s| socket.write(s)} 53 | ensure 54 | body.close if body.respond_to? :close 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/swiftiplied_mongrel.rb: -------------------------------------------------------------------------------- 1 | require 'swiftcore/swiftiplied_mongrel' 2 | 3 | module Rack 4 | module Handler 5 | class SwiftipliedMongrel < Handler::Mongrel 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/thin.rb: -------------------------------------------------------------------------------- 1 | require "thin" 2 | require "rack/content_length" 3 | require "rack/chunked" 4 | 5 | module Rack 6 | module Handler 7 | class Thin 8 | def self.run(app, options={}) 9 | app = Rack::Chunked.new(Rack::ContentLength.new(app)) 10 | server = ::Thin::Server.new(options[:Host] || '0.0.0.0', 11 | options[:Port] || 8080, 12 | app) 13 | yield server if block_given? 14 | server.start 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/handler/webrick.rb: -------------------------------------------------------------------------------- 1 | require 'webrick' 2 | require 'stringio' 3 | require 'rack/content_length' 4 | 5 | module Rack 6 | module Handler 7 | class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet 8 | def self.run(app, options={}) 9 | options[:BindAddress] = options.delete(:Host) if options[:Host] 10 | @server = ::WEBrick::HTTPServer.new(options) 11 | @server.mount "/", Rack::Handler::WEBrick, app 12 | yield @server if block_given? 13 | @server.start 14 | end 15 | 16 | def self.shutdown 17 | @server.shutdown 18 | @server = nil 19 | end 20 | 21 | def initialize(server, app) 22 | super server 23 | @app = Rack::ContentLength.new(app) 24 | end 25 | 26 | def service(req, res) 27 | env = req.meta_vars 28 | env.delete_if { |k, v| v.nil? } 29 | 30 | rack_input = StringIO.new(req.body.to_s) 31 | rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) 32 | 33 | env.update({"rack.version" => Rack::VERSION, 34 | "rack.input" => rack_input, 35 | "rack.errors" => $stderr, 36 | 37 | "rack.multithread" => true, 38 | "rack.multiprocess" => false, 39 | "rack.run_once" => false, 40 | 41 | "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" 42 | }) 43 | 44 | env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] 45 | env["QUERY_STRING"] ||= "" 46 | env["REQUEST_PATH"] ||= "/" 47 | unless env["PATH_INFO"] == "" 48 | path, n = req.request_uri.path, env["SCRIPT_NAME"].length 49 | env["PATH_INFO"] = path[n, path.length-n] 50 | end 51 | 52 | status, headers, body = @app.call(env) 53 | begin 54 | res.status = status.to_i 55 | headers.each { |k, vs| 56 | if k.downcase == "set-cookie" 57 | res.cookies.concat vs.split("\n") 58 | else 59 | vs.split("\n").each { |v| 60 | res[k] = v 61 | } 62 | end 63 | } 64 | body.each { |part| 65 | res.body << part 66 | } 67 | ensure 68 | body.close if body.respond_to? :close 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/head.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | 3 | class Head 4 | def initialize(app) 5 | @app = app 6 | end 7 | 8 | def call(env) 9 | status, headers, body = @app.call(env) 10 | 11 | if env["REQUEST_METHOD"] == "HEAD" 12 | [status, headers, []] 13 | else 14 | [status, headers, body] 15 | end 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/lobster.rb: -------------------------------------------------------------------------------- 1 | require 'zlib' 2 | 3 | require 'rack/request' 4 | require 'rack/response' 5 | 6 | module Rack 7 | # Paste has a Pony, Rack has a Lobster! 8 | class Lobster 9 | LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2 10 | P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0 11 | t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ 12 | I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0]) 13 | 14 | LambdaLobster = lambda { |env| 15 | if env["QUERY_STRING"].include?("flip") 16 | lobster = LobsterString.split("\n"). 17 | map { |line| line.ljust(42).reverse }. 18 | join("\n") 19 | href = "?" 20 | else 21 | lobster = LobsterString 22 | href = "?flip" 23 | end 24 | 25 | content = ["Lobstericious!", 26 | "
", lobster, "
", 27 | "flip!"] 28 | length = content.inject(0) { |a,e| a+e.size }.to_s 29 | [200, {"Content-Type" => "text/html", "Content-Length" => length}, content] 30 | } 31 | 32 | def call(env) 33 | req = Request.new(env) 34 | if req.GET["flip"] == "left" 35 | lobster = LobsterString.split("\n"). 36 | map { |line| line.ljust(42).reverse }. 37 | join("\n") 38 | href = "?flip=right" 39 | elsif req.GET["flip"] == "crash" 40 | raise "Lobster crashed" 41 | else 42 | lobster = LobsterString 43 | href = "?flip=left" 44 | end 45 | 46 | res = Response.new 47 | res.write "Lobstericious!" 48 | res.write "
"
49 |       res.write lobster
50 |       res.write "
" 51 | res.write "

flip!

" 52 | res.write "

crash!

" 53 | res.finish 54 | end 55 | 56 | end 57 | end 58 | 59 | if $0 == __FILE__ 60 | require 'rack' 61 | require 'rack/showexceptions' 62 | Rack::Handler::WEBrick.run \ 63 | Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), 64 | :Port => 9292 65 | end 66 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/lock.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class Lock 3 | FLAG = 'rack.multithread'.freeze 4 | 5 | def initialize(app, lock = Mutex.new) 6 | @app, @lock = app, lock 7 | end 8 | 9 | def call(env) 10 | old, env[FLAG] = env[FLAG], false 11 | @lock.synchronize { @app.call(env) } 12 | ensure 13 | env[FLAG] = old 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/logger.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | module Rack 4 | # Sets up rack.logger to write to rack.errors stream 5 | class Logger 6 | def initialize(app, level = ::Logger::INFO) 7 | @app, @level = app, level 8 | end 9 | 10 | def call(env) 11 | logger = ::Logger.new(env['rack.errors']) 12 | logger.level = @level 13 | 14 | env['rack.logger'] = logger 15 | @app.call(env) 16 | ensure 17 | logger.close 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/methodoverride.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class MethodOverride 3 | HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS) 4 | 5 | METHOD_OVERRIDE_PARAM_KEY = "_method".freeze 6 | HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze 7 | 8 | def initialize(app) 9 | @app = app 10 | end 11 | 12 | def call(env) 13 | if env["REQUEST_METHOD"] == "POST" 14 | req = Request.new(env) 15 | method = req.POST[METHOD_OVERRIDE_PARAM_KEY] || 16 | env[HTTP_METHOD_OVERRIDE_HEADER] 17 | method = method.to_s.upcase 18 | if HTTP_METHODS.include?(method) 19 | env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"] 20 | env["REQUEST_METHOD"] = method 21 | end 22 | end 23 | 24 | @app.call(env) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/mime.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | module Mime 3 | # Returns String with mime type if found, otherwise use +fallback+. 4 | # +ext+ should be filename extension in the '.ext' format that 5 | # File.extname(file) returns. 6 | # +fallback+ may be any object 7 | # 8 | # Also see the documentation for MIME_TYPES 9 | # 10 | # Usage: 11 | # Rack::Mime.mime_type('.foo') 12 | # 13 | # This is a shortcut for: 14 | # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream') 15 | 16 | def mime_type(ext, fallback='application/octet-stream') 17 | MIME_TYPES.fetch(ext.to_s.downcase, fallback) 18 | end 19 | module_function :mime_type 20 | 21 | # List of most common mime-types, selected various sources 22 | # according to their usefulness in a webserving scope for Ruby 23 | # users. 24 | # 25 | # To amend this list with your local mime.types list you can use: 26 | # 27 | # require 'webrick/httputils' 28 | # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types') 29 | # Rack::Mime::MIME_TYPES.merge!(list) 30 | # 31 | # To add the list mongrel provides, use: 32 | # 33 | # require 'mongrel/handlers' 34 | # Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES) 35 | 36 | MIME_TYPES = { 37 | ".3gp" => "video/3gpp", 38 | ".a" => "application/octet-stream", 39 | ".ai" => "application/postscript", 40 | ".aif" => "audio/x-aiff", 41 | ".aiff" => "audio/x-aiff", 42 | ".asc" => "application/pgp-signature", 43 | ".asf" => "video/x-ms-asf", 44 | ".asm" => "text/x-asm", 45 | ".asx" => "video/x-ms-asf", 46 | ".atom" => "application/atom+xml", 47 | ".au" => "audio/basic", 48 | ".avi" => "video/x-msvideo", 49 | ".bat" => "application/x-msdownload", 50 | ".bin" => "application/octet-stream", 51 | ".bmp" => "image/bmp", 52 | ".bz2" => "application/x-bzip2", 53 | ".c" => "text/x-c", 54 | ".cab" => "application/vnd.ms-cab-compressed", 55 | ".cc" => "text/x-c", 56 | ".chm" => "application/vnd.ms-htmlhelp", 57 | ".class" => "application/octet-stream", 58 | ".com" => "application/x-msdownload", 59 | ".conf" => "text/plain", 60 | ".cpp" => "text/x-c", 61 | ".crt" => "application/x-x509-ca-cert", 62 | ".css" => "text/css", 63 | ".csv" => "text/csv", 64 | ".cxx" => "text/x-c", 65 | ".deb" => "application/x-debian-package", 66 | ".der" => "application/x-x509-ca-cert", 67 | ".diff" => "text/x-diff", 68 | ".djv" => "image/vnd.djvu", 69 | ".djvu" => "image/vnd.djvu", 70 | ".dll" => "application/x-msdownload", 71 | ".dmg" => "application/octet-stream", 72 | ".doc" => "application/msword", 73 | ".dot" => "application/msword", 74 | ".dtd" => "application/xml-dtd", 75 | ".dvi" => "application/x-dvi", 76 | ".ear" => "application/java-archive", 77 | ".eml" => "message/rfc822", 78 | ".eps" => "application/postscript", 79 | ".exe" => "application/x-msdownload", 80 | ".f" => "text/x-fortran", 81 | ".f77" => "text/x-fortran", 82 | ".f90" => "text/x-fortran", 83 | ".flv" => "video/x-flv", 84 | ".for" => "text/x-fortran", 85 | ".gem" => "application/octet-stream", 86 | ".gemspec" => "text/x-script.ruby", 87 | ".gif" => "image/gif", 88 | ".gz" => "application/x-gzip", 89 | ".h" => "text/x-c", 90 | ".htc" => "text/x-component", 91 | ".hh" => "text/x-c", 92 | ".htm" => "text/html", 93 | ".html" => "text/html", 94 | ".ico" => "image/vnd.microsoft.icon", 95 | ".ics" => "text/calendar", 96 | ".ifb" => "text/calendar", 97 | ".iso" => "application/octet-stream", 98 | ".jar" => "application/java-archive", 99 | ".java" => "text/x-java-source", 100 | ".jnlp" => "application/x-java-jnlp-file", 101 | ".jpeg" => "image/jpeg", 102 | ".jpg" => "image/jpeg", 103 | ".js" => "application/javascript", 104 | ".json" => "application/json", 105 | ".log" => "text/plain", 106 | ".m3u" => "audio/x-mpegurl", 107 | ".m4v" => "video/mp4", 108 | ".man" => "text/troff", 109 | ".manifest"=> "text/cache-manifest", 110 | ".mathml" => "application/mathml+xml", 111 | ".mbox" => "application/mbox", 112 | ".mdoc" => "text/troff", 113 | ".me" => "text/troff", 114 | ".mid" => "audio/midi", 115 | ".midi" => "audio/midi", 116 | ".mime" => "message/rfc822", 117 | ".mml" => "application/mathml+xml", 118 | ".mng" => "video/x-mng", 119 | ".mov" => "video/quicktime", 120 | ".mp3" => "audio/mpeg", 121 | ".mp4" => "video/mp4", 122 | ".mp4v" => "video/mp4", 123 | ".mpeg" => "video/mpeg", 124 | ".mpg" => "video/mpeg", 125 | ".ms" => "text/troff", 126 | ".msi" => "application/x-msdownload", 127 | ".odp" => "application/vnd.oasis.opendocument.presentation", 128 | ".ods" => "application/vnd.oasis.opendocument.spreadsheet", 129 | ".odt" => "application/vnd.oasis.opendocument.text", 130 | ".ogg" => "application/ogg", 131 | ".ogv" => "video/ogg", 132 | ".p" => "text/x-pascal", 133 | ".pas" => "text/x-pascal", 134 | ".pbm" => "image/x-portable-bitmap", 135 | ".pdf" => "application/pdf", 136 | ".pem" => "application/x-x509-ca-cert", 137 | ".pgm" => "image/x-portable-graymap", 138 | ".pgp" => "application/pgp-encrypted", 139 | ".pkg" => "application/octet-stream", 140 | ".pl" => "text/x-script.perl", 141 | ".pm" => "text/x-script.perl-module", 142 | ".png" => "image/png", 143 | ".pnm" => "image/x-portable-anymap", 144 | ".ppm" => "image/x-portable-pixmap", 145 | ".pps" => "application/vnd.ms-powerpoint", 146 | ".ppt" => "application/vnd.ms-powerpoint", 147 | ".ps" => "application/postscript", 148 | ".psd" => "image/vnd.adobe.photoshop", 149 | ".py" => "text/x-script.python", 150 | ".qt" => "video/quicktime", 151 | ".ra" => "audio/x-pn-realaudio", 152 | ".rake" => "text/x-script.ruby", 153 | ".ram" => "audio/x-pn-realaudio", 154 | ".rar" => "application/x-rar-compressed", 155 | ".rb" => "text/x-script.ruby", 156 | ".rdf" => "application/rdf+xml", 157 | ".roff" => "text/troff", 158 | ".rpm" => "application/x-redhat-package-manager", 159 | ".rss" => "application/rss+xml", 160 | ".rtf" => "application/rtf", 161 | ".ru" => "text/x-script.ruby", 162 | ".s" => "text/x-asm", 163 | ".sgm" => "text/sgml", 164 | ".sgml" => "text/sgml", 165 | ".sh" => "application/x-sh", 166 | ".sig" => "application/pgp-signature", 167 | ".snd" => "audio/basic", 168 | ".so" => "application/octet-stream", 169 | ".svg" => "image/svg+xml", 170 | ".svgz" => "image/svg+xml", 171 | ".swf" => "application/x-shockwave-flash", 172 | ".t" => "text/troff", 173 | ".tar" => "application/x-tar", 174 | ".tbz" => "application/x-bzip-compressed-tar", 175 | ".tcl" => "application/x-tcl", 176 | ".tex" => "application/x-tex", 177 | ".texi" => "application/x-texinfo", 178 | ".texinfo" => "application/x-texinfo", 179 | ".text" => "text/plain", 180 | ".tif" => "image/tiff", 181 | ".tiff" => "image/tiff", 182 | ".torrent" => "application/x-bittorrent", 183 | ".tr" => "text/troff", 184 | ".txt" => "text/plain", 185 | ".vcf" => "text/x-vcard", 186 | ".vcs" => "text/x-vcalendar", 187 | ".vrml" => "model/vrml", 188 | ".war" => "application/java-archive", 189 | ".wav" => "audio/x-wav", 190 | ".webm" => "video/webm", 191 | ".wma" => "audio/x-ms-wma", 192 | ".wmv" => "video/x-ms-wmv", 193 | ".wmx" => "video/x-ms-wmx", 194 | ".wrl" => "model/vrml", 195 | ".wsdl" => "application/wsdl+xml", 196 | ".xbm" => "image/x-xbitmap", 197 | ".xhtml" => "application/xhtml+xml", 198 | ".xls" => "application/vnd.ms-excel", 199 | ".xml" => "application/xml", 200 | ".xpm" => "image/x-xpixmap", 201 | ".xsl" => "application/xml", 202 | ".xslt" => "application/xslt+xml", 203 | ".yaml" => "text/yaml", 204 | ".yml" => "text/yaml", 205 | ".zip" => "application/zip", 206 | } 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/mock.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | require 'stringio' 3 | require 'rack' 4 | require 'rack/lint' 5 | require 'rack/utils' 6 | require 'rack/response' 7 | 8 | module Rack 9 | # Rack::MockRequest helps testing your Rack application without 10 | # actually using HTTP. 11 | # 12 | # After performing a request on a URL with get/post/put/delete, it 13 | # returns a MockResponse with useful helper methods for effective 14 | # testing. 15 | # 16 | # You can pass a hash with additional configuration to the 17 | # get/post/put/delete. 18 | # :input:: A String or IO-like to be used as rack.input. 19 | # :fatal:: Raise a FatalWarning if the app writes to rack.errors. 20 | # :lint:: If true, wrap the application in a Rack::Lint. 21 | 22 | class MockRequest 23 | class FatalWarning < RuntimeError 24 | end 25 | 26 | class FatalWarner 27 | def puts(warning) 28 | raise FatalWarning, warning 29 | end 30 | 31 | def write(warning) 32 | raise FatalWarning, warning 33 | end 34 | 35 | def flush 36 | end 37 | 38 | def string 39 | "" 40 | end 41 | end 42 | 43 | DEFAULT_ENV = { 44 | "rack.version" => Rack::VERSION, 45 | "rack.input" => StringIO.new, 46 | "rack.errors" => StringIO.new, 47 | "rack.multithread" => true, 48 | "rack.multiprocess" => true, 49 | "rack.run_once" => false, 50 | } 51 | 52 | def initialize(app) 53 | @app = app 54 | end 55 | 56 | def get(uri, opts={}) request("GET", uri, opts) end 57 | def post(uri, opts={}) request("POST", uri, opts) end 58 | def put(uri, opts={}) request("PUT", uri, opts) end 59 | def delete(uri, opts={}) request("DELETE", uri, opts) end 60 | 61 | def request(method="GET", uri="", opts={}) 62 | env = self.class.env_for(uri, opts.merge(:method => method)) 63 | 64 | if opts[:lint] 65 | app = Rack::Lint.new(@app) 66 | else 67 | app = @app 68 | end 69 | 70 | errors = env["rack.errors"] 71 | MockResponse.new(*(app.call(env) + [errors])) 72 | end 73 | 74 | # Return the Rack environment used for a request to +uri+. 75 | def self.env_for(uri="", opts={}) 76 | uri = URI(uri) 77 | uri.path = "/#{uri.path}" unless uri.path[0] == ?/ 78 | 79 | env = DEFAULT_ENV.dup 80 | 81 | env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET" 82 | env["SERVER_NAME"] = uri.host || "example.org" 83 | env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80" 84 | env["QUERY_STRING"] = uri.query.to_s 85 | env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path 86 | env["rack.url_scheme"] = uri.scheme || "http" 87 | env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off" 88 | 89 | env["SCRIPT_NAME"] = opts[:script_name] || "" 90 | 91 | if opts[:fatal] 92 | env["rack.errors"] = FatalWarner.new 93 | else 94 | env["rack.errors"] = StringIO.new 95 | end 96 | 97 | if params = opts[:params] 98 | if env["REQUEST_METHOD"] == "GET" 99 | params = Utils.parse_nested_query(params) if params.is_a?(String) 100 | params.update(Utils.parse_nested_query(env["QUERY_STRING"])) 101 | env["QUERY_STRING"] = Utils.build_nested_query(params) 102 | elsif !opts.has_key?(:input) 103 | opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded" 104 | if params.is_a?(Hash) 105 | if data = Utils::Multipart.build_multipart(params) 106 | opts[:input] = data 107 | opts["CONTENT_LENGTH"] ||= data.length.to_s 108 | opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}" 109 | else 110 | opts[:input] = Utils.build_nested_query(params) 111 | end 112 | else 113 | opts[:input] = params 114 | end 115 | end 116 | end 117 | 118 | empty_str = "" 119 | empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding 120 | opts[:input] ||= empty_str 121 | if String === opts[:input] 122 | rack_input = StringIO.new(opts[:input]) 123 | else 124 | rack_input = opts[:input] 125 | end 126 | 127 | rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) 128 | env['rack.input'] = rack_input 129 | 130 | env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s 131 | 132 | opts.each { |field, value| 133 | env[field] = value if String === field 134 | } 135 | 136 | env 137 | end 138 | end 139 | 140 | # Rack::MockResponse provides useful helpers for testing your apps. 141 | # Usually, you don't create the MockResponse on your own, but use 142 | # MockRequest. 143 | 144 | class MockResponse 145 | def initialize(status, headers, body, errors=StringIO.new("")) 146 | @status = status.to_i 147 | 148 | @original_headers = headers 149 | @headers = Rack::Utils::HeaderHash.new 150 | headers.each { |field, values| 151 | @headers[field] = values 152 | @headers[field] = "" if values.empty? 153 | } 154 | 155 | @body = "" 156 | body.each { |part| @body << part } 157 | 158 | @errors = errors.string if errors.respond_to?(:string) 159 | end 160 | 161 | # Status 162 | attr_reader :status 163 | 164 | # Headers 165 | attr_reader :headers, :original_headers 166 | 167 | def [](field) 168 | headers[field] 169 | end 170 | 171 | 172 | # Body 173 | attr_reader :body 174 | 175 | def =~(other) 176 | @body =~ other 177 | end 178 | 179 | def match(other) 180 | @body.match other 181 | end 182 | 183 | 184 | # Errors 185 | attr_accessor :errors 186 | 187 | 188 | include Response::Helpers 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/nulllogger.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | class NullLogger 3 | def initialize(app) 4 | @app = app 5 | end 6 | 7 | def call(env) 8 | env['rack.logger'] = self 9 | @app.call(env) 10 | end 11 | 12 | def info(progname = nil, &block); end 13 | def debug(progname = nil, &block); end 14 | def warn(progname = nil, &block); end 15 | def error(progname = nil, &block); end 16 | def fatal(progname = nil, &block); end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/recursive.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | module Rack 4 | # Rack::ForwardRequest gets caught by Rack::Recursive and redirects 5 | # the current request to the app at +url+. 6 | # 7 | # raise ForwardRequest.new("/not-found") 8 | # 9 | 10 | class ForwardRequest < Exception 11 | attr_reader :url, :env 12 | 13 | def initialize(url, env={}) 14 | @url = URI(url) 15 | @env = env 16 | 17 | @env["PATH_INFO"] = @url.path 18 | @env["QUERY_STRING"] = @url.query if @url.query 19 | @env["HTTP_HOST"] = @url.host if @url.host 20 | @env["HTTP_PORT"] = @url.port if @url.port 21 | @env["rack.url_scheme"] = @url.scheme if @url.scheme 22 | 23 | super "forwarding to #{url}" 24 | end 25 | end 26 | 27 | # Rack::Recursive allows applications called down the chain to 28 | # include data from other applications (by using 29 | # rack['rack.recursive.include'][...] or raise a 30 | # ForwardRequest to redirect internally. 31 | 32 | class Recursive 33 | def initialize(app) 34 | @app = app 35 | end 36 | 37 | def call(env) 38 | dup._call(env) 39 | end 40 | 41 | def _call(env) 42 | @script_name = env["SCRIPT_NAME"] 43 | @app.call(env.merge('rack.recursive.include' => method(:include))) 44 | rescue ForwardRequest => req 45 | call(env.merge(req.env)) 46 | end 47 | 48 | def include(env, path) 49 | unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ || 50 | path[@script_name.size].nil?) 51 | raise ArgumentError, "can only include below #{@script_name}, not #{path}" 52 | end 53 | 54 | env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name, 55 | "REQUEST_METHOD" => "GET", 56 | "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "", 57 | "rack.input" => StringIO.new("")) 58 | @app.call(env) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/reloader.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com 2 | # Rack::Reloader is subject to the terms of an MIT-style license. 3 | # See COPYING or http://www.opensource.org/licenses/mit-license.php. 4 | 5 | require 'pathname' 6 | 7 | module Rack 8 | 9 | # High performant source reloader 10 | # 11 | # This class acts as Rack middleware. 12 | # 13 | # What makes it especially suited for use in a production environment is that 14 | # any file will only be checked once and there will only be made one system 15 | # call stat(2). 16 | # 17 | # Please note that this will not reload files in the background, it does so 18 | # only when actively called. 19 | # 20 | # It is performing a check/reload cycle at the start of every request, but 21 | # also respects a cool down time, during which nothing will be done. 22 | class Reloader 23 | def initialize(app, cooldown = 10, backend = Stat) 24 | @app = app 25 | @cooldown = cooldown 26 | @last = (Time.now - cooldown) 27 | @cache = {} 28 | @mtimes = {} 29 | 30 | extend backend 31 | end 32 | 33 | def call(env) 34 | if @cooldown and Time.now > @last + @cooldown 35 | if Thread.list.size > 1 36 | Thread.exclusive{ reload! } 37 | else 38 | reload! 39 | end 40 | 41 | @last = Time.now 42 | end 43 | 44 | @app.call(env) 45 | end 46 | 47 | def reload!(stderr = $stderr) 48 | rotation do |file, mtime| 49 | previous_mtime = @mtimes[file] ||= mtime 50 | safe_load(file, mtime, stderr) if mtime > previous_mtime 51 | end 52 | end 53 | 54 | # A safe Kernel::load, issuing the hooks depending on the results 55 | def safe_load(file, mtime, stderr = $stderr) 56 | load(file) 57 | stderr.puts "#{self.class}: reloaded `#{file}'" 58 | file 59 | rescue LoadError, SyntaxError => ex 60 | stderr.puts ex 61 | ensure 62 | @mtimes[file] = mtime 63 | end 64 | 65 | module Stat 66 | def rotation 67 | files = [$0, *$LOADED_FEATURES].uniq 68 | paths = ['./', *$LOAD_PATH].uniq 69 | 70 | files.map{|file| 71 | next if file =~ /\.(so|bundle)$/ # cannot reload compiled files 72 | 73 | found, stat = figure_path(file, paths) 74 | next unless found && stat && mtime = stat.mtime 75 | 76 | @cache[file] = found 77 | 78 | yield(found, mtime) 79 | }.compact 80 | end 81 | 82 | # Takes a relative or absolute +file+ name, a couple possible +paths+ that 83 | # the +file+ might reside in. Returns the full path and File::Stat for the 84 | # path. 85 | def figure_path(file, paths) 86 | found = @cache[file] 87 | found = file if !found and Pathname.new(file).absolute? 88 | found, stat = safe_stat(found) 89 | return found, stat if found 90 | 91 | paths.find do |possible_path| 92 | path = ::File.join(possible_path, file) 93 | found, stat = safe_stat(path) 94 | return ::File.expand_path(found), stat if found 95 | end 96 | 97 | return false, false 98 | end 99 | 100 | def safe_stat(file) 101 | return unless file 102 | stat = ::File.stat(file) 103 | return file, stat if stat.file? 104 | rescue Errno::ENOENT, Errno::ENOTDIR 105 | @cache.delete(file) and false 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/request.rb: -------------------------------------------------------------------------------- 1 | require 'rack/utils' 2 | 3 | module Rack 4 | # Rack::Request provides a convenient interface to a Rack 5 | # environment. It is stateless, the environment +env+ passed to the 6 | # constructor will be directly modified. 7 | # 8 | # req = Rack::Request.new(env) 9 | # req.post? 10 | # req.params["data"] 11 | # 12 | # The environment hash passed will store a reference to the Request object 13 | # instantiated so that it will only instantiate if an instance of the Request 14 | # object doesn't already exist. 15 | 16 | class Request 17 | # The environment of the request. 18 | attr_reader :env 19 | 20 | def initialize(env) 21 | @env = env 22 | end 23 | 24 | def body; @env["rack.input"] end 25 | def scheme; @env["rack.url_scheme"] end 26 | def script_name; @env["SCRIPT_NAME"].to_s end 27 | def path_info; @env["PATH_INFO"].to_s end 28 | def port; @env["SERVER_PORT"].to_i end 29 | def request_method; @env["REQUEST_METHOD"] end 30 | def query_string; @env["QUERY_STRING"].to_s end 31 | def content_length; @env['CONTENT_LENGTH'] end 32 | def content_type; @env['CONTENT_TYPE'] end 33 | def session; @env['rack.session'] ||= {} end 34 | def session_options; @env['rack.session.options'] ||= {} end 35 | def logger; @env['rack.logger'] end 36 | 37 | # The media type (type/subtype) portion of the CONTENT_TYPE header 38 | # without any media type parameters. e.g., when CONTENT_TYPE is 39 | # "text/plain;charset=utf-8", the media-type is "text/plain". 40 | # 41 | # For more information on the use of media types in HTTP, see: 42 | # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 43 | def media_type 44 | content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase 45 | end 46 | 47 | # The media type parameters provided in CONTENT_TYPE as a Hash, or 48 | # an empty Hash if no CONTENT_TYPE or media-type parameters were 49 | # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", 50 | # this method responds with the following Hash: 51 | # { 'charset' => 'utf-8' } 52 | def media_type_params 53 | return {} if content_type.nil? 54 | content_type.split(/\s*[;,]\s*/)[1..-1]. 55 | collect { |s| s.split('=', 2) }. 56 | inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash } 57 | end 58 | 59 | # The character set of the request body if a "charset" media type 60 | # parameter was given, or nil if no "charset" was specified. Note 61 | # that, per RFC2616, text/* media types that specify no explicit 62 | # charset are to be considered ISO-8859-1. 63 | def content_charset 64 | media_type_params['charset'] 65 | end 66 | 67 | def host_with_port 68 | if forwarded = @env["HTTP_X_FORWARDED_HOST"] 69 | forwarded.split(/,\s?/).last 70 | else 71 | @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}" 72 | end 73 | end 74 | 75 | def host 76 | # Remove port number. 77 | host_with_port.to_s.gsub(/:\d+\z/, '') 78 | end 79 | 80 | def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end 81 | def path_info=(s); @env["PATH_INFO"] = s.to_s end 82 | 83 | def delete?; request_method == "DELETE" end 84 | def get?; request_method == "GET" end 85 | def head?; request_method == "HEAD" end 86 | def options?; request_method == "OPTIONS" end 87 | def post?; request_method == "POST" end 88 | def put?; request_method == "PUT" end 89 | def trace?; request_method == "TRACE" end 90 | 91 | # The set of form-data media-types. Requests that do not indicate 92 | # one of the media types presents in this list will not be eligible 93 | # for form-data / param parsing. 94 | FORM_DATA_MEDIA_TYPES = [ 95 | 'application/x-www-form-urlencoded', 96 | 'multipart/form-data' 97 | ] 98 | 99 | # The set of media-types. Requests that do not indicate 100 | # one of the media types presents in this list will not be eligible 101 | # for param parsing like soap attachments or generic multiparts 102 | PARSEABLE_DATA_MEDIA_TYPES = [ 103 | 'multipart/related', 104 | 'multipart/mixed' 105 | ] 106 | 107 | # Determine whether the request body contains form-data by checking 108 | # the request Content-Type for one of the media-types: 109 | # "application/x-www-form-urlencoded" or "multipart/form-data". The 110 | # list of form-data media types can be modified through the 111 | # +FORM_DATA_MEDIA_TYPES+ array. 112 | # 113 | # A request body is also assumed to contain form-data when no 114 | # Content-Type header is provided and the request_method is POST. 115 | def form_data? 116 | type = media_type 117 | meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'] 118 | (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type) 119 | end 120 | 121 | # Determine whether the request body contains data by checking 122 | # the request media_type against registered parse-data media-types 123 | def parseable_data? 124 | PARSEABLE_DATA_MEDIA_TYPES.include?(media_type) 125 | end 126 | 127 | # Returns the data recieved in the query string. 128 | def GET 129 | if @env["rack.request.query_string"] == query_string 130 | @env["rack.request.query_hash"] 131 | else 132 | @env["rack.request.query_string"] = query_string 133 | @env["rack.request.query_hash"] = parse_query(query_string) 134 | end 135 | end 136 | 137 | # Returns the data recieved in the request body. 138 | # 139 | # This method support both application/x-www-form-urlencoded and 140 | # multipart/form-data. 141 | def POST 142 | if @env["rack.input"].nil? 143 | raise "Missing rack.input" 144 | elsif @env["rack.request.form_input"].eql? @env["rack.input"] 145 | @env["rack.request.form_hash"] 146 | elsif form_data? || parseable_data? 147 | @env["rack.request.form_input"] = @env["rack.input"] 148 | unless @env["rack.request.form_hash"] = parse_multipart(env) 149 | form_vars = @env["rack.input"].read 150 | 151 | # Fix for Safari Ajax postings that always append \0 152 | form_vars.sub!(/\0\z/, '') 153 | 154 | @env["rack.request.form_vars"] = form_vars 155 | @env["rack.request.form_hash"] = parse_query(form_vars) 156 | 157 | @env["rack.input"].rewind 158 | end 159 | @env["rack.request.form_hash"] 160 | else 161 | {} 162 | end 163 | end 164 | 165 | # The union of GET and POST data. 166 | def params 167 | self.GET.update(self.POST) 168 | rescue EOFError => e 169 | self.GET 170 | end 171 | 172 | # shortcut for request.params[key] 173 | def [](key) 174 | params[key.to_s] 175 | end 176 | 177 | # shortcut for request.params[key] = value 178 | def []=(key, value) 179 | params[key.to_s] = value 180 | end 181 | 182 | # like Hash#values_at 183 | def values_at(*keys) 184 | keys.map{|key| params[key] } 185 | end 186 | 187 | # the referer of the client or '/' 188 | def referer 189 | @env['HTTP_REFERER'] || '/' 190 | end 191 | alias referrer referer 192 | 193 | def user_agent 194 | @env['HTTP_USER_AGENT'] 195 | end 196 | 197 | def cookies 198 | return {} unless @env["HTTP_COOKIE"] 199 | 200 | if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] 201 | @env["rack.request.cookie_hash"] 202 | else 203 | @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"] 204 | # According to RFC 2109: 205 | # If multiple cookies satisfy the criteria above, they are ordered in 206 | # the Cookie header such that those with more specific Path attributes 207 | # precede those with less specific. Ordering with respect to other 208 | # attributes (e.g., Domain) is unspecified. 209 | @env["rack.request.cookie_hash"] = 210 | Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)| 211 | h[k] = Array === v ? v.first : v 212 | h 213 | } 214 | end 215 | end 216 | 217 | def xhr? 218 | @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" 219 | end 220 | 221 | # Tries to return a remake of the original request URL as a string. 222 | def url 223 | url = scheme + "://" 224 | url << host 225 | 226 | if scheme == "https" && port != 443 || 227 | scheme == "http" && port != 80 228 | url << ":#{port}" 229 | end 230 | 231 | url << fullpath 232 | 233 | url 234 | end 235 | 236 | def path 237 | script_name + path_info 238 | end 239 | 240 | def fullpath 241 | query_string.empty? ? path : "#{path}?#{query_string}" 242 | end 243 | 244 | def accept_encoding 245 | @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part| 246 | m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick 247 | 248 | if m 249 | [m[1], (m[2] || 1.0).to_f] 250 | else 251 | raise "Invalid value for Accept-Encoding: #{part.inspect}" 252 | end 253 | end 254 | end 255 | 256 | def ip 257 | if addr = @env['HTTP_X_FORWARDED_FOR'] 258 | (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip 259 | else 260 | @env['REMOTE_ADDR'] 261 | end 262 | end 263 | 264 | protected 265 | def parse_query(qs) 266 | Utils.parse_nested_query(qs) 267 | end 268 | 269 | def parse_multipart(env) 270 | Utils::Multipart.parse_multipart(env) 271 | end 272 | end 273 | end 274 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/response.rb: -------------------------------------------------------------------------------- 1 | require 'rack/request' 2 | require 'rack/utils' 3 | require 'time' 4 | 5 | module Rack 6 | # Rack::Response provides a convenient interface to create a Rack 7 | # response. 8 | # 9 | # It allows setting of headers and cookies, and provides useful 10 | # defaults (a OK response containing HTML). 11 | # 12 | # You can use Response#write to iteratively generate your response, 13 | # but note that this is buffered by Rack::Response until you call 14 | # +finish+. +finish+ however can take a block inside which calls to 15 | # +write+ are syncronous with the Rack response. 16 | # 17 | # Your application's +call+ should end returning Response#finish. 18 | 19 | class Response 20 | attr_accessor :length 21 | 22 | def initialize(body=[], status=200, header={}, &block) 23 | @status = status.to_i 24 | @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. 25 | merge(header)) 26 | 27 | @writer = lambda { |x| @body << x } 28 | @block = nil 29 | @length = 0 30 | 31 | @body = [] 32 | 33 | if body.respond_to? :to_str 34 | write body.to_str 35 | elsif body.respond_to?(:each) 36 | body.each { |part| 37 | write part.to_s 38 | } 39 | else 40 | raise TypeError, "stringable or iterable required" 41 | end 42 | 43 | yield self if block_given? 44 | end 45 | 46 | attr_reader :header 47 | attr_accessor :status, :body 48 | 49 | def [](key) 50 | header[key] 51 | end 52 | 53 | def []=(key, value) 54 | header[key] = value 55 | end 56 | 57 | def set_cookie(key, value) 58 | Utils.set_cookie_header!(header, key, value) 59 | end 60 | 61 | def delete_cookie(key, value={}) 62 | Utils.delete_cookie_header!(header, key, value) 63 | end 64 | 65 | def redirect(target, status=302) 66 | self.status = status 67 | self["Location"] = target 68 | end 69 | 70 | def finish(&block) 71 | @block = block 72 | 73 | if [204, 304].include?(status.to_i) 74 | header.delete "Content-Type" 75 | [status.to_i, header, []] 76 | else 77 | [status.to_i, header, self] 78 | end 79 | end 80 | alias to_a finish # For *response 81 | 82 | def each(&callback) 83 | @body.each(&callback) 84 | @writer = callback 85 | @block.call(self) if @block 86 | end 87 | 88 | # Append to body and update Content-Length. 89 | # 90 | # NOTE: Do not mix #write and direct #body access! 91 | # 92 | def write(str) 93 | s = str.to_s 94 | @length += Rack::Utils.bytesize(s) 95 | @writer.call s 96 | 97 | header["Content-Length"] = @length.to_s 98 | str 99 | end 100 | 101 | def close 102 | body.close if body.respond_to?(:close) 103 | end 104 | 105 | def empty? 106 | @block == nil && @body.empty? 107 | end 108 | 109 | alias headers header 110 | 111 | module Helpers 112 | def invalid?; @status < 100 || @status >= 600; end 113 | 114 | def informational?; @status >= 100 && @status < 200; end 115 | def successful?; @status >= 200 && @status < 300; end 116 | def redirection?; @status >= 300 && @status < 400; end 117 | def client_error?; @status >= 400 && @status < 500; end 118 | def server_error?; @status >= 500 && @status < 600; end 119 | 120 | def ok?; @status == 200; end 121 | def forbidden?; @status == 403; end 122 | def not_found?; @status == 404; end 123 | 124 | def redirect?; [301, 302, 303, 307].include? @status; end 125 | def empty?; [201, 204, 304].include? @status; end 126 | 127 | # Headers 128 | attr_reader :headers, :original_headers 129 | 130 | def include?(header) 131 | !!headers[header] 132 | end 133 | 134 | def content_type 135 | headers["Content-Type"] 136 | end 137 | 138 | def content_length 139 | cl = headers["Content-Length"] 140 | cl ? cl.to_i : cl 141 | end 142 | 143 | def location 144 | headers["Location"] 145 | end 146 | end 147 | 148 | include Helpers 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/rewindable_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | require 'tempfile' 3 | require 'rack/utils' 4 | 5 | module Rack 6 | # Class which can make any IO object rewindable, including non-rewindable ones. It does 7 | # this by buffering the data into a tempfile, which is rewindable. 8 | # 9 | # rack.input is required to be rewindable, so if your input stream IO is non-rewindable 10 | # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class 11 | # to easily make it rewindable. 12 | # 13 | # Don't forget to call #close when you're done. This frees up temporary resources that 14 | # RewindableInput uses, though it does *not* close the original IO object. 15 | class RewindableInput 16 | def initialize(io) 17 | @io = io 18 | @rewindable_io = nil 19 | @unlinked = false 20 | end 21 | 22 | def gets 23 | make_rewindable unless @rewindable_io 24 | @rewindable_io.gets 25 | end 26 | 27 | def read(*args) 28 | make_rewindable unless @rewindable_io 29 | @rewindable_io.read(*args) 30 | end 31 | 32 | def each(&block) 33 | make_rewindable unless @rewindable_io 34 | @rewindable_io.each(&block) 35 | end 36 | 37 | def rewind 38 | make_rewindable unless @rewindable_io 39 | @rewindable_io.rewind 40 | end 41 | 42 | # Closes this RewindableInput object without closing the originally 43 | # wrapped IO oject. Cleans up any temporary resources that this RewindableInput 44 | # has created. 45 | # 46 | # This method may be called multiple times. It does nothing on subsequent calls. 47 | def close 48 | if @rewindable_io 49 | if @unlinked 50 | @rewindable_io.close 51 | else 52 | @rewindable_io.close! 53 | end 54 | @rewindable_io = nil 55 | end 56 | end 57 | 58 | private 59 | 60 | # Ruby's Tempfile class has a bug. Subclass it and fix it. 61 | class Tempfile < ::Tempfile 62 | def _close 63 | @tmpfile.close if @tmpfile 64 | @data[1] = nil if @data 65 | @tmpfile = nil 66 | end 67 | end 68 | 69 | def make_rewindable 70 | # Buffer all data into a tempfile. Since this tempfile is private to this 71 | # RewindableInput object, we chmod it so that nobody else can read or write 72 | # it. On POSIX filesystems we also unlink the file so that it doesn't 73 | # even have a file entry on the filesystem anymore, though we can still 74 | # access it because we have the file handle open. 75 | @rewindable_io = Tempfile.new('RackRewindableInput') 76 | @rewindable_io.chmod(0000) 77 | @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding) 78 | @rewindable_io.binmode 79 | if filesystem_has_posix_semantics? 80 | @rewindable_io.unlink 81 | raise 'Unlink failed. IO closed.' if @rewindable_io.closed? 82 | @unlinked = true 83 | end 84 | 85 | buffer = "" 86 | while @io.read(1024 * 4, buffer) 87 | entire_buffer_written_out = false 88 | while !entire_buffer_written_out 89 | written = @rewindable_io.write(buffer) 90 | entire_buffer_written_out = written == Rack::Utils.bytesize(buffer) 91 | if !entire_buffer_written_out 92 | buffer.slice!(0 .. written - 1) 93 | end 94 | end 95 | end 96 | @rewindable_io.rewind 97 | end 98 | 99 | def filesystem_has_posix_semantics? 100 | RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/ 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/runtime.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Sets an "X-Runtime" response header, indicating the response 3 | # time of the request, in seconds 4 | # 5 | # You can put it right before the application to see the processing 6 | # time, or before all the other middlewares to include time for them, 7 | # too. 8 | class Runtime 9 | def initialize(app, name = nil) 10 | @app = app 11 | @header_name = "X-Runtime" 12 | @header_name << "-#{name}" if name 13 | end 14 | 15 | def call(env) 16 | start_time = Time.now 17 | status, headers, body = @app.call(env) 18 | request_time = Time.now - start_time 19 | 20 | if !headers.has_key?(@header_name) 21 | headers[@header_name] = "%0.6f" % request_time 22 | end 23 | 24 | [status, headers, body] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/sendfile.rb: -------------------------------------------------------------------------------- 1 | require 'rack/file' 2 | 3 | module Rack 4 | class File #:nodoc: 5 | unless instance_methods(false).include?('to_path') 6 | alias :to_path :path 7 | end 8 | end 9 | 10 | # = Sendfile 11 | # 12 | # The Sendfile middleware intercepts responses whose body is being 13 | # served from a file and replaces it with a server specific X-Sendfile 14 | # header. The web server is then responsible for writing the file contents 15 | # to the client. This can dramatically reduce the amount of work required 16 | # by the Ruby backend and takes advantage of the web server's optimized file 17 | # delivery code. 18 | # 19 | # In order to take advantage of this middleware, the response body must 20 | # respond to +to_path+ and the request must include an X-Sendfile-Type 21 | # header. Rack::File and other components implement +to_path+ so there's 22 | # rarely anything you need to do in your application. The X-Sendfile-Type 23 | # header is typically set in your web servers configuration. The following 24 | # sections attempt to document 25 | # 26 | # === Nginx 27 | # 28 | # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile 29 | # but requires parts of the filesystem to be mapped into a private URL 30 | # hierarachy. 31 | # 32 | # The following example shows the Nginx configuration required to create 33 | # a private "/files/" area, enable X-Accel-Redirect, and pass the special 34 | # X-Sendfile-Type and X-Accel-Mapping headers to the backend: 35 | # 36 | # location ~ /files/(.*) { 37 | # internal; 38 | # alias /var/www/$1; 39 | # } 40 | # 41 | # location / { 42 | # proxy_redirect off; 43 | # 44 | # proxy_set_header Host $host; 45 | # proxy_set_header X-Real-IP $remote_addr; 46 | # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 47 | # 48 | # proxy_set_header X-Sendfile-Type X-Accel-Redirect; 49 | # proxy_set_header X-Accel-Mapping /files/=/var/www/; 50 | # 51 | # proxy_pass http://127.0.0.1:8080/; 52 | # } 53 | # 54 | # Note that the X-Sendfile-Type header must be set exactly as shown above. The 55 | # X-Accel-Mapping header should specify the name of the private URL pattern, 56 | # followed by an equals sign (=), followed by the location on the file system 57 | # that it maps to. The middleware performs a simple substitution on the 58 | # resulting path. 59 | # 60 | # See Also: http://wiki.codemongers.com/NginxXSendfile 61 | # 62 | # === lighttpd 63 | # 64 | # Lighttpd has supported some variation of the X-Sendfile header for some 65 | # time, although only recent version support X-Sendfile in a reverse proxy 66 | # configuration. 67 | # 68 | # $HTTP["host"] == "example.com" { 69 | # proxy-core.protocol = "http" 70 | # proxy-core.balancer = "round-robin" 71 | # proxy-core.backends = ( 72 | # "127.0.0.1:8000", 73 | # "127.0.0.1:8001", 74 | # ... 75 | # ) 76 | # 77 | # proxy-core.allow-x-sendfile = "enable" 78 | # proxy-core.rewrite-request = ( 79 | # "X-Sendfile-Type" => (".*" => "X-Sendfile") 80 | # ) 81 | # } 82 | # 83 | # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore 84 | # 85 | # === Apache 86 | # 87 | # X-Sendfile is supported under Apache 2.x using a separate module: 88 | # 89 | # http://tn123.ath.cx/mod_xsendfile/ 90 | # 91 | # Once the module is compiled and installed, you can enable it using 92 | # XSendFile config directive: 93 | # 94 | # RequestHeader Set X-Sendfile-Type X-Sendfile 95 | # ProxyPassReverse / http://localhost:8001/ 96 | # XSendFile on 97 | 98 | class Sendfile 99 | F = ::File 100 | 101 | def initialize(app, variation=nil) 102 | @app = app 103 | @variation = variation 104 | end 105 | 106 | def call(env) 107 | status, headers, body = @app.call(env) 108 | if body.respond_to?(:to_path) 109 | case type = variation(env) 110 | when 'X-Accel-Redirect' 111 | path = F.expand_path(body.to_path) 112 | if url = map_accel_path(env, path) 113 | headers[type] = url 114 | body = [] 115 | else 116 | env['rack.errors'] << "X-Accel-Mapping header missing" 117 | end 118 | when 'X-Sendfile', 'X-Lighttpd-Send-File' 119 | path = F.expand_path(body.to_path) 120 | headers[type] = path 121 | body = [] 122 | when '', nil 123 | else 124 | env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n" 125 | end 126 | end 127 | [status, headers, body] 128 | end 129 | 130 | private 131 | def variation(env) 132 | @variation || 133 | env['sendfile.type'] || 134 | env['HTTP_X_SENDFILE_TYPE'] 135 | end 136 | 137 | def map_accel_path(env, file) 138 | if mapping = env['HTTP_X_ACCEL_MAPPING'] 139 | internal, external = mapping.split('=', 2).map{ |p| p.strip } 140 | file.sub(/^#{internal}/i, external) 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/server.rb: -------------------------------------------------------------------------------- 1 | require 'optparse' 2 | 3 | module Rack 4 | class Server 5 | class Options 6 | def parse!(args) 7 | options = {} 8 | opt_parser = OptionParser.new("", 24, ' ') do |opts| 9 | opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]" 10 | 11 | opts.separator "" 12 | opts.separator "Ruby options:" 13 | 14 | lineno = 1 15 | opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line| 16 | eval line, TOPLEVEL_BINDING, "-e", lineno 17 | lineno += 1 18 | } 19 | 20 | opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") { 21 | options[:debug] = true 22 | } 23 | opts.on("-w", "--warn", "turn warnings on for your script") { 24 | options[:warn] = true 25 | } 26 | 27 | opts.on("-I", "--include PATH", 28 | "specify $LOAD_PATH (may be used more than once)") { |path| 29 | options[:include] = path.split(":") 30 | } 31 | 32 | opts.on("-r", "--require LIBRARY", 33 | "require the library, before executing your script") { |library| 34 | options[:require] = library 35 | } 36 | 37 | opts.separator "" 38 | opts.separator "Rack options:" 39 | opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s| 40 | options[:server] = s 41 | } 42 | 43 | opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host| 44 | options[:Host] = host 45 | } 46 | 47 | opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port| 48 | options[:Port] = port 49 | } 50 | 51 | opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e| 52 | options[:environment] = e 53 | } 54 | 55 | opts.on("-D", "--daemonize", "run daemonized in the background") { |d| 56 | options[:daemonize] = d ? true : false 57 | } 58 | 59 | opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f| 60 | options[:pid] = f 61 | } 62 | 63 | opts.separator "" 64 | opts.separator "Common options:" 65 | 66 | opts.on_tail("-h", "--help", "Show this message") do 67 | puts opts 68 | exit 69 | end 70 | 71 | opts.on_tail("--version", "Show version") do 72 | puts "Rack #{Rack.version}" 73 | exit 74 | end 75 | end 76 | opt_parser.parse! args 77 | options[:config] = args.last if args.last 78 | options 79 | end 80 | end 81 | 82 | # Start a new rack server (like running rackup). This will parse ARGV and 83 | # provide standard ARGV rackup options, defaulting to load 'config.ru'. 84 | # 85 | # Providing an options hash will prevent ARGV parsing and will not include 86 | # any default options. 87 | # 88 | # This method can be used to very easily launch a CGI application, for 89 | # example: 90 | # 91 | # Rack::Server.start( 92 | # :app => lambda do |e| 93 | # [200, {'Content-Type' => 'text/html'}, ['hello world']] 94 | # end, 95 | # :server => 'cgi' 96 | # ) 97 | # 98 | # Further options available here are documented on Rack::Server#initialize 99 | def self.start(options = nil) 100 | new(options).start 101 | end 102 | 103 | attr_writer :options 104 | 105 | # Options may include: 106 | # * :app 107 | # a rack application to run (overrides :config) 108 | # * :config 109 | # a rackup configuration file path to load (.ru) 110 | # * :environment 111 | # this selects the middleware that will be wrapped around 112 | # your application. Default options available are: 113 | # - development: CommonLogger, ShowExceptions, and Lint 114 | # - deployment: CommonLogger 115 | # - none: no extra middleware 116 | # note: when the server is a cgi server, CommonLogger is not included. 117 | # * :server 118 | # choose a specific Rack::Handler, e.g. cgi, fcgi, webrick 119 | # * :daemonize 120 | # if true, the server will daemonize itself (fork, detach, etc) 121 | # * :pid 122 | # path to write a pid file after daemonize 123 | # * :Host 124 | # the host address to bind to (used by supporting Rack::Handler) 125 | # * :Port 126 | # the port to bind to (used by supporting Rack::Handler) 127 | # * :AccessLog 128 | # webrick acess log options (or supporting Rack::Handler) 129 | # * :debug 130 | # turn on debug output ($DEBUG = true) 131 | # * :warn 132 | # turn on warnings ($-w = true) 133 | # * :include 134 | # add given paths to $LOAD_PATH 135 | # * :require 136 | # require the given libraries 137 | def initialize(options = nil) 138 | @options = options 139 | end 140 | 141 | def options 142 | @options ||= parse_options(ARGV) 143 | end 144 | 145 | def default_options 146 | { 147 | :environment => "development", 148 | :pid => nil, 149 | :Port => 9292, 150 | :Host => "0.0.0.0", 151 | :AccessLog => [], 152 | :config => "config.ru" 153 | } 154 | end 155 | 156 | def app 157 | @app ||= begin 158 | if !::File.exist? options[:config] 159 | abort "configuration #{options[:config]} not found" 160 | end 161 | 162 | app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) 163 | self.options.merge! options 164 | app 165 | end 166 | end 167 | 168 | def self.middleware 169 | @middleware ||= begin 170 | m = Hash.new {|h,k| h[k] = []} 171 | m["deployment"].concat [lambda {|server| server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }] 172 | m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]] 173 | m 174 | end 175 | end 176 | 177 | def middleware 178 | self.class.middleware 179 | end 180 | 181 | def start 182 | if options[:debug] 183 | $DEBUG = true 184 | require 'pp' 185 | p options[:server] 186 | pp wrapped_app 187 | pp app 188 | end 189 | 190 | if options[:warn] 191 | $-w = true 192 | end 193 | 194 | if includes = options[:include] 195 | $LOAD_PATH.unshift(*includes) 196 | end 197 | 198 | if library = options[:require] 199 | require library 200 | end 201 | 202 | daemonize_app if options[:daemonize] 203 | write_pid if options[:pid] 204 | 205 | trap(:INT) do 206 | if server.respond_to?(:shutdown) 207 | server.shutdown 208 | else 209 | exit 210 | end 211 | end 212 | 213 | server.run wrapped_app, options 214 | end 215 | 216 | def server 217 | @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options) 218 | end 219 | 220 | private 221 | def parse_options(args) 222 | options = default_options 223 | 224 | # Don't evaluate CGI ISINDEX parameters. 225 | # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html 226 | args.clear if ENV.include?("REQUEST_METHOD") 227 | 228 | options.merge! opt_parser.parse! args 229 | ENV["RACK_ENV"] = options[:environment] 230 | options 231 | end 232 | 233 | def opt_parser 234 | Options.new 235 | end 236 | 237 | def build_app(app) 238 | middleware[options[:environment]].reverse_each do |middleware| 239 | middleware = middleware.call(self) if middleware.respond_to?(:call) 240 | next unless middleware 241 | klass = middleware.shift 242 | app = klass.new(app, *middleware) 243 | end 244 | app 245 | end 246 | 247 | def wrapped_app 248 | @wrapped_app ||= build_app app 249 | end 250 | 251 | def daemonize_app 252 | if RUBY_VERSION < "1.9" 253 | exit if fork 254 | Process.setsid 255 | exit if fork 256 | Dir.chdir "/" 257 | ::File.umask 0000 258 | STDIN.reopen "/dev/null" 259 | STDOUT.reopen "/dev/null", "a" 260 | STDERR.reopen "/dev/null", "a" 261 | else 262 | Process.daemon 263 | end 264 | end 265 | 266 | def write_pid 267 | ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") } 268 | at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) } 269 | end 270 | end 271 | end 272 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/session/abstract/id.rb: -------------------------------------------------------------------------------- 1 | # AUTHOR: blink ; blink#ruby-lang@irc.freenode.net 2 | # bugrep: Andreas Zehnder 3 | 4 | require 'time' 5 | require 'rack/request' 6 | require 'rack/response' 7 | 8 | module Rack 9 | 10 | module Session 11 | 12 | module Abstract 13 | 14 | # ID sets up a basic framework for implementing an id based sessioning 15 | # service. Cookies sent to the client for maintaining sessions will only 16 | # contain an id reference. Only #get_session and #set_session are 17 | # required to be overwritten. 18 | # 19 | # All parameters are optional. 20 | # * :key determines the name of the cookie, by default it is 21 | # 'rack.session' 22 | # * :path, :domain, :expire_after, :secure, and :httponly set the related 23 | # cookie options as by Rack::Response#add_cookie 24 | # * :defer will not set a cookie in the response. 25 | # * :renew (implementation dependent) will prompt the generation of a new 26 | # session id, and migration of data to be referenced at the new id. If 27 | # :defer is set, it will be overridden and the cookie will be set. 28 | # * :sidbits sets the number of bits in length that a generated session 29 | # id will be. 30 | # 31 | # These options can be set on a per request basis, at the location of 32 | # env['rack.session.options']. Additionally the id of the session can be 33 | # found within the options hash at the key :id. It is highly not 34 | # recommended to change its value. 35 | # 36 | # Is Rack::Utils::Context compatible. 37 | 38 | class ID 39 | DEFAULT_OPTIONS = { 40 | :path => '/', 41 | :domain => nil, 42 | :expire_after => nil, 43 | :secure => false, 44 | :httponly => true, 45 | :defer => false, 46 | :renew => false, 47 | :sidbits => 128 48 | } 49 | 50 | attr_reader :key, :default_options 51 | def initialize(app, options={}) 52 | @app = app 53 | @key = options[:key] || "rack.session" 54 | @default_options = self.class::DEFAULT_OPTIONS.merge(options) 55 | end 56 | 57 | def call(env) 58 | context(env) 59 | end 60 | 61 | def context(env, app=@app) 62 | load_session(env) 63 | status, headers, body = app.call(env) 64 | commit_session(env, status, headers, body) 65 | end 66 | 67 | private 68 | 69 | # Generate a new session id using Ruby #rand. The size of the 70 | # session id is controlled by the :sidbits option. 71 | # Monkey patch this to use custom methods for session id generation. 72 | 73 | def generate_sid 74 | "%0#{@default_options[:sidbits] / 4}x" % 75 | rand(2**@default_options[:sidbits] - 1) 76 | end 77 | 78 | # Extracts the session id from provided cookies and passes it and the 79 | # environment to #get_session. It then sets the resulting session into 80 | # 'rack.session', and places options and session metadata into 81 | # 'rack.session.options'. 82 | 83 | def load_session(env) 84 | request = Rack::Request.new(env) 85 | session_id = request.cookies[@key] 86 | 87 | begin 88 | session_id, session = get_session(env, session_id) 89 | env['rack.session'] = session 90 | rescue 91 | env['rack.session'] = Hash.new 92 | end 93 | 94 | env['rack.session.options'] = @default_options. 95 | merge(:id => session_id) 96 | end 97 | 98 | # Acquires the session from the environment and the session id from 99 | # the session options and passes them to #set_session. If successful 100 | # and the :defer option is not true, a cookie will be added to the 101 | # response with the session's id. 102 | 103 | def commit_session(env, status, headers, body) 104 | session = env['rack.session'] 105 | options = env['rack.session.options'] 106 | session_id = options[:id] 107 | 108 | if not session_id = set_session(env, session_id, session, options) 109 | env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.") 110 | elsif options[:defer] and not options[:renew] 111 | env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE 112 | else 113 | cookie = Hash.new 114 | cookie[:value] = session_id 115 | cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? 116 | Utils.set_cookie_header!(headers, @key, cookie.merge(options)) 117 | end 118 | 119 | [status, headers, body] 120 | end 121 | 122 | # All thread safety and session retrival proceedures should occur here. 123 | # Should return [session_id, session]. 124 | # If nil is provided as the session id, generation of a new valid id 125 | # should occur within. 126 | 127 | def get_session(env, sid) 128 | raise '#get_session not implemented.' 129 | end 130 | 131 | # All thread safety and session storage proceedures should occur here. 132 | # Should return true or false dependant on whether or not the session 133 | # was saved or not. 134 | def set_session(env, sid, session, options) 135 | raise '#set_session not implemented.' 136 | end 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/session/cookie.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | require 'rack/request' 3 | require 'rack/response' 4 | 5 | module Rack 6 | 7 | module Session 8 | 9 | # Rack::Session::Cookie provides simple cookie based session management. 10 | # The session is a Ruby Hash stored as base64 encoded marshalled data 11 | # set to :key (default: rack.session). 12 | # When the secret key is set, cookie data is checked for data integrity. 13 | # 14 | # Example: 15 | # 16 | # use Rack::Session::Cookie, :key => 'rack.session', 17 | # :domain => 'foo.com', 18 | # :path => '/', 19 | # :expire_after => 2592000, 20 | # :secret => 'change_me' 21 | # 22 | # All parameters are optional. 23 | 24 | class Cookie 25 | 26 | def initialize(app, options={}) 27 | @app = app 28 | @key = options[:key] || "rack.session" 29 | @secret = options[:secret] 30 | @default_options = {:domain => nil, 31 | :path => "/", 32 | :expire_after => nil}.merge(options) 33 | end 34 | 35 | def call(env) 36 | load_session(env) 37 | status, headers, body = @app.call(env) 38 | commit_session(env, status, headers, body) 39 | end 40 | 41 | private 42 | 43 | def load_session(env) 44 | request = Rack::Request.new(env) 45 | session_data = request.cookies[@key] 46 | 47 | if @secret && session_data 48 | session_data, digest = session_data.split("--") 49 | session_data = nil unless digest == generate_hmac(session_data) 50 | end 51 | 52 | begin 53 | session_data = session_data.unpack("m*").first 54 | session_data = Marshal.load(session_data) 55 | env["rack.session"] = session_data 56 | rescue 57 | env["rack.session"] = Hash.new 58 | end 59 | 60 | env["rack.session.options"] = @default_options.dup 61 | end 62 | 63 | def commit_session(env, status, headers, body) 64 | session_data = Marshal.dump(env["rack.session"]) 65 | session_data = [session_data].pack("m*") 66 | 67 | if @secret 68 | session_data = "#{session_data}--#{generate_hmac(session_data)}" 69 | end 70 | 71 | if session_data.size > (4096 - @key.size) 72 | env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.") 73 | else 74 | options = env["rack.session.options"] 75 | cookie = Hash.new 76 | cookie[:value] = session_data 77 | cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? 78 | Utils.set_cookie_header!(headers, @key, cookie.merge(options)) 79 | end 80 | 81 | [status, headers, body] 82 | end 83 | 84 | def generate_hmac(data) 85 | OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data) 86 | end 87 | 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/session/memcache.rb: -------------------------------------------------------------------------------- 1 | # AUTHOR: blink ; blink#ruby-lang@irc.freenode.net 2 | 3 | require 'rack/session/abstract/id' 4 | require 'memcache' 5 | 6 | module Rack 7 | module Session 8 | # Rack::Session::Memcache provides simple cookie based session management. 9 | # Session data is stored in memcached. The corresponding session key is 10 | # maintained in the cookie. 11 | # You may treat Session::Memcache as you would Session::Pool with the 12 | # following caveats. 13 | # 14 | # * Setting :expire_after to 0 would note to the Memcache server to hang 15 | # onto the session data until it would drop it according to it's own 16 | # specifications. However, the cookie sent to the client would expire 17 | # immediately. 18 | # 19 | # Note that memcache does drop data before it may be listed to expire. For 20 | # a full description of behaviour, please see memcache's documentation. 21 | 22 | class Memcache < Abstract::ID 23 | attr_reader :mutex, :pool 24 | DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \ 25 | :namespace => 'rack:session', 26 | :memcache_server => 'localhost:11211' 27 | 28 | def initialize(app, options={}) 29 | super 30 | 31 | @mutex = Mutex.new 32 | mserv = @default_options[:memcache_server] 33 | mopts = @default_options. 34 | reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k } 35 | @pool = MemCache.new mserv, mopts 36 | unless @pool.active? and @pool.servers.any?{|c| c.alive? } 37 | raise 'No memcache servers' 38 | end 39 | end 40 | 41 | def generate_sid 42 | loop do 43 | sid = super 44 | break sid unless @pool.get(sid, true) 45 | end 46 | end 47 | 48 | def get_session(env, session_id) 49 | @mutex.lock if env['rack.multithread'] 50 | unless session_id and session = @pool.get(session_id) 51 | session_id, session = generate_sid, {} 52 | unless /^STORED/ =~ @pool.add(session_id, session) 53 | raise "Session collision on '#{session_id.inspect}'" 54 | end 55 | end 56 | session.instance_variable_set '@old', @pool.get(session_id, true) 57 | return [session_id, session] 58 | rescue MemCache::MemCacheError, Errno::ECONNREFUSED 59 | # MemCache server cannot be contacted 60 | warn "#{self} is unable to find memcached server." 61 | warn $!.inspect 62 | return [ nil, {} ] 63 | ensure 64 | @mutex.unlock if @mutex.locked? 65 | end 66 | 67 | def set_session(env, session_id, new_session, options) 68 | expiry = options[:expire_after] 69 | expiry = expiry.nil? ? 0 : expiry + 1 70 | 71 | @mutex.lock if env['rack.multithread'] 72 | if options[:renew] or options[:drop] 73 | @pool.delete session_id 74 | return false if options[:drop] 75 | session_id = generate_sid 76 | @pool.add session_id, {} # so we don't worry about cache miss on #set 77 | end 78 | 79 | session = @pool.get(session_id) || {} 80 | old_session = new_session.instance_variable_get '@old' 81 | old_session = old_session ? Marshal.load(old_session) : {} 82 | 83 | unless Hash === old_session and Hash === new_session 84 | env['rack.errors']. 85 | puts 'Bad old_session or new_session sessions provided.' 86 | else # merge sessions 87 | # alterations are either update or delete, making as few changes as 88 | # possible to prevent possible issues. 89 | 90 | # removed keys 91 | delete = old_session.keys - new_session.keys 92 | if $VERBOSE and not delete.empty? 93 | env['rack.errors']. 94 | puts "//@#{session_id}: delete #{delete*','}" 95 | end 96 | delete.each{|k| session.delete k } 97 | 98 | # added or altered keys 99 | update = new_session.keys. 100 | select{|k| new_session[k] != old_session[k] } 101 | if $VERBOSE and not update.empty? 102 | env['rack.errors'].puts "//@#{session_id}: update #{update*','}" 103 | end 104 | update.each{|k| session[k] = new_session[k] } 105 | end 106 | 107 | @pool.set session_id, session, expiry 108 | return session_id 109 | rescue MemCache::MemCacheError, Errno::ECONNREFUSED 110 | # MemCache server cannot be contacted 111 | warn "#{self} is unable to find memcached server." 112 | warn $!.inspect 113 | return false 114 | ensure 115 | @mutex.unlock if @mutex.locked? 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/session/pool.rb: -------------------------------------------------------------------------------- 1 | # AUTHOR: blink ; blink#ruby-lang@irc.freenode.net 2 | # THANKS: 3 | # apeiros, for session id generation, expiry setup, and threadiness 4 | # sergio, threadiness and bugreps 5 | 6 | require 'rack/session/abstract/id' 7 | require 'thread' 8 | 9 | module Rack 10 | module Session 11 | # Rack::Session::Pool provides simple cookie based session management. 12 | # Session data is stored in a hash held by @pool. 13 | # In the context of a multithreaded environment, sessions being 14 | # committed to the pool is done in a merging manner. 15 | # 16 | # The :drop option is available in rack.session.options if you wish to 17 | # explicitly remove the session from the session cache. 18 | # 19 | # Example: 20 | # myapp = MyRackApp.new 21 | # sessioned = Rack::Session::Pool.new(myapp, 22 | # :domain => 'foo.com', 23 | # :expire_after => 2592000 24 | # ) 25 | # Rack::Handler::WEBrick.run sessioned 26 | 27 | class Pool < Abstract::ID 28 | attr_reader :mutex, :pool 29 | DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false 30 | 31 | def initialize(app, options={}) 32 | super 33 | @pool = Hash.new 34 | @mutex = Mutex.new 35 | end 36 | 37 | def generate_sid 38 | loop do 39 | sid = super 40 | break sid unless @pool.key? sid 41 | end 42 | end 43 | 44 | def get_session(env, sid) 45 | session = @pool[sid] if sid 46 | @mutex.lock if env['rack.multithread'] 47 | unless sid and session 48 | env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil? 49 | session = {} 50 | sid = generate_sid 51 | @pool.store sid, session 52 | end 53 | session.instance_variable_set('@old', {}.merge(session)) 54 | return [sid, session] 55 | ensure 56 | @mutex.unlock if env['rack.multithread'] 57 | end 58 | 59 | def set_session(env, session_id, new_session, options) 60 | @mutex.lock if env['rack.multithread'] 61 | session = @pool[session_id] 62 | if options[:renew] or options[:drop] 63 | @pool.delete session_id 64 | return false if options[:drop] 65 | session_id = generate_sid 66 | @pool.store session_id, 0 67 | end 68 | old_session = new_session.instance_variable_get('@old') || {} 69 | session = merge_sessions session_id, old_session, new_session, session 70 | @pool.store session_id, session 71 | return session_id 72 | rescue 73 | warn "#{new_session.inspect} has been lost." 74 | warn $!.inspect 75 | ensure 76 | @mutex.unlock if env['rack.multithread'] 77 | end 78 | 79 | private 80 | 81 | def merge_sessions sid, old, new, cur=nil 82 | cur ||= {} 83 | unless Hash === old and Hash === new 84 | warn 'Bad old or new sessions provided.' 85 | return cur 86 | end 87 | 88 | delete = old.keys - new.keys 89 | warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty? 90 | delete.each{|k| cur.delete k } 91 | 92 | update = new.keys.select{|k| new[k] != old[k] } 93 | warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty? 94 | update.each{|k| cur[k] = new[k] } 95 | 96 | cur 97 | end 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/showexceptions.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require 'erb' 3 | require 'rack/request' 4 | require 'rack/utils' 5 | 6 | module Rack 7 | # Rack::ShowExceptions catches all exceptions raised from the app it 8 | # wraps. It shows a useful backtrace with the sourcefile and 9 | # clickable context, the whole Rack environment and the request 10 | # data. 11 | # 12 | # Be careful when you use this on public-facing sites as it could 13 | # reveal information helpful to attackers. 14 | 15 | class ShowExceptions 16 | CONTEXT = 7 17 | 18 | def initialize(app) 19 | @app = app 20 | @template = ERB.new(TEMPLATE) 21 | end 22 | 23 | def call(env) 24 | @app.call(env) 25 | rescue StandardError, LoadError, SyntaxError => e 26 | backtrace = pretty(env, e) 27 | [500, 28 | {"Content-Type" => "text/html", 29 | "Content-Length" => backtrace.join.size.to_s}, 30 | backtrace] 31 | end 32 | 33 | def pretty(env, exception) 34 | req = Rack::Request.new(env) 35 | path = (req.script_name + req.path_info).squeeze("/") 36 | 37 | frames = exception.backtrace.map { |line| 38 | frame = OpenStruct.new 39 | if line =~ /(.*?):(\d+)(:in `(.*)')?/ 40 | frame.filename = $1 41 | frame.lineno = $2.to_i 42 | frame.function = $4 43 | 44 | begin 45 | lineno = frame.lineno-1 46 | lines = ::File.readlines(frame.filename) 47 | frame.pre_context_lineno = [lineno-CONTEXT, 0].max 48 | frame.pre_context = lines[frame.pre_context_lineno...lineno] 49 | frame.context_line = lines[lineno].chomp 50 | frame.post_context_lineno = [lineno+CONTEXT, lines.size].min 51 | frame.post_context = lines[lineno+1..frame.post_context_lineno] 52 | rescue 53 | end 54 | 55 | frame 56 | else 57 | nil 58 | end 59 | }.compact 60 | 61 | env["rack.errors"].puts "#{exception.class}: #{exception.message}" 62 | env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l } 63 | env["rack.errors"].flush 64 | 65 | [@template.result(binding)] 66 | end 67 | 68 | def h(obj) # :nodoc: 69 | case obj 70 | when String 71 | Utils.escape_html(obj) 72 | else 73 | Utils.escape_html(obj.inspect) 74 | end 75 | end 76 | 77 | # :stopdoc: 78 | 79 | # adapted from Django 80 | # Copyright (c) 2005, the Lawrence Journal-World 81 | # Used under the modified BSD license: 82 | # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 83 | TEMPLATE = <<'HTML' 84 | 85 | 86 | 87 | 88 | 89 | <%=h exception.class %> at <%=h path %> 90 | 142 | 190 | 191 | 192 | 193 |
194 |

<%=h exception.class %> at <%=h path %>

195 |

<%=h exception.message %>

196 | 197 | 198 | 199 | 200 | 201 | 202 |
Ruby<%=h frames.first.filename %>: in <%=h frames.first.function %>, line <%=h frames.first.lineno %>
Web<%=h req.request_method %> <%=h(req.host + path)%>
203 | 204 |

Jump to:

205 | 211 |
212 | 213 |
214 |

Traceback (innermost first)

215 |
    216 | <% frames.each { |frame| %> 217 |
  • 218 | <%=h frame.filename %>: in <%=h frame.function %> 219 | 220 | <% if frame.context_line %> 221 |
    222 | <% if frame.pre_context %> 223 |
      224 | <% frame.pre_context.each { |line| %> 225 |
    1. <%=h line %>
    2. 226 | <% } %> 227 |
    228 | <% end %> 229 | 230 |
      231 |
    1. <%=h frame.context_line %>...
    232 | 233 | <% if frame.post_context %> 234 |
      235 | <% frame.post_context.each { |line| %> 236 |
    1. <%=h line %>
    2. 237 | <% } %> 238 |
    239 | <% end %> 240 |
    241 | <% end %> 242 |
  • 243 | <% } %> 244 |
245 |
246 | 247 |
248 |

Request information

249 | 250 |

GET

251 | <% unless req.GET.empty? %> 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> 261 | 262 | 263 | 264 | 265 | <% } %> 266 | 267 |
VariableValue
<%=h key %>
<%=h val.inspect %>
268 | <% else %> 269 |

No GET data.

270 | <% end %> 271 | 272 |

POST

273 | <% unless req.POST.empty? %> 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> 283 | 284 | 285 | 286 | 287 | <% } %> 288 | 289 |
VariableValue
<%=h key %>
<%=h val.inspect %>
290 | <% else %> 291 |

No POST data.

292 | <% end %> 293 | 294 | 295 | 296 | <% unless req.cookies.empty? %> 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | <% req.cookies.each { |key, val| %> 306 | 307 | 308 | 309 | 310 | <% } %> 311 | 312 |
VariableValue
<%=h key %>
<%=h val.inspect %>
313 | <% else %> 314 |

No cookie data.

315 | <% end %> 316 | 317 |

Rack ENV

318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> 327 | 328 | 329 | 330 | 331 | <% } %> 332 | 333 |
VariableValue
<%=h key %>
<%=h val %>
334 | 335 |
336 | 337 |
338 |

339 | You're seeing this error because you use Rack::ShowExceptions. 340 |

341 |
342 | 343 | 344 | 345 | HTML 346 | 347 | # :startdoc: 348 | end 349 | end 350 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/showstatus.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'rack/request' 3 | require 'rack/utils' 4 | 5 | module Rack 6 | # Rack::ShowStatus catches all empty responses the app it wraps and 7 | # replaces them with a site explaining the error. 8 | # 9 | # Additional details can be put into rack.showstatus.detail 10 | # and will be shown as HTML. If such details exist, the error page 11 | # is always rendered, even if the reply was not empty. 12 | 13 | class ShowStatus 14 | def initialize(app) 15 | @app = app 16 | @template = ERB.new(TEMPLATE) 17 | end 18 | 19 | def call(env) 20 | status, headers, body = @app.call(env) 21 | headers = Utils::HeaderHash.new(headers) 22 | empty = headers['Content-Length'].to_i <= 0 23 | 24 | # client or server error, or explicit message 25 | if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"] 26 | req = Rack::Request.new(env) 27 | message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s 28 | detail = env["rack.showstatus.detail"] || message 29 | body = @template.result(binding) 30 | size = Rack::Utils.bytesize(body) 31 | [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] 32 | else 33 | [status, headers, body] 34 | end 35 | end 36 | 37 | def h(obj) # :nodoc: 38 | case obj 39 | when String 40 | Utils.escape_html(obj) 41 | else 42 | Utils.escape_html(obj.inspect) 43 | end 44 | end 45 | 46 | # :stopdoc: 47 | 48 | # adapted from Django 49 | # Copyright (c) 2005, the Lawrence Journal-World 50 | # Used under the modified BSD license: 51 | # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 52 | TEMPLATE = <<'HTML' 53 | 54 | 55 | 56 | 57 | <%=h message %> at <%=h req.script_name + req.path_info %> 58 | 59 | 76 | 77 | 78 |
79 |

<%=h message %> (<%= status.to_i %>)

80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
Request Method:<%=h req.request_method %>
Request URL:<%=h req.url %>
90 |
91 |
92 |

<%= detail %>

93 |
94 | 95 |
96 |

97 | You're seeing this error because you use Rack::ShowStatus. 98 |

99 |
100 | 101 | 102 | HTML 103 | 104 | # :startdoc: 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/static.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | 3 | # The Rack::Static middleware intercepts requests for static files 4 | # (javascript files, images, stylesheets, etc) based on the url prefixes 5 | # passed in the options, and serves them using a Rack::File object. This 6 | # allows a Rack stack to serve both static and dynamic content. 7 | # 8 | # Examples: 9 | # use Rack::Static, :urls => ["/media"] 10 | # will serve all requests beginning with /media from the "media" folder 11 | # located in the current directory (ie media/*). 12 | # 13 | # use Rack::Static, :urls => ["/css", "/images"], :root => "public" 14 | # will serve all requests beginning with /css or /images from the folder 15 | # "public" in the current directory (ie public/css/* and public/images/*) 16 | 17 | class Static 18 | 19 | def initialize(app, options={}) 20 | @app = app 21 | @urls = options[:urls] || ["/favicon.ico"] 22 | root = options[:root] || Dir.pwd 23 | @file_server = Rack::File.new(root) 24 | end 25 | 26 | def call(env) 27 | path = env["PATH_INFO"] 28 | can_serve = @urls.any? { |url| path.index(url) == 0 } 29 | 30 | if can_serve 31 | @file_server.call(env) 32 | else 33 | @app.call(env) 34 | end 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/control_tower/vendor/rack/urlmap.rb: -------------------------------------------------------------------------------- 1 | module Rack 2 | # Rack::URLMap takes a hash mapping urls or paths to apps, and 3 | # dispatches accordingly. Support for HTTP/1.1 host names exists if 4 | # the URLs start with http:// or https://. 5 | # 6 | # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part 7 | # relevant for dispatch is in the SCRIPT_NAME, and the rest in the 8 | # PATH_INFO. This should be taken care of when you need to 9 | # reconstruct the URL in order to create links. 10 | # 11 | # URLMap dispatches in such a way that the longest paths are tried 12 | # first, since they are most specific. 13 | 14 | class URLMap 15 | def initialize(map = {}) 16 | remap(map) 17 | end 18 | 19 | def remap(map) 20 | @mapping = map.map { |location, app| 21 | if location =~ %r{\Ahttps?://(.*?)(/.*)} 22 | host, location = $1, $2 23 | else 24 | host = nil 25 | end 26 | 27 | unless location[0] == ?/ 28 | raise ArgumentError, "paths need to start with /" 29 | end 30 | location = location.chomp('/') 31 | match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') 32 | 33 | [host, location, match, app] 34 | }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first 35 | end 36 | 37 | def call(env) 38 | path = env["PATH_INFO"] 39 | script_name = env['SCRIPT_NAME'] 40 | hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') 41 | @mapping.each { |host, location, match, app| 42 | next unless (hHost == host || sName == host \ 43 | || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) 44 | next unless path.to_s =~ match && rest = $1 45 | next unless rest.empty? || rest[0] == ?/ 46 | env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest) 47 | return app.call(env) 48 | } 49 | [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] 50 | ensure 51 | env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name 52 | end 53 | end 54 | end 55 | 56 | -------------------------------------------------------------------------------- /lib/rack/handler/control_tower.rb: -------------------------------------------------------------------------------- 1 | # This file is covered by the Ruby license. See COPYING for more details. 2 | # Copyright (C) 2009-2010, Apple Inc. All rights reserved. 3 | 4 | require "control_tower" 5 | 6 | module Rack 7 | module Handler 8 | class ControlTower 9 | def self.run(app, options={}) 10 | options[:port] = options.delete(:Port) if options[:Port] 11 | options[:host] = options.delete(:Host) if options[:Host] 12 | 13 | app = Rack::Chunked.new(Rack::ContentLength.new(app)) 14 | server = ::ControlTower::Server.new(app, options) 15 | yield server if block_given? 16 | server.start 17 | end 18 | end 19 | 20 | register 'control_tower', ControlTower 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rack/session/gcd.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright © 2010 Apple Inc. All rights reserved. 3 | # 4 | # IMPORTANT NOTE: This file is licensed only for use on Apple-branded computers 5 | # and is subject to the terms and conditions of the Apple Software License Agreement 6 | # accompanying the package this file is a part of. You may not port this file to 7 | # another platform without Apple's written consent. 8 | # 9 | 10 | require 'rack/session/abstract/id' 11 | 12 | module Rack 13 | module Session 14 | class GCDSession 15 | attr_reader :session_id 16 | 17 | def initialize(sid, store) 18 | @session_id = sid 19 | @session_store = store 20 | @session_values = {} 21 | @session_timer = nil 22 | @session_expires_at = nil 23 | @session_access_queue = Dispatch::Queue.new("session-#{sid}-access") 24 | 25 | # Sessions are self expiring. This is the block that handles expiry. The first task is to cancel the timer so that it only 26 | # fires once 27 | @timer_block = lambda do |src| 28 | @session_timer.cancel! unless @session_timer.nil? 29 | time_remaining = @session_expires_at - Time.now 30 | if time_remaining < 0 31 | warn "Session #{@session_id} has expired" if $DEBUG 32 | @session_store.delete(@session_id) 33 | else 34 | @session_timer = Dispatch::Source.timer(time_remaining, 500, 0.5, @session_access_queue, &@timer_block) 35 | end 36 | end 37 | end 38 | 39 | def set_timer(seconds) 40 | # If this is the first time we're setting the timer, then we need to create the timer source as well 41 | @session_timer ||= Dispatch::Source.timer(seconds, 500, 0.5, @session_access_queue, &@timer_block) 42 | @session_access_queue.sync { @session_expires_at = Time.now + seconds } 43 | end 44 | 45 | def [](key) 46 | @session_values[key] 47 | end 48 | alias :fetch :[] 49 | 50 | def []=(key, value) 51 | @session_access_queue.sync do 52 | @session_values[key] = value 53 | end 54 | value 55 | end 56 | alias :store :[]= 57 | 58 | def delete(key) 59 | @session_access_queue.sync do 60 | @session_values.delete(key) 61 | end 62 | end 63 | 64 | def clear 65 | @session_access_queue.sync do 66 | @session_values.clear 67 | end 68 | end 69 | end 70 | 71 | class GCD < Abstract::ID 72 | def initialize(app, options={}) 73 | super 74 | @sessions = {} 75 | end 76 | 77 | # Use UUIDs for session keys and save time on uniqueness checks 78 | def generate_sid 79 | uuid = CFUUIDCreate(nil) 80 | CFMakeCollectable(uuid) 81 | uuid_string = CFUUIDCreateString(nil, uuid) 82 | CFMakeCollectable(uuid_string) 83 | uuid_string 84 | end 85 | 86 | def get_session(env, sid) 87 | env['rack.errors'].puts("Searching through #{@sessions.length} live sessions") if $VERBOSE 88 | session = @sessions[sid] if sid 89 | unless sid and session 90 | env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil? 91 | sid = generate_sid 92 | session = GCDSession.new(sid, @sessions) 93 | @sessions[sid] = session 94 | end 95 | return sid, session 96 | end 97 | 98 | def set_session(env, sid, session, options) 99 | session = @sessions[sid] 100 | if options[:renew] or options[:drop] 101 | @sessions.delete(sid) 102 | return false if options[:drop] 103 | session_id = generate_sid 104 | @sessions[session_id] = 0 105 | end 106 | session ||= GCDSession.new(generate_sid, @sessions) 107 | session.set_timer(options[:expire_after]) unless options[:expire_after].nil? 108 | session.session_id 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /sample/README.rdoc: -------------------------------------------------------------------------------- 1 | == ControlTower Sample Files 2 | 3 | The samples in this directory should help you explore ControlTower's features. 4 | To use these samples, first build and install the Control Tower gem. Then, start 5 | the Rack-up config for each example like so: 6 | 7 | > control_tower -R 8 | 9 | and test it with a curl or wget like so: 10 | 11 | > curl http://0.0.0.0:3000/ 12 | 13 | To try a POST request, use curl (or similar tool) to send a post with some file 14 | content like so: 15 | 16 | > curl -F "file=@README.rdoc" http://0.0.0.0:3000/ 17 | 18 | Each of the samples demonstrates a different feature of ControlTower. 19 | 20 | 21 | === simple_hello.ru 22 | 23 | This is just a basic 'hello, world' style app. It demonstrates the basic concept 24 | of Rack. Specifically, the object that you pass as an argument to 'run' in the 25 | Rack-up config must have a #call method. That call method takes one argument, 26 | the environment (which is returned with the request in this sample), and it must 27 | return an array of 3 items: the response code, a hash of response headers, and 28 | the response body. 29 | 30 | 31 | === sinatra_hello.ru 32 | 33 | This sample demonstrates how you can use a Rack-based web app framework with 34 | ControlTower. In this case, the "MyApp" class inherits from Sinatra::Base, and 35 | is compliant with the Rack app contract. Don't forget to install Sinatra before 36 | you give this one a try! 37 | 38 | 39 | === file_upload.ru 40 | 41 | POST bodies are delivered as either StringIO or Tempfiles, and if the Post is a 42 | multipart-form MIME type, the Rack utilities will parse the various parts. This 43 | sample demonstrates these features by returning the parts of a multipart-form 44 | POST body, including reading the contents of any file uploaded. 45 | 46 | 47 | === x_sendfile.ru 48 | 49 | MacRuby includes the ability to use the sendfile(2) API with IO objects. Since 50 | ControlTower directly handles interaction with the system-level sockets for 51 | incoming connections, we can utilize this to send files to a client very 52 | efficiently (sendfile avoids excess kernel-userland context switches). This 53 | sample demonstrates how to use the X-Sendfile header in your response to take 54 | advantage of this feature. 55 | 56 | 57 | === photo_search.ru 58 | 59 | This is a slightly more complicated example. This sample takes the first letter 60 | from the query string (the bit that comes after a "?" in the URL) and searches 61 | Picasa for photos with that first letter as the first letter of their 62 | descriptions using the Picasa API. Try benchmarking this sample when running 63 | with the "-c" switch, and you'll see why being GCD based is an advantage when 64 | working with apps that call out to APIs which might have non-trivial latency. 65 | -------------------------------------------------------------------------------- /sample/example_file.txt: -------------------------------------------------------------------------------- 1 | This is a file with some content to demonstrate ControlTower's X-Sendfile functionality. 2 | 3 | See the README for more info. 4 | -------------------------------------------------------------------------------- /sample/file_upload.ru: -------------------------------------------------------------------------------- 1 | # This sample will read a file passed as part of a multipart-post body in a 2 | # section named 'file' 3 | 4 | require 'rack' 5 | 6 | class Uploader 7 | def call(env) 8 | params = Rack::Request.new(env).params 9 | response = params.map do |k,v| 10 | if k == 'file' && v[:tempfile] 11 | "#{k} => File Contents: #{v[:tempfile].read}" 12 | else 13 | "#{k} => #{v.inspect}" 14 | end 15 | end.join("\n") + "\n" 16 | [200, { 'Content-Type' => 'text/plain' }, response] 17 | end 18 | end 19 | 20 | run Uploader.new 21 | -------------------------------------------------------------------------------- /sample/gcd_sessions.ru: -------------------------------------------------------------------------------- 1 | require 'rack/session/gcd' 2 | 3 | class SessionCheck 4 | def call(env) 5 | session = env['rack.session'] 6 | if session 7 | if session[:num_accesses] 8 | session[:num_accesses] += 1 9 | msg = "This session has been accessed #{session[:num_accesses]} times" 10 | else 11 | session[:num_accesses] = 1 12 | msg = 'This is the first time this session has been accessed' 13 | end 14 | else 15 | msg = 'Whoops! No session...' 16 | end 17 | [200, {'Content-Type' => 'text/plain'}, msg] 18 | end 19 | end 20 | 21 | $VERBOSE = true, $DEBUG = true 22 | use Rack::Session::GCD, :expire_after => 30 23 | run SessionCheck.new 24 | -------------------------------------------------------------------------------- /sample/photo_search.ru: -------------------------------------------------------------------------------- 1 | framework 'Foundation' 2 | require 'rack' 3 | 4 | class PhotoSearch 5 | RESPONSE_TEMPLATE =<<-RESPONSE 6 | 7 | 8 | %s is for... 9 | 10 |

%s is for:

11 |

%s

12 | 13 | 14 | RESPONSE 15 | 16 | PICASA_URL_TEMPLATE = "http://picasaweb.google.com/data/feed/api/all?q=%s&max-results=%i" 17 | 18 | def build_response(letter, photo) 19 | summary = photo.nodesForXPath("./summary", error:nil).first.stringValue 20 | src = photo.nodesForXPath("./media:group/media:content/@url", error:nil).first.stringValue 21 | RESPONSE_TEMPLATE % [letter, letter, summary, src] 22 | end 23 | 24 | def get_photo(letter, max_results=10) 25 | return "Must provide a letter" unless letter 26 | return "No results found in the first 200 results" if max_results > 200 27 | url = NSURL.URLWithString(PICASA_URL_TEMPLATE % [Rack::Utils.escape(letter), max_results]) 28 | xmlDoc = NSXMLDocument.alloc.initWithContentsOfURL(url, options:NSXMLDocumentTidyXML, error:nil) 29 | 30 | entries = xmlDoc.nodesForXPath("//entry", error:nil) 31 | entries.each do |entry| 32 | summary = entry.nodesForXPath("./summary", error:nil)[0].stringValue 33 | if summary[0] == letter 34 | return build_response(letter, entry) 35 | end 36 | end 37 | get_photo(letter, max_results+=10) 38 | end 39 | 40 | def call(env) 41 | letter_to_search = Rack::Utils.unescape(env['QUERY_STRING']).chars.first 42 | [200, { 'Content-Type' => 'text/html; charset=UTF-8' }, get_photo(letter_to_search)] 43 | end 44 | 45 | 46 | end 47 | 48 | run PhotoSearch.new 49 | -------------------------------------------------------------------------------- /sample/simple_hello.ru: -------------------------------------------------------------------------------- 1 | # This is just a basic "hello, world" stlye rack-up to get you going 2 | 3 | class Hello 4 | def call(env) 5 | [200, { 'Content-Type' => 'text/plain' }, "Hello, world! Your environment is #{env}"] 6 | end 7 | end 8 | 9 | run Hello.new 10 | -------------------------------------------------------------------------------- /sample/sinatra_hello.ru: -------------------------------------------------------------------------------- 1 | # This is a basic Sinatra sample. 2 | # 3 | # NOTE: You must have the Sinatra gem installed before running 4 | 5 | require 'rubygems' 6 | require 'sinatra/base' 7 | 8 | class MyApp < Sinatra::Base 9 | get '/' do 10 | 'Hello world!' 11 | end 12 | end 13 | 14 | run MyApp.new 15 | -------------------------------------------------------------------------------- /sample/x_sendfile.ru: -------------------------------------------------------------------------------- 1 | # This is a quick demonstration of ControlTower's X-Sendfile functionality 2 | # 3 | # NOTE: Set the EXAMPLE_FILE environment variable to the path of the file you 4 | # want to send. See the README for more info. 5 | 6 | class FileSender 7 | def call(env) 8 | headers = { 'Content-Type' => 'text/plain' } 9 | headers['X-Sendfile'] = ::File.expand_path(ENV['EXAMPLE_FILE']) if ENV['EXAMPLE_FILE'] 10 | [200, headers, "You shouldn't get this body... see the README for more info\n"] 11 | end 12 | end 13 | 14 | run FileSender.new 15 | -------------------------------------------------------------------------------- /spec/ctparser_spec.rb: -------------------------------------------------------------------------------- 1 | # This file is covered by the Ruby license. See COPYING for more details. 2 | # Copyright (C) 2009-2010, Apple Inc. All rights reserved. 3 | 4 | require "bacon" 5 | require 'stringio' 6 | 7 | framework "CTParser" 8 | 9 | describe "Instantiating a new Parser" do 10 | it "should return an object of class CTParser" do 11 | parser = CTParser.new 12 | parser.class.should == CTParser 13 | end 14 | end 15 | 16 | describe "Parsing a minimal header" do 17 | before do 18 | @env = { 'rack.input' => StringIO.new } 19 | @parser = CTParser.new 20 | @header = "GET / HTTP/1.1\r\n\r\n" 21 | @read = @parser.parseData(@header, forEnvironment:@env) 22 | end 23 | 24 | it "should parse the full header length" do 25 | @read.should == @header.length 26 | end 27 | 28 | it "should finish" do 29 | @parser.finished.should == 1 30 | end 31 | 32 | it "should not have any errors" do 33 | @parser.errorCond.should == 0 34 | end 35 | 36 | it "should have read as many bytes as it read" do 37 | @parser.nread.should == @read 38 | end 39 | 40 | it "should populate SERVER_PROTOCOL" do 41 | @env['SERVER_PROTOCOL'].should == 'HTTP/1.1' 42 | end 43 | 44 | it "should populate PATH_INFO" do 45 | @env['PATH_INFO'].should == "/" 46 | end 47 | 48 | it "should populate HTTP_VERSION" do 49 | @env['HTTP_VERSION'].should == 'HTTP/1.1' 50 | end 51 | 52 | it "should populate GATEWAY_INTERFACE" do 53 | @env['GATEWAY_INTERFACE'].should == 'CGI/1.2' 54 | end 55 | 56 | it "should populate REQUEST_METHOD" do 57 | @env['REQUEST_METHOD'].should == 'GET' 58 | end 59 | 60 | it "should not have generated any fragments" do 61 | @env['FRAGMENT'].should.be.nil 62 | end 63 | 64 | it "should not populate QUERY_STRING" do 65 | @env['QUERY_STRING'].should.be.empty 66 | end 67 | end 68 | --------------------------------------------------------------------------------