Merge branch 'master' of github.com:EiffelWebFramework/EWF into widget_integration

This commit is contained in:
2014-07-07 10:30:42 +02:00
318 changed files with 14865 additions and 10865 deletions

View File

@@ -1,51 +1,62 @@
# Available libraries.
http_client-safe : c:\_dev\EWF\EWF-dev\library\network\http_client\http_client-safe.ecf
http_client : c:\_dev\EWF\EWF-dev\library\network\http_client\http_client.ecf
conneg-safe : c:\_dev\EWF\EWF-dev\library\network\protocol\CONNEG\conneg-safe.ecf
conneg : c:\_dev\EWF\EWF-dev\library\network\protocol\CONNEG\conneg.ecf
http-safe : c:\_dev\EWF\EWF-dev\library\network\protocol\http\http-safe.ecf
http : c:\_dev\EWF\EWF-dev\library\network\protocol\http\http.ecf
http_authorization-safe : c:\_dev\EWF\EWF-dev\library\server\authentication\http_authorization\http_authorization-safe.ecf
http_authorization : c:\_dev\EWF\EWF-dev\library\server\authentication\http_authorization\http_authorization.ecf
ewsgi-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\ewsgi-safe.ecf
ewsgi : c:\_dev\EWF\EWF-dev\library\server\ewsgi\ewsgi.ecf
ewsgi_spec-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\ewsgi_spec-safe.ecf
ewsgi_spec : c:\_dev\EWF\EWF-dev\library\server\ewsgi\ewsgi_spec.ecf
cgi-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\cgi\cgi-safe.ecf
cgi : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\cgi\cgi.ecf
libfcgi-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\libfcgi\libfcgi-safe.ecf
libfcgi : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\libfcgi\libfcgi.ecf
nino-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\nino\nino-safe.ecf
nino : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\nino\nino.ecf
null-safe : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\null\null-safe.ecf
null : c:\_dev\EWF\EWF-dev\library\server\ewsgi\connectors\null\null.ecf
fcgi-safe : c:\_dev\EWF\EWF-dev\library\server\libfcgi\fcgi-safe.ecf
fcgi : c:\_dev\EWF\EWF-dev\library\server\libfcgi\fcgi.ecf
libfcgi-safe : c:\_dev\EWF\EWF-dev\library\server\libfcgi\libfcgi-safe.ecf
libfcgi : c:\_dev\EWF\EWF-dev\library\server\libfcgi\libfcgi.ecf
router-safe : c:\_dev\EWF\EWF-dev\library\server\request\router\router-safe.ecf
router : c:\_dev\EWF\EWF-dev\library\server\request\router\router.ecf
wsf-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\wsf-safe.ecf
wsf : c:\_dev\EWF\EWF-dev\library\server\wsf\wsf.ecf
all-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\all-safe.ecf
cgi-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\cgi-safe.ecf
cgi : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\cgi.ecf
libfcgi-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\libfcgi-safe.ecf
libfcgi : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\libfcgi.ecf
nino-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\nino-safe.ecf
nino : c:\_dev\EWF\EWF-dev\library\server\wsf\connector\nino.ecf
cgi-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\default\cgi-safe.ecf
cgi : c:\_dev\EWF\EWF-dev\library\server\wsf\default\cgi.ecf
libfcgi-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\default\libfcgi-safe.ecf
libfcgi : c:\_dev\EWF\EWF-dev\library\server\wsf\default\libfcgi.ecf
nino-safe : c:\_dev\EWF\EWF-dev\library\server\wsf\default\nino-safe.ecf
nino : c:\_dev\EWF\EWF-dev\library\server\wsf\default\nino.ecf
wsf_extension-safe : c:\_dev\EWF\EWF-dev\library\server\wsf_extension\wsf_extension-safe.ecf
wsf_extension : c:\_dev\EWF\EWF-dev\library\server\wsf_extension\wsf_extension.ecf
encoder-safe : c:\_dev\EWF\EWF-dev\library\text\encoder\encoder-safe.ecf
encoder : c:\_dev\EWF\EWF-dev\library\text\encoder\encoder.ecf
error-safe : c:\_dev\EWF\EWF-dev\library\utility\general\error\error-safe.ecf
error : c:\_dev\EWF\EWF-dev\library\utility\general\error\error.ecf
http_client-safe : C:\_dev\projects\ewf\ewf\library\network\http_client\http_client-safe.ecf
http_client : C:\_dev\projects\ewf\ewf\library\network\http_client\http_client.ecf
conneg-safe : C:\_dev\projects\ewf\ewf\library\network\protocol\content_negotiation\conneg-safe.ecf
conneg : C:\_dev\projects\ewf\ewf\library\network\protocol\content_negotiation\conneg.ecf
http-safe : C:\_dev\projects\ewf\ewf\library\network\protocol\http\http-safe.ecf
http : C:\_dev\projects\ewf\ewf\library\network\protocol\http\http.ecf
notification_email-safe : C:\_dev\projects\ewf\ewf\library\runtime\process\notification_email\notification_email-safe.ecf
notification_email : C:\_dev\projects\ewf\ewf\library\runtime\process\notification_email\notification_email.ecf
openid-safe : C:\_dev\projects\ewf\ewf\library\security\openid\consumer\openid-safe.ecf
openid : C:\_dev\projects\ewf\ewf\library\security\openid\consumer\openid.ecf
demo-safe : C:\_dev\projects\ewf\ewf\library\security\openid\consumer\demo\demo-safe.ecf
http_authorization-safe : C:\_dev\projects\ewf\ewf\library\server\authentication\http_authorization\http_authorization-safe.ecf
http_authorization : C:\_dev\projects\ewf\ewf\library\server\authentication\http_authorization\http_authorization.ecf
ewsgi-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\ewsgi-safe.ecf
ewsgi : C:\_dev\projects\ewf\ewf\library\server\ewsgi\ewsgi.ecf
ewsgi_spec-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\ewsgi_spec-safe.ecf
ewsgi_spec : C:\_dev\projects\ewf\ewf\library\server\ewsgi\ewsgi_spec.ecf
cgi-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\cgi\cgi-safe.ecf
cgi : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\cgi\cgi.ecf
libfcgi-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\libfcgi\libfcgi-safe.ecf
libfcgi : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\libfcgi\libfcgi.ecf
nino-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\nino\nino-safe.ecf
nino : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\nino\nino.ecf
null-safe : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\null\null-safe.ecf
null : C:\_dev\projects\ewf\ewf\library\server\ewsgi\connectors\null\null.ecf
libfcgi-safe : C:\_dev\projects\ewf\ewf\library\server\libfcgi\libfcgi-safe.ecf
libfcgi : C:\_dev\projects\ewf\ewf\library\server\libfcgi\libfcgi.ecf
wsf-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf-safe.ecf
wsf : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf.ecf
wsf_extension-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_extension-safe.ecf
wsf_extension : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_extension.ecf
wsf_policy_driven-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_policy_driven-safe.ecf
wsf_policy_driven : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_policy_driven.ecf
wsf_router_context-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_router_context-safe.ecf
wsf_router_context : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_router_context.ecf
wsf_session-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_session-safe.ecf
wsf_session : C:\_dev\projects\ewf\ewf\library\server\wsf\wsf_session.ecf
all-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\all-safe.ecf
cgi-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\cgi-safe.ecf
cgi : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\cgi.ecf
libfcgi-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\libfcgi-safe.ecf
libfcgi : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\libfcgi.ecf
nino-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\nino-safe.ecf
nino : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\nino.ecf
openshift-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\connector\openshift-safe.ecf
cgi-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\default\cgi-safe.ecf
cgi : C:\_dev\projects\ewf\ewf\library\server\wsf\default\cgi.ecf
libfcgi-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\default\libfcgi-safe.ecf
libfcgi : C:\_dev\projects\ewf\ewf\library\server\wsf\default\libfcgi.ecf
nino-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\default\nino-safe.ecf
nino : C:\_dev\projects\ewf\ewf\library\server\wsf\default\nino.ecf
openshift-safe : C:\_dev\projects\ewf\ewf\library\server\wsf\default\openshift-safe.ecf
wsf_html-safe : C:\_dev\projects\ewf\ewf\library\server\wsf_html\wsf_html-safe.ecf
wsf_html : C:\_dev\projects\ewf\ewf\library\server\wsf_html\wsf_html.ecf
encoder-safe : C:\_dev\projects\ewf\ewf\library\text\encoder\encoder-safe.ecf
encoder : C:\_dev\projects\ewf\ewf\library\text\encoder\encoder.ecf
error-safe : C:\_dev\projects\ewf\ewf\library\utility\general\error\error-safe.ecf
error : C:\_dev\projects\ewf\ewf\library\utility\general\error\error.ecf

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
<target name="http_client">
<root all_classes="true"/>
<file_rule>
@@ -10,16 +10,7 @@
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf">
<condition>
<version type="compiler" min="7.1.8.8674"/>
</condition>
</library>
<library name="curl_local" location="..\..\..\contrib\ise_library\cURL-safe.ecf">
<condition>
<version type="compiler" max="7.1.8.8673"/>
</condition>
</library>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>

View File

@@ -11,16 +11,7 @@
</option>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
<condition>
<version type="compiler" min="7.1.8.8674"/>
</condition>
</library>
<library name="curl_local" location="..\..\..\contrib\ise_library\cURL.ecf">
<condition>
<version type="compiler" max="7.1.8.8673"/>
</condition>
</library>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
<library name="encoder" location="../../text/encoder/encoder.ecf"/>
<cluster name="src" location=".\src\" recursive="true"/>
</target>

View File

@@ -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

View File

@@ -111,12 +111,14 @@ feature -- Settings
timeout: INTEGER
-- HTTP transaction timeout in seconds.
--| 0 means it nevers timeout
do
Result := session.timeout
end
connect_timeout: INTEGER
-- HTTP connection timeout in seconds.
--| 0 means it nevers timeout
do
Result := session.connect_timeout
end
@@ -218,7 +220,7 @@ feature {NONE} -- Utilities: encoding
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -5,7 +5,7 @@ note
- headers
- query_parameters
- form parameters
- upload_data or upload_filename
- upload_data xor upload_filename
And in addition it has
- credentials_required
- proxy
@@ -86,16 +86,20 @@ feature -- Access
feature -- Status report
has_form_data: BOOLEAN
-- Has any form parameters?
--| i.e coming from POST or PUT content.
do
Result := not form_parameters.is_empty
end
has_upload_data: BOOLEAN
-- Has associated upload_data?
do
Result := attached upload_data as d and then not d.is_empty
end
has_upload_filename: BOOLEAN
-- Has associated upload_filename?
do
Result := attached upload_filename as fn and then not fn.is_empty
end
@@ -109,11 +113,13 @@ feature -- Status report
feature -- Element change
add_header (k: READABLE_STRING_8; v: READABLE_STRING_8)
-- Add http header line `k:v'.
do
headers.force (v, k)
end
add_header_line (s: READABLE_STRING_8)
-- Add http header line `s'.
local
i: INTEGER
do
@@ -124,6 +130,7 @@ feature -- Element change
end
add_header_lines (lst: ITERABLE [READABLE_STRING_8])
-- Add collection of http header lines `lst'
do
across
lst as c
@@ -133,44 +140,61 @@ feature -- Element change
end
add_query_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
-- Add a query parameter `k=v'.
do
query_parameters.force (v, k)
end
add_form_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
-- Add a form parameter `k'= `v'.
do
form_parameters.force (v, k)
end
set_credentials_required (b: BOOLEAN)
-- If b is True, credentials are required, otherwise just optional.
do
credentials_required := b
end
set_upload_data (a_data: like upload_data)
-- Set `upload_data' to `a_data'
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_no_upload_data: a_data /= Void implies not has_upload_data
has_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
do
upload_data := a_data
if a_data = Void or else a_data.is_empty then
upload_data := Void
else
upload_data := a_data
end
ensure
(a_data /= Void and then not a_data.is_empty) implies (has_upload_data and not has_upload_filename)
end
set_upload_filename (a_fn: detachable READABLE_STRING_GENERAL)
-- Set `upload_filename' to `a_fn'
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_no_upload_filename: a_fn /= Void implies not has_upload_filename
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
do
if a_fn = Void then
if a_fn = Void or else a_fn.is_empty then
upload_filename := Void
else
create upload_filename.make_from_string_general (a_fn)
end
ensure
(a_fn /= Void and then not a_fn.is_empty) implies (has_upload_filename and not has_upload_data)
end
set_write_agent (agt: like write_agent)
-- Set `write_agent' to `agt'.
do
write_agent := agt
end
set_output_file (f: FILE)
-- Set `output_file' to `f'.
require
f_is_open_write: f.is_open_write
do
@@ -178,6 +202,7 @@ feature -- Element change
end
set_output_content_file (f: FILE)
-- Set `output_content_file' to `f'.
require
f_is_open_write: f.is_open_write
do
@@ -187,6 +212,8 @@ feature -- Element change
feature -- Status setting
set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
-- Set proxy to `a_host:a_port'.
--| this can be used for instance with "http://fiddler2.com/" web debugging proxy.
do
if a_host = Void then
proxy := Void
@@ -212,6 +239,7 @@ feature -- Conversion helpers
feature {NONE} -- Implementation
parameters_to_url_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
-- Build url encoded string using parameters from `ht'.
do
create Result.make (64)
from
@@ -230,6 +258,7 @@ feature {NONE} -- Implementation
end
url_encoder: URL_ENCODER
-- Shared URL encoder.
once
create Result
end

View File

