diff --git a/examples/filter/filter-safe.ecf b/examples/filter/filter-safe.ecf index ade4f80f..6c86cb8e 100644 --- a/examples/filter/filter-safe.ecf +++ b/examples/filter/filter-safe.ecf @@ -12,22 +12,23 @@ - - + + + + - - - - - - - - - - + + + + + + + + + diff --git a/examples/filter/src/filter/authentication_filter.e b/examples/filter/src/filter/authentication_filter.e index 9b407c7b..a18da1e7 100644 --- a/examples/filter/src/filter/authentication_filter.e +++ b/examples/filter/src/filter/authentication_filter.e @@ -8,9 +8,9 @@ class AUTHENTICATION_FILTER inherit - WSF_FILTER_CONTEXT_HANDLER [FILTER_HANDLER_CONTEXT] + WSF_FILTER - WSF_URI_TEMPLATE_CONTEXT_HANDLER [FILTER_HANDLER_CONTEXT] + WSF_URI_TEMPLATE_HANDLER SHARED_DATABASE_API @@ -18,7 +18,7 @@ inherit feature -- Basic operations - execute (ctx: FILTER_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE) + execute (req: WSF_REQUEST; res: WSF_RESPONSE) -- Execute the filter local l_auth: HTTP_AUTHORIZATION @@ -31,8 +31,8 @@ feature -- Basic operations attached l_auth.password as l_auth_password and then l_auth_password.same_string (l_user.password) then - ctx.set_user (l_user) - execute_next (ctx, req, res) + req.set_execution_variable ("user", l_user) + execute_next (req, res) else handle_unauthorized ("Unauthorized", req, res) end @@ -56,6 +56,6 @@ feature {NONE} -- Implementation end note - copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others" + copyright: "2011-2013, Olivier Ligot, Jocelyn Fiat and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/examples/filter/src/filter_server.e b/examples/filter/src/filter_server.e index 25d16fef..7368581d 100644 --- a/examples/filter/src/filter_server.e +++ b/examples/filter/src/filter_server.e @@ -24,39 +24,60 @@ create feature {NONE} -- Initialization make + local + l_message: STRING + l_factory: INET_ADDRESS_FACTORY do + create router.make (1) initialize_filter initialize_json - set_service_option ("port", 9090) + set_service_option ("port", port) + create l_message.make_empty + l_message.append_string ("Launching filter server at ") + create l_factory + l_message.append_string (l_factory.create_localhost.host_name) + l_message.append_string (" port ") + l_message.append_integer (port) + io.put_string (l_message) + io.put_new_line make_and_launch end create_filter -- Create `filter' local - l_router: WSF_ROUTER - l_authentication_filter_hdl: AUTHENTICATION_FILTER - l_user_filter: USER_HANDLER - l_routing_filter: WSF_ROUTING_FILTER + l_cors_filter: WSF_CORS_FILTER do - create l_router.make (1) - create l_authentication_filter_hdl - create l_user_filter - l_authentication_filter_hdl.set_next (l_user_filter) - - l_router.handle_with_request_methods ("/user/{userid}", l_authentication_filter_hdl, l_router.methods_get) - create l_routing_filter.make (l_router) - l_routing_filter.set_execute_default_action (agent execute_default) - filter := l_routing_filter + create l_cors_filter + filter := l_cors_filter end setup_filter -- Setup `filter' local + l_options_filter: WSF_CORS_OPTIONS_FILTER + l_authentication_filter: AUTHENTICATION_FILTER + l_user_filter: USER_HANDLER + l_methods: WSF_REQUEST_METHODS + l_routing_filter: WSF_ROUTING_FILTER l_logging_filter: WSF_LOGGING_FILTER do + create l_options_filter.make (router) + create l_authentication_filter + l_options_filter.set_next (l_authentication_filter) + create l_user_filter + l_authentication_filter.set_next (l_user_filter) + + create l_methods + l_methods.enable_options + l_methods.enable_get + router.handle_with_request_methods ("/user/{userid}", l_options_filter, l_methods) + create l_routing_filter.make (router) + l_routing_filter.set_execute_default_action (agent execute_default) + filter.set_next (l_routing_filter) + create l_logging_filter - filter.set_next (l_logging_filter) + l_routing_filter.set_next (l_logging_filter) end initialize_json @@ -73,30 +94,24 @@ feature -- Basic operations end execute_default (req: WSF_REQUEST; res: WSF_RESPONSE) - -- I'm using this method to handle the method not allowed response - -- in the case that the given uri does not have a corresponding http method - -- to handle it. local - h : HTTP_HEADER - l_description : STRING - l_api_doc : STRING + l_message: WSF_DEFAULT_ROUTER_RESPONSE do - if req.content_length_value > 0 then - req.input.read_string (req.content_length_value.as_integer_32) - end - create h.make - h.put_content_type_text_plain - l_api_doc := "%NPlease check the API%NURI:/user/{userid} METHOD: GET%N" - l_description := req.request_method + req.request_uri + " is not allowed" + "%N" + l_api_doc - h.put_content_length (l_description.count) - h.put_current_date - res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) - res.put_header_text (h.string) - res.put_string (l_description) + create l_message.make_with_router (req, router) + l_message.set_documentation_included (True) + res.send (l_message) end +feature {NONE} -- Implementation + + port: INTEGER = 9090 + -- Port number + + router: WSF_ROUTER; + -- Router + note - copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others" + copyright: "2011-2013, Olivier Ligot, Jocelyn Fiat and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/examples/filter/src/resource/user_handler.e b/examples/filter/src/resource/user_handler.e index 223e93d5..722ccd03 100644 --- a/examples/filter/src/resource/user_handler.e +++ b/examples/filter/src/resource/user_handler.e @@ -8,11 +8,11 @@ class USER_HANDLER inherit - WSF_FILTER_CONTEXT_HANDLER [FILTER_HANDLER_CONTEXT] + WSF_FILTER - WSF_URI_TEMPLATE_CONTEXT_HANDLER [FILTER_HANDLER_CONTEXT] + WSF_URI_TEMPLATE_HANDLER - WSF_RESOURCE_CONTEXT_HANDLER_HELPER [FILTER_HANDLER_CONTEXT] + WSF_RESOURCE_HANDLER_HELPER redefine do_get end @@ -23,30 +23,30 @@ inherit feature -- Basic operations - execute (ctx: FILTER_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE) + execute (req: WSF_REQUEST; res: WSF_RESPONSE) -- Execute request handler do - execute_methods (ctx, req, res) - execute_next (ctx, req, res) + execute_methods (req, res) + execute_next (req, res) end - do_get (ctx: FILTER_HANDLER_CONTEXT; req: WSF_REQUEST; res: WSF_RESPONSE) + do_get (req: WSF_REQUEST; res: WSF_RESPONSE) -- Using GET to retrieve resource information. -- If the GET request is SUCCESS, we response with -- 200 OK, and a representation of the user -- If the GET request is not SUCCESS, we response with -- 404 Resource not found require else - authenticated_user_attached: attached ctx.user + authenticated_user_attached: attached {USER} req.execution_variable ("user") local id : STRING do if attached req.orig_path_info as orig_path then id := get_user_id_from_path (orig_path) if attached retrieve_user (id) as l_user then - if l_user ~ ctx.user then + if l_user ~ req.execution_variable ("user") then compute_response_get (req, res, l_user) - elseif attached ctx.user as l_auth_user then + elseif attached {USER} req.execution_variable ("user") as l_auth_user then -- Trying to access another user that the authenticated one, -- which is forbidden in this example... handle_forbidden ("You try to access the user " + id.out + " while authenticating with the user " + l_auth_user.id.out, req, res) @@ -92,6 +92,6 @@ feature {NONE} -- Implementation end note - copyright: "2011-2012, Olivier Ligot, Jocelyn Fiat and others" + copyright: "2011-2013, Olivier Ligot, Jocelyn Fiat and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" end diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 001067a9..4e89707e 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -213,6 +213,25 @@ feature -- Header change: general put_header (k + colon_space + v) end + put_header_key_methods (k: READABLE_STRING_8; a_methods: ITERABLE [READABLE_STRING_8]) + -- Add header `k: a_methods', or replace existing header of same header methods/key + local + s: STRING_8 + do + create s.make_empty + across + a_methods as c + loop + if not s.is_empty then + s.append_string (", ") + end + s.append (c.item) + end + if not s.is_empty then + put_header_key_value (k, s) + end + end + feature -- Content related header put_content_type (t: READABLE_STRING_8) @@ -397,26 +416,38 @@ feature -- Content-type helpers 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_methods ({HTTP_HEADER_NAMES}.header_access_control_allow_methods, a_methods) + 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 - local - s: STRING_8 do - create s.make_empty - across - a_methods as c - loop - if not s.is_empty then - s.append_character (',') - end - s.append_character (' ') - s.append (c.item) - end - if not s.is_empty then - put_header_key_value ({HTTP_HEADER_NAMES}.header_allow, s) - end + put_header_key_methods ({HTTP_HEADER_NAMES}.header_allow, a_methods) end feature -- Date @@ -738,7 +769,7 @@ feature {NONE} -- Constants semi_colon_space: STRING = "; " note - copyright: "2011-2012, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2013, 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/network/protocol/http/src/http_header_names.e b/library/network/protocol/http/src/http_header_names.e index 8d679ea9..4bab76e8 100644 --- a/library/network/protocol/http/src/http_header_names.e +++ b/library/network/protocol/http/src/http_header_names.e @@ -194,6 +194,23 @@ feature -- Response header name -- Indicates the authentication scheme that should be used to access the requested entity. --| Example: WWW-Authenticate: Basic +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 + + 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 + + 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 + feature -- Request or Response header name header_cache_control: STRING = "Cache-Control" @@ -248,7 +265,7 @@ feature -- MIME related header_content_transfer_encoding: STRING = "Content-Transfer-Encoding" note - copyright: "2011-2012, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2013, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/library/server/wsf/router/filter/wsf_cors_filter.e b/library/server/wsf/router/filter/wsf_cors_filter.e new file mode 100644 index 00000000..b306ad60 --- /dev/null +++ b/library/server/wsf/router/filter/wsf_cors_filter.e @@ -0,0 +1,33 @@ +note + description: "Cross-Origin Resource Sharing filter." + author: "Olivier Ligot" + date: "$Date$" + revision: "$Revision$" + EIS: "name=Cross-Origin Resource Sharing", "src=http://www.w3.org/TR/cors/", "tag=W3C" + +class + WSF_CORS_FILTER + +inherit + WSF_FILTER + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter. + do + res.header.put_access_control_allow_all_origin + execute_next (req, res) + end + +note + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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/router/filter/wsf_cors_options_filter.e b/library/server/wsf/router/filter/wsf_cors_options_filter.e new file mode 100644 index 00000000..7ca0bd87 --- /dev/null +++ b/library/server/wsf/router/filter/wsf_cors_options_filter.e @@ -0,0 +1,59 @@ +note + description: "Filter that handles an OPTIONS request, with Cross-Origin Resource Sharing support." + author: "Olvier Ligot" + date: "$Date$" + revision: "$Revision$" + EIS: "name=Cross-Origin Resource Sharing", "src=http://www.w3.org/TR/cors/", "tag=W3C" + +class + WSF_CORS_OPTIONS_FILTER + +inherit + WSF_FILTER + + WSF_URI_TEMPLATE_HANDLER + +create + make + +feature {NONE} -- Initialization + + make (a_router: like router) + -- Initialize Current with `a_router'. + do + router := a_router + ensure + router_set: router = a_router + end + +feature -- Access + + router: WSF_ROUTER + -- Associated router + +feature -- Basic operations + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Execute the filter. + local + msg: WSF_CORS_OPTIONS_RESPONSE + do + if req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) then + create msg.make (req, router) + res.send (msg) + else + execute_next (req, res) + end + end + +note + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + source: "[ + Eiffel Software + 5949 Hollister Ave., Goleta, CA 93117 USA + Telephone 805-685-1006, Fax 805-685-6869 + Website http://www.eiffel.com + Customer support http://support.eiffel.com + ]" +end diff --git a/library/server/wsf/src/response/wsf_cors_options_response.e b/library/server/wsf/src/response/wsf_cors_options_response.e new file mode 100644 index 00000000..a0790a0b --- /dev/null +++ b/library/server/wsf/src/response/wsf_cors_options_response.e @@ -0,0 +1,64 @@ +note + description: "Response to an OPTIONS request, with Cross-Origin Resource Sharing support." + author: "Olivier Ligt" + date: "$Date$" + revision: "$Revision$" + EIS: "name=Cross-Origin Resource Sharing", "src=http://www.w3.org/TR/cors/", "tag=W3C" + +class + WSF_CORS_OPTIONS_RESPONSE + +inherit + WSF_RESPONSE_MESSAGE + +create + make + +feature {NONE} -- Initialization + + make (req: WSF_REQUEST; a_router: like router) + do + request := req + router := a_router + create header.make + end + +feature -- Access + + request: WSF_REQUEST + -- Associated request + + router: WSF_ROUTER + -- Associated router + + header: HTTP_HEADER + -- Response' header + +feature {WSF_RESPONSE} -- Output + + send_to (res: WSF_RESPONSE) + local + l_methods: WSF_REQUEST_METHODS + do + res.set_status_code ({HTTP_STATUS_CODE}.No_content) + header.put_current_date + header.put_access_control_allow_headers ({HTTP_HEADER_NAMES}.header_authorization) + l_methods := router.allowed_methods_for_request (request) + if not l_methods.is_empty then + header.put_allow (l_methods) + header.put_access_control_allow_methods (l_methods) + end + res.put_header_text (header.string) + end + +note + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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/wsf_response.e b/library/server/wsf/src/wsf_response.e index edd69fed..81d1e208 100644 --- a/library/server/wsf/src/wsf_response.e +++ b/library/server/wsf/src/wsf_response.e @@ -31,6 +31,7 @@ feature {NONE} -- Initialization do transfered_content_length := 0 wgi_response := r + create header.make end feature {WSF_RESPONSE_EXPORTER} -- Properties @@ -114,6 +115,13 @@ feature -- Status setting feature -- Header output operation + header: HTTP_HEADER + -- Header + -- This is useful when we want to fill the `header' + -- in two pass (i.e. in two different classes). + -- We first call features of `header', and finally + -- we call `put_header_text' + put_header_text (a_text: READABLE_STRING_8) -- Sent `a_text' and just before send the status code require @@ -121,9 +129,23 @@ feature -- Header output operation header_not_committed: not header_committed a_text_ends_with_single_crlf: a_text.count > 2 implies not a_text.substring (a_text.count - 2, a_text.count).same_string ("%R%N") a_text_does_not_end_with_double_crlf: a_text.count > 4 implies not a_text.substring (a_text.count - 4, a_text.count).same_string ("%R%N%R%N") + local + l_text: READABLE_STRING_8 + l_header: HTTP_HEADER do wgi_response.set_status_code (status_code, status_reason_phrase) - wgi_response.put_header_text (a_text) + if header.is_empty then + l_text := a_text + else + create l_header.make_from_raw_header_data (a_text) + across + l_header as c + loop + header.put_header (c.item.string) + end + l_text := header.string + end + wgi_response.put_header_text (l_text) ensure status_set: status_is_set status_committed: status_committed @@ -376,7 +398,7 @@ feature -- Error reporting end note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" + copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software