Cookies y Rack

Cookies may be used to maintain data related to the user during navigation, possibly across multiple visits.

Introducción

  1. A cookie, is a small piece of data sent from a website and stored in a user's web browser while the user is browsing that website.

  2. Every time the user loads the website, the browser sends the cookie back to the server to notify the website of the user's previous activity
  3. Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items in a shopping cart) or to record the user's browsing activity (including clicking particular buttons, logging in, or recording which pages were visited by the user as far back as months or years ago).
  4. A user's session cookie (also known as an in-memory cookie or transient cookie) for a website exists in temporary memory only while the user is reading and navigating the website.
  5. When an expiry date or validity interval is not set at cookie creation time, a session cookie is created. Web browsers normally delete session cookies when the user closes the browser
  6. A persistent cookie will outlast user sessions. If a persistent cookie has its Max-Age set to 1 year, then, during that year, the initial value set in that cookie would be sent back to the server every time the user visited the server. This could be used to record information such as how the user initially came to this website. For this reason, persistent cookies are also called tracking cookies
  7. A secure cookie has the secure attribute enabled and is only used via HTTPS, ensuring that the cookie is always encrypted when transmitting from client to server. This makes the cookie less likely to be exposed to cookie theft via eavesdropping.
  8. First-party cookies are cookies that belong to the same domain that is shown in the browser's address bar (or that belong to the sub domain of the domain in the address bar).

  9. Third-party cookies are cookies that belong to domains different from the one shown in the address bar.
    1. Web pages can feature content from third-party domains (such as banner adverts), which opens up the potential for tracking the user's browsing history.
    2. Privacy setting options in most modern browsers allow the blocking of third-party tracking cookies.
    3. As an example, suppose a user visits www.example1.com.
    4. This web site contains an advert from ad.foxytracking.com, which, when downloaded, sets a cookie belonging to the advert's domain (ad.foxytracking.com).
    5. Then, the user visits another website, www.example2.com, which also contains an advert from ad.foxytracking.com, and which also sets a cookie belonging to that domain (ad.foxytracking.com).
    6. Eventually, both of these cookies will be sent to the advertiser when loading their ads or visiting their website.
    7. The advertiser can then use these cookies to build up a browsing history of the user across all the websites that have ads from this advertiser.

Propiedades de un cookie

Un cookie tiene los siguientes atributos:

  1. nombre
  2. valor
  3. domain (dominio)
  4. path o camino
  5. secure / seguridad

Cuando ejecutamos este programa:

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat study_cookie1.ru 
run lambda { |e|
  [ 200, 
    { 'Content-Type' => 'text/html',
      'Set-cookie'   => "id=123456\nname=jack\nphone=65452334"
    }, 
    [ 'hello world' ]
  ]
}
y hacemos www.example.com un alias de 127.0.0.1:
[~]$ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1 localhost www.example.com
al visitar la página www.example.com:9292 y abrir las herramientas para desarrolladores tenemos:

Observemos que:

  1. Como no hemos establecido el tiempo de caducidad ( expires Max-Age ), los cookies son de sesión.
  2. Como no hemos establecido el dominio, los cookies son de dominio www.example.com.

Estableciendo expires

Modifiquemos el ejemplo anterior para establecer una fecha de caducidad:

[~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat study_cookie2.ru 
run lambda { |e|
  t = Time.now.gmtime + 3*60
  [ 200, 
    { 'Content-Type' => 'text/html',
      'Set-cookie'   => "chuchu=chachi;expires=#{t.strftime("%a, %d-%b-%Y %H:%M:%S GMT")}"
    }, 
    [ 'hello world' ]
  ]
}

Al ejecutar este programa vemos que hemos establecido la caducidad. Obsérvese la diferencia entre GMT y el tiempo de Canarias.

Estableciendo el atributo domain de una cookie

  1. Establezcamos domain a example.com:
    ~/local/src/ruby/sinatra/rack/rack-session-cookie(master)]$ cat study_cookie3.ru 
    run lambda { |e|
      t = Time.now.gmtime + 3*60
      [ 200, 
        { 'Content-Type' => 'text/html',
          'Set-cookie'   => "chuchu=chachi;expires=#{t.strftime("%a, %d-%b-%Y %H:%M:%S GMT")}" +
                            ";domain=example.com"
        }, 
        [ 'hello world' ]
      ]
    }
    
  2. Manipulamos /etc/hosts:
    [~]$ cat /etc/hosts
    127.0.0.1 localhost www.example.com test.example.com app.test
    
  3. Ejecutamos el servidor y lo visitamos con el navegador en www.example.com:9292.
  4. A continuación arrancamos este segundo servidor en el puerto 8080:
    [~/local/src/ruby/sinatra/rack/rack-simple(master)]$ cat config.ru 
    require './myapp'
    run MyApp.new
    

    [~/local/src/ruby/sinatra/rack/rack-simple(master)]$ cat myapp.rb 
    # my_app.rb
    #
    class MyApp
      def call env
        [200, {"Content-Type" => "text/html"}, ["Hello Rack Participants"]] 
      end
    end
    
  5. y visitamos test.example.com:8080 (que de nuevo es resuelto a localhost)
