diff --git a/README.md b/README.md index a1cdee27..029f4f1e 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ For more information please have a look at the related wiki: For download, check * https://github.com/EiffelWebFramework/EWF/downloads +Tasks and issues are managed with github issue system +* See https://github.com/EiffelWebFramework/EWF/issues +* And visual dashboard: https://waffle.io/eiffelwebframework/ewf + ## Requirements * Compiling from EiffelStudio 7.2 to 13.11 and more recent version of the compiler. * Developped using EiffelStudio 13.11 (on Windows, Linux) diff --git a/contrib/library/network/server/nino/nino-safe.ecf b/contrib/library/network/server/nino/nino-safe.ecf index b76c24df..11e63dfd 100644 --- a/contrib/library/network/server/nino/nino-safe.ecf +++ b/contrib/library/network/server/nino/nino-safe.ecf @@ -1,5 +1,5 @@ - + @@ -8,7 +8,7 @@ /CVS$ /.svn$ - diff --git a/contrib/library/text/parser/json/library/json-safe.ecf b/contrib/library/text/parser/json/library/json-safe.ecf index 0eb6cc52..d1936431 100644 --- a/contrib/library/text/parser/json/library/json-safe.ecf +++ b/contrib/library/text/parser/json/library/json-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/contrib/library/text/parser/json/library/kernel/json_string.e b/contrib/library/text/parser/json/library/kernel/json_string.e index a692219d..89ec4648 100644 --- a/contrib/library/text/parser/json/library/kernel/json_string.e +++ b/contrib/library/text/parser/json/library/kernel/json_string.e @@ -61,99 +61,29 @@ feature -- Access item: STRING -- Contents with escaped entities if any +feature -- Conversion + unescaped_string_8: STRING_8 - -- Unescaped string from `item' + -- Unescaped string from `item'. + --| note: valid only if `item' does not encode any unicode character. local s: like item - i, n: INTEGER - c: CHARACTER do s := item - n := s.count - create Result.make (n) - from i := 1 until i > n loop - c := s[i] - if c = '\' then - if i < n then - inspect s[i+1] - when '\' then - Result.append_character ('\') - i := i + 2 - when '%"' then - Result.append_character ('%"') - i := i + 2 - when 'n' then - Result.append_character ('%N') - i := i + 2 - when 'r' then - Result.append_character ('%R') - i := i + 2 - when 'u' then - --| Leave Unicode \uXXXX unescaped - Result.append_character ('\') - i := i + 1 - else - Result.append_character ('\') - i := i + 1 - end - else - Result.append_character ('\') - i := i + 1 - end - else - Result.append_character (c) - i := i + 1 - end - end + create Result.make (s.count) + unescape_to_string_8 (Result) end unescaped_string_32: STRING_32 -- Unescaped string 32 from `item' + --| some encoders uses UTF-8 , and not the recommended pure json encoding + --| thus, let's support the UTF-8 encoding during decoding. local - s: like item - i, n: INTEGER - c: CHARACTER - hex: STRING + s: READABLE_STRING_8 do s := item - n := s.count - create Result.make (n) - from i := 1 until i > n loop - c := s[i] - if c = '\' then - if i < n then - inspect s[i+1] - when '\' then - Result.append_character ('\') - i := i + 2 - when '%"' then - Result.append_character ('%"') - i := i + 2 - when 'n' then - Result.append_character ('%N') - i := i + 2 - when 'r' then - Result.append_character ('%R') - i := i + 2 - when 'u' then - hex := s.substring (i+2, i+2+4 - 1) - if hex.count = 4 then - Result.append_code (hexadecimal_to_natural_32 (hex)) - end - i := i + 2 + 4 - else - Result.append_character ('\') - i := i + 1 - end - else - Result.append_character ('\') - i := i + 1 - end - else - Result.append_character (c.to_character_32) - i := i + 1 - end - end + create Result.make (s.count) + unescape_to_string_32 (Result) end representation: STRING @@ -165,6 +95,156 @@ feature -- Access Result.append_character ('%"') end + unescape_to_string_8 (a_output: STRING_8) + -- Unescape string `item' into `a_output'. + --| note: valid only if `item' does not encode any unicode character. + local + s: like item + i, n: INTEGER + c: CHARACTER + do + s := item + n := s.count + from i := 1 until i > n loop + c := s[i] + if c = '\' then + if i < n then + inspect s[i+1] + when '\' then + a_output.append_character ('\') + i := i + 2 + when '%"' then + a_output.append_character ('%"') + i := i + 2 + when 'b' then + a_output.append_character ('%B') + i := i + 2 + when 'f' then + a_output.append_character ('%F') + i := i + 2 + when 'n' then + a_output.append_character ('%N') + i := i + 2 + when 'r' then + a_output.append_character ('%R') + i := i + 2 + when 't' then + a_output.append_character ('%T') + i := i + 2 + when 'u' then + --| Leave Unicode \uXXXX unescaped + a_output.append_character ('\') + i := i + 1 + else + a_output.append_character ('\') + i := i + 1 + end + else + a_output.append_character ('\') + i := i + 1 + end + else + a_output.append_character (c) + i := i + 1 + end + end + end + + unescape_to_string_32 (a_output: STRING_32) + -- Unescape string `item' into `a_output' string 32. + --| some encoders uses UTF-8 , and not the recommended pure json encoding + --| thus, let's support the UTF-8 encoding during decoding. + local + s: READABLE_STRING_8 + i, n: INTEGER + c: NATURAL_32 + ch: CHARACTER_8 + hex: READABLE_STRING_8 + do + s := item + n := s.count + from i := 1 until i > n loop + ch := s.item (i) + if ch = '\' then + if i < n then + inspect s[i+1] + when '\' then + a_output.append_character ('\') + i := i + 2 + when '%"' then + a_output.append_character ('%"') + i := i + 2 + when 'b' then + a_output.append_character ('%B') + i := i + 2 + when 'f' then + a_output.append_character ('%F') + i := i + 2 + when 'n' then + a_output.append_character ('%N') + i := i + 2 + when 'r' then + a_output.append_character ('%R') + i := i + 2 + when 't' then + a_output.append_character ('%T') + i := i + 2 + when 'u' then + hex := s.substring (i + 2, i + 5) -- i+2 , i+2+4-1 + if hex.count = 4 then + a_output.append_code (hexadecimal_to_natural_32 (hex)) + end + i := i + 6 -- i +2 +4 + else + a_output.append_character ('\') + i := i + 1 + end + else + a_output.append_character ('\') + i := i + 1 + end + else + c := ch.natural_32_code + if c <= 0x7F then + -- 0xxxxxxx + check ch = c.to_character_32 end + a_output.append_character (ch) + elseif c <= 0xDF then + -- 110xxxxx 10xxxxxx + i := i + 1 + if i <= n then + a_output.append_code ( + ((c & 0x1F) |<< 6) | + (s.code (i) & 0x3F) + ) + end + elseif c <= 0xEF then + -- 1110xxxx 10xxxxxx 10xxxxxx + i := i + 2 + if i <= n then + a_output.append_code ( + ((c & 0xF) |<< 12) | + ((s.code (i - 1) & 0x3F) |<< 6) | + (s.code (i) & 0x3F) + ) + end + elseif c <= 0xF7 then + -- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + i := i + 3 + if i <= n then + a_output.append_code ( + ((c & 0x7) |<< 18) | + ((s.code (i - 2) & 0x3F) |<< 12) | + ((s.code (i - 1) & 0x3F) |<< 6) | + (s.code (i) & 0x3F) + ) + end + end + i := i + 1 + end + end + end + feature -- Visitor pattern accept (a_visitor: JSON_VISITOR) @@ -213,8 +293,18 @@ feature {NONE} -- Implementation is_hexadecimal (s: READABLE_STRING_8): BOOLEAN -- Is `s' an hexadecimal value? + local + i: INTEGER do - Result := across s as scur all scur.item.is_hexa_digit end + from + Result := True + i := 1 + until + i > s.count or not Result + loop + Result := s[i].is_hexa_digit + i := i + 1 + end end hexadecimal_to_natural_32 (s: READABLE_STRING_8): NATURAL_32 @@ -264,8 +354,11 @@ feature {NONE} -- Implementation inspect c when '%"' then Result.append_string ("\%"") when '\' then Result.append_string ("\\") - when '%R' then Result.append_string ("\r") + when '%B' then Result.append_string ("\b") + when '%F' then Result.append_string ("\f") when '%N' then Result.append_string ("\n") + when '%R' then Result.append_string ("\r") + when '%T' then Result.append_string ("\t") else Result.extend (c) end @@ -292,8 +385,11 @@ feature {NONE} -- Implementation inspect c when '%"' then Result.append_string ("\%"") when '\' then Result.append_string ("\\") - when '%R' then Result.append_string ("\r") + when '%B' then Result.append_string ("\b") + when '%F' then Result.append_string ("\f") when '%N' then Result.append_string ("\n") + when '%R' then Result.append_string ("\r") + when '%T' then Result.append_string ("\t") else Result.extend (c) end diff --git a/contrib/library/text/parser/json/test/autotest/test_suite/test_json_suite.e b/contrib/library/text/parser/json/test/autotest/test_suite/test_json_suite.e index eb5df766..c2a66b71 100644 --- a/contrib/library/text/parser/json/test/autotest/test_suite/test_json_suite.e +++ b/contrib/library/text/parser/json/test/autotest/test_suite/test_json_suite.e @@ -1,4 +1,4 @@ -note +note description: "[ Eiffel tests that can be executed by testing tool. ]" @@ -62,6 +62,27 @@ feature -- Tests Pass end end + test_json_utf_8_pass1 + local + parse_json: like new_json_parser + utf: UTF_CONVERTER + s: READABLE_STRING_32 + do + s := {STRING_32} "{ %"nihaoma%": %"你好吗\t?%" }" + + parse_json := new_json_parser (utf.string_32_to_utf_8_string_8 (s)) + json_value := parse_json.parse_json + assert ("utf8.pass1.json", parse_json.is_parsed = True) + if + attached {JSON_OBJECT} json_value as jo and then + attached {JSON_STRING} jo.item ("nihaoma") as js + then + assert ("utf8.nihaoma", js.unescaped_string_32.same_string ({STRING_32} "你好吗%T?")) + else + assert ("utf8.nihaoma", False) + end + end + feature -- Tests Failures test_json_fail1 -- diff --git a/doc/wiki/Documentation.md b/doc/wiki/Documentation.md index ad1d2494..89c4264c 100644 --- a/doc/wiki/Documentation.md +++ b/doc/wiki/Documentation.md @@ -14,7 +14,8 @@ The framework also provides a router component to help dispatching the incoming A service can be a web api, a web interface, … what ever run on top of HTTP. - + + # Service > see interface: **WSF_SERVICE** @@ -28,7 +29,8 @@ For convenience, the framework provides richer service interface that handles th > [Learn more about service](Documentation__Service) - + + # Request and Response > see interface: **WSF_REQUEST** and **WSF_RESPONSE** @@ -50,7 +52,8 @@ The **WSF_RESPONSE** represents the communication toward the client, a service n > [Learn more about request](Documentation__Request) and [about response](Documentation__Response) - + + # Connectors: > see **WGI_CONNECTOR** @@ -65,7 +68,8 @@ It is fairly easy to add new connector, it just has to follow the EWSGI interfac > [Learn more about connector](Documentation__Connector) - + + # Router or Request Dispatcher: > Routes HTTP requests to the proper execution code @@ -165,10 +169,12 @@ examples ## EWF application generators - + + # EWSGI Specification - + + # Libraries External libraries are included, such as Cypress OAuth (Security), HTML parsing library, Template Engine Smarty. diff --git a/doc/wiki/Documentation__Request.md b/doc/wiki/Documentation__Request.md index 09b73ab0..379df595 100644 --- a/doc/wiki/Documentation__Request.md +++ b/doc/wiki/Documentation__Request.md @@ -1 +1,17 @@ -See WSF_REQUEST \ No newline at end of file +See WSF_REQUEST + +## About parameters +Note that by default there is a smart computation for the query/post/... parameters: +for instance +- `q=a&q=b` : will create a **WSF_MULTIPLE_STRING** parameter with name **q** and value `[a,b]` +- `tab[a]=ewf&tab[b]=demo` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "a": "ewf", "b": "demo"}` +- `tab[]=ewf&tab[]=demo` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "1": "ewf", "2": "demo"}` +- `tab[foo]=foo&tab[foo]=bar` : will create a **WSF_TABLE** parameter with name **tab** and value `{ "foo": "bar"}` **WARNING: only the last `tab[foo]` is kept**. + +Those rules are applied to query, post, path, .... parameters. + +## How to get the input data (i.e entity-body) ? +See `{WSF_REQUEST}.read_input_data_into (buf: STRING)` + +## How to get the raw header data (i.e the http header text) ? +See `{WSF_REQUEST}.raw_header_data: detachable READABLE_STRING_32` diff --git a/examples/desktop_app/README.md b/examples/desktop_app/README.md new file mode 100644 index 00000000..f9054fb4 --- /dev/null +++ b/examples/desktop_app/README.md @@ -0,0 +1,2 @@ +This example demonstrates the use of embedded Vision2 web browser component, and embedded EWF server (using nino). + diff --git a/examples/desktop_app/desktop_app.ecf b/examples/desktop_app/desktop_app.ecf new file mode 100644 index 00000000..78fd6e17 --- /dev/null +++ b/examples/desktop_app/desktop_app.ecf @@ -0,0 +1,28 @@ + + + Vision2+web browser widget+embedded web service + + This example demonstrates how to build a vision2 desktop application that embed a web browser accessing the service of an embedded web service. + + + + + + + + + + + + + + + /EIFGENs$ + /CVS$ + /.svn$ + + + + diff --git a/examples/desktop_app/files/index.html b/examples/desktop_app/files/index.html new file mode 100644 index 00000000..345e6aef --- /dev/null +++ b/examples/desktop_app/files/index.html @@ -0,0 +1 @@ +Test diff --git a/examples/desktop_app/home.html b/examples/desktop_app/home.html new file mode 100644 index 00000000..26e7c6e2 --- /dev/null +++ b/examples/desktop_app/home.html @@ -0,0 +1,32 @@ + + + + + + +

