diff --git a/library/server/ewsgi/specification/request/wgi_meta_names.e b/library/server/ewsgi/specification/request/wgi_meta_names.e index 8b10840e..41e4026d 100644 --- a/library/server/ewsgi/specification/request/wgi_meta_names.e +++ b/library/server/ewsgi/specification/request/wgi_meta_names.e @@ -46,6 +46,8 @@ feature -- Access http_referer: STRING = "HTTP_REFERER" + http_transfer_encoding: STRING = "HTTP_TRANSFER_ENCODING" + gateway_interface: STRING = "GATEWAY_INTERFACE" auth_type: STRING = "AUTH_TYPE" @@ -77,7 +79,7 @@ feature -- Extra names orig_path_info: STRING = "ORIG_PATH_INFO" note - copyright: "2011-2011, Eiffel Software and others" + copyright: "2011-2012, 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/ewsgi/specification/request/wgi_request.e b/library/server/ewsgi/specification/request/wgi_request.e index 72c593b5..7929d318 100644 --- a/library/server/ewsgi/specification/request/wgi_request.e +++ b/library/server/ewsgi/specification/request/wgi_request.e @@ -77,6 +77,20 @@ feature -- Access: Input input: WGI_INPUT_STREAM -- Server input channel + require + is_not_chunked_input: not is_chunked_input + deferred + end + + is_chunked_input: BOOLEAN + -- Is request using chunked transfer-encoding? + deferred + end + + chunked_input: detachable WGI_CHUNKED_INPUT_STREAM + -- Chunked server input channel + require + is_chunked_input: is_chunked_input deferred end @@ -580,6 +594,12 @@ feature -- HTTP_* deferred end + http_transfer_encoding: detachable READABLE_STRING_8 + -- Transfer-Encoding + -- for instance chunked + deferred + end + feature -- Extra CGI environment variables request_uri: READABLE_STRING_8 @@ -606,7 +626,7 @@ invariant path_info_identical: path_info ~ meta_string_variable ({WGI_META_NAMES}.path_info) note - copyright: "2011-2011, Eiffel Software and others" + copyright: "2011-2012, 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/ewsgi/src/helper/wgi_request_from_table.e b/library/server/ewsgi/src/helper/wgi_request_from_table.e index 8570682d..f3926bd2 100644 --- a/library/server/ewsgi/src/helper/wgi_request_from_table.e +++ b/library/server/ewsgi/src/helper/wgi_request_from_table.e @@ -26,15 +26,24 @@ feature {NONE} -- Initialization wgi_connector := a_wgi_connector input := a_input set_meta_variables (a_vars) - update_path_info + if attached http_transfer_encoding as l_transfer_encoding and then l_transfer_encoding.same_string ("chunked") then + is_chunked_input := True + create chunked_input.make (a_input) + end end feature -- Access: Input + is_chunked_input: BOOLEAN + -- Is request using chunked transfer-encoding? + input: WGI_INPUT_STREAM -- Server input channel + chunked_input: detachable WGI_CHUNKED_INPUT_STREAM + -- Chunked server input channel + feature -- EWSGI access wgi_version: STRING = "0.1" @@ -211,6 +220,13 @@ feature -- Access: HTTP_* CGI meta parameters - 1.1 Result := meta_string_variable ({WGI_META_NAMES}.http_authorization) end + http_transfer_encoding: detachable READABLE_STRING_8 + -- Transfer-Encoding + -- for instance chunked + do + Result := meta_string_variable ({WGI_META_NAMES}.http_transfer_encoding) + end + feature -- Access: Extension to CGI meta parameters - 1.1 request_uri: READABLE_STRING_8 @@ -340,20 +356,6 @@ feature {NONE} -- Element change: CGI meta parameter related to PATH_INFO end end -feature {NONE} -- I/O: implementation - - read_input (nb: INTEGER) - -- Read `nb' bytes from `input' - do - input.read_string (nb) - end - - last_input_string: STRING - -- Last string read from `input' - do - Result := input.last_string - end - feature {NONE} -- Implementation: utilities single_slash_starting_string (s: READABLE_STRING_8): STRING_8 diff --git a/library/server/ewsgi/src/wgi_chunked_input_stream.e b/library/server/ewsgi/src/wgi_chunked_input_stream.e index 016e7107..e3699444 100644 --- a/library/server/ewsgi/src/wgi_chunked_input_stream.e +++ b/library/server/ewsgi/src/wgi_chunked_input_stream.e @@ -6,118 +6,167 @@ note class WGI_CHUNKED_INPUT_STREAM + create make + feature {NONE} -- Implementation - make ( an_input : like input) + + make (an_input: like input) do - create last_chunked.make_empty - create last_chunk_size.make_empty + create tmp_hex_chunk_size.make_empty input := an_input end -feature --Input - read - -- Read all the data in a chunked stream. - -- Make the result available in `last_chunked'. - -- Chunked-Body = *chunk - -- last-chunk - -- trailer - -- CRLF - -- chunk = chunk-size [ chunk-extension ] CRLF - -- chunk-data CRLF - -- chunk-size = 1*HEX - -- last-chunk = 1*("0") [ chunk-extension ] CRLF - -- chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - -- chunk-ext-name = token - -- chunk-ext-val = token | quoted-string - -- chunk-data = chunk-size(OCTET) - -- trailer = *(entity-header CRLF) +feature -- Input + + data: READABLE_STRING_8 + local + d: like internal_data + do + d := internal_data + if d = Void then + d := fetched_data + internal_data := d + end + Result := d + end + +feature {NONE} -- Parser + + internal_data: detachable READABLE_STRING_8 + + tmp_hex_chunk_size: STRING_8 + last_chunk_size: INTEGER + last_chunk: detachable STRING_8 + + fetched_data: READABLE_STRING_8 + -- Read all the data in a chunked stream. + -- Make the result available in `last_chunked'. + -- Chunked-Body = *chunk + -- last-chunk + -- trailer + -- CRLF + -- chunk = chunk-size [ chunk-extension ] CRLF + -- chunk-data CRLF + -- chunk-size = 1*HEX + -- last-chunk = 1*("0") [ chunk-extension ] CRLF + -- chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + -- chunk-ext-name = token + -- chunk-ext-val = token | quoted-string + -- chunk-data = chunk-size(OCTET) + -- trailer = *(entity-header CRLF) local eoc : BOOLEAN + s: STRING_8 do from - read_chunk - last_chunked.append (input.last_string) - if last_chunk_size.is_equal ("0") then - eoc := true - end + create s.make (1024) until eoc loop read_chunk - last_chunked.append (input.last_string) - if last_chunk_size.is_equal ("0") then + if attached last_chunk as l_last_chunk then + s.append (l_last_chunk) + else eoc := true end + if last_chunk_size = 0 then + eoc := True + end end read_trailer + Result := s end -feature {NONE} -- Parser - last_chunk_size : STRING + reset_chunk + do + last_chunk := Void + last_chunk_size := 0 + end read_chunk do - -- clear last results - last_chunk_size.wipe_out - - -- new read + reset_chunk read_chunk_size - read_chunk_data + if last_chunk_size > 0 then + read_chunk_data + end end read_chunk_data - local - hex : HEXADECIMAL_STRING_TO_INTEGER_CONVERTER + require + last_chunk_size > 0 do - create hex.make - hex.parse_string_with_type (last_chunk_size, hex.type_integer) - if hex.parse_successful then - input.read_string (hex.parsed_integer) - end + input.read_string (last_chunk_size) + last_chunk := input.last_string + -- read CRLF input.read_character - if input.last_character.is_equal ('%R') then + if input.last_character = '%R' then input.read_character end + ensure + last_chunk_attached: attached last_chunk as el_last_chunk + last_chunk_size_ok: el_last_chunk.count = last_chunk_size end read_chunk_size + require + tmp_hex_chunk_size_is_empty: tmp_hex_chunk_size.is_empty local eol : BOOLEAN + c: CHARACTER + hex : HEXADECIMAL_STRING_TO_INTEGER_CONVERTER do from input.read_character until eol loop - - if input.last_character.is_equal ('%R') then + c := input.last_character + inspect c + when '%R' then -- We are in the end of the line, we need to read the next character to start the next line. + eol := True input.read_character - eol := true - elseif input.last_character.is_equal (';') then + when ';' then -- We are in an extension chunk data read_extension_chunk else - last_chunk_size.append_character (input.last_character) + tmp_hex_chunk_size.append_character (c) input.read_character end end + if tmp_hex_chunk_size.same_string ("0") then + last_chunk_size := 0 + else + create hex.make + hex.parse_string_with_type (tmp_hex_chunk_size, hex.type_integer) + if hex.parse_successful then + last_chunk_size := hex.parsed_integer + else + last_chunk_size := 0 -- ERROR ... + end + end + tmp_hex_chunk_size.wipe_out end read_extension_chunk do - print (" Reading extension chunk ") + debug + print (" Reading extension chunk ") + end from input.read_character until - input.last_character.is_equal ('%R') + input.last_character = '%R' loop - print (input.last_character) + debug + print (input.last_character) + end input.read_character end end @@ -125,29 +174,31 @@ feature {NONE} -- Parser read_trailer do if not input.end_of_input then - print (" Reading trailer ") + debug + print (" Reading trailer ") + end from input.read_character until - input.last_character.is_equal ('%R') + input.last_character = '%R' loop - print (input.last_character) + debug + print (input.last_character) + end input.read_character end -- read the LF input.read_character end end -feature {NONE} -- Access + +feature {NONE} -- Implementation input: WGI_INPUT_STREAM -- Input Stream -feature -- Access - last_chunked: STRING_8 - ;note - copyright: "2011-2011, Eiffel Software and others" + copyright: "2011-2012, 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/support/wsf_mime_handler_helper.e b/library/server/wsf/src/support/wsf_mime_handler_helper.e index 5845df53..ca554ed6 100644 --- a/library/server/wsf/src/support/wsf_mime_handler_helper.e +++ b/library/server/wsf/src/support/wsf_mime_handler_helper.e @@ -9,6 +9,20 @@ deferred class feature {NONE} -- Implementation + full_input_data (req: WSF_REQUEST): READABLE_STRING_8 + do + if req.is_chunked_input then + if attached req.chunked_input as l_chunked_input then + Result := l_chunked_input.data + else + check has_chunked_input: False end + Result := "" + end + else + Result := read_input_data (req.input, req.content_length_value) + end + end + read_input_data (a_input: WGI_INPUT_STREAM; nb: NATURAL_64): READABLE_STRING_8 -- All data from input form local diff --git a/library/server/wsf/src/wsf_application_x_www_form_urlencoded_handler.e b/library/server/wsf/src/wsf_application_x_www_form_urlencoded_handler.e index f92e6360..3433b6a7 100644 --- a/library/server/wsf/src/wsf_application_x_www_form_urlencoded_handler.e +++ b/library/server/wsf/src/wsf_application_x_www_form_urlencoded_handler.e @@ -21,7 +21,7 @@ feature -- Status report feature -- Execution - handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST; + handle (a_content_type: READABLE_STRING_8; req: WSF_REQUEST; a_vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8]) local l_content: READABLE_STRING_8 @@ -29,12 +29,12 @@ feature -- Execution s: READABLE_STRING_8 l_name, l_value: READABLE_STRING_8 do - l_content := read_input_data (req.input, a_content_length) + l_content := full_input_data (req) if a_raw_data /= Void then a_raw_data.replace (l_content) end + check content_count_same_as_content_length_if_not_chunked: (not req.is_chunked_input) implies (l_content.count = req.content_length_value.to_integer_32) end --| FIXME: truncated value n := l_content.count - check n_same_as_content_length: n = a_content_length.to_integer_32 end --| FIXME: truncated value if n > 0 then from p := 1 diff --git a/library/server/wsf/src/wsf_mime_handler.e b/library/server/wsf/src/wsf_mime_handler.e index f7f57d38..8b94630a 100644 --- a/library/server/wsf/src/wsf_mime_handler.e +++ b/library/server/wsf/src/wsf_mime_handler.e @@ -15,7 +15,7 @@ feature -- Status report feature -- Execution - handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST; + handle (a_content_type: READABLE_STRING_8; req: WSF_REQUEST; a_vars: TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8]) -- Handle MIME content from request `req', eventually fill the `a_vars' (not yet available from `req') -- and if `a_raw_data' is attached, store any read data inside `a_raw_data' diff --git a/library/server/wsf/src/wsf_multipart_form_data_handler.e b/library/server/wsf/src/wsf_multipart_form_data_handler.e index 4ecfa75b..edc15afe 100644 --- a/library/server/wsf/src/wsf_multipart_form_data_handler.e +++ b/library/server/wsf/src/wsf_multipart_form_data_handler.e @@ -43,12 +43,12 @@ feature -- Status report feature -- Execution - handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST; + handle (a_content_type: READABLE_STRING_8; req: WSF_REQUEST; a_vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8]) local s: READABLE_STRING_8 do - s := read_input_data (req.input, a_content_length) + s := full_input_data (req) if a_raw_data /= Void then a_raw_data.replace (s) end diff --git a/library/server/wsf/src/wsf_request.e b/library/server/wsf/src/wsf_request.e index be473168..be77a412 100644 --- a/library/server/wsf/src/wsf_request.e +++ b/library/server/wsf/src/wsf_request.e @@ -124,10 +124,26 @@ feature -- Access: Input input: WGI_INPUT_STREAM -- Server input channel + require + is_not_chunked_input: not is_chunked_input do Result := wgi_request.input end + is_chunked_input: BOOLEAN + -- Is request using chunked transfer-encoding? + do + Result := wgi_request.is_chunked_input + end + + chunked_input: detachable WGI_CHUNKED_INPUT_STREAM + -- Server input channel + require + is_chunked_input: is_chunked_input + do + Result := wgi_request.chunked_input + end + feature -- Helper is_request_method (m: READABLE_STRING_8): BOOLEAN @@ -768,6 +784,13 @@ feature -- HTTP_* Result := wgi_request.http_authorization end + http_transfer_encoding: detachable READABLE_STRING_8 + -- Transfer-Encoding + -- for instance chunked + do + Result := wgi_request.http_transfer_encoding + end + feature -- Extra CGI environment variables request_uri: READABLE_STRING_8 @@ -1133,13 +1156,11 @@ feature {NONE} -- Form fields and related local vars: like internal_form_data_parameters_table l_raw_data_cell: detachable CELL [detachable STRING_8] - n: NATURAL_64 l_type: like content_type do vars := internal_form_data_parameters_table if vars = Void then - n := content_length_value - if n = 0 then + if not is_chunked_input and content_length_value = 0 then create vars.make (0) vars.compare_objects else @@ -1151,9 +1172,10 @@ feature {NONE} -- Form fields and related l_type := content_type if l_type /= Void and then attached mime_handler (l_type) as hdl then - hdl.handle (l_type, n, Current, vars, l_raw_data_cell) + hdl.handle (l_type, Current, vars, l_raw_data_cell) end if l_raw_data_cell /= Void and then attached l_raw_data_cell.item as l_raw_data then + -- What if no mime handler is associated to `l_type' ? set_meta_string_variable ("RAW_POST_DATA", l_raw_data) end end diff --git a/library/server/wsf/tests/echo/echo-safe.ecf b/library/server/wsf/tests/echo/echo-safe.ecf new file mode 100644 index 00000000..d5cbbae4 --- /dev/null +++ b/library/server/wsf/tests/echo/echo-safe.ecf @@ -0,0 +1,22 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + + + diff --git a/library/server/wsf/tests/echo/src/echo_server.e b/library/server/wsf/tests/echo/src/echo_server.e new file mode 100644 index 00000000..3b42c80f --- /dev/null +++ b/library/server/wsf/tests/echo/src/echo_server.e @@ -0,0 +1,75 @@ +note + description : "Objects that ..." + author : "$Author$" + date : "$Date$" + revision : "$Revision$" + +class + ECHO_SERVER + +inherit + WSF_SERVICE + +create + make + +feature {NONE} -- Initialization + + make + -- Initialize `Current'. + local + launcher: DEFAULT_SERVICE_LAUNCHER + do + create launcher.make_and_launch_with_options (agent execute, <<["port", 9091]>>) + end + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + local + page: WSF_PAGE_RESPONSE + l_body: STRING_8 + do + create l_body.make (1024) + create page.make_with_body (l_body) + page.header.put_content_type_text_plain + + l_body.append ("REQUEST_METHOD=" + req.request_method + "%N") + l_body.append ("REQUEST_URI=" + req.request_uri + "%N") + l_body.append ("PATH_INFO=" + req.path_info + "%N") + l_body.append ("QUERY_STRING=" + req.query_string + "%N") + + l_body.append ("Query parameters:%N") + across + req.query_parameters as q + loop + l_body.append ("%T"+ q.item.name + "=" + q.item.string_representation +"%N") + end + + l_body.append ("Form parameters:%N") + across + req.form_parameters as q + loop + l_body.append ("%T"+ q.item.name + "=" + q.item.string_representation +"%N") + end + + l_body.append ("Meta variables:%N") + across + req.meta_variables as q + loop + l_body.append ("%T"+ q.item.name + "=" + q.item.string_representation +"%N") + end + + res.put_response (page) + end + +feature -- Access + +feature -- Change + +feature {NONE} -- Implementation + +invariant +-- invariant_clause: True + +end diff --git a/library/server/wsf/tests/src/test_wsf_request.e b/library/server/wsf/tests/src/test_wsf_request.e index fbe165d6..bf9407f5 100644 --- a/library/server/wsf/tests/src/test_wsf_request.e +++ b/library/server/wsf/tests/src/test_wsf_request.e @@ -53,11 +53,14 @@ feature {NONE} -- Events execute (req: WSF_REQUEST; res: WSF_RESPONSE) local q: detachable STRING_32 + page: WSF_PAGE_RESPONSE do + create page.make if attached req.request_uri as l_uri then if l_uri.starts_with (test_url ("get/01")) then - res.write_header (200, <<["Content-Type", "text/plain"]>>) - res.write_string ("get-01") + page.set_status_code (200) + page.header.put_content_type_text_plain + page.put_string ("get-01") create q.make_empty across @@ -69,11 +72,11 @@ feature {NONE} -- Events q.append (qcur.item.name.as_string_32 + "=" + qcur.item.as_string) end if not q.is_empty then - res.write_string ("(" + q + ")") + page.put_string ("(" + q + ")") end elseif l_uri.starts_with (test_url ("post/01")) then - res.write_header (200, <<["Content-Type", "text/plain"]>>) - res.write_string ("post-01") + page.put_header (200, <<["Content-Type", "text/plain"]>>) + page.put_string ("post-01") create q.make_empty across @@ -86,7 +89,7 @@ feature {NONE} -- Events end if not q.is_empty then - res.write_string ("(" + q + ")") + page.put_string ("(" + q + ")") end create q.make_empty @@ -102,16 +105,18 @@ feature {NONE} -- Events end if not q.is_empty then - res.write_string (" : " + q ) + page.put_string (" : " + q ) end else - res.write_header (200, <<["Content-Type", "text/plain"]>>) - res.write_string ("Hello") + page.put_header (200, <<["Content-Type", "text/plain"]>>) + page.put_string ("Hello") end else - res.write_header (200, <<["Content-Type", "text/plain"]>>) - res.write_string ("Bye") + page.put_header (200, <<["Content-Type", "text/plain"]>>) + page.put_string ("Bye") end + + page.send_to (res) end test_url (a_query_url: READABLE_STRING_8): READABLE_STRING_8 diff --git a/library/server/wsf/tests/src/test_wsf_request_chunked_input.e b/library/server/wsf/tests/src/test_wsf_request_chunked_input.e new file mode 100644 index 00000000..c213a73a --- /dev/null +++ b/library/server/wsf/tests/src/test_wsf_request_chunked_input.e @@ -0,0 +1,64 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + TEST_WSF_REQUEST_CHUNKED_INPUT + +inherit + TEST_WSF_REQUEST + redefine + test_get_request_01, + test_post_request_01 + end + +feature -- Test routines + + test_get_request_01 + -- New test routine + local + ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT + do + get_http_session + if attached http_session as sess then +-- create ctx.make +-- ctx.set_proxy ("127.0.0.1", 8888) --| debugging with http://www.fiddler2.com/ + + test_get_request ("get/01", ctx, "get-01") + test_get_request ("get/01/?foo=bar", ctx, "get-01(foo=bar)") + test_get_request ("get/01/?foo=bar&abc=def", ctx, "get-01(foo=bar&abc=def)") + test_get_request ("get/01/?lst=a&lst=b", ctx, "get-01(lst=[a,b])") + else + assert ("not_implemented", False) + end + end + + test_post_request_01 + -- New test routine + local + ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + get_http_session + if attached http_session as sess then + create ctx.make + ctx.add_form_parameter ("id", "123") + ctx.headers.extend ("chunked", "Transfer-Encoding") +-- ctx.set_proxy ("127.0.0.1", 8888) --| debugging with http://www.fiddler2.com/ + + test_post_request ("post/01", ctx, "post-01 : id=123") + test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : id=123") + test_post_request ("post/01/?foo=bar&abc=def", ctx, "post-01(foo=bar&abc=def) : id=123") + test_post_request ("post/01/?lst=a&lst=b", ctx, "post-01(lst=[a,b]) : id=123") + else + assert ("not_implemented", False) + end + end + +end + + diff --git a/library/server/wsf/tests/src/test_wsf_request_script_url.e b/library/server/wsf/tests/src/test_wsf_request_script_url.e index 4b441cd2..110a1e6f 100644 --- a/library/server/wsf/tests/src/test_wsf_request_script_url.e +++ b/library/server/wsf/tests/src/test_wsf_request_script_url.e @@ -80,7 +80,6 @@ feature {NONE} -- Implementation new_request (a_meta: ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]): WSF_REQUEST local wgi_req: WGI_REQUEST - req: WSF_REQUEST do create {WGI_REQUEST_NULL} wgi_req.make (Current, a_meta) create Result.make_from_wgi (wgi_req) diff --git a/library/server/wsf/tests/tests-safe.ecf b/library/server/wsf/tests/tests-safe.ecf index 08f7ccf4..df2c4883 100644 --- a/library/server/wsf/tests/tests-safe.ecf +++ b/library/server/wsf/tests/tests-safe.ecf @@ -1,5 +1,5 @@ - + @@ -14,11 +14,11 @@ - - + + - + diff --git a/library/server/wsf/tests/tests.ecf b/library/server/wsf/tests/tests.ecf index 1e184b24..2f97447d 100644 --- a/library/server/wsf/tests/tests.ecf +++ b/library/server/wsf/tests/tests.ecf @@ -1,5 +1,5 @@ - +