Merge branch 'master' of github.com:EiffelWebFramework/EWF into widget_integration
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
15
library/network/http_client/package.iron
Normal file
15
library/network/http_client/package.iron
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
*~
|
||||
EIFGEN* # ignore all files in the EIFGENs/ directory
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
31
library/network/protocol/content_negotiation/README.md
Normal file
31
library/network/protocol/content_negotiation/README.md
Normal 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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
15
library/network/protocol/content_negotiation/package.iron
Normal file
15
library/network/protocol/content_negotiation/package.iron
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
@@ -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>
|
||||
|
||||
15
library/network/protocol/http/package.iron
Normal file
15
library/network/protocol/http/package.iron
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
667
library/network/protocol/http/src/http_header_modifier.e
Normal file
667
library/network/protocol/http/src/http_header_modifier.e
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
150
library/network/protocol/http/src/http_parameter_table.e
Normal file
150
library/network/protocol/http/src/http_parameter_table.e
Normal 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
|
||||
214
library/network/protocol/http/tests/http_content_type_set.e
Normal file
214
library/network/protocol/http/tests/http_content_type_set.e
Normal 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 8–48 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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
15
library/runtime/process/notification_email/package.iron
Normal file
15
library/runtime/process/notification_email/package.iron
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
16
library/security/openid/package.iron
Normal file
16
library/security/openid/package.iron
Normal 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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
3
library/server/libfcgi/Clib/Makefile-win.SH
Normal file
3
library/server/libfcgi/Clib/Makefile-win.SH
Normal file
@@ -0,0 +1,3 @@
|
||||
all::
|
||||
build_win64.bat
|
||||
|
||||
@@ -16,3 +16,4 @@ link.exe %LINK_FLAGS% /DLL %E_libFCGI_OUTDIR%\fcgi_stdio.obj %E_libFCGI_OUTDIR%
|
||||
copy %E_libFCGI_OUTDIR%\libfcgi.* %~dp0..\spec\lib\windows\msc
|
||||
|
||||
endlocal
|
||||
exit 0
|
||||
|
||||
@@ -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
|
||||
|
||||
20
library/server/libfcgi/Clib/build_windows.bat
Normal file
20
library/server/libfcgi/Clib/build_windows.bat
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user