diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 61f6a0e5..db797f9a 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -1,8 +1,9 @@ note description: "[ - The class provides an easy way to build HTTP header. + The class represents a HTTP header, and it provides simple routine + to build it. - You will also find some helper feature to help coding most common usage + You will also find some helper features to help coding most common usages Please, have a look at constants classes such as HTTP_MIME_TYPES @@ -24,6 +25,8 @@ class inherit ITERABLE [READABLE_STRING_8] + HTTP_HEADER_MODIFIER + create make, make_with_count, @@ -116,6 +119,8 @@ feature -- Access result_has_single_ending_cr_lf: Result.count >= 4 implies not Result.substring (Result.count - 3, Result.count).same_string ("%R%N%R%N") end +feature -- Conversion + to_name_value_iterable: ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]] -- Iterable representation of the header entries. local @@ -132,7 +137,7 @@ feature -- Access Result := res end -feature -- Conversion +feature -- append_string_to (a_result: STRING_8) -- Append current as string representation to `a_result' @@ -250,60 +255,6 @@ feature -- Header: merging end end -feature -- Status report - - has, has_header_named (a_name: READABLE_STRING_8): BOOLEAN - -- Has header item for `n'? - do - Result := across headers as c some has_same_header_name (c.item, a_name) end - end - - has_content_length: BOOLEAN - -- Has header "Content-Length" - do - Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_length) - end - - has_content_type: BOOLEAN - -- Has header "Content-Type" - do - Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_type) - end - - has_transfer_encoding_chunked: BOOLEAN - -- Has "Transfer-Encoding: chunked" header - do - if has_header_named ({HTTP_HEADER_NAMES}.header_transfer_encoding) then - Result := attached header_named_value ({HTTP_HEADER_NAMES}.header_transfer_encoding) as v and then v.same_string (str_chunked) - end - end - -feature -- Access - - header_named_value (a_name: READABLE_STRING_8): detachable STRING_8 - -- First header item found for `a_name' if any - require - has_header: has_header_named (a_name) - local - n: INTEGER - l_line: READABLE_STRING_8 - do - n := a_name.count - - across - headers as ic - until - Result /= Void - loop - l_line := ic.item - if has_same_header_name (l_line, a_name) then - Result := l_line.substring (n + 2, l_line.count) - Result.left_adjust - Result.right_adjust - end - end - end - feature -- Removal remove_header_named (a_name: READABLE_STRING_8) @@ -334,375 +285,16 @@ feature -- Header change: general -- Add header `h' -- if it already exists, there will be multiple header with same name -- which can also be valid - require - h_not_empty: not h.is_empty do headers.force (h) end put_header (h: READABLE_STRING_8) -- Add header `h' or replace existing header of same header name - require - h_not_empty: not h.is_empty do force_header_by_name (header_name_colon (h), h) end - add_header_key_value (k,v: READABLE_STRING_8) - -- Add header `k:v'. - -- If it already exists, there will be multiple header with same name - -- which can also be valid - local - s: STRING_8 - do - create s.make (k.count + 2 + v.count) - s.append (k) - s.append (colon_space) - s.append (v) - add_header (s) - ensure - added: has_header_named (k) - end - - put_header_key_value (k,v: READABLE_STRING_8) - -- Add header `k:v', or replace existing header of same header name/key - local - s: STRING_8 - do - create s.make (k.count + 2 + v.count) - s.append (k) - s.append (colon_space) - s.append (v) - put_header (s) - ensure - added: has_header_named (k) - end - - put_header_key_values (k: READABLE_STRING_8; a_values: ITERABLE [READABLE_STRING_8]; a_separator: detachable READABLE_STRING_8) - -- Add header `k: a_values', or replace existing header of same header values/key. - -- Use `comma_space' as default separator if `a_separator' is Void or empty. - local - s: STRING_8 - l_separator: READABLE_STRING_8 - do - if a_separator /= Void and then not a_separator.is_empty then - l_separator := a_separator - else - l_separator := comma_space - end - create s.make_empty - across - a_values as c - loop - if not s.is_empty then - s.append_string (l_separator) - end - s.append (c.item) - end - if not s.is_empty then - put_header_key_value (k, s) - end - ensure - added: has_header_named (k) - end - -feature -- Content related header - - put_content_type (t: READABLE_STRING_8) - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, t) - end - - add_content_type (t: READABLE_STRING_8) - -- same as `put_content_type', but allow multiple definition of "Content-Type" - do - add_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, t) - end - - put_content_type_with_parameters (t: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) - local - s: STRING_8 - do - if a_params /= Void and then not a_params.is_empty then - create s.make_from_string (t) - across - a_params as p - loop - if attached p.item as nv then - s.append_character (';') - s.append_character (' ') - s.append (nv.name) - s.append_character ('=') - s.append_character ('%"') - s.append (nv.value) - s.append_character ('%"') - end - end - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s) - else - put_content_type (t) - end - end - - add_content_type_with_parameters (t: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) - local - s: STRING_8 - do - if a_params /= Void and then not a_params.is_empty then - create s.make_from_string (t) - across - a_params as p - loop - if attached p.item as nv then - s.append_character (';') - s.append_character (' ') - s.append (nv.name) - s.append_character ('=') - s.append_character ('%"') - s.append (nv.value) - s.append_character ('%"') - end - end - add_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s) - else - add_content_type (t) - end - end - - put_content_type_with_charset (t: READABLE_STRING_8; c: READABLE_STRING_8) - do - put_content_type_with_parameters (t, <<["charset", c]>>) - end - - add_content_type_with_charset (t: READABLE_STRING_8; c: READABLE_STRING_8) - -- same as `put_content_type_with_charset', but allow multiple definition of "Content-Type" - do - add_content_type_with_parameters (t, <<["charset", c]>>) - end - - put_content_type_with_name (t: READABLE_STRING_8; n: READABLE_STRING_8) - do - put_content_type_with_parameters (t, <<["name", n]>>) - end - - add_content_type_with_name (t: READABLE_STRING_8; n: READABLE_STRING_8) - -- same as `put_content_type_with_name', but allow multiple definition of "Content-Type" - do - add_content_type_with_parameters (t, <<["name", n]>>) - end - - put_content_length (n: INTEGER) - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_length, n.out) - end - - put_content_transfer_encoding (a_mechanism: READABLE_STRING_8) - -- Put "Content-Transfer-Encoding" header with for instance "binary" - --| encoding := "Content-Transfer-Encoding" ":" mechanism - --| - --| mechanism := "7bit" ; case-insensitive - --| / "quoted-printable" - --| / "base64" - --| / "8bit" - --| / "binary" - --| / x-token - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) - end - - put_content_language (a_lang: READABLE_STRING_8) - -- Put "Content-Language" header of value `a_lang'. - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang) - end - - put_content_encoding (a_enc: READABLE_STRING_8) - -- Put "Content-Encoding" header of value `a_enc'. - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_encoding, a_enc) - end - - put_transfer_encoding (a_enc: READABLE_STRING_8) - -- Put "Transfer-Encoding" header with for instance "chunked" - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_transfer_encoding, a_enc) - end - - put_transfer_encoding_binary - -- Put "Transfer-Encoding: binary" header - do - put_transfer_encoding (str_binary) - end - - put_transfer_encoding_chunked - -- Put "Transfer-Encoding: chunked" header - do - put_transfer_encoding (str_chunked) - end - - put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8) - -- Put "Content-Disposition" header - --| See RFC2183 - --| disposition := "Content-Disposition" ":" - --| disposition-type - --| *(";" disposition-parm) - --| disposition-type := "inline" - --| / "attachment" - --| / extension-token - --| ; values are not case-sensitive - --| disposition-parm := filename-parm - --| / creation-date-parm - --| / modification-date-parm - --| / read-date-parm - --| / size-parm - --| / parameter - --| filename-parm := "filename" "=" value - --| creation-date-parm := "creation-date" "=" quoted-date-time - --| modification-date-parm := "modification-date" "=" quoted-date-time - --| read-date-parm := "read-date" "=" quoted-date-time - --| size-parm := "size" "=" 1*DIGIT - --| quoted-date-time := quoted-string - --| ; contents MUST be an RFC 822 `date-time' - --| ; numeric timezones (+HHMM or -HHMM) MUST be used - do - if a_params /= Void then - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_disposition, a_type + semi_colon_space + a_params) - else - put_header_key_value ({HTTP_HEADER_NAMES}.header_content_disposition, a_type) - end - end - -feature -- Content-type helpers - - put_content_type_text_css do put_content_type ({HTTP_MIME_TYPES}.text_css) end - put_content_type_text_csv do put_content_type ({HTTP_MIME_TYPES}.text_csv) end - put_content_type_text_html do put_content_type ({HTTP_MIME_TYPES}.text_html) end - put_content_type_text_javascript do put_content_type ({HTTP_MIME_TYPES}.text_javascript) end - put_content_type_text_json do put_content_type ({HTTP_MIME_TYPES}.text_json) end - put_content_type_text_plain do put_content_type ({HTTP_MIME_TYPES}.text_plain) end - put_content_type_text_xml do put_content_type ({HTTP_MIME_TYPES}.text_xml) end - - put_content_type_application_json do put_content_type ({HTTP_MIME_TYPES}.application_json) end - put_content_type_application_javascript do put_content_type ({HTTP_MIME_TYPES}.application_javascript) end - put_content_type_application_zip do put_content_type ({HTTP_MIME_TYPES}.application_zip) end - put_content_type_application_pdf do put_content_type ({HTTP_MIME_TYPES}.application_pdf) end - - put_content_type_image_gif do put_content_type ({HTTP_MIME_TYPES}.image_gif) end - put_content_type_image_png do put_content_type ({HTTP_MIME_TYPES}.image_png) end - put_content_type_image_jpg do put_content_type ({HTTP_MIME_TYPES}.image_jpg) end - put_content_type_image_svg_xml do put_content_type ({HTTP_MIME_TYPES}.image_svg_xml) end - - put_content_type_message_http do put_content_type ({HTTP_MIME_TYPES}.message_http) end - - put_content_type_multipart_mixed do put_content_type ({HTTP_MIME_TYPES}.multipart_mixed) end - put_content_type_multipart_alternative do put_content_type ({HTTP_MIME_TYPES}.multipart_alternative) end - put_content_type_multipart_related do put_content_type ({HTTP_MIME_TYPES}.multipart_related) end - put_content_type_multipart_form_data do put_content_type ({HTTP_MIME_TYPES}.multipart_form_data) end - put_content_type_multipart_signed do put_content_type ({HTTP_MIME_TYPES}.multipart_signed) end - put_content_type_multipart_encrypted do put_content_type ({HTTP_MIME_TYPES}.multipart_encrypted) end - put_content_type_application_x_www_form_encoded do put_content_type ({HTTP_MIME_TYPES}.application_x_www_form_encoded) end - -feature -- Cross-Origin Resource Sharing - - put_access_control_allow_origin (s: READABLE_STRING_8) - -- Put "Access-Control-Allow-Origin" header. - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_origin, s) - end - - put_access_control_allow_all_origin - -- Put "Access-Control-Allow-Origin: *" header. - do - put_access_control_allow_origin ("*") - end - - put_access_control_allow_methods (a_methods: ITERABLE [READABLE_STRING_8]) - -- If `a_methods' is not empty, put `Access-Control-Allow-Methods' header with list `a_methods' of methods - do - put_header_key_values ({HTTP_HEADER_NAMES}.header_access_control_allow_methods, a_methods, Void) - end - - put_access_control_allow_headers (s: READABLE_STRING_8) - -- Put "Access-Control-Allow-Headers" header. - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_headers, s) - end - -feature -- Method related - - put_allow (a_methods: ITERABLE [READABLE_STRING_8]) - -- If `a_methods' is not empty, put `Allow' header with list `a_methods' of methods - do - put_header_key_values ({HTTP_HEADER_NAMES}.header_allow, a_methods, Void) - end - -feature -- Date - - put_date (s: READABLE_STRING_8) - -- Put "Date: " header - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_date, s) - end - - put_current_date - -- Put current date time with "Date" header - do - put_utc_date (create {DATE_TIME}.make_now_utc) - end - - put_utc_date (a_utc_date: DATE_TIME) - -- Put UTC date time `dt' with "Date" header - do - put_date (date_to_rfc1123_http_date_format (a_utc_date)) - end - - put_last_modified (a_utc_date: DATE_TIME) - -- Put UTC date time `dt' with "Date" header - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_last_modified, date_to_rfc1123_http_date_format (a_utc_date)) - end - -feature -- Authorization - - put_authorization (s: READABLE_STRING_8) - -- Put authorization `s' with "Authorization" header - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_authorization, s) - end - -feature -- Others - - put_expires (sec: INTEGER) - do - put_expires_string (sec.out) - end - - put_expires_string (s: STRING) - do - put_header_key_value ("Expires", s) - end - - put_expires_date (a_utc_date: DATE_TIME) - do - put_header_key_value ("Expires", date_to_rfc1123_http_date_format (a_utc_date)) - end - - put_cache_control (s: READABLE_STRING_8) - -- `s' could be for instance "no-cache, must-revalidate" - do - put_header_key_value ("Cache-Control", s) - end - - put_pragma (s: READABLE_STRING_8) - do - put_header_key_value ("Pragma", s) - end - - put_pragma_no_cache - do - put_pragma ("no-cache") - end - feature -- Redirection remove_location @@ -711,79 +303,7 @@ feature -- Redirection remove_header_named ({HTTP_HEADER_NAMES}.header_location) end - put_location (a_location: READABLE_STRING_8) - -- Tell the client the new location `a_location' - require - a_location_valid: not a_location.is_empty - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_location, a_location) - end - - put_refresh (a_location: READABLE_STRING_8; a_timeout_in_seconds: INTEGER) - -- Tell the client to refresh page with `a_location' after `a_timeout_in_seconds' in seconds - require - a_location_valid: not a_location.is_empty - do - put_header_key_value ({HTTP_HEADER_NAMES}.header_refresh, a_timeout_in_seconds.out + "; url=" + a_location) - end - -feature -- Cookie - - put_cookie (key, value: READABLE_STRING_8; expiration, path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) - -- Set a cookie on the client's machine - -- with key 'key' and value 'value'. - -- Note: you should avoid using "localhost" as `domain' for local cookies - -- since they are not always handled by browser (for instance Chrome) - require - make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) - domain_without_port_info: domain /= Void implies domain.index_of (':', 1) = 0 - local - s: STRING - do - s := {HTTP_HEADER_NAMES}.header_set_cookie + colon_space + key + "=" + value - if - domain /= Void and then not domain.same_string ("localhost") - then - s.append ("; Domain=") - s.append (domain) - end - if path /= Void then - s.append ("; Path=") - s.append (path) - end - if expiration /= Void then - s.append ("; Expires=") - s.append (expiration) - end - if secure then - s.append ("; Secure") - end - if http_only then - s.append ("; HttpOnly") - end - add_header (s) - end - - put_cookie_with_expiration_date (key, value: READABLE_STRING_8; expiration: DATE_TIME; path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) - -- Set a cookie on the client's machine - -- with key 'key' and value 'value'. - require - make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) - do - put_cookie (key, value, date_to_rfc1123_http_date_format (expiration), path, domain, secure, http_only) - end - -feature {NONE} -- Implementation: Header - - has_same_header_name (h: READABLE_STRING_8; a_name: READABLE_STRING_8): BOOLEAN - -- Header line `h' has same name as `a_name' ? - do - if h.starts_with (a_name) then - if h.valid_index (a_name.count + 1) then - Result := h[a_name.count + 1] = ':' - end - end - end +feature {NONE} -- Implementation: Header change force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8) -- Add header `h' or replace existing header of same header name `n' @@ -811,6 +331,27 @@ feature {NONE} -- Implementation: Header end end +feature {NONE} -- Implementation: Header conversion + + append_line_to (a_line: READABLE_STRING_8; h: STRING_8) + -- Append header line `a_line' to string `h'. + --| this is used to build the header text + require + not_ending_with_new_line: not a_line.ends_with_general ("%N") + do + h.append_string (a_line) + append_end_of_line_to (h) + end + + append_end_of_line_to (h: STRING_8) + -- Append the CRLN end of header line to string `h'. + do + h.append_character ('%R') + h.append_character ('%N') + end + +feature {NONE} -- Implementation: Header queries + header_name_colon (h: READABLE_STRING_8): detachable STRING_8 -- If any, header's name with colon --| ex: for "Foo-bar: something", this will return "Foo-bar:" @@ -870,51 +411,6 @@ feature {NONE} -- Implementation: Header end end -feature {NONE} -- Implementation - - append_line_to (s: READABLE_STRING_8; h: STRING_8) - do - h.append_string (s) - append_end_of_line_to (h) - end - - append_end_of_line_to (h: STRING_8) - do - h.append_character ('%R') - h.append_character ('%N') - end - -feature -- Access - - 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 {NONE} -- Constants - - str_binary: STRING = "binary" - str_chunked: STRING = "chunked" - - colon_space: IMMUTABLE_STRING_8 - once - create Result.make_from_string (": ") - end - - semi_colon_space: IMMUTABLE_STRING_8 - once - create Result.make_from_string ("; ") - end - - comma_space: IMMUTABLE_STRING_8 - once - create Result.make_from_string (", ") - end - note copyright: "2011-2014, 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/src/http_header_modifier.e b/library/network/protocol/http/src/http_header_modifier.e new file mode 100644 index 00000000..3af4e45b --- /dev/null +++ b/library/network/protocol/http/src/http_header_modifier.e @@ -0,0 +1,667 @@ +note + description: "[ + The class provides an easy way to build and modify HTTP header text + thanks to add_header (..) and put_header (..) + + You will also find some helper features to help coding most common usages + + Please, have a look at constants classes such as + HTTP_MIME_TYPES + HTTP_HEADER_NAMES + HTTP_STATUS_CODE + HTTP_REQUEST_METHODS + (or HTTP_CONSTANTS which groups them for convenience) + + Note the return status code is not part of the HTTP header + ]" + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date$" + revision: "$Revision$" + +deferred class + HTTP_HEADER_MODIFIER + +inherit + ITERABLE [READABLE_STRING_8] + +feature -- Access: deferred + + new_cursor: INDEXABLE_ITERATION_CURSOR [READABLE_STRING_8] + -- Fresh cursor associated with current structure. + deferred + end + +feature -- Header change: deferred + + add_header (h: READABLE_STRING_8) + -- Add header `h' + -- if it already exists, there will be multiple header with same name + -- which can also be valid + require + h_not_empty: h /= Void and then not h.is_empty + deferred + end + + put_header (h: READABLE_STRING_8) + -- Add header `h' or replace existing header of same header name + require + h_not_empty: h /= Void and then not h.is_empty + deferred + end + +feature -- Status report + + has, has_header_named (a_name: READABLE_STRING_8): BOOLEAN + -- Has header item for `n'? + local + ic: like new_cursor + do + from + ic := new_cursor + until + ic.after or Result + loop + Result := has_same_header_name (ic.item, a_name) + ic.forth + end + end + + has_content_length: BOOLEAN + -- Has header "Content-Length" + do + Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_length) + end + + has_content_type: BOOLEAN + -- Has header "Content-Type" + do + Result := has_header_named ({HTTP_HEADER_NAMES}.header_content_type) + end + + has_transfer_encoding_chunked: BOOLEAN + -- Has "Transfer-Encoding: chunked" header + do + if has_header_named ({HTTP_HEADER_NAMES}.header_transfer_encoding) then + Result := attached item ({HTTP_HEADER_NAMES}.header_transfer_encoding) as v and then v.same_string (str_chunked) + end + end + +feature -- Access + + item alias "[]" (a_header_name: READABLE_STRING_8): detachable READABLE_STRING_8 assign force + -- First header item found for `a_name' if any + local + res: STRING_8 + n: INTEGER + l_line: READABLE_STRING_8 + ic: like new_cursor + do + n := a_header_name.count + + from + ic := new_cursor + until + ic.after or Result /= Void + loop + l_line := ic.item + if has_same_header_name (l_line, a_header_name) then + res := l_line.substring (n + 2, l_line.count) + res.left_adjust + res.right_adjust + Result := res + end + ic.forth + end + end + + header_named_value (a_name: READABLE_STRING_8): like item + -- First header item found for `a_name' if any + obsolete + "Use `item' [2014-03]" + do + Result := item (a_name) + end + +feature -- Header change: general + + force (a_value: detachable READABLE_STRING_8; a_header_name: READABLE_STRING_8) + -- Put header `a_header_name:a_value' or replace existing header of name `a_header_name'. + --| this is used as assigner for `item' + do + if a_value = Void then + put_header_key_value (a_header_name, "") + else + put_header_key_value (a_header_name, a_value) + end + end + + add_header_key_value (a_header_name, a_value: READABLE_STRING_8) + -- Add header `a_header_name:a_value'. + -- If it already exists, there will be multiple header with same name + -- which can also be valid + local + s: STRING_8 + do + create s.make (a_header_name.count + 2 + a_value.count) + s.append (a_header_name) + s.append (colon_space) + s.append (a_value) + add_header (s) + ensure + added: has_header_named (a_header_name) + end + + put_header_key_value (a_header_name, a_value: READABLE_STRING_8) + -- Add header `a_header_name:a_value', or replace existing header of same header name/key + local + s: STRING_8 + do + create s.make (a_header_name.count + 2 + a_value.count) + s.append (a_header_name) + s.append (colon_space) + s.append (a_value) + put_header (s) + ensure + added: has_header_named (a_header_name) + end + + put_header_key_values (a_header_name: READABLE_STRING_8; a_values: ITERABLE [READABLE_STRING_8]; a_separator: detachable READABLE_STRING_8) + -- Add header `a_header_name: a_values', or replace existing header of same header values/key. + -- Use `comma_space' as default separator if `a_separator' is Void or empty. + local + s: STRING_8 + l_separator: READABLE_STRING_8 + do + if a_separator /= Void and then not a_separator.is_empty then + l_separator := a_separator + else + l_separator := comma_space + end + create s.make_empty + across + a_values as c + loop + if not s.is_empty then + s.append_string (l_separator) + end + s.append (c.item) + end + if not s.is_empty then + put_header_key_value (a_header_name, s) + end + ensure + added: has_header_named (a_header_name) + end + +feature -- Content related header + + put_content_type (a_content_type: READABLE_STRING_8) + -- Put header line "Content-Type:" + type `a_content_type' + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, a_content_type) + end + + add_content_type (a_content_type: READABLE_STRING_8) + -- same as `put_content_type', but allow multiple definition of "Content-Type" + do + add_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, a_content_type) + end + + put_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Put header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params' + --| note: see `put_content_type_with_charset' for examples. + local + s: STRING_8 + do + if a_params /= Void and then not a_params.is_empty then + create s.make_from_string (a_content_type) + across + a_params as p + loop + if attached p.item as nv then + s.append_character (';') + s.append_character (' ') + s.append (nv.name) + s.append_character ('=') + s.append_character ('%"') + s.append (nv.value) + s.append_character ('%"') + end + end + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s) + else + put_content_type (a_content_type) + end + end + + add_content_type_with_parameters (a_content_type: READABLE_STRING_8; a_params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]) + -- Add header line "Content-Type:" + type `a_content_type' and extra paramaters `a_params'. + --| note: see `put_content_type_with_charset' for examples. + local + s: STRING_8 + do + if a_params /= Void and then not a_params.is_empty then + create s.make_from_string (a_content_type) + across + a_params as p + loop + if attached p.item as nv then + s.append_character (';') + s.append_character (' ') + s.append (nv.name) + s.append_character ('=') + s.append_character ('%"') + s.append (nv.value) + s.append_character ('%"') + end + end + add_header_key_value ({HTTP_HEADER_NAMES}.header_content_type, s) + else + add_content_type (a_content_type) + end + end + + put_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8) + -- Put content type `a_content_type' with `a_charset' as "charset" parameter. + do + put_content_type_with_parameters (a_content_type, <<["charset", a_charset]>>) + end + + add_content_type_with_charset (a_content_type: READABLE_STRING_8; a_charset: READABLE_STRING_8) + -- Same as `put_content_type_with_charset', but allow multiple definition of "Content-Type". + do + add_content_type_with_parameters (a_content_type, <<["charset", a_charset]>>) + end + + put_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8) + -- Put content type `a_content_type' with `a_name' as "name" parameter. + do + put_content_type_with_parameters (a_content_type, <<["name", a_name]>>) + end + + add_content_type_with_name (a_content_type: READABLE_STRING_8; a_name: READABLE_STRING_8) + -- same as `put_content_type_with_name', but allow multiple definition of "Content-Type" + do + add_content_type_with_parameters (a_content_type, <<["name", a_name]>>) + end + + put_content_length (a_length: INTEGER) + -- Put "Content-Length:" + length `a_length'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_length, a_length.out) + end + + put_content_transfer_encoding (a_mechanism: READABLE_STRING_8) + -- Put "Content-Transfer-Encoding" header with `a_mechanism' + --| encoding := "Content-Transfer-Encoding" ":" mechanism + --| + --| mechanism := "7bit" ; case-insensitive + --| / "quoted-printable" + --| / "base64" + --| / "8bit" + --| / "binary" + --| / x-token + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_transfer_encoding, a_mechanism) + end + + put_content_language (a_lang: READABLE_STRING_8) + -- Put "Content-Language" header of value `a_lang'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_language, a_lang) + end + + put_content_encoding (a_encoding: READABLE_STRING_8) + -- Put "Content-Encoding" header of value `a_encoding'. + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_encoding, a_encoding) + end + + put_transfer_encoding (a_encoding: READABLE_STRING_8) + -- Put "Transfer-Encoding" header with `a_encoding' value. + --| for instance "chunked" + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_transfer_encoding, a_encoding) + end + + put_transfer_encoding_binary + -- Put "Transfer-Encoding: binary" header + do + put_transfer_encoding (str_binary) + end + + put_transfer_encoding_chunked + -- Put "Transfer-Encoding: chunked" header + do + put_transfer_encoding (str_chunked) + end + + put_content_disposition (a_type: READABLE_STRING_8; a_params: detachable READABLE_STRING_8) + -- Put "Content-Disposition" header + --| See RFC2183 + --| disposition := "Content-Disposition" ":" + --| disposition-type + --| *(";" disposition-parm) + --| disposition-type := "inline" + --| / "attachment" + --| / extension-token + --| ; values are not case-sensitive + --| disposition-parm := filename-parm + --| / creation-date-parm + --| / modification-date-parm + --| / read-date-parm + --| / size-parm + --| / parameter + --| filename-parm := "filename" "=" value + --| creation-date-parm := "creation-date" "=" quoted-date-time + --| modification-date-parm := "modification-date" "=" quoted-date-time + --| read-date-parm := "read-date" "=" quoted-date-time + --| size-parm := "size" "=" 1*DIGIT + --| quoted-date-time := quoted-string + --| ; contents MUST be an RFC 822 `date-time' + --| ; numeric timezones (+HHMM or -HHMM) MUST be used + do + if a_params /= Void then + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_disposition, a_type + semi_colon_space + a_params) + else + put_header_key_value ({HTTP_HEADER_NAMES}.header_content_disposition, a_type) + end + end + +feature -- Content-type helpers + + put_content_type_text_css do put_content_type ({HTTP_MIME_TYPES}.text_css) end + put_content_type_text_csv do put_content_type ({HTTP_MIME_TYPES}.text_csv) end + put_content_type_text_html do put_content_type ({HTTP_MIME_TYPES}.text_html) end + put_content_type_text_javascript do put_content_type ({HTTP_MIME_TYPES}.text_javascript) end + put_content_type_text_json do put_content_type ({HTTP_MIME_TYPES}.text_json) end + put_content_type_text_plain do put_content_type ({HTTP_MIME_TYPES}.text_plain) end + put_content_type_text_xml do put_content_type ({HTTP_MIME_TYPES}.text_xml) end + + put_content_type_application_json do put_content_type ({HTTP_MIME_TYPES}.application_json) end + put_content_type_application_javascript do put_content_type ({HTTP_MIME_TYPES}.application_javascript) end + put_content_type_application_zip do put_content_type ({HTTP_MIME_TYPES}.application_zip) end + put_content_type_application_pdf do put_content_type ({HTTP_MIME_TYPES}.application_pdf) end + + put_content_type_image_gif do put_content_type ({HTTP_MIME_TYPES}.image_gif) end + put_content_type_image_png do put_content_type ({HTTP_MIME_TYPES}.image_png) end + put_content_type_image_jpg do put_content_type ({HTTP_MIME_TYPES}.image_jpg) end + put_content_type_image_svg_xml do put_content_type ({HTTP_MIME_TYPES}.image_svg_xml) end + + put_content_type_message_http do put_content_type ({HTTP_MIME_TYPES}.message_http) end + + put_content_type_multipart_mixed do put_content_type ({HTTP_MIME_TYPES}.multipart_mixed) end + put_content_type_multipart_alternative do put_content_type ({HTTP_MIME_TYPES}.multipart_alternative) end + put_content_type_multipart_related do put_content_type ({HTTP_MIME_TYPES}.multipart_related) end + put_content_type_multipart_form_data do put_content_type ({HTTP_MIME_TYPES}.multipart_form_data) end + put_content_type_multipart_signed do put_content_type ({HTTP_MIME_TYPES}.multipart_signed) end + put_content_type_multipart_encrypted do put_content_type ({HTTP_MIME_TYPES}.multipart_encrypted) end + put_content_type_application_x_www_form_encoded do put_content_type ({HTTP_MIME_TYPES}.application_x_www_form_encoded) end + + put_content_type_utf_8_text_plain do put_content_type_with_charset ({HTTP_MIME_TYPES}.text_plain, "utf-8") end + +feature -- Cross-Origin Resource Sharing + + put_access_control_allow_origin (a_origin: READABLE_STRING_8) + -- Put "Access-Control-Allow-Origin: " + `a_origin' header. + -- `a_origin' specifies a URI that may access the resource + --| for instance "http://example.com" + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_origin, a_origin) + end + + put_access_control_allow_all_origin + -- Put "Access-Control-Allow-Origin: *" header. + do + put_access_control_allow_origin ("*") + end + + put_access_control_allow_credentials (b: BOOLEAN) + -- Indicates whether or not the response to the request can be exposed when the credentials flag is true. + -- When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. + -- Note that simple GET requests are not preflighted, and so if a request is made for a resource with credentials, + -- if this header is not returned with the resource, the response is ignored by the browser and not returned to web content. + -- ex: Access-Control-Allow-Credentials: true | false + do + if b then + put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_Credentials, "true") + else + put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_Credentials, "false") + end + end + + put_access_control_allow_methods (a_methods: ITERABLE [READABLE_STRING_8]) + -- If `a_methods' is not empty, put `Access-Control-Allow-Methods' header with list `a_methods' of methods + -- `a_methods' specifies the method or methods allowed when accessing the resource. + -- This is used in response to a preflight request. + -- ex: Access-Control-Allow-Methods: [, ]* + do + put_header_key_values ({HTTP_HEADER_NAMES}.header_access_control_allow_methods, a_methods, Void) + end + + put_access_control_allow_headers (a_headers: READABLE_STRING_8) + -- Put "Access-Control-Allow-Headers" header. with value `a_headers' + -- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. + -- ex: Access-Control-Allow-Headers: [, ]* + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_access_control_allow_headers, a_headers) + end + + put_access_control_allow_iterable_headers (a_fields: ITERABLE [READABLE_STRING_8]) + -- Put "Access-Control-Allow-Headers" header. with value `a_headers' + -- Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. + -- ex: Access-Control-Allow-Headers: [, ]* + do + put_header_key_values ({HTTP_HEADER_NAMES}.header_access_control_allow_headers, a_fields, Void) + end + +feature -- Method related + + put_allow (a_methods: ITERABLE [READABLE_STRING_8]) + -- If `a_methods' is not empty, put `Allow' header with list `a_methods' of methods + do + put_header_key_values ({HTTP_HEADER_NAMES}.header_allow, a_methods, Void) + end + +feature -- Date + + put_date (a_date: READABLE_STRING_8) + -- Put "Date: " header + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_date, a_date) + end + + put_current_date + -- Put current date time with "Date" header + do + put_utc_date (create {DATE_TIME}.make_now_utc) + end + + put_utc_date (a_utc_date: DATE_TIME) + -- Put UTC date time `a_utc_date' with "Date" header + -- using RFC1123 date formating. + do + put_date (date_to_rfc1123_http_date_format (a_utc_date)) + end + + put_last_modified (a_utc_date: DATE_TIME) + -- Put UTC date time `dt' with "Last-Modified" header + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_last_modified, date_to_rfc1123_http_date_format (a_utc_date)) + end + +feature -- Authorization + + put_authorization (a_authorization: READABLE_STRING_8) + -- Put `a_authorization' with "Authorization" header + -- The Authorization header is constructed as follows: + -- 1. Username and password are combined into a string "username:password". + -- 2. The resulting string literal is then encoded using Base64. + -- 3. The authorization method and a space, i.e. "Basic " is then put before the encoded string. + -- ex: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_authorization, a_authorization) + end + +feature -- Others + + put_expires (a_seconds: INTEGER) + -- Put "Expires" header to `a_seconds' seconds + do + put_expires_string (a_seconds.out) + end + + put_expires_string (a_expires: STRING) + -- Put "Expires" header with `a_expires' string value + do + put_header_key_value ("Expires", a_expires) + end + + put_expires_date (a_utc_date: DATE_TIME) + -- Put "Expires" header with UTC date time value + -- formatted following RFC1123 specification. + do + put_header_key_value ("Expires", date_to_rfc1123_http_date_format (a_utc_date)) + end + + put_cache_control (a_cache_control: READABLE_STRING_8) + -- Put "Cache-Control" header with value `a_cache_control' + --| note: ex "Cache-Control: no-cache, must-revalidate" + do + put_header_key_value ("Cache-Control", a_cache_control) + end + + put_pragma (a_pragma: READABLE_STRING_8) + -- Put "Pragma" header with value `a_pragma' + do + put_header_key_value ("Pragma", a_pragma) + end + + put_pragma_no_cache + -- Put "Pragma" header with "no-cache" a_pragma + do + put_pragma ("no-cache") + end + +feature -- Redirection + + put_location (a_uri: READABLE_STRING_8) + -- Tell the client the new location `a_uri' + -- using "Location" header. + require + a_uri_valid: not a_uri.is_empty + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_location, a_uri) + end + + put_refresh (a_uri: READABLE_STRING_8; a_timeout_in_seconds: INTEGER) + -- Tell the client to refresh page with `a_uri' after `a_timeout_in_seconds' in seconds + -- using "Refresh" header. + require + a_uri_valid: not a_uri.is_empty + do + put_header_key_value ({HTTP_HEADER_NAMES}.header_refresh, a_timeout_in_seconds.out + "; url=" + a_uri) + end + +feature -- Cookie + + put_cookie (key, value: READABLE_STRING_8; expiration, path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) + -- Set a cookie on the client's machine + -- with key 'key' and value 'value'. + -- Note: you should avoid using "localhost" as `domain' for local cookies + -- since they are not always handled by browser (for instance Chrome) + require + make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) + domain_without_port_info: domain /= Void implies domain.index_of (':', 1) = 0 + local + s: STRING + do + s := {HTTP_HEADER_NAMES}.header_set_cookie + colon_space + key + "=" + value + if + domain /= Void and then not domain.same_string ("localhost") + then + s.append ("; Domain=") + s.append (domain) + end + if path /= Void then + s.append ("; Path=") + s.append (path) + end + if expiration /= Void then + s.append ("; Expires=") + s.append (expiration) + end + if secure then + s.append ("; Secure") + end + if http_only then + s.append ("; HttpOnly") + end + add_header (s) + end + + put_cookie_with_expiration_date (key, value: READABLE_STRING_8; expiration: DATE_TIME; path, domain: detachable READABLE_STRING_8; secure, http_only: BOOLEAN) + -- Set a cookie on the client's machine + -- with key 'key' and value 'value'. + require + make_sense: (key /= Void and value /= Void) and then (not key.is_empty and not value.is_empty) + do + put_cookie (key, value, date_to_rfc1123_http_date_format (expiration), path, domain, secure, http_only) + end + + +feature -- Access + + 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 {NONE} -- Implementation + + has_same_header_name (h: READABLE_STRING_8; a_name: READABLE_STRING_8): BOOLEAN + -- Header line `h' has same name as `a_name' ? + do + if h.starts_with (a_name) then + if h.valid_index (a_name.count + 1) then + Result := h[a_name.count + 1] = ':' + end + end + end + +feature {NONE} -- Constants + + str_binary: STRING = "binary" + str_chunked: STRING = "chunked" + + colon_space: IMMUTABLE_STRING_8 + once + create Result.make_from_string (": ") + end + + semi_colon_space: IMMUTABLE_STRING_8 + once + create Result.make_from_string ("; ") + end + + comma_space: IMMUTABLE_STRING_8 + once + create Result.make_from_string (", ") + end + +note + copyright: "2011-2014, 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/src/http_header_names.e b/library/network/protocol/http/src/http_header_names.e index 4bab76e8..f64bdc26 100644 --- a/library/network/protocol/http/src/http_header_names.e +++ b/library/network/protocol/http/src/http_header_names.e @@ -199,17 +199,26 @@ feature -- Cross-Origin Resource Sharing header_access_control_allow_origin: STRING = "Access-Control-Allow-Origin" -- Indicates whether a resource can be shared based by returning -- the value of the Origin request header in the response. - -- | Example: Access-Control-Allow-Origin: http://example.org + --| Example: Access-Control-Allow-Origin: http://example.org + + header_access_control_allow_credentials: STRING = "Access-Control-Allow-Credentials" + -- Indicates whether or not the response to the request can be exposed when the credentials flag is true. + -- When used as part of a response to a preflight request, this indicates whether or not the actual request can be made using credentials. + -- Note that simple GET requests are not preflighted, and so if a request is made for a resource with credentials, + -- if this header is not returned with the resource, the response is ignored by the browser and not returned to web content. + --| Access-Control-Allow-Credentials: true | false header_access_control_allow_methods: STRING = "Access-Control-Allow-Methods" -- Indicates, as part of the response to a preflight request, -- which methods can be used during the actual request. - -- | Example: Access-Control-Allow-Methods: PUT, DELETE + --| Access-Control-Allow-Methods: [, ]* + --| Example: Access-Control-Allow-Methods: PUT, DELETE header_access_control_allow_headers: STRING = "Access-Control-Allow-Headers" -- Indicates, as part of the response to a preflight request, -- which header field names can be used during the actual request. - -- | Example: Access-Control-Allow-Headers: Authorization + --| Access-Control-Allow-Headers: [, ]* + --| Example: Access-Control-Allow-Headers: Authorization feature -- Request or Response header name @@ -265,7 +274,7 @@ feature -- MIME related header_content_transfer_encoding: STRING = "Content-Transfer-Encoding" note - copyright: "2011-2013, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2014, Jocelyn Fiat, 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/policy_driven/wsf_method_helper.e b/library/server/wsf/policy_driven/wsf_method_helper.e index 39b2f8d8..d94e3c17 100644 --- a/library/server/wsf/policy_driven/wsf_method_helper.e +++ b/library/server/wsf/policy_driven/wsf_method_helper.e @@ -376,10 +376,11 @@ feature -- Error reporting local h: HTTP_HEADER m: READABLE_STRING_8 + utf: UTF_CONVERTER do - m := req.error_handler.as_string_representation + m := utf.string_32_to_utf_8_string_8 (req.error_handler.as_string_representation) create h.make - h.put_content_type_text_plain + h.put_content_type_utf_8_text_plain h.put_content_length (m.count) res.set_status_code (req.error_handler.primary_error_code) res.put_header_lines (h) diff --git a/library/server/wsf/router/documentation/wsf_router_self_documentation_message.e b/library/server/wsf/router/documentation/wsf_router_self_documentation_message.e index acd21662..799f3cb6 100644 --- a/library/server/wsf/router/documentation/wsf_router_self_documentation_message.e +++ b/library/server/wsf/router/documentation/wsf_router_self_documentation_message.e @@ -98,8 +98,8 @@ feature {WSF_RESPONSE} -- Output local h: HTTP_HEADER l_description: STRING_8 - l_base_url: STRING_8 - l_api_resource: detachable STRING_8 + l_base_url: READABLE_STRING_8 + l_api_resource: detachable READABLE_STRING_8 do create h.make h.put_content_type_text_html @@ -132,7 +132,7 @@ feature {WSF_RESPONSE} -- Output if attached router.base_url as u then l_base_url := u else - create l_base_url.make_empty + create {STRING_8} l_base_url.make_empty end debug @@ -324,7 +324,7 @@ feature {NONE} -- Implementation end note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/router/wsf_router_item.e b/library/server/wsf/router/wsf_router_item.e index 1800e7b9..00467390 100644 --- a/library/server/wsf/router/wsf_router_item.e +++ b/library/server/wsf/router/wsf_router_item.e @@ -3,8 +3,8 @@ note Entry of WSF_ROUTER It contains - mapping - - request methods - + - request methods + ]" date: "$Date$" revision: "$Revision$" @@ -40,20 +40,23 @@ feature -- Access feature -- Status report - debug_output: STRING + debug_output: READABLE_STRING_GENERAL -- String that should be displayed in debugger to represent `Current'. + local + s: STRING_32 do - create Result.make_from_string (mapping.debug_output) + create s.make_from_string_general (mapping.debug_output) if attached request_methods as mtds then - Result.append_string (" [ ") + s.append_string (" [ ") across mtds as c loop - Result.append_string (c.item) - Result.append_string (" ") + s.append_string (c.item) + s.append_string (" ") end - Result.append_string ("]") + s.append_string ("]") end + Result := s end feature -- Change @@ -68,7 +71,7 @@ invariant mapping_attached: mapping /= Void note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/router/wsf_router_mapping.e b/library/server/wsf/router/wsf_router_mapping.e index 873a25cb..9a2a3135 100644 --- a/library/server/wsf/router/wsf_router_mapping.e +++ b/library/server/wsf/router/wsf_router_mapping.e @@ -48,10 +48,10 @@ feature -- Documentation feature -- Status report - debug_output: STRING + debug_output: READABLE_STRING_GENERAL -- String that should be displayed in debugger to represent `Current'. do - Result := description.as_string_8 + " : " + associated_resource + Result := description + {STRING_32} " : " + associated_resource.to_string_32 end feature -- Status @@ -88,7 +88,7 @@ feature -- Helper end note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/router_context/support/starts_with/wsf_starts_with_context_mapping.e b/library/server/wsf/router_context/support/starts_with/wsf_starts_with_context_mapping.e index a43e19bb..95f54564 100644 --- a/library/server/wsf/router_context/support/starts_with/wsf_starts_with_context_mapping.e +++ b/library/server/wsf/router_context/support/starts_with/wsf_starts_with_context_mapping.e @@ -45,14 +45,14 @@ feature {NONE} -- Execution feature -- Status report - debug_output: STRING + debug_output: READABLE_STRING_GENERAL -- String that should be displayed in debugger to represent `Current'. do Result := Precursor + " {" + ({C}).name + "}" end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/router_context/wsf_router_context_mapping.e b/library/server/wsf/router_context/wsf_router_context_mapping.e index bf7f8895..87ea3a67 100644 --- a/library/server/wsf/router_context/wsf_router_context_mapping.e +++ b/library/server/wsf/router_context/wsf_router_context_mapping.e @@ -22,14 +22,14 @@ feature -- Access feature -- Status report - debug_output: STRING + debug_output: READABLE_STRING_GENERAL -- String that should be displayed in debugger to represent `Current'. do Result := Precursor + " {" + ({C}).name + "}" end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/session/wsf_cookie_session.e b/library/server/wsf/session/wsf_cookie_session.e index 0e499f6d..aced8b95 100644 --- a/library/server/wsf/session/wsf_cookie_session.e +++ b/library/server/wsf/session/wsf_cookie_session.e @@ -60,15 +60,16 @@ feature {NONE} -- Initialization feature -- Cookie - apply_to (h: HTTP_HEADER; a_request: WSF_REQUEST; a_path: detachable READABLE_STRING_8) + apply_to (h: HTTP_HEADER_MODIFIER; a_request: WSF_REQUEST; a_path: detachable READABLE_STRING_8) + -- local dt: detachable DATE_TIME l_domain: detachable READABLE_STRING_8 do l_domain := a_request.server_name if l_domain.same_string ("localhost") then - -- Due to limitation of specific handling of local cookies - -- it is recommended to use Void or IP instead of "localhost" + -- Due to limitation of specific handling of local cookies + -- it is recommended to use Void or IP instead of "localhost" l_domain := Void end if is_destroyed then @@ -79,13 +80,18 @@ feature -- Cookie create dt.make_now_utc dt.day_add (40) end - h.put_cookie_with_expiration_date (cookie_name, uuid, dt, a_path, l_domain, False, True) + h.put_cookie_with_expiration_date (cookie_name, id, dt, a_path, l_domain, False, True) end end cookie_name: READABLE_STRING_8 -feature -- Access +feature -- Access + + id: READABLE_STRING_8 + do + Result := uuid + end uuid: READABLE_STRING_8 @@ -135,8 +141,8 @@ feature {NONE} -- Storage load do - if manager.session_exists (uuid) then - if attached manager.session_data (uuid) as d then + if manager.session_exists (id) then + if attached manager.session_data (id) as d then data := d set_expiration (data.expiration) else @@ -177,7 +183,7 @@ feature {NONE} -- Implementation end note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/session/wsf_fs_session_manager.e b/library/server/wsf/session/wsf_fs_session_manager.e index e478ca58..88eb66e3 100644 --- a/library/server/wsf/session/wsf_fs_session_manager.e +++ b/library/server/wsf/session/wsf_fs_session_manager.e @@ -68,7 +68,7 @@ feature -- Persistence delete_session (a_session) else ensure_session_folder_exists - create f.make_with_path (file_name (a_session.uuid)) + create f.make_with_path (file_name (a_session.id)) if not f.exists or else f.is_writable then f.create_read_write a_session.data.set_expiration (a_session.expiration) @@ -91,7 +91,7 @@ feature -- Persistence rescued: BOOLEAN do if not rescued then - create f.make_with_path (file_name (a_session.uuid)) + create f.make_with_path (file_name (a_session.id)) if f.exists then f.delete end diff --git a/library/server/wsf/session/wsf_session.e b/library/server/wsf/session/wsf_session.e index 52a7f7aa..ab2f51d8 100644 --- a/library/server/wsf/session/wsf_session.e +++ b/library/server/wsf/session/wsf_session.e @@ -7,26 +7,43 @@ note deferred class WSF_SESSION -feature -- Access +feature -- Access + + id: READABLE_STRING_8 + -- Session identifier. + deferred + end uuid: READABLE_STRING_8 + obsolete + "Use `id' which is more general [2014-03]" deferred end data: WSF_SESSION_DATA + -- Data associated with current session. deferred end expiration: detachable DATE_TIME + -- Expiration date for current session, if any. deferred end expired: BOOLEAN + -- Is current session expired now? + do + Result := expired_at (create {DATE_TIME}.make_now_utc) + end + + expired_at (dt: DATE_TIME): BOOLEAN + -- Is current session expired at date and time `dt'? do if attached expiration as e then - Result := e < (create {DATE_TIME}.make_now_utc) + Result := e < (dt) end end + feature -- status is_pending: BOOLEAN @@ -36,27 +53,32 @@ feature -- status end is_destroyed: BOOLEAN + -- Is current session in destroyed state? deferred end feature -- Entries - table: TABLE_ITERABLE [detachable ANY, READABLE_STRING_32] + table: TABLE_ITERABLE [detachable ANY, READABLE_STRING_GENERAL] + -- Table of session data indexed by key do Result := data end - item (k: READABLE_STRING_GENERAL): detachable ANY + item alias "[]" (k: READABLE_STRING_GENERAL): detachable ANY assign remember + -- Session value associated with key `k'. do Result := data.item (table_key (k)) end remember (v: detachable ANY; k: READABLE_STRING_GENERAL) + -- Remember value `v' in association with key `k'. do data.force (v, table_key (k)) end forget (k: READABLE_STRING_GENERAL) + -- Forget about value associated with key `k'. do data.remove (table_key (k)) end @@ -71,19 +93,30 @@ feature {NONE} -- Implementation feature -- Control destroy + -- Destroy current session. deferred end commit + -- Commit current session, including data associated. deferred end - apply_to (h: HTTP_HEADER; req: WSF_REQUEST; a_path: detachable READABLE_STRING_8) + apply_to (h: HTTP_HEADER_MODIFIER; req: WSF_REQUEST; a_path: detachable READABLE_STRING_8) + -- Apply current session to header `h' for request `req' and optional path `a_path'. + -- note: either use `apply_to' or `apply', not both. deferred end + apply (req: WSF_REQUEST; res: WSF_RESPONSE; a_path: detachable READABLE_STRING_8) + -- Apply current session to response `res' for request `req' and optional path `a_path'. + -- note: either use `apply' or `apply_to', not both. + do + apply_to (res.header, req, a_path) + end + note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/session/wsf_session_data.e b/library/server/wsf/session/wsf_session_data.e index a7675ec0..8eed0ef0 100644 --- a/library/server/wsf/session/wsf_session_data.e +++ b/library/server/wsf/session/wsf_session_data.e @@ -8,7 +8,13 @@ class WSF_SESSION_DATA inherit - HASH_TABLE [detachable ANY, READABLE_STRING_32] + STRING_TABLE [detachable ANY] + rename + make as old_make, + make_caseless as make + redefine + empty_duplicate + end create make @@ -24,4 +30,22 @@ feature -- Element change expiration := dt end +feature {NONE} -- Duplication + + empty_duplicate (n: INTEGER): like Current + -- Create an empty copy of Current that can accommodate `n' items + do + create Result.make (n) + end + +note + copyright: "2011-2014, 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 diff --git a/library/server/wsf/src/response/wsf_trace_response.e b/library/server/wsf/src/response/wsf_trace_response.e index d4c2709e..4b08c80d 100644 --- a/library/server/wsf/src/response/wsf_trace_response.e +++ b/library/server/wsf/src/response/wsf_trace_response.e @@ -45,7 +45,7 @@ feature {WSF_RESPONSE} -- Output req := request if attached req.raw_header_data as l_header then create s.make (l_header.count) - s.append (l_header.to_string_8) + s.append (l_header.to_string_8) -- Is valid as string 8, as ensured by req.raw_header_data s.append_character ('%N') else create s.make_empty @@ -99,7 +99,7 @@ feature {WSF_RESPONSE} -- Output end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2014, 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/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index 35b4286e..6e4469b9 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -1287,41 +1287,43 @@ feature {NONE} -- Cookies local i,j,p,n: INTEGER l_cookies: like internal_cookies_table + s32: READABLE_STRING_32 k,v,s: STRING do l_cookies := internal_cookies_table if l_cookies = Void then + create l_cookies.make_equal (0) if attached {WSF_STRING} meta_variable ({WSF_META_NAMES}.http_cookie) as val then - s := val.value - create l_cookies.make_equal (5) - from - n := s.count - p := 1 - i := 1 - until - p < 1 - loop - i := s.index_of ('=', p) - if i > 0 then - j := s.index_of (';', i) - if j = 0 then - j := n + 1 - k := s.substring (p, i - 1) - v := s.substring (i + 1, n) + s32 := val.value + if s32.is_valid_as_string_8 then + s := s32.to_string_8 + from + n := s.count + p := 1 + i := 1 + until + p < 1 + loop + i := s.index_of ('=', p) + if i > 0 then + j := s.index_of (';', i) + if j = 0 then + j := n + 1 + k := s.substring (p, i - 1) + v := s.substring (i + 1, n) - p := 0 -- force termination - else - k := s.substring (p, i - 1) - v := s.substring (i + 1, j - 1) - p := j + 1 + p := 0 -- force termination + else + k := s.substring (p, i - 1) + v := s.substring (i + 1, j - 1) + p := j + 1 + end + k.left_adjust + k.right_adjust + add_value_to_table (k, v, l_cookies) end - k.left_adjust - k.right_adjust - add_value_to_table (k, v, l_cookies) end end - else - create l_cookies.make_equal (0) end internal_cookies_table := l_cookies end diff --git a/library/server/wsf/src/wsf_response.e b/library/server/wsf/src/wsf_response.e index dbb36855..977acd4a 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -35,7 +35,7 @@ feature {NONE} -- Initialization wres: detachable WSF_WGI_DELAYED_HEADER_RESPONSE do transfered_content_length := 0 - create header.make + create internal_header.make wgi_response := r if attached {WSF_WGI_DELAYED_HEADER_RESPONSE} r as r_delayed then r_delayed.update_wsf_response (Current) @@ -53,7 +53,7 @@ feature {NONE} -- Initialization do transfered_content_length := 0 wgi_response := res.wgi_response - header := res.header + internal_header := res.internal_header set_status_code ({HTTP_STATUS_CODE}.ok) -- Default value end @@ -62,7 +62,7 @@ feature {WSF_RESPONSE, WSF_RESPONSE_EXPORTER} -- Properties wgi_response: WGI_RESPONSE -- Associated WGI_RESPONSE. - header: WSF_HEADER + internal_header: WSF_HEADER -- Associated response header. feature {WSF_RESPONSE_EXPORTER} -- Change @@ -158,7 +158,7 @@ feature {WSF_RESPONSE_EXPORTER} -- Header output operation -- commit status code and reason phrase wgi_response.set_status_code (status_code, status_reason_phrase) -- commit header text - wgi_response.put_header_text (header.string) + wgi_response.put_header_text (internal_header.string) end ensure status_committed: status_committed @@ -170,6 +170,26 @@ feature {WSF_RESPONSE_EXPORTER} -- Header output operation put_error ("Content already sent, new header text ignored!") end +feature -- Header access + + header: HTTP_HEADER_MODIFIER + -- Associated header builder interface. + local + res: like internal_response_header + do + res := internal_response_header + if res = Void then + create {WSF_RESPONSE_HEADER} res.make_with_response (Current) + internal_response_header := res + end + Result := res + end + +feature {NONE} -- Header access + + internal_response_header: detachable like header + -- Cached version of `header'. + feature -- Header output operation put_header_line (h: READABLE_STRING_8) @@ -181,7 +201,7 @@ feature -- Header output operation if header_committed then report_content_already_sent_and_header_ignored else - header.put_header (h) + internal_header.put_header (h) end end @@ -194,7 +214,7 @@ feature -- Header output operation if header_committed then report_content_already_sent_and_header_ignored else - header.add_header (h) + internal_header.add_header (h) end end @@ -209,7 +229,7 @@ feature -- Header output operation if header_committed then report_content_already_sent_and_header_ignored else - header.put_raw_header_data (a_text) + internal_header.put_raw_header_data (a_text) end ensure message_writable: message_writable @@ -227,7 +247,7 @@ feature -- Header output operation if header_committed then report_content_already_sent_and_header_ignored else - header.append_raw_header_data (a_text) + internal_header.append_raw_header_data (a_text) end ensure status_set: status_is_set @@ -496,7 +516,7 @@ feature -- Error reporting end note - copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others" + copyright: "2011-2014, 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/src/wsf_response_header.e b/library/server/wsf/src/wsf_response_header.e new file mode 100644 index 00000000..bb87b3f0 --- /dev/null +++ b/library/server/wsf/src/wsf_response_header.e @@ -0,0 +1,64 @@ +note + description: "[ + Interface to build the http header associated with WSF_RESPONSE. + ]" + date: "$Date$" + revision: "$Revision$" + +class + WSF_RESPONSE_HEADER + +inherit + HTTP_HEADER_MODIFIER + + WSF_RESPONSE_EXPORTER -- to access WSF_RESPONSE.internal_header + +create + make_with_response + +feature {NONE} -- Initialization + + make_with_response (res: WSF_RESPONSE) + do + response := res + end + +feature -- Access + + response: WSF_RESPONSE + +feature -- Access + + new_cursor: INDEXABLE_ITERATION_CURSOR [READABLE_STRING_8] + -- Fresh cursor associated with current structure. + do + Result := response.internal_header.new_cursor + end + +feature -- Header change: core + + add_header (h: READABLE_STRING_8) + -- Add header `h' + -- if it already exists, there will be multiple header with same name + -- which can also be valid + do + response.add_header_line (h) + end + + put_header (h: READABLE_STRING_8) + -- Add header `h' or replace existing header of same header name + do + response.put_header_line (h) + end + +note + copyright: "2011-2014, 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 diff --git a/library/utility/general/error/src/error.e b/library/utility/general/error/src/error.e index e4c266b0..f5231c09 100644 --- a/library/utility/general/error/src/error.e +++ b/library/utility/general/error/src/error.e @@ -54,9 +54,9 @@ feature -- String representation feature -- Status report - debug_output: STRING + debug_output: STRING_32 do - Result := string_representation.as_string_8 + Result := string_representation end feature -- Change @@ -80,7 +80,7 @@ invariant name_attached: name /= Void note - copyright: "2011-2012, Eiffel Software and others" + copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/utility/general/error/src/error_handler.e b/library/utility/general/error/src/error_handler.e index 45085d56..09941972 100644 --- a/library/utility/general/error/src/error_handler.e +++ b/library/utility/general/error/src/error_handler.e @@ -260,7 +260,7 @@ feature -- Access has_error_implies_result_attached: has_error implies Result /= Void end - as_string_representation: STRING + as_string_representation: STRING_32 -- String representation of all error(s). require has_error @@ -269,7 +269,7 @@ feature -- Access Result := e.string_representation else check has_error: False end - Result := "Error occured" + Result := {STRING_32} "Error occured" end end