@@ -36,9 +36,10 @@ feature {NONE} -- Initialization
end
set_defaults
-- Set default settings.
do
timeout := 5
connect_timeout := 1
timeout := 0 --| never timeout
connect_timeout := 0 --| never timeout
max_redirects := 5
set_basic_auth_type
end
@@ -76,11 +77,15 @@ feature -- Basic operation
end
end
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for `a_method' request based on Current, `a_path' and `ctx'.
deferred
end
feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for GET request based on Current, `a_path' and `ctx'.
deferred
@@ -103,6 +108,18 @@ feature -- Basic operation
deferred
end
patch (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PATCH request based on Current, `a_path' and `ctx'
-- with input `data'
deferred
end
patch_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PATCH request based on Current, `a_path' and `ctx'
-- with uploaded data file `fn'
deferred
end
put (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
-- Response for PUT request based on Current, `a_path' and `ctx'
-- with input `data'
@@ -133,10 +150,12 @@ feature -- Status report
feature -- Settings
timeout: INTEGER
-- HTTP transaction timeout in seconds. Defaults to 5 seconds.
-- HTTP transaction timeout in seconds.
-- Defaults to 0 second i.e never timeout.
connect_timeout: INTEGER
-- HTTP connection timeout in seconds. Defaults to 1 second.
-- HTTP connection timeout in seconds.
-- Defaults to 0 second i.e never timeout.
max_redirects: INTEGER
-- Maximum number of times to follow redirects.
@@ -281,7 +300,7 @@ feature -- Element change
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -162,6 +162,7 @@ feature -- Execution
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
@@ -170,6 +171,7 @@ feature -- Execution
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
create l_upload_file.make_with_name (l_upload_filename)

View File

@@ -31,7 +31,7 @@ feature -- Status report
Result := curl.is_dynamic_library_exists
end
feature -- Basic operation
feature -- Custom
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local
@@ -41,6 +41,18 @@ feature -- Basic operation
Result := req.execute
end
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
end
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
end
feature -- Helper
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := custom ("GET", a_path, ctx)
@@ -53,63 +65,32 @@ feature -- Basic operation
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_post (a_path, a_ctx, data, Void)
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
end
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_post (a_path, a_ctx, Void, fn)
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
end
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
end
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
do
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
end
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
local
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
f: detachable RAW_FILE
l_data: detachable READABLE_STRING_8
do
--| Quick and dirty hack using real file, for PUT uploaded data
--| FIXME [2012-05-23]: better use libcurl for that purpose
ctx := a_ctx
if data /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_data (data)
end
if ctx /= Void then
l_data := ctx.upload_data
end
if l_data /= Void then
create f.make_open_write (create {FILE_NAME}.make_temporary_name)
f.put_string (l_data)
f.close
check ctx /= Void then
ctx.set_upload_data (Void)
ctx.set_upload_filename (f.path.name)
end
end
Result := custom ("PUT", a_path, ctx)
if f /= Void then
f.delete
end
if l_data /= Void and a_ctx /= Void then
a_ctx.set_upload_filename (Void)
a_ctx.set_upload_data (l_data)
end
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
local
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
do
ctx := a_ctx
if fn /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_filename (fn)
end
Result := custom ("PUT", a_path, ctx)
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
@@ -119,10 +100,12 @@ feature -- Basic operation
feature {NONE} -- Implementation
impl_post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
f: detachable RAW_FILE
l_data: detachable READABLE_STRING_8
do
ctx := a_ctx
if data /= Void then
@@ -137,8 +120,35 @@ feature {NONE} -- Implementation
end
ctx.set_upload_filename (fn)
end
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current, ctx)
if ctx /= Void then
l_data := ctx.upload_data
if l_data /= Void and a_method.is_case_insensitive_equal_general ("PUT") then
--| Quick and dirty hack using real file, for PUT uploaded data
--| FIXME [2012-05-23]: better use libcurl for that purpose
if ctx.has_upload_filename then
check put_conflict_file_and_data: False end
end
create f.make_open_write (create {FILE_NAME}.make_temporary_name)
f.put_string (l_data)
f.close
check ctx /= Void then
ctx.set_upload_data (Void)
ctx.set_upload_filename (f.path.name)
end
end
end
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
Result := req.execute
if f /= Void then
f.delete
end
if l_data /= Void and a_ctx /= Void then
a_ctx.set_upload_filename (Void)
a_ctx.set_upload_data (l_data)
end
end
feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation

View File

@@ -8,10 +8,14 @@
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
<assertions precondition="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http_client" location="..\http_client-safe.ecf" readonly="false"/>
<library name="http_client" location="..\http_client-safe.ecf" readonly="false" use_application_options="true">
<option>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
</library>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<tests name="tests" location=".\"/>
</target>

View File

@@ -1,125 +0,0 @@
CONNEG is a library that provides utilities to select the best repesentation of a resource for a client
where there are multiple representations available.
Using this labrary you can retrieve the best Variant for media type, language preference, enconding and compression.
The library is based on eMIME Eiffel MIME library based on Joe Gregorio code
Take into account that the library is under development so is expected that the API change.
The library contains utilities that deal with content negotiation (server driven negotiation).This utility class
is based on ideas taken from the Book Restful WebServices Cookbook
The class CONNEG_SERVER_SIDE contains several features that helps to write different type of negotiations (media type, language,
charset and compression).
So for each of the following questions, you will have a corresponding method to help in the solution.
- How to implement Media type negotiation?
Hint: Use CONNEG_SERVER_SIDE.media_type_preference
- How to implement Language Negotiation?
Hint: Use CONNEG_SERVER_SIDE.language_preference
- How to implement Character encoding Negotiation?
Hint: Use CONNEG_SERVER_SIDE.charset_preference
- How to implement Compression Negotiation?
Hint: Use CONNEG_SERVER_SIDE.encoding_preference
There is also a [test case](test/conneg_server_side_test.e "conneg_server_side_test") where you can check how to use this class.
note
description: "Summary description for CONNEG_SERVER_SIDE. Utility class to support Server Side Content Negotiation "
author: ""
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
class interface
CONNEG_SERVER_SIDE
create
make
feature -- Initialization
make (a_mime: STRING_8; a_language: STRING_8; a_charset: STRING_8; an_encoding: STRING_8)
feature -- Compression Negotiation
encoding_preference (server_encoding_supported: LIST [STRING_8]; header: STRING_8): COMPRESSION_VARIANT_RESULTS
-- server_encoding_supported represent a list of encoding supported by the server.
-- header represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server support
-- one Encoding, or empty in other case.
-- Representation: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
feature -- Encoding Negotiation
charset_preference (server_charset_supported: LIST [STRING_8]; header: STRING_8): CHARACTER_ENCODING_VARIANT_RESULTS
-- server_charset_supported represent a list of charset supported by the server.
-- header represent the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server support
-- one Charset, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
feature -- Language Negotiation
language_preference (server_language_supported: LIST [STRING_8]; header: STRING_8): LANGUAGE_VARIANT_RESULTS
-- server_language_supported represent a list of languages supported by the server.
-- header represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server support
-- one Language, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
feature -- Media Type Negotiation
media_type_preference (mime_types_supported: LIST [STRING_8]; header: STRING_8): MEDIA_TYPE_VARIANT_RESULTS
-- mime_types_supported represent media types supported by the server.
-- header represent the Accept header, ie, the client preferences.
-- Return which media type to use for representaion in a response, if the server support
-- one media type, or empty in other case.
-- Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
feature -- Server Side Defaults Formats
charset_default: STRING_8
encoding_default: STRING_8
language_default: STRING_8
mime_default: STRING_8
set_charset_default (a_charset: STRING_8)
-- set the charset_default with `a_charset'
ensure
set_charset: a_charset ~ charset_default
set_encoding_defautl (an_encoding: STRING_8)
ensure
set_encoding: an_encoding ~ encoding_default
set_language_default (a_language: STRING_8)
-- set the language_default with `a_language'
ensure
set_language: a_language ~ language_default
set_mime_default (a_mime: STRING_8)
-- set the mime_default with `a_mime'
ensure
set_mime_default: a_mime ~ mime_default
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end -- class CONNEG_SERVER_SIDE

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env ruby
# Niklaus Giger, 15.01.2011
# Small ruby-script run all tests using ec (the Eiffel compiler)
# we assumen that ec outputs everything in english!
# For the command line options look at
# http://docs.eiffel.com/book/eiffelstudio/eiffelstudio-command-line-options
# we use often the -batch open.
#
# TODO: Fix problems when compiling takes too long and/or there
# are ec process lingering around from a previous failed build
require 'tempfile'
require 'fileutils'
# Override system command.
# run command. if not successful, complain and exit with error
def system(cmd)
puts cmd
res = Kernel.system(cmd)
if !res
puts "Failed running: #{cmd}"
exit 2
end
end
def runTestForProject(where)
if !File.directory?(where)
puts "Directory #{where} does not exist"
exit 2
end
# create a temporary file with input for the
# interactive mode of ec
commands2run=<<EOF
T
E
q
EOF
file = Tempfile.new('commands2run')
file.puts commands2run
file.close
Dir.chdir(where)
# First we have to remove old compilation
FileUtils.rm_rf("EIFGENs")
# compile the library
cmd = "ec -config library/emime-safe.ecf -target emime -batch -c_compile"
res = system(cmd)
# compile the test
cmd = "ec -config test/test-safe.ecf -target test -batch -c_compile"
res = system(cmd)
logFile = "#{__FILE__}.log"
sleep 1
cmd = "ec -config test/test-safe.ecf -target test -batch -loop 1>#{logFile} 2>#{__FILE__}.auto_test_output <#{file.path}"
res = system(cmd)
m= nil
IO.readlines(logFile).each{
|line|
m = /(\d+) tests total \((\d+) executed, (\d+) failing, (\d+) unresolved/.match(line)
break if m
}
puts
if m[3].to_i == 0 and m[4].to_i == 0 then
puts "#{m[1]} tests completed successfully"
else
puts "Failures while running #{m[1]} failed. #{m[2]} executed #{m[3]} failures #{m[4]} unresolved"
exit 2
end
end
runTestForProject(Dir.pwd)

View File

@@ -1,45 +0,0 @@
note
description: "Summary description for {CHARACTER_ENCODING_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CHARACTER_ENCODING_VARIANT_RESULTS
feature
character_type : detachable STRING
set_character_type ( a_character_type: STRING)
do
character_type := a_character_type
ensure
set_character_type : a_character_type ~ character_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Charset"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,282 +0,0 @@
note
description: "COMMON_ACCEPT_HEADER_PARSER, this class allows to parse Accept-Charset and Accept-Encoding headers"
author: ""
date: "$Date$"
revision: "$Revision$"
description : "[
Charset Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
Encoding Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
]"
class
COMMON_ACCEPT_HEADER_PARSER
feature -- Parser
parse_common (header: STRING): COMMON_RESULTS
-- Parses `header' charset/encoding into its component parts.
-- For example, the charset 'iso-8889-5' would get parsed
-- into:
-- ('iso-8889-5', {'q':'1.0'})
local
l_parts: LIST [STRING]
sub_parts: LIST [STRING]
p: STRING
i: INTEGER
l_header: STRING
do
create Result.make
l_parts := header.split (';')
if l_parts.count = 1 then
Result.put ("1.0", "q")
else
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
end
l_header := trim (l_parts[1])
Result.set_field (trim (l_header))
end
fitness_and_quality_parsed (a_field: STRING; parsed_charsets: LIST [COMMON_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given charset/encoding against a list of charsets/encodings
-- that have already been parsed by parse_common. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed().
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: COMMON_RESULTS
range: COMMON_RESULTS
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_common(a_field)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if attached target.field as l_target_field
then
from
parsed_charsets.start
until
parsed_charsets.after
loop
range := parsed_charsets.item_for_iteration
if attached range.field as l_range_common then
if l_target_field.same_string (l_range_common) or l_target_field.same_string ("*") or l_range_common.same_string ("*") then
if l_range_common.same_string (l_target_field) then
l_fitness := 100
else
l_fitness := 0
end
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
end
parsed_charsets.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_field: STRING; parsed_common: LIST [COMMON_RESULTS]): REAL_64
-- Find the best match for a given charset/encoding against a list of charsets/encodings that
-- have already been parsed by parse_charsets(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality()
do
Result := fitness_and_quality_parsed (a_field, parsed_common).quality
end
quality (a_field: STRING; commons: STRING): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons : LIST [STRING]
res : ARRAYED_LIST [COMMON_RESULTS]
p_res : COMMON_RESULTS
do
l_commons := commons.split (',')
from
create res.make (10);
l_commons.start
until
l_commons.after
loop
p_res := parse_common (l_commons.item_for_iteration)
res.put_left (p_res)
l_commons.forth
end
Result := quality_parsed (a_field, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [COMMON_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: COMMON_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
do
l_res := header.split (',')
create {ARRAYED_LIST [COMMON_RESULTS]} l_header_results.make (l_res.count)
from
l_res.start
until
l_res.after
loop
p_res := parse_common (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
--| TODO extract method
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
if attached l_header_results.item.field as l_field then
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (l_field) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
else
check has_field: False end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature -- Util
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,119 +0,0 @@
note
description: "Summary description for {COMMON_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMMON_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
end
feature -- Access
field: detachable STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_field (a_field: STRING)
-- Set type with `a_charset'
do
field := a_field
ensure
field_assigned: field ~ field
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached field as t then
Result.append_string ("'" + t + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,44 +0,0 @@
note
description: "Summary description for {COMPRESSION_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMPRESSION_VARIANT_RESULTS
feature
compression_type : detachable STRING
set_compression_type ( a_compression_type: STRING)
do
compression_type := a_compression_type
ensure
set_compression_type : a_compression_type ~ compression_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Encoding"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,216 +0,0 @@
note
description: "Summary description for {CONNEG_SERVER_SIDE}. Utility class to support Server Side Content Negotiation "
author: ""
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
class
CONNEG_SERVER_SIDE
inherit
SHARED_CONNEG
REFACTORING_HELPER
create
make
feature -- Initialization
make ( a_mime: STRING; a_language : STRING; a_charset :STRING; an_encoding: STRING)
do
set_mime_default (a_mime)
set_language_default (a_language)
set_charset_default (a_charset)
set_encoding_defautl (an_encoding)
end
feature -- Server Side Defaults Formats
mime_default : STRING
set_mime_default ( a_mime: STRING)
-- set the mime_default with `a_mime'
do
mime_default := a_mime
ensure
set_mime_default: a_mime ~ mime_default
end
language_default : STRING
set_language_default (a_language : STRING)
-- set the language_default with `a_language'
do
language_default := a_language
ensure
set_language : a_language ~ language_default
end
charset_default : STRING
set_charset_default (a_charset : STRING)
-- set the charset_default with `a_charset'
do
charset_default := a_charset
ensure
set_charset : a_charset ~ charset_default
end
encoding_default : STRING
set_encoding_defautl (an_encoding : STRING)
do
encoding_default := an_encoding
ensure
set_encoding : an_encoding ~ encoding_default
end
feature -- Media Type Negotiation
media_type_preference ( mime_types_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : MEDIA_TYPE_VARIANT_RESULTS
-- mime_types_supported represent media types supported by the server.
-- header represent the Accept header, ie, the client preferences.
-- Return which media type to use for representaion in a response, if the server support
-- one media type, or empty in other case.
-- Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
local
mime_match: STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_media_type (mime_default)
else
-- select the best match, server support, client preferences
mime_match := mime.best_match (mime_types_supported, header)
if mime_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (mime_types_supported)
else
-- Set the best match
Result.set_media_type(mime_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Encoding Negotiation
charset_preference (server_charset_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : CHARACTER_ENCODING_VARIANT_RESULTS
-- server_charset_supported represent a list of charset supported by the server.
-- header represent the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server support
-- one Charset, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
local
charset_match : STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept-Charset header, ie the header is empty, in this case use default charset encoding
-- (UTF-8)
Result.set_acceptable (TRUE)
Result.set_character_type (charset_default)
else
-- select the best match, server support, client preferences
charset_match := common.best_match (server_charset_supported, header)
if charset_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_charset_supported)
else
-- Set the best match
Result.set_character_type(charset_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Compression Negotiation
encoding_preference (server_encoding_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : COMPRESSION_VARIANT_RESULTS
-- server_encoding_supported represent a list of encoding supported by the server.
-- header represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server support
-- one Encoding, or empty in other case.
-- Representation: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
local
compression_match : STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept-Encoding header, ie the header is empty, in this case do not compress representations
Result.set_acceptable (TRUE)
Result.set_compression_type (encoding_default)
else
-- select the best match, server support, client preferences
compression_match := common.best_match (server_encoding_supported, header)
if compression_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_encoding_supported)
else
-- Set the best match
Result.set_compression_type(compression_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Language Negotiation
language_preference (server_language_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : LANGUAGE_VARIANT_RESULTS
-- server_language_supported represent a list of languages supported by the server.
-- header represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server support
-- one Language, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
local
language_match: STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_language_type (language_default)
else
-- select the best match, server support, client preferences
language_match := language.best_match (server_language_supported, header)
if language_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_language_supported)
else
-- Set the best match
Result.set_language_type(language_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Apache Conneg Algorithm
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,352 +0,0 @@
note
description: "Summary description for {LANGUAGE_PARSE}."
author: ""
date: "$Date$"
revision: "$Revision$"
description : "Language Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4"
class
LANGUAGE_PARSE
inherit
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: STRING): LANGUAGE_RESULTS
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*"
end
l_types := l_full_type.split ('-')
if l_types.count = 1 then
Result.set_type (trim (l_types[1]))
else
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
end
end
parse_media_range (a_range: STRING): LANGUAGE_RESULTS
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: LANGUAGE_RESULTS
range: LANGUAGE_RESULTS
keys: LIST [STRING]
param_matches: INTEGER
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if
attached target.type as l_target_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if (
attached range.sub_type as l_range_sub_type and then attached target.sub_type as l_target_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
) then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [LANGUAGE_RESULTS]
p_res : LANGUAGE_RESULTS
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.put_left (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [LANGUAGE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: LANGUAGE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
do
l_res := header.split (',')
create {ARRAYED_LIST [LANGUAGE_RESULTS]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,143 +0,0 @@
note
description: "Summary description for {LANGUAGE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
create mime_type.make_from_string ("*")
end
feature -- Access
type: detachable STRING
sub_type: detachable STRING
mime_type: STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
mime_type := a_type + "-" + st
else
mime_type := a_type
end
ensure
type_assigned: type ~ a_type
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
mime_type := t + "-" + a_sub_type
else
mime_type := "*"
end
ensure
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached type as t then
Result.append_string ("'" + t + "',")
end
if attached sub_type as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,46 +0,0 @@
note
description: "Summary description for {LANGUAGE_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_VARIANT_RESULTS
feature
language_type : detachable STRING
set_language_type ( a_language_type: STRING)
do
language_type := a_language_type
ensure
set_language_type : a_language_type ~ language_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Language"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,47 +0,0 @@
note
description: "Summary description for {MEDIA_TYPE_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
MEDIA_TYPE_VARIANT_RESULTS
feature
media_type : detachable STRING
set_media_type ( a_media_type: STRING)
do
media_type := a_media_type
ensure
set_media_type : a_media_type ~ media_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,144 +0,0 @@
note
description: "Summary description for {PARSE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
PARSE_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
create mime_type.make_from_string ("*/*")
end
feature -- Access
type: detachable STRING
sub_type: detachable STRING
mime_type: STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
mime_type := a_type + "/" + st
else
mime_type := a_type + "/*"
end
ensure
type_assigned: type ~ a_type
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
mime_type := t + "/" + a_sub_type
else
mime_type := "*/" + a_sub_type
end
ensure
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached type as t then
Result.append_string ("'" + t + "',")
end
if attached sub_type as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,30 +0,0 @@
note
description: "Summary description for {SHARED_MIME}."
date: "$Date$"
revision: "$Revision$"
class
SHARED_CONNEG
feature
mime: MIME_PARSE
once
create Result
end
common: COMMON_ACCEPT_HEADER_PARSER
-- Charset and Encoding
once
create Result
end
language: LANGUAGE_PARSE
once
create Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,58 +0,0 @@
note
description: "Summary description for {VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
VARIANT_RESULTS
feature -- Mime, Language, Charset and Encoding Results
mime_result : detachable STRING
set_mime_result ( a_mime: STRING)
-- set the mime_result with `a_mime'
do
mime_result := a_mime
ensure
set_mime_result: a_mime ~ mime_result
end
language_result : detachable STRING
set_language_result (a_language : STRING)
-- set the language_result with `a_language'
do
language_result := a_language
ensure
set_language : a_language ~ language_result
end
charset_result : detachable STRING
set_charset_result (a_charset : STRING)
-- set the charset_result with `a_charset'
do
charset_result := a_charset
ensure
set_charset : a_charset ~ charset_result
end
encoding_result : detachable STRING
set_encoding_defautl (an_encoding : STRING)
do
encoding_result := an_encoding
ensure
set_encoding : an_encoding ~ encoding_result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,4 +0,0 @@
*~
EIFGEN* # ignore all files in the EIFGENs/ directory

View File

@@ -1,167 +0,0 @@
note
description: "Summary description for {CONNEG_SERVER_SIDE_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CONNEG_SERVER_SIDE_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create conneg.make ("application/json", "es", "UTF-8", "")
-- set default values
end
feature -- Test routines
test_media_type_negotiation
local
media_variants : MEDIA_TYPE_VARIANT_RESULTS
mime_types_supported : LIST [STRING]
l_types : STRING
do
-- Scenario 1, the server side does not support client preferences
l_types := "application/json,application/xbel+xml,application/xml"
mime_types_supported := l_types.split(',')
media_variants := conneg.media_type_preference (mime_types_supported, "text/html")
assert ("Expected Not Acceptable", not media_variants.is_acceptable)
assert ("Same Value at 1",mime_types_supported.at (1).is_equal (media_variants.supported_variants.at (1)))
assert ("Same count",mime_types_supported.count = media_variants.supported_variants.count)
assert ("Variant header is void",media_variants.variant_header = Void)
assert ("Media type is void",media_variants.media_type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept:
media_variants := conneg.media_type_preference (mime_types_supported, "")
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is dettached",media_variants.supported_variants = Void)
assert ("Mime is defaul", conneg.mime_default.is_equal (media_variants.media_type))
assert ("Variant header", media_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
media_variants := conneg.media_type_preference (mime_types_supported, "text/*,application/json;q=0.5")
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is dettached",media_variants.supported_variants = Void)
assert ("Variant Header", media_variants.variant_header.is_equal ("Accept"))
assert ("Media Type is application/json", media_variants.media_type.is_equal ("application/json"))
end
test_charset_negotiation
local
charset_variants : CHARACTER_ENCODING_VARIANT_RESULTS
charset_supported : LIST [STRING]
l_charset : STRING
do
-- Scenario 1, the server side does not support client preferences
l_charset := "UTF-8, iso-8859-5"
charset_supported := l_charset.split(',')
charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1")
assert ("Expected Not Acceptable", not charset_variants.is_acceptable)
assert ("Same Value at 1",charset_supported.at (1).is_equal (charset_variants.supported_variants.at (1)))
assert ("Same count",charset_supported.count = charset_variants.supported_variants.count)
assert ("Variant header is void",charset_variants.variant_header = Void)
assert ("Character type is void",charset_variants.character_type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Charset:
charset_variants := conneg.charset_preference (charset_supported, "")
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is dettached",charset_variants.supported_variants = Void)
assert ("Charset is defaul", conneg.charset_default.is_equal (charset_variants.character_type))
assert ("Variant header", charset_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1, UTF-8;q=0.3, iso-8859-5")
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is dettached",charset_variants.supported_variants = Void)
assert ("Variant Header", charset_variants.variant_header.is_equal ("Accept-Charset"))
assert ("Character Type is iso-8859-5", charset_variants.character_type.is_equal ("iso-8859-5"))
end
test_compression_negotiation
local
compression_variants : COMPRESSION_VARIANT_RESULTS
compression_supported : LIST [STRING]
l_compression : STRING
do
-- Scenario 1, the server side does not support client preferences
l_compression := ""
compression_supported := l_compression.split(',')
compression_variants := conneg.encoding_preference (compression_supported, "gzip")
assert ("Expected Not Acceptable", not compression_variants.is_acceptable)
assert ("Same Value at 1",compression_supported.at (1).is_equal (compression_variants.supported_variants.at (1)))
assert ("Same count",compression_supported.count = compression_variants.supported_variants.count)
assert ("Variant header is void",compression_variants.variant_header = Void)
assert ("Compression type is void",compression_variants.compression_type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Encoding
compression_variants := conneg.encoding_preference (compression_supported, "")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is dettached",compression_variants.supported_variants = Void)
assert ("Compression is defaul", conneg.encoding_default.is_equal (compression_variants.compression_type))
assert ("Variant header", compression_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
l_compression := "gzip"
compression_supported := l_compression.split(',')
conneg.set_encoding_defautl ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "compress,gzip;q=0.7")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is dettached",compression_variants.supported_variants = Void)
assert ("Variant Header", compression_variants.variant_header.is_equal ("Accept-Encoding"))
assert ("Encoding Type is gzip", compression_variants.compression_type.is_equal ("gzip"))
end
test_language_negotiation
local
language_variants : LANGUAGE_VARIANT_RESULTS
languages_supported : LIST [STRING]
l_languages : STRING
do
-- Scenario 1, the server side does not support client preferences
l_languages := "es,en,en-US,fr;q=0.6"
languages_supported := l_languages.split(',')
language_variants := conneg.language_preference (languages_supported, "de")
assert ("Expected Not Acceptable", not language_variants.is_acceptable)
assert ("Same Value at 1",languages_supported.at (1).is_equal (language_variants.supported_variants.at (1)))
assert ("Same count",languages_supported.count = language_variants.supported_variants.count)
assert ("Variant header is void",language_variants.variant_header = Void)
assert ("Language type is void",language_variants.language_type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Language:
language_variants := conneg.language_preference (languages_supported, "")
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is dettached",language_variants.supported_variants = Void)
assert ("Language is defaul", conneg.language_default.is_equal (language_variants.language_type))
assert ("Variant header", language_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
language_variants := conneg.language_preference (languages_supported, "fr,es;q=0.4")
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is dettached",language_variants.supported_variants = Void)
assert ("Variant Header", language_variants.variant_header.is_equal ("Accept-Language"))
assert ("Language Type is fr", language_variants.language_type.is_equal ("fr"))
end
feature -- Implementation
conneg : CONNEG_SERVER_SIDE
end

View File

@@ -1,117 +0,0 @@
note
description: "Summary description for {LANGUAGE_PARSER_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_PARSER_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create parser
end
feature -- Test routines
test_parse_media_range
do
assert ("Expected ('da', {'q':'1.0',})", parser.parse_media_range ("da").out.same_string ("('da', {'q':'1.0',})"));
assert ("Expected ('en', 'gb', {'q':'0.8',})", parser.parse_media_range ("en-gb;q=0.8").out.same_string ("('en', 'gb', {'q':'0.8',})"));
assert ("Expected ('en', {'q':'0.7',})", parser.parse_media_range ("en;q=0.7").out.same_string ("('en', {'q':'0.7',})"));
assert ("Expected ('en', '*', {'q':'1.0',})", parser.parse_media_range ("en-*").out.same_string ("('en', '*', {'q':'1.0',})"));
end
test_RFC2616_example
local
accept : STRING
do
accept := "da, en-gb;q=0.8, en;q=0.7";
assert ("Expected 1.0", 1.0 = parser.quality ("da", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-gb", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-*", accept))
end
test_best_match
local
mime_types_supported : LIST [STRING]
l_types : STRING
do
l_types := "en-gb,en-us"
mime_types_supported := l_types.split(',')
assert ("Expected en-us", parser.best_match (mime_types_supported, "en-us").same_string ("en-us"))
assert ("Direct match with a q parameter", parser.best_match (mime_types_supported, "en-gb;q=1").same_string ("en-gb"))
assert ("Direct match second choice with a q parameter", parser.best_match (mime_types_supported, "en-us;q=1").same_string ("en-us"))
assert ("Direct match using a subtype wildcard", parser.best_match (mime_types_supported, "en-*;q=1").is_equal ("en-gb"))
assert ("Match using a type wildcard", parser.best_match (mime_types_supported, "*").same_string ("en-gb"))
l_types := "en-gb,es"
mime_types_supported := l_types.split(',')
assert ("Match using a type versus a lower weighted subtype", parser.best_match (mime_types_supported, "es-*;q=0.5,*;q=0.1").same_string ("es"))
assert ("Fail to match anything",parser.best_match (mime_types_supported, "fr; q=0.9").same_string (""))
l_types := "en-gb,en-us"
mime_types_supported := l_types.split(',')
assert ("Test 1 verify fitness ordering", parser.best_match (mime_types_supported, "en-gb,en-us,*").same_string ("en-gb"))
l_types := "es,en-gb;q=1.0,fr;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Match default es at first position", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr;q=0.6,es"
mime_types_supported := l_types.split(',')
assert ("Match default es at last position", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr,es"
mime_types_supported := l_types.split(',')
assert ("Match first top quality and fitness", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "es;q=1.0,*/*;q=0.1,en;q=0.9").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "es,*/*;q=0.1,en").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "en,es,*/*;q=0.1").same_string ("en"))
l_types := "es,en;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "fr;q=1.0, en;q=0.6, es").same_string ("es"))
end
test_support_wildcard
local
mime_types_supported : LIST[STRING]
l_types : STRING
do
l_types := "en-*,fr"
mime_types_supported := l_types.split(',')
assert ("match using a type wildcard", parser.best_match (mime_types_supported, "en-gb").same_string ("en-*"))
end
parser : LANGUAGE_PARSE
end

View File

@@ -0,0 +1,31 @@
CONNEG is a library that provides utilities to select the best repesentation of a resource for a client where there are multiple representations available.
Using this library you can retrieve the best variant for media type, language preference, charset, and enconding/compression.
Take into account that the library is under development so is expected that the API change.
The library contains utilities that deal with content negotiation (server driven negotiation).This utility class
is based on ideas taken from the Book Restful WebServices Cookbook
The class SERVER_CONTENT_NEGOTIATION contains several features that helps to write different types of negotiation (media type, language,
charset and compression).
So for each of the following questions, you will have a corresponding method to help in the solution.
- How to implement Media type negotiation?
Hint: Use SERVER_CONTENT_NEGOTIATION.media_type_preference
or SERVER_MEDIA_TYPE_NEGOTIATION.preference
- How to implement Language Negotiation?
Hint: Use SERVER_CONTENT_NEGOTIATION.language_preference
or SERVER_LANGUAGE_NEGOTIATION.preference
- How to implement Character Negotiation?
Hint: Use SERVER_CONTENT_NEGOTIATION.charset_preference
or SERVER_CHARSET_NEGOTIATION.preference
- How to implement Encoding Negotiation?
Hint: Use SERVER_CONTENT_NEGOTIATION.encoding_preference
or SERVER_ENCODING_NEGOTIATION.preference
There is also a [test case](test/conneg_server_side_test.e "conneg_server_side_test") where you can check how to use this class.

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="conneg" uuid="D15DC4C4-D74F-4E4B-B4B1-2B03A35A284D" library_target="conneg">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="conneg" uuid="D15DC4C4-D74F-4E4B-B4B1-2B03A35A284D" library_target="conneg">
<target name="conneg">
<root all_classes="true"/>
<file_rule>
@@ -12,7 +12,12 @@
<assertions precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\http\http-safe.ecf"/>
<cluster name="conneg" location=".\src\" recursive="true"/>
<library name="http" location="..\http\http-safe.ecf" readonly="false"/>
<cluster name="conneg" location=".\src\" recursive="true">
<file_rule>
<exclude>/implementation</exclude>
</file_rule>
</cluster>
<cluster name="conneg_imp" location=".\src\implementation\" recursive="true" hidden="true"/>
</target>
</system>

View File

@@ -13,6 +13,12 @@
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http" location="../http/http.ecf"/>
<cluster name="conneg" location=".\src" recursive="true"/>
<cluster name="conneg" location=".\src\" recursive="true">
<file_rule>
<exclude>/implementation</exclude>
</file_rule>
</cluster>
<cluster name="conneg_imp" location=".\src\implementation" recursive="true" hidden="true">
</cluster>
</target>
</system>

View File

@@ -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

View File

@@ -1,6 +1,5 @@
note
description: "Summary description for {FITNESS_AND_QUALITY}."
author: ""
description: "FITNESS_AND_QUALITY. Object holding a fitness/quality values."
date: "$Date$"
revision: "$Revision$"
@@ -21,13 +20,15 @@ create
feature -- Initialization
make (a_fitness: INTEGER; a_quality: REAL_64)
-- Create an object with `a_fitness' and `a_quality'
do
fitness := a_fitness
quality := a_quality
create mime_type.make_empty
create {STRING_8} entity.make_empty
ensure
fitness_assigned : fitness = a_fitness
quality_assigned : quality = a_quality
entity_empty: entity.is_empty
end
feature -- Access
@@ -36,17 +37,17 @@ feature -- Access
quality: REAL_64
mime_type: STRING
entity: READABLE_STRING_8
-- optionally used
-- empty by default
--| Could be a mime type, an encoding, ...
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_from_string (mime_type)
create Result.make_from_string (entity)
Result.append (" (")
Result.append ("quality=" + quality.out)
Result.append (" ; fitness=" + fitness.out)
@@ -55,12 +56,12 @@ feature -- Status report
feature -- Element Change
set_mime_type (a_mime_type: STRING)
-- set mime_type with `a_mime_type'
set_entity (a_entity: READABLE_STRING_8)
-- set `entity' with `a_entity'
do
mime_type := a_mime_type
entity := a_entity
ensure
mime_type_assigned : mime_type.same_string (a_mime_type)
entity_assigned : entity.same_string (a_entity)
end
feature -- Comparision
@@ -75,7 +76,7 @@ feature -- Comparision
end
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,256 @@
note
description: "[
{HTTP_ACCEPT_LANGUAGE_UTILITIES} is in charge to parse language tags defined as follow:
Accept-Language = "Accept-Language" ":"
1#( language-l_range [ ";" "q" "=" qvalue ] )
language-l_range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
Example:
Accept-Language: da, en-gb;q=0.8, en;q=0.7
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
class
HTTP_ACCEPT_LANGUAGE_UTILITIES
inherit
HTTP_HEADER_UTILITIES
feature -- Parser
accept_language_list (a_header_value: READABLE_STRING_8): LIST [HTTP_ACCEPT_LANGUAGE]
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language l_range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
local
l_res: LIST [READABLE_STRING_8]
l_lang: HTTP_ACCEPT_LANGUAGE
do
l_res := a_header_value.split (',')
create {ARRAYED_LIST [HTTP_ACCEPT_LANGUAGE]} Result.make (l_res.count)
from
l_res.start
until
l_res.after
loop
create l_lang.make_from_string (l_res.item_for_iteration)
Result.force (l_lang)
l_res.forth
end
end
accept_language (a_accept_language_item: READABLE_STRING_8): HTTP_ACCEPT_LANGUAGE
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language l_range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
create Result.make_from_string (a_accept_language_item)
end
quality (a_language: READABLE_STRING_8; a_ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a `a_language' when compared against the
-- language l_range in `a_ranges'.
do
Result := quality_from_list (a_language, accept_language_list (a_ranges))
end
best_match (a_supported: ITERABLE [READABLE_STRING_8]; a_header_value: READABLE_STRING_8): READABLE_STRING_8
-- Choose the `parse_language' with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_ACCEPT_LANGUAGE]
l_weighted_matches: LIST [FITNESS_AND_QUALITY]
l_fitness_and_quality, l_first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_header_results := accept_language_list (a_header_value)
--| weighted matches
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} l_weighted_matches.make (0)
across a_supported as ic loop
l_fitness_and_quality := fitness_and_quality_from_list (ic.item, l_header_results)
l_fitness_and_quality.set_entity (entity_value (ic.item))
l_weighted_matches.force (l_fitness_and_quality)
end
--| Keep only top quality+fitness types
from
l_weighted_matches.start
l_first_one := l_weighted_matches.item
l_weighted_matches.forth
until
l_weighted_matches.after
loop
l_fitness_and_quality := l_weighted_matches.item
if l_first_one < l_fitness_and_quality then
l_first_one := l_fitness_and_quality
if not l_weighted_matches.isfirst then
from
l_weighted_matches.back
until
l_weighted_matches.before
loop
l_weighted_matches.remove
l_weighted_matches.back
end
l_weighted_matches.forth
end
check
l_weighted_matches.item = l_fitness_and_quality
end
l_weighted_matches.forth
elseif l_first_one ~ l_fitness_and_quality then
l_weighted_matches.forth
else
check
l_first_one > l_fitness_and_quality
end
l_weighted_matches.remove
end
end
if l_first_one /= Void and then l_first_one.quality /= 0.0 then
if l_weighted_matches.count = 1 then
Result := l_first_one.entity
else
from
l_fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or l_fitness_and_quality /= Void
loop
s := l_header_results.item.language_range
from
l_weighted_matches.start
until
l_weighted_matches.after or l_fitness_and_quality /= Void
loop
l_fitness_and_quality := l_weighted_matches.item
if l_fitness_and_quality.entity.same_string (s) then
--| Found
else
l_fitness_and_quality := Void
l_weighted_matches.forth
end
end
l_header_results.forth
end
if l_fitness_and_quality /= Void then
Result := l_fitness_and_quality.entity
else
Result := l_first_one.entity
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
fitness_and_quality_from_list (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [HTTP_ACCEPT_LANGUAGE]): FITNESS_AND_QUALITY
-- Find the best match for a given `a_language' against a list of language ranges `a_parsed_ranges'
-- that have already been parsed by parse_language_range.
local
l_best_fitness: INTEGER
l_target_q: REAL_64
l_best_fit_q: REAL_64
l_target: HTTP_ACCEPT_LANGUAGE
l_target_type: READABLE_STRING_8
l_range: HTTP_ACCEPT_LANGUAGE
l_param_matches: INTEGER
l_element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
l_best_fitness := -1
l_best_fit_q := 0.0
create l_target.make_from_string (a_language)
l_target_q := l_target.quality
l_target_type := l_target.language
from
a_parsed_ranges.start
until
a_parsed_ranges.after
loop
l_range := a_parsed_ranges.item_for_iteration
if
attached l_range.language as l_range_type and then
( l_target_type.same_string (l_range_type)
or l_range_type.same_string ("*")
or l_target_type.same_string ("*")
)
then
l_param_matches := 0
if attached l_target.parameters as l_target_parameters then
across
l_target_parameters as ic
loop
l_element := ic.key
if
not l_element.same_string ("q") and then
l_range.has_parameter (l_element) and then
(attached ic.item as t_item and attached l_range.parameter (l_element) as r_item) and then
t_item.same_string (r_item)
then
l_param_matches := l_param_matches + 1
end
end
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if
attached l_range.specialization as l_range_sub_type and then
attached l_target.specialization as l_target_sub_type and then
( l_target_sub_type.same_string (l_range_sub_type)
or l_range_sub_type.same_string ("*")
or l_target_sub_type.same_string ("*")
)
then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + l_param_matches
if l_fitness > l_best_fitness then
l_best_fitness := l_fitness
l_element := l_range.parameter ("q")
if l_element /= Void then
l_best_fit_q := l_element.to_real_64.min (l_target_q)
else
l_best_fit_q := 0.0
end
end
end
a_parsed_ranges.forth
end
create Result.make (l_best_fitness, l_best_fit_q)
end
quality_from_list (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [HTTP_ACCEPT_LANGUAGE]): REAL_64
-- Find the best match for a given `a_language' against a list of ranges `parsed_ranges' that
-- have already been parsed by parse_language_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'a_parsed_ranges' must be a list
-- of parsed language ranges.
do
Result := fitness_and_quality_from_list (a_language, a_parsed_ranges).quality
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,58 +1,33 @@
note
description: "Summary description for {MIME_PARSE}."
author: ""
description: "[
{HTTP_ACCEPT_MEDIA_TYPE_UTILITIES}. is encharge to parse Accept request-header field defined as follow:
Accept = "Accept" ":"
#( media-range [ accept-params ] )
media-range = ( "*/*"
| ( type "/" "*" )
| ( type "/" subtype )
) *( ";" parameter )
accept-params = ";" "q" "=" qvalue *( accept-extension )
accept-extension = ";" token [ "=" ( token | quoted-string ) ]
Example:
Accept: text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c
]"
date: "$Date$"
revision: "$Revision$"
description : "Accept Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"
EIS: "name=Accept", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
class
MIME_PARSE
HTTP_ACCEPT_MEDIA_TYPE_UTILITIES
inherit
REFACTORING_HELPER
HTTP_HEADER_UTILITIES
feature -- Parser
parse_mime_type (a_mime_type: STRING): PARSE_RESULTS
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*/*"
end
l_types := l_full_type.split ('/')
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
end
parse_media_range (a_range: STRING): PARSE_RESULTS
media_type (a_range: READABLE_STRING_8): HTTP_MEDIA_TYPE
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
@@ -61,142 +36,33 @@ feature -- Parser
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
create Result.make_from_string (a_range)
if attached Result.parameter ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
attached {REAL_64} q.to_real_64 as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
else
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
else
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: PARSE_RESULTS
range: PARSE_RESULTS
keys: LIST [STRING]
param_matches: INTEGER
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if
attached target.type as l_target_type and
attached target.sub_type as l_target_sub_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
) and
(
attached range.sub_type as l_range_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
quality (a_mime_type: READABLE_STRING_8; ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [PARSE_RESULTS]
p_res : PARSE_RESULTS
l_ranges : LIST [READABLE_STRING_8]
res : ARRAYED_LIST [HTTP_MEDIA_TYPE]
p_res : HTTP_MEDIA_TYPE
do
l_ranges := ranges.split (',')
from
@@ -205,25 +71,25 @@ feature -- Parser
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.put_left (p_res)
p_res := media_type (l_ranges.item_for_iteration)
res.force (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
Result := quality_from_list (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
best_match (supported: ITERABLE [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [PARSE_RESULTS]
l_header_results: LIST [HTTP_MEDIA_TYPE]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: PARSE_RESULTS
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_MEDIA_TYPE
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
s: READABLE_STRING_8
do
l_res := header.split (',')
create {ARRAYED_LIST [PARSE_RESULTS]} l_header_results.make (l_res.count)
create {ARRAYED_LIST [HTTP_MEDIA_TYPE]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
@@ -231,22 +97,17 @@ feature -- Parser
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
p_res := media_type (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (0)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
across supported as ic loop
fitness_and_quality := fitness_and_quality_from_list (ic.item, l_header_results)
fitness_and_quality.set_entity (entity_value (ic.item))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
@@ -282,7 +143,7 @@ feature -- Parser
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
Result := first_one.entity
else
from
fitness_and_quality := Void
@@ -290,14 +151,14 @@ feature -- Parser
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
s := l_header_results.item.simple_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
if fitness_and_quality.entity.same_string (s) then
--| Found
else
fitness_and_quality := Void
@@ -307,9 +168,9 @@ feature -- Parser
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
Result := fitness_and_quality.entity
else
Result := first_one.mime_type
Result := first_one.entity
end
end
else
@@ -319,31 +180,107 @@ feature -- Parser
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
fitness_and_quality_from_list (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range.
local
p: INTEGER
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: HTTP_MEDIA_TYPE
range: HTTP_MEDIA_TYPE
param_matches: INTEGER
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
best_fitness := -1
best_fit_q := 0.0
target := media_type (a_mime_type)
if attached target.parameter ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
Result := trim (s.string)
target_q := 1.0
end
if
attached target.type as l_target_type and
attached target.subtype as l_target_sub_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
) and
(
attached range.subtype as l_range_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
)
then
if attached target.parameters as l_keys then
param_matches := 0
across l_keys as ic loop
element := ic.key
if
not element.same_string ("q") and then
range.has_parameter (element) and then
(attached target.parameter (element) as t_item and attached range.parameter (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
end
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.parameter ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
quality_from_list (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parse_media_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
Result := fitness_and_quality_from_list (a_mime_type, parsed_ranges).quality
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,237 @@
note
description: "[
HTTP_ANY_ACCEPT_HEADER_PARSER, this class allows to parse Accept-* headers
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
EIS: "name=Encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
class
HTTP_ANY_ACCEPT_HEADER_UTILITIES
inherit
HTTP_HEADER_UTILITIES
feature -- Parser
header (a_header: READABLE_STRING_8): HTTP_ANY_ACCEPT
-- Parses `a_header' charset/encoding into its component parts.
-- For example, the charset 'iso-8889-5' would get parsed
-- into:
-- ('iso-8889-5', {'q':'1.0'})
do
create Result.make_from_string (a_header)
if Result.parameter ("q") = Void then
Result.put_parameter ("1.0", "q")
end
end
quality (a_field: READABLE_STRING_8; a_header: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [HTTP_ANY_ACCEPT]
p_res: HTTP_ANY_ACCEPT
do
l_commons := a_header.split (',')
from
create res.make (10)
l_commons.start
until
l_commons.after
loop
p_res := header (l_commons.item_for_iteration)
res.force (p_res)
l_commons.forth
end
Result := quality_from_list (a_field, res)
end
best_match (a_supported: ITERABLE [READABLE_STRING_8]; a_header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_ANY_ACCEPT]
l_weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_ANY_ACCEPT
l_fitness_and_quality, l_first_one: detachable FITNESS_AND_QUALITY
do
l_res := a_header.split (',')
create {ARRAYED_LIST [HTTP_ANY_ACCEPT]} l_header_results.make (l_res.count)
from
l_res.start
until
l_res.after
loop
p_res := header (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} l_weighted_matches.make (0)
across a_supported as ic loop
l_fitness_and_quality := fitness_and_quality_from_list (ic.item, l_header_results)
l_fitness_and_quality.set_entity (entity_value (ic.item))
l_weighted_matches.force (l_fitness_and_quality)
end
--| Keep only top quality+fitness types
--| TODO extract method
from
l_weighted_matches.start
l_first_one := l_weighted_matches.item
l_weighted_matches.forth
until
l_weighted_matches.after
loop
l_fitness_and_quality := l_weighted_matches.item
if l_first_one < l_fitness_and_quality then
l_first_one := l_fitness_and_quality
if not l_weighted_matches.isfirst then
from
l_weighted_matches.back
until
l_weighted_matches.before
loop
l_weighted_matches.remove
l_weighted_matches.back
end
l_weighted_matches.forth
end
check
l_weighted_matches.item = l_fitness_and_quality
end
l_weighted_matches.forth
elseif l_first_one.is_equal (l_fitness_and_quality) then
l_weighted_matches.forth
else
check
l_first_one > l_fitness_and_quality
end
l_weighted_matches.remove
end
end
if l_first_one /= Void and then l_first_one.quality /= 0.0 then
if l_weighted_matches.count = 1 then
Result := l_first_one.entity
else
from
l_fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or l_fitness_and_quality /= Void
loop
if attached l_header_results.item.value as l_field then
from
l_weighted_matches.start
until
l_weighted_matches.after or l_fitness_and_quality /= Void
loop
l_fitness_and_quality := l_weighted_matches.item
if l_fitness_and_quality.entity.same_string (l_field) then
--| Found
else
l_fitness_and_quality := Void
l_weighted_matches.forth
end
end
else
check
has_field: False
end
end
l_header_results.forth
end
if l_fitness_and_quality /= Void then
Result := l_fitness_and_quality.entity
else
Result := l_first_one.entity
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
fitness_and_quality_from_list (a_field: READABLE_STRING_8; a_parsed_charsets: LIST [HTTP_ANY_ACCEPT]): FITNESS_AND_QUALITY
-- Find the best match for a given charset/encoding against a list of charsets/encodings
-- that have already been parsed by parse_common. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed().
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: HTTP_ANY_ACCEPT
range: HTTP_ANY_ACCEPT
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := header (a_field)
if attached target.parameter ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if attached target.value as l_target_field then
from
a_parsed_charsets.start
until
a_parsed_charsets.after
loop
range := a_parsed_charsets.item_for_iteration
if attached range.value as l_range_common then
if
l_target_field.same_string (l_range_common)
or l_target_field.same_string ("*")
or l_range_common.same_string ("*")
or l_target_field.same_string ("identity")
then
if l_range_common.same_string (l_target_field) then
l_fitness := 100
else
l_fitness := 0
end
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.parameter ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
end
a_parsed_charsets.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_from_list (a_field: READABLE_STRING_8; a_parsed_common: LIST [HTTP_ANY_ACCEPT]): REAL_64
-- Find the best match for a given charset/encoding against a list of charsets/encodings that
-- have already been parsed by parse_charsets(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality()
do
Result := fitness_and_quality_from_list (a_field, a_parsed_common).quality
end
note
copyright: "2011-2014, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,33 @@
note
description: "Summary description for {HTTP_HEADER_UTILITIES}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
HTTP_HEADER_UTILITIES
inherit
REFACTORING_HELPER
feature {NONE} -- Helpers
entity_value (a_str: READABLE_STRING_8): STRING_8
-- `s' with any trailing parameters stripped
local
p: INTEGER
do
p := a_str.index_of (';', 1)
if p > 0 then
create Result.make_from_string (a_str.substring (1, p - 1))
else
create Result.make_from_string (a_str)
end
Result.left_adjust
Result.right_adjust
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,240 @@
note
description: "Object that represents a result after parsing Language Headers."
date: "$Date$"
revision: "$Revision$"
class
HTTP_ACCEPT_LANGUAGE
inherit
HTTP_HEADER_UTILITIES
REFACTORING_HELPER
DEBUG_OUTPUT
create
make_from_string,
make,
make_with_language,
make_default
feature {NONE} -- Initialization
make_from_string (a_accept_language_item: READABLE_STRING_8)
-- Instantiate Current from part of accept-language header, i.e language tag and parameters.
--
-- Languages-ranges are languages with specialization and a 'q' quality parameter.
-- For example, the language l_range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
local
i: INTEGER
s: STRING
do
fixme (generator + ".make_from_string: improve code!!!")
i := a_accept_language_item.index_of (';', 1)
if i > 0 then
create s.make_from_string (a_accept_language_item.substring (1, i - 1))
else
create s.make_from_string (a_accept_language_item)
end
s.left_adjust
s.right_adjust
make_with_language (s)
if i > 0 then
create parameters.make_from_substring (a_accept_language_item, i + 1, a_accept_language_item.count)
check attached parameters as l_params and then not l_params.has_error end
end
check quality_initialized_to_1: quality = 1.0 end
-- Get quality from parameter if any, and format the value as expected.
if attached parameter ("q") as q then
if q.same_string ("1") then
--| Use 1.0 formatting
put_parameter ("1.0", "q")
elseif q.is_double and then attached q.to_real_64 as r then
if r <= 0.0 then
quality := 0.0 --| Should it be 1.0 ?
put_parameter ("0.0", "q")
elseif r >= 1.0 then
quality := 1.0
put_parameter ("1.0", "q")
else
quality := r
end
else
put_parameter ("1.0", "q")
quality := 1.0
end
else
put_parameter ("1.0", "q")
end
end
make_with_language (a_lang_tag: READABLE_STRING_8)
-- Instantiate Current from language tag `a_lang_tag'.
do
initialize
set_language_range (a_lang_tag)
ensure
language_range_set: language_range.same_string (a_lang_tag) and a_lang_tag /= language_range
end
make (a_root_lang: READABLE_STRING_8; a_specialization: detachable READABLE_STRING_8)
-- Instantiate Current with `a_root_lang' and `a_specialization'.
do
initialize
create language_range.make_empty
language := a_root_lang
specialization := a_specialization
update_language_range (a_root_lang, a_specialization)
end
make_default
-- Instantiate Current with default "*" language.
do
make ("*", Void)
end
initialize
-- Initialize Current
do
create parameters.make (1)
quality := 1.0
end
feature -- Access
language_range: STRING_8
-- language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
language: READABLE_STRING_8
-- First part of the language range, i.e the root language
specialization: detachable READABLE_STRING_8
-- Optional second part of the language range, i.e the dialect, or specialized language type
quality: REAL_64
-- Associated quality, by default 1.0
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_from_string (language_range)
Result.append_character (';')
Result.append ("q=")
Result.append_double (quality)
end
feature -- Element change
set_language_range (a_lang_range: READABLE_STRING_8)
local
i: INTEGER
do
create language_range.make_from_string (a_lang_range)
i := a_lang_range.index_of ('-', 1)
if i > 0 then
language := a_lang_range.substring (1, i - 1)
specialization := a_lang_range.substring (i + 1, a_lang_range.count)
else
language := a_lang_range
end
ensure
language_range_set: language_range.same_string (a_lang_range) and a_lang_range /= language_range
end
set_language (a_root_lang: READABLE_STRING_8)
-- Set `'anguage' with `a_root_lang'
require
a_root_lang_attached: a_root_lang /= Void
do
language := a_root_lang
update_language_range (a_root_lang, specialization)
ensure
type_assigned: language ~ a_root_lang
end
set_specialization (a_specialization: detachable READABLE_STRING_8)
-- Set `specialization' with `a_specialization'
do
specialization := a_specialization
update_language_range (language, a_specialization)
ensure
specialization_assigned: specialization ~ a_specialization
end
feature -- Parameters: Access
parameter (a_key: READABLE_STRING_8): detachable READABLE_STRING_8
-- Parameter associated with `a_key', if present
-- otherwise default value of type `STRING'
do
if attached parameters as l_params then
Result := l_params.item (a_key)
end
end
parameters: detachable HTTP_PARAMETER_TABLE
-- Table of all parameters for the media range
feature -- Parameters: Status report
has_parameter (a_key: READABLE_STRING_8): BOOLEAN
-- Is there an parameter in the parameters table with key `a_key'?
do
if attached parameters as l_params then
Result := l_params.has_key (a_key)
end
end
feature -- Parameters: Change
put_parameter (a_value: READABLE_STRING_8; a_key: READABLE_STRING_8)
-- Insert `a_value' with `a_key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `a_value'
local
l_parameters: like parameters
do
l_parameters := parameters
if l_parameters = Void then
create l_parameters.make (1)
parameters := l_parameters
end
l_parameters.force (a_value, a_key)
ensure
is_set: attached parameters as l_params and then (l_params.has_key (a_key) and l_params.has_item (a_value))
end
feature {NONE} -- Implementation
update_language_range (a_lang: like language; a_specialization: like specialization)
-- Update `language_range' with `a_lang' and `a_specialization'
local
l_language_range: like language_range
do
l_language_range := language_range -- Reuse same object, be careful not to keep reference on existing string at first.
l_language_range.wipe_out
l_language_range.append (a_lang)
if a_specialization /= Void then
l_language_range.append_character ('-')
l_language_range.append (a_specialization)
end
end
invariant
valid_quality: 0.0 <= quality and quality <= 1.0
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,114 @@
note
description: "Object that represents a results after parsing Accept(-*) headers."
date: "$Date$"
revision: "$Revision$"
class
HTTP_ANY_ACCEPT
inherit
HTTP_HEADER_UTILITIES
REFACTORING_HELPER
DEBUG_OUTPUT
create
make_from_string
feature -- Initialization
make_from_string (a_string: READABLE_STRING_8)
local
s: STRING_8
i: INTEGER
do
initialize
i := a_string.index_of (';', 1)
if i > 0 then
create s.make_from_string (a_string.substring (1, i - 1))
else
create s.make_from_string (a_string)
end
s.left_adjust
s.right_adjust
set_value (s)
if i > 0 then
create parameters.make_from_substring (a_string, i + 1, a_string.count)
end
end
initialize
do
end
feature -- Access
value: READABLE_STRING_8
-- Value composing an Accept(-*) header value
feature -- Access: parameters
parameter (a_key: READABLE_STRING_8): detachable READABLE_STRING_8
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
if attached parameters as l_parameters then
Result := l_parameters.item (a_key)
end
end
parameters: detachable HTTP_PARAMETER_TABLE
-- Table of all parameters for the media range
feature -- Status Report
has_parameter (a_key: READABLE_STRING_8): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
if attached parameters as l_parameters then
Result := l_parameters.has_key (a_key)
end
end
feature -- Element change
set_value (v: READABLE_STRING_8)
-- Set `value' with `v'
do
value := v
ensure
value_set: attached value as l_value implies l_value.same_string (v)
end
put_parameter (a_value: READABLE_STRING_8; a_key: READABLE_STRING_8)
-- Insert `a_value' with `a_key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `a_value'
local
l_parameters: like parameters
do
l_parameters := parameters
if l_parameters = Void then
create l_parameters.make (1)
parameters := l_parameters
end
l_parameters.force (a_value, a_key)
ensure
is_set: attached parameters as l_params and then (l_params.has_key (a_key) and l_params.has_item (a_value))
end
feature -- Status Report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_from_string (value)
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,92 @@
note
description: "Summary description for {SERVER_CHARSET_NEGOTIATION}. Utility class to support Server Side Content Negotiation on charset "
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
SERVER_CHARSET_NEGOTIATION
inherit
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
make (a_charset_dft: READABLE_STRING_8)
do
create accept_charset_utilities
set_default_charset (a_charset_dft)
ensure
default_charset_set: default_charset = a_charset_dft
end
accept_charset_utilities: HTTP_ANY_ACCEPT_HEADER_UTILITIES
-- Charset
feature -- Access: Server Side Defaults Formats
default_charset: READABLE_STRING_8
-- Character set that is acceptable for the response.
feature -- Change Element
set_default_charset (a_charset: READABLE_STRING_8)
-- Set `default_charset' with `a_charset'
do
default_charset := a_charset
ensure
default_charset_set: a_charset = default_charset
end
feature -- Charset Negotiation
preference (a_server_charset_supported: ITERABLE [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): HTTP_ACCEPT_CHARSET_VARIANTS
-- `a_server_charset_supported' represent a list of character sets supported by the server.
-- `a_header' represents the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server supports
-- the requested Charset, or empty in other case.
note
EIS: "name=charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
local
l_charset_match: READABLE_STRING_8
do
create Result.make
Result.set_supported_variants (a_server_charset_supported)
if a_header = Void or else a_header.is_empty then
-- the request has no Accept-Charset header, ie the header is empty, in this case use default charset encoding
Result.set_acceptable (True)
Result.set_variant_value (default_charset)
else
Result.set_vary_header_value
-- select the best match, server support, client preferences
l_charset_match := accept_charset_utilities.best_match (a_server_charset_supported, a_header)
if l_charset_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
else
-- Set the best match
Result.set_variant_value (l_charset_match)
Result.set_acceptable (True)
end
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,67 @@
note
description: "Summary description for {SERVER_CONTENT_NEGOTIATION}. Utility class to support Server Side Content Negotiation "
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
SERVER_CONTENT_NEGOTIATION
inherit
SERVER_MEDIA_TYPE_NEGOTIATION
rename
make as make_media_type,
preference as media_type_preference
end
SERVER_LANGUAGE_NEGOTIATION
rename
make as make_language,
preference as language_preference
end
SERVER_CHARSET_NEGOTIATION
rename
make as make_charset,
preference as charset_preference
end
SERVER_ENCODING_NEGOTIATION
rename
make as make_encoding,
preference as encoding_preference
end
create
make
feature {NONE} -- Initialization
make (a_mediatype_dft: READABLE_STRING_8; a_language_dft: READABLE_STRING_8; a_charset_dft: READABLE_STRING_8; a_encoding_dft: READABLE_STRING_8)
-- Initialize Current with default Media type, language, charset and encoding.
do
make_media_type (a_mediatype_dft)
make_language (a_language_dft)
make_charset (a_charset_dft)
make_encoding (a_encoding_dft)
ensure
default_media_type_set: default_media_type = a_mediatype_dft
default_language_set: default_language = a_language_dft
default_charset_set: default_charset = a_charset_dft
default_encoding_set: default_encoding = a_encoding_dft
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,91 @@
note
description: "Summary description for {SERVER_ENCODING_NEGOTIATION}. Utility class to support Server Side Content Negotiation on encoding"
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
SERVER_ENCODING_NEGOTIATION
inherit
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
make (a_encoding_dft: READABLE_STRING_8)
do
create accept_encoding_utilities
set_default_encoding (a_encoding_dft)
ensure
default_encoding_set: default_encoding = a_encoding_dft
end
accept_encoding_utilities: HTTP_ANY_ACCEPT_HEADER_UTILITIES
-- Encoding
feature -- Access: Server Side Defaults Formats
default_encoding: READABLE_STRING_8
-- Content-coding that is acceptable in the response.
feature -- Change Element
set_default_encoding (a_encoding: READABLE_STRING_8)
-- Set `default_encoding' with `a_encoding'
do
default_encoding := a_encoding
ensure
default_encoding_set: a_encoding = default_encoding
end
feature -- Encoding Negotiation
preference (a_server_encoding_supported: ITERABLE [READABLE_STRING_8]; a_header_value: detachable READABLE_STRING_8): HTTP_ACCEPT_ENCODING_VARIANTS
-- `a_server_encoding_supported' represent a list of encoding supported by the server.
-- `a_header_value' represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server supports
-- the requested Encoding, or empty in other case.
note
EIS: "name=encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
local
l_compression_match: READABLE_STRING_8
do
create Result.make
Result.set_supported_variants (a_server_encoding_supported)
if a_header_value = Void or else a_header_value.is_empty then
-- the request has no Accept-Encoding header, ie the header is empty, in this case do not compress representations
Result.set_acceptable (True)
Result.set_variant_value (default_encoding)
else
Result.set_vary_header_value
-- select the best match, server support, client preferences
l_compression_match := accept_encoding_utilities.best_match (a_server_encoding_supported, a_header_value)
if l_compression_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
else
-- Set the best match
Result.set_variant_value (l_compression_match)
Result.set_acceptable (True)
end
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,93 @@
note
description: "Summary description for {SERVER_LANGUAGE_NEGOTIATION}. Utility class to support Server Side Content Negotiation on language"
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
SERVER_LANGUAGE_NEGOTIATION
inherit
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
make (a_language_dft: READABLE_STRING_8)
do
create accept_language_utilities
set_default_language (a_language_dft)
ensure
default_language_set: default_language = a_language_dft
end
accept_language_utilities: HTTP_ACCEPT_LANGUAGE_UTILITIES
-- Language
feature -- Access: Server Side Defaults Formats
default_language: READABLE_STRING_8
-- Natural language that is preferred as a response to the request.
feature -- Change Element
set_default_language (a_language: READABLE_STRING_8)
-- Set `default_language' with `a_language'
do
default_language := a_language
ensure
default_language_set: a_language = default_language
end
feature -- Language Negotiation
preference (a_server_language_supported: ITERABLE [READABLE_STRING_8]; a_header_value: detachable READABLE_STRING_8): HTTP_ACCEPT_LANGUAGE_VARIANTS
-- `a_server_language_supported' represent a list of languages supported by the server.
-- `a_header_value' represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server supports
-- the requested Language, or empty in other case.
note
EIS: "name=language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
local
l_language_match: READABLE_STRING_8
do
create Result.make
Result.set_supported_variants (a_server_language_supported)
if a_header_value = Void or else a_header_value.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (True)
Result.set_variant_value (default_language)
else
Result.set_vary_header_value
-- select the best match, server support, client preferences
l_language_match := accept_language_utilities.best_match (a_server_language_supported, a_header_value)
if l_language_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
else
-- Set the best match
Result.set_variant_value (l_language_match)
Result.set_acceptable (True)
end
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,91 @@
note
description: "Summary description for {SERVER_MEDIA_TYPE_NEGOTIATION}. Utility class to support Server Side Content Negotiation on media type"
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
SERVER_MEDIA_TYPE_NEGOTIATION
inherit
REFACTORING_HELPER
create
make
feature {NONE} -- Initialization
make (a_mediatype_dft: READABLE_STRING_8)
do
create accept_media_type_utilities
set_default_media_type (a_mediatype_dft)
ensure
default_media_type_set: default_media_type = a_mediatype_dft
end
accept_media_type_utilities: HTTP_ACCEPT_MEDIA_TYPE_UTILITIES
-- MIME
feature -- Access: Server Side Defaults Formats
default_media_type: READABLE_STRING_8
-- Media type which is acceptable for the response.
feature -- Change Element
set_default_media_type (a_mediatype: READABLE_STRING_8)
-- Set `default_media_type' with `a_mediatype'
do
default_media_type := a_mediatype
ensure
default_media_type_set: a_mediatype = default_media_type
end
feature -- Media Type Negotiation
preference (a_mime_types_supported: ITERABLE [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): HTTP_ACCEPT_MEDIA_TYPE_VARIANTS
-- `a_mime_types_supported' represent media types supported by the server.
-- `a_header represent' the Accept header, ie, the client preferences.
-- Return which media type to use for representation in a response, if the server supports
-- the requested media type, or empty in other case.
note
EIS: "name=media type", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
local
l_mime_match: READABLE_STRING_8
do
create Result.make
Result.set_supported_variants (a_mime_types_supported)
if a_header = Void or else a_header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (True)
Result.set_variant_value (default_media_type)
else
Result.set_vary_header_value
-- select the best match, server support, client preferences
l_mime_match := accept_media_type_utilities.best_match (a_mime_types_supported, a_header)
if l_mime_match.is_empty then
-- The server does not support any of the media types preferred by the client
Result.set_acceptable (False)
else
-- Set the best match
Result.set_variant_value (l_mime_match)
Result.set_acceptable (True)
end
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,36 @@
note
description: "[
{HTTP_ACCEPT_CHARSET_VARIANTS}
Represent the character sets results between client preferences and character sets variants supported by the server.
If the server is unable to supports the requested Accept-Charset values, the server can build
a response with the list of supported character sets.
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_ACCEPT_CHARSET_VARIANTS
inherit
HTTP_ACCEPT_VARIANTS
rename
variant_value as charset
end
create
make
feature -- Change
set_vary_header_value
-- <Precursor>
do
vary_header_value := {HTTP_HEADER_NAMES}.header_accept_charset -- "Accept-Charset"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,36 @@
note
description: "[
{HTTP_ACCEPT_ENCODING_VARIANTS}
Represent the encoding results between client preferences and encoding variants supported by the server.
If the server is unable to supports the requested Accept-Encoding values, the server can build
a response with the list of supported encodings
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name= Compression", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
class
HTTP_ACCEPT_ENCODING_VARIANTS
inherit
HTTP_ACCEPT_VARIANTS
rename
variant_value as encoding
end
create
make
feature -- Change
set_vary_header_value
-- <Precursor>
do
vary_header_value := {HTTP_HEADER_NAMES}.header_accept_encoding -- "Accept-Encoding"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,35 @@
note
description: "[
{HTTP_ACCEPT_LANGUAGE_VARIANTS}.
Represent the language results between client preferences and language variants supported by the server.
If the server is unable to supports the requested Accept-Language values, the server can build
a response with the list of supported languages
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_ACCEPT_LANGUAGE_VARIANTS
inherit
HTTP_ACCEPT_VARIANTS
rename
variant_value as language
end
create
make
feature -- Change
set_vary_header_value
-- <Precursor>
do
vary_header_value := {HTTP_HEADER_NAMES}.header_accept_language -- "Accept-Language"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,35 @@
note
description: "[
{HTTP_ACCEPT_MEDIA_TYPE_VARIANTS}.
Represents the media type results between client preferences and media type variants supported by the server..
If the server is unable to supports the requested Accept values, the server can build
a response with the list of supported representations
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_ACCEPT_MEDIA_TYPE_VARIANTS
inherit
HTTP_ACCEPT_VARIANTS
rename
variant_value as media_type
end
create
make
feature -- Change
set_vary_header_value
-- <Precursor>
do
vary_header_value := {HTTP_HEADER_NAMES}.header_accept -- "Accept"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,92 @@
note
description: "Generic {HTTP_ACCEPT_VARIANTS}.with common functionality to most header variants.."
date: "$Date$"
revision: "$Revision$"
deferred class
HTTP_ACCEPT_VARIANTS
feature {NONE} -- Initialization
make
do
end
feature -- Change
set_vary_header_value
-- Set the `vary_header_value'
deferred
ensure
is_valid_header_set : is_valid_header_name (vary_header_value)
end
feature -- Access
vary_header_value: detachable READABLE_STRING_8
-- Name of header to be added to the Vary header of the response
-- this indicates the Accept-* header source of the matched `variant_value' if any,
-- if this is using the default, the `vary_header_value' is Void.
supported_variants: detachable ITERABLE [READABLE_STRING_8]
-- Set of supported variants for the response
variant_value: detachable READABLE_STRING_8
-- Associated value, it could be value of:
-- content type
-- language
-- character set
-- encoding.
feature -- Status_Report
is_acceptable: BOOLEAN
-- is the current variant accepted?
is_valid_header_name (a_header_name: detachable READABLE_STRING_8): BOOLEAN
-- is `a_header_name' a valid accept header name?
note
EIS:"name=Accept", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
EIS:"name=Accept-Charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
EIS:"name=Accept-Encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
EIS:"name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
do
if a_header_name /= Void then
Result := a_header_name.same_string ({HTTP_HEADER_NAMES}.header_accept) -- "Accept",
or else a_header_name.same_string ({HTTP_HEADER_NAMES}.header_accept_language) -- "Accept-Language",
or else a_header_name.same_string ({HTTP_HEADER_NAMES}.header_accept_encoding) -- "Accept-Encoding",
or else a_header_name.same_string ({HTTP_HEADER_NAMES}.header_accept_charset) -- "Accept-Charset"
end
end
feature -- Change Element
set_variant_value (v: READABLE_STRING_8)
-- Set `variant_value' as `v'
do
variant_value := v
ensure
type_set: attached variant_value as l_variant implies l_variant = v
end
set_acceptable (b: BOOLEAN)
-- Set `is_acceptable' with `b'
do
is_acceptable := b
ensure
is_acceptable_set: is_acceptable = b
end
set_supported_variants (a_supported: ITERABLE [READABLE_STRING_8])
-- Set `supported variants' with `a_supported'
do
supported_variants := a_supported
ensure
set_supported_variants: attached supported_variants as l_supported_variants implies l_supported_variants = a_supported
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -16,10 +16,10 @@ feature {NONE} -- Initialization
make
local
mime_parse : MIME_PARSE
mime_parse : HTTP_ACCEPT_MEDIA_TYPE_UTILITIES
accept : STRING
charset_parse : COMMON_ACCEPT_HEADER_PARSER
language : LANGUAGE_PARSE
charset_parse : HTTP_ANY_ACCEPT_HEADER_UTILITIES
language : HTTP_ACCEPT_LANGUAGE_UTILITIES
do
create mime_parse
-- parse_result := mime_parse.parse_mime_type ("application/xhtml;q=0.5")
@@ -59,12 +59,12 @@ feature {NONE} -- Initialization
print ("%N"+mime_parse.quality ("*/*;q=0.1", accept).out)
accept := "application/atom+xml"
print ("%N"+mime_parse.parse_mime_type (accept).out)
print ("%N"+mime_parse.media_type (accept).out)
create charset_parse
accept := "iso-8859-5"
print ("%N" + charset_parse.parse_common (accept).out)
print ("%N" + charset_parse.header (accept).out)
accept := "unicode-1-1;q=0.8"
print ("%N" + charset_parse.parse_common (accept).out)
print ("%N" + charset_parse.header (accept).out)
accept:= "iso-8859-5, unicode-1-1;q=0.8"
@@ -78,10 +78,10 @@ feature {NONE} -- Initialization
print (language.best_match (accept.split (','), "da"))
print (language.best_match (accept.split (','), "en-*"))
print ("%N"+language.parse_media_range ("da").out)
print ("%N"+language.parse_media_range ("en-gb;q=0.8").out)
print ("%N"+language.parse_media_range ("en;q=0.7").out)
print ("%N"+language.parse_media_range ("en-*").out)
print ("%N"+language.accept_language ("da").out)
print ("%N"+language.accept_language ("en-gb;q=0.8").out)
print ("%N"+language.accept_language ("en;q=0.7").out)
print ("%N"+language.accept_language ("en-*").out)
end
end

View File

@@ -21,13 +21,33 @@ feature {NONE} -- Events
create parser
end
feature -- Helpers
format (a_common: HTTP_ANY_ACCEPT): STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached a_common.value as t then
Result.append_string ("'" + t + "',")
end
Result.append_string (" {")
if attached a_common.parameters as l_parameters then
across
l_parameters as ic
loop
Result.append ("'" + ic.key + "':'" + ic.item + "',");
end
end
Result.append ("})")
end
feature -- Test routines
test_parse_charsets
do
assert ("Expected ('iso-8859-5', {'q':'1.0',})", parser.parse_common("iso-8859-5").out.same_string("('iso-8859-5', {'q':'1.0',})") )
assert ("Expected ('unicode-1-1', {'q':'0.8',})", parser.parse_common("unicode-1-1;q=0.8").out.same_string("('unicode-1-1', {'q':'0.8',})") )
assert ("Expected ('*', {'q':'1.0',})", parser.parse_common("*").out.same_string("('*', {'q':'1.0',})") )
assert ("Expected ('iso-8859-5', {'q':'1.0',})", format (parser.header("iso-8859-5")).same_string("('iso-8859-5', {'q':'1.0',})") )
assert ("Expected ('unicode-1-1', {'q':'0.8',})", format (parser.header("unicode-1-1;q=0.8")).same_string("('unicode-1-1', {'q':'0.8',})") )
assert ("Expected ('*', {'q':'1.0',})", format (parser.header("*")).same_string("('*', {'q':'1.0',})") )
end
@@ -52,6 +72,6 @@ feature -- Test routines
assert ("Expected unicode-1-1", parser.best_match (charset_supported, "unicode-1-1;q=1").same_string ("unicode-1-1"))
end
parser : COMMON_ACCEPT_HEADER_PARSER
parser : HTTP_ANY_ACCEPT_HEADER_UTILITIES
end

View File

@@ -0,0 +1,260 @@
note
description: "Summary description for {CONNEG_SERVER_SIDE_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CONNEG_SERVER_SIDE_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create conneg.make ("application/json", "es", "UTF-8", "")
-- set default values
end
feature -- Test routines
test_media_type_negotiation
local
media_variants : HTTP_ACCEPT_MEDIA_TYPE_VARIANTS
mime_types_supported : LIST [STRING]
l_types : STRING
do
-- Scenario 1, the server side does not support client preferences
l_types := "application/json,application/xbel+xml,application/xml"
mime_types_supported := l_types.split(',')
media_variants := conneg.media_type_preference (mime_types_supported, "text/html")
assert ("Expected Not Acceptable", not media_variants.is_acceptable)
if attached media_variants.supported_variants as l_supported_variants then
assert ("Same Value at 1", same_text (first_of (mime_types_supported), first_of (l_supported_variants)))
assert ("Same count", count_of (mime_types_supported) = count_of (l_supported_variants))
else
assert ("Has supported_variants results", False)
end
assert ("Variant Header", attached media_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept"))
assert ("Media type is void",media_variants.media_type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept:
media_variants := conneg.media_type_preference (mime_types_supported, "")
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is set",media_variants.supported_variants = mime_types_supported)
assert ("Mime is default", attached media_variants.media_type as l_media_type and then conneg.default_media_type.same_string (l_media_type))
assert ("Variant header", media_variants.vary_header_value = Void)
--Scenario 3, the server select the best match, and set the vary header
media_variants := conneg.media_type_preference (mime_types_supported, "text/*,application/xml;q=0.5,application/json;q=0.6")
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is set",media_variants.supported_variants = mime_types_supported)
assert ("Variant Header", attached media_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept"))
assert ("Media Type is application/json", attached media_variants.media_type as l_media_type and then l_media_type.same_string ("application/json"))
end
test_charset_negotiation
local
charset_variants : HTTP_ACCEPT_CHARSET_VARIANTS
charset_supported : LIST [STRING]
l_charset_value : STRING
do
-- Scenario 1, the server side does not support client preferences
l_charset_value := "UTF-8, iso-8859-5"
charset_supported := l_charset_value.split(',')
charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1")
assert ("Expected Not Acceptable", not charset_variants.is_acceptable)
if attached charset_variants.supported_variants as l_supported_variants then
assert ("Same Value at 1", same_text (first_of (charset_supported), first_of (l_supported_variants)))
assert ("Same count",charset_supported.count = count_of (l_supported_variants))
else
assert("Has supported_variants results", False)
end
assert ("Variant Header", attached charset_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Charset"))
assert ("Character type is void",charset_variants.charset = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Charset:
charset_variants := conneg.charset_preference (charset_supported, "")
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is set",charset_variants.supported_variants = charset_supported)
assert ("Charset is default", attached charset_variants.charset as l_charset and then conneg.default_charset.same_string (l_charset))
assert ("Variant header", charset_variants.vary_header_value = Void)
--Scenario 3, the server select the best match, and set the vary header
charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1, UTF-8;q=0.3, iso-8859-5")
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is set",charset_variants.supported_variants = charset_supported)
assert ("Variant Header", attached charset_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Charset"))
assert ("Character Type is iso-8859-5", attached charset_variants.charset as l_charset and then l_charset.same_string ("iso-8859-5"))
end
test_compression_negotiation
local
compression_variants : HTTP_ACCEPT_ENCODING_VARIANTS
compression_supported : LIST [STRING]
l_compression : STRING
do
-- Scenario 1, the server side does not support client preferences
l_compression := ""
compression_supported := l_compression.split(',')
compression_variants := conneg.encoding_preference (compression_supported, "gzip")
assert ("Expected Not Acceptable", not compression_variants.is_acceptable)
if attached compression_variants.supported_variants as l_supported_variants then
assert ("Same Value at 1", same_text (first_of (compression_supported), first_of (l_supported_variants)))
assert ("Same count",compression_supported.count = count_of (l_supported_variants))
else
assert ("Has supported_variants results", False)
end
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Compression type is void",compression_variants.encoding = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Encoding
compression_variants := conneg.encoding_preference (compression_supported, "")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is set",compression_variants.supported_variants = compression_supported)
assert ("Compression is default", attached compression_variants.encoding as l_encoding and then conneg.default_encoding.same_string (l_encoding))
assert ("Variant header", compression_variants.vary_header_value = Void)
--Scenario 3, the server select the best match, and set the vary header
l_compression := "gzip"
compression_supported := l_compression.split(',')
conneg.set_default_encoding("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "compress,gzip;q=0.7")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is set",compression_variants.supported_variants = compression_supported)
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is gzip", attached compression_variants.encoding as l_type and then l_type.same_string ("gzip"))
-- Scenario 4, the server set `identity' and the client doesn't mention identity
l_compression := "identity"
compression_supported := l_compression.split(',')
conneg.set_default_encoding ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "gzip;q=0.7")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is set",compression_variants.supported_variants = compression_supported)
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is Identity", attached compression_variants.encoding as l_type and then l_type.same_string ("identity"))
-- Scenario 5, the server set `identity' and the client mention identity,q=0
l_compression := "identity"
compression_supported := l_compression.split(',')
conneg.set_default_encoding ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "identity;q=0")
assert ("Expected Not Acceptable", not compression_variants.is_acceptable)
assert ("Variants is attached",attached compression_variants.supported_variants )
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is Void", compression_variants.encoding = Void)
-- Scenario 6, the server set `identity' and the client mention *,q=0
l_compression := "identity"
compression_supported := l_compression.split(',')
conneg.set_default_encoding ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "*;q=0")
assert ("Expected Not Acceptable", not compression_variants.is_acceptable)
assert ("Variants is attached",attached compression_variants.supported_variants )
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is Void", compression_variants.encoding = Void)
-- Scenario 7, the server set `identity' and the client mention identity;q=0.5, gzip;q=0.7,compress
l_compression := "identity"
compression_supported := l_compression.split(',')
conneg.set_default_encoding ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "identity;q=0.5, gzip;q=0.7,compress")
assert ("Expected Acceptable",compression_variants.is_acceptable)
assert ("Variants is set",compression_variants.supported_variants = compression_supported)
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is identity", attached compression_variants.encoding as l_type and then l_type.same_string ("identity"))
-- Scenario 8, the server set `identity' and the client mention identity;q=0.5
l_compression := "identity"
compression_supported := l_compression.split(',')
conneg.set_default_encoding ("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "identity;q=0.5")
assert ("Expected Acceptable",compression_variants.is_acceptable)
assert ("Variants is set",compression_variants.supported_variants = compression_supported)
assert ("Variant Header", attached compression_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Encoding"))
assert ("Encoding Type is identity", attached compression_variants.encoding as l_type and then l_type.same_string ("identity"))
end
test_language_negotiation
local
language_variants : HTTP_ACCEPT_LANGUAGE_VARIANTS
languages_supported : LIST [STRING]
l_languages : STRING
do
-- Scenario 1, the server side does not support client preferences
l_languages := "es,en,en-US,fr;q=0.6"
languages_supported := l_languages.split(',')
language_variants := conneg.language_preference (languages_supported, "de")
assert ("Expected Not Acceptable", not language_variants.is_acceptable)
assert ("Variant Header", attached language_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Language"))
assert ("Language type is Void",language_variants.language = Void)
if attached language_variants.supported_variants as l_supported_variants then
assert ("Same Value at 1", same_text (first_of (languages_supported), first_of (l_supported_variants)))
assert ("Same count",languages_supported.count = count_of (l_supported_variants))
else
assert ("Has supported variants results", False)
end
-- Scenario 2, the client doesnt send values in the header, Accept-Language:
language_variants := conneg.language_preference (languages_supported, "")
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is attached",language_variants.supported_variants = languages_supported)
assert ("Language is default", attached language_variants.language as l_lang and then conneg.default_language.same_string (l_lang))
assert ("Variant header", language_variants.vary_header_value = Void)
--Scenario 3, the server select the best match, and set the vary header
language_variants := conneg.language_preference (languages_supported, "fr,es;q=0.4")
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is detached",language_variants.supported_variants = languages_supported)
assert ("Variant Header", attached language_variants.vary_header_value as l_variant_header and then l_variant_header.same_string ("Accept-Language"))
assert ("Language Type is fr", attached language_variants.language as l_lang and then l_lang.same_string ("fr"))
end
feature -- Implementation
conneg : SERVER_CONTENT_NEGOTIATION
same_text (s1,s2: detachable READABLE_STRING_8): BOOLEAN
do
if s1 = Void then
Result := s2 = Void
elseif s2 = Void then
Result := False
else
Result := s1.same_string (s2)
end
end
count_of (i: ITERABLE [READABLE_STRING_8]): INTEGER
do
across
i as ic
loop
Result := Result + 1
end
end
first_of (i: ITERABLE [READABLE_STRING_8]): detachable READABLE_STRING_8
do
across
i as ic
until
ic.item /= Void
loop
end
end
end

View File

@@ -0,0 +1,141 @@
note
description: "Summary description for {LANGUAGE_PARSER_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_PARSER_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create parser
end
feature -- Helpers
format (a_language: HTTP_ACCEPT_LANGUAGE): STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached a_language.language as t then
Result.append_string ("'" + t + "',")
end
if attached a_language.specialization as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
if attached a_language.parameters as l_params then
across
l_params as ic
loop
Result.append ("'" + ic.key + "':'"+ ic.item + "',");
end
end
Result.append ("})")
end
feature -- Test routines
test_parse_language
do
assert ("Expected ('da', {'q':'1.0',})", format (parser.accept_language ("da")).same_string ("('da', {'q':'1.0',})"));
assert ("Expected ('en', 'gb', {'q':'0.8',})", format (parser.accept_language ("en-gb;q=0.8")).same_string ("('en', 'gb', {'q':'0.8',})"));
assert ("Expected ('en', {'q':'0.7',})", format (parser.accept_language ("en;q=0.7")).same_string ("('en', {'q':'0.7',})"));
assert ("Expected ('en', '*', {'q':'1.0',})", format (parser.accept_language ("en-*")).same_string ("('en', '*', {'q':'1.0',})"));
end
test_RFC2616_example
local
accept : STRING
do
accept := "da, en-gb;q=0.8, en;q=0.7";
assert ("Expected 1.0", 1.0 = parser.quality ("da", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-gb", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-*", accept))
end
test_best_match
local
langs_supported : LIST [STRING]
l_types : STRING
do
l_types := "en-gb,en-us"
langs_supported := l_types.split(',')
assert ("Expected en-us", parser.best_match (langs_supported, "en-us").same_string ("en-us"))
assert ("Direct match with a q parameter", parser.best_match (langs_supported, "en-gb;q=1").same_string ("en-gb"))
assert ("Direct match second choice with a q parameter", parser.best_match (langs_supported, "en-us;q=1").same_string ("en-us"))
assert ("Direct match using a subtype wildcard", parser.best_match (langs_supported, "en-*;q=1").is_equal ("en-gb"))
assert ("Match using a type wildcard", parser.best_match (langs_supported, "*").same_string ("en-gb"))
l_types := "en-gb,es"
langs_supported := l_types.split(',')
assert ("Match using a type versus a lower weighted subtype", parser.best_match (langs_supported, "es-*;q=0.5,*;q=0.1").same_string ("es"))
assert ("Fail to match anything",parser.best_match (langs_supported, "fr; q=0.9").same_string (""))
l_types := "en-gb,en-us"
langs_supported := l_types.split(',')
assert ("Test 1 verify fitness ordering", parser.best_match (langs_supported, "en-gb,en-us,*").same_string ("en-gb"))
l_types := "es,en-gb;q=1.0,fr;q=0.6"
langs_supported := l_types.split(',')
assert ("Match default es at first position", parser.best_match (langs_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr;q=0.6,es"
langs_supported := l_types.split(',')
assert ("Match default es at last position", parser.best_match (langs_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr,es"
langs_supported := l_types.split(',')
assert ("Match first top quality and fitness", parser.best_match (langs_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "fr;q=1.0,en,es"
langs_supported := l_types.split(',')
assert ("Test 1", parser.best_match (langs_supported, "es;q=1.0,*/*;q=0.1,en;q=0.9").same_string ("es"))
l_types := "fr;q=1.0,en,es"
langs_supported := l_types.split(',')
assert ("Test 1", parser.best_match (langs_supported, "es,*/*;q=0.1,en").same_string ("es"))
l_types := "fr;q=1.0,en,es"
langs_supported := l_types.split(',')
assert ("Test 2", parser.best_match (langs_supported, "en,es,*/*;q=0.1").same_string ("en"))
l_types := "es,en;q=0.6"
langs_supported := l_types.split(',')
assert ("Test 2", parser.best_match (langs_supported, "fr;q=1.0, en;q=0.6, es").same_string ("es"))
end
test_support_wildcard
local
mime_types_supported : LIST[STRING]
l_types : STRING
do
l_types := "en-*,fr"
mime_types_supported := l_types.split(',')
assert ("match using a type wildcard", parser.best_match (mime_types_supported, "en-gb").same_string ("en-*"))
end
parser : HTTP_ACCEPT_LANGUAGE_UTILITIES
end

View File

@@ -24,19 +24,42 @@ feature {NONE} -- Events
create parser
end
feature -- Helper
format (a_mediatype: HTTP_MEDIA_TYPE): STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached a_mediatype.type as t then
Result.append_string ("'" + t + "',")
end
if attached a_mediatype.subtype as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
if attached a_mediatype.parameters as l_params then
across
l_params as ic
loop
Result.append ("'" + ic.key + "':'" + ic.item + "',");
end
end
Result.append ("})")
end
feature -- Test routines
test_parse_media_range
do
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=1").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.media_type("application/xml;q=1")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml ; q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=1;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=2;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.media_type("application/xml")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.media_type("application/xml;q=")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.media_type("application/xml ; q=")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", format (parser.media_type("application/xml ; q=1;b=other")).same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", format (parser.media_type("application/xml ; q=2;b=other")).same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
-- Accept header that includes *
assert ("Expected ('*', '*', {'q':'.2',})", parser.parse_media_range(" *; q=.2").out.same_string("('*', '*', {'q':'.2',})"))
assert ("Expected ('*', '*', {'q':'.2',})", format (parser.media_type(" *; q=.2")).same_string("('*', '*', {'q':'.2',})"))
end
@@ -122,7 +145,7 @@ feature -- Test routines
parser : MIME_PARSE
parser : HTTP_ACCEPT_MEDIA_TYPE_UTILITIES
end

View File

@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-6-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-6-0 http://www.eiffel.com/developers/xml/configuration-1-6-0.xsd" name="test" uuid="7860561C-779A-4E45-A7B9-06A1E0E984E8">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="test" uuid="7860561C-779A-4E45-A7B9-06A1E0E984E8">
<target name="test">
<root class="APPLICATION" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
<exclude>/.git$</exclude>
</file_rule>
<option warning="true">
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="conneg" location="..\conneg-safe.ecf"/>
<library name="conneg" location="..\conneg-safe.ecf" readonly="false">
<option>
<assertions precondition="true" postcondition="true" check="true" invariant="true" supplier_precondition="true"/>
</option>
</library>
<library name="http" location="..\..\http\http-safe.ecf" readonly="false"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<cluster name="test" location=".\" recursive="true"/>
</target>

View File

@@ -12,6 +12,7 @@
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http" location="..\..\http\http.ecf" readonly="false"/>
<library name="conneg" location="..\conneg.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing.ecf"/>
<cluster name="test" location=".\" recursive="true"/>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="http" uuid="F8BE3C55-88E8-4103-A936-B1E5CB1D330E" library_target="http">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="http" uuid="F8BE3C55-88E8-4103-A936-B1E5CB1D330E" library_target="http">
<target name="http">
<root all_classes="true"/>
<file_rule>

View File

@@ -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

View File

@@ -4,6 +4,7 @@ note
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=RFC3875", "protocol=URI", "src=http://tools.ietf.org/html/rfc3875"
class
HTTP_CONTENT_TYPE
@@ -34,7 +35,7 @@ feature -- Constant
charset_parameter_name: STRING_8 = "charset"
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

View File

@@ -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,61 +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
c: like headers.new_cursor
n: INTEGER
l_line: READABLE_STRING_8
do
from
n := a_name.count
c := headers.new_cursor
until
c.after or Result /= Void
loop
l_line := c.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
c.forth
end
end
feature -- Removal
remove_header_named (a_name: READABLE_STRING_8)
@@ -335,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
@@ -712,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'
@@ -812,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:"
@@ -871,53 +411,8 @@ 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-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

View File

@@ -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: <method>[, <method>]*
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: <field-name>[, <field-name>]*
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: <field-name>[, <field-name>]*
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

View File

@@ -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: <method>[, <method>]*
--| 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: <field-name>[, <field-name>]*
--| 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

View File

@@ -2,7 +2,7 @@ note
description: "[
This class is to represent a media type
the Internet Media Type [9] of the attached entity if the type
the Internet Media Type of the attached entity if the type
was provided via a "Content-type" field in the wgi_request header,
or if the server can determine it in the absence of a supplied
"Content-type" field. The syntax is the same as for the HTTP
@@ -29,6 +29,8 @@ note
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Wikipedia/Media Type", "protocol=URI", "src=http://en.wikipedia.org/wiki/Internet_media_type"
EIS: "name=RFC2046", "protocol=URI", "src=http://tools.ietf.org/html/rfc2046"
class
HTTP_MEDIA_TYPE
@@ -76,15 +78,7 @@ feature {NONE} -- Initialization
p := s.index_of (';', i)
if p > 0 then
t := s.substring (i, p - 1)
i := p + 1
p := s.index_of (';', i)
if p = 0 then
add_parameter_from_string (s, i, n)
i := n
else
add_parameter_from_string (s, i, p - 1)
i := p + 1
end
create parameters.make_from_substring (s, p + 1, s.count)
else
t := s.substring (i, n)
end
@@ -94,9 +88,14 @@ feature {NONE} -- Initialization
-- Extract type and subtype
p := t.index_of ('/', 1)
if p = 0 then
has_error := True
t.right_adjust
type := t
subtype := ""
if t.same_string ("*") then
--| Accept *; should be */*
else
has_error := True
end
subtype := "*"
else
subtype := t.substring (p + 1, t.count)
type := t
@@ -108,7 +107,7 @@ feature {NONE} -- Initialization
subtype := type
end
ensure
not has_error implies (create {HTTP_CONTENT_TYPE}.make_from_string (string)).same_string (string)
not has_error implies (create {HTTP_MEDIA_TYPE}.make_from_string (string)).same_string (string)
end
feature -- Status report
@@ -141,7 +140,7 @@ feature -- Access
end
end
parameters: detachable HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
parameters: detachable HTTP_PARAMETER_TABLE
-- Parameters
feature -- Conversion
@@ -254,7 +253,7 @@ feature -- Element change
-- Remove parameter named `a_name'
do
if attached parameters as plst then
plst.prune (a_name)
plst.remove (a_name)
if plst.is_empty then
parameters := Void
end
@@ -262,49 +261,6 @@ feature -- Element change
internal_string := Void
end
feature {NONE} -- Implementation
add_parameter_from_string (s: READABLE_STRING_8; start_index, end_index: INTEGER)
-- Add parameter from string " attribute=value "
local
pn,pv: STRING_8
i: INTEGER
p: INTEGER
err: BOOLEAN
do
-- Skip spaces
from
i := start_index
until
i > end_index or not s[i].is_space
loop
i := i + 1
end
if i < end_index then
p := s.index_of ('=', i)
if p > 0 and p < end_index then
pn := s.substring (i, p - 1)
pv := s.substring (p + 1, end_index)
pv.right_adjust
if pv.count > 0 and pv [1] = '%"' then
if pv [pv.count] = '%"' then
pv := pv.substring (2, pv.count - 1)
else
err := True
-- missing closing double quote.
end
end
if not err then
add_parameter (pn, pv)
end
else
-- expecting: attribute "=" value
err := True
end
end
has_error := has_error or err
end
feature {NONE} -- Internal
internal_string: detachable STRING_8
@@ -334,7 +290,7 @@ invariant
type_and_subtype_not_empty: not has_error implies not type.is_empty and not subtype.is_empty
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

View File

@@ -0,0 +1,150 @@
note
description: "[
Table representing parameters of the form q=1.0;note="blabla";foo=bar
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_PARAMETER_TABLE
inherit
HASH_TABLE [READABLE_STRING_8, STRING_8]
redefine
empty_duplicate
end
create
make,
make_from_string,
make_from_substring
feature {NONE} -- Initialization
make_from_string (s: READABLE_STRING_8)
-- Build table of parameters for `s'
do
make_from_substring (s, 1, s.count)
end
make_from_substring (s: READABLE_STRING_8; a_start_index: INTEGER; a_end_index: INTEGER)
-- Build table of parameters for `s.substring (a_start_index, a_end_index)'
local
cl: CELL [INTEGER]
i: INTEGER
do
make (1)
from
i := a_start_index
create cl.put (i)
until
i >= a_end_index
loop
force_substring (s, i, cl)
i := cl.item
end
end
feature -- Status report
has_error: BOOLEAN
-- Current has error?
--| Mainly in relation with `make_from_string' and `force_substring'
feature {NONE} -- Implementation
force_substring (s: READABLE_STRING_8; start_index: INTEGER; out_end_index: CELL [INTEGER])
-- Add parameter from string " attribute=value "
-- and put in `out_end_index' the index after found parameter.
local
n: INTEGER
pn,pv: STRING_8
i: INTEGER
p, q: INTEGER
err: BOOLEAN
do
n := s.count
-- Skip spaces
from
i := start_index
until
i > n or not s[i].is_space
loop
i := i + 1
end
if s[i] = ';' then
-- empty parameter
out_end_index.replace (i + 1)
elseif i < n then
p := s.index_of ('=', i)
if p > 0 then
pn := s.substring (i, p - 1)
if p >= n then
pv := ""
out_end_index.replace (n + 1)
else
if s[p+1] = '%"' then
q := s.index_of ('%"', p + 2)
if q > 0 then
pv := s.substring (p + 2, q - 1)
from
i := q + 1
until
i > n or not s[i].is_space
loop
i := i + 1
end
if s[i] = ';' then
i := i + 1
end
out_end_index.replace (i)
else
err := True
pv := ""
-- missing closing double quote.
end
else
q := s.index_of (';', p + 1)
if q = 0 then
q := n + 1
end
pv := s.substring (p + 1, q - 1)
out_end_index.replace (q + 1)
end
pv.right_adjust
if not err then
force (pv, pn)
end
end
else
-- expecting: attribute "=" value
err := True
end
end
if err then
out_end_index.replace (n + 1)
end
has_error := has_error or err
ensure
entry_processed: out_end_index.item > start_index
end
feature {NONE} -- Duplication
empty_duplicate (n: INTEGER): like Current
-- Create an empty copy of Current that can accommodate `n' items
do
Result := Precursor (n)
end
note
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
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

View File

@@ -0,0 +1,214 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
HTTP_CONTENT_TYPE_SET
inherit
EQA_TEST_SET
feature -- Content type
test_http_content_types_with_params
local
do
test_content_type_with_params ("text/plain; param1=%"something;foo=bar%"; param2=%"another-thing%"",
"text", "plain", <<["param1", "something;foo=bar"], ["param2", "another-thing"]>>
)
test_content_type ("*", "*", "*")
test_content_type_with_params ("* ; q=0.8", "*", "*", <<["q", "0.8"]>>)
test_content_type ("*/*", "*", "*")
test_content_type ("text/*", "text", "*")
end
test_http_content_type
do
test_content_type ("application/atom+xml", "application", "atom+xml") -- Atom feeds
test_content_type ("application/ecmascript", "application", "ecmascript") -- ECMAScript/JavaScript; Defined in RFC 4329 (equivalent to application/javascript but with stricter processing rules)
test_content_type ("application/EDI-X12", "application", "EDI-X12") -- EDI X12 data; Defined in RFC 1767
test_content_type ("application/EDIFACT", "application", "EDIFACT") -- EDI EDIFACT data; Defined in RFC 1767
test_content_type ("application/json", "application", "json") -- JavaScript Object Notation JSON; Defined in RFC 4627
test_content_type ("application/javascript", "application", "javascript") -- ECMAScript/JavaScript; Defined in RFC 4329 (equivalent to application/ecmascript but with looser processing rules) It is not accepted in IE 8 or earlier - text/javascript is accepted but it is defined as obsolete in RFC 4329. The "type" attribute of the <script> tag in HTML5 is optional. In practice, omitting the media type of JavaScript programs is the most interoperable solution, since all browsers have always assumed the correct default even before HTML5.
test_content_type ("application/octet-stream", "application", "octet-stream") -- Arbitrary binary data.[10] Generally speaking this type identifies files that are not associated with a specific application. Contrary to past assumptions by software packages such as Apache this is not a type that should be applied to unknown files. In such a case, a server or application should not indicate a content type, as it may be incorrect, but rather, should omit the type in order to allow the recipient to guess the type.[11]
test_content_type ("application/ogg", "application", "ogg") -- Ogg, a multimedia bitstream container format; Defined in RFC 5334
test_content_type ("application/pdf", "application", "pdf") -- Portable Document Format, PDF has been in use for document exchange on the Internet since 1993; Defined in RFC 3778
test_content_type ("application/postscript", "application", "postscript") -- PostScript; Defined in RFC 2046
test_content_type ("application/rdf+xml", "application", "rdf+xml") -- Resource Description Framework; Defined by RFC 3870
test_content_type ("application/rss+xml", "application", "rss+xml") -- RSS feeds
test_content_type ("application/soap+xml", "application", "soap+xml") -- SOAP; Defined by RFC 3902
test_content_type ("application/font-woff", "application", "font-woff") -- Web Open Font Format; (candidate recommendation; use application/x-font-woff until standard is official)
test_content_type ("application/xhtml+xml", "application", "xhtml+xml") -- XHTML; Defined by RFC 3236
test_content_type ("application/xml", "application", "xml") -- XML files; Defined by RFC 3023
test_content_type ("application/xml-dtd", "application", "xml-dtd") -- DTD files; Defined by RFC 3023
test_content_type ("application/xop+xml", "application", "xop+xml") -- XOP
test_content_type ("application/zip", "application", "zip") -- ZIP archive files; Registered[12]
test_content_type ("application/gzip", "application", "gzip") -- Gzip, Defined in RFC 6713
-- Type audio
-- For Audio.
test_content_type ("audio/basic", "audio", "basic") -- μ-law audio at 8 kHz, 1 channel; Defined in RFC 2046
test_content_type ("audio/L24", "audio", "L24") -- 24bit Linear PCM audio at 848 kHz, 1-N channels; Defined in RFC 3190
test_content_type ("audio/mp4", "audio", "mp4") -- MP4 audio
test_content_type ("audio/mpeg", "audio", "mpeg") -- MP3 or other MPEG audio; Defined in RFC 3003
test_content_type ("audio/ogg", "audio", "ogg") -- Ogg Vorbis, Speex, Flac and other audio; Defined in RFC 5334
test_content_type ("audio/vorbis", "audio", "vorbis") -- Vorbis encoded audio; Defined in RFC 5215
test_content_type ("audio/vnd.rn-realaudio", "audio", "vnd.rn-realaudio") -- RealAudio; Documented in RealPlayer Help[13]
test_content_type ("audio/vnd.wave", "audio", "vnd.wave") -- WAV audio; Defined in RFC 2361
test_content_type ("audio/webm", "audio", "webm") -- WebM open media format
-- Type image
test_content_type ("image/gif", "image", "gif") -- GIF image; Defined in RFC 2045 and RFC 2046
test_content_type ("image/jpeg", "image", "jpeg") -- JPEG JFIF image; Defined in RFC 2045 and RFC 2046
test_content_type ("image/pjpeg", "image", "pjpeg") -- JPEG JFIF image; Associated with Internet Explorer; Listed in ms775147(v=vs.85) - Progressive JPEG, initiated before global browser support for progressive JPEGs (Microsoft and Firefox).
test_content_type ("image/png", "image", "png") -- Portable Network Graphics; Registered,[14] Defined in RFC 2083
test_content_type ("image/svg+xml", "image", "svg+xml") -- SVG vector image; Defined in SVG Tiny 1.2 Specification Appendix M
test_content_type ("image/tiff", "image", "tiff") -- Tag Image File Format (only for Baseline TIFF); Defined in RFC 3302
-- Type message
test_content_type ("message/http", "message", "http") -- Defined in RFC 2616
test_content_type ("message/imdn+xml", "message", "imdn+xml") -- IMDN Instant Message Disposition Notification; Defined in RFC 5438
test_content_type ("message/partial", "message", "partial") -- Email; Defined in RFC 2045 and RFC 2046
test_content_type ("message/rfc822", "message", "rfc822") -- Email; EML files, MIME files, MHT files, MHTML files; Defined in RFC 2045 and RFC 2046
-- Type model
-- For 3D models.
test_content_type ("model/example", "model", "example") -- Defined in RFC 4735
test_content_type ("model/iges", "model", "iges") -- IGS files, IGES files; Defined in RFC 2077
test_content_type ("model/mesh", "model", "mesh") -- MSH files, MESH files; Defined in RFC 2077, SILO files
test_content_type ("model/vrml", "model", "vrml") -- WRL files, VRML files; Defined in RFC 2077
test_content_type ("model/x3d+binary", "model", "x3d+binary") -- X3D ISO standard for representing 3D computer graphics, X3DB binary files
test_content_type ("model/x3d+vrml", "model", "x3d+vrml") -- X3D ISO standard for representing 3D computer graphics, X3DV VRML files
test_content_type ("model/x3d+xml", "model", "x3d+xml") -- X3D ISO standard for representing 3D computer graphics, X3D XML files
-- Type multipart
-- For archives and other objects made of more than one part.
test_content_type ("multipart/mixed", "multipart", "mixed") -- MIME Email; Defined in RFC 2045 and RFC 2046
test_content_type ("multipart/alternative", "multipart", "alternative") -- MIME Email; Defined in RFC 2045 and RFC 2046
test_content_type ("multipart/related", "multipart", "related") -- MIME Email; Defined in RFC 2387 and used by MHTML (HTML mail)
test_content_type ("multipart/form-data", "multipart", "form-data") -- MIME Webform; Defined in RFC 2388
test_content_type ("multipart/signed", "multipart", "signed") -- Defined in RFC 1847
test_content_type ("multipart/encrypted", "multipart", "encrypted") -- Defined in RFC 1847
-- Type text
-- For human-readable text and source code.
test_content_type ("text/cmd", "text", "cmd") -- commands; subtype resident in Gecko browsers like Firefox 3.5
test_content_type ("text/css", "text", "css") -- Cascading Style Sheets; Defined in RFC 2318
test_content_type ("text/csv", "text", "csv") -- Comma-separated values; Defined in RFC 4180
test_content_type ("text/html", "text", "html") -- HTML; Defined in RFC 2854
test_content_type ("text/javascript", "text", "javascript") -- (Obsolete) JavaScript; Defined in and obsoleted by RFC 4329 in order to discourage its usage in favor of application/javascript. However, text/javascript is allowed in HTML 4 and 5 and, unlike application/javascript, has cross-browser support. The "type" attribute of the <script> tag in HTML5 is optional and there is no need to use it at all since all browsers have always assumed the correct default (even in HTML 4 where it was required by the specification).
test_content_type ("text/plain", "text", "plain") -- Textual data; Defined in RFC 2046 and RFC 3676
test_content_type ("text/vcard", "text", "vcard") -- vCard (contact information); Defined in RFC 6350
test_content_type ("text/xml", "text", "xml") -- Extensible Markup Language; Defined in RFC 3023
-- Type video
-- For video.
test_content_type ("video/mpeg", "video", "mpeg") -- MPEG-1 video with multiplexed audio; Defined in RFC 2045 and RFC 2046
test_content_type ("video/mp4", "video", "mp4") -- MP4 video; Defined in RFC 4337
test_content_type ("video/ogg", "video", "ogg") -- Ogg Theora or other video (with audio); Defined in RFC 5334
test_content_type ("video/quicktime", "video", "quicktime") -- QuickTime video; Registered[15]
test_content_type ("video/webm", "video", "webm") -- WebM Matroska-based open media format
test_content_type ("video/x-matroska", "video", "x-matroska") -- Matroska open media format
test_content_type ("video/x-ms-wmv", "video", "x-ms-wmv") -- Windows Media Video; Documented in Microsoft KB 288102
test_content_type ("video/x-flv", "video", "x-flv") -- Flash video (FLV files)
-- List of common media subtype prefixes
-- Prefix vnd
-- For vendor-specific files.
test_content_type ("application/vnd.oasis.opendocument.text", "application", "vnd.oasis.opendocument.text") -- OpenDocument Text; Registered[16]
test_content_type ("application/vnd.oasis.opendocument.spreadsheet", "application", "vnd.oasis.opendocument.spreadsheet") -- OpenDocument Spreadsheet; Registered[17]
test_content_type ("application/vnd.oasis.opendocument.presentation", "application", "vnd.oasis.opendocument.presentation") -- OpenDocument Presentation; Registered[18]
test_content_type ("application/vnd.oasis.opendocument.graphics", "application", "vnd.oasis.opendocument.graphics") -- OpenDocument Graphics; Registered[19]
test_content_type ("application/vnd.ms-excel", "application", "vnd.ms-excel") -- Microsoft Excel files
test_content_type ("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application", "vnd.openxmlformats-officedocument.spreadsheetml.sheet") -- Microsoft Excel 2007 files
test_content_type ("application/vnd.ms-powerpoint", "application", "vnd.ms-powerpoint") -- Microsoft Powerpoint files
test_content_type ("application/vnd.openxmlformats-officedocument.presentationml.presentation", "application", "vnd.openxmlformats-officedocument.presentationml.presentation") -- Microsoft Powerpoint 2007 files
test_content_type ("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application", "vnd.openxmlformats-officedocument.wordprocessingml.document") -- Microsoft Word 2007 files
test_content_type ("application/vnd.mozilla.xul+xml", "application", "vnd.mozilla.xul+xml") -- Mozilla XUL files
test_content_type ("application/vnd.google-earth.kml+xml", "application", "vnd.google-earth.kml+xml") -- KML files (e.g. for Google Earth)[20]
test_content_type ("application/vnd.google-earth.kmz", "application", "vnd.google-earth.kmz") -- KMZ files (e.g. for Google Earth)[21]
test_content_type ("application/vnd.dart", "application", "vnd.dart") -- Dart files [22]
test_content_type ("application/vnd.android.package-archive", "application", "vnd.android.package-archive") -- For download apk files.
test_content_type ("application/vnd.ms-xpsdocument", "application", "vnd.ms-xpsdocument") -- XPS document[23]
-- Prefix x
-- For non-standard files. Deprecated by RFC 6648.[2]
test_content_type ("application/x-7z-compressed", "application", "x-7z-compressed") -- 7-Zip compression format.
test_content_type ("application/x-deb", "application", "x-deb") -- deb (file format), a software package format used by the Debian project
test_content_type ("application/x-dvi", "application", "x-dvi") -- device-independent document in DVI format
test_content_type ("application/x-font-ttf", "application", "x-font-ttf") -- TrueType Font No registered MIME type, but this is the most commonly used
test_content_type ("application/x-javascript", "application", "x-javascript") --
test_content_type ("application/x-latex", "application", "x-latex") -- LaTeX files
test_content_type ("application/x-mpegURL", "application", "x-mpegURL") -- .m3u8 variant playlist
test_content_type ("application/x-rar-compressed", "application", "x-rar-compressed") -- RAR archive files
test_content_type ("application/x-shockwave-flash", "application", "x-shockwave-flash") -- Adobe Flash files for example with the extension .swf
test_content_type ("application/x-stuffit", "application", "x-stuffit") -- StuffIt archive files
test_content_type ("application/x-tar", "application", "x-tar") -- Tarball files
test_content_type ("application/x-www-form-urlencoded", "application", "x-www-form-urlencoded") -- Form Encoded Data; Documented in HTML 4.01 Specification, Section 17.13.4.1
test_content_type ("application/x-xpinstall", "application", "x-xpinstall") -- Add-ons to Mozilla applications (Firefox, Thunderbird, SeaMonkey, and the discontinued Sunbird)
test_content_type ("audio/x-aac", "audio", "x-aac") -- .aac audio files
test_content_type ("audio/x-caf", "audio", "x-caf") -- Apple's CAF audio files
test_content_type ("image/x-xcf", "image", "x-xcf") -- GIMP image file
test_content_type ("text/x-gwt-rpc", "text", "x-gwt-rpc") -- GoogleWebToolkit data
test_content_type ("text/x-jquery-tmpl", "text", "x-jquery-tmpl") -- jQuery template data
test_content_type ("text/x-markdown", "text", "x-markdown") -- Markdown formatted text
test_content_type ("application/x-pkcs12", "application", "x-pkcs12") -- a variant of PKCS standard files
end
test_http_content_type_multipart
do
test_content_type_with_params ("multipart/mixed; boundary=%"simple boundary%"", "multipart", "mixed", <<["boundary", "simple boundary"]>>)
test_content_type_with_params ("multipart/alternative; boundary=boundary42", "multipart", "alternative", <<["boundary", "boundary42"]>>)
test_content_type_with_params ("multipart/mixed;%N boundary=%"---- main boundary ----%"", "multipart", "mixed", <<["boundary", "---- main boundary ----"]>>)
test_content_type_with_params ("multipart/digest;%N boundary=%"---- next message ----%"", "multipart", "digest", <<["boundary", "---- next message ----"]>>)
end
test_http_content_type_partial
do
-- Content-Type: Message/Partial; number=2; total=3;
-- id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
-- Content-Type: Message/Partial;
-- id="oc=jpbe0M2Yt4s@thumper.bellcore.com";
-- number=2
-- Content-Type: Message/Partial; number=3; total=3;
-- id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
test_content_type_with_params ("Message/Partial; number=2; total=3;%N id=%"oc=jpbe0M2Yt4s@thumper.bellcore.com%"", "Message", "Partial", <<["number", "2"], ["total", "3"], ["id", "oc=jpbe0M2Yt4s@thumper.bellcore.com"]>>)
test_content_type_with_params ("Message/Partial; id=%"oc=jpbe0M2Yt4s@thumper.bellcore.com%";%N number=2", "Message", "Partial", <<["id", "oc=jpbe0M2Yt4s@thumper.bellcore.com"], ["number", "2"] >>)
test_content_type_with_params ("Message/Partial; number=3; total=3; id=%"oc=jpbe0M2Yt4s@thumper.bellcore.com%";%N", "Message", "Partial", <<["number", "3"], ["total", "3"], ["id", "oc=jpbe0M2Yt4s@thumper.bellcore.com"]>>)
assert ("Partial is not implemented", True)
end
feature {NONE} -- Implementation
test_content_type (a_string: READABLE_STRING_8; a_type, a_subtype: READABLE_STRING_8)
local
ct1,ct2: HTTP_CONTENT_TYPE
do
create ct1.make_from_string (a_string)
create ct2.make (a_type, a_subtype)
assert (a_string, ct1.same_as (ct2))
end
test_content_type_with_params (a_string: READABLE_STRING_8; a_type, a_subtype: READABLE_STRING_8; params: detachable ARRAY [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
local
ct1,ct2: HTTP_CONTENT_TYPE
do
create ct1.make_from_string (a_string)
create ct2.make (a_type, a_subtype)
if params /= Void then
across
params as c
loop
ct2.add_parameter (c.item.name, c.item.value)
end
end
assert (a_string, ct1.same_as (ct2))
end
end

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="tests" uuid="0582ACC2-11D8-4FE5-888D-61837BA8F43E">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="tests" uuid="0582ACC2-11D8-4FE5-888D-61837BA8F43E">
<target name="tests">
<root class="AUTOTEST" feature="make"/>
<file_rule>
@@ -7,11 +7,16 @@
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard">
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="standard">
<assertions precondition="true" postcondition="true" check="true"/>
</option>
<setting name="concurrency" value="none"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\http-safe.ecf" readonly="false"/>
<library name="http" location="..\http-safe.ecf" readonly="false">
<option>
<assertions precondition="true" postcondition="true" check="true"/>
</option>
</library>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<tests name="src" location=".\" recursive="true"/>
</target>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="notification_email" uuid="99D9A065-CD45-4E20-9C86-579C8AD42E5E" library_target="notification_email">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="notification_email" uuid="99D9A065-CD45-4E20-9C86-579C8AD42E5E" library_target="notification_email">
<target name="notification_email">
<root all_classes="true"/>
<file_rule>
@@ -13,6 +13,6 @@
<library name="http" location="..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="process" location="$ISE_LIBRARY\library\process\process-safe.ecf"/>
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
<cluster name="src" location="." recursive="true"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -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

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="demo" uuid="DC4D6549-D5F4-4E1A-959A-6BD536737A21" library_target="demo">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="demo" uuid="DC4D6549-D5F4-4E1A-959A-6BD536737A21" library_target="demo">
<target name="demo">
<root class="APPLICATION" feature="make_and_launch"/>
<file_rule>
@@ -7,17 +7,17 @@
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="ewsgi" location="../../../../server/ewsgi/ewsgi-safe.ecf" readonly="false"/>
<library name="ewsgi_nino_connector" location="../../../../server/ewsgi/connectors/nino/nino-safe.ecf" readonly="false"/>
<library name="http" location="../../../../network/protocol/http/http-safe.ecf"/>
<library name="ewsgi" location="..\..\..\..\server\ewsgi\ewsgi-safe.ecf" readonly="false"/>
<library name="ewsgi_nino_connector" location="..\..\..\..\server\ewsgi\connectors\nino\nino-safe.ecf" readonly="false"/>
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="openid" location="..\openid-safe.ecf" readonly="false"/>
<library name="wsf" location="../../../../server/wsf/wsf-safe.ecf" readonly="false"/>
<library name="wsf_nino_connector" location="../../../../server/wsf/connector/nino-safe.ecf" readonly="false"/>
<library name="wsf" location="..\..\..\..\server\wsf\wsf-safe.ecf" readonly="false"/>
<library name="wsf_nino_connector" location="..\..\..\..\server\wsf\connector\nino-safe.ecf" readonly="false"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="openid" uuid="FCDB4F81-31EC-462B-9183-D506E6798C0B" library_target="openid">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="openid" uuid="FCDB4F81-31EC-462B-9183-D506E6798C0B" library_target="openid">
<target name="openid">
<root all_classes="true"/>
<file_rule>

View File

@@ -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

View File

@@ -0,0 +1,235 @@
note
description : "simple application root class"
date : "$Date$"
revision : "$Revision$"
class
DEMO_BASIC
inherit
WSF_DEFAULT_SERVICE
redefine
initialize
end
SHARED_HTML_ENCODER
create
make_and_launch
feature {NONE} -- Initialization
initialize
-- Initialize current service.
do
set_service_option ("port", 9090)
set_service_option ("verbose", True)
end
feature -- Credentials
is_known_login (a_login: READABLE_STRING_GENERAL): BOOLEAN
-- Is `a_login' a known username?
do
Result := valid_credentials.has (a_login)
end
is_valid_credential (a_login: READABLE_STRING_GENERAL; a_password: detachable READABLE_STRING_GENERAL): BOOLEAN
-- Is `a_login:a_password' a valid credential?
do
if
a_password /= Void and
attached valid_credentials.item (a_login) as l_passwd
then
Result := a_password.is_case_insensitive_equal (l_passwd)
end
ensure
Result implies is_known_login (a_login)
end
demo_credential: STRING_32
-- First valid known credential display for demo in dialog.
do
valid_credentials.start
create Result.make_from_string_general (valid_credentials.key_for_iteration)
Result.append_character (':')
Result.append (valid_credentials.item_for_iteration)
end
valid_credentials: STRING_TABLE [READABLE_STRING_32]
-- Password indexed by login.
once
create Result.make_caseless (3)
Result.force ("world", "eiffel")
Result.force ("bar", "foo")
Result.force ("password", "user")
ensure
not Result.is_empty
end
feature -- Basic operations
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
-- <Precursor>
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
l_authenticated_username := auth.login
else
l_invalid_credential := True
end
end
if l_invalid_credential then
handle_unauthorized ("ERROR: Invalid credential", req, res)
else
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_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
page: WSF_HTML_PAGE_RESPONSE
do
create s.make_empty
append_html_header (req, s)
s.append ("<p>The authenticated user is <strong>")
s.append (html_encoder.general_encoded_string (a_username))
s.append ("</strong> ...</p>")
append_html_menu (a_username, req, s)
append_html_logout (a_username, req, s)
append_html_footer (req, s)
create page.make
page.set_body (s)
res.send (page)
end
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 ...<br/>")
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)
-- 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_html_login (req, s)
append_html_menu (Void, req, s)
append_html_footer (req, s)
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.
)
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 ("<p>The current page is " + html_encoder.encoded_string (req.path_info) + "</p>")
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 ("<li><a href=%""+ req.absolute_script_url ("") +"%">Your account</a> (displayed only is user is authenticated!)</li>")
end
s.append ("<li><a href=%""+ req.absolute_script_url ("") +"%">home</a></li>")
s.append ("<li><a href=%""+ req.script_url ("/public/area") +"%">public area</a></li>")
s.append ("<li><a href=%""+ req.script_url ("/protected/area") +"%">protected area</a></li>")
end
append_html_login (req: WSF_REQUEST; s: STRING)
-- Append login link to `s'.
do
s.append ("<li><a href=%""+ req.script_url ("/login") +"%">sign in</a></li>")
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 ("<li><a href=%""+ l_logout_url +"%">logout</a></li>")
end
append_html_footer (req: WSF_REQUEST; s: STRING)
-- Append html footer to `s'.
local
hauth: HTTP_AUTHORIZATION
do
s.append ("<hr/>")
if attached req.http_authorization as l_http_authorization then
s.append ("Has <em>Authorization:</em> header: ")
create hauth.make (req.http_authorization)
if attached hauth.login as l_login then
s.append (" login=<strong>" + html_encoder.encoded_string (l_login)+ "</strong>")
end
if attached hauth.password as l_password then
s.append (" password=<strong>" + html_encoder.encoded_string (l_password)+ "</strong>")
end
s.append ("<br/>")
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 ("<pre>")
s.append (l_header)
s.append ("</pre>")
end
end
end

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="basic" uuid="9CB82EB8-CA38-44CD-9CC8-EBC56401BBB2">
<target name="basic">
<root class="DEMO_BASIC" feature="make_and_launch"/>
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="http_auth" location="..\http_authorization-safe.ecf"/>
<library name="encoders" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
<library name="wsf" location="..\..\..\wsf\wsf-safe.ecf"/>
<library name="default_nino" location="..\..\..\wsf\default\nino-safe.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="http_authorization" uuid="321674DB-CE7C-417C-ADE8-64CFA376CD3E" library_target="http_authorization">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="http_authorization" uuid="321674DB-CE7C-417C-ADE8-64CFA376CD3E" library_target="http_authorization">
<target name="http_authorization">
<root all_classes="true"/>
<file_rule>

View File

@@ -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

View File

@@ -1,8 +1,12 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
description : "[
Object representing Authorization http header
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=RFC2617 HTTP Authentication: Basic and Digest Access Authentication", "protocol=URI", "src=http://tools.ietf.org/html/rfc2617"
EIS: "name=Wikipedia Basic Access Authentication", "protocol=URI", "src=http://en.wikipedia.org/wiki/Basic_access_authentication"
EIS: "name=Wikipedia Digest Access Authentication", "protocol=URI", "src=http://en.wikipedia.org/wiki/Digest_access_authentication"
class
HTTP_AUTHORIZATION
@@ -10,101 +14,152 @@ class
inherit
REFACTORING_HELPER
DEBUG_OUTPUT
create
make,
make_basic_auth,
make_custom_auth
feature {NONE} -- Initialization
feature -- Initialization
make (a_http_authorization: detachable READABLE_STRING_GENERAL)
make (a_http_authorization: detachable READABLE_STRING_8)
-- Initialize `Current'.
local
i: INTEGER
t, s: STRING_8
u,p: READABLE_STRING_8
u,p: READABLE_STRING_32
utf: UTF_CONVERTER
do
if a_http_authorization /= Void then
s := a_http_authorization.as_string_8
create http_authorization.make_from_string (s)
if not s.is_empty then
login := Void
password := Void
if a_http_authorization = Void then
-- Default: Basic
type := basic_auth_type
http_authorization := Void
else
create http_authorization.make_from_string (a_http_authorization)
create t.make_empty
type := t
if not a_http_authorization.is_empty then
i := 1
if s[i] = ' ' then
if a_http_authorization[i] = ' ' then
i := i + 1
end
i := s.index_of (' ', i)
i := a_http_authorization.index_of (' ', i)
if i > 0 then
t := s.substring (1, i - 1).as_lower
t.append (a_http_authorization.substring (1, i - 1))
t.right_adjust; t.left_adjust
type := t
if t.same_string ("basic") then
s := (create {BASE64}).decoded_string (s.substring (i + 1, s.count))
if t.same_string (Basic_auth_type) then
type := Basic_auth_type
s := (create {BASE64}).decoded_string (a_http_authorization.substring (i + 1, a_http_authorization.count))
i := s.index_of (':', 1) --| Let's assume ':' is forbidden in login ...
if i > 0 then
u := s.substring (1, i - 1).as_string_32
p := s.substring (i + 1, s.count).as_string_32
u := utf.utf_8_string_8_to_string_32 (s.substring (1, i - 1)) -- UTF_8 decoding to support unicode password
p := utf.utf_8_string_8_to_string_32 (s.substring (i + 1, s.count)) -- UTF_8 decoding to support unicode password
login := u
password := p
check
(create {HTTP_AUTHORIZATION}.make_custom_auth (u, p, t)).http_authorization ~ http_authorization
end
end
elseif t.same_string ("digest") then
elseif t.same_string (Digest_auth_type) then
type := Digest_auth_type
to_implement ("HTTP Authorization %"digest%", not yet implemented")
else
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
end
end
end
else
http_authorization := Void
end
ensure
a_http_authorization /= Void implies http_authorization /= Void
end
make_basic_auth (u: READABLE_STRING_32; p: READABLE_STRING_32)
-- Create a Basic authentication.
do
make_custom_auth (u, p, "basic")
make_custom_auth (u, p, Basic_auth_type)
end
make_custom_auth (u: READABLE_STRING_32; p: READABLE_STRING_32; a_type: READABLE_STRING_8)
-- Create a custom `a_type' authentication.
require
a_type_accepted: a_type.is_case_insensitive_equal (Basic_auth_type)
or a_type.is_case_insensitive_equal (Digest_auth_type)
local
t: STRING_8
utf: UTF_CONVERTER
do
login := u
password := p
create t.make_from_string (a_type.as_lower)
create t.make_from_string (a_type)
t.left_adjust; t.right_adjust
type := t
if t.same_string ("basic") then
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (u + ":" + p))
if t.is_case_insensitive_equal (Basic_auth_type) then
type := Basic_auth_type
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (utf.string_32_to_utf_8_string_8 (u + {STRING_32} ":" + p)))
elseif t.is_case_insensitive_equal (Digest_auth_type) then
type := Digest_auth_type
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
create http_authorization.make_from_string (t + " ...NOT IMPLEMENTED")
else
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
create http_authorization.make_from_string ("Digest ...NOT IMPLEMENTED")
end
end
feature -- Access
type: detachable READABLE_STRING_8
http_authorization: detachable IMMUTABLE_STRING_8
type: READABLE_STRING_8
login: detachable READABLE_STRING_32
password: detachable READABLE_STRING_32
http_authorization: detachable IMMUTABLE_STRING_8
feature -- Status report
is_basic: BOOLEAN
-- Is Basic authorization?
do
if attached type as t then
Result := t.same_string ("basic")
Result := type.is_case_insensitive_equal (Basic_auth_type)
end
is_digest: BOOLEAN
-- Is Basic authorization?
do
Result := type.is_case_insensitive_equal (Digest_auth_type)
end
debug_output: STRING_32
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_empty
Result.append (type)
Result.append (" ")
if attached login as l_login then
Result.append ("login=[")
Result.append (l_login)
Result.append ("] ")
end
if attached password as l_password then
Result.append ("password=[")
Result.append (l_password)
Result.append ("] ")
end
end
feature -- Constants
Basic_auth_type: STRING_8 = "Basic"
Digest_auth_type: STRING_8 = "Digest"
invariant
type_is_lower: attached type as t implies t.same_string (t.as_lower)
type_valid: (type.is_case_insensitive_equal (basic_auth_type) implies type = basic_auth_type)
or (type.is_case_insensitive_equal (Digest_auth_type) implies type = Digest_auth_type)
end

View File

@@ -0,0 +1,57 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
HTTP_AUTHORIZATION_TESTS
inherit
EQA_TEST_SET
feature -- Test routines
test_basic
-- New test routine
local
l_auth: READABLE_STRING_8
u,p: detachable READABLE_STRING_32
h: HTTP_AUTHORIZATION
do
l_auth := "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
create h.make (l_auth)
assert ("login", attached h.login as l_login and then l_login.same_string ("Aladdin"))
assert ("password", attached h.password as l_password and then l_password.same_string ("open sesame"))
check_basic_auth_for_login_password ("Aladdin", "open sesame", "Aladdin")
check_basic_auth_for_login_password ("", "", "empty")
check_basic_auth_for_login_password ("@#$@%%@??<.,.,", "@@#$&)&*>M<?>:ASDFDSA''", "symbol")
check_basic_auth_for_login_password ({STRING_32} "%/20320/%/22909/%/21527/", {STRING_32}"%/20320/%/22909/%/21527/", "unicode")
check_basic_auth_for_login_password (create {STRING_32}.make_filled ('u', 100) + {STRING_32}"%/20320/%/22909/%/21527/", create {STRING_32}.make_filled ('p', 100) + {STRING_32}"%/20320/%/22909/%/21527/", "long unicode")
end
feature -- Impl
check_basic_auth_for_login_password (u,p: READABLE_STRING_32; a_title: READABLE_STRING_8)
local
h: HTTP_AUTHORIZATION
l_auth: detachable READABLE_STRING_8
do
create h.make_basic_auth (u, p)
assert (a_title + ":login", attached h.login as l_login and then l_login.same_string (u))
assert (a_title + ":password", attached h.password as l_password and then l_password.same_string (p))
l_auth := h.http_authorization
create h.make (l_auth)
assert (a_title + ":basic", h.type.is_case_insensitive_equal ("Basic"))
assert (a_title + ":login", attached h.login as l_login and then l_login.same_string (u))
assert (a_title + ":password", attached h.password as l_password and then l_password.same_string (p))
end
end

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="testing" uuid="1AEF36BD-FB72-4B52-8845-4EF4AC7B709A">
<target name="testing">
<root class="ANY" feature="default_create"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="transitional" syntax="standard">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="none"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http_authorization" location="..\http_authorization-safe.ecf" readonly="false" use_application_options="true"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<tests name="tests" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="connector_cgi" uuid="3BCBC1C5-9D99-45BB-B15D-B03D2C069CED" library_target="connector_cgi">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="connector_cgi" uuid="3BCBC1C5-9D99-45BB-B15D-B03D2C069CED" library_target="connector_cgi">
<target name="connector_cgi">
<root all_classes="true"/>
<file_rule>

View File

@@ -62,6 +62,7 @@ feature -- Execution
rescued: BOOLEAN
do
if not rescued then
a_input.reset
create req.make (vars, a_input, Current)
create res.make (a_output, a_output)
service.execute (req, res)

View File

@@ -32,6 +32,21 @@ feature {NONE} -- Initialization
create last_string.make_empty
end
feature -- Reset
reset
-- Reset current input stream
-- especially any previous error if any.
do
debug ("wsf")
io.error.put_string (generator + " : reset %N")
end
last_string.wipe_out
last_character := '%U'
internal_end_of_input := False
fcgi.fcgi_clearerr
end
feature -- Status report
is_open_read: BOOLEAN
@@ -43,9 +58,11 @@ feature -- Status report
end_of_input: BOOLEAN
-- Has the end of input stream been reached?
do
Result := fcgi.fcgi_end_of_input
Result := fcgi.fcgi_end_of_input or internal_end_of_input
end
internal_end_of_input: BOOLEAN
feature -- Input
read_character
@@ -59,6 +76,7 @@ feature -- Input
if s.count >= 1 then
last_character := s.item (1)
else
internal_end_of_input := True
last_character := '%U'
end
end
@@ -69,6 +87,7 @@ feature -- Input
-- Make result available in `last_string'.
do
fcgi.fill_string_from_stdin (last_string, nb_char)
internal_end_of_input := last_string.count < nb_char
end
feature -- Access
@@ -86,7 +105,7 @@ feature {NONE} -- Implementation
-- Bridge to FCGI world
note
copyright: "2011-2011, Eiffel Software and others"
copyright: "2011-2013, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="connector_nino" uuid="F91861FB-4FEA-455F-9570-828D7903DC64" library_target="connector_nino">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="connector_nino" uuid="F91861FB-4FEA-455F-9570-828D7903DC64" library_target="connector_nino">
<target name="connector_nino">
<root all_classes="true"/>
<file_rule>
@@ -7,12 +7,13 @@
<exclude>/\.git$</exclude>
<exclude>/\.svn$</exclude>
</file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="encoder" location="..\..\..\..\text\encoder\encoder-safe.ecf"/>
<library name="ewsgi" location="..\..\ewsgi-safe.ecf" readonly="false"/>
<library name="http" location="../../../../network/protocol/http/http-safe.ecf"/>
<library name="nino" location="../../../../../contrib/library/network/server/nino/nino-safe.ecf" readonly="false">
<library name="http" location="..\..\..\..\network\protocol\http\http-safe.ecf"/>
<library name="nino" location="..\..\..\..\..\contrib\library\network\server\nino\nino-safe.ecf" readonly="false">
<renaming old_name="HTTP_CONSTANTS" new_name="NINO_HTTP_CONSTANTS"/>
</library>
<cluster name="src" location=".\src\" recursive="true"/>

View File

@@ -10,6 +10,7 @@
<option warning="true" full_class_checking="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="../../../../text/encoder/encoder.ecf"/>
<library name="ewsgi" location="..\..\ewsgi.ecf" readonly="false"/>
<library name="http" location="../../../../network/protocol/http/http.ecf"/>
<library name="nino" location="../../../../../contrib/library/network/server/nino/nino.ecf" readonly="false">

View File

@@ -28,13 +28,13 @@ feature {NONE} -- Implementation
create connector.make_with_base (a_service, a_base_url)
end
make_with_callback (a_callback: like {WGI_AGENT_SERVICE}.callback)
make_with_callback (a_callback: PROCEDURE [ANY, TUPLE [req: WGI_REQUEST; res: WGI_RESPONSE]])
-- Initialize `Current'.
do
make_custom_with_callback (a_callback, Void)
end
make_custom_with_callback (a_callback: like {WGI_AGENT_SERVICE}.callback; a_base_url: detachable STRING)
make_custom_with_callback (a_callback: PROCEDURE [ANY, TUPLE [req: WGI_REQUEST; res: WGI_RESPONSE]]; a_base_url: detachable STRING)
-- Initialize `Current'.
require
base_url_starts_with_slash: (a_base_url /= Void and then not a_base_url.is_empty) implies a_base_url.starts_with ("/")
@@ -104,7 +104,7 @@ feature -- Server
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -69,16 +69,18 @@ feature -- Request processing
vn: STRING
e: EXECUTION_ENVIRONMENT
enc: URL_ENCODER
do
l_request_uri := a_handler.uri
l_headers_map := a_handler.request_header_map
create e
if attached e.starting_environment_variables as vars then
if attached e.starting_environment as vars then
create enc
create env.make_equal (vars.count)
across
vars as c
loop
env.force (c.item.to_string_8, c.key)
env.force (enc.encoded_string (c.item), enc.encoded_string (c.key))
end
else
create env.make (0)

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="ewsgi" uuid="D924DBE1-1231-434A-80EF-234BA09D1E30" library_target="ewsgi">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="ewsgi" uuid="D924DBE1-1231-434A-80EF-234BA09D1E30" library_target="ewsgi">
<target name="ewsgi">
<root all_classes="true"/>
<file_rule>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-11-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-11-0 http://www.eiffel.com/developers/xml/configuration-1-11-0.xsd" name="ewsgi_spec" uuid="AA193B9F-02FD-47B9-B60D-C42B9AB35E1C" library_target="ewsgi_spec">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="ewsgi_spec" uuid="AA193B9F-02FD-47B9-B60D-C42B9AB35E1C" library_target="ewsgi_spec">
<target name="ewsgi_spec">
<root all_classes="true"/>
<file_rule>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="hello_world" uuid="734385F1-0D17-4B5F-9138-24DC8D4F06C6" library_target="hello_world">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="hello_world" uuid="734385F1-0D17-4B5F-9138-24DC8D4F06C6" library_target="hello_world">
<target name="hello_world">
<root class="HELLO_WORLD" feature="make"/>
<file_rule>

View File

@@ -20,8 +20,9 @@ feature {NONE} -- Initialization
res.set_post_commit_action (agent commit)
end
wgi_response: WGI_RESPONSE
feature -- Access
wgi_response: WGI_RESPONSE
feature {WGI_FILTER_RESPONSE} -- Change

View File

@@ -9,6 +9,9 @@ class
inherit
WGI_INPUT_STREAM
redefine
last_character_available
end
create
make
@@ -36,11 +39,17 @@ feature -- Input
index := index + 1
if index > chunk_upper then
read_chunk_block
if last_chunk_data = Void then
if
last_chunk_size = 0
then
read_trailer_and_crlf
last_character := '%U'
else
last_character := last_chunk_data.item (index)
end
else
last_character := last_chunk_data.item (index)
end
last_character := last_chunk_data.item (index)
end
read_string (nb: INTEGER)
@@ -54,7 +63,7 @@ feature -- Input
i: like index
do
last_string.wipe_out
if last_trailer /= Void then
if is_trailer_reached then
-- trailer already reached, no more data
check input.end_of_input end
else
@@ -103,6 +112,12 @@ feature -- Access
last_character: CHARACTER_8
-- Last item read.
last_character_available: BOOLEAN
-- <Precursor>
do
Result := not is_trailer_reached
end
feature -- Access: chunk
last_chunk_size: INTEGER
@@ -142,7 +157,13 @@ feature -- Status report
end_of_input: BOOLEAN
-- Has the end of input stream been reached?
do
Result := input.end_of_input
Result := input.end_of_input or is_trailer_reached
end
is_trailer_reached: BOOLEAN
-- Trailer reached?
do
Result := last_trailer /= Void
end
feature {NONE} -- Parser
@@ -320,11 +341,7 @@ feature {NONE} -- Parser
check l_input.last_character = '%N' end
end
end
if s.is_empty then
last_trailer := Void
else
last_trailer := s
end
last_trailer := s
end
feature {NONE} -- Implementation
@@ -333,7 +350,7 @@ feature {NONE} -- Implementation
-- Input Stream
;note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -62,14 +62,19 @@ feature -- Input
i > end_pos
loop
read_character
a_string.put (last_character, i)
if end_of_input then
Result := i
if last_character_available then
a_string.put (last_character, i)
if end_of_input then
Result := i
-- Jump out of the loop.
i := end_pos + 1
else
i := i + 1
end
else
-- reached end of input
-- Jump out of the loop.
i := end_pos + 1
else
i := i + 1
end
end
if not end_of_input then
@@ -107,13 +112,20 @@ feature -- Input
i > end_pos
loop
read_character
a_string.extend (last_character)
l_count := l_count + 1
if end_of_input then
if last_character_available then
a_string.extend (last_character)
l_count := l_count + 1
if end_of_input then
-- Jump out of the loop.
i := end_pos + 1
else
i := i + 1
end
else
-- reached end of input
-- Jump out of the loop.
i := end_pos + 1
else
i := i + 1
end
end
last_appended_count := l_count
@@ -194,6 +206,15 @@ feature -- Access
deferred
end
last_character_available: BOOLEAN
-- Is `last_character' available? i.e read?
--| with chunked encoding, we may reach the end and the `last_character'
--| should not be used.
--| to redefine in descendant if needed
do
Result := True
end
last_appended_count: INTEGER
-- Count of characters actually read by last `append_to_string' call.

View File

@@ -0,0 +1,3 @@
all::
build_win64.bat

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -68,10 +68,28 @@ feature {FCGI_IMP} -- Internal
feof (v: POINTER): INTEGER
-- FCGI_feof()
-- 0 means EOF not detected.
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_feof"
"return FCGI_feof($v);"
end
ferror (v: POINTER): INTEGER
-- FCGI_ferror()
-- 0 means no error.
external
"C inline use %"fcgi_stdio.h%""
alias
"return FCGI_ferror($v);"
end
clearerr (v: POINTER)
-- FCGI_clearerr().
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_clearerr($v)"
end
feature {NONE} -- Input
@@ -108,27 +126,27 @@ feature -- Error
feature -- Access
stdout: POINTER
-- FCGI_stdout() return pointer on output FCGI_FILE
-- FCGI_stdout return pointer on output FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stdout"
"return FCGI_stdout;"
end
stdin: POINTER
-- FCGI_stdin() return pointer on input FCGI_FILE
-- FCGI_stdin return pointer on input FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stdin"
"return FCGI_stdin;"
end
stderr: POINTER
-- FCGI_stderr() return pointer on error FCGI_FILE
-- FCGI_stderr return pointer on error FCGI_FILE
external
"C inline use %"fcgi_stdio.h%""
alias
"FCGI_stderr"
"return FCGI_stderr;"
end
note

View File

@@ -32,7 +32,12 @@ feature -- Access
fcgi_end_of_input: BOOLEAN
do
Result := fcgi.feof (fcgi.stdin) = 0
Result := fcgi.feof (fcgi.stdin) /= 0 --| in fact, True if feof (..) = EOF
end
fcgi_clearerr
do
fcgi.clearerr (fcgi.stdin)
end
feature -- FCGI Connection
@@ -52,7 +57,7 @@ feature -- FCGI Connection
eif_environ = (char**) environ;
]"
end
fcgi_finish
-- Finish current request from HTTP server started from
-- the most recent call to `fcgi_accept'.
@@ -175,7 +180,7 @@ feature -- I/O Routines
--RFO end
note
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
copyright: "Copyright (c) 1984-2013, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

Some files were not shown because too many files have changed in this diff Show More