From 871e9792a57c4e8fc5de07a1687f91162a9015e4 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Fri, 13 Mar 2015 15:28:38 -0300 Subject: [PATCH 1/6] Added HTTP_COOKIE and test cases. Added WSF_COOKIE class, inherit from HTTP_COOKIE. --- .../network/protocol/http/src/http_cookie.e | 320 ++++++++++++++++++ .../http/tests/http_cookie_test_set.e | 130 +++++++ library/server/wsf/src/support/wsf_cookie.e | 25 ++ 3 files changed, 475 insertions(+) create mode 100644 library/network/protocol/http/src/http_cookie.e create mode 100644 library/network/protocol/http/tests/http_cookie_test_set.e create mode 100644 library/server/wsf/src/support/wsf_cookie.e diff --git a/library/network/protocol/http/src/http_cookie.e b/library/network/protocol/http/src/http_cookie.e new file mode 100644 index 00000000..4dddd775 --- /dev/null +++ b/library/network/protocol/http/src/http_cookie.e @@ -0,0 +1,320 @@ +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 RCF6265. + + 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=https://httpwg.github.io/specs/rfc6265.html", "protocol=uri" +class + HTTP_COOKIE + +create + make + +feature {NONE} -- Initialization + + make (a_name: READABLE_STRING_32; a_value: READABLE_STRING_32) + -- Create an object instance of cookie with name `a_name' and value `a_value'. + require + make_sense: (a_name /= Void and a_value /= Void) and then (not a_name.is_empty and not a_value.is_empty) + do + set_name (a_name) + set_value(a_value) + set_max_age (-1) + ensure + name_set: name = a_name + value_set: value = a_value + max_age_set: max_age < 0 + end + +feature -- Access + + name: STRING_32 + -- name of the cookie. + + value: STRING_32 + -- value of the cookie. + + expiration: detachable STRING + -- Value of the Expires attribute. + + path: detachable STRING_32 + -- 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 STRING_32 + -- Value of the Domain attribute. + -- Domain to which the cookies apply. + + secure: BOOLEAN + -- Value of the Secure attribute. + -- By default False. + --| Idicate 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 cooki 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_32):BOOLEAN + -- Has `a_name' valid characters for cookies? + local + l_iterator: STRING_ITERATION_CURSOR + l_found: BOOLEAN + do + create l_iterator.make (a_name) + from + l_iterator.start + until + l_iterator.after or l_found + loop + if valid_characters.index_of (l_iterator.item.to_character_8, 0) = -1 then + Result := False + l_found := True + else + l_iterator.forth + end + end + end + + include_max_age: BOOLEAN + -- Does the Set-Cookie header will include Max-Age attribute? + --|By default will include both. + + include_expires: BOOLEAN + -- Does the Set-Cookie header will include Expires attribute? + --|By default will include both. + +feature -- Change Element + + set_name (a_name: READABLE_STRING_32) + -- Set `name' with `a_name'. + do + name := a_name + ensure + name_set: name = a_name + end + + set_value (a_value: READABLE_STRING_32) + -- Set `value' with `a_value'. + do + value := a_value + ensure + value_set: value = a_value + end + + set_expiration (a_date: READABLE_STRING_32) + -- Set `expiration' with `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' with `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_path (a_path: READABLE_STRING_32) + -- Set `path' with `a_path' + do + path := a_path + ensure + path_set: path = a_path + end + + set_domain (a_domain: READABLE_STRING_32) + -- Set `domain' with `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 a_domain.index_of (':', 1) = 0 + do + domain := a_domain + ensure + domain_set: domain = a_domain + end + + set_secure (a_secure: BOOLEAN) + -- Set `secure' with `a_secure' + do + secure := a_secure + ensure + secure_set: secure = a_secure + end + + set_http_only (a_http_only: BOOLEAN) + -- Set `http_only' with `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' with `a_max_age' + do + max_age := a_max_age + ensure + max_age_set: max_age = a_max_age + end + + + mark_max_age + -- Set `include_max_age' to True. + -- Set `include_expires' to False. + -- Set-Cookie will include only Max-Age attribute and not Expires. + do + include_max_age := True + include_expires := False + ensure + max_age_true: include_max_age + expire_false: not include_expires + end + + mark_expires + -- Set `include_expires' to True. + -- Set `include_max_age' to False + -- Set-Cookie will include only Expires attribute and not Max_Age. + do + include_expires := True + include_max_age := False + ensure + expires_true: include_expires + max_age_false: not include_max_age + end + + set_default_expires_max_age + -- Set `include_expires' to False. + -- Set `include_max_age' to False + -- Set-Cookie will include both Max-Age, Expires attributes. + do + include_expires := False + include_max_age := False + ensure + expires_false: not include_expires + max_age_false: not include_max_age + end + +feature -- 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 + + cookie_header: STRING + -- String representation of Set-Cookie header 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 + -- Expire + if include_expires then + if attached expiration as l_expires then + s.append ("; Expires=") + s.append (l_expires) + end + -- Max-Age + elseif include_max_age then + s.append ("; Max-Age=") + s.append (max_age.out) + else + -- Default + check default: (not include_expires) and (not include_max_age) end + if attached expiration as l_expires then + s.append ("; Expires=") + s.append (l_expires) + end + + s.append ("; Max-Age=") + s.append (max_age.out) + 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 + + + legal_characters, valid_characters: SPECIAL [CHARACTER_8] + -- RFC6265 that specifies that the following is valid for characters in cookies. Cookies are also supposed to be double quoted. + -- 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" + once + Result := ("!#$%%&'()*+-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~").area + end + +note + copyright: "2011-2015, 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 diff --git a/library/network/protocol/http/tests/http_cookie_test_set.e b/library/network/protocol/http/tests/http_cookie_test_set.e new file mode 100644 index 00000000..ab6a8468 --- /dev/null +++ b/library/network/protocol/http/tests/http_cookie_test_set.e @@ -0,0 +1,130 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + HTTP_COOKIE_TEST_SET + +inherit + EQA_TEST_SET + +feature -- Test routines + + test_cookie_expected_header + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1")) + end + + test_cookie_value_with_illegal_characters + -- values (cookie name and value) + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + assert ("Not valid space", not l_cookie.has_valid_characters ("Use! 12")) + assert ("Not valid comma", not l_cookie.has_valid_characters ("Use!,12")) + assert ("Not valid semicolon", not l_cookie.has_valid_characters ("Use!;12")) + assert ("Not valid backslash", not l_cookie.has_valid_characters ("Use!\12")) + assert ("Not valid Dquote", not l_cookie.has_valid_characters ("Use!%"12")) + end + + test_cookie_full_attributes + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + end + + test_cookie_include_expires + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + l_cookie.mark_expires + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Secure; HttpOnly")) + end + + test_cookie_full_include_max_age + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + l_cookie.mark_max_age + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Max-Age=-1; Secure; HttpOnly")) + end + + test_cookie_defaults_and_http_only + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_http_only (True) + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; HttpOnly")) + end + + test_cookie_defaults_and_secure + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_secure (True) + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; Secure")) + end + + + test_cookie_default_and_domain + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Max-Age=-1")) + end + + + test_cookie_default_and_path + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_path ("/") + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Path=/; Max-Age=-1")) + end + + test_cookie_default_and_custom_max_age + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + l_cookie.set_max_age (120) + assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=120")) + end + + +end + + diff --git a/library/server/wsf/src/support/wsf_cookie.e b/library/server/wsf/src/support/wsf_cookie.e new file mode 100644 index 00000000..9a011a86 --- /dev/null +++ b/library/server/wsf/src/support/wsf_cookie.e @@ -0,0 +1,25 @@ +note + description: "This class represents the value of a HTTP cookie, transferred in a request." + date: "$Date$" + revision: "$Revision$" + +class + WSF_COOKIE + +inherit + + HTTP_COOKIE + +create + make +note + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, 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 From 4a35ff7b772a32f0124ce0f055d836a19f0f796c Mon Sep 17 00:00:00 2001 From: jvelilla Date: Fri, 13 Mar 2015 17:41:48 -0300 Subject: [PATCH 2/6] Updated code based on Jocelyn's comments. --- .../network/protocol/http/src/http_cookie.e | 78 ++++++++++++------- .../http/tests/http_cookie_test_set.e | 18 ++--- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/library/network/protocol/http/src/http_cookie.e b/library/network/protocol/http/src/http_cookie.e index 4dddd775..92503db3 100644 --- a/library/network/protocol/http/src/http_cookie.e +++ b/library/network/protocol/http/src/http_cookie.e @@ -3,7 +3,7 @@ note 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 RCF6265. + 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. @@ -20,7 +20,7 @@ note ]" date: "$Date$" revision: "$Revision$" - EIS: "name=HTTP Cookie specification", "src=https://httpwg.github.io/specs/rfc6265.html", "protocol=uri" + EIS: "name=HTTP Cookie specification", "src=http://tools.ietf.org/html/rfc6265", "protocol=uri" class HTTP_COOKIE @@ -29,7 +29,7 @@ create feature {NONE} -- Initialization - make (a_name: READABLE_STRING_32; a_value: READABLE_STRING_32) + 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 make_sense: (a_name /= Void and a_value /= Void) and then (not a_name.is_empty and not a_value.is_empty) @@ -45,21 +45,21 @@ feature {NONE} -- Initialization feature -- Access - name: STRING_32 + name: STRING_8 -- name of the cookie. - value: STRING_32 + value: STRING_8 -- value of the cookie. - expiration: detachable STRING + expiration: detachable STRING_8 -- Value of the Expires attribute. - path: detachable STRING_32 + path: detachable 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 STRING_32 + domain: detachable STRING_8 -- Value of the Domain attribute. -- Domain to which the cookies apply. @@ -79,23 +79,22 @@ feature -- Access --| 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_32):BOOLEAN + has_valid_characters (a_name: READABLE_STRING_GENERAL):BOOLEAN -- Has `a_name' valid characters for cookies? local l_iterator: STRING_ITERATION_CURSOR l_found: BOOLEAN do create l_iterator.make (a_name) - from - l_iterator.start + + across + l_iterator as ic until - l_iterator.after or l_found + l_found loop - if valid_characters.index_of (l_iterator.item.to_character_8, 0) = -1 then + if is_valid_character (ic.item.to_character_8) then Result := False l_found := True - else - l_iterator.forth end end end @@ -110,28 +109,28 @@ feature -- Access feature -- Change Element - set_name (a_name: READABLE_STRING_32) + set_name (a_name: READABLE_STRING_GENERAL) -- Set `name' with `a_name'. do - name := a_name + name := a_name.as_string_8 ensure name_set: name = a_name end - set_value (a_value: READABLE_STRING_32) + set_value (a_value: READABLE_STRING_GENERAL) -- Set `value' with `a_value'. do - value := a_value + value := a_value.as_string_8 ensure value_set: value = a_value end - set_expiration (a_date: READABLE_STRING_32) + set_expiration (a_date: READABLE_STRING_GENERAL) -- Set `expiration' with `a_date' do - expiration := a_date + expiration := a_date.as_string_32 ensure - expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date) + expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date.as_string_8) end set_expiration_date (a_date: DATE_TIME) @@ -142,22 +141,22 @@ feature -- Change Element expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date)) end - set_path (a_path: READABLE_STRING_32) + set_path (a_path: READABLE_STRING_GENERAL) -- Set `path' with `a_path' do - path := a_path + path := a_path.as_string_8 ensure path_set: path = a_path end - set_domain (a_domain: READABLE_STRING_32) + set_domain (a_domain: READABLE_STRING_GENERAL) -- Set `domain' with `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 a_domain.index_of (':', 1) = 0 do - domain := a_domain + domain := a_domain.as_string_8 ensure domain_set: domain = a_domain end @@ -236,7 +235,7 @@ feature -- Date Utils feature -- Output - cookie_header: STRING + header_line: STRING -- String representation of Set-Cookie header of current. local s: STRING @@ -307,6 +306,31 @@ feature {NONE} -- Constants Result := ("!#$%%&'()*+-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~").area end + + is_valid_character (c: CHARACTER): BOOLEAN + -- RFC6265 that specifies that the following is valid for characters in cookies. Cookies are also supposed to be double quoted. + -- 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.natural_32_code + 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-2015, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/network/protocol/http/tests/http_cookie_test_set.e b/library/network/protocol/http/tests/http_cookie_test_set.e index ab6a8468..55fb1f36 100644 --- a/library/network/protocol/http/tests/http_cookie_test_set.e +++ b/library/network/protocol/http/tests/http_cookie_test_set.e @@ -20,7 +20,7 @@ feature -- Test routines l_cookie: HTTP_COOKIE do create l_cookie.make ("user_id", "u12345") - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1")) end test_cookie_value_with_illegal_characters @@ -46,7 +46,7 @@ feature -- Test routines l_cookie.set_path ("/") l_cookie.set_secure (True) l_cookie.set_http_only (True) - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) end test_cookie_include_expires @@ -60,7 +60,7 @@ feature -- Test routines l_cookie.set_secure (True) l_cookie.set_http_only (True) l_cookie.mark_expires - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Secure; HttpOnly")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Secure; HttpOnly")) end test_cookie_full_include_max_age @@ -74,7 +74,7 @@ feature -- Test routines l_cookie.set_secure (True) l_cookie.set_http_only (True) l_cookie.mark_max_age - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Max-Age=-1; Secure; HttpOnly")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Max-Age=-1; Secure; HttpOnly")) end test_cookie_defaults_and_http_only @@ -83,7 +83,7 @@ feature -- Test routines do create l_cookie.make ("user_id", "u12345") l_cookie.set_http_only (True) - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; HttpOnly")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; HttpOnly")) end test_cookie_defaults_and_secure @@ -92,7 +92,7 @@ feature -- Test routines do create l_cookie.make ("user_id", "u12345") l_cookie.set_secure (True) - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; Secure")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Max-Age=-1; Secure")) end @@ -102,7 +102,7 @@ feature -- Test routines do create l_cookie.make ("user_id", "u12345") l_cookie.set_domain ("www.example.com") - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Max-Age=-1")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Max-Age=-1")) end @@ -112,7 +112,7 @@ feature -- Test routines do create l_cookie.make ("user_id", "u12345") l_cookie.set_path ("/") - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Path=/; Max-Age=-1")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Path=/; Max-Age=-1")) end test_cookie_default_and_custom_max_age @@ -121,7 +121,7 @@ feature -- Test routines do create l_cookie.make ("user_id", "u12345") l_cookie.set_max_age (120) - assert("Expected", l_cookie.cookie_header.same_string ("Set-Cookie: user_id=u12345; Max-Age=120")) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Max-Age=120")) end From c4d362ff313d2e788b907d4f0bd31a08b4c7e0ff Mon Sep 17 00:00:00 2001 From: jvelilla Date: Tue, 17 Mar 2015 14:27:53 -0300 Subject: [PATCH 3/6] Added the add_cookie feature Added test cases to check cookies in WSF_RESPONSE- Added mock classes use for test cases. --- library/server/wsf/src/wsf_response.e | 33 +++- .../tests/src/test_wsf_response_test_suite.e | 147 ++++++++++++++++++ .../server/wsf/tests/src/wgi_response_null.e | 132 ++++++++++++++++ .../server/wsf/tests/src/wsf_service_null.e | 23 +++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 library/server/wsf/tests/src/test_wsf_response_test_suite.e create mode 100644 library/server/wsf/tests/src/wgi_response_null.e create mode 100644 library/server/wsf/tests/src/wsf_service_null.e diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index af0d373e..ce6f9ea8 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -316,6 +316,37 @@ feature -- Header output operation: helpers end end +feature -- Header add cookie + + add_cookie (a_cookie: WSF_COOKIE) + -- Add a Set-Cookie header field to the response, iff there is not exist + -- a Set-Cookie header field with the same cookie-name. + --| Servers SHOULD NOT include more than one Set-Cookie header field in + --| the same response with the same cookie-name. + local + l_same_cookie_name: BOOLEAN + l_cookie_header: STRING + l_cn: STRING + l_nv: STRING + do + across internal_header.headers as ic until l_same_cookie_name loop + if ic.item.starts_with ("Set-Cookie") then + l_cookie_header := ic.item.twin + l_cookie_header.to_lower + l_cn := a_cookie.name + l_cn.to_lower + l_nv := l_cookie_header.split (';').at (1).split (':').at (2) + l_nv.adjust + if l_nv.starts_with (l_cn) then + l_same_cookie_name := True + end + end + end + if not l_same_cookie_name then + internal_header.add_header (a_cookie.header_line) + end + end + feature -- Output report transfered_content_length: NATURAL_64 @@ -520,7 +551,7 @@ feature -- Error reporting end note - copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others" + copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/server/wsf/tests/src/test_wsf_response_test_suite.e b/library/server/wsf/tests/src/test_wsf_response_test_suite.e new file mode 100644 index 00000000..2f6c0771 --- /dev/null +++ b/library/server/wsf/tests/src/test_wsf_response_test_suite.e @@ -0,0 +1,147 @@ +note + description: "Summary description for {TEST_WSF_RESPONSE_TEST_SUITE}." + date: "$Date$" + revision: "$Revision$" + +class + TEST_WSF_RESPONSE_TEST_SUITE + +inherit + WSF_TO_WGI_SERVICE + rename + default_create as df_wgi, + execute as execute_wgi + end + EQA_TEST_SET + redefine + on_prepare + select + default_create + end + +feature {NONE} -- Events + + on_prepare + do + make_from_service (create {WSF_SERVICE_NULL}) + end + +feature -- Test Cases + + test_add_single_cookie + local + w_res: WSF_RESPONSE + l_cookie: WSF_COOKIE + l_header: WSF_HEADER + l_res: WGI_RESPONSE_NULL + do + create {WGI_RESPONSE_NULL} l_res.make + create w_res.make_from_wgi (l_res) + + create l_header.make + l_header.put_content_type_text_html + + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + + w_res.put_header_text (l_header.string) + w_res.add_cookie (l_cookie) + w_res.set_status_code ({HTTP_STATUS_CODE}.ok) + w_res.put_string ("Test") + assert ("Expected", l_res.output.same_string("200 %R%NContent-Type: text/html%R%NSet-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly%R%N%R%NTest") ) + end + + + test_add_multiple_cookiewith_similar_cookie_name + local + w_res: WSF_RESPONSE + l_cookie: WSF_COOKIE + l_header: WSF_HEADER + l_res: WGI_RESPONSE_NULL + do + create {WGI_RESPONSE_NULL} l_res.make + create w_res.make_from_wgi (l_res) + + create l_header.make + l_header.put_content_type_text_html + + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + w_res.put_header_text (l_header.string) + w_res.add_cookie (l_cookie) + + + create l_cookie.make ("user_id", "newUser") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=newUser; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + + w_res.add_cookie (l_cookie) -- Ignored + w_res.set_status_code ({HTTP_STATUS_CODE}.ok) + w_res.put_string ("Test") + assert ("Expected", l_res.output.same_string("200 %R%NContent-Type: text/html%R%NSet-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly%R%N%R%NTest") ) + end + + + test_add_multiple_cookie_with_similar_cookie_name_2 + local + w_res: WSF_RESPONSE + l_cookie: WSF_COOKIE + l_header: WSF_HEADER + l_res: WGI_RESPONSE_NULL + do + create {WGI_RESPONSE_NULL} l_res.make + create w_res.make_from_wgi (l_res) + + create l_header.make + l_header.put_content_type_text_html + w_res.put_header_text (l_header.string) + + create l_cookie.make ("user_id", "u12345") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + + + w_res.add_cookie (l_cookie) + create l_cookie.make ("user_id", "newUser") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=newUser; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + + w_res.add_cookie (l_cookie) -- Ignored + + + create l_cookie.make ("ewf_sessionid", "test") + l_cookie.set_domain ("www.example.com") + l_cookie.set_expiration ("Sat, 18 Apr 2015 21:22:05 GMT") + l_cookie.set_path ("/") + l_cookie.set_secure (True) + l_cookie.set_http_only (True) + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: ewf_sessionid=test; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly")) + + w_res.add_cookie (l_cookie) + w_res.set_status_code ({HTTP_STATUS_CODE}.ok) + w_res.put_string ("Test") + assert ("Expected", l_res.output.same_string("200 %R%NContent-Type: text/html%R%NSet-Cookie: user_id=u12345; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly%R%NSet-Cookie: ewf_sessionid=test; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly%R%N%R%NTest") ) + end +end diff --git a/library/server/wsf/tests/src/wgi_response_null.e b/library/server/wsf/tests/src/wgi_response_null.e new file mode 100644 index 00000000..8bf6795c --- /dev/null +++ b/library/server/wsf/tests/src/wgi_response_null.e @@ -0,0 +1,132 @@ +note + description: "Summary description for {WGI_RESPONSE_NULL}." + date: "$Date$" + revision: "$Revision$" + +class + WGI_RESPONSE_NULL + +inherit + + WGI_RESPONSE + +create + make + +feature {NONE} -- Initialization + + make + do + create output.make_empty + create error.make_empty + end + +feature {WGI_CONNECTOR, WGI_SERVICE} -- Commit + + commit + -- Commit the current response + do + end + +feature -- Status report + + status_committed: BOOLEAN + -- Status code set and committed? + + header_committed: BOOLEAN + -- Header committed? + + message_committed: BOOLEAN + -- Message committed? + + message_writable: BOOLEAN + -- Can message be written? + do + Result := status_is_set and header_committed + end + +feature -- Status setting + + status_is_set: BOOLEAN + -- Is status set? + do + Result := status_code > 0 + end + + set_status_code (a_code: INTEGER; a_reason_phrase: detachable READABLE_STRING_8) + -- Set response status code + -- Should be done before sending any data back to the client + do + status_code := a_code + status_reason_phrase := a_reason_phrase + if attached a_reason_phrase as l_rp then + output.prepend (l_rp) + end + output.prepend (" ") + output.prepend (a_code.out) + output.append ("%R%N") + status_committed := True + end + + status_code: INTEGER + -- Response status + + status_reason_phrase: detachable READABLE_STRING_8 + -- Custom status reason phrase for the Response (optional) + +feature -- Header output operation + + put_header_text (a_text: READABLE_STRING_8) + do + output.append (a_text) + output.append ("%R%N") + header_committed := True + end + +feature -- Output operation + + put_character (c: CHARACTER_8) + -- Send the character `c' + do + output.append_character (c) + end + + put_string (s: READABLE_STRING_8) + -- Send the string `s' + do + output.append (s) + end + + put_substring (s: READABLE_STRING_8; start_index, end_index: INTEGER) + -- Send the substring `start_index:end_index]' + --| Could be optimized according to the target output + do + output.append_substring (s, start_index, end_index) + end + + flush + do + output.wipe_out + end + +feature -- Error reporting + + put_error (a_message: READABLE_STRING_8) + -- Report error described by `a_message' + -- This might be used by the underlying connector + do + if attached error as err then + err.append (a_message) + end + end + +feature {EQA_TEST_SET} -- Implementation: Access + + output: STRING + -- Server output channel + + error: detachable STRING + -- Server output channel + + +end diff --git a/library/server/wsf/tests/src/wsf_service_null.e b/library/server/wsf/tests/src/wsf_service_null.e new file mode 100644 index 00000000..c35a7a83 --- /dev/null +++ b/library/server/wsf/tests/src/wsf_service_null.e @@ -0,0 +1,23 @@ +note + description: "Summary description for {WSF_SERVICE_NULL}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + WSF_SERVICE_NULL + +inherit + + WSF_SERVICE + + +feature -- Execute + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the request + -- See `req.input' for input stream + -- `req.meta_variables' for the CGI meta variable + -- and `res' for output buffer + do + end +end From 9dc22bee24aac596f693135a8cf947d85ebf949e Mon Sep 17 00:00:00 2001 From: jvelilla Date: Tue, 17 Mar 2015 17:00:07 -0300 Subject: [PATCH 4/6] Updated HTTP_COOKIE class based on comments. Added missing descriptions in test classes --- .../network/protocol/http/src/http_cookie.e | 109 +++++++++--------- library/server/wsf/src/wsf_response.e | 15 +-- .../server/wsf/tests/src/wgi_response_null.e | 6 +- .../server/wsf/tests/src/wsf_service_null.e | 7 +- 4 files changed, 69 insertions(+), 68 deletions(-) diff --git a/library/network/protocol/http/src/http_cookie.e b/library/network/protocol/http/src/http_cookie.e index 92503db3..941a47c5 100644 --- a/library/network/protocol/http/src/http_cookie.e +++ b/library/network/protocol/http/src/http_cookie.e @@ -1,21 +1,21 @@ note description: "[ - This class represents the value of a HTTP cookie, transferred in a request. - The class has features to build an HTTP cookie. + 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 + 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. + 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 + 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. + 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$" @@ -32,7 +32,10 @@ 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 - make_sense: (a_name /= Void and a_value /= Void) and then (not a_name.is_empty and not a_value.is_empty) + a_name_not_blank: a_name /= Void and then not a_name.is_whitespace + a_value_not_empty: a_value /= Void and then not a_value.is_empty + 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) @@ -66,33 +69,33 @@ feature -- Access secure: BOOLEAN -- Value of the Secure attribute. -- By default False. - --| Idicate if the cookie should only be sent over secured(encrypted connections, for example SSL). + --| 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. + --| 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 cooki expires. + --| 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_GENERAL):BOOLEAN + 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 is_valid_character (ic.item.to_character_8) then + if not is_valid_character (ic.item.to_character_8) then Result := False l_found := True end @@ -100,37 +103,43 @@ feature -- Access end include_max_age: BOOLEAN - -- Does the Set-Cookie header will include Max-Age attribute? + -- Does the Set-Cookie header include Max-Age attribute? --|By default will include both. include_expires: BOOLEAN - -- Does the Set-Cookie header will include Expires attribute? + -- Does the Set-Cookie header include Expires attribute? --|By default will include both. feature -- Change Element - set_name (a_name: READABLE_STRING_GENERAL) + set_name (a_name: READABLE_STRING_8) -- Set `name' with `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.as_string_8 + name := a_name ensure name_set: name = a_name end - set_value (a_value: READABLE_STRING_GENERAL) + set_value (a_value: READABLE_STRING_8) -- Set `value' with `a_value'. + require + a_value_not_empty: a_value /= Void and then not a_value.is_empty + a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) do - value := a_value.as_string_8 + value := a_value ensure value_set: value = a_value end - set_expiration (a_date: READABLE_STRING_GENERAL) + set_expiration (a_date: READABLE_STRING_8) -- Set `expiration' with `a_date' do - expiration := a_date.as_string_32 + expiration := a_date ensure - expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date.as_string_8) + expiration_set: attached expiration as l_expiration and then l_expiration.same_string (a_date) end set_expiration_date (a_date: DATE_TIME) @@ -141,22 +150,22 @@ feature -- Change Element expiration_set: attached expiration as l_expiration and then l_expiration.same_string (date_to_rfc1123_http_date_format (a_date)) end - set_path (a_path: READABLE_STRING_GENERAL) + set_path (a_path: READABLE_STRING_8) -- Set `path' with `a_path' do - path := a_path.as_string_8 + path := a_path ensure path_set: path = a_path end - set_domain (a_domain: READABLE_STRING_GENERAL) + set_domain (a_domain: READABLE_STRING_8) -- Set `domain' with `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 a_domain.index_of (':', 1) = 0 do - domain := a_domain.as_string_8 + domain := a_domain ensure domain_set: domain = a_domain end @@ -218,11 +227,11 @@ feature -- Change Element include_expires := False include_max_age := False ensure - expires_false: not include_expires - max_age_false: not include_max_age + expires_false: not include_expires + max_age_false: not include_max_age end -feature -- Date Utils +feature {NONE} -- Date Utils date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8 -- String representation of `dt' using the RFC 1123 @@ -236,7 +245,7 @@ feature -- Date Utils feature -- Output header_line: STRING - -- String representation of Set-Cookie header of current. + -- String representation of Set-Cookie header line of Current. local s: STRING do @@ -260,17 +269,21 @@ feature -- Output -- Max-Age elseif include_max_age then s.append ("; Max-Age=") - s.append (max_age.out) + s.append_integer (max_age) else -- Default - check default: (not include_expires) and (not include_max_age) end + check + -- By default the attributes include_expires and include_max_age are False. + -- Meaning that Expires and Max-Age headers are included in the response. + default: (not include_expires) and (not include_max_age) + end if attached expiration as l_expires then s.append ("; Expires=") s.append (l_expires) end s.append ("; Max-Age=") - s.append (max_age.out) + s.append_integer (max_age) end if secure then @@ -291,24 +304,8 @@ feature {NONE} -- Constants end - legal_characters, valid_characters: SPECIAL [CHARACTER_8] - -- RFC6265 that specifies that the following is valid for characters in cookies. Cookies are also supposed to be double quoted. - -- 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" - once - Result := ("!#$%%&'()*+-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~").area - end - - is_valid_character (c: CHARACTER): BOOLEAN - -- RFC6265 that specifies that the following is valid for characters in cookies. Cookies are also supposed to be double quoted. + -- 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: ! diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index ce6f9ea8..3dac3fc3 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -325,19 +325,16 @@ feature -- Header add cookie --| the same response with the same cookie-name. local l_same_cookie_name: BOOLEAN - l_cookie_header: STRING - l_cn: STRING l_nv: STRING do - across internal_header.headers as ic until l_same_cookie_name loop + across + internal_header.headers as ic + until l_same_cookie_name + loop if ic.item.starts_with ("Set-Cookie") then - l_cookie_header := ic.item.twin - l_cookie_header.to_lower - l_cn := a_cookie.name - l_cn.to_lower - l_nv := l_cookie_header.split (';').at (1).split (':').at (2) + l_nv := ic.item.split (';').at (1).split (':').at (2) l_nv.adjust - if l_nv.starts_with (l_cn) then + if l_nv.starts_with (a_cookie.name) then l_same_cookie_name := True end end diff --git a/library/server/wsf/tests/src/wgi_response_null.e b/library/server/wsf/tests/src/wgi_response_null.e index 8bf6795c..035413c1 100644 --- a/library/server/wsf/tests/src/wgi_response_null.e +++ b/library/server/wsf/tests/src/wgi_response_null.e @@ -1,5 +1,9 @@ note - description: "Summary description for {WGI_RESPONSE_NULL}." + description: "[ + Mock implementation of the WGI_RESPONSE interface. + + Used for testing the ewf core and also web applications. + ]" date: "$Date$" revision: "$Revision$" diff --git a/library/server/wsf/tests/src/wsf_service_null.e b/library/server/wsf/tests/src/wsf_service_null.e index c35a7a83..4f6cf001 100644 --- a/library/server/wsf/tests/src/wsf_service_null.e +++ b/library/server/wsf/tests/src/wsf_service_null.e @@ -1,6 +1,9 @@ note - description: "Summary description for {WSF_SERVICE_NULL}." - author: "" + description: "[ + Mock implementation of the WGI_SERVICE interface. + + Used for testing the ewf core and also web applications + ]" date: "$Date$" revision: "$Revision$" From 08db0748f4b9eb68e716258cdb76d009227a6622 Mon Sep 17 00:00:00 2001 From: jvelilla Date: Thu, 19 Mar 2015 09:39:41 -0300 Subject: [PATCH 5/6] Updated is_valid_character, using NATURAL_32 as an argument to avoid multiple conversions. Updated add_cookie, added features has_cookie_name and is_cookie line to avoid the use of STRING.split and STRING.start_with. --- .../network/protocol/http/src/http_cookie.e | 6 +-- library/server/wsf/src/wsf_response.e | 48 ++++++++++++++++--- .../server/wsf/tests/src/wgi_response_null.e | 2 +- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/library/network/protocol/http/src/http_cookie.e b/library/network/protocol/http/src/http_cookie.e index 941a47c5..a896e3b2 100644 --- a/library/network/protocol/http/src/http_cookie.e +++ b/library/network/protocol/http/src/http_cookie.e @@ -95,7 +95,7 @@ feature -- Access until l_found loop - if not is_valid_character (ic.item.to_character_8) then + if not is_valid_character (ic.item.natural_32_code) then Result := False l_found := True end @@ -304,7 +304,7 @@ feature {NONE} -- Constants end - is_valid_character (c: CHARACTER): BOOLEAN + 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 @@ -317,7 +317,7 @@ feature {NONE} -- Constants EIS: "name=valid-characters", "src=http://tools.ietf.org/html/rfc6265#section-4.1.1", "protocol=uri" do Result := True - inspect c.natural_32_code + inspect c when 0x21 then when 0x23 .. 0x2B then when 0x2D .. 0x3A then diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index 3dac3fc3..0a906c20 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -331,12 +331,8 @@ feature -- Header add cookie internal_header.headers as ic until l_same_cookie_name loop - if ic.item.starts_with ("Set-Cookie") then - l_nv := ic.item.split (';').at (1).split (':').at (2) - l_nv.adjust - if l_nv.starts_with (a_cookie.name) then - l_same_cookie_name := True - end + if is_cookie_line (ic.item) then + l_same_cookie_name := has_cookie_name (ic.item, a_cookie.name) end end if not l_same_cookie_name then @@ -547,6 +543,46 @@ feature -- Error reporting wgi_response.put_error (a_message) end +feature {NONE} -- Implemenation + + has_cookie_name (a_cookie_line, a_cookie_name: READABLE_STRING_32 ): BOOLEAN + -- Has the cookie line `a_cookie_line', the cookie name `a_cookie_name'? + local + i,j: INTEGER + do + Result := False + i := a_cookie_line.index_of ('=', 1) + j := a_cookie_line.index_of (':', 1) + + if i > j and j > 0 then + i := i - 1 + j := j + 1 + from until not a_cookie_line[j].is_space loop + j := j + 1 + end + if a_cookie_line.substring (j, i).same_string (a_cookie_name) then + Result := True + end + end + end + + + is_cookie_line (a_line: READABLE_STRING_32): BOOLEAN + -- Is the line `a_line' a cookie line? + --| Set-Cookie: user_id=%"u12;345%"; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly + local + j: INTEGER + do + Result := False + j := a_line.index_of (':', 1) + if j > 0 then + j := j - 1 + if a_line.substring (1, j).same_string ("Set-Cookie") then + Result := True + end + end + end + note copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/server/wsf/tests/src/wgi_response_null.e b/library/server/wsf/tests/src/wgi_response_null.e index 035413c1..95b47c83 100644 --- a/library/server/wsf/tests/src/wgi_response_null.e +++ b/library/server/wsf/tests/src/wgi_response_null.e @@ -124,7 +124,7 @@ feature -- Error reporting end end -feature {EQA_TEST_SET} -- Implementation: Access +feature -- Implementation: Access output: STRING -- Server output channel From 30261632f60f9858015681a8d2dcf106a67a700e Mon Sep 17 00:00:00 2001 From: jvelilla Date: Thu, 19 Mar 2015 15:23:06 -0300 Subject: [PATCH 6/6] Updated HTTP_COOKIE, enable to add a cookie with empty value. Added feature to check if a date is valid rcf1123 is_valid_rfc1123_date. Added test cases related to valid cookie dates. Updated wsf_response add_cookie basedo on review comments. --- .../network/protocol/http/src/http_cookie.e | 16 +++++++++--- .../http/tests/http_cookie_test_set.e | 25 +++++++++++++++++++ library/server/wsf/src/wsf_response.e | 21 ++-------------- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/library/network/protocol/http/src/http_cookie.e b/library/network/protocol/http/src/http_cookie.e index a896e3b2..e46c07fa 100644 --- a/library/network/protocol/http/src/http_cookie.e +++ b/library/network/protocol/http/src/http_cookie.e @@ -33,7 +33,6 @@ feature {NONE} -- Initialization -- 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_value_not_empty: a_value /= Void and then not a_value.is_empty 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 @@ -110,6 +109,16 @@ feature -- Access -- Does the Set-Cookie header include Expires attribute? --|By default will include both. + + 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 -- Change Element set_name (a_name: READABLE_STRING_8) @@ -126,7 +135,6 @@ feature -- Change Element set_value (a_value: READABLE_STRING_8) -- Set `value' with `a_value'. require - a_value_not_empty: a_value /= Void and then not a_value.is_empty a_value_has_valid_characters: a_value /= Void and then has_valid_characters (a_value) do value := a_value @@ -136,6 +144,8 @@ feature -- Change Element set_expiration (a_date: READABLE_STRING_8) -- Set `expiration' with `a_date' + require + rfc1133_date: a_date /= Void and then is_valid_rfc1123_date (a_date) do expiration := a_date ensure @@ -163,7 +173,7 @@ feature -- Change Element -- 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 a_domain.index_of (':', 1) = 0 + domain_without_port_info: a_domain /= Void implies not a_domain.has (':') do domain := a_domain ensure diff --git a/library/network/protocol/http/tests/http_cookie_test_set.e b/library/network/protocol/http/tests/http_cookie_test_set.e index 55fb1f36..182f0efd 100644 --- a/library/network/protocol/http/tests/http_cookie_test_set.e +++ b/library/network/protocol/http/tests/http_cookie_test_set.e @@ -36,6 +36,15 @@ feature -- Test routines assert ("Not valid Dquote", not l_cookie.has_valid_characters ("Use!%"12")) end + test_cookie_empty_value + -- values (cookie name and value) + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "") + assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=; Max-Age=-1")) + end + test_cookie_full_attributes local l_cookie: HTTP_COOKIE @@ -124,6 +133,22 @@ feature -- Test routines assert("Expected", l_cookie.header_line.same_string ("Set-Cookie: user_id=u12345; Max-Age=120")) end + test_cookie_date_rfc1123_valid + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + assert ("Valid RFC1123", l_cookie.is_valid_rfc1123_date ("Thu, 19 Mar 2015 16:14:03 GMT")) + end + + test_cookie_date_rfc1123_invalid + local + l_cookie: HTTP_COOKIE + do + create l_cookie.make ("user_id", "u12345") + assert ("Invalid RFC1123", not l_cookie.is_valid_rfc1123_date ("Thuesday, 19 Mar 2015 16:14:03 GMT")) + end + end diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index 0a906c20..58242c29 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -331,7 +331,7 @@ feature -- Header add cookie internal_header.headers as ic until l_same_cookie_name loop - if is_cookie_line (ic.item) then + if ic.item.starts_with ("Set-Cookie:") then l_same_cookie_name := has_cookie_name (ic.item, a_cookie.name) end end @@ -560,24 +560,7 @@ feature {NONE} -- Implemenation from until not a_cookie_line[j].is_space loop j := j + 1 end - if a_cookie_line.substring (j, i).same_string (a_cookie_name) then - Result := True - end - end - end - - - is_cookie_line (a_line: READABLE_STRING_32): BOOLEAN - -- Is the line `a_line' a cookie line? - --| Set-Cookie: user_id=%"u12;345%"; Domain=www.example.com; Path=/; Expires=Sat, 18 Apr 2015 21:22:05 GMT; Max-Age=-1; Secure; HttpOnly - local - j: INTEGER - do - Result := False - j := a_line.index_of (':', 1) - if j > 0 then - j := j - 1 - if a_line.substring (1, j).same_string ("Set-Cookie") then + if a_cookie_name.same_characters (a_cookie_line, j, i, 1) then Result := True end end