class Cuba

Secure HTTP Headers

This plugin will automatically apply several headers that are related to security. This includes:

- HTTP Strict Transport Security (HSTS) [2].
- X-Frame-Options [3].
- X-XSS-Protection [4].
- X-Content-Type-Options [5].
- X-Download-Options [6].
- X-Permitted-Cross-Domain-Policies [7].

References

[1]: github.com/twitter/secureheaders [2]: tools.ietf.org/html/rfc6797 [3]: tools.ietf.org/html/draft-ietf-websec-x-frame-options-02 [4]: msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx [5]: msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx [6]: msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx [7]: www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html

Constants

DEFAULT
EMPTY
REGEXES
SEGMENT
SLASH

Attributes

captures[R]
env[R]
req[R]
res[R]

Public Class Methods

app() click to toggle source
# File lib/cuba.rb, line 90
def self.app
  @app ||= Rack::Builder.new
end
call(env) click to toggle source
# File lib/cuba.rb, line 106
def self.call(env)
  prototype.call(env)
end
deepclone(obj) click to toggle source
# File lib/cuba.rb, line 121
def self.deepclone(obj)
  Marshal.load(Marshal.dump(obj))
end
define(&block) click to toggle source
# File lib/cuba.rb, line 98
def self.define(&block)
  app.run new(&block)
end
inherited(child) click to toggle source
# File lib/cuba.rb, line 125
def self.inherited(child)
  child.settings.replace(deepclone(settings))
end
new(&blk) click to toggle source
# File lib/cuba.rb, line 134
def initialize(&blk)
  @blk = blk
  @captures = []
end
plugin(mixin) click to toggle source
# File lib/cuba.rb, line 110
def self.plugin(mixin)
  include mixin
  extend  mixin::ClassMethods if defined?(mixin::ClassMethods)

  mixin.setup(self) if mixin.respond_to?(:setup)
end
prototype() click to toggle source
# File lib/cuba.rb, line 102
def self.prototype
  @prototype ||= app.to_app
end
reset!() click to toggle source
# File lib/cuba.rb, line 85
def self.reset!
  @app = nil
  @prototype = nil
end
settings() click to toggle source
# File lib/cuba.rb, line 117
def self.settings
  @settings ||= {}
end
use(middleware, *args, &block) click to toggle source
# File lib/cuba.rb, line 94
def self.use(middleware, *args, &block)
  app.use(middleware, *args, &block)
end

Public Instance Methods

accept(mimetype) click to toggle source

If you want to match against the HTTP_ACCEPT value.

@example

# HTTP_ACCEPT=application/xml
on accept("application/xml") do
  # automatically set to application/xml.
  res.write res["Content-Type"]
end
# File lib/cuba.rb, line 314
def accept(mimetype)
  lambda do
    accept = String(env["HTTP_ACCEPT"]).split(",")

    if accept.any? { |s| s.strip == mimetype }
      res[Rack::CONTENT_TYPE] = mimetype
    end
  end
end
call(env) click to toggle source
# File lib/cuba.rb, line 143
def call(env)
  dup.call!(env)
end
call!(env) click to toggle source
# File lib/cuba.rb, line 147
def call!(env)
  @env = env
  @req = settings[:req].new(env)
  @res = settings[:res].new(settings[:default_headers].dup)

  # This `catch` statement will either receive a
  # rack response tuple via a `halt`, or will
  # fall back to issuing a 404.
  #
  # When it `catch`es a throw, the return value
  # of this whole `call!` method will be the
  # rack response tuple, which is exactly what we want.
  catch(:halt) do
    instance_eval(&@blk)

    not_found
    res.finish
  end
end
default() click to toggle source

Syntactic sugar for providing catch-all matches.

@example

on default do
  res.write "404"
end
# File lib/cuba.rb, line 330
def default
  true
end
delete() click to toggle source
# File lib/cuba.rb, line 358
def delete;  req.delete?  end
extension(ext = "\\w+") click to toggle source

A matcher for files with a certain extension.

@example

# PATH_INFO=/style/app.css
on "style", extension("css") do |file|
  res.write file # writes app
end
# File lib/cuba.rb, line 272
def extension(ext = "\\w+")
  lambda { consume("([^\\/]+?)\.#{ext}\\z") }
end
get() click to toggle source

Syntatic sugar for providing HTTP Verb matching.

@example

on get, "signup" do
end

on post, "signup" do
end
# File lib/cuba.rb, line 354
def get;     req.get?     end
halt(response) click to toggle source
# File lib/cuba.rb, line 382
def halt(response)
  throw :halt, response
end
head() click to toggle source
# File lib/cuba.rb, line 359
def head;    req.head?    end
host(hostname) click to toggle source

Useful for matching against the request host (i.e. HTTP_HOST).

@example

on host("account1.example.com"), "api" do
  res.write "You have reached the API of account1."
end
# File lib/cuba.rb, line 302
def host(hostname)
  hostname === req.host
end
match(matcher, segment = SEGMENT) click to toggle source
# File lib/cuba.rb, line 254
def match(matcher, segment = SEGMENT)
  case matcher
  when String then consume(matcher.gsub(/:\w+/, segment))
  when Regexp then consume(matcher)
  when Symbol then consume(segment)
  when Proc   then matcher.call
  else
    matcher
  end