This is a local file test with js

  • back to home
  • Let AJAX change this text

    + +
    + diff --git a/examples/desktop_app/src/app_embedded_web_service.e b/examples/desktop_app/src/app_embedded_web_service.e new file mode 100644 index 00000000..e9ebed88 --- /dev/null +++ b/examples/desktop_app/src/app_embedded_web_service.e @@ -0,0 +1,230 @@ +note + description: "Summary description for {APP_EMBEDDED_WEB_SERVICE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + APP_EMBEDDED_WEB_SERVICE + +inherit + EMBEDDED_WEB_SERVICE + redefine + make + end + +create + make + +feature {NONE} -- Initialization + + make + do + Precursor + create request_exit_operation_actions + local_connection_restriction_enabled := True + end + +feature -- Execution + + request_exit_operation_actions: ACTION_SEQUENCE [TUPLE] + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the request + -- See `req.input' for input stream + -- `req.meta_variables' for the CGI meta variable + -- and `res' for output buffer + local + router: WSF_ROUTER + sess: detachable WSF_ROUTER_SESSION + m: WSF_HTML_PAGE_RESPONSE + b: STRING + fs: WSF_FILE_SYSTEM_HANDLER + do + create router.make (3) + router.handle ("/test/{var}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_test)) + router.handle ("/env", create {WSF_URI_AGENT_HANDLER}.make (agent handle_env)) + router.handle ("/exit", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_exit)) + create fs.make_with_path ((create {EXECUTION_ENVIRONMENT}).current_working_path.extended ("files")) + router.handle ("/files", fs) + create sess + router.dispatch (req, res, sess) + if not sess.dispatched then + create m.make + create b.make_from_string ("

    Hello Eiffel desktop user

    ") + b.append ("
  • test
  • ") + b.append ("
  • env
  • ") + b.append ("
  • files
  • ") + b.append ("
  • exit
  • ") + m.set_body (b) + res.send (m) + end + end + + handle_test (req: WSF_REQUEST; res: WSF_RESPONSE) + local + m: WSF_HTML_PAGE_RESPONSE + b: STRING + l_name: READABLE_STRING_32 + do + if attached {WSF_STRING} req.item ("var") as p_name then + l_name := p_name.value + else + l_name := {STRING_32} "Embedded web service and web_browser in vision2 application" + end + create m.make + create b.make_from_string ("

    This is a test about "+ m.html_encoded_string (l_name) +"

    ") + b.append ("
  • back to home
  • ") + if l_name.is_case_insensitive_equal_general ("start") then + b.append ("
  • test javascript+ajax
  • ") + elseif l_name.is_case_insensitive_equal_general ("js") then + b.append ("[ +

    Let AJAX change this text

    + +
    + ]") + m.add_javascript_content ("[ + function loadXMLDoc() + { + var xmlhttp; + if (window.XMLHttpRequest) + {// code for IE7+, Firefox, Chrome, Opera, Safari + xmlhttp=new XMLHttpRequest(); + } + else + {// code for IE6, IE5 + xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); + } + xmlhttp.onreadystatechange=function() + { + if (xmlhttp.readyState==4 && xmlhttp.status==200) + { + document.getElementById("myDiv").innerHTML=xmlhttp.responseText; + } + } + xmlhttp.open("GET","/test/ajax.txt",true); + xmlhttp.send(); + } + ]") + elseif l_name.is_case_insensitive_equal_general ("ajax.txt") then + b := "This is AJAX response ... from " + req.absolute_script_url ("") + end + m.set_body (b) + res.send (m) + end + + handle_env (req: WSF_REQUEST; res: WSF_RESPONSE) + local + s: STRING_8 + p: WSF_PAGE_RESPONSE + v: STRING_8 + do + create s.make (2048) + s.append ("**DEBUG**%N") + req.set_raw_input_data_recorded (True) + + append_iterable_to ("Meta variables:", req.meta_variables, s) + s.append_character ('%N') + + append_iterable_to ("Path parameters", req.path_parameters, s) + s.append_character ('%N') + + append_iterable_to ("Query parameters", req.query_parameters, s) + s.append_character ('%N') + + append_iterable_to ("Form parameters", req.form_parameters, s) + s.append_character ('%N') + + if attached req.content_type as l_type then + s.append ("Content: type=" + l_type.debug_output) + s.append (" length=") + s.append_natural_64 (req.content_length_value) + s.append_character ('%N') + create v.make (req.content_length_value.to_integer_32) + req.read_input_data_into (v) + across + v.split ('%N') as v_cursor + loop + s.append (" |") + s.append (v_cursor.item) + s.append_character ('%N') + end + end + + create p.make_with_body (s) + p.header.put_content_type_text_plain + res.send (p) + end + + handle_exit (req: WSF_REQUEST; res: WSF_RESPONSE) + local + m: WSF_HTML_PAGE_RESPONSE + b: STRING + do + create m.make + create b.make_from_string ("

    Embedded server is about to shutdown

    ") + b.append ("
  • back to home
  • ") + m.set_body (b) + res.send (m) + if attached {WGI_NINO_CONNECTOR} req.wgi_connector as nino then + nino.server.shutdown_server + end + request_exit_operation_actions.call (Void) + end + +feature {NONE} -- Implementation + + append_iterable_to (a_title: READABLE_STRING_8; it: detachable ITERABLE [WSF_VALUE]; s: STRING_8) + local + n: INTEGER + t: READABLE_STRING_8 + v: READABLE_STRING_8 + do + s.append (a_title) + s.append_character (':') + if it /= Void then + across it as c loop + n := n + 1 + end + if n = 0 then + s.append (" empty") + s.append_character ('%N') + else + s.append_character ('%N') + across + it as c + loop + s.append (" - ") + s.append (c.item.url_encoded_name) + t := c.item.generating_type + if t.same_string ("WSF_STRING") then + else + s.append_character (' ') + s.append_character ('{') + s.append (t) + s.append_character ('}') + end + s.append_character ('=') + v := c.item.string_representation.as_string_8 + if v.has ('%N') then + s.append_character ('%N') + across + v.split ('%N') as v_cursor + loop + s.append (" |") + s.append (v_cursor.item) + s.append_character ('%N') + end + else + s.append (v) + s.append_character ('%N') + end + end + end + else + s.append (" none") + s.append_character ('%N') + end + end + +end diff --git a/examples/desktop_app/src/desktop_app.e b/examples/desktop_app/src/desktop_app.e new file mode 100644 index 00000000..85aaf27c --- /dev/null +++ b/examples/desktop_app/src/desktop_app.e @@ -0,0 +1,71 @@ +note + description: "Objects that represent the Vision2 application.% + %The original version of this class has been generated by EiffelBuild." + generator: "EiffelBuild" + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date: 2012-09-29 01:29:13 +0200 (sam., 29 sept. 2012) $" + revision: "$Revision: 89488 $" + + +class + DESKTOP_APP + +inherit + EV_APPLICATION + +create + make_and_launch + +feature {NONE} -- Initialization + + make_and_launch + -- Create `Current', build and display `main_window', + -- then launch the application. + local + l_win: like main_window + l_embeded_services: APP_EMBEDDED_WEB_SERVICE + do + default_create + create l_win.make + main_window := l_win + l_win.show + create l_embeded_services.make + l_embeded_services.set_port_number (0) -- Use first available port number + + l_embeded_services.on_launched_actions.force (agent on_web_service_launched (l_win)) + l_embeded_services.request_exit_operation_actions.force (agent on_quit) + l_embeded_services.launch + launch + end + + on_quit + do + if attached main_window as win then + win.destroy_and_exit_if_last + end + end + + on_web_service_launched (a_win: attached like main_window) + do + add_idle_action_kamikaze (agent a_win.open_link) + end + +feature {NONE} -- Implementation + + main_window: detachable MAIN_WINDOW + -- Main window of `Current' + +;note + copyright: "Copyright (c) 1984-2009, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 356 Storke Road, 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/examples/desktop_app/src/main_window.e b/examples/desktop_app/src/main_window.e new file mode 100644 index 00000000..b259db99 --- /dev/null +++ b/examples/desktop_app/src/main_window.e @@ -0,0 +1,202 @@ +note + description: "Objects that represent an EV_TITLED_WINDOW.% + %The original version of this class was generated by EiffelBuild." + generator: "EiffelBuild" + legal: "See notice at end of class." + status: "See notice at end of class." + date: "$Date: 2010-08-17 10:49:12 +0200 (mar., 17 août 2010) $" + revision: "$Revision: 84189 $" + +class + MAIN_WINDOW + +inherit + EV_TITLED_WINDOW + redefine + create_interface_objects, initialize, is_in_default_state + end + + SHARED_EMBEDED_WEB_SERVICE_INFORMATION + undefine + default_create, copy + end + +create + make + +feature {NONE} -- Initialization + + make + -- Creation method + do + default_create + end + + initialize + -- Initialize `Current'. + do + Precursor {EV_TITLED_WINDOW} + + set_title ("Desktop Application (demo embedded EWF+browser)") + + -- Connect events. + -- Close the application when an interface close + -- request is received on `Current'. i.e. the cross is clicked. + close_request_actions.extend (agent destroy_and_exit_if_last) + + -- Call `user_initialization'. + user_initialization + end + + create_interface_objects + -- Create objects + do + create home_button.make_with_text ("Home") + create back_button.make_with_text ("Back") + create forth_button.make_with_text ("Forth") + create refresh_button.make_with_text ("Refresh") + create stop_button.make_with_text ("Stop") + create url_text_field.make_with_text ("http://localhost:" + port_number.out) + create go_button.make_with_text ("Go") + + create web_browser + end + + user_initialization + -- Called by `initialize'. + -- Any custom user initialization that + -- could not be performed in `initialize', + -- (due to regeneration of implementation class) + -- can be added here. + local + l_browser_box: EV_VERTICAL_BOX + l_server_box: EV_VERTICAL_BOX + l_hor_box: EV_HORIZONTAL_BOX + vb: EV_VERTICAL_BOX + do + set_size (800, 600) + + create vb + extend (vb) + vb.set_border_width (3) + vb.set_padding_width (3) + + -- browser part + create l_browser_box + + create l_hor_box + l_browser_box.extend (l_hor_box) + l_browser_box.disable_item_expand (l_hor_box) + + home_button.select_actions.force_extend (agent on_home_button_action) + l_hor_box.extend (home_button) + l_hor_box.disable_item_expand (home_button) + + back_button.select_actions.force_extend (agent on_back_button_action) + l_hor_box.extend (back_button) + l_hor_box.disable_item_expand (back_button) + + forth_button.select_actions.force_extend (agent on_forth_button_action) + l_hor_box.extend (forth_button) + l_hor_box.disable_item_expand (forth_button) + + refresh_button.select_actions.force_extend (agent on_refresh_button_action) + l_hor_box.extend (refresh_button) + l_hor_box.disable_item_expand (refresh_button) + + stop_button.select_actions.force_extend (agent on_stop_button_action) + l_hor_box.extend (stop_button) + l_hor_box.disable_item_expand (stop_button) + + l_hor_box.extend (url_text_field) + + go_button.select_actions.force_extend (agent on_go_button_action) + l_hor_box.extend (go_button) + l_hor_box.disable_item_expand (go_button) + + l_browser_box.extend (web_browser) + + -------------------- + vb.extend (l_browser_box) + end + + is_in_default_state: BOOLEAN + do + Result := True + end + +feature -- Basic operation + + open_link + do + url_text_field.set_text ("http://localhost:" + port_number.out) + on_go_button_action + end + +feature {NONE} -- Implementation + + home_button, go_button, back_button, forth_button, stop_button, refresh_button: EV_BUTTON + -- Buttons + + url_text_field: EV_TEXT_FIELD + -- URL text field + + on_go_button_action + -- Action for `go_button' + local + l_uri: STRING_32 + do + l_uri := url_text_field.text + if l_uri /= Void and then not l_uri.is_empty then + web_browser.load_uri (l_uri) + else + on_home_button_action + end + end + + on_home_button_action + -- Action for `home_button' + do + web_browser.load_uri ("http://localhost:" + port_number.out) + end + + on_back_button_action + -- Action for `back_button' + do + web_browser.back + end + + on_forth_button_action + -- Action for `forth_button' + do + web_browser.forth + end + + on_refresh_button_action + -- Action for `refresh_button' + do + web_browser.refresh + end + + on_stop_button_action + -- Action for `stop_button' + do + web_browser.stop + end + + web_browser: EV_WEB_BROWSER + -- Web browser widget + +;note + copyright: "Copyright (c) 1984-2009, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 356 Storke Road, 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/examples/desktop_app/src/service/embedded_web_service.e b/examples/desktop_app/src/service/embedded_web_service.e new file mode 100644 index 00000000..995f2393 --- /dev/null +++ b/examples/desktop_app/src/service/embedded_web_service.e @@ -0,0 +1,114 @@ +note + description: "Summary description for {EMBEDDED_WEB_SERVICE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class + EMBEDDED_WEB_SERVICE + +inherit + THREAD + rename + make as make_thread, + execute as execute_thread + end + + WSF_SERVICE + rename + execute as execute_embedded + end + + SHARED_EMBEDED_WEB_SERVICE_INFORMATION + +feature -- Initialization + + make + do + make_thread + create on_launched_actions + end + +feature {NONE} -- Execution + + execute_embedded (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the request + -- See `req.input' for input stream + -- `req.meta_variables' for the CGI meta variable + -- and `res' for output buffer + local + filter: WSF_AGENT_FILTER + m: WSF_PAGE_RESPONSE + do + if local_connection_restriction_enabled then + if + attached req.remote_addr as l_remote_addr and then + l_remote_addr.is_case_insensitive_equal_general ("127.0.0.1") + then + execute (req, res) + else + create m.make_with_body ("Only local connection is allowed") + m.set_status_code (403) -- Forbidden + res.send (m) + end + else + execute (req, res) + end + end + + execute_thread + local + nino: WSF_NINO_SERVICE_LAUNCHER + opts: WSF_SERVICE_LAUNCHER_OPTIONS + do + create opts.default_create + opts.set_verbose (True) + opts.set_option ("port", port_number) + create nino.make (Current, opts) + nino.on_launched_actions.force (agent on_launched) + nino.launch + end + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the request + -- See `req.input' for input stream + -- `req.meta_variables' for the CGI meta variable + -- and `res' for output buffer + deferred + end + + on_launched (conn: WGI_CONNECTOR) + do + if attached {WGI_NINO_CONNECTOR} conn as nino then + set_port_number (nino.port) + end + on_launched_actions.call (Void) + end + +feature -- Control + + wait + -- Wait for server to be terminated. + do + join + end + +feature -- Access + + on_launched_actions: ACTION_SEQUENCE [TUPLE] + +feature -- Status report + + local_connection_restriction_enabled: BOOLEAN + -- Accept only local connection? + --| based on 127.0.0.1 IP + --| TO IMPROVE + +feature -- Change + + set_local_connection_restriction_enabled (b: BOOLEAN) + do + local_connection_restriction_enabled := b + end + +end diff --git a/examples/desktop_app/src/service/shared_embeded_web_service_information.e b/examples/desktop_app/src/service/shared_embeded_web_service_information.e new file mode 100644 index 00000000..ba04e855 --- /dev/null +++ b/examples/desktop_app/src/service/shared_embeded_web_service_information.e @@ -0,0 +1,27 @@ +note + description: "Summary description for {SHARED_EMBEDED_WEB_SERVICE_INFORMATION}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + SHARED_EMBEDED_WEB_SERVICE_INFORMATION + +feature -- Access + + port_number: INTEGER + do + Result := port_number_cell.item + end + + set_port_number (a_port: like port_number) + do + port_number_cell.replace (a_port) + end + + port_number_cell: CELL [INTEGER] + once ("process") + create Result.put (0) + end + +end diff --git a/examples/restbucksCRUD/client/client-safe.ecf b/examples/restbucksCRUD/client/client-safe.ecf index 5c53670e..69acd23e 100644 --- a/examples/restbucksCRUD/client/client-safe.ecf +++ b/examples/restbucksCRUD/client/client-safe.ecf @@ -1,5 +1,5 @@ - + @@ -11,7 +11,7 @@ - + diff --git a/examples/restbucksCRUD/restbucks-safe.ecf b/examples/restbucksCRUD/restbucks-safe.ecf index 735a2e52..626f0d5e 100644 --- a/examples/restbucksCRUD/restbucks-safe.ecf +++ b/examples/restbucksCRUD/restbucks-safe.ecf @@ -1,11 +1,13 @@ - + /EIFGENs$ /\.git$ /\.svn$ + @@ -14,8 +16,8 @@ - + @@ -27,7 +29,7 @@ - @@ -39,7 +41,7 @@ - diff --git a/examples/upload_image/upload_image-safe.ecf b/examples/upload_image/upload_image-safe.ecf index ba2e34de..66fb0fcc 100644 --- a/examples/upload_image/upload_image-safe.ecf +++ b/examples/upload_image/upload_image-safe.ecf @@ -1,5 +1,5 @@ - + @@ -20,11 +20,10 @@ - + - + - - + diff --git a/library/network/http_client/http_client-safe.ecf b/library/network/http_client/http_client-safe.ecf index 1c7091d1..041fabd1 100644 --- a/library/network/http_client/http_client-safe.ecf +++ b/library/network/http_client/http_client-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/network/http_client/package.iron b/library/network/http_client/package.iron new file mode 100644 index 00000000..72eed711 --- /dev/null +++ b/library/network/http_client/package.iron @@ -0,0 +1,15 @@ +package http_client + +project + http_client = "http_client-safe.ecf" + http_client = "http_client.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/network/protocol/content_negotiation/conneg-safe.ecf b/library/network/protocol/content_negotiation/conneg-safe.ecf index 2bb7d7b2..af7edc59 100644 --- a/library/network/protocol/content_negotiation/conneg-safe.ecf +++ b/library/network/protocol/content_negotiation/conneg-safe.ecf @@ -1,5 +1,5 @@ - + @@ -18,7 +18,6 @@ /implementation - + diff --git a/library/network/protocol/content_negotiation/package.iron b/library/network/protocol/content_negotiation/package.iron new file mode 100644 index 00000000..b01d71f6 --- /dev/null +++ b/library/network/protocol/content_negotiation/package.iron @@ -0,0 +1,15 @@ +package content_negotiation + +project + conneg = "conneg-safe.ecf" + conneg = "conneg.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/network/protocol/http/http-safe.ecf b/library/network/protocol/http/http-safe.ecf index 024bdb8c..4cffe316 100644 --- a/library/network/protocol/http/http-safe.ecf +++ b/library/network/protocol/http/http-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/network/protocol/http/package.iron b/library/network/protocol/http/package.iron new file mode 100644 index 00000000..b1045d62 --- /dev/null +++ b/library/network/protocol/http/package.iron @@ -0,0 +1,15 @@ +package http + +project + http = "http-safe.ecf" + http = "http.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end 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/runtime/process/notification_email/notification_email-safe.ecf b/library/runtime/process/notification_email/notification_email-safe.ecf index 8bf4ed88..01d95e0a 100644 --- a/library/runtime/process/notification_email/notification_email-safe.ecf +++ b/library/runtime/process/notification_email/notification_email-safe.ecf @@ -1,5 +1,5 @@ - + @@ -13,6 +13,6 @@ - + diff --git a/library/runtime/process/notification_email/package.iron b/library/runtime/process/notification_email/package.iron new file mode 100644 index 00000000..23279943 --- /dev/null +++ b/library/runtime/process/notification_email/package.iron @@ -0,0 +1,15 @@ +package notification_email + +project + notification_email = "notification_email-safe.ecf" + notification_email = "notification_email.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/security/openid/consumer/demo/demo-safe.ecf b/library/security/openid/consumer/demo/demo-safe.ecf index 10bd0633..ac320d01 100644 --- a/library/security/openid/consumer/demo/demo-safe.ecf +++ b/library/security/openid/consumer/demo/demo-safe.ecf @@ -1,5 +1,5 @@ - + @@ -7,17 +7,17 @@ /CVS$ /.svn$ - - - - + + + - - + + diff --git a/library/security/openid/consumer/openid-safe.ecf b/library/security/openid/consumer/openid-safe.ecf index 059358e4..67c5f936 100644 --- a/library/security/openid/consumer/openid-safe.ecf +++ b/library/security/openid/consumer/openid-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/security/openid/package.iron b/library/security/openid/package.iron new file mode 100644 index 00000000..9e5b27f9 --- /dev/null +++ b/library/security/openid/package.iron @@ -0,0 +1,16 @@ +package openid + +project + openid = "consumer/openid.ecf" + openid = "consumer/openid-safe.ecf" + demo = "consumer/demo/demo-safe.ecf" + +note + title: Eiffel OpenID + description: OpenID library (for now only consumer) + tags: openid,security + license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt) +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/server/authentication/http_authorization/example/demo_basic.e b/library/server/authentication/http_authorization/example/demo_basic.e index 0883c8f6..fddbc3ee 100644 --- a/library/server/authentication/http_authorization/example/demo_basic.e +++ b/library/server/authentication/http_authorization/example/demo_basic.e @@ -73,75 +73,163 @@ feature -- Basic operations -- local auth: HTTP_AUTHORIZATION + l_authenticated_username: detachable READABLE_STRING_32 + l_invalid_credential: BOOLEAN do if attached req.http_authorization as l_http_auth then create auth.make (l_http_auth) if attached auth.login as l_login and then is_valid_credential (l_login, auth.password) then - handle_authorized (l_login, req, res) + l_authenticated_username := auth.login else - handle_unauthorized ("ERROR: Invalid credential", req, res) + l_invalid_credential := True end + end + if l_invalid_credential then + handle_unauthorized ("ERROR: Invalid credential", req, res) else - handle_unauthorized ("ERROR: Authentication information is missing ...", req, res) + if l_authenticated_username /= Void then + handle_authenticated (l_authenticated_username, req, res) + elseif req.path_info.same_string_general ("/login") then + handle_unauthorized ("Please provide credential ...", req, res) + elseif req.path_info.starts_with_general ("/protected/") then + -- any "/protected/*" url + handle_unauthorized ("Protected area, please sign in before", req, res) + else + handle_anonymous (req, res) + end end end - handle_authorized (a_username: READABLE_STRING_32; req: WSF_REQUEST; res: WSF_RESPONSE) + handle_authenticated (a_username: READABLE_STRING_32; req: WSF_REQUEST; res: WSF_RESPONSE) -- User `a_username' is authenticated, execute request `req' with response `res'. require valid_username: not a_username.is_empty known_username: is_known_login (a_username) local s: STRING - l_logout_url: STRING + page: WSF_HTML_PAGE_RESPONSE do create s.make_empty - s.append ("Welcome %"") + + append_html_header (req, s) + + s.append ("

    The authenticated user is ") s.append (html_encoder.general_encoded_string (a_username)) - s.append ("%" ...
    ") + s.append ("
    ...

    ") - l_logout_url := req.absolute_script_url ("/") - l_logout_url.replace_substring_all ("://", "://_@") -- Hack to clear http authorization, i.e connect with bad username. - s.append ("logout") + append_html_menu (a_username, req, s) + append_html_logout (a_username, req, s) + append_html_footer (req, s) - -- Append the raw header data for information - if attached req.raw_header_data as l_header then - s.append ("
    ")
    -				s.append (l_header)
    -				s.append ("
    ") - end + create page.make + page.set_body (s) + res.send (page) + end - res.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "text/html"], ["Content-Length", s.count.out]>>) - res.put_string (s) + handle_anonymous (req: WSF_REQUEST; res: WSF_RESPONSE) + -- No user is authenticated, execute request `req' with response `res'. + local + s: STRING + page: WSF_HTML_PAGE_RESPONSE + do + create s.make_empty + append_html_header (req, s) + + s.append ("Anonymous visitor ...
    ") + + append_html_login (req, s) + append_html_menu (Void, req, s) + append_html_footer (req, s) + + create page.make + page.set_body (s) + res.send (page) end handle_unauthorized (a_description: STRING; req: WSF_REQUEST; res: WSF_RESPONSE) - -- Handle forbidden. + -- Restricted page, authenticated user is required. + -- Send `a_description' as part of the response. local h: HTTP_HEADER s: STRING + page: WSF_HTML_PAGE_RESPONSE do create s.make_from_string (a_description) - -- Append the raw header data for information - if attached req.raw_header_data as l_header then - s.append ("
    ")
    -				s.append (l_header)
    -				s.append ("
    ") - end + append_html_login (req, s) + append_html_menu (Void, req, s) + append_html_footer (req, s) - create h.make - h.put_content_type_text_html - h.put_content_length (s.count) - h.put_current_date - h.put_header_key_value ({HTTP_HEADER_NAMES}.header_www_authenticate, + create page.make + page.set_status_code ({HTTP_STATUS_CODE}.unauthorized) + page.header.put_header_key_value ({HTTP_HEADER_NAMES}.header_www_authenticate, "Basic realm=%"Please enter a valid username and password (demo [" + html_encoder.encoded_string (demo_credential) + "])%"" --| warning: for this example: a valid credential is provided in the message, of course that for real application. ) - res.set_status_code ({HTTP_STATUS_CODE}.unauthorized) - res.put_header_text (h.string) - res.put_string (s) + page.set_body (s) + res.send (page) end +feature -- Helper + + append_html_header (req: WSF_REQUEST; s: STRING) + -- Append header paragraph to `s'. + do + s.append ("

    The current page is " + html_encoder.encoded_string (req.path_info) + "

    ") + end + + append_html_menu (a_username: detachable READABLE_STRING_32; req: WSF_REQUEST; s: STRING) + -- Append menu to `s'. + -- when an user is authenticated, `a_username' is attached. + do + if a_username /= Void then + s.append ("
  • Your account (displayed only is user is authenticated!)
  • ") + end + s.append ("
  • home
  • ") + s.append ("
  • public area
  • ") + s.append ("
  • protected area
  • ") + end + + append_html_login (req: WSF_REQUEST; s: STRING) + -- Append login link to `s'. + do + s.append ("
  • sign in
  • ") + end + + append_html_logout (a_username: detachable READABLE_STRING_32; req: WSF_REQUEST; s: STRING) + -- Append logout link to `s'. + local + l_logout_url: STRING + do + l_logout_url := req.absolute_script_url ("/login") + l_logout_url.replace_substring_all ("://", "://_@") -- Hack to clear http authorization, i.e connect with bad username "_". + s.append ("
  • logout
  • ") + end + + append_html_footer (req: WSF_REQUEST; s: STRING) + -- Append html footer to `s'. + local + hauth: HTTP_AUTHORIZATION + do + s.append ("
    ") + if attached req.http_authorization as l_http_authorization then + s.append ("Has Authorization: header: ") + create hauth.make (req.http_authorization) + if attached hauth.login as l_login then + s.append (" login=" + html_encoder.encoded_string (l_login)+ "") + end + if attached hauth.password as l_password then + s.append (" password=" + html_encoder.encoded_string (l_password)+ "") + end + s.append ("
    ") + end + if attached req.raw_header_data as l_header then + -- Append the raw header data for information + s.append ("Raw header data:") + s.append ("
    ")
    +				s.append (l_header)
    +				s.append ("
    ") + end + end end diff --git a/library/server/authentication/http_authorization/http_authorization-safe.ecf b/library/server/authentication/http_authorization/http_authorization-safe.ecf index e5282ae4..4f5f95a9 100644 --- a/library/server/authentication/http_authorization/http_authorization-safe.ecf +++ b/library/server/authentication/http_authorization/http_authorization-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/authentication/http_authorization/package.iron b/library/server/authentication/http_authorization/package.iron new file mode 100644 index 00000000..314ae856 --- /dev/null +++ b/library/server/authentication/http_authorization/package.iron @@ -0,0 +1,15 @@ +package http_authorization + +project + http_authorization = "http_authorization-safe.ecf" + http_authorization = "http_authorization.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/server/ewsgi/connectors/cgi/cgi-safe.ecf b/library/server/ewsgi/connectors/cgi/cgi-safe.ecf index ef1e2700..8d0eaded 100644 --- a/library/server/ewsgi/connectors/cgi/cgi-safe.ecf +++ b/library/server/ewsgi/connectors/cgi/cgi-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/ewsgi/connectors/nino/nino-safe.ecf b/library/server/ewsgi/connectors/nino/nino-safe.ecf index b27dd0b2..433154aa 100644 --- a/library/server/ewsgi/connectors/nino/nino-safe.ecf +++ b/library/server/ewsgi/connectors/nino/nino-safe.ecf @@ -1,5 +1,5 @@ - + @@ -7,13 +7,13 @@ /\.git$ /\.svn$ - - + - - + + diff --git a/library/server/ewsgi/ewsgi-safe.ecf b/library/server/ewsgi/ewsgi-safe.ecf index e2979ff7..62bce7d7 100644 --- a/library/server/ewsgi/ewsgi-safe.ecf +++ b/library/server/ewsgi/ewsgi-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/ewsgi/ewsgi_spec-safe.ecf b/library/server/ewsgi/ewsgi_spec-safe.ecf index 32ac76f1..eaae9ddf 100644 --- a/library/server/ewsgi/ewsgi_spec-safe.ecf +++ b/library/server/ewsgi/ewsgi_spec-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/ewsgi/examples/hello_world/hello-safe.ecf b/library/server/ewsgi/examples/hello_world/hello-safe.ecf index a381ea19..8d992f5c 100644 --- a/library/server/ewsgi/examples/hello_world/hello-safe.ecf +++ b/library/server/ewsgi/examples/hello_world/hello-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/libfcgi/Clib/Makefile-win.SH b/library/server/libfcgi/Clib/Makefile-win.SH new file mode 100644 index 00000000..9eef0e8b --- /dev/null +++ b/library/server/libfcgi/Clib/Makefile-win.SH @@ -0,0 +1,3 @@ +all:: + build_win64.bat + diff --git a/library/server/libfcgi/Clib/build_win32.bat b/library/server/libfcgi/Clib/build_win32.bat index 2eae0679..2ef8e1e0 100644 --- a/library/server/libfcgi/Clib/build_win32.bat +++ b/library/server/libfcgi/Clib/build_win32.bat @@ -16,3 +16,4 @@ link.exe %LINK_FLAGS% /DLL %E_libFCGI_OUTDIR%\fcgi_stdio.obj %E_libFCGI_OUTDIR% copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\windows\msc endlocal +exit 0 diff --git a/library/server/libfcgi/Clib/build_win64.bat b/library/server/libfcgi/Clib/build_win64.bat index 303632cf..301db24a 100644 --- a/library/server/libfcgi/Clib/build_win64.bat +++ b/library/server/libfcgi/Clib/build_win64.bat @@ -16,3 +16,4 @@ link.exe %LINK_FLAGS% /DLL %E_libFCGI_OUTDIR%\fcgi_stdio.obj %E_libFCGI_OUTDIR% copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\win64\msc endlocal +exit 0 diff --git a/library/server/libfcgi/Clib/build_windows.bat b/library/server/libfcgi/Clib/build_windows.bat new file mode 100644 index 00000000..2c6f5424 --- /dev/null +++ b/library/server/libfcgi/Clib/build_windows.bat @@ -0,0 +1,20 @@ +setlocal +echo off + +if "%ISE_PLATFORM%" == "win64" goto build_win64 +goto build_win32 +goto end + +:build_win64 +echo Building libfcgi for win64 +%~dp0\build_win64.bat +goto end + +:build_win32 +echo Building libfcgi for win32 +%~dp0\build_win32.bat +goto end + +:end +endlocal +exit 0 diff --git a/library/server/libfcgi/libfcgi-safe.ecf b/library/server/libfcgi/libfcgi-safe.ecf index 525ce69e..0ae6aede 100644 --- a/library/server/libfcgi/libfcgi-safe.ecf +++ b/library/server/libfcgi/libfcgi-safe.ecf @@ -1,5 +1,5 @@ - + @@ -46,16 +46,16 @@ /fake$ - /windows$ /mac$ + /windows$ + /linux$ /fake$ /windows$ - /linux$ diff --git a/library/server/wsf/connector/all-safe.ecf b/library/server/wsf/connector/all-safe.ecf index 11f19f8f..ad23f576 100644 --- a/library/server/wsf/connector/all-safe.ecf +++ b/library/server/wsf/connector/all-safe.ecf @@ -1,5 +1,5 @@ - + @@ -10,21 +10,20 @@ - - - - - - + + + + + + + - - - - - - - + + + + + diff --git a/library/server/wsf/connector/nino-safe.ecf b/library/server/wsf/connector/nino-safe.ecf index c7a77a3a..a6bcb9e1 100644 --- a/library/server/wsf/connector/nino-safe.ecf +++ b/library/server/wsf/connector/nino-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/wsf/connector/openshift-safe.ecf b/library/server/wsf/connector/openshift-safe.ecf index 50e1a697..7bbb62cf 100644 --- a/library/server/wsf/connector/openshift-safe.ecf +++ b/library/server/wsf/connector/openshift-safe.ecf @@ -1,5 +1,5 @@ - + @@ -10,9 +10,9 @@ - - + + - + diff --git a/library/server/wsf/default/cgi-safe.ecf b/library/server/wsf/default/cgi-safe.ecf index 6034e626..8a6faae1 100644 --- a/library/server/wsf/default/cgi-safe.ecf +++ b/library/server/wsf/default/cgi-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/wsf/default/nino-safe.ecf b/library/server/wsf/default/nino-safe.ecf index 1aafe224..f9f0aa19 100644 --- a/library/server/wsf/default/nino-safe.ecf +++ b/library/server/wsf/default/nino-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/server/wsf/default/openshift-safe.ecf b/library/server/wsf/default/openshift-safe.ecf index 1f5a6c95..904ad908 100644 --- a/library/server/wsf/default/openshift-safe.ecf +++ b/library/server/wsf/default/openshift-safe.ecf @@ -1,5 +1,5 @@ - + @@ -7,10 +7,11 @@ /\.git$ /\.svn$ - - - - + + + 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/filter/wsf_maintenance_filter.e b/library/server/wsf/router/filter/wsf_maintenance_filter.e index 00c63937..3e9cc147 100644 --- a/library/server/wsf/router/filter/wsf_maintenance_filter.e +++ b/library/server/wsf/router/filter/wsf_maintenance_filter.e @@ -61,6 +61,7 @@ feature -- Basic operations create h.make_with_count (1) h.put_content_length (s.count) h.put_content_type_text_plain + res.set_status_code ({HTTP_STATUS_CODE}.service_unavailable) res.put_header_lines (h) res.put_string (s) else @@ -69,7 +70,7 @@ feature -- Basic operations 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/implementation/wsf_percent_encoder.e b/library/server/wsf/src/implementation/wsf_percent_encoder.e index 8d60b89a..93797fa2 100644 --- a/library/server/wsf/src/implementation/wsf_percent_encoder.e +++ b/library/server/wsf/src/implementation/wsf_percent_encoder.e @@ -9,509 +9,11 @@ note class WSF_PERCENT_ENCODER -feature -- Percent encoding - - percent_encoded_string (v: READABLE_STRING_GENERAL): STRING_8 - -- Return `a_string' percent-encoded - do - create Result.make (v.count) - append_percent_encoded_string_to (v, Result) - end - - 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 - - percent_decoded_string (v: READABLE_STRING_GENERAL): STRING_32 - -- Return the percent decoded 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 - do - create Result.make (v.count) - append_percent_decoded_string_to (v, Result) - end - - 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 +inherit + PERCENT_ENCODER 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/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 93980c60..6e4469b9 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -16,6 +16,9 @@ note And also has execution_variable (a_name: READABLE_STRING_GENERAL): detachable ANY --| to keep value attached to the request + + About https support: `is_https' indicates if the request is made through an https connection or not. + ]" date: "$Date$" revision: "$Revision$" @@ -120,6 +123,20 @@ feature {NONE} -- Initialization if meta_variable ({WSF_META_NAMES}.request_time) = Void then set_meta_string_variable ({WSF_META_NAMES}.request_time, date_time_utilities.unix_time_stamp (Void).out) end + + --| HTTPS support + is_https := False + if attached meta_string_variable ("HTTPS") as l_https and then not l_https.is_empty then + is_https := l_https.is_case_insensitive_equal_general ("on") + or else l_https.is_case_insensitive_equal_general ("yes") + or else l_https.is_case_insensitive_equal_general ("true") + or else l_https.is_case_insensitive_equal_general ("1") + --| Usually, if not empty, this means this is https + --| but it occurs that server (like IIS) sets "off" when this is NOT https + --| so, let's be flexible, and accepts other variants of "on" + else + check is_not_https: is_https = False end + end end wgi_request: WGI_REQUEST @@ -156,10 +173,15 @@ feature -- Destroy raw_input_data_recorded := False request_method := empty_string_8 set_uploaded_file_path (Void) + is_https := False end feature -- Status report + is_https: BOOLEAN + -- Is https connection? + --| based on meta variable HTTPS=on . + debug_output: STRING_8 do create Result.make_from_string (request_method + " " + request_uri) @@ -294,25 +316,28 @@ feature -- Access: Input until l_step = 0 or l_input.end_of_input loop - l_input.append_to_string (s, l_step) - nb := l_input.last_appended_count - l_size := l_size + nb.to_natural_64 - len := len - nb.to_natural_64 - - debug ("wsf") - io.error.put_string (" append (s, " + l_step.out + ") -> " + nb.out + " (" + l_size.out + " / "+ content_length_value.out + ")%N") - end - - a_file.put_string (s) - if l_raw_data /= Void then - l_raw_data.append (s) - end - s.wipe_out - if nb < l_step then - l_step := 0 - elseif len < l_step.to_natural_64 then + if len < l_step.to_natural_64 then l_step := len.to_integer_32 end + if l_step > 0 then + l_input.append_to_string (s, l_step) + nb := l_input.last_appended_count + l_size := l_size + nb.to_natural_64 + len := len - nb.to_natural_64 + + debug ("wsf") + io.error.put_string (" append (s, " + l_step.out + ") -> " + nb.out + " (" + l_size.out + " / "+ content_length_value.out + ")%N") + end + + a_file.put_string (s) + if l_raw_data /= Void then + l_raw_data.append (s) + end + s.wipe_out + if nb < l_step then + l_step := 0 + end + end end a_file.flush debug ("wsf") @@ -1262,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 @@ -1735,10 +1762,7 @@ feature -- URL Utility do s := internal_server_url if s = Void then - if - server_protocol.count >= 5 and then - server_protocol.substring (1, 5).is_case_insensitive_equal ("https") - then + if is_https then create s.make_from_string ("https://") else create s.make_from_string ("http://") @@ -1746,8 +1770,14 @@ feature -- URL Utility s.append (server_name) p := server_port if p > 0 then - s.append_character (':') - s.append_integer (p) + if is_https and p = 443 then + -- :443 is default for https, so no need to put it + elseif not is_https and p = 80 then + -- :80 is default for http, so no need to put it + else + s.append_character (':') + s.append_integer (p) + end end end Result := s @@ -2065,7 +2095,7 @@ invariant wgi_request.content_type /= Void implies content_type /= Void 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.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/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index 7cf1b0bf..9fd98569 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -1,5 +1,5 @@ - + @@ -11,11 +11,11 @@ + - diff --git a/library/server/wsf/wsf_policy_driven-safe.ecf b/library/server/wsf/wsf_policy_driven-safe.ecf index 189fae62..46b47a4c 100644 --- a/library/server/wsf/wsf_policy_driven-safe.ecf +++ b/library/server/wsf/wsf_policy_driven-safe.ecf @@ -1,5 +1,5 @@ - + @@ -10,12 +10,12 @@ + + + + - - - - diff --git a/library/text/encoder/encoder-safe.ecf b/library/text/encoder/encoder-safe.ecf index b7307359..e6f73687 100644 --- a/library/text/encoder/encoder-safe.ecf +++ b/library/text/encoder/encoder-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/text/encoder/license.lic b/library/text/encoder/license.lic index cf2d1ed9..545cc9d7 100644 --- a/library/text/encoder/license.lic +++ b/library/text/encoder/license.lic @@ -1,5 +1,5 @@ ${NOTE_KEYWORD} - copyright: "2011-${YEAR}, Eiffel Software and others" + copyright: "Copyright (c) 2011-${YEAR}, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/text/encoder/src/base64.e b/library/text/encoder/src/base64.e index 10e63952..2da2146a 100644 --- a/library/text/encoder/src/base64.e +++ b/library/text/encoder/src/base64.e @@ -125,17 +125,17 @@ feature -- Decoder byte_count := 0 pos := next_encoded_character_position (v, pos) - if pos <= n then + if pos < n then byte1 := base64chars.index_of (v[pos], 1) - 1 byte_count := byte_count + 1 pos := next_encoded_character_position (v, pos) - if pos <= n then + if pos < n then byte2 := base64chars.index_of (v[pos], 1) - 1 byte_count := byte_count + 1 pos := next_encoded_character_position (v, pos) - if pos <= n then + if pos < n then c := v[pos] if c /= '=' then byte3 := base64chars.index_of (c, 1) - 1 @@ -150,8 +150,14 @@ feature -- Decoder byte_count := byte_count + 1 end end + else + has_error := True end + else + has_error := True end + else + has_error := True end -- pos := pos + byte_count @@ -293,7 +299,7 @@ feature {NONE} -- Constants character_map: STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" note - copyright: "Copyright (c) 1984-2011, Eiffel Software and others" + copyright: "Copyright (c) 2011-2014, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/text/encoder/src/json_encoder.e b/library/text/encoder/src/json_encoder.e index 333c7e34..84909edd 100644 --- a/library/text/encoder/src/json_encoder.e +++ b/library/text/encoder/src/json_encoder.e @@ -50,8 +50,11 @@ feature -- Encoder inspect c when '%"' then Result.append_string ("\%"") when '\' then Result.append_string ("\\") - when '%R' then Result.append_string ("\r") + when '%B' then Result.append_string ("\b") + when '%F' then Result.append_string ("\f") when '%N' then Result.append_string ("\n") + when '%R' then Result.append_string ("\r") + when '%T' then Result.append_string ("\t") else Result.extend (c) end @@ -103,12 +106,21 @@ feature -- Decoder when '%"' then Result.append_character ('%"') i := i + 2 + when 'b' then + Result.append_character ('%B') + i := i + 2 + when 'f' then + Result.append_character ('%F') + i := i + 2 when 'n' then Result.append_character ('%N') i := i + 2 when 'r' then Result.append_character ('%R') i := i + 2 + when 't' then + Result.append_character ('%T') + i := i + 2 when 'u' then hex := v.substring (i+2, i+2+4 - 1) if hex.count = 4 then @@ -170,7 +182,7 @@ feature {NONE} -- Implementation end note - copyright: "2011-2012, Eiffel Software and others" + copyright: "Copyright (c) 2011-2014, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/text/encoder/src/percent_encoder.e b/library/text/encoder/src/percent_encoder.e new file mode 100644 index 00000000..75b9961e --- /dev/null +++ b/library/text/encoder/src/percent_encoder.e @@ -0,0 +1,599 @@ +note + description: "[ + Component to handle percent encoding + ]" + date: "$Date: 2014-04-09 16:37:28 +0200 (mer., 09 avr. 2014) $" + revision: "$Revision: 94801 $" + EIS: "name=Percent-encoding", "protocol=URI", "src=http://en.wikipedia.org/wiki/Percent-encoding" + +class + PERCENT_ENCODER + +feature -- Status report + + has_error: BOOLEAN + -- Error occurred + --| For now this is not fully implemented, and thus not reliable. + +feature -- Percent encoding + + percent_encoded_string (s: READABLE_STRING_GENERAL): STRING_8 + -- Return `s' percent-encoded + do + create Result.make (s.count) + append_percent_encoded_string_to (s, Result) + end + + append_percent_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL) + -- Append `s' as percent-encoded value to `a_result' + local + c: NATURAL_32 + i,n: INTEGER + do + has_error := False + 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 + + partial_encoded_string (s: READABLE_STRING_GENERAL; a_ignore: ITERABLE [CHARACTER]): STRING_8 + -- Return `s' as percent-encoded value, + -- but does not escape character listed in `a_ignore'. + do + create Result.make (s.count) + append_partial_percent_encoded_string_to (s, Result, a_ignore) + end + + append_partial_percent_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL; a_ignore: ITERABLE [CHARACTER]) + -- Append `s' as percent-encoded value to `a_result', + -- but does not escape character listed in `a_ignore'. + local + c: NATURAL_32 + ch: CHARACTER_8 + i,n: INTEGER + do + has_error := False + 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 + check c.is_valid_character_8_code end + ch := c.to_character_8 + if across a_ignore as ic some ic.item = ch end then + a_result.append_code (c) + else + append_percent_encoded_character_code_to (c, a_result) + end + else + if c.is_valid_character_8_code then + ch := c.to_character_8 + if across a_ignore as ic some ic.item = ch end then + a_result.append_code (c) + else + append_percent_encoded_character_code_to (c, a_result) + end + else + append_percent_encoded_character_code_to (c, a_result) + end + 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 + + percent_decoded_string (v: READABLE_STRING_GENERAL): STRING_32 + -- Return the percent decoded 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 + do + create Result.make (v.count) + append_percent_decoded_string_to (v, Result) + end + + 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 + has_error := False + 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? + has_error := True + 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) 2011-2014, 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/text/encoder/src/url_encoder.e b/library/text/encoder/src/url_encoder.e index 41d5c8e6..8975e8cb 100644 --- a/library/text/encoder/src/url_encoder.e +++ b/library/text/encoder/src/url_encoder.e @@ -20,6 +20,12 @@ inherit {NONE} all end + PERCENT_ENCODER + rename + percent_encoded_string as general_encoded_string, + percent_decoded_string as general_decoded_string + end + feature -- Access name: READABLE_STRING_8 @@ -27,10 +33,6 @@ feature -- Access create {IMMUTABLE_STRING_8} Result.make_from_string ("URL-encoded") end -feature -- Status report - - has_error: BOOLEAN - feature -- Encoder encoded_string (s: READABLE_STRING_32): STRING_8 @@ -39,333 +41,16 @@ feature -- Encoder Result := general_encoded_string (s) end - general_encoded_string (s: READABLE_STRING_GENERAL): STRING_8 - -- URL-encoded value of `s'. - local - i, n: INTEGER - c: CHARACTER_8 - l_code: NATURAL_32 - do - has_error := False - create Result.make (s.count + s.count // 10) - n := s.count - from i := 1 until i > n loop - l_code := s.code (i) - if l_code.is_valid_character_8_code then - c := l_code.to_character_8 - inspect c - when - 'A' .. 'Z', - 'a' .. 'z', '0' .. '9', - '.', '-', '~', '_' - then - Result.extend (c) - else - Result.append (url_encoded_char (l_code)) - end - else - Result.append (url_encoded_char (l_code)) - end - i := i + 1 - end - end - - partial_encoded_string (s: READABLE_STRING_GENERAL; a_ignore: ARRAY [CHARACTER]): STRING_8 - -- URL-encoded value of `s'. - local - i, n: INTEGER - l_code: NATURAL_32 - c: CHARACTER_8 - s8: STRING_8 - do - has_error := False - create s8.make (s.count + s.count // 10) - Result := s8 - n := s.count - from i := 1 until i > n loop - l_code := s.code (i) - if l_code.is_valid_character_8_code then - c := l_code.to_character_8 - inspect c - when - 'A' .. 'Z', - 'a' .. 'z', '0' .. '9', - '.', '-', '~', '_' - then - s8.extend (c) - else - if a_ignore.has (c) then - s8.extend (c) - else - s8.append (url_encoded_char (l_code)) - end - end - else - if a_ignore.has (c) then - s8.extend (c) - else - s8.append (url_encoded_char (l_code)) - end - end - i := i + 1 - end - end - -feature {NONE} -- encoder character - - url_encoded_char (a_code: NATURAL_32): STRING_8 - do - create Result.make (3) - if a_code.is_valid_character_8_code then - Result.extend ('%%') - Result.append (a_code.to_hex_string) - from - until - Result.count < 2 or else Result[2] /= '0' - loop - Result.remove (2) - end - else - has_error := True --| Non-ascii escape not currently supported - end - ensure - exists: Result /= Void - end - feature -- Decoder decoded_string (v: READABLE_STRING_8): STRING_32 -- The URL-encoded equivalent of the given string - local - i, n: INTEGER - c: CHARACTER - pr: CELL [INTEGER] - changed: BOOLEAN do - has_error := False - n := v.count - create Result.make (n) - from i := 1 - until i > n - loop - c := v.item (i) - inspect c - when '+' then - changed := True - Result.append_character ({CHARACTER_32}' ') - when '%%' then - -- An escaped character ? - if i = n then - Result.append_character (c.to_character_32) - else - changed := True - create pr.put (i) - Result.append (url_decoded_char (v, pr)) - i := pr.item - end - else - Result.append_character (c.to_character_32) - end - i := i + 1 - end - end - -feature {NONE} -- decoded character - - url_decoded_char (buf: STRING_8; posr: CELL [INTEGER]): STRING_32 - -- Character(s) resulting from decoding the URL-encoded string - require - stream_exists: buf /= Void - posr_exists: posr /= Void - valid_start: posr.item <= buf.count - local - c: CHARACTER - i, n, nb: INTEGER - not_a_digit: BOOLEAN - ascii_pos, ival: INTEGER - pos: INTEGER - do - --| pos is index in stream of escape character ('%') - pos := posr.item - create Result.make (4) - if buf.item (pos + 1) = 'u' then - -- 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). - -- UTF-8 result can be 1 to 4 characters - n := buf.count - from i := pos + 2 - until (i > n) or not_a_digit - loop - c := buf.item (i) - if c.is_hexa_digit then - ival := ival * 16 - if c.is_digit then - ival := ival + (c |-| '0') - else - ival := ival + (c.upper |-| 'A') + 10 - end - i := i + 1 - else - not_a_digit := True - end - end - posr.replace (i) - -- ival is now UCS2 value; needs conversion to UTF8 - Result.append_code (ival.as_natural_32) - nb := utf8_bytes_in_sequence (buf, pos) - else - -- ASCII char? - ascii_pos := hex_to_integer_32 (buf.substring (pos+1, pos+2)) - if ascii_pos >= 0x80 and ascii_pos <= 0xff then - -- Might be improperly escaped - Result.append_code (ascii_pos.as_natural_32) - posr.replace (pos + 2) - else - Result.append_code (ascii_pos.as_natural_32) - posr.replace (pos + 2) - end - end - ensure - exists: Result /= Void - end - -feature {NONE} -- UTF8 - - utf8_bytes_in_sequence (s: STRING_8; spos: INTEGER): INTEGER - -- If the given character is a legal first byte element in a - -- utf8 byte sequence (aka character), then return the number - -- of bytes in that sequence - -- Result of zero means it's not a utf8 first byte - require - exists: s /= Void - long_enough: s.count >= spos - do - Result := bytes_in_utf8_char (s.item (spos)) - end - - bytes_in_utf8_char (v: CHARACTER_8): INTEGER - -- If the given byte a legal first byte element in a utf8 sequence, - -- then the number of bytes in that character - -- Zero denotes an error, i.e. not a legal UTF8 char - -- - -- The first byte of a UTF8 encodes the length - local - c: NATURAL_8 - do - c := v.code.to_natural_8 - Result := 1 -- 7 bit ASCII - if (c & 0x80) /= 0 then - -- Hi bit means not ASCII - Result := 0 - if (c & 0xe0) = 0xc0 then - -- If we see a first byte as b110xxxxx - -- then we expect a two-byte character - Result := 2 - elseif (c & 0xf0) = 0xe0 then - -- If we see a first byte as b1110xxxx - -- then we expect a three-byte character - Result := 3 - elseif (c & 0xf8) = 0xf0 then - -- If we see a first byte as b11110xxx - -- then we expect a four-byte character - Result := 4 - elseif (c & 0xfc) = 0xf8 then - -- If we see a first byte as b111110xx - -- then we expect a five-byte character - Result := 5 - elseif (c & 0xfe) = 0xfc then - -- If we see a first byte as b1111110x - -- then we expect a six-byte character - Result := 6 - end - end - end - -feature {NONE} -- Hexadecimal and strings - - hex_to_integer_32 (s: STRING): INTEGER_32 - -- Hexadecimal string `s' converted to INTEGER_32 value - require - s_not_void: s /= Void - local - i, nb: INTEGER; - char: CHARACTER - do - nb := s.count - - if nb >= 2 and then s.item (2) = 'x' then - i := 3 - else - i := 1 - end - - from - until - i > nb - loop - Result := Result * 16 - char := s.item (i) - if char >= '0' and then char <= '9' then - Result := Result + (char |-| '0') - else - Result := Result + (char.lower |-| 'a' + 10) - end - i := i + 1 - end - end - - hex_to_integer_64 (s: STRING): INTEGER_64 - -- Hexadecimal string `s' converted to INTEGER_64 value - require - s_not_void: s /= Void - local - i, nb: INTEGER; - char: CHARACTER - do - nb := s.count - - if nb >= 2 and then s.item (2) = 'x' then - i := 3 - else - i := 1 - end - - from - until - i > nb - loop - Result := Result * 16 - char := s.item (i) - if char >= '0' and then char <= '9' then - Result := Result + (char |-| '0') - else - Result := Result + (char.lower |-| 'a' + 10) - end - i := i + 1 - end - end - - hex_to_pointer (s: STRING): POINTER - -- Hexadecimal string `s' converted to POINTER value - require - s_not_void: s /= Void - local - val_32: INTEGER_32 - val_64: INTEGER_64 - do - if Pointer_bytes = Integer_64_bytes then - val_64 := hex_to_integer_64 (s) - ($Result).memory_copy ($val_64, Pointer_bytes) - else - val_32 := hex_to_integer_32 (s) - ($Result).memory_copy ($val_32, Pointer_bytes) - end + Result := general_decoded_string (v) end note - copyright: "2011-2012, Eiffel Software and others" + copyright: "Copyright (c) 2011-2014, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/text/encoder/src/utf8_url_encoder.e b/library/text/encoder/src/utf8_url_encoder.e index 1edfef50..33c97b2e 100644 --- a/library/text/encoder/src/utf8_url_encoder.e +++ b/library/text/encoder/src/utf8_url_encoder.e @@ -14,23 +14,6 @@ class inherit URL_ENCODER - redefine - name, - general_encoded_string, - encoded_string, partial_encoded_string, - decoded_string - select - encoded_string, - decoded_string, - has_error - end - - UTF8_ENCODER - rename - general_encoded_string as utf8_general_encoded_string, - encoded_string as utf8_encoded_string, - decoded_string as utf8_decoded_string, - has_error as utf8_has_error redefine name end @@ -42,43 +25,8 @@ feature -- Access create {IMMUTABLE_STRING_8} Result.make_from_string ("UTF8-URL-encoded") end -feature -- Encoder - - encoded_string (s: READABLE_STRING_32): STRING_8 - -- URL-encoded value of `s'. - do - Result := general_encoded_string (s) - end - - general_encoded_string (s: READABLE_STRING_GENERAL): STRING_8 - do - Result := utf8_general_encoded_string (s) - Result := Precursor {URL_ENCODER} (Result) - has_error := has_error or utf8_has_error - end - - partial_encoded_string (s: READABLE_STRING_GENERAL; a_ignore: ARRAY [CHARACTER]): STRING_8 - -- URL-encoded value of `s'. - do - Result := utf8_general_encoded_string (s) - Result := Precursor {URL_ENCODER} (Result, a_ignore) - has_error := has_error or utf8_has_error - end - -feature -- Decoder - - decoded_string (v: READABLE_STRING_8): STRING_32 - -- The URL-encoded equivalent of the given string - do - Result := Precursor {URL_ENCODER} (v) - if not has_error then - Result := utf8_decoded_string (Result) - has_error := utf8_has_error - end - end - note - copyright: "2011-2013, Eiffel Software and others" + copyright: "Copyright (c) 2011-2014, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/text/encoder/tests/test_base64.e b/library/text/encoder/tests/test_base64.e index e0b0d5ea..14530dc9 100644 --- a/library/text/encoder/tests/test_base64.e +++ b/library/text/encoder/tests/test_base64.e @@ -34,6 +34,38 @@ feature -- Test routines assert ("decoded encoded string is same", u ~ s) end +feature -- Tests + + test_valid_64_encoding + do + assert ("Expected encoded True:", is_valid_base64_encoding ((create {BASE64}).encoded_string ("content"))) + end + + test_not_valid64_encoding + do + assert ("Expected encoded False:", not is_valid_base64_encoding ("content")) + assert ("Expected encoded False:", not is_valid_base64_encoding ("!@#$%%^")) + end + +feature {NONE} -- Implementation + + is_valid_base64_encoding (a_string: STRING): BOOLEAN + -- is `a_string' base64 encoded? + local + l_encoder: BASE64 + l_string: STRING + l_retry: BOOLEAN + do + if not l_retry then + create l_encoder + l_string := l_encoder.decoded_string (a_string) + Result := not l_encoder.has_error + end + rescue + l_retry := True + retry + end + note copyright: "Copyright (c) 1984-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/text/encoder/tests/test_json_encoder.e b/library/text/encoder/tests/test_json_encoder.e index 6c54c974..3d370213 100644 --- a/library/text/encoder/tests/test_json_encoder.e +++ b/library/text/encoder/tests/test_json_encoder.e @@ -21,6 +21,7 @@ feature -- Test routines do test_json_encoded_encoding ({STRING_32}"il était une fois %"Ni & Hao%" (你好) \a\b\c") test_json_encoded_encoding ({STRING_32}" it's `abc’ ") + test_json_encoded_encoding ({STRING_32}"tab%Tnew line%N %"double quote %"") end test_json_encoded_encoding (s: STRING_32) diff --git a/library/text/encoder/tests/test_url_encoder.e b/library/text/encoder/tests/test_url_encoder.e index 25cff7b7..c8df97d7 100644 --- a/library/text/encoder/tests/test_url_encoder.e +++ b/library/text/encoder/tests/test_url_encoder.e @@ -22,6 +22,7 @@ feature -- Test routines test_url_encoded_encoding ({STRING_32}"http://domain.tld/foo/bar/script.php?test='toto'&foo=bar&title=il tait une fois") test_url_encoded_encoding ({STRING_32}"t") test_url_encoded_decoding ({STRING_8}"%%E9t%%E9", {STRING_32}"t") + test_url_encoded_decoding ({STRING_8}"Test%%0A", {STRING_32}"Test%N") test_utf8_url_encoded_decoding ({STRING_8}"%%C3%%A9t%%C3%%A9", {STRING_32}"t") end diff --git a/library/text/encoder/tests/test_utf8_encoder.e b/library/text/encoder/tests/test_utf8_encoder.e index e1f29c61..b2c1525a 100644 --- a/library/text/encoder/tests/test_utf8_encoder.e +++ b/library/text/encoder/tests/test_utf8_encoder.e @@ -1,4 +1,4 @@ -note +note description: "[ Eiffel tests that can be executed by testing tool. ]" @@ -18,23 +18,43 @@ feature -- Test routines test_url_encoded_encoder note testing: "url-encoded" + local + utf8: STRING_8 do - test_utf8_decoding ("%%C3%%A9t%%C3%%A9", {STRING_32}"t") + create utf8.make_empty + utf8.append_code (195) --+ + utf8.append_code (169) -- é + utf8.append_code (116) -- t + utf8.append_code (195) --+ + utf8.append_code (169) -- é + test_utf8_decoding (utf8, {STRING_32}"été") + + create utf8.make_empty + utf8.append_code (228) --+ + utf8.append_code (189) --+ + utf8.append_code (160) -- 你 + + utf8.append_code (229) --+ + utf8.append_code (165) --+ + utf8.append_code (189) -- 好 + + utf8.append_code (229) --+ + utf8.append_code (144) --+ + utf8.append_code (151) -- 吗 + + test_utf8_decoding (utf8, {STRING_32}"你好吗") end test_utf8_decoding (s: STRING_8; e: STRING_32) local - url: URL_ENCODER u: STRING_32 b: UTF8_ENCODER do create b - create url - u := b.decoded_string (url.decoded_string (s)) + u := b.decoded_string (s) assert ("decoded encoded string is same for %"" + s + "%"", u ~ e) end - note copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/text/parser/uri_template/package.iron b/library/text/parser/uri_template/package.iron new file mode 100644 index 00000000..f9510205 --- /dev/null +++ b/library/text/parser/uri_template/package.iron @@ -0,0 +1,15 @@ +package uri_template + +project + uri_template = "uri_template-safe.ecf" + uri_template = "uri_template.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +end diff --git a/library/text/parser/uri_template/tests/test_uri_template_matcher.e b/library/text/parser/uri_template/tests/test_uri_template_matcher.e index e0caf791..87604661 100644 --- a/library/text/parser/uri_template/tests/test_uri_template_matcher.e +++ b/library/text/parser/uri_template/tests/test_uri_template_matcher.e @@ -15,7 +15,7 @@ inherit feature -- Matcher - test_uri_template_matcher + test_uri_template_matcher_01 note testing: "uri-template" local @@ -80,6 +80,19 @@ feature -- Matcher end + test_uri_template_matcher_02 + note + testing: "uri-template" + local + tpl: URI_TEMPLATE + do + create tpl.make ("/test/{vars}") + uri_template_match (tpl, "/test/foo%%2Fbar", <<["vars", "foo%%2Fbar"]>>, <<>>) + + create tpl.make ("/test{/vars}") + uri_template_match (tpl, "/test/foo%%2Fbar/abc%%2Fdef", <<["vars", "/foo%%2Fbar/abc%%2Fdef"], ["vars[1]", "foo%%2Fbar"], ["vars[2]", "abc%%2Fdef"]>>, <<>>) + end + feature {NONE} -- Implementations uri_template_mismatch (a_uri_template: URI_TEMPLATE; a_uri: STRING) diff --git a/library/utility/general/error/package.iron b/library/utility/general/error/package.iron new file mode 100644 index 00000000..3db2e673 --- /dev/null +++ b/library/utility/general/error/package.iron @@ -0,0 +1,15 @@ +package error + +project + error = "error-safe.ecf" + error = "error.ecf" + +note +-- title: +-- description: +-- tags: +-- license: +-- copyright: +-- link[doc]: "Documentation" http:// + +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 diff --git a/tests/all-safe.ecf b/tests/all-safe.ecf index 869f2432..9319a159 100644 --- a/tests/all-safe.ecf +++ b/tests/all-safe.ecf @@ -1,5 +1,5 @@ - + Integration project including many lib @@ -47,7 +47,6 @@ - @@ -55,6 +54,7 @@ + diff --git a/tests/all-stable-safe.ecf b/tests/all-stable-safe.ecf index 32b6fd1c..fccfd95c 100644 --- a/tests/all-stable-safe.ecf +++ b/tests/all-stable-safe.ecf @@ -1,5 +1,5 @@ - + Integration project including many lib @@ -10,45 +10,45 @@ - - - - - - - - - - - - - - - - - - - - - + - + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + Compiling as Windows , on other platforms than Windows diff --git a/tools/ise_wizard/ewf_ise_wizard-safe.ecf b/tools/ise_wizard/ewf_ise_wizard-safe.ecf index 36ca6b24..8b68184e 100644 --- a/tools/ise_wizard/ewf_ise_wizard-safe.ecf +++ b/tools/ise_wizard/ewf_ise_wizard-safe.ecf @@ -1,5 +1,5 @@ - +