|
| 1 | +note |
| 2 | + description: "[ |
| 3 | + This class represents the value of a HTTP cookie, transferred in a request. |
| 4 | + The class has features to build an HTTP cookie. |
| 5 | + |
| 6 | + Following a newer RFC standard for Cookies http://tools.ietf.org/html/rfc6265 |
| 7 | + |
| 8 | + Domain |
| 9 | + * WARNING: Some existing user agents treat an absent Domain attribute as if the Domain attribute were present and contained the current host name. |
| 10 | + * 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. |
| 11 | + |
| 12 | + Max-Age, Expires |
| 13 | + * 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. |
| 14 | + * 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). |
| 15 | + * You will need to call the feature |
| 16 | + |
| 17 | + HttpOnly, Secure |
| 18 | + * Note that the HttpOnly attribute is independent of the Secure attribute: a cookie can have both the HttpOnly and the Secure attribute. |
| 19 | +
|
| 20 | +]" |
| 21 | + date: "$Date$" |
| 22 | + revision: "$Revision$" |
| 23 | + EIS: "name=HTTP Cookie specification", "src=http://tools.ietf.org/html/rfc6265", "protocol=uri" |
| 24 | +class |
| 25 | + HTTP_COOKIE |
| 26 | + |
| 27 | +create |
| 28 | + make |
| 29 | + |
| 30 | +feature {NONE} -- Initialization |
| 31 | + |
| 32 | + make (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8) |
| 33 | + -- Create an object instance of cookie with name `a_name' and value `a_value'. |
| 34 | + require |
| 35 | + a_name_not_blank: a_name /= Void and then not a_name.is_whitespace |
| 36 | + a_name_has_valid_characters: a_name /= Void and then has_valid_characters (a_name) |
| 37 | + a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) |
| 38 | + do |
| 39 | + set_name (a_name) |
| 40 | + set_value(a_value) |
| 41 | + set_max_age (-1) |
| 42 | + ensure |
| 43 | + name_set: name = a_name |
| 44 | + value_set: value = a_value |
| 45 | + max_age_set: max_age < 0 |
| 46 | + end |
| 47 | + |
| 48 | +feature -- Access |
| 49 | + |
| 50 | + name: STRING_8 |
| 51 | + -- name of the cookie. |
| 52 | + |
| 53 | + value: STRING_8 |
| 54 | + -- value of the cookie. |
| 55 | + |
| 56 | + expiration: detachable STRING_8 |
| 57 | + -- Value of the Expires attribute. |
| 58 | + |
| 59 | + path: detachable STRING_8 |
| 60 | + -- Value of the Path attribute. |
| 61 | + -- Path to which the cookie applies. |
| 62 | + --| The path "/", specify a cookie that apply to all URLs in your site. |
| 63 | + |
| 64 | + domain: detachable STRING_8 |
| 65 | + -- Value of the Domain attribute. |
| 66 | + -- Domain to which the cookies apply. |
| 67 | + |
| 68 | + secure: BOOLEAN |
| 69 | + -- Value of the Secure attribute. |
| 70 | + -- By default False. |
| 71 | + --| Indicate if the cookie should only be sent over secured(encrypted connections, for example SSL). |
| 72 | + |
| 73 | + http_only: BOOLEAN |
| 74 | + -- Value of the http_only attribute. |
| 75 | + -- By default false. |
| 76 | + --| Limits the scope of the cookie to HTTP requests. |
| 77 | + |
| 78 | + max_age: INTEGER |
| 79 | + -- Value of the Max-Age attribute. |
| 80 | + --| How much time in seconds should elapsed before the cookie expires. |
| 81 | + --| By default max_age < 0 indicate a cookie will last only for the current user-agent (Browser, etc) session. |
| 82 | + --| A value of 0 instructs the user-agent to delete the cookie. |
| 83 | + |
| 84 | + has_valid_characters (a_name: READABLE_STRING_8):BOOLEAN |
| 85 | + -- Has `a_name' valid characters for cookies? |
| 86 | + local |
| 87 | + l_iterator: STRING_ITERATION_CURSOR |
| 88 | + l_found: BOOLEAN |
| 89 | + do |
| 90 | + create l_iterator.make (a_name) |
| 91 | + Result := True |
| 92 | + across |
| 93 | + l_iterator as ic |
| 94 | + until |
| 95 | + l_found |
| 96 | + loop |
| 97 | + if not is_valid_character (ic.item.natural_32_code) then |
| 98 | + Result := False |
| 99 | + l_found := True |
| 100 | + end |
| 101 | + end |
| 102 | + end |
| 103 | + |
| 104 | + include_max_age: BOOLEAN |
| 105 | + -- Does the Set-Cookie header include Max-Age attribute? |
| 106 | + --|By default will include both. |
| 107 | + |
| 108 | + include_expires: BOOLEAN |
| 109 | + -- Does the Set-Cookie header include Expires attribute? |
| 110 | + --|By default will include both. |
| 111 | + |
| 112 | + |
| 113 | + is_valid_rfc1123_date (a_string: READABLE_STRING_8): BOOLEAN |
| 114 | + -- Is the date represented by `a_string' a valid rfc1123 date? |
| 115 | + local |
| 116 | + d: HTTP_DATE |
| 117 | + do |
| 118 | + create d.make_from_string (a_string) |
| 119 | + Result := not d.has_error and then d.rfc1123_string.same_string (a_string) |
| 120 | + end |
| 121 | + |
| 122 | +feature -- Change Element |
| 123 | + |
| 124 | + set_name (a_name: READABLE_STRING_8) |
| 125 | + -- Set `name' with `a_name'. |
| 126 | + require |
| 127 | + a_name_not_blank: a_name /= Void and then not a_name.is_whitespace |
| 128 | + a_name_has_valid_characters: a_name /= Void and then has_valid_characters (a_name) |
| 129 | + do |
| 130 | + name := a_name |
| 131 | + ensure |
| 132 | + name_set: name = a_name |
| 133 | + end |
| 134 | + |
| 135 | + set_value (a_value: READABLE_STRING_8) |
| 136 | + -- Set `value' with `a_value'. |
| 137 | + require |
| 138 | + a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) |
| 139 | + do |
| 140 | + value := a_value |
| 141 | + ensure |
| 142 | + value_set: value = a_value |
| 143 | + end |
| 144 | + |
| 145 | + set_expiration (a_date: READABLE_STRING_8) |
| 146 | + -- Set `expiration' with `a_date' |
| 147 | + require |
| 148 | + rfc1133_date: a_date /= Void and then is_valid_rfc1123_date (a_date) |
| 149 | + do |
| 150 | + expiration := a_date |
| 151 | + ensure |
| 152 | + expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date) |
| 153 | + end |
| 154 | + |
| 155 | + set_expiration_date (a_date: DATE_TIME) |
| 156 | + -- Set `expiration' with `a_date' |
| 157 | + do |
| 158 | + set_expiration (date_to_rfc1123_http_date_format (a_date)) |
| 159 | + ensure |
| 160 | + expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date)) |
| 161 | + end |
| 162 | + |
| 163 | + set_path (a_path: READABLE_STRING_8) |
| 164 | + -- Set `path' with `a_path' |
| 165 | + do |
| 166 | + path := a_path |
| 167 | + ensure |
| 168 | + path_set: path = a_path |
| 169 | + end |
| 170 | + |
| 171 | + set_domain (a_domain: READABLE_STRING_8) |
| 172 | + -- Set `domain' with `a_domain' |
| 173 | + -- Note: you should avoid using "localhost" as `domain' for local cookies |
| 174 | + -- since they are not always handled by browser (for instance Chrome) |
| 175 | + require |
| 176 | + domain_without_port_info: a_domain /= Void implies not a_domain.has (':') |
| 177 | + do |
| 178 | + domain := a_domain |
| 179 | + ensure |
| 180 | + domain_set: domain = a_domain |
| 181 | + end |
| 182 | + |
| 183 | + set_secure (a_secure: BOOLEAN) |
| 184 | + -- Set `secure' with `a_secure' |
| 185 | + do |
| 186 | + secure := a_secure |
| 187 | + ensure |
| 188 | + secure_set: secure = a_secure |
| 189 | + end |
| 190 | + |
| 191 | + set_http_only (a_http_only: BOOLEAN) |
| 192 | + -- Set `http_only' with `a_http_only' |
| 193 | + do |
| 194 | + http_only := a_http_only |
| 195 | + ensure |
| 196 | + http_only_set: http_only = a_http_only |
| 197 | + end |
| 198 | + |
| 199 | + set_max_age (a_max_age: INTEGER) |
| 200 | + -- Set `max_age' with `a_max_age' |
| 201 | + do |
| 202 | + max_age := a_max_age |
| 203 | + ensure |
| 204 | + max_age_set: max_age = a_max_age |
| 205 | + end |
| 206 | + |
| 207 | + |
| 208 | + mark_max_age |
| 209 | + -- Set `include_max_age' to True. |
| 210 | + -- Set `include_expires' to False. |
| 211 | + -- Set-Cookie will include only Max-Age attribute and not Expires. |
| 212 | + do |
| 213 | + include_max_age := True |
| 214 | + include_expires := False |
| 215 | + ensure |
| 216 | + max_age_true: include_max_age |
| 217 | + expire_false: not include_expires |
| 218 | + end |
| 219 | + |
| 220 | + mark_expires |
| 221 | + -- Set `include_expires' to True. |
| 222 | + -- Set `include_max_age' to False |
| 223 | + -- Set-Cookie will include only Expires attribute and not Max_Age. |
| 224 | + do |
| 225 | + include_expires := True |
| 226 | + include_max_age := False |
| 227 | + ensure |
| 228 | + expires_true: include_expires |
| 229 | + max_age_false: not include_max_age |
| 230 | + end |
| 231 | + |
| 232 | + set_default_expires_max_age |
| 233 | + -- Set `include_expires' to False. |
| 234 | + -- Set `include_max_age' to False |
| 235 | + -- Set-Cookie will include both Max-Age, Expires attributes. |
| 236 | + do |
| 237 | + include_expires := False |
| 238 | + include_max_age := False |
| 239 | + ensure |
| 240 | + expires_false: not include_expires |
| 241 | + max_age_false: not include_max_age |
| 242 | + end |
| 243 | + |
| 244 | +feature {NONE} -- Date Utils |
| 245 | + |
| 246 | + date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8 |
| 247 | + -- String representation of `dt' using the RFC 1123 |
| 248 | + local |
| 249 | + d: HTTP_DATE |
| 250 | + do |
| 251 | + create d.make_from_date_time (dt) |
| 252 | + Result := d.string |
| 253 | + end |
| 254 | + |
| 255 | +feature -- Output |
| 256 | + |
| 257 | + header_line: STRING |
| 258 | + -- String representation of Set-Cookie header line of Current. |
| 259 | + local |
| 260 | + s: STRING |
| 261 | + do |
| 262 | + s := {HTTP_HEADER_NAMES}.header_set_cookie + colon_space + name + "=" + value |
| 263 | + if |
| 264 | + attached domain as l_domain and then not l_domain.same_string ("localhost") |
| 265 | + then |
| 266 | + s.append ("; Domain=") |
| 267 | + s.append (l_domain) |
| 268 | + end |
| 269 | + if attached path as l_path then |
| 270 | + s.append ("; Path=") |
| 271 | + s.append (l_path) |
| 272 | + end |
| 273 | + -- Expire |
| 274 | + if include_expires then |
| 275 | + if attached expiration as l_expires then |
| 276 | + s.append ("; Expires=") |
| 277 | + s.append (l_expires) |
| 278 | + end |
| 279 | + -- Max-Age |
| 280 | + elseif include_max_age then |
| 281 | + s.append ("; Max-Age=") |
| 282 | + s.append_integer (max_age) |
| 283 | + else |
| 284 | + -- Default |
| 285 | + check |
| 286 | + -- By default the attributes include_expires and include_max_age are False. |
| 287 | + -- Meaning that Expires and Max-Age headers are included in the response. |
| 288 | + default: (not include_expires) and (not include_max_age) |
| 289 | + end |
| 290 | + if attached expiration as l_expires then |
| 291 | + s.append ("; Expires=") |
| 292 | + s.append (l_expires) |
| 293 | + end |
| 294 | + |
| 295 | + s.append ("; Max-Age=") |
| 296 | + s.append_integer (max_age) |
| 297 | + end |
| 298 | + |
| 299 | + if secure then |
| 300 | + s.append ("; Secure") |
| 301 | + end |
| 302 | + if http_only then |
| 303 | + s.append ("; HttpOnly") |
| 304 | + end |
| 305 | + Result := s |
| 306 | + end |
| 307 | + |
| 308 | +feature {NONE} -- Constants |
| 309 | + |
| 310 | + |
| 311 | + colon_space: IMMUTABLE_STRING_8 |
| 312 | + once |
| 313 | + create Result.make_from_string (": ") |
| 314 | + end |
| 315 | + |
| 316 | + |
| 317 | + is_valid_character (c: NATURAL_32): BOOLEAN |
| 318 | + -- RFC6265 that specifies that the following is valid for characters in cookies. |
| 319 | + -- The following character ranges are valid:http://tools.ietf.org/html/rfc6265#section-4.1.1 |
| 320 | + -- %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E |
| 321 | + -- 0x21: ! |
| 322 | + -- 0x23-2B: #$%&'()*+ |
| 323 | + -- 0x2D-3A: -./0123456789: |
| 324 | + -- 0x3C-5B: <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[ |
| 325 | + -- 0x5D-7E: ]^_`abcdefghijklmnopqrstuvwxyz{|}~ |
| 326 | + note |
| 327 | + EIS: "name=valid-characters", "src=http://tools.ietf.org/html/rfc6265#section-4.1.1", "protocol=uri" |
| 328 | + do |
| 329 | + Result := True |
| 330 | + inspect c |
| 331 | + when 0x21 then |
| 332 | + when 0x23 .. 0x2B then |
| 333 | + when 0x2D .. 0x3A then |
| 334 | + when 0x3C .. 0x5B then |
| 335 | + when 0x5D .. 0x7E then |
| 336 | + else |
| 337 | + Result := False |
| 338 | + end |
| 339 | + end |
| 340 | + |
| 341 | +note |
| 342 | + copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others" |
| 343 | + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" |
| 344 | + source: "[ |
| 345 | + Eiffel Software |
| 346 | + 5949 Hollister Ave., Goleta, CA 93117 USA |
| 347 | + Telephone 805-685-1006, Fax 805-685-6869 |
| 348 | + Website http://www.eiffel.com |
| 349 | + Customer support http://support.eiffel.com |
| 350 | + ]" |
| 351 | +end |
0 commit comments