diff options
| author | Gibheer <gibheer@gmail.com> | 2013-10-28 14:37:24 +0100 | 
|---|---|---|
| committer | Gibheer <gibheer@gmail.com> | 2013-10-28 14:37:24 +0100 | 
| commit | a0422c10546b2e0dea252e68d1870f362095cdab (patch) | |
| tree | 1574d6466c5b0895911bfb3055cea88d02a5d670 | |
| parent | 290165c440caceb53fd12ad6aa460069852a26ec (diff) | |
add cookie support to response
This commit adds support for response cookies. Response now has a method
cookie to fetch the current cookie. One cookie has multiple crumbs which
represent a key value pair. For each crumb multiple options can be set
according to the specs.
| -rw-r--r-- | lib/zero/response.rb | 26 | ||||
| -rw-r--r-- | lib/zero/response/cookie.rb | 105 | ||||
| -rw-r--r-- | spec/unit/zero/response/cookie/add_crumb_spec.rb | 54 | ||||
| -rw-r--r-- | spec/unit/zero/response/cookie/get_crumb_spec.rb | 21 | ||||
| -rw-r--r-- | spec/unit/zero/response/cookie/to_header_spec.rb | 29 | ||||
| -rw-r--r-- | spec/unit/zero/response/to_a_spec.rb | 7 | 
6 files changed, 236 insertions, 6 deletions
| diff --git a/lib/zero/response.rb b/lib/zero/response.rb index ab4acde..cb58cfe 100644 --- a/lib/zero/response.rb +++ b/lib/zero/response.rb @@ -1,3 +1,5 @@ +require 'zero/response/cookie' +  module Zero    # This is the representation of a response    class Response @@ -10,18 +12,17 @@ module Zero      # Constructor      # Sets default status code to 200. -    #      def initialize        @status = 200        @header = {}        @body   = [] +      @cookies = {}      end      # Sets the status.      # Also converts every input directly to an integer.      #      # @param [Integer] status The status code -    #      def status=(status)        @status = status.to_i      end @@ -53,8 +54,8 @@ module Zero      # Removes Content-Type, Content-Length and body on status code 204 and 304.      #      # @return [Array] Usable by webservers -    #      def to_a +      add_cookie_headers        # Remove content length and body, on status 204 and 304        if status == 204 or status == 304          header.delete('Content-Length') @@ -72,7 +73,6 @@ module Zero      # Sets the content length header to the current length of the body      # Also creates one, if it does not exists -    #      def content_length        self.header['Content-Length'] = body.join.bytesize.to_s      end @@ -81,7 +81,6 @@ module Zero      # Also creates it, if it does not exists      #      # @param [String] value Content-Type tp set -    #      def content_type=(value)        self.header['Content-Type'] = value      end @@ -89,10 +88,25 @@ module Zero      # Sets the Location header to the given URL and the status code to 302.      #       # @param [String] location Redirect URL -    #      def redirect(location, status = 302)        self.status = status        self.header['Location'] = location      end + +    # get the cookie for the response +    # +    # This returns the cookie holding all crumbs for the response. +    # @response [Cookie] the cookie with crumbs holding the information +    def cookie +      @cookie ||= Cookie.new +    end + +    private + +    # merge the cookie header into the other headers +    def add_cookie_headers +      return unless @cookie +      header.merge!(cookie.to_header) +    end    end  end diff --git a/lib/zero/response/cookie.rb b/lib/zero/response/cookie.rb new file mode 100644 index 0000000..45ef65f --- /dev/null +++ b/lib/zero/response/cookie.rb @@ -0,0 +1,105 @@ +module Zero +  class Response +    class Cookie +      # initialize an empty cookie +      def initialize +        @crumbs = {} +      end + +      # add a new crumb +      # +      # This adds a new crumb to the cookie specified through the key. +      # @param key [String] the identifier for the crumb +      # @param value [String] the value for the crumb +      # @param options [Hash] hash with further options for the crumb +      # @option options [Time]   :expire the time when the crumb should expire +      # @option options [String] :domain the domain the crumb should be sent to +      # @option options [String] :path   path when the crumb should be sent +      # @option options [Array]  :flags set flags for :secure or :http_only +      def add_crumb(key, value, options = {:flags => []}) +        @crumbs[key] = Crumb.new(key, value, options) +      end + +      # get the crumb for the key +      # +      # This method returns the crumb for the specified key. The crumb holds all +      # information, like the expire time and domain and so on. +      # @param key [String] the key to return +      # @returns [Cookie::Crumb] a cookie crumb or nil when the key +      #                             does not exist +      def get_crumb(key) +        @crumbs[key] +      end + +      # merge all crumbs to one header line +      # +      # This merges all crumbs together to a header line, where each cookie is +      # separated by the `Set-Cookie` header. +      # @returns [Hash] a key value pair to merge with the headers +      def to_header +        {'Set-Cookie' => @crumbs.map{|key, crumb| crumb}.join("\nSet-Cookie: ")} +      end +       +      private + +      class Crumb +        attr_reader :key, :secure, :http_only +        attr_accessor :domain, :path, :expire, :value + +        def initialize(key, value, options = {}) +          options[:flags] ||= [] +          @key       = key +          @value     = value +          @domain    = options[:domain] +          @expire    = options[:expire] +          @path      = options[:path] +          @secure    = options[:flags].include?(:secure) +          @http_only = options[:flags].include?(:http_only) +        end + +        # set the `http_only` flag +        # +        # This method sets the flag to only allow modifications from the +        # server and makes the browser not allow modifications through +        # javascript. +        def deny_client_side_modification! +          @http_only = true +        end + +        # remove the `http_only` flag +        # +        # This removes the `http_only` flag to allow modifications of the +        # crumb through javascript. +        def allow_client_side_modification! +          @http_only = false +        end + +        # set the `secure` flag +        # +        # This sets the `secure` flag on the crumb which tells the browser to +        # only send it through secure channels, like https. +        # Keep in mind, that this does not encrypt the content of the Crumb! +        def secure! +          @secure = true +        end + +        # unset the `secure` flag +        # +        # This unsets the `secure` flag which tells the browser, that it can +        # send the crumb over unsecure channel too, like plain http. +        def unsecure! +          @secure = false +        end + +        def to_s +          "#{@key}=#{@value}" + +            (@expire ? "; Expires=#{@expire.rfc2822}" : '') + +            (@path   ? "; Path=#{@path}" : '') + +            (@domain ? "; Domain=#{domain}" : '') + +            (@http_only ? '; HttpOnly' : '') + +            (@secure    ? '; Secure' : '') +        end +      end +    end +  end +end diff --git a/spec/unit/zero/response/cookie/add_crumb_spec.rb b/spec/unit/zero/response/cookie/add_crumb_spec.rb new file mode 100644 index 0000000..3c8dbee --- /dev/null +++ b/spec/unit/zero/response/cookie/add_crumb_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Zero::Response::Cookie, '#add_crumb' do +  let(:cookie) { Zero::Response::Cookie.new } +  subject { cookie.add_crumb(key, value, options) } +  let(:options) { {} } +  let(:key)     { 'key' } +  let(:value)   { 'value' } + +  before :each do +    subject +  end + +  context 'with no argument' do +    it 'adds a new crumb' do +      expect(cookie.get_crumb(key).key).to be(key) +    end +  end + +  context 'with flags' do +    let(:options) { {:flags => [:secure, :http_only]} } + +    it 'adds a crumb with secure header' do +      expect(cookie.get_crumb(key).secure).to be(true) +    end + +    it 'adds a crumb with http_only header' do +      expect(cookie.get_crumb(key).http_only).to be(true) +    end +  end + +  context 'with expire' do +    let(:time) { Time.now } +    let(:options) { {:expire => time} } + +    it 'adds a crumb with expire header' do +      expect(cookie.get_crumb(key).expire).to be(time) +    end +  end + +  context 'with domain and path' do +    let(:domain) { 'libzero.org' } +    let(:path)   { '/admin' } +    let(:options) { {:domain => domain, :path => path} } + +    it 'adds a crumb with domain header' do +      expect(cookie.get_crumb(key).domain).to be(domain) +    end + +    it 'adds a crumb with path header' do +      expect(cookie.get_crumb(key).path).to be(path) +    end +  end +end diff --git a/spec/unit/zero/response/cookie/get_crumb_spec.rb b/spec/unit/zero/response/cookie/get_crumb_spec.rb new file mode 100644 index 0000000..3c315db --- /dev/null +++ b/spec/unit/zero/response/cookie/get_crumb_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Zero::Response::Cookie, '#add_crumb' do +  let(:cookie) { Zero::Response::Cookie.new } +  subject { cookie.add_crumb(key, value, options) } +  let(:options) { {} } +  let(:key)     { 'key' } +  let(:value)   { 'value' } + +  before :each do +    subject +  end + +  it 'returns the crumb when the crumb exists' do +    expect(cookie.get_crumb(key).key).to be(key) +  end + +  it 'returns nil for the wrong key' do +    expect(cookie.get_crumb('wrong key')).to be(nil) +  end +end diff --git a/spec/unit/zero/response/cookie/to_header_spec.rb b/spec/unit/zero/response/cookie/to_header_spec.rb new file mode 100644 index 0000000..7cdc278 --- /dev/null +++ b/spec/unit/zero/response/cookie/to_header_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Zero::Response::Cookie, '#add_crumb' do +  let(:cookie) { Zero::Response::Cookie.new } +  subject { cookie.add_crumb(key, value, options) } +  let(:options) { { +    :domain => domain, +    :path   => path, +    :expire => time, +    :flags  => flags +  } } +  let(:key)     { 'key' } +  let(:value)   { 'value' } +  let(:time)    { Time.new } +  let(:domain)  { 'libzero.org' } +  let(:path)    { '/admin' } +  let(:flags)   { [:secure, :http_only] } + +  before :each do +    subject +  end + +  it 'returns the header line' do +    expect(cookie.to_header).to eq( +      {'Set-Cookie' => "#{key}=#{value}; Expires=#{time.rfc2822};" + +                        " Path=#{path}; Domain=#{domain}; HttpOnly; Secure"} +    ) +  end +end diff --git a/spec/unit/zero/response/to_a_spec.rb b/spec/unit/zero/response/to_a_spec.rb index b016eb9..b547f98 100644 --- a/spec/unit/zero/response/to_a_spec.rb +++ b/spec/unit/zero/response/to_a_spec.rb @@ -69,5 +69,12 @@ describe Zero::Response do        value[1].should eq({})  # Headers        value[2].should eq([])  # Body      end + +    it "adds the cookie to the headers" do +      key = 'key' +      value = 'value' +      subject.cookie.add_crumb(key, value) +      expect(subject.to_a[1]['Set-Cookie']).to eq("#{key}=#{value}") +    end    end  end | 