end
not_found() click to toggle source
# File lib/cuba.rb, line 429
def not_found
  res.status = 404
end
on(*args) { |*captures| ... } click to toggle source

The heart of the path / verb / any condition matching.

@example

on get do
  res.write "GET"
end

on get, "signup" do
  res.write "Signup"
end

on "user/:id" do |uid|
  res.write "User: #{uid}"
end

on "styles", extension("css") do |file|
  res.write render("styles/#{file}.sass")
end
# File lib/cuba.rb, line 193
def on(*args, &block)
  try do
    # For every block, we make sure to reset captures so that
    # nesting matchers won't mess with each other's captures.
    @captures = []

    # We stop evaluation of this entire matcher unless
    # each and every `arg` defined for this matcher evaluates
    # to a non-false value.
    #
    # Short circuit examples:
    #    on true, false do
    #
    #    # PATH_INFO=/user
    #    on true, "signup"
    return unless args.all? { |arg| match(arg) }

    # The captures we yield here were generated and assembled
    # by evaluating each of the `arg`s above. Most of these
    # are carried out by #consume.
    yield(*captures)

    if res.status.nil?
      if res.body.empty?
        not_found
      else
        res.headers[Rack::CONTENT_TYPE] ||= DEFAULT
        res.status = 200
      end
    end

    halt(res.finish)
  end
end
options() click to toggle source
# File lib/cuba.rb, line 360
def options; req.options? end
param(key, default = nil) click to toggle source

Ensures that certain request parameters are present. Acts like a precondition / assertion for your route. A default value can be provided as a second argument. In that case, it always matches and the result is either the parameter or the default value.

@example

# POST with data like user[fname]=John&user[lname]=Doe
on "signup", param("user") do |atts|
  User.create(atts)
end

on "login", param("username", "guest") do |username|
  # If not provided, username == "guest"
end
# File lib/cuba.rb, line 290
def param(key, default = nil)
  value = req.params[key.to_s] || default

  lambda { captures << value unless value.to_s.empty? }
end
patch() click to toggle source
# File lib/cuba.rb, line 357
def patch;   req.patch?   end
post() click to toggle source
# File lib/cuba.rb, line 355
def post;    req.post?    end
put() click to toggle source
# File lib/cuba.rb, line 356
def put;     req.put?     end
root() click to toggle source

Access the root of the application.

@example

# GET /
on root do
  res.write "Home"
end
# File lib/cuba.rb, line 342
def root
  env[Rack::PATH_INFO] == SLASH || env[Rack::PATH_INFO] == EMPTY
end
run(app) click to toggle source

If you want to halt the processing of an existing handler and continue it via a different handler.

@example

def redirect(*args)
  run Cuba.new { on(default) { res.redirect(*args) }}
end

on "account" do
  redirect "/login" unless session["uid"]

  res.write "Super secure account info."
end
# File lib/cuba.rb, line 378
def run(app)
  halt app.call(req.env)
end
session() click to toggle source
# File lib/cuba.rb, line 167
def session
  env["rack.session"] || raise(RuntimeError,
    "You're missing a session handler. You can get started " +
    "by adding Cuba.use Rack::Session::Cookie")
end
settings() click to toggle source
# File lib/cuba.rb, line 139
def settings
  self.class.settings
end
trace() click to toggle source
# File lib/cuba.rb, line 363
def trace;   req.trace?   end
vars() click to toggle source

Returns a hash with the information set by the with method.

with(role: "admin", site: "main") do
  on default do
    res.write(vars.inspect)
  end
end
# => '{:role=>"admin", :site=>"main"}'
# File lib/cuba.rb, line 425
def vars
  env["cuba.vars"] ||= {}
end
with(dict = {}) { || ... } click to toggle source

Adds ability to pass information to a nested Cuba application. It receives two parameters: a hash that represents the passed information and a block. The vars method is used to retrieve a hash with the passed information.

class Platforms < Cuba
  define do
    platform = vars[:platform]

    on default do
      res.write(platform) # => "heroku" or "salesforce"
    end
  end
end

Cuba.define do
  on "(heroku|salesforce)" do |platform|
    with(platform: platform) do
      run(Platforms)
    end
  end
end
# File lib/cuba.rb, line 409
def with(dict = {})
  old, env["cuba.vars"] = vars, vars.merge(dict)
  yield
ensure
  env["cuba.vars"] = old
end

Private Instance Methods

consume(pattern) click to toggle source
# File lib/cuba.rb, line 240
def consume(pattern)
  matchdata = env[Rack::PATH_INFO].match(REGEXES[pattern])

  return false unless matchdata

  path, *vars = matchdata.captures

  env[Rack::SCRIPT_NAME] += "/#{path}"
  env[Rack::PATH_INFO] = "#{vars.pop}#{matchdata.post_match}"

  captures.push(*vars)
end
try() { || ... } click to toggle source

@private Used internally by on to ensure that SCRIPT_NAME and

PATH_INFO are reset to their proper values.
# File lib/cuba.rb, line 230
def try
  script, path = env[Rack::SCRIPT_NAME], env[Rack::PATH_INFO]

  yield

ensure
  env[Rack::SCRIPT_NAME], env[Rack::PATH_INFO] = script, path
end