From db4f665de1f3c2d073819f611fa4191e14468b96 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Thu, 28 Feb 2013 13:10:04 +0100 Subject: [PATCH] Added a version of ISE Library URI modified to be compilable with compiler < 7.2 Fixed openid when redirection is involved Fixed Openid Attribute Exchange implementation (AX) Added WSF_REQUEST.items_as_string_items: ... for convenience, and ease integration with other components (such as the new openid) --- contrib/ise_library/text/uri/license.lic | 1 + .../uri/src/implementation/percent_encoder.e | 508 +++++++++ contrib/ise_library/text/uri/src/iri.e | 352 +++++++ contrib/ise_library/text/uri/src/uri.e | 971 ++++++++++++++++++ contrib/ise_library/text/uri/uri-safe.ecf | 17 + contrib/ise_library/text/uri/uri.ecf | 17 + .../openid/consumer/demo/application.e | 13 +- .../openid/consumer/demo/demo-safe.ecf | 8 +- .../openid/consumer/src/openid_consumer.e | 38 +- .../consumer/src/openid_consumer_validation.e | 60 +- .../openid/consumer/tests/test_openid.e | 2 +- library/server/wsf/src/wsf_request.e | 39 +- 12 files changed, 1970 insertions(+), 56 deletions(-) create mode 100644 contrib/ise_library/text/uri/license.lic create mode 100644 contrib/ise_library/text/uri/src/implementation/percent_encoder.e create mode 100644 contrib/ise_library/text/uri/src/iri.e create mode 100644 contrib/ise_library/text/uri/src/uri.e create mode 100644 contrib/ise_library/text/uri/uri-safe.ecf create mode 100644 contrib/ise_library/text/uri/uri.ecf diff --git a/contrib/ise_library/text/uri/license.lic b/contrib/ise_library/text/uri/license.lic new file mode 100644 index 00000000..c929225f --- /dev/null +++ b/contrib/ise_library/text/uri/license.lic @@ -0,0 +1 @@ +reference:forum2 diff --git a/contrib/ise_library/text/uri/src/implementation/percent_encoder.e b/contrib/ise_library/text/uri/src/implementation/percent_encoder.e new file mode 100644 index 00000000..72b7b894 --- /dev/null +++ b/contrib/ise_library/text/uri/src/implementation/percent_encoder.e @@ -0,0 +1,508 @@ +note + description: "[ + Component to handle percent encoding + ]" + date: "$Date: 2013-01-26 01:40:46 +0100 (sam., 26 janv. 2013) $" + revision: "$Revision: 90880 $" + EIS: "name=Percent-encoding", "protocol=URI", "src=http://en.wikipedia.org/wiki/Percent-encoding" + +class + PERCENT_ENCODER + +feature -- Percent encoding + + append_percent_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL) + -- Append `a_string' as percent-encoded value to `a_result' + local + c: NATURAL_32 + i,n: INTEGER + do + from + i := 1 + n := s.count + until + i > n + loop + c := s.code (i) + if + --| unreserved ALPHA / DIGIT + (48 <= c and c <= 57) -- DIGIT: 0 .. 9 + or (65 <= c and c <= 90) -- ALPHA: A .. Z + or (97 <= c and c <= 122) -- ALPHA: a .. z + then + a_result.append_code (c) + else + inspect c + when + 45, 46, 95, 126 -- unreserved characters: -._~ + then + a_result.append_code (c) + when + 58, 64, -- reserved =+ gen-delims: :@ + 33, 36, 38, 39, 40, 41, 42, -- reserved =+ sub-delims: !$&'()* + 43, 44, 59, 61, -- reserved = sub-delims: +,;= + 37 -- percent encoding: % + then + append_percent_encoded_character_code_to (c, a_result) + else + append_percent_encoded_character_code_to (c, a_result) + end + end + i := i + 1 + end + end + +feature -- Percent encoding: character + + append_percent_encoded_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL) + -- Append character code `a_code' as percent-encoded content into `a_result' + do + if a_code > 0xFF then + -- Unicode + append_percent_encoded_unicode_character_code_to (a_code, a_result) + elseif a_code > 0x7F then + -- Extended ASCII + -- This requires percent-encoding on UTF-8 converted character. + append_percent_encoded_unicode_character_code_to (a_code, a_result) + else + -- ASCII + append_percent_encoded_ascii_character_code_to (a_code, a_result) + end + ensure + appended: a_result.count > old a_result.count + end + +feature {NONE} -- Implementation: character encoding + + append_percent_encoded_ascii_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL) + -- Append extended ascii character code `a_code' as percent-encoded content into `a_result' + -- Note: it does not UTF-8 convert this extended ASCII. + require + is_extended_ascii: a_code <= 0xFF + local + c: INTEGER + do + if a_code > 0xFF then + -- Unicode + append_percent_encoded_unicode_character_code_to (a_code, a_result) + else + -- Extended ASCII + c := a_code.to_integer_32 + a_result.append_code (37) -- 37 '%%' + a_result.append_code (hex_digit [c |>> 4]) + a_result.append_code (hex_digit [c & 0xF]) + end + ensure + appended: a_result.count > old a_result.count + end + + append_percent_encoded_unicode_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL) + -- Append Unicode character code `a_code' as UTF-8 and percent-encoded content into `a_result' + -- Note: it does include UTF-8 conversion of extended ASCII and Unicode. + do + if a_code <= 0x7F then + -- 0xxxxxxx + append_percent_encoded_ascii_character_code_to (a_code, a_result) + elseif a_code <= 0x7FF then + -- 110xxxxx 10xxxxxx + append_percent_encoded_ascii_character_code_to ((a_code |>> 6) | 0xC0, a_result) + append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result) + elseif a_code <= 0xFFFF then + -- 1110xxxx 10xxxxxx 10xxxxxx + append_percent_encoded_ascii_character_code_to ((a_code |>> 12) | 0xE0, a_result) + append_percent_encoded_ascii_character_code_to (((a_code |>> 6) & 0x3F) | 0x80, a_result) + append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result) + else + -- c <= 1FFFFF - there are no higher code points + -- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + append_percent_encoded_ascii_character_code_to ((a_code |>> 18) | 0xF0, a_result) + append_percent_encoded_ascii_character_code_to (((a_code |>> 12) & 0x3F) | 0x80, a_result) + append_percent_encoded_ascii_character_code_to (((a_code |>> 6) & 0x3F) | 0x80, a_result) + append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result) + end + ensure + appended: a_result.count > old a_result.count + end + +feature -- Percent decoding + + append_percent_decoded_string_to (v: READABLE_STRING_GENERAL; a_result: STRING_GENERAL) + -- Append to `a_result' a string equivalent to the percent-encoded string `v' + --| Note that is `a_result' is a STRING_8, any Unicode character will be kept as UTF-8 + local + i,n: INTEGER + c: NATURAL_32 + pr: CELL [INTEGER] + a_result_is_string_32: BOOLEAN + do + a_result_is_string_32 := attached {STRING_32} a_result + from + i := 1 + create pr.put (i) + n := v.count + until + i > n + loop + c := v.code (i) + inspect c + when 43 then -- 43 '+' + -- Some implementation are replacing spaces with "+" instead of "%20" + a_result.append_code (32) -- 32 ' ' + when 37 then -- 37 '%%' + -- An escaped character ? + if i = n then -- Error? + a_result.append_code (c) + else + if a_result_is_string_32 then + -- Convert UTF-8 to UTF-32 + pr.replace (i) + c := next_percent_decoded_unicode_character_code (v, pr) + a_result.append_code (c) + i := pr.item + else + -- Keep UTF-8 + pr.replace (i) + c := next_percent_decoded_character_code (v, pr) + a_result.append_code (c) + i := pr.item + end + end + else + if c <= 0x7F then + a_result.append_code (c) + else + if a_result_is_string_32 then + a_result.append_code (c) + else + append_percent_encoded_character_code_to (c, a_result) + end + end + end + i := i + 1 + end + end + +feature {NONE} -- Implementation: decoding + + next_percent_decoded_character_code (v: READABLE_STRING_GENERAL; a_position: CELL [INTEGER]): NATURAL_32 + -- Character decoded from string `v' starting from index `a_position.item' + -- note: it also updates `a_position.item' to indicate the new index position. + require + valid_start: a_position.item <= v.count + is_percent_char: v.code (a_position.item) = 37 -- 37 '%%' + local + c: NATURAL_32 + i, n: INTEGER + not_a_digit: BOOLEAN + ascii_pos: NATURAL_32 + ival: NATURAL_32 + pos: INTEGER + c_is_digit: BOOLEAN + do + --| pos is index in stream of escape character ('%') + pos := a_position.item + c := v.code (pos + 1) + if c = 85 or c = 117 then -- 117 'u' 85 'U' + -- NOTE: this is not a standard, but it can occur, so use this for decoding only + -- An escaped Unicode (ucs2) value, from ECMA scripts + -- has the form: %u where is the UCS value + -- of the character (two byte integer, one to 4 chars + -- after escape sequence). + -- See: http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations + -- UTF-8 result can be 1 to 4 characters. + from + i := pos + 2 + n := v.count + until + (i > n) or not_a_digit + loop + c := v.code (i) + c_is_digit := (48 <= c and c <= 57) -- DIGIT: 0 .. 9 + if + c_is_digit + or (97 <= c and c <= 102) -- ALPHA: a..f + or (65 <= c and c <= 70) -- ALPHA: A..F + then + ival := ival * 16 + if c_is_digit then + ival := ival + (c - 48) -- 48 '0' + else + if c > 70 then -- a..f + ival := ival + (c - 97) + 10 -- 97 'a' + else -- A..F + ival := ival + (c - 65) + 10 -- 65 'A' + end + end + i := i + 1 + else + not_a_digit := True + i := i - 1 + end + end + a_position.replace (i) + Result := ival + else + -- ASCII char? + ascii_pos := hexadecimal_string_to_natural_32 (v.substring (pos + 1, pos + 2)) + Result := ascii_pos + a_position.replace (pos + 2) + end + end + + next_percent_decoded_unicode_character_code (v: READABLE_STRING_GENERAL; a_position: CELL [INTEGER]): NATURAL_32 + -- Next decoded character from `v' at position `a_position.item' + -- note: it also updates `a_position' to indicate the new index position. + require + valid_start: a_position.item <= v.count + is_percent_char: v.code (a_position.item) = 37 -- 37 '%%' + local + n, j: INTEGER + c: NATURAL_32 + c1, c2, c3, c4: NATURAL_32 + pr: CELL [INTEGER] + do + create pr.put (a_position.item) + c1 := next_percent_decoded_character_code (v, pr) + + j := pr.item + n := v.count + + Result := c1 + a_position.replace (j) + + if c1 <= 0x7F then + -- 0xxxxxxx + Result := c1 + elseif c1 <= 0xDF then + -- 110xxxxx 10xxxxxx + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c2 := next_percent_decoded_character_code (v, pr) + j := pr.item + Result := ( + ((c1 & 0x1F) |<< 6) | + ( c2 & 0x3F ) + ) + a_position.replace (j) + else + -- Do not try to decode + end + end + elseif c1 <= 0xEF then + -- 1110xxxx 10xxxxxx 10xxxxxx + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c2 := next_percent_decoded_character_code (v, pr) + j := pr.item + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c3 := next_percent_decoded_character_code (v, pr) + j := pr.item + + Result := ( + ((c1 & 0xF) |<< 12) | + ((c2 & 0x3F) |<< 6) | + ( c3 & 0x3F ) + ) + a_position.replace (j) + else + -- Do not try to decode + end + end + else + -- Do not try to decode + end + end + elseif c1 <= 0xF7 then + -- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c2 := next_percent_decoded_character_code (v, pr) + j := pr.item + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c3 := next_percent_decoded_character_code (v, pr) + j := pr.item + if j + 2 <= n then + c := v.code (j + 1) + if c = 37 then -- 37 '%%' + pr.replace (j + 1) + c4 := next_percent_decoded_character_code (v, pr) + j := pr.item + + a_position.replace (j) + + Result := ( + ((c1 & 0x7) |<< 18 ) | + ((c2 & 0x3F) |<< 12) | + ((c3 & 0x3F) |<< 6) | + ( c4 & 0x3F ) + ) + else + -- Do not try to decode + end + end + else + -- Do not try to decode + end + end + else + -- Do not try to decode + end + end + else + Result := c1 + end + end + +feature -- RFC and characters + + is_hexa_decimal_character (c: CHARACTER_32): BOOLEAN + -- Is hexadecimal character ? + do + Result := ('a' <= c and c <= 'f') or ('A' <= c and c <= 'F') -- HEXA + or ('0' <= c and c <= '9') -- DIGIT + end + + is_alpha_or_digit_character (c: CHARACTER_32): BOOLEAN + -- Is ALPHA or DIGIT character ? + do + Result := ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z') -- ALPHA + or ('0' <= c and c <= '9') -- DIGIT + end + + is_alpha_character (c: CHARACTER_32): BOOLEAN + -- Is ALPHA character ? + do + Result := ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z') + end + + is_digit_character (c: CHARACTER_32): BOOLEAN + -- Is DIGIT character ? + do + Result := ('0' <= c and c <= '9') + end + + is_unreserved_character (c: CHARACTER_32): BOOLEAN + -- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + do + if + ('a' <= c and c <= 'z') -- ALPHA + or ('A' <= c and c <= 'Z') -- ALPHA + or ('0' <= c and c <= '9') -- DIGIT + then + Result := True + else + inspect c + when '-', '_', '.', '~' then -- unreserved + Result := True + else + end + end + end + + is_reserved_character (c: CHARACTER_32): BOOLEAN + -- reserved = gen-delims / sub-delims + do + Result := is_gen_delims_character (c) or is_sub_delims_character (c) + end + + is_gen_delims_character (c: CHARACTER_32): BOOLEAN + -- gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + do + inspect c + when ':' , '/', '?' , '#' , '[' , ']' , '@' then + Result := True + else + end + end + + is_sub_delims_character (c: CHARACTER_32): BOOLEAN + -- sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + -- / "*" / "+" / "," / ";" / "=" + do + inspect c + when '!' , '$' , '&' , '%'' , '(' , ')' , '*' , '+' , ',' , ';' , '=' then -- sub-delims + Result := True + else + end + end + +feature {NONE} -- Implementation + + hex_digit: SPECIAL [NATURAL_32] + -- Hexadecimal digits. + once + create Result.make_filled (0, 16) + Result [0] := {NATURAL_32} 48 -- 48 '0' + Result [1] := {NATURAL_32} 49 -- 49 '1' + Result [2] := {NATURAL_32} 50 -- 50 '2' + Result [3] := {NATURAL_32} 51 -- 51 '3' + Result [4] := {NATURAL_32} 52 -- 52 '4' + Result [5] := {NATURAL_32} 53 -- 53 '5' + Result [6] := {NATURAL_32} 54 -- 54 '6' + Result [7] := {NATURAL_32} 55 -- 55 '7' + Result [8] := {NATURAL_32} 56 -- 56 '8' + Result [9] := {NATURAL_32} 57 -- 57 '9' + Result [10] := {NATURAL_32} 65 -- 65 'A' + Result [11] := {NATURAL_32} 66 -- 66 'B' + Result [12] := {NATURAL_32} 67 -- 67 'C' + Result [13] := {NATURAL_32} 68 -- 68 'D' + Result [14] := {NATURAL_32} 69 -- 69 'E' + Result [15] := {NATURAL_32} 70 -- 70 'F' + end + + is_hexa_decimal (a_string: READABLE_STRING_GENERAL): BOOLEAN + -- Is `a_string' a valid hexadecimal sequence? + local + l_convertor: like ctoi_convertor + do + l_convertor := ctoi_convertor + l_convertor.parse_string_with_type (a_string, {NUMERIC_INFORMATION}.type_natural_32) + Result := l_convertor.is_integral_integer + end + + hexadecimal_string_to_natural_32 (a_hex_string: READABLE_STRING_GENERAL): NATURAL_32 + -- Convert hexadecimal value `a_hex_string' to its corresponding NATURAL_32 value. + require + is_hexa: is_hexa_decimal (a_hex_string) + local + l_convertor: like ctoi_convertor + do + l_convertor := ctoi_convertor + l_convertor.parse_string_with_type (a_hex_string, {NUMERIC_INFORMATION}.type_no_limitation) + Result := l_convertor.parsed_natural_32 + end + + ctoi_convertor: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER + -- Converter used to convert string to integer or natural. + once + create Result.make + Result.set_leading_separators_acceptable (False) + Result.set_trailing_separators_acceptable (False) + ensure + ctoi_convertor_not_void: Result /= Void + end + +note + copyright: "Copyright (c) 1984-2013, 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/contrib/ise_library/text/uri/src/iri.e b/contrib/ise_library/text/uri/src/iri.e new file mode 100644 index 00000000..81fd1a97 --- /dev/null +++ b/contrib/ise_library/text/uri/src/iri.e @@ -0,0 +1,352 @@ +note + description : "[ + Object that represents an IRI Scheme + + See http://en.wikipedia.org/wiki/Internationalized_Resource_Identifier + See http://tools.ietf.org/html/rfc3987 (IRI) + + ]" + author: "$Author: manus $" + date: "$Date: 2013-01-26 01:40:46 +0100 (sam., 26 janv. 2013) $" + revision: "$Revision: 90880 $" + EIS: "name=IRI-RFC3987", "protocol=URI", "src=http://tools.ietf.org/html/rfc3987" + EIS: "name=IRI-Wikipedia", "protocol=URI", "src=http://en.wikipedia.org/wiki/Internationalized_Resource_Identifier" + +class + IRI + +inherit + URI + rename + make_from_string as make_from_uri_string, + userinfo as uri_userinfo, + path as uri_path, path_segments as uri_path_segments, + query as uri_query, query_items as uri_query_items, + fragment as uri_fragment, + username_password as uri_username_password, + username as uri_username, password as uri_password, + hier as uri_hier, + authority as uri_authority, + string as uri_string + end + +create + make_from_string, + make_from_uri + +feature {NONE} -- Initialization + + make_from_string (a_string: READABLE_STRING_GENERAL) + -- Make from Internationalized resource identifier text `a_string' + note + EIS: "name=IRI-RFC3987", "protocol=URI", "src=http://tools.ietf.org/html/rfc3987" + EIS: "name=IRI-Wikipedia", "protocol=URI", "src=http://en.wikipedia.org/wiki/Internationalized_Resource_Identifier" + local + l_uri_string: STRING_8 + do + create l_uri_string.make (a_string.count) + iri_into_uri (a_string, l_uri_string) + make_from_uri_string (l_uri_string) + end + + make_from_uri (a_uri: URI) + -- Make Current Internationalized resource identifier from `uri' object + do + make_from_uri_string (a_uri.string) + end + +feature -- Access + + userinfo: detachable READABLE_STRING_32 + -- User information. + --| username:password + --| RFC3986: userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + do + Result := to_internationalized_percent_encoded_string (uri_userinfo) + end + + path: READABLE_STRING_32 + -- Path component containing data, usually organized in hierarchical form. + do + Result := to_attached_internationalized_percent_encoded_string (uri_path) + end + + query: detachable READABLE_STRING_32 + -- Query string. + do + Result := to_internationalized_percent_encoded_string (uri_query) + end + + fragment: detachable READABLE_STRING_32 + -- The fragment identifier component of a URI allows indirect + -- identification of a secondary resource by reference to a primary + -- resource and additional identifying information. + do + Result := to_internationalized_percent_encoded_string (uri_fragment) + end + +feature -- Access + + path_segments: LIST [READABLE_STRING_32] + -- Segments composing `path'. + do + Result := path.split ('/') + end + + query_items: detachable LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]] + -- Query items composing the `query'. + local + lst: LIST [READABLE_STRING_32] + i: INTEGER + do + if attached query as q then + lst := q.split ('&') + create {ARRAYED_LIST [like query_items.item]} Result.make (lst.count) + across + lst as e + loop + i := e.item.index_of ('=', 1) + if i > 0 then + Result.force ([e.item.substring (1, i - 1), e.item.substring (i + 1, e.item.count)]) + else + Result.force ([e.item, Void]) + end + end + end + end + +feature -- Query + + hier: READABLE_STRING_32 + -- Hier part. + -- hier-part = "//" authority path-abempty + -- / path-absolute + -- / path-rootless + -- / path-empty + local + s: STRING_32 + do + create s.make (10) + if attached authority as l_authority then + s.append_character ('/') + s.append_character ('/') + s.append (l_authority) + end + s.append (path) + Result := s + end + + username_password: detachable TUPLE [username: READABLE_STRING_32; password: detachable READABLE_STRING_32] + -- Username and password value extrated from `userinfo'. + --| userinfo = username:password + local + i: INTEGER + u,p: detachable READABLE_STRING_32 + do + if attached userinfo as t then + i := t.index_of (':', 1) + if i > 0 then + p := t.substring (i + 1, t.count) + u := t.substring (1, i - 1) + else + u := t + p := Void + end + Result := [u, p] + end + end + + username: detachable READABLE_STRING_32 + -- Eventual username. + do + if attached username_password as up then + Result := up.username + end + end + + password: detachable READABLE_STRING_32 + -- Eventual password. + do + if attached username_password as up then + Result := up.password + end + end + + authority: detachable READABLE_STRING_32 + -- Hierarchical element for naming authority. + --| RFC3986: authority = [ userinfo "@" ] host [ ":" port ] + local + s: STRING_32 + do + if attached host as h then + if attached userinfo as u then + create s.make_from_string (u) + s.append_character ('@') + s.append_string_general (h) + else + create s.make (h.count) + s.append_string_general (h) + end + if port /= 0 then + s.append_character (':') + s.append_integer (port) + end + Result := s + else + check not is_valid or else (userinfo = Void and port = 0) end + end + end + +feature -- Conversion + + string: READABLE_STRING_32 + -- String representation. + -- scheme://username:password@hostname/path?query#fragment + local + s: STRING_32 + do + if attached scheme as l_scheme and then not l_scheme.is_empty then + create s.make (l_scheme.count) + s.append_string_general (l_scheme) + s.append_character (':') + else + create s.make_empty + end + s.append (hier) + if attached query as q then + s.append_character ('?') + s.append (q) + end + if attached fragment as f then + s.append_character ('#') + s.append (f) + end + Result := s + end + + to_uri: URI + do + create Result.make_from_string (uri_string) + end + +feature {NONE} -- Implementation: Internationalization + + iri_into_uri (a_string: READABLE_STRING_GENERAL; a_result: STRING_8) + require + is_valid_iri: True + local + i,n: INTEGER + c: NATURAL_32 + do + from + i := 1 + n := a_string.count + until + i > n + loop + c := a_string.code (i) + if c > 0x7F then + -- extended ASCII and/or Unicode + append_percent_encoded_character_code_to (c, a_result) +-- elseif c = 37 then -- '%' +-- -- Check for %u + code +-- if i + 1 <= n then +-- c := a_string.code (i + 1) +-- if c = 85 or c = 117 then -- 85 'U' 117 'u' +-- TODO: Convert it to standard percent-encoding without %U... +-- end +-- else +-- a_result.append_code (c) +-- end + else + -- keep as it is + a_result.append_code (c) + end + i := i + 1 + end + end + + to_internationalized_percent_encoded_string (s: detachable READABLE_STRING_8): detachable STRING_32 + -- Convert string `s' to Internationalized Resource Identifier string + -- Result is Void if `s' is Void. + do + if s /= Void then + create Result.make (s.count) + append_percent_encoded_string_into_internationalized_percent_encoded_string (s, Result) + end + end + + to_attached_internationalized_percent_encoded_string (s: READABLE_STRING_8): STRING_32 + -- Convert string `s' to Internationalized Resource Identifier string + do + create Result.make (s.count) + append_percent_encoded_string_into_internationalized_percent_encoded_string (s, Result) + end + + append_percent_encoded_string_into_internationalized_percent_encoded_string (v: READABLE_STRING_GENERAL; a_result: STRING_32) + -- Append to `a_result' the Internationalized URL-decoded equivalent of the given percent-encoded string `v' + -- It simply decode any percent-encoded Unicode character and kept the rest untouched + -- "http://example.com/summer/%C3%A9t%C3%A9" will be converted to IRI "http://example.com/summer/été" + local + i,n: INTEGER + c1, + c: NATURAL_32 + pr: CELL [INTEGER] + do + from + i := 1 + create pr.put (i) + n := v.count + until + i > n + loop + c := v.code (i) + inspect c + when 43 then -- 43 '+' + -- Some implementation are replacing spaces with "+" instead of "%20" + -- Here fix this bad behavior + a_result.append_code (37) -- 37 '%' + a_result.append_code (50) -- 50 '2' + a_result.append_code (48) -- 48 '0' + when 37 then -- 37 '%%' + -- An escaped character ? + if i = n then -- Error? + a_result.append_code (c) + elseif i + 1 <= n then + c1 := v.code (i + 1) + if c1 = 85 or c1 = 117 then -- 117 'u' 85 'U' + -- %u + UTF-32 code + pr.replace (i) + c1 := next_percent_decoded_character_code (v, pr) + i := pr.item + a_result.append_code (c1) + else + pr.replace (i) + c1 := next_percent_decoded_unicode_character_code (v, pr) + if c1 > 0x7F then + a_result.append_code (c1) + i := pr.item + else + a_result.append_code (c) + end + end + end + else + a_result.append_code (c) + end + i := i + 1 + end + end + + +;note + copyright: "Copyright (c) 1984-2013, 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/contrib/ise_library/text/uri/src/uri.e b/contrib/ise_library/text/uri/src/uri.e new file mode 100644 index 00000000..b34a5249 --- /dev/null +++ b/contrib/ise_library/text/uri/src/uri.e @@ -0,0 +1,971 @@ +note + description : "[ + Object that represent a URI Scheme + + See http://en.wikipedia.org/wiki/URI_scheme + See http://en.wikipedia.org/wiki/Uniform_resource_identifier + See http://en.wikipedia.org/wiki/Uniform_resource_locator + See http://tools.ietf.org/html/rfc3986 (URI) + + Global syntax element: + pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + pct-encoded = "%" HEXDIG HEXDIG + unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + reserved = gen-delims / sub-delims + gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + / "*" / "+" / "," / ";" / "=" + ]" + date: "$Date: 2013-01-21 10:25:01 +0100 (lun., 21 janv. 2013) $" + revision: "$Revision: 90748 $" + EIS: "name=URI-RFC3986 Generic syntax", "protocol=URI", "src=http://tools.ietf.org/html/rfc3986" + EIS: "name=URI-Wikipedia", "protocol=URI", "src=http://en.wikipedia.org/wiki/URI_scheme" + EIS: "name=IRI-RFC3987", "protocol=URI", "src=http://tools.ietf.org/html/rfc3987" + EIS: "name=IRI-Wikipedia", "protocol=URI", "src=http://en.wikipedia.org/wiki/Internationalized_Resource_Identifier" + EIS: "name=Percent-encoding", "protocol=URI", "src=http://en.wikipedia.org/wiki/Percent-encoding" + + EIS: "name=url-RFC1738", "protocol=URI", "src=http://tools.ietf.org/html/rfc1738" + EIS: "name=mailto-RFC2368", "protocol=URI", "src=http://tools.ietf.org/html/rfc2368" + EIS: "name=ipv6-RFC2373", "protocol=URI", "src=http://tools.ietf.org/html/rfc2373" + EIS: "name=ipv6-RFC2373 in URL", "protocol=URI", "src=http://tools.ietf.org/html/rfc2732" + +class + URI + +inherit + ANY + + PERCENT_ENCODER + export + {NONE} all + end + + DEBUG_OUTPUT + +create + make_from_string + +feature {NONE} -- Initialization + + make_from_string (a_string: READABLE_STRING_8) + -- Parse `a_string' as a URI as specified by RFC3986 + --| Note: for now the result of the parsing does not check the strict validity of each part. + --| URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + note + EIS: "name=Syntax Components", "protocol=URI", "src=http://tools.ietf.org/html/rfc3986#section-3" + local + p,q: INTEGER + s, t: STRING_8 + do + is_valid := True + p := a_string.index_of (':', 1) + if p > 0 then + set_scheme (a_string.substring (1, p - 1)) + if a_string.count > p + 1 and then a_string[p+1] = '/' and then a_string[p+2] = '/' then + --| Starts by scheme:// + --| waiting for hierarchical part username:password@hostname:port + p := p + 2 + q := a_string.index_of ('@', p + 1) + if q > 0 then + --| found user:passwd + t := a_string.substring (p + 1, q - 1) + set_userinfo (t) + p := q + end + q := a_string.index_of ('/', p + 1) + if q > 0 then + t := a_string.substring (p + 1, q - 1) + else + q := a_string.count + t := a_string.substring (p + 1, q) + q := 0 --| end of processing + end + if not t.is_empty and then t[1] = '[' then + p := t.index_of (']', 2) + if p > 0 then + p := t.index_of (':', p + 1) + else + is_valid := False + end + else + p := t.index_of (':', 1) + end + if p > 0 then + set_hostname (t.substring (1, p - 1)) + t.remove_head (p) + if t.is_integer then + set_port (t.to_integer) + else + set_port (0) + is_valid := False + end + else + set_hostname (t) + set_port (0) + end + else + --| Keep eventual '/' as part of the path + q := p + 1 + set_hostname (Void) + end + + if q > 0 and q <= a_string.count then + --| found query + t := a_string.substring (q, a_string.count) + q := t.index_of ('?', 1) + if q > 0 then + s := t.substring (1, q - 1) + if is_valid_in_uri_string (s) then + set_path (s) + else + set_path ("") + is_valid := False + end + t.remove_head (q) + q := t.index_of ('#', 1) + if q > 0 then + set_query (t.substring (1, q - 1)) + t.remove_head (q) + set_fragment (t) + else + set_query (t) + end + else + if is_valid_in_uri_string (t) then + set_path (t) + else + set_path ("") + is_valid := False + end + end + else + set_path ("") + end + else + set_scheme ("") + set_hostname (Void) + set_path ("") + end + if is_valid then + check_validity (True) + end + ensure + same_if_valid: is_valid and not is_corrected implies a_string.starts_with (string) + end + +feature -- Basic operation + + check_validity (a_fixing: BOOLEAN) + -- Check validity of URI + -- If `a_fixing' is True, attempt to correct input URI. + local + s: STRING_8 + do + -- check scheme + -- TODO: RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + if not is_valid_scheme (scheme) then + is_valid := False + end + + -- check userinfo + -- TODO: RFC3986: userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + if not is_valid_userinfo (userinfo) then + is_valid := False + end + + -- check host + -- TODO: RFC3986: host = IP-literal / IPv4address / reg-name + if not is_valid_host (host) then + is_valid := False + end + + -- Check path + -- TODO: no space, all character well escaped, ... + if path.has (' ') then + -- Fix bad URI + if a_fixing then + create s.make_from_string (path) + s.replace_substring_all (" ", "%%20") + set_path (s) + is_corrected := True + end + end + if not is_valid_path (path) then + is_valid := False + end + + -- Check query + -- TODO: no space, all character well escaped, ... + if attached query as q then + if q.has (' ') then + -- Fix bad URI + if a_fixing then + create s.make_from_string (q) + s.replace_substring_all (" ", "%%20") + set_query (s) + is_corrected := True + else + is_valid := False + end + end + end + if not is_valid_query (query) then + is_valid := True + end + + -- Check fragment + if not is_valid_fragment (fragment) then + is_valid := False + end + end + +feature -- Status + + is_valid: BOOLEAN + -- Is Current valid? + + is_corrected: BOOLEAN + -- Is Current valid after eventual correction? + + has_authority: BOOLEAN + do + Result := host /= Void + end + + has_query: BOOLEAN + do + Result := query /= Void + end + + has_path: BOOLEAN + do + Result := not path.is_empty + end + + has_fragment: BOOLEAN + do + Result := fragment /= Void + end + +feature -- Access + + scheme: IMMUTABLE_STRING_8 + -- Scheme name. + --| scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + + userinfo: detachable IMMUTABLE_STRING_8 + -- User information. + --| username:password + --| RFC3986: userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + + host: detachable IMMUTABLE_STRING_8 + -- Host name. + --| RFC3986: host = IP-literal / IPv4address / reg-name + + port: INTEGER + -- Associated port, if `0' this is not defined. + -- RFC3986: port = *DIGIT + + path: IMMUTABLE_STRING_8 + -- Path component containing data, usually organized in hierarchical form. + + query: detachable IMMUTABLE_STRING_8 + -- Query string. + + fragment: detachable IMMUTABLE_STRING_8 + -- The fragment identifier component of a URI allows indirect + -- identification of a secondary resource by reference to a primary + -- resource and additional identifying information. + +feature -- Access + + decoded_path: READABLE_STRING_32 + -- Decoded `path' + local + s: STRING_32 + do + create s.make (path.count) + append_decoded_www_form_urlencoded_string_to (path, s) + Result := s + end + + path_segments: LIST [READABLE_STRING_8] + -- Segments composing `path'. + do + Result := path.split ('/') + end + + decoded_path_segments: LIST [READABLE_STRING_32] + -- Decoded Segments composing `path'. + local + lst: like path_segments + do + lst := path_segments + create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (lst.count) + across + lst as e + loop + Result.force (decoded_www_form_urlencoded_string (e.item)) + end + end + + query_items: detachable LIST [TUPLE [name: READABLE_STRING_8; value: detachable READABLE_STRING_8]] + -- Query items composing the `query'. + local + lst: LIST [READABLE_STRING_8] + i: INTEGER + do + if attached query as q then + lst := q.split ('&') + create {ARRAYED_LIST [like query_items.item]} Result.make (lst.count) + across + lst as e + loop + i := e.item.index_of ('=', 1) + if i > 0 then + Result.force ([e.item.substring (1, i - 1), e.item.substring (i + 1, e.item.count)]) + else + Result.force ([e.item, Void]) + end + end + end + end + + decoded_query_items: detachable LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]] + -- Decoded query items composing the `query'. + do + if attached query_items as lst then + create {ARRAYED_LIST [like decoded_query_items.item]} Result.make (lst.count) + across + lst as e + loop + if attached e.item.value as l_val then + Result.force ([decoded_www_form_urlencoded_string (e.item.name), decoded_www_form_urlencoded_string (l_val)]) + else + Result.force ([decoded_www_form_urlencoded_string (e.item.name), Void]) + end + end + end + end + +feature -- Query + + hier: READABLE_STRING_8 + -- Hier part. + -- hier-part = "//" authority path-abempty + -- / path-absolute + -- / path-rootless + -- / path-empty + local + s: STRING_8 + do + create s.make (10) + if attached authority as l_authority then + s.append_character ('/') + s.append_character ('/') + s.append (l_authority) + end + s.append (path) + Result := s + end + + username_password: detachable TUPLE [username: READABLE_STRING_8; password: detachable READABLE_STRING_8] + -- Username and password value extrated from `userinfo'. + --| userinfo = username:password + local + i: INTEGER + u,p: detachable READABLE_STRING_8 + do + if attached userinfo as t then + i := t.index_of (':', 1) + if i > 0 then + p := t.substring (i + 1, t.count) + u := t.substring (1, i - 1) + else + u := t + p := Void + end + Result := [u, p] + end + end + + username: detachable READABLE_STRING_8 + -- Eventual username. + do + if attached username_password as up then + Result := up.username + end + end + + password: detachable READABLE_STRING_8 + -- Eventual password. + do + if attached username_password as up then + Result := up.password + end + end + + authority: detachable READABLE_STRING_8 + -- Hierarchical element for naming authority. + --| RFC3986: authority = [ userinfo "@" ] host [ ":" port ] + local + s: STRING_8 + do + if attached host as h then + if attached userinfo as u then + create s.make_from_string (u) + s.append_character ('@') + s.append (h) + else + create s.make_from_string (h) + end + if port /= 0 then + s.append_character (':') + s.append_integer (port) + end + Result := s + else + check not is_valid or else (userinfo = Void and port = 0) end + end + end + +feature -- Conversion + + string: READABLE_STRING_8 + -- String representation. + -- scheme://username:password@hostname/path?query#fragment + local + s: STRING_8 + do + if attached scheme as l_scheme and then not l_scheme.is_empty then + create s.make_from_string (l_scheme) + s.append_character (':') + else + create s.make_empty + end + s.append (hier) + if attached query as q then + s.append_character ('?') + s.append (q) + end + if attached fragment as f then + s.append_character ('#') + s.append (f) + end + Result := s + end + + resolved_uri: URI + -- Resolved URI, i.e remove segment-component from `path' + local + p: STRING_8 + lst: like path_segments + l_first: BOOLEAN + do + from + lst := path_segments + lst.start + until + lst.off + loop + if lst.item.same_string (".") then + lst.remove + elseif lst.item.same_string ("..") then + lst.back + if not lst.before then + lst.remove + lst.remove + else + lst.forth + lst.remove + end + else + lst.forth + end + end + create p.make (path.count) + l_first := True + across + lst as c + loop + if l_first then + l_first := False + else + p.append_character ('/') + end + p.append (c.item) + end + if p.is_empty then + else + if p.item (1) /= '/' then + if not path.is_empty and then path.item (1) = '/' then + p.prepend_character ('/') + end + end + end + create Result.make_from_string (string) + Result.set_path (p) + end + +feature -- Comparison + + is_same_uri (a_uri: URI): BOOLEAN + -- Is `a_uri' same as Current ? + --| See http://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_unreserved_characters + do + Result := decoded_www_form_urlencoded_string (string).same_string (decoded_www_form_urlencoded_string (a_uri.string)) + end + +feature -- Element Change + + set_scheme (v: READABLE_STRING_8) + -- Set `scheme' to `v' + require + is_valid_scheme (v) + do + create scheme.make_from_string (v) + ensure + scheme_set: scheme.same_string (v) + end + + set_userinfo (v: detachable READABLE_STRING_8) + require + is_valid_userinfo (v) + do + if v = Void then + userinfo := Void + else + create userinfo.make_from_string (v.as_string_8) + end + ensure + userinfo_set: is_same_string (v, userinfo) + end + + set_hostname (v: detachable READABLE_STRING_8) + -- Set `host' to `v' + require + is_valid_host (v) + do + if v = Void then + host := Void + else + create host.make_from_string (v) + end + ensure + hostname_set: is_same_string (v, host) + end + + set_port (v: like port) + -- Set `port' to `v' + do + port := v + ensure + port_set: port = v + end + + set_path (a_path: READABLE_STRING_8) + -- Set `path' to `a_path' + require + is_valid_path (a_path) + do + create path.make_from_string (a_path) + ensure + path_set: path.same_string_general (a_path) + end + + set_query (v: detachable READABLE_STRING_8) + -- Set `query' to `v' + require + is_valid_query (v) + do + if v = Void then + query := Void + else + create query.make_from_string (v) + end + ensure + query_set: is_same_string (v, query) + end + + set_fragment (v: detachable READABLE_STRING_8) + -- Set `fragment' to `v' + require + is_valid_fragment (v) + do + if v = Void then + fragment := Void + else + create fragment.make_from_string (v) + end + ensure + fragment_set: is_same_string (v, fragment) + end + +feature -- Change: query + + remove_query + -- Remove query from Current URI + do + query := Void + end + + add_query_parameter (a_name: READABLE_STRING_GENERAL; a_value: detachable READABLE_STRING_GENERAL) + -- Add non percent-encoded parameters + local + q: detachable STRING + do + if attached query as l_query then + create q.make_from_string (l_query) + else + create q.make_empty + end + if not q.is_empty then + q.append_character ('&') + end + + q.append (www_form_urlencoded_string (a_name)) + if a_value /= Void then + q.append_character ('=') + q.append (www_form_urlencoded_string (a_value)) + end + create query.make_from_string (q) + end + + add_query_parameters (lst: ITERABLE [TUPLE [name: READABLE_STRING_GENERAL; value: detachable READABLE_STRING_GENERAL]]) + -- Add non percent-encoded parameters from manifest + do + across + lst as c + loop + add_query_parameter (c.item.name, c.item.value) + end + end + + add_query_parameters_from_table (tb: TABLE_ITERABLE [detachable READABLE_STRING_GENERAL, READABLE_STRING_GENERAL]) + -- Add non percent-encoded parameters from table + do + across + tb as c + loop + add_query_parameter (c.key, c.item) + end + end + +feature -- Status report + + is_valid_scheme (s: READABLE_STRING_GENERAL): BOOLEAN + -- scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + local + i,n: INTEGER + c: CHARACTER_32 + do + if s.is_empty then + Result := False -- Check for URI-reference .. + else + from + i := 1 + n := s.count + Result := is_alpha_character (string_item (s, i)) + i := 2 + until + not Result or i > n + loop + c := string_item (s, i) + Result := is_alpha_or_digit_character (c) or c = '+' or c = '-' or c = '.' + i := i + 1 + end + end + end + + is_valid_userinfo (s: detachable READABLE_STRING_GENERAL): BOOLEAN + -- userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + local + i,n: INTEGER + c: CHARACTER_32 + do + Result := True + if s /= Void then + from + i := 1 + n := s.count + until + not Result or i > n + loop + c := string_item (s, i) + -- unreserved + if is_unreserved_character (c) + or is_sub_delims_character (c) + or c = ':' + then + -- True + elseif c = '%%' then + if + i + 2 <= n and then + is_hexa_decimal_character (string_item (s, i+ 1)) and is_hexa_decimal_character (string_item (s, i + 2)) + then + -- True + i := i + 2 + else + Result := False + end + else + Result := False + end + i := i + 1 + end + end + end + + is_valid_host (s: detachable READABLE_STRING_GENERAL): BOOLEAN + -- host = IP-literal / IPv4address / reg-name + -- IP-literal = "[" ( IPv6address / IPvFuture ) "]" + -- IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + -- IPv6address = 6( h16 ":" ) ls32 + -- / "::" 5( h16 ":" ) ls32 + -- / [ h16 ] "::" 4( h16 ":" ) ls32 + -- / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + -- / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + -- / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + -- / [ *4( h16 ":" ) h16 ] "::" ls32 + -- / [ *5( h16 ":" ) h16 ] "::" h16 + -- / [ *6( h16 ":" ) h16 ] "::" + -- + -- ls32 = ( h16 ":" h16 ) / IPv4address + -- ; least-significant 32 bits of address + -- + -- h16 = 1*4HEXDIG + -- ; 16 bits of address represented in hexadecimal + -- + -- IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + -- + -- dec-octet = DIGIT ; 0-9 + -- / %x31-39 DIGIT ; 10-99 + -- / "1" 2DIGIT ; 100-199 + -- / "2" %x30-34 DIGIT ; 200-249 + -- / "25" %x30-35 ; 250-255 + -- + -- reg-name = *( unreserved / pct-encoded / sub-delims ) + do + Result := True + if s /= Void and then not s.is_empty then + if string_item (s, 1) = '[' and string_item (s, s.count) = ']' then + Result := True -- IPV6 : to complete + else + Result := is_hexa_decimal_character (string_item (s, 1)) -- IPV4 or reg-name : to complete + end + end + end + + is_valid_path (s: READABLE_STRING_GENERAL): BOOLEAN + -- path = path-abempty ; begins with "/" or is empty + -- / path-absolute ; begins with "/" but not "//" + -- / path-noscheme ; begins with a non-colon segment + -- / path-rootless ; begins with a segment + -- / path-empty ; zero characters + -- + -- path-abempty = *( "/" segment ) + -- path-absolute = "/" [ segment-nz *( "/" segment ) ] + -- path-noscheme = segment-nz-nc *( "/" segment ) + -- path-rootless = segment-nz *( "/" segment ) + -- path-empty = 0 + -- segment = *pchar + -- segment-nz = 1*pchar + -- segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + -- ; non-zero-length segment without any colon ":" + -- + -- pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + do + if s.is_empty or string_item (s, 1) = '/' then + Result := is_valid_in_uri_string (s) + elseif has_authority then + if string_item (s, 1) = '/' and (s.count > 1 implies string_item (s, 2) /= '/') then + Result := is_valid_in_uri_string (s) + end + elseif s.is_empty then + Result := True + else + Result := is_valid_in_uri_string (s) + end + -- TO COMPLETE + end + + is_valid_query (s: detachable READABLE_STRING_GENERAL): BOOLEAN + -- query = *( pchar / "/" / "?" ) + local + i,n: INTEGER + c: CHARACTER_32 + do + Result := True + if s /= Void then + from + i := 1 + n := s.count + until + not Result or i > n + loop + c := string_item (s, i) + -- pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + if -- pchar + is_unreserved_character (c) + or is_sub_delims_character (c) + or c = ':' or c = '@' + then + Result := True + elseif c = '/' or c = '?' then + Result := True + elseif c = '%%' then + if + i + 2 <= n and then + is_hexa_decimal_character (string_item (s, i + 1)) and is_hexa_decimal_character (string_item (s, i + 2)) + then + -- True + i := i + 2 + else + Result := False + end + else + Result := False + end + i := i + 1 + end + end + end + + is_valid_fragment (s: detachable READABLE_STRING_GENERAL): BOOLEAN + --fragment = *( pchar / "/" / "?" ) + local + i,n: INTEGER + c: CHARACTER_32 + do + Result := True + if s /= Void then + from + i := 1 + n := s.count + until + not Result or i > n + loop + c := string_item (s, i) + -- pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + if -- pchar + is_unreserved_character (c) + or is_sub_delims_character (c) + or c = ':' or c = '@' + then + Result := True + elseif c = '/' or c = '?' then + Result := True + elseif c = '%%' then + if + i + 2 <= n and then + is_alpha_or_digit_character (string_item (s, i + 1)) and is_alpha_or_digit_character (string_item (s, i + 2)) + then + i := i + 2 + else + Result := False + end + else + Result := False + end + i := i + 1 + end + end + end + +feature -- Helper + + string_item (s: READABLE_STRING_GENERAL; i: INTEGER): CHARACTER_32 + do + Result := s.code (i).to_character_32 + end + + append_www_form_urlencoded_string_to (a_string: READABLE_STRING_GENERAL; a_target: STRING_GENERAL) + -- The application/x-www-form-urlencoded encoded string for `a_string'. + -- character encoding is UTF-8. + -- See http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + do + append_percent_encoded_string_to (a_string, a_target) + end + + www_form_urlencoded_string (a_string: READABLE_STRING_GENERAL): STRING_8 + -- The application/x-www-form-urlencoded encoded string for `a_string'. + -- character encoding is UTF-8. + -- See http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + do + create Result.make (a_string.count) + append_percent_encoded_string_to (a_string, Result) + end + + append_decoded_www_form_urlencoded_string_to (a_string: READABLE_STRING_GENERAL; a_target: STRING_GENERAL) + -- The string decoded from application/x-www-form-urlencoded encoded string `a_string'. + -- character encoding is UTF-8. + -- See http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + do + append_percent_decoded_string_to (a_string, a_target) + end + + decoded_www_form_urlencoded_string (a_string: READABLE_STRING_GENERAL): STRING_32 + -- The string decoded from application/x-www-form-urlencoded encoded string `a_string'. + -- character encoding is UTF-8. + -- See http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + do + create Result.make (a_string.count) + append_percent_decoded_string_to (a_string, Result) + end + +feature -- Assertion helper + + is_valid_in_uri_string (s: READABLE_STRING_GENERAL): BOOLEAN + -- Is `s' composed only of ASCII character? + local + i,n: INTEGER + do + from + Result := True + i := 1 + n := s.count + until + not Result or i > n + loop + if s.code (i) > 0x7F then + Result := False + end + i := i + 1 + end + end + + is_same_string (s1, s2: detachable READABLE_STRING_GENERAL): BOOLEAN + -- `s1' and `s2' have same string content? + do + if s1 = Void then + Result := s2 = Void + elseif s2 = Void then + Result := False + else + Result := s1.same_string (s2) + end + end + +feature -- Status report + + debug_output: STRING + -- String that should be displayed in debugger to represent `Current'. + local + s: STRING + do + create s.make_empty + s.append (string) + Result := s + end + +;note + copyright: "Copyright (c) 1984-2013, 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/contrib/ise_library/text/uri/uri-safe.ecf b/contrib/ise_library/text/uri/uri-safe.ecf new file mode 100644 index 00000000..7ae5299d --- /dev/null +++ b/contrib/ise_library/text/uri/uri-safe.ecf @@ -0,0 +1,17 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + diff --git a/contrib/ise_library/text/uri/uri.ecf b/contrib/ise_library/text/uri/uri.ecf new file mode 100644 index 00000000..c92ba12d --- /dev/null +++ b/contrib/ise_library/text/uri/uri.ecf @@ -0,0 +1,17 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + diff --git a/draft/library/security/openid/consumer/demo/application.e b/draft/library/security/openid/consumer/demo/application.e index d1eca72e..383dbd02 100644 --- a/draft/library/security/openid/consumer/demo/application.e +++ b/draft/library/security/openid/consumer/demo/application.e @@ -86,13 +86,13 @@ feature {NONE} -- Initialization if l_openid_mode.same_string ("id_res") then o := new_openid_consumer (req) - create v.make_from_string (o, req.absolute_script_url (req.request_uri)) + create v.make_from_items (o, req.items_as_string_items) v.validate if v.is_valid then s.append ("
User authenticated
") - s.append ("
    Query") + s.append ("
      Request items") across - req.query_parameters as c + req.items as c loop s.append ("
    • " + c.item.url_encoded_name + "=" + c.item.string_representation + "
    • ") end @@ -140,9 +140,10 @@ feature {NONE} -- Initialization do create Result.make (req.absolute_script_url ("/openid")) --- Result.ask_email (True) - Result.ask_nickname (False) + Result.ask_email (True) + Result.ask_all_info (False) +-- Result.ask_nickname (False) -- Result.ask_fullname (False) - Result.ask_country (True) +-- Result.ask_country (True) end end diff --git a/draft/library/security/openid/consumer/demo/demo-safe.ecf b/draft/library/security/openid/consumer/demo/demo-safe.ecf index 47d91375..9a829782 100644 --- a/draft/library/security/openid/consumer/demo/demo-safe.ecf +++ b/draft/library/security/openid/consumer/demo/demo-safe.ecf @@ -12,12 +12,12 @@ - - - - + + + + diff --git a/draft/library/security/openid/consumer/src/openid_consumer.e b/draft/library/security/openid/consumer/src/openid_consumer.e index 48f010b6..b9f91d68 100644 --- a/draft/library/security/openid/consumer/src/openid_consumer.e +++ b/draft/library/security/openid/consumer/src/openid_consumer.e @@ -47,7 +47,7 @@ feature -- Change across ax_to_sreg_map as c loop - ask_info (c.item, is_required) + ask_info (c.key.to_string_32, is_required) end end @@ -133,7 +133,7 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation discovering_info (id: READABLE_STRING_8): detachable OPENID_DISCOVER local - cl: LIBCURL_HTTP_CLIENT + sess: HTTP_CLIENT_SESSION ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT xrds_location: detachable READABLE_STRING_8 @@ -148,18 +148,17 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation r_version: INTEGER l_xrds_content: detachable READABLE_STRING_8 do - create cl.make - sess := cl.new_session (id) - sess.set_is_insecure (True) - if attached sess.get ("", ctx) as rep then + sess := new_session (id) + if attached sess.head ("", ctx) as rep then if rep.error_occurred then report_error ("Unable get answer from openid provider at " + rep.url) else if attached rep.header ("Content-Type") as l_content_type and then - l_content_type.has_substring ("application/xrds+xml") + l_content_type.has_substring ("application/xrds+xml") and then + attached sess.get ("", ctx) as l_getres then - l_xrds_content := rep.body + l_xrds_content := l_getres.body elseif attached rep.header ("X-XRDS-Location") as loc then xrds_location := loc else @@ -168,8 +167,7 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation end end if l_xrds_content = Void and xrds_location /= Void then - sess := cl.new_session (xrds_location) - sess.set_is_insecure (True) + sess := new_session (xrds_location) if attached sess.get ("", ctx) as rep then if rep.error_occurred then r_err := True @@ -256,7 +254,6 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation Result.sreg_supported := r_sreg_supported Result.identifier_select := r_identifier_select Result.has_error := r_err - end end @@ -457,9 +454,9 @@ feature {NONE} -- Implementation Result.force ("language", "pref/language") Result.force ("timezone", "pref/timezone") - -- extension - Result.force ("firstname", "namePerson/first") - Result.force ("lastname", "namePerson/last") +-- -- extension +-- Result.force ("firstname", "namePerson/first") +-- Result.force ("lastname", "namePerson/last") end ax_to_sreg (n: READABLE_STRING_8): detachable READABLE_STRING_8 @@ -495,6 +492,8 @@ feature {NONE} -- Implementation has_error end +feature -- Helper + xml_content (e: XML_ELEMENT): STRING_8 do create Result.make_empty @@ -507,4 +506,15 @@ feature {NONE} -- Implementation end end + new_session (a_uri: READABLE_STRING_8): HTTP_CLIENT_SESSION + local + cl: LIBCURL_HTTP_CLIENT + do + create cl.make + Result := cl.new_session (a_uri) + Result.set_is_insecure (True) + Result.set_max_redirects (5) + Result.add_header ("Accept", "application/xrds+xml, */*") + end + end diff --git a/draft/library/security/openid/consumer/src/openid_consumer_validation.e b/draft/library/security/openid/consumer/src/openid_consumer_validation.e index 34e26499..4f632901 100644 --- a/draft/library/security/openid/consumer/src/openid_consumer_validation.e +++ b/draft/library/security/openid/consumer/src/openid_consumer_validation.e @@ -8,25 +8,19 @@ class OPENID_CONSUMER_VALIDATION create - make_from_uri, - make_from_string + make_from_items feature {NONE} -- Initialization - make_from_uri (o: OPENID_CONSUMER; a_uri: URI) + make_from_items (o: OPENID_CONSUMER; lst: like values) do openid := o - uri := a_uri + values := lst return_url := o.return_url create attributes.make (0) end - make_from_string (o: OPENID_CONSUMER; a_uri: READABLE_STRING_8) - do - make_from_uri (o, create {URI}.make_from_string (a_uri)) - end - - uri: URI + values: detachable ITERABLE [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]] return_url: READABLE_STRING_8 @@ -45,7 +39,6 @@ feature -- Basic operation local l_claimed_id: detachable READABLE_STRING_8 tb: STRING_TABLE [detachable READABLE_STRING_32] - cl: LIBCURL_HTTP_CLIENT ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT ret: URI sess: HTTP_CLIENT_SESSION @@ -53,7 +46,7 @@ feature -- Basic operation is_valid := False create ret.make_from_string (return_url) create tb.make (5) - if attached uri.decoded_query_items as q_lst then + if attached values as q_lst then if attached item_by_name ("openid.claimed_id", q_lst) as q_claimed_id then l_claimed_id := q_claimed_id.as_string_8 elseif attached item_by_name ("openid.identity", q_lst) as l_id then @@ -103,7 +96,6 @@ feature -- Basic operation end tb.force ("check_authentication", "openid.mode") - create cl.make create ctx.make across tb as c @@ -112,8 +104,7 @@ feature -- Basic operation ctx.add_form_parameter (c.key.to_string_32, l_value) end end - sess := cl.new_session (d_info.server_uri) - sess.set_is_insecure (True) + sess := openid.new_session (d_info.server_uri) if attached sess.post ("", ctx, Void) as res then if res.error_occurred then elseif attached {STRING} res.body as l_body then @@ -128,7 +119,7 @@ feature -- Basic operation end end - get_attributes (lst: LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]]) + get_attributes (lst: like values) local s: READABLE_STRING_32 sreg_keys: ARRAYED_LIST [READABLE_STRING_32] @@ -139,14 +130,14 @@ feature -- Basic operation get_ax_attributes (lst) end - get_sreg_attributes (lst: LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]]) + get_sreg_attributes (lst: like values) local s: READABLE_STRING_32 sreg_keys: ARRAYED_LIST [READABLE_STRING_32] do - if attached item_by_name ("openid.signed", lst) as l_signed then + if lst /= Void and then attached item_by_name ("openid.signed", lst) as l_signed then -- sreg attributes - create sreg_keys.make (3) + create sreg_keys.make (5) across l_signed.split (',') as c loop @@ -166,7 +157,7 @@ feature -- Basic operation end end - get_ax_attributes (lst: LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]]) + get_ax_attributes (lst: like values) local s: READABLE_STRING_32 ax_keys: ARRAYED_LIST [READABLE_STRING_32] @@ -174,38 +165,47 @@ feature -- Basic operation k_value, k_type, k_count, k: STRING i: INTEGER do - if attached item_by_name ("openid.signed", lst) as l_signed then + if lst /= Void and then attached item_by_name ("openid.signed", lst) as l_signed then -- ax attributes across l_signed.split (',') as c loop + i := i + 1 s := c.item if s.starts_with ("ns.") then - if attached item_by_name (s, lst) as v then + if attached item_by_name ("openid." + s, lst) as v then if s.same_string ("ns.ax") and v.same_string ("http://openid.net/srv/ax/1.0") then l_alias := "ax." else if v.same_string ("http://openid.net/srv/ax/1.0") then - l_alias := s.substring (("ns.").count, s.count) + "." + l_alias := s.substring (("ns.").count + 1, s.count) + "." end end end end end if l_alias /= Void then - create ax_keys.make (lst.count) + k_value := l_alias + "value." + k_type := l_alias + "type." + k_count := l_alias + "count." + + create ax_keys.make (i) across l_signed.split (',') as c loop s := c.item - if s.starts_with (l_alias) then + if + s.starts_with (k_value) + or s.starts_with (k_type) + then ax_keys.force ("openid." + s) end end - k_value := "openid." + l_alias + "value." - k_type := "openid." + l_alias + "type." - k_count := "openid." + l_alias + "count." + k_value := "openid." + k_value + k_type := "openid." + k_type + k_count := "openid." + k_count + across ax_keys as c loop @@ -230,15 +230,15 @@ feature -- Basic operation else -- no alias !!! end +-- attributes.force (v, k) end - attributes.force (v, s.substring (5, s.count)) end end end end end - item_by_name (a_name: READABLE_STRING_32; lst: like {URI}.decoded_query_items): detachable READABLE_STRING_32 + item_by_name (a_name: READABLE_STRING_32; lst: like values): detachable READABLE_STRING_32 local l_found: BOOLEAN do diff --git a/draft/library/security/openid/consumer/tests/test_openid.e b/draft/library/security/openid/consumer/tests/test_openid.e index 80b495c0..c82f8dd7 100644 --- a/draft/library/security/openid/consumer/tests/test_openid.e +++ b/draft/library/security/openid/consumer/tests/test_openid.e @@ -24,7 +24,7 @@ feature check o.error = Void end get_openid_response_uri (l_url) if attached openid_response_uri as u and then u.is_valid then - create v.make_from_uri (o, u) + create v.make_from_items (o, u.decoded_query_items) v.validate if v.is_valid then print ("Succeed ...%N") diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index a3210461..c16c8262 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -347,7 +347,7 @@ feature {NONE} -- Access: global variable end end -feature -- Access: global variable +feature -- Access: global variables items: ITERABLE [WSF_VALUE] do @@ -411,6 +411,43 @@ feature -- Access: global variable Result.keep_head (n - 1) end +feature -- Helpers: global variables + + items_as_string_items: ITERABLE [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]] + -- `items' as strings items + -- i.e: flatten any table or related into multiple string items + local + res: ARRAYED_LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]] + do + if attached items_table as tb then + create res.make (tb.count) + across + tb as c + loop + append_value_as_string_items_to (c.item, res) + end + else + create res.make (0) + end + Result := res + end + + append_value_as_string_items_to (v: WSF_VALUE; a_target: LIST [TUPLE [name: READABLE_STRING_32; value: detachable READABLE_STRING_32]]) + -- Append value `v' to `a_target' as multiple string items + do + if attached {WSF_STRING} v as s then + a_target.force ([s.name, s.value]) + elseif attached {ITERABLE [WSF_VALUE]} v as lst then + across + lst as c + loop + append_value_as_string_items_to (c.item, a_target) + end + else + a_target.force ([v.name, v.string_representation]) + end + end + feature -- Execution variables execution_variable (a_name: READABLE_STRING_GENERAL): detachable ANY