note description: "[ This class represents the value of a HTTP cookie, transferred in a request. The class has features to build an HTTP cookie. Following a newer RFC standard for Cookies http://tools.ietf.org/html/rfc6265 Domain * WARNING: Some existing user agents treat an absent Domain attribute as if the Domain attribute were present and contained the current host name. * For example, if example.com returns a Set-Cookie header without a Domain attribute, these user agents will erroneously send the cookie to www.example.com as well. Max-Age, Expires * If a cookie has both the Max-Age and the Expires attribute, the Max-Age attribute has precedence and controls the expiration date of the cookie. * If a cookie has neither the Max-Age nor the Expires attribute, the user agent will retain the cookie until "the current session is over" (as defined by the user agent). * You will need to call the feature HttpOnly, Secure * Note that the HttpOnly attribute is independent of the Secure attribute: a cookie can have both the HttpOnly and the Secure attribute. ]" date: "$Date$" revision: "$Revision$" EIS: "name=HTTP Cookie specification", "src=http://tools.ietf.org/html/rfc6265", "protocol=uri" class HTTP_COOKIE create make feature {NONE} -- Initialization make (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8) -- Create an object instance of cookie with name `a_name' and value `a_value'. require a_name_not_blank: a_name /= Void and then not a_name.is_whitespace a_name_has_valid_characters: a_name /= Void and then has_valid_characters (a_name) a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) do set_name (a_name) set_value(a_value) unset_max_age ensure name_set: name = a_name value_set: value = a_value max_age_set: max_age < 0 end feature -- Access name: READABLE_STRING_8 -- name of the cookie. value: READABLE_STRING_8 -- value of the cookie. expiration: detachable READABLE_STRING_8 -- Value of the Expires attribute. path: detachable READABLE_STRING_8 -- Value of the Path attribute. -- Path to which the cookie applies. --| The path "/", specify a cookie that apply to all URLs in your site. domain: detachable READABLE_STRING_8 -- Value of the Domain attribute. -- Domain to which the cookies apply. secure: BOOLEAN -- Value of the Secure attribute. -- By default False. --| Indicate if the cookie should only be sent over secured(encrypted connections, for example SSL). http_only: BOOLEAN -- Value of the http_only attribute. -- By default false. --| Limits the scope of the cookie to HTTP requests. max_age: INTEGER -- Value of the Max-Age attribute. --| How much time in seconds should elapsed before the cookie expires. --| By default max_age < 0 indicate a cookie will last only for the current user-agent (Browser, etc) session. --| A value of 0 instructs the user-agent to delete the cookie. has_valid_characters (a_name: READABLE_STRING_8):BOOLEAN -- Has `a_name' valid characters for cookies? local l_iterator: STRING_ITERATION_CURSOR l_found: BOOLEAN do create l_iterator.make (a_name) Result := True across l_iterator as ic until l_found loop if not is_valid_character (ic.item.natural_32_code) then Result := False l_found := True end end end is_valid_rfc1123_date (a_string: READABLE_STRING_8): BOOLEAN -- Is the date represented by `a_string' a valid rfc1123 date? local d: HTTP_DATE do create d.make_from_string (a_string) Result := not d.has_error and then d.rfc1123_string.same_string (a_string) end feature -- Obsolete query include_max_age: BOOLEAN obsolete "Use `max_age > 0' [2017-05-31]" do Result := max_age > 0 end include_expires: BOOLEAN obsolete "Use `expires /= Void' [2017-05-31]" do Result := expiration /= Void end feature -- Obsolete element change mark_max_age -- Set `max_age > 0' -- Set `expires to void' -- Set-Cookie will include only Max-Age attribute and not Expires. obsolete "Uset `set_max_age' and `unset_*' features to add or remove the attributes from the response header [2017-05-31]" do max_age := 1 expiration := Void ensure max_age_true: include_max_age expire_false: not include_expires end mark_expires -- Set `mark_age' to -1. -- Set `expiration to a default date' -- Set-Cookie will include only Expires attribute and not Max_Age. obsolete "Use `set_expiration' and `unset_*' features to add or remove the attribute from the response header [2017-05-31]" do max_age := -1 set_expiration_date (create {DATE_TIME}.make_now_utc) ensure expires_true: include_expires max_age_false: not include_max_age end feature -- Change Element set_name (a_name: READABLE_STRING_8) -- Set `name' to `a_name'. require a_name_not_blank: a_name /= Void and then not a_name.is_whitespace a_name_has_valid_characters: a_name /= Void and then has_valid_characters (a_name) do name := a_name ensure name_set: name = a_name end set_value (a_value: READABLE_STRING_8) -- Set `value' to `a_value'. require a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) do value := a_value ensure value_set: value = a_value end set_expiration (a_date: READABLE_STRING_8) -- Set `expiration' to RFC1123 date string `a_date'. require rfc1133_date: a_date /= Void and then is_valid_rfc1123_date (a_date) do expiration := a_date ensure expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date) end set_expiration_date (a_date: DATE_TIME) -- Set `expiration' to `a_date'. do set_expiration (date_to_rfc1123_http_date_format (a_date)) ensure expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date)) end set_expiration_from_max_age -- Set `expiration` value from `max_age`. local dt: DATE_TIME do if max_age < 0 then unset_expiration else if max_age = 0 then create dt.make_from_epoch (0) else create dt.make_now_utc dt.second_add (max_age) end set_expiration_date (dt) end end set_path (a_path: READABLE_STRING_8) -- Set `path' to `a_path'. do path := a_path ensure path_set: path = a_path end set_domain (a_domain: READABLE_STRING_8) -- Set `domain' to `a_domain'. -- Note: you should avoid using "localhost" as `domain' for local cookies -- since they are not always handled by browser (for instance Chrome) require domain_without_port_info: a_domain /= Void implies not a_domain.has (':') do domain := a_domain ensure domain_set: domain = a_domain end set_secure (a_secure: BOOLEAN) -- Set `secure' to `a_secure'. do secure := a_secure ensure secure_set: secure = a_secure end set_http_only (a_http_only: BOOLEAN) -- Set `http_only' to `a_http_only'. do http_only := a_http_only ensure http_only_set: http_only = a_http_only end set_max_age (a_max_age: INTEGER) -- Set `max_age' to `a_max_age'. require valid_max_age: a_max_age >= 0 do max_age := a_max_age ensure max_age_set: max_age = a_max_age end unset_max_age -- Set `max_age' to -1. do max_age := -1 ensure max_age_unset: max_age = -1 end unset_expiration -- Set `expiration' to Void. do expiration := Void ensure expiration_void: expiration = Void end feature {NONE} -- Date Utils date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8 -- String representation of `dt' using the RFC 1123 local d: HTTP_DATE do create d.make_from_date_time (dt) Result := d.string end feature -- Output header_line: STRING -- String representation of Set-Cookie header line of Current. local s: STRING do s := {HTTP_HEADER_NAMES}.header_set_cookie + colon_space + name + "=" + value if attached domain as l_domain and then not l_domain.same_string ("localhost") then s.append ("; Domain=") s.append (l_domain) end if attached path as l_path then s.append ("; Path=") s.append (l_path) end -- Expires if attached expiration as l_expires then s.append ("; Expires=") s.append (l_expires) end -- Max-age if max_age >= 0 then s.append ("; Max-Age=") s.append_integer (max_age) end if secure then s.append ("; Secure") end if http_only then s.append ("; HttpOnly") end Result := s end feature {NONE} -- Constants colon_space: IMMUTABLE_STRING_8 once create Result.make_from_string (": ") end is_valid_character (c: NATURAL_32): BOOLEAN -- RFC6265 that specifies that the following is valid for characters in cookies. -- The following character ranges are valid:http://tools.ietf.org/html/rfc6265#section-4.1.1 -- %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E -- 0x21: ! -- 0x23-2B: #$%&'()*+ -- 0x2D-3A: -./0123456789: -- 0x3C-5B: <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[ -- 0x5D-7E: ]^_`abcdefghijklmnopqrstuvwxyz{|}~ note EIS: "name=valid-characters", "src=http://tools.ietf.org/html/rfc6265#section-4.1.1", "protocol=uri" do Result := True inspect c when 0x21 then when 0x23 .. 0x2B then when 0x2D .. 0x3A then when 0x3C .. 0x5B then when 0x5D .. 0x7E then else Result := False end end note copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end