La figura muestra que el cookie generado por www.example.com:9292 es enviado a test.example.com:8080:

El atributo path

Si path es / entonces casa con todos las páginas en el dominio. Si path es /foo entonces casa con foobar y /foo/chuchu/toto.html.

El atributo secure

Si se pone secure el cookie solo se envía si se usa https

Envío de Cookies

As long as the URL requested is within the same domain and path defined in the cookie (and all of the other restrictions - secure, not expired, etc) hold, the cookie will be sent for every request. The client will include a header field similar to this:
Cookie: name1 = value1 [;name2=value2]

Establecer un cookie usando Rack::Response

[~/local/src/ruby/sinatra/rack/rack-debugging(master)]$ cat hello_cookie.rb 
require 'rack'

class HelloWorld
  def call env
    response = Rack::Response.new("Hello world!")
    response.status = 200
    response.headers['Content-type'] = "text/plain"
    response.set_cookie('asignatura', 'SYTW')
    response.finish
  end
end

Rack::Handler::WEBrick::run HelloWorld.new

Obtener los valores de los cookies usando Rack::Request

Es posible acceder a los cookies con el objeto Rack::Request mediante el método cookies. Vease la documentación de Rack::Response y Rack::Request.

[~/rack/rack-debugging(master)]$ cat hello_cookie.rb 
require 'rack'

class HelloWorld
  def call env
    req      = Rack::Request.new(env)
    response = Rack::Response.new("Hello world! cookies = #{req.cookies.inspect}\n")
    response.write("asignatura => #{req.cookies['asignatura']}") if req.cookies['asignatura']
    response.status = 200
    response['Content-type'] = "text/plain"
    response.set_cookie('asignatura', 'SYTW')
    response.finish
  end
end

Rack::Handler::WEBrick::run HelloWorld.new

Limpiamos las cookies


Recargamos la página. El mensaje de la página se refiere a las cookies que acaban de llegar, mientras que el inspector nos avisa de que ya se ha establecido una cookie


Recargamos de nuevo la página. Ahora los dos están sicronizados

El código del método cookies

El método cookies retorna un hash:
# File lib/rack/request.rb, line 290
def cookies
  hash   = @env["rack.request.cookie_hash"] ||= {}
  string = @env["HTTP_COOKIE"]

  return hash if string == @env["rack.request.cookie_string"]
  hash.clear

  # According to RFC 2109:
  #   If multiple cookies satisfy the criteria above, they are ordered in
  #   the Cookie header such that those with more specific Path attributes
  #   precede those with less specific.  Ordering with respect to other
  #   attributes (e.g., Domain) is unspecified.
  cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
  cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
  @env["rack.request.cookie_string"] = string
  hash
end

Código de set_cookie

        # File lib/rack/response.rb, line 57
57:     def set_cookie(key, value)
58:       Utils.set_cookie_header!(header, key, value)
59:     end

Aquí value es un hash con claves :domain, :path, :expires, :secure y :httponly

Código de delete_cookie

    # File lib/rack/response.rb, line 61
61:     def delete_cookie(key, value={})
62:       Utils.delete_cookie_header!(header, key, value)
63:     end
Aquí value es un hash con claves :domain, :path, :expires, :secure y :httponly

domains, periods, cookies and localhost

  1. By design domain names must have at least two dots otherwise browser will say they are invalid.
  2. Only hosts within the specified domain can set a cookie for a domain
  3. domains must have at least two (2) or three (3) periods in them to prevent domains of the form: .com, .edu, and va.us.
  4. Any domain that fails within one of the seven special top level domains COM, EDU, NET, ORG, GOV, MIL, and INT require two periods.
  5. Any other domain requires at least three.
  6. On localhost, when we set a cookie on server side and specify the domain explicitly as localhost (or .localhost), the cookie does not seem to be accepted by some browsers.



Subsecciones
Casiano Rodriguez León 2015-06-18