diff --git a/examples/restbucksCRUD/src/resource/order_handler.e b/examples/restbucksCRUD/src/resource/order_handler.e index c11883d1..0bddd924 100644 --- a/examples/restbucksCRUD/src/resource/order_handler.e +++ b/examples/restbucksCRUD/src/resource/order_handler.e @@ -4,22 +4,10 @@ note date: "$Date$" revision: "$Revision$" -class - ORDER_HANDLER +class ORDER_HANDLER inherit - WSF_URI_HANDLER - rename - execute as uri_execute, - new_mapping as new_uri_mapping - end WSF_URI_TEMPLATE_HANDLER - rename - execute as uri_template_execute, - new_mapping as new_uri_template_mapping - select - new_uri_template_mapping - end WSF_RESOURCE_HANDLER_HELPER redefine @@ -28,22 +16,20 @@ inherit do_put, do_delete end + SHARED_DATABASE_API + SHARED_EJSON + REFACTORING_HELPER + SHARED_ORDER_VALIDATION WSF_SELF_DOCUMENTED_HANDLER -feature -- execute +feature -- Execute - uri_execute (req: WSF_REQUEST; res: WSF_RESPONSE) - -- Execute request handler - do - execute_methods (req, res) - end - - uri_template_execute (req: WSF_REQUEST; res: WSF_RESPONSE) + execute (req: WSF_REQUEST; res: WSF_RESPONSE) -- Execute request handler do execute_methods (req, res) diff --git a/examples/restbucksCRUD/src/restbucks_server.e b/examples/restbucksCRUD/src/restbucks_server.e index 3fbd8add..653ce132 100644 --- a/examples/restbucksCRUD/src/restbucks_server.e +++ b/examples/restbucksCRUD/src/restbucks_server.e @@ -3,21 +3,23 @@ note date : "$Date$" revision : "$Revision$" -class - RESTBUCKS_SERVER +class RESTBUCKS_SERVER inherit - ANY - WSF_URI_TEMPLATE_ROUTED_SERVICE - redefine - execute_default + WSF_ROUTED_SKELETON_SERVICE + undefine + requires_proxy end + + WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE WSF_HANDLER_HELPER WSF_DEFAULT_SERVICE + WSF_NO_PROXY_POLICY + create make @@ -42,18 +44,9 @@ feature {NONE} -- Initialization router.handle_with_request_methods ("/api/doc", doc, router.methods_GET) end -feature -- Execution - - 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. - do - Precursor (req, res) - end note - copyright: "2011-2012, Javier Velilla and others" + copyright: "2011-2013, Javier Velilla and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software diff --git a/examples/upload_image/src/image_uploader.e b/examples/upload_image/src/image_uploader.e index 7e190261..87c03aeb 100644 --- a/examples/upload_image/src/image_uploader.e +++ b/examples/upload_image/src/image_uploader.e @@ -10,11 +10,14 @@ class inherit ANY - WSF_ROUTED_SERVICE + WSF_ROUTED_SKELETON_SERVICE + undefine + requires_proxy + end - WSF_URI_TEMPLATE_ROUTED_SERVICE + WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE - WSF_URI_ROUTED_SERVICE + WSF_NO_PROXY_POLICY WSF_DEFAULT_SERVICE diff --git a/library/network/protocol/http/src/http_header.e b/library/network/protocol/http/src/http_header.e index 69a04671..b1eee52d 100644 --- a/library/network/protocol/http/src/http_header.e +++ b/library/network/protocol/http/src/http_header.e @@ -871,6 +871,8 @@ feature {NONE} -- Implementation 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 diff --git a/library/security/openid/consumer/demo/application.e b/library/security/openid/consumer/demo/application.e index 542f101a..c92a7146 100644 --- a/library/security/openid/consumer/demo/application.e +++ b/library/security/openid/consumer/demo/application.e @@ -7,10 +7,18 @@ class APPLICATION inherit - WSF_URI_TEMPLATE_ROUTED_SERVICE + + WSF_ROUTED_SKELETON_SERVICE + undefine + requires_proxy + end + + WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE WSF_SERVICE + WSF_NO_PROXY_POLICY + create make_and_launch diff --git a/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_helper_for_routed_service.e b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_helper_for_routed_service.e new file mode 100644 index 00000000..e304aae0 --- /dev/null +++ b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_helper_for_routed_service.e @@ -0,0 +1,62 @@ +note + + description: "Facilities inheritance to add URI template-base routing to a routed service" + + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE + +feature -- Access + + router: WSF_ROUTER + -- Router used to dispatch the request according to the WSF_REQUEST object + -- and associated request methods; + -- This should not be implemented by descendants. Instead, you gain an effective + -- version by also inheriting from WSF_ROUTED_SERVICE, or one of it's descendants. + deferred + ensure + router_not_void: Result /= Void + end + +feature -- Mapping helper: uri + + map_uri_template (a_tpl: STRING; h: WSF_URI_TEMPLATE_HANDLER) + -- Map `h' as handler for `a_tpl' + require + a_tpl_attached: a_tpl /= Void + h_attached: h /= Void + do + map_uri_template_with_request_methods (a_tpl, h, Void) + end + + map_uri_template_with_request_methods (a_tpl: READABLE_STRING_8; h: WSF_URI_TEMPLATE_HANDLER; rqst_methods: detachable WSF_REQUEST_METHODS) + -- Map `h' as handler for `a_tpl' for request methods `rqst_methods'. + require + a_tpl_attached: a_tpl /= Void + h_attached: h /= Void + do + router.map_with_request_methods (create {WSF_URI_TEMPLATE_MAPPING}.make (a_tpl, h), rqst_methods) + end + +feature -- Mapping helper: uri agent + + map_uri_template_agent (a_tpl: READABLE_STRING_8; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]]) + -- Map `proc' as handler for `a_tpl' + require + a_tpl_attached: a_tpl /= Void + proc_attached: proc /= Void + do + map_uri_template_agent_with_request_methods (a_tpl, proc, Void) + end + + map_uri_template_agent_with_request_methods (a_tpl: READABLE_STRING_8; proc: PROCEDURE [ANY, TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]]; rqst_methods: detachable WSF_REQUEST_METHODS) + -- Map `proc' as handler for `a_tpl' for request methods `rqst_methods'. + require + a_tpl_attached: a_tpl /= Void + proc_attached: proc /= Void + do + map_uri_template_with_request_methods (a_tpl, create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (proc), rqst_methods) + end + +end diff --git a/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_router_helper.e b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_router_helper.e index cf60b7f0..b1db35ad 100644 --- a/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_router_helper.e +++ b/library/server/wsf/router/support/uri_template/helpers/wsf_uri_template_router_helper.e @@ -7,6 +7,8 @@ note deferred class WSF_URI_TEMPLATE_ROUTER_HELPER + obsolete "Use class WSF_URI_TEMPLATE_HELPER_FOR_ROUTED_SERVICE in conjunction with WSF_ROUTED_SKELETON_SERVICE" + feature -- Access router: WSF_ROUTER @@ -19,7 +21,7 @@ feature -- Mapping helper: uri -- Map `h' as handler for `a_tpl' require a_tpl_attached: a_tpl /= Void - h_attached: h /= Void + h_attached: h /= Void do map_uri_template_with_request_methods (a_tpl, h, Void) end @@ -39,7 +41,7 @@ feature -- Mapping helper: uri agent -- Map `proc' as handler for `a_tpl' require a_tpl_attached: a_tpl /= Void - proc_attached: proc /= Void + proc_attached: proc /= Void do map_uri_template_agent_with_request_methods (a_tpl, proc, Void) end diff --git a/library/server/wsf/router/wsf_no_proxy_policy.e b/library/server/wsf/router/wsf_no_proxy_policy.e new file mode 100644 index 00000000..36aa4d7e --- /dev/null +++ b/library/server/wsf/router/wsf_no_proxy_policy.e @@ -0,0 +1,36 @@ +note + + description: "[ + Policy that no client ever need use a proxy. + + Users of this policy cannot safely use chunked transfer-encoding, or any + HTTP/1.1-specific features. So best used only for examples. + ]" + + date: "$Date$" + revision: "$Revision$" + +class WSF_NO_PROXY_POLICY + +inherit + + WSF_PROXY_USE_POLICY + redefine + requires_proxy + end + +feature -- Access + + requires_proxy (req: WSF_REQUEST): BOOLEAN + -- Does `req' require use of `proxy_server'? + do + end + + proxy_server (req: WSF_REQUEST): URI + -- Absolute URI of proxy server which `req' must use + do + create Result.make_from_string ("") + -- doesn't meet the postcondition, but the precondition is never true. + end + +end diff --git a/library/server/wsf/router/wsf_proxy_use_policy.e b/library/server/wsf/router/wsf_proxy_use_policy.e new file mode 100644 index 00000000..a788c977 --- /dev/null +++ b/library/server/wsf/router/wsf_proxy_use_policy.e @@ -0,0 +1,88 @@ +note + + description: "[ + Policies that determine if the client must use a proxy server + to access the resource. + + The default policy implemented here is to require + use of the proxy for HTTP/1.0 clients (only) for all requests. + ]" + + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_PROXY_USE_POLICY + +feature -- Access + + requires_proxy (req: WSF_REQUEST): BOOLEAN + -- Does `req' require use of `proxy_server'? + require + req_attached: req /= Void + do + if is_http_1_0 (req) then + Result := True + end + end + + proxy_server (req: WSF_REQUEST): URI + -- Absolute URI of proxy server which `req' must use + require + req_attached: req /= Void + proxy_required: requires_proxy (req) + deferred + ensure + proxy_server_attached: Result /= Void + valid_uri: Result.is_valid + absolute_uri: not Result.scheme.is_empty + http_or_https: Result.scheme.is_case_insensitive_equal ("http") or + Result.scheme.is_case_insensitive_equal ("https") + end + + is_http_1_0 (req: WSF_REQUEST): BOOLEAN + -- Does `req' come from an HTTP/1.0 client? + require + req_attached: req /= Void + local + l_protocol: READABLE_STRING_8 + l_tokens: LIST [READABLE_STRING_8] + l_protocol_name, l_protocol_version, l_major, l_minor: STRING_8 + do + l_protocol := req.server_protocol + l_tokens := l_protocol.split ('/') + if l_tokens.count = 2 then + l_protocol_name := l_tokens [1].as_string_8 + l_protocol_name.left_adjust + l_protocol_name.right_adjust + if l_protocol_name.is_case_insensitive_equal ({HTTP_CONSTANTS}.http_version_1_0.substring (1, 4)) then + l_protocol_version := l_tokens [2].as_string_8 + l_protocol_version.left_adjust + l_protocol_version.right_adjust + l_tokens := l_protocol_version.split ('.') + if l_tokens.count = 2 then + l_major := l_tokens [1].as_string_8 + l_major.left_adjust + l_major.right_adjust + l_minor := l_tokens [2].as_string_8 + l_minor.left_adjust + l_minor.right_adjust + if l_major.is_integer and then l_major.to_integer = 1 and then + l_minor.is_integer and then l_minor.to_integer = 0 then + Result := True + end + end + end + 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/router/wsf_routed_service.e b/library/server/wsf/router/wsf_routed_service.e index 06cd6a9e..7526088a 100644 --- a/library/server/wsf/router/wsf_routed_service.e +++ b/library/server/wsf/router/wsf_routed_service.e @@ -48,6 +48,9 @@ feature -- Execution if not sess.dispatched then execute_default (req, res) end + ensure + response_status_is_set: res.status_is_set + header_sent: res.header_committed end execute_default (req: WSF_REQUEST; res: WSF_RESPONSE) diff --git a/library/server/wsf/router/wsf_routed_skeleton_service.e b/library/server/wsf/router/wsf_routed_skeleton_service.e new file mode 100644 index 00000000..6253c8cd --- /dev/null +++ b/library/server/wsf/router/wsf_routed_skeleton_service.e @@ -0,0 +1,283 @@ +note + description: "Summary description for {WSF_ROUTED_SERVICE}." + author: "" + date: "$Date$" + revision: "$Revision$" + +deferred class WSF_ROUTED_SKELETON_SERVICE + +inherit + + WSF_ROUTED_SERVICE + redefine + execute + end + + WSF_SYSTEM_OPTIONS_ACCESS_POLICY + + WSF_PROXY_USE_POLICY + +feature -- Execution + + execute (req: WSF_REQUEST; res: WSF_RESPONSE) + -- If the service is available, and request URI is not too long, dispatch the request + -- and if handler is not found, execute the default procedure `execute_default'. + local + l_sess: WSF_ROUTER_SESSION + do + --| When we reach here, the request has already passed check for 400 (Bad request), + --| which is implemented in WSF_REQUEST.make_from_wgi (when it calls `analyze'). + if unavailable then + handle_unavailable (res) + elseif requires_proxy (req) then + handle_use_proxy (req, res) + elseif maximum_uri_length > 0 and then req.request_uri.count.to_natural_32 > maximum_uri_length then + handle_request_uri_too_long (res) + elseif req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) and then + req.request_uri.same_string ("*") then + handle_server_options (req, res) + else + create l_sess + router.dispatch (req, res, l_sess) + if not l_sess.dispatched then + execute_default (req, res) + end + end + end + +feature -- Measurement + + maximum_uri_length: NATURAL + -- Maximum length in characters (or zero for no limit) permitted + -- for {WSF_REQUEST}.request_uri + +feature -- Status report + + unavailable: BOOLEAN + -- Is service currently unavailable? + + unavailablity_message: detachable READABLE_STRING_8 + -- Message to be included as text of response body for {HTTP_STATUS_CODE}.service_unavailable + + unavailability_duration: NATURAL + -- Delta seconds for service unavailability (0 if not known) + + unavailable_until: detachable DATE_TIME + -- Time at which service becomes available again (if known) + +feature -- Status setting + + set_available + -- Set `unavailable' to `False'. + do + unavailable := False + unavailablity_message := Void + unavailable_until := Void + ensure + available: unavailable = False + unavailablity_message_detached: unavailablity_message = Void + unavailable_until_detached: unavailable_until = Void + end + + set_unavailable (a_message: READABLE_STRING_8; a_duration: NATURAL; a_until: detachable DATE_TIME) + -- Set `unavailable' to `True'. + require + a_message_attached: a_message /= Void + a_duration_xor_a_until: a_duration > 0 implies a_until = Void + do + unavailable := True + unavailablity_message := a_message + unavailability_duration := a_duration + ensure + unavailable: unavailable = True + unavailablity_message_aliased: unavailablity_message = a_message + unavailability_duration_set: unavailability_duration = a_duration + unavailable_until_aliased: unavailable_until = a_until + end + + set_maximum_uri_length (a_len: NATURAL) + -- Set `maximum_uri_length' to `a_len'. + -- Can pass zero to mean no restrictions. + do + maximum_uri_length := a_len + ensure + maximum_uri_length_set: maximum_uri_length = a_len + end + +feature {NONE} -- Implementation + + handle_unavailable (res: WSF_RESPONSE) + -- Write "Service unavailable" response to `res'. + require + unavailable: unavailable + res_attached: res /= Void + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_plain + check attached unavailablity_message as m then + -- invariant `unavailability_message_attached' plus precondition `unavailable' + h.put_content_length (m.count) + h.put_current_date + res.set_status_code ({HTTP_STATUS_CODE}.service_unavailable) + if unavailability_duration > 0 then + h.put_header_key_value ({HTTP_HEADER_NAMES}.header_retry_after, unavailability_duration.out) + elseif attached unavailable_until as u then + h.put_header_key_value ({HTTP_HEADER_NAMES}.header_retry_after, + h.date_to_rfc1123_http_date_format (u)) + end + res.put_header_text (h.string) + res.put_string (m) + end + ensure + response_status_is_set: res.status_is_set + status_is_service_unavailable: res.status_code = {HTTP_STATUS_CODE}.service_unavailable + body_sent: res.message_committed and then res.transfered_content_length > 0 + body_content_was_unavailablity_message: True -- doesn't seem to be any way to check + end + + handle_request_uri_too_long (res: WSF_RESPONSE) + -- Write "Request URI too long" response into `res'. + require + res_attached: res /= Void + local + h: HTTP_HEADER + m: READABLE_STRING_8 + do + create h.make + h.put_content_type_text_plain + h.put_current_date + m := "Maximum permitted length for request URI is " + maximum_uri_length.out + " characters" + h.put_content_length (m.count) + res.set_status_code ({HTTP_STATUS_CODE}.request_uri_too_long) + res.put_header_text (h.string) + res.put_string (m) + ensure + response_status_is_set: res.status_is_set + status_is_request_uri_too_long: res.status_code = {HTTP_STATUS_CODE}.request_uri_too_long + body_sent: res.message_committed and then res.transfered_content_length > 0 + end + + frozen handle_server_options (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write response to OPTIONS * into `res'. + require + req_attached: req /= Void + res_attached: res /= Void + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) + server_options_requested: req.request_uri.same_string ("*") + do + --| First check if forbidden. + --| (N.B. authentication requires an absoluteURI (RFC3617 page 3), and so cannot be used for OPTIONS *. + --| Otherwise construct an Allow response automatically from the router. + if is_system_options_forbidden (req) then + handle_system_options_forbidden (req, res) + else + handle_system_options (req, res) + end + ensure + response_status_is_set: res.status_is_set + valid_response_code: res.status_code = {HTTP_STATUS_CODE}.forbidden or + res.status_code = {HTTP_STATUS_CODE}.not_found or res.status_code = {HTTP_STATUS_CODE}.ok + header_sent: res.header_committed and res.message_committed + end + + frozen handle_system_options_forbidden (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write a 403 Forbidden or a 404 Not found response into `res'. + require + req_attached: req /= Void + res_attached: res /= Void + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) + server_options_requested: req.request_uri.same_string ("*") + local + m: detachable READABLE_STRING_8 + h: HTTP_HEADER + do + m := system_options_forbidden_text (req) + if attached {READABLE_STRING_8} m as l_msg then + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_content_length (l_msg.count) + res.set_status_code ({HTTP_STATUS_CODE}.forbidden) + res.put_header_text (h.string) + res.put_string (l_msg) + else + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_content_length (0) + res.set_status_code ({HTTP_STATUS_CODE}.not_found) + res.put_header_text (h.string) + end + ensure + response_status_is_set: res.status_is_set + valid_response_code: res.status_code = {HTTP_STATUS_CODE}.forbidden or + res.status_code = {HTTP_STATUS_CODE}.not_found + header_sent: res.header_committed + message_sent: res.message_committed + end + + handle_system_options (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write response to OPTIONS * into `res'. + -- This may be redefined by the user, but normally this will not be necessary. + require + req_attached: req /= Void + res_attached: res /= Void + method_is_options: req.is_request_method ({HTTP_REQUEST_METHODS}.method_options) + server_options_requested: req.request_uri.same_string ("*") + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_allow (router.all_allowed_methods) + h.put_content_length (0) + res.set_status_code ({HTTP_STATUS_CODE}.ok) + res.put_header_text (h.string) + ensure + response_status_is_set: res.status_is_set + response_code_ok: res.status_code = {HTTP_STATUS_CODE}.ok + header_sent: res.header_committed and res.message_committed + empty_body: res.transfered_content_length = 0 + end + + frozen handle_use_proxy (req: WSF_REQUEST; res: WSF_RESPONSE) + -- Write Use Proxy response `res'. + require + res_attached: res /= Void + req_attached: req /= Void + proxy_required: requires_proxy (req) + local + h: HTTP_HEADER + do + create h.make + h.put_content_type_text_plain + h.put_current_date + h.put_location (proxy_server (req).string) + h.put_content_length (0) + res.set_status_code ({HTTP_STATUS_CODE}.use_proxy) + ensure + response_status_is_set: res.status_is_set + response_code_use_proxy: res.status_code = {HTTP_STATUS_CODE}.use_proxy + header_sent: res.header_committed and res.message_committed + end + +invariant + + unavailability_message_attached: unavailable implies attached unavailablity_message as m and then + m.count > 0 + unavailability_duration_xor_unavailable_until: unavailability_duration > 0 implies unavailable_until = Void + +;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/wsf_router.e b/library/server/wsf/router/wsf_router.e index e95f1c37..efd46a6d 100644 --- a/library/server/wsf/router/wsf_router.e +++ b/library/server/wsf/router/wsf_router.e @@ -317,6 +317,26 @@ feature -- Status report end end + all_allowed_methods: WSF_REQUEST_METHODS + -- Methods allowed for ALL requests handled by `Current' + local + l_mapping: WSF_ROUTER_MAPPING + do + create Result + across + mappings as c + loop + if attached c.item.request_methods as m then + Result := Result + m + end + l_mapping := c.item.mapping + if attached {WSF_ROUTING_HANDLER} l_mapping.handler as l_routing then + Result := Result + l_routing.router.all_allowed_methods + end + --| not sure if that covers everything - Jocelyn, please comment + end + end + feature -- Hook execute_before (a_mapping: WSF_ROUTER_MAPPING) diff --git a/library/server/wsf/router/wsf_system_options_access_policy.e b/library/server/wsf/router/wsf_system_options_access_policy.e new file mode 100644 index 00000000..6915af52 --- /dev/null +++ b/library/server/wsf/router/wsf_system_options_access_policy.e @@ -0,0 +1,40 @@ +note + + description: "[ + Policy to decide if OPTIONS * is honoured. + Servers that wish to forbid OPTIONS * requests + can redefine `is_system_options_forbidden'. + + Response 403 Forbidden is meant to be accompanied + by an entity body describing the reason for the refusal. + Since authentication cannot be used for OPTIONS *, there + are limited grounds for selective refusal (the IP address might + be used though), so we provide a convenient default for + `system_options_forbidden_text'. + ]" + + date: "$Date$" + revision: "$Revision$" + +class WSF_SYSTEM_OPTIONS_ACCESS_POLICY + +feature -- Access + + is_system_options_forbidden (req: WSF_REQUEST): BOOLEAN + -- Should we return 403 Forbidden in response to OPTIONS * requests? + require + req_attached: req /= Void + do + -- by default, unconditionally no. + end + + system_options_forbidden_text (req: WSF_REQUEST): detachable READABLE_STRING_8 + -- Content of 403 Forbidden response; + -- Returning `Void' means instead respond with 403 Not found + require + req_attached: req /= Void + do + Result := "OPTIONS * is not permitted" + end + +end diff --git a/library/server/wsf/src/response/wsf_method_not_allowed_response.e b/library/server/wsf/src/response/wsf_method_not_allowed_response.e index 5e647c63..4b1b580c 100644 --- a/library/server/wsf/src/response/wsf_method_not_allowed_response.e +++ b/library/server/wsf/src/response/wsf_method_not_allowed_response.e @@ -1,6 +1,8 @@ note description: "[ - This class is used to report a 405 Method not allowed response + This class is used to report a 405 Method not allowed response, + or a 501 not implemented response, depending upon whether + the method is known to the server. ]" date: "$Date$" revision: "$Revision$" @@ -76,25 +78,45 @@ feature {WSF_RESPONSE} -- Output send_to (res: WSF_RESPONSE) local - s: STRING + s, l_html_error_code_text: STRING l_text: detachable READABLE_STRING_GENERAL l_loc: detachable READABLE_STRING_8 h: like header + --l_recognized: BOOLEAN + l_messages: HTTP_STATUS_CODE_MESSAGES do + create l_messages h := header - res.set_status_code ({HTTP_STATUS_CODE}.method_not_allowed) + -- To be considered later + --l_recognized := recognized_methods.has (request.request_method.as_upper) + --if l_recognized then + res.set_status_code (l_messages.method_not_allowed) + --else + -- res.set_status_code (l_messages.not_implemented) + --end if attached suggested_methods as lst and then not lst.is_empty then h.put_allow (lst) end - s := "Not allowed" + if attached l_messages.http_status_code_message (res.status_code) as l_msg then + s := l_msg + else + check + impossible: False + -- as res.status_code is set to one of the codes that will produce + -- a non-void response, even though there is no postcondition to prove it + end + s := "Bug in server" + end + + l_html_error_code_text := html_error_code_text (l_messages, True) if request.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then s := "" s.append ("") s.append (html_encoder.encoded_string (request.request_uri)) - s.append ("Error 405 (Method Not Allowed)!!") + s.append (l_html_error_code_text + "!!") s.append ("%N") s.append ( "[ @@ -111,15 +133,15 @@ feature {WSF_RESPONSE} -- Output - + ]") s.append ("
") s.append ("
") s.append ("
") s.append ("
") s.append ("
") - s.append ("Error 405 (Method Not Allowed)
") - s.append ("
Error 405 (Method Not Allowed): the request method ") + s.append (l_html_error_code_text + "
") + s.append ("
" + l_html_error_code_text + ": the request method ") s.append (request.request_method) s.append (" is inappropriate for the URL for " + html_encoder.encoded_string (request.request_uri) + ".
") if attached suggested_methods as lst and then not lst.is_empty then @@ -180,7 +202,7 @@ feature {WSF_RESPONSE} -- Output h.put_content_type_text_html else - s := "Error 405 (Method Not Allowed): the request method " + s := l_html_error_code_text + ": the request method " s.append (request.request_method) s.append (" is inappropriate for the URL for '" + html_encoder.encoded_string (request.request_uri) + "'.%N") if attached suggested_methods as lst and then not lst.is_empty then @@ -239,8 +261,56 @@ feature {WSF_RESPONSE} -- Output res.flush end +feature {NONE} -- Implementation + + -- To be discussed later... + --recognized_methods: WSF_REQUEST_METHODS + -- All methods defined in HTTP/1.1 specification + --| Should this include CONNECT? It probably shouldn't be recognized by an origin server, + --| We will need a way to extend this for additional methods that the server implements. E.g. PATCH. + -- do + -- create Result.make_from_iterable (<< + -- {HTTP_REQUEST_METHODS}.method_head, + -- {HTTP_REQUEST_METHODS}.method_get, + -- {HTTP_REQUEST_METHODS}.method_trace, + -- {HTTP_REQUEST_METHODS}.method_options, + -- {HTTP_REQUEST_METHODS}.method_post, + -- {HTTP_REQUEST_METHODS}.method_put, + -- {HTTP_REQUEST_METHODS}.method_delete + -- >>) + -- ensure + -- recognized_methods_not_void: Result /= Void + -- end + + html_error_code_text (a_messages: HTTP_STATUS_CODE_MESSAGES; a_recognized: BOOLEAN): READABLE_STRING_8 + -- Message for including in HTML error text according to `a_recognized' + require + a_messages_attached: a_messages /= Void + local + l_code: INTEGER + do + if a_recognized then + l_code := a_messages.method_not_allowed + else + l_code := a_messages.not_implemented + end + if attached a_messages.http_status_code_message (l_code) as l_msg then + Result := "Error " + l_code.out + " (" + l_msg + ")" + else + check + impossible: False + -- as res.status_code is set to one of the codes that will produce + -- a non-void response, even though there is no postcondition to prove it. + -- The postcondition wouldn't be needed if there was a precondition using is_valid_http_status_code + end + Result := "Bug in server" + end + ensure + html_error_code_text_attached: Result /= Void + 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 diff --git a/library/server/wsf/wsf-safe.ecf b/library/server/wsf/wsf-safe.ecf index 95a12d0c..4fb23bbf 100644 --- a/library/server/wsf/wsf-safe.ecf +++ b/library/server/wsf/wsf-safe.ecf @@ -1,5 +1,5 @@ - + @@ -16,6 +16,7 @@ + diff --git a/library/server/wsf/wsf.ecf b/library/server/wsf/wsf.ecf index 7c540415..3d19ec4d 100644 --- a/library/server/wsf/wsf.ecf +++ b/library/server/wsf/wsf.ecf @@ -16,7 +16,9 @@ - + +