Compare commits
21 Commits
es17.01
...
dev_reques
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b81f690d0c | ||
|
|
2748e1d9ee | ||
|
|
27ee20f99b | ||
|
|
9a3164df70 | ||
|
|
02383810b4 | ||
|
|
dbf5e76047 | ||
|
|
5c31905427 | ||
|
|
c7ef652322 | ||
|
|
7feb45b549 | ||
|
|
5bbd031275 | ||
|
|
90e60fad26 | ||
|
|
98c20ee7c1 | ||
|
|
0b99e84728 | ||
|
|
9e5e8bb1bf | ||
|
|
10a83c6ad8 | ||
|
|
1ec3b8e7a4 | ||
|
|
0783049fb4 | ||
|
|
7e54825b84 | ||
|
|
40cbe7dfc9 | ||
|
|
d4b9301a57 | ||
|
|
06cda97535 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -7,20 +7,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- jwt: new JSON Web Token (JWT) library (supports for claim exp, iat, nbf, iss, aud).
|
||||
|
||||
### Changed
|
||||
- http_network:
|
||||
Integrated changes on SOCKET so that EiffelWeb can be compiled with 16.05 to 17.05 and after.
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
- http_client:
|
||||
Improved query and form data encoding (based on a very early version of the general URI percent-encoding rules).
|
||||
- now correct encoding of space by '%20' in path segment, and '+' in query parameters.
|
||||
Unify and fixed query parameters handling for libcurl and net implementation.
|
||||
Fixed file uploading (various issue in libcurl, and net implementation).
|
||||
Fixed form multipart encoding by using correctly the boundary.
|
||||
- Code cleaning:
|
||||
Removed many obsolete calls, and added timestamp on EiffelWeb obsolete features to benefit from upcoming improvement on the EiffelStudio Inspector tool.
|
||||
- Removed a few obsolete calls.
|
||||
- `http_client`: Added support for multiple file in form data. Made clear what is the meaning of `upload_filename`, `upload_data` and `form_data`.
|
||||
### Security
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
|
||||
<target name="web_server">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option debug="true" warning="true" void_safety="all">
|
||||
<debug name="nino" enabled="true"/>
|
||||
<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="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="nino" location="..\..\nino-safe.ecf"/>
|
||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
|
||||
<target name="web_server">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option debug="true" warning="true" void_safety="all">
|
||||
<debug name="nino" enabled="true"/>
|
||||
<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="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="nino" location="..\..\nino-safe.ecf"/>
|
||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
|
||||
<target name="web_server">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="none">
|
||||
<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.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="nino" location="..\..\nino.ecf"/>
|
||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
|
||||
<target name="web_server">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="none">
|
||||
<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.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="nino" location="..\..\nino.ecf"/>
|
||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<condition>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<condition>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||
</cluster>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||
</cluster>
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
|
||||
<cluster name="parameters" location="$|parameters" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||
</cluster>
|
||||
|
||||
@@ -16,8 +16,19 @@ feature -- Access
|
||||
deferred
|
||||
end
|
||||
|
||||
get (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := new_session (a_url).get ("", ctx)
|
||||
end
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for `a_method' request based on `a_url' and optional `ctx'.
|
||||
do
|
||||
Result := new_session (a_url).custom (a_method, "", ctx)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -58,11 +58,11 @@ feature -- Access
|
||||
-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
|
||||
--| note: the value from Current context override the one from the session in case of conflict
|
||||
|
||||
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
query_parameters: HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
|
||||
-- Query parameters to be appended to the url
|
||||
--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url
|
||||
|
||||
form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
form_parameters: HTTP_CLIENT_REQUEST_FORM_PARAMETERS
|
||||
-- Form parameters
|
||||
|
||||
upload_data: detachable READABLE_STRING_8
|
||||
@@ -145,13 +145,25 @@ feature -- Element change
|
||||
add_query_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
|
||||
-- Add a query parameter `k=v'.
|
||||
do
|
||||
query_parameters.force (v.to_string_32, k.to_string_32)
|
||||
query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
|
||||
end
|
||||
|
||||
add_form_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
|
||||
-- Add a form parameter `k'= `v'.
|
||||
do
|
||||
form_parameters.force (v.to_string_32, k.to_string_32)
|
||||
form_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
|
||||
end
|
||||
|
||||
add_file_form_parameter (k: READABLE_STRING_GENERAL; a_location: READABLE_STRING_GENERAL; a_content_type: detachable READABLE_STRING_8)
|
||||
-- Add a form file parameter named `k`, located at `a_location`, with optional content type `a_content_type`.
|
||||
require
|
||||
has_no_upload_data_or_filename: not has_upload_data and not has_upload_filename
|
||||
local
|
||||
param: HTTP_CLIENT_REQUEST_FILE_PARAMETER
|
||||
do
|
||||
create param.make_with_path (k, create {PATH}.make_from_string (a_location))
|
||||
param.set_content_type (a_content_type)
|
||||
form_parameters.force (param)
|
||||
end
|
||||
|
||||
set_credentials_required (b: BOOLEAN)
|
||||
@@ -164,7 +176,8 @@ feature -- Element change
|
||||
-- Set `upload_data' to `a_data'
|
||||
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
||||
require
|
||||
has_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
|
||||
has_no_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
|
||||
has_no_form_data: (a_data /= Void and then not a_data.is_empty) implies not has_form_data
|
||||
do
|
||||
if a_data = Void or else a_data.is_empty then
|
||||
upload_data := Void
|
||||
@@ -180,6 +193,7 @@ feature -- Element change
|
||||
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
||||
require
|
||||
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
|
||||
has_no_form_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_form_data
|
||||
do
|
||||
if a_fn = Void or else a_fn.is_empty then
|
||||
upload_filename := Void
|
||||
@@ -266,9 +280,9 @@ feature -- URL helpers
|
||||
a_url.append_character ('&')
|
||||
end
|
||||
l_first_param := False
|
||||
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, a_url)
|
||||
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, a_url)
|
||||
a_url.append_character ('=')
|
||||
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, a_url)
|
||||
ic.item.append_query_value_encoded_to (a_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -315,38 +329,35 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
end
|
||||
|
||||
parameters_to_uri_percent_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
|
||||
-- Build query urlencoded string using parameters from `ht'.
|
||||
parameters_to_uri_percent_encoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
|
||||
-- Build query urlencoded string using parameters from `a_params'.
|
||||
do
|
||||
create Result.make (64)
|
||||
across
|
||||
ht as ic
|
||||
a_params as ic
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character ('&')
|
||||
end
|
||||
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, Result)
|
||||
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, Result)
|
||||
Result.append_character ('=')
|
||||
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, Result)
|
||||
ic.item.append_query_value_encoded_to (Result)
|
||||
end
|
||||
end
|
||||
|
||||
parameters_to_x_www_form_urlencoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
|
||||
-- Build x-www-form-urlencoded string using parameters from `ht'.
|
||||
parameters_to_x_www_form_urlencoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
|
||||
-- Build x-www-form-urlencoded string using parameters from `a_params'.
|
||||
do
|
||||
create Result.make (64)
|
||||
from
|
||||
ht.start
|
||||
until
|
||||
ht.after
|
||||
across
|
||||
a_params as ic
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character ('&')
|
||||
end
|
||||
Result.append (x_www_form_url_encoder.encoded_string (ht.key_for_iteration))
|
||||
x_www_form_url_encoder.append_percent_encoded_string_to (ic.item.name, Result)
|
||||
Result.append_character ('=')
|
||||
Result.append (x_www_form_url_encoder.encoded_string (ht.item_for_iteration))
|
||||
ht.forth
|
||||
ic.item.append_form_url_encoded_to (Result)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_FORM_PARAMETERS}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_REQUEST_FORM_PARAMETERS
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Status report
|
||||
|
||||
has_file_parameter: BOOLEAN
|
||||
-- Has any file parameter?
|
||||
do
|
||||
Result := across items as ic some attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -0,0 +1,71 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_PARAMETER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT_REQUEST_PARAMETER
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: READABLE_STRING_32
|
||||
|
||||
content_type: detachable READABLE_STRING_8
|
||||
|
||||
count: INTEGER
|
||||
-- Integer representing the length of source value.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
append_form_url_encoded_to (a_output: STRING_8)
|
||||
-- Append as form url encoded string to `a_output`.
|
||||
deferred
|
||||
end
|
||||
|
||||
append_query_value_encoded_to (a_output: STRING_8)
|
||||
deferred
|
||||
end
|
||||
|
||||
append_as_mime_encoded_to (a_output: STRING_8)
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_name (a_name: READABLE_STRING_GENERAL)
|
||||
do
|
||||
name := a_name.as_string_32
|
||||
end
|
||||
|
||||
set_content_type (ct: detachable READABLE_STRING_8)
|
||||
do
|
||||
content_type := ct
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
x_www_form_url_encoder: X_WWW_FORM_URL_ENCODER
|
||||
-- Shared x-www-form-urlencoded encoder.
|
||||
once
|
||||
create Result
|
||||
end
|
||||
|
||||
uri_percent_encoder: URI_PERCENT_ENCODER
|
||||
once
|
||||
create Result
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -0,0 +1,62 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_PARAMETERS}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT_REQUEST_PARAMETERS [G -> HTTP_CLIENT_REQUEST_PARAMETER]
|
||||
|
||||
inherit
|
||||
ITERABLE [G]
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (nb: INTEGER)
|
||||
do
|
||||
create items.make (nb)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
is_empty: BOOLEAN
|
||||
do
|
||||
Result := items.is_empty
|
||||
end
|
||||
|
||||
count: INTEGER
|
||||
do
|
||||
Result := items.count
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
extend, force (i: G)
|
||||
do
|
||||
items.force (i)
|
||||
end
|
||||
|
||||
feature -- Iteration
|
||||
|
||||
new_cursor: ARRAYED_LIST_ITERATION_CURSOR [G]
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := items.new_cursor
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
items: ARRAYED_LIST [G]
|
||||
|
||||
invariant
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -0,0 +1,26 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_QUERY_PARAMETERS}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_STRING_PARAMETER]
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -0,0 +1,155 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_FILE_PARAMETER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_REQUEST_FILE_PARAMETER
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST_PARAMETER
|
||||
|
||||
create
|
||||
make_with_path
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_path (a_name: READABLE_STRING_GENERAL; a_path: PATH)
|
||||
do
|
||||
set_name (a_name)
|
||||
location := a_path
|
||||
if attached a_path.entry as e then
|
||||
file_name := e.name
|
||||
end
|
||||
set_content_type ("application/octet-stream") -- Default
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
count: INTEGER
|
||||
local
|
||||
f: RAW_FILE
|
||||
do
|
||||
create f.make_with_path (location)
|
||||
if f.exists and then f.is_access_readable then
|
||||
Result := f.count
|
||||
end
|
||||
end
|
||||
|
||||
location: PATH
|
||||
|
||||
file_name: detachable READABLE_STRING_32
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_file_name (fn: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if fn = Void then
|
||||
file_name := Void
|
||||
else
|
||||
file_name := fn.to_string_32
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
exists: BOOLEAN
|
||||
local
|
||||
fut: FILE_UTILITIES
|
||||
do
|
||||
Result := fut.file_path_exists (location)
|
||||
end
|
||||
|
||||
feature {NONE} -- Data
|
||||
|
||||
file_content: detachable STRING_8
|
||||
require
|
||||
exists: exists
|
||||
local
|
||||
f: RAW_FILE
|
||||
do
|
||||
create f.make_with_path (location)
|
||||
if f.exists and then f.is_access_readable then
|
||||
create Result.make (f.count)
|
||||
f.open_read
|
||||
from
|
||||
until
|
||||
f.exhausted or f.end_of_file
|
||||
loop
|
||||
f.read_stream_thread_aware (2_048)
|
||||
Result.append (f.last_string)
|
||||
end
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Data
|
||||
|
||||
append_file_content_to (a_output: STRING)
|
||||
-- Append content of file located at `location`to `a_output'.
|
||||
require
|
||||
exists: exists
|
||||
local
|
||||
f: RAW_FILE
|
||||
l_buffer_size: INTEGER
|
||||
do
|
||||
create f.make_with_path (location)
|
||||
if f.exists and then f.is_access_readable then
|
||||
f.open_read
|
||||
from
|
||||
l_buffer_size := 2_048
|
||||
until
|
||||
f.exhausted or f.end_of_file
|
||||
loop
|
||||
f.read_stream_thread_aware (l_buffer_size)
|
||||
a_output.append (f.last_string)
|
||||
end
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
append_form_url_encoded_to (a_output: STRING_8)
|
||||
-- Append as form url encoded string to `a_output`.
|
||||
do
|
||||
if exists and then attached file_content as s then
|
||||
x_www_form_url_encoder.append_percent_encoded_string_to (s, a_output)
|
||||
else
|
||||
check exists: False end
|
||||
end
|
||||
end
|
||||
|
||||
append_query_value_encoded_to (a_output: STRING_8)
|
||||
do
|
||||
if exists and then attached file_content as s then
|
||||
uri_percent_encoder.append_query_value_encoded_string_to (s, a_output)
|
||||
else
|
||||
check exists: False end
|
||||
end
|
||||
end
|
||||
|
||||
append_as_mime_encoded_to (a_output: STRING_8)
|
||||
-- Encoded unicode string for mime value.
|
||||
-- For instance uploaded filename, or form data key or values.
|
||||
do
|
||||
-- FIXME: find the proper encoding!
|
||||
if exists then
|
||||
append_file_content_to (a_output)
|
||||
else
|
||||
check exists: False end
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -0,0 +1,68 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_STRING_PARAMETER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_REQUEST_STRING_PARAMETER
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST_PARAMETER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name, a_value: READABLE_STRING_GENERAL)
|
||||
do
|
||||
set_name (a_name)
|
||||
value := a_value.as_string_32
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
value: READABLE_STRING_32
|
||||
|
||||
count: INTEGER
|
||||
do
|
||||
Result := value.count
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
append_form_url_encoded_to (a_output: STRING_8)
|
||||
-- Append as form url encoded string to `a_output`.
|
||||
do
|
||||
x_www_form_url_encoder.append_percent_encoded_string_to (value, a_output)
|
||||
end
|
||||
|
||||
append_query_value_encoded_to (a_output: STRING_8)
|
||||
do
|
||||
uri_percent_encoder.append_query_value_encoded_string_to (value, a_output)
|
||||
end
|
||||
|
||||
append_as_mime_encoded_to (a_output: STRING_8)
|
||||
-- Encoded unicode string for mime value.
|
||||
-- For instance uploaded filename, or form data key or values.
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
-- FIXME: find the proper encoding!
|
||||
utf.utf_32_string_into_utf_8_string_8 (value, a_output)
|
||||
end
|
||||
|
||||
invariant
|
||||
|
||||
note
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, 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
|
||||
@@ -58,7 +58,6 @@ feature -- Execution
|
||||
ctx: like context
|
||||
p_slist: POINTER
|
||||
retried: BOOLEAN
|
||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
l_upload_data: detachable READABLE_STRING_8
|
||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||
l_headers: like headers
|
||||
@@ -151,70 +150,19 @@ feature -- Execution
|
||||
--| Credentials not provided ...
|
||||
end
|
||||
end
|
||||
|
||||
if ctx.has_upload_data then
|
||||
l_upload_data := ctx.upload_data
|
||||
end
|
||||
if ctx.has_upload_filename then
|
||||
l_upload_filename := ctx.upload_filename
|
||||
end
|
||||
if ctx.has_form_data then
|
||||
l_form_data := ctx.form_parameters
|
||||
check non_empty_form_data: not l_form_data.is_empty end
|
||||
if l_upload_data = Void and l_upload_filename = Void then
|
||||
-- Send as form-urlencoded
|
||||
if
|
||||
attached l_headers.item ("Content-Type") as l_ct
|
||||
then
|
||||
if l_ct.starts_with ("application/x-www-form-urlencoded") then
|
||||
-- Content-Type is already application/x-www-form-urlencoded
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
elseif l_ct.starts_with ("multipart/form-data") then
|
||||
l_use_curl_form := True
|
||||
else
|
||||
-- Not supported, use libcurl form.
|
||||
l_use_curl_form := True
|
||||
end
|
||||
else
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
end
|
||||
else
|
||||
l_use_curl_form := True
|
||||
end
|
||||
if l_use_curl_form then
|
||||
create l_form.make
|
||||
create l_last.make
|
||||
from
|
||||
l_form_data.start
|
||||
until
|
||||
l_form_data.after
|
||||
loop
|
||||
curl.formadd_string_string (l_form, l_last,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copyname, l_form_data.key_for_iteration,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copycontents, l_form_data.item_for_iteration,
|
||||
{CURL_FORM_CONSTANTS}.curlform_end
|
||||
)
|
||||
l_form_data.forth
|
||||
end
|
||||
if l_upload_filename /= Void then
|
||||
curl.formadd_string_string (l_form, l_last,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
|
||||
{CURL_FORM_CONSTANTS}.curlform_file, l_upload_filename,
|
||||
{CURL_FORM_CONSTANTS}.curlform_end
|
||||
)
|
||||
l_upload_filename := Void
|
||||
end
|
||||
l_last.release_item
|
||||
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
|
||||
end
|
||||
end
|
||||
|
||||
if l_upload_data /= Void then
|
||||
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
|
||||
check no_form_data: not ctx.has_form_data end
|
||||
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
|
||||
@@ -224,6 +172,7 @@ feature -- Execution
|
||||
or request_method.is_case_insensitive_equal ("PUT")
|
||||
or request_method.is_case_insensitive_equal ("PATCH")
|
||||
end
|
||||
check no_form_data: not ctx.has_form_data end
|
||||
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||
@@ -238,12 +187,59 @@ feature -- Execution
|
||||
l_upload_file.open_read
|
||||
curl_easy.set_curl_function (l_custom_function)
|
||||
end
|
||||
elseif
|
||||
ctx.has_form_data and
|
||||
attached ctx.form_parameters as l_form_data
|
||||
then
|
||||
check non_empty_form_data: not l_form_data.is_empty end
|
||||
-- Send as form-urlencoded
|
||||
if
|
||||
attached l_headers.item ("Content-Type") as l_ct
|
||||
then
|
||||
if l_ct.starts_with ("application/x-www-form-urlencoded") then
|
||||
-- Content-Type is already application/x-www-form-urlencoded
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
elseif l_ct.starts_with ("multipart/form-data") or l_form_data.has_file_parameter then
|
||||
l_use_curl_form := True
|
||||
else
|
||||
-- Not supported, use libcurl form.
|
||||
l_use_curl_form := True
|
||||
end
|
||||
else
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
end
|
||||
if l_use_curl_form then
|
||||
create l_form.make
|
||||
create l_last.make
|
||||
across
|
||||
l_form_data as ic
|
||||
loop
|
||||
if attached {HTTP_CLIENT_REQUEST_STRING_PARAMETER} ic.item as strparam then
|
||||
curl.formadd_string_string (l_form, l_last,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copyname, strparam.name,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copycontents, strparam.value,
|
||||
{CURL_FORM_CONSTANTS}.curlform_end
|
||||
)
|
||||
elseif attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam then
|
||||
curl.formadd_string_string (l_form, l_last,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
|
||||
{CURL_FORM_CONSTANTS}.curlform_file, fileparam.location.name,
|
||||
{CURL_FORM_CONSTANTS}.curlform_end
|
||||
)
|
||||
else
|
||||
check supported_parameter_type: False end
|
||||
end
|
||||
end
|
||||
l_last.release_item
|
||||
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
|
||||
end
|
||||
else
|
||||
check no_upload_data: l_upload_data = Void and l_upload_filename = Void end
|
||||
-- No form, or upload data to send!
|
||||
check no_data: not (ctx.has_upload_data or ctx.has_upload_filename or ctx.has_form_data) end
|
||||
end
|
||||
end -- ctx /= Void
|
||||
|
||||
--| Header
|
||||
--| Header
|
||||
across
|
||||
l_headers as curs
|
||||
loop
|
||||
|
||||
@@ -91,8 +91,8 @@ feature -- Access
|
||||
l_authorization: HTTP_AUTHORIZATION
|
||||
l_platform: STRING
|
||||
l_upload_data: detachable READABLE_STRING_8
|
||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
ctx: like context
|
||||
l_ct: detachable READABLE_STRING_8
|
||||
l_upload_file: detachable RAW_FILE
|
||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||
l_form_string: STRING
|
||||
@@ -149,7 +149,7 @@ feature -- Access
|
||||
then
|
||||
create l_authorization.make_basic_auth (u_name, u_pass)
|
||||
if attached l_authorization.http_authorization as auth then
|
||||
headers.extend (auth, "Authorization")
|
||||
headers.force (auth, "Authorization")
|
||||
end
|
||||
check headers.has_key ("Authorization") end
|
||||
end
|
||||
@@ -176,7 +176,7 @@ feature -- Access
|
||||
else
|
||||
l_platform := "Unknown"
|
||||
end
|
||||
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
||||
headers.force ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
||||
end
|
||||
|
||||
-- handle sending data
|
||||
@@ -191,67 +191,52 @@ feature -- Access
|
||||
l_upload_data := ctx.upload_data
|
||||
end
|
||||
|
||||
if ctx.has_form_data then
|
||||
l_form_data := ctx.form_parameters
|
||||
if l_upload_data = Void and l_upload_filename = Void then
|
||||
if
|
||||
attached headers.item ("Content-Type") as l_ct
|
||||
then
|
||||
if l_ct.starts_with ("application/x-www-form-urlencoded") then
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
elseif l_ct.starts_with ("multipart/form-data") then
|
||||
-- create form using multipart/form-data encoding
|
||||
l_boundary := new_mime_boundary (l_form_data)
|
||||
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
|
||||
else
|
||||
-- not supported !
|
||||
-- Send as form-urlencoded
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
end
|
||||
else
|
||||
-- Send as form-urlencoded
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
end
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
if l_is_chunked_transfer_encoding then
|
||||
-- Discard chunked transfer encoding
|
||||
headers.remove ("Transfer-Encoding")
|
||||
l_is_chunked_transfer_encoding := False
|
||||
end
|
||||
elseif l_form_data /= Void then
|
||||
check l_upload_data = Void end
|
||||
|
||||
-- create form using multipart/form-data encoding
|
||||
l_boundary := new_mime_boundary (l_form_data)
|
||||
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
if l_is_chunked_transfer_encoding then
|
||||
-- Discard chunked transfer encoding
|
||||
headers.remove ("Transfer-Encoding")
|
||||
l_is_chunked_transfer_encoding := False
|
||||
end
|
||||
end
|
||||
elseif l_upload_data /= Void then
|
||||
if l_upload_data /= Void then
|
||||
check ctx.has_upload_data end
|
||||
check no_form_data: not ctx.has_form_data end
|
||||
if not headers.has ("Content-Type") then
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
headers.force ("application/x-www-form-urlencoded", "Content-Type")
|
||||
end
|
||||
if not l_is_chunked_transfer_encoding then
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
headers.force (l_upload_data.count.out, "Content-Length")
|
||||
end
|
||||
elseif l_upload_filename /= Void then
|
||||
check ctx.has_upload_filename end
|
||||
check no_form_data: not ctx.has_form_data end
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_access_readable then
|
||||
if not l_is_chunked_transfer_encoding then
|
||||
headers.extend (l_upload_file.count.out, "Content-Length")
|
||||
headers.force (l_upload_file.count.out, "Content-Length")
|
||||
end
|
||||
end
|
||||
check l_upload_file /= Void end
|
||||
elseif
|
||||
ctx.has_form_data and
|
||||
attached ctx.form_parameters as l_form_data
|
||||
then
|
||||
l_ct := headers.item ("Content-Type")
|
||||
if l_ct /= Void and then l_ct.starts_with ("application/x-www-form-urlencoded") then
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
elseif
|
||||
(l_ct /= Void and then l_ct.starts_with ("multipart/form-data"))
|
||||
or l_form_data.has_file_parameter
|
||||
then
|
||||
-- create form using multipart/form-data encoding
|
||||
l_boundary := new_mime_boundary (l_form_data)
|
||||
headers.force ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_boundary)
|
||||
else
|
||||
-- not supported !
|
||||
-- Send as form-urlencoded
|
||||
headers.force ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
|
||||
end
|
||||
headers.force (l_upload_data.count.out, "Content-Length")
|
||||
if l_is_chunked_transfer_encoding then
|
||||
-- Discard chunked transfer encoding
|
||||
headers.remove ("Transfer-Encoding")
|
||||
l_is_chunked_transfer_encoding := False
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -482,14 +467,9 @@ feature {NONE} -- Helpers
|
||||
Result := a_status >= 300 and a_status < 400
|
||||
end
|
||||
|
||||
form_date_and_uploaded_files_to_mime_string (a_form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_upload_filename: detachable READABLE_STRING_GENERAL; a_mime_boundary: READABLE_STRING_8): STRING
|
||||
form_date_and_uploaded_files_to_mime_string (a_form_parameters: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]; a_mime_boundary: READABLE_STRING_8): STRING
|
||||
-- Form data and uploaded files converted to mime string.
|
||||
-- TODO: design a proper MIME... component.
|
||||
local
|
||||
l_path: PATH
|
||||
l_mime_type: READABLE_STRING_8
|
||||
l_upload_file: detachable RAW_FILE
|
||||
l_mime_type_mapping: HTTP_FILE_EXTENSION_MIME_MAPPING
|
||||
do
|
||||
create Result.make (100)
|
||||
across
|
||||
@@ -500,48 +480,26 @@ feature {NONE} -- Helpers
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Disposition: form-data; name=")
|
||||
Result.append_character ('%"')
|
||||
Result.append (string_to_mime_encoded_string (ic.key))
|
||||
Result.append (string_to_mime_encoded_string (ic.item.name))
|
||||
Result.append_character ('%"')
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append (string_to_mime_encoded_string (ic.item))
|
||||
Result.append (http_end_of_header_line)
|
||||
end
|
||||
|
||||
if a_upload_filename /= Void then
|
||||
-- get file extension, otherwise set default
|
||||
create l_mime_type_mapping.make_default
|
||||
create l_path.make_from_string (a_upload_filename)
|
||||
if
|
||||
attached l_path.extension as ext and then
|
||||
attached l_mime_type_mapping.mime_type (ext) as l_mt
|
||||
attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam and then
|
||||
attached fileparam.file_name as fn
|
||||
then
|
||||
l_mime_type := l_mt
|
||||
else
|
||||
l_mime_type := "application/octet-stream"
|
||||
Result.append ("; filename=")
|
||||
Result.append_character ('%"')
|
||||
Result.append (string_to_mime_encoded_string (fn))
|
||||
Result.append_character ('%"')
|
||||
end
|
||||
Result.append ("--")
|
||||
Result.append (a_mime_boundary)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Disposition: form-data; name=%"")
|
||||
Result.append (string_to_mime_encoded_string (a_upload_filename))
|
||||
Result.append_character ('%"')
|
||||
Result.append ("; filename=%"")
|
||||
Result.append (string_to_mime_encoded_string (a_upload_filename))
|
||||
Result.append_character ('%"')
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Type: ")
|
||||
Result.append (l_mime_type)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append (http_end_of_header_line)
|
||||
|
||||
create l_upload_file.make_with_path (l_path)
|
||||
if l_upload_file.exists and then l_upload_file.is_access_readable then
|
||||
append_file_content_to (l_upload_file, l_upload_file.count, Result)
|
||||
-- Reset l_upload_file to Void, since the related content is already processed.
|
||||
l_upload_file := Void
|
||||
if attached ic.item.content_type as ct then
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Type: ")
|
||||
Result.append (ct)
|
||||
end
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append (http_end_of_header_line)
|
||||
ic.item.append_as_mime_encoded_to (Result)
|
||||
Result.append (http_end_of_header_line)
|
||||
end
|
||||
Result.append ("--")
|
||||
Result.append (a_mime_boundary)
|
||||
@@ -893,7 +851,7 @@ feature {NONE} -- Helpers
|
||||
end
|
||||
end
|
||||
|
||||
new_mime_boundary (a_data: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING
|
||||
new_mime_boundary (a_data: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]): STRING
|
||||
-- New MIME boundary.
|
||||
local
|
||||
s: STRING
|
||||
@@ -904,7 +862,7 @@ feature {NONE} -- Helpers
|
||||
across
|
||||
a_data as ic
|
||||
loop
|
||||
i := i + ic.item.count + ic.key.count
|
||||
i := i + ic.item.count + ic.item.name.count
|
||||
end
|
||||
create ran.set_seed (i) -- FIXME: use a real random seed.
|
||||
ran.start
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
|
||||
<target name="test_http_client">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="testing_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
|
||||
<target name="testing_http_client">
|
||||
<root class="TEST" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
@@ -10,7 +10,8 @@
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<variable name="netssl_http_client_enabled" value="false"/>
|
||||
<variable name="ssl_enabled" value="true"/>
|
||||
<variable name="netssl_http_client_enabled" value="true"/>
|
||||
<variable name="net_http_client_disabled" value="false"/>
|
||||
<variable name="libcurl_http_client_disabled" value="false"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
@@ -59,6 +59,11 @@ feature -- Tests
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
libcurl_test_post_with_multiple_file_and_form_data
|
||||
do
|
||||
test_post_with_multiple_file_and_form_data
|
||||
end
|
||||
|
||||
libcurl_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
|
||||
@@ -59,6 +59,11 @@ feature -- Tests
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
net_test_post_with_multiple_file_and_form_data
|
||||
do
|
||||
test_post_with_multiple_file_and_form_data
|
||||
end
|
||||
|
||||
net_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
|
||||
@@ -21,8 +21,7 @@ feature -- Initialization
|
||||
on_prepare
|
||||
do
|
||||
Precursor
|
||||
global_requestbin_path := "/s0jkhhs0"
|
||||
if global_requestbin_path = Void then
|
||||
if is_using_requestbin and global_requestbin_path = Void then
|
||||
global_requestbin_path := new_requestbin_path
|
||||
end
|
||||
end
|
||||
@@ -33,7 +32,13 @@ feature -- Factory
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Requestbin
|
||||
feature -- Requestbin
|
||||
|
||||
is_using_requestbin: BOOLEAN = False
|
||||
is_using_mockbincom: BOOLEAN
|
||||
do
|
||||
Result := not is_using_requestbin
|
||||
end
|
||||
|
||||
global_requestbin_path: detachable READABLE_STRING_8
|
||||
|
||||
@@ -42,7 +47,7 @@ feature -- Requestbin
|
||||
i,j: INTEGER
|
||||
do
|
||||
if
|
||||
attached new_session ("http://requestb.in") as sess and then
|
||||
attached new_session ("https://requestb.in") as sess and then
|
||||
attached sess.post ("/api/v1/bins", Void, Void) as resp
|
||||
then
|
||||
if resp.error_occurred then
|
||||
@@ -67,13 +72,30 @@ feature -- Requestbin
|
||||
if not Result.starts_with ("/") then
|
||||
Result.prepend_character ('/')
|
||||
end
|
||||
print ("new_requestbin_path => http://requestb.in" + Result + "?inspect%N")
|
||||
print ("new_requestbin_path => " + sess.base_url + Result + "?inspect%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_web_session: like new_session
|
||||
do
|
||||
if is_using_mockbincom then
|
||||
Result := new_session ("http://mockbin.com/request")
|
||||
end
|
||||
if Result = Void and is_using_requestbin then
|
||||
if attached global_requestbin_path as l_path then
|
||||
Result := new_session ("https://requestb.in" + l_path)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
if Result = Void then
|
||||
Result := new_session ("http://mockbin.com/request") -- Default
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
test_post_url_encoded
|
||||
@@ -81,288 +103,200 @@ feature -- Factory
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
-- URL ENCODED POST REQUEST
|
||||
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
||||
-- also check that User-Agent was sent
|
||||
create h.make_empty
|
||||
sess := new_session ("http://requestb.in")
|
||||
if
|
||||
attached sess.post (requestbin_path, Void, "Hello World") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- URL ENCODED POST REQUEST
|
||||
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
||||
-- also check that User-Agent was sent
|
||||
create h.make_empty
|
||||
sess := new_web_session
|
||||
if
|
||||
attached sess.post ("", Void, "Hello World") as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.add_form_parameter ("First Key", "First Value")
|
||||
l_ctx.add_form_parameter ("Second Key", "Second Value")
|
||||
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
|
||||
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.add_form_parameter ("First Key", "First Value")
|
||||
l_ctx.add_form_parameter ("Second Key", "Second Value")
|
||||
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
|
||||
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
|
||||
if
|
||||
attached sess.post ("", l_ctx, "") as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_uncommon_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
|
||||
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
|
||||
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
|
||||
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
|
||||
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
|
||||
l_ctx.add_form_parameter ("test", "!$&'()*") --
|
||||
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
|
||||
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
|
||||
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
|
||||
l_ctx.add_form_parameter ("?", "?") -- question mark
|
||||
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
|
||||
l_ctx.add_form_parameter ("&", "&") -- ampersand
|
||||
|
||||
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
|
||||
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
|
||||
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
|
||||
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
|
||||
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
|
||||
l_ctx.add_form_parameter ("test", "!$&'()*") --
|
||||
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
|
||||
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
|
||||
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
|
||||
l_ctx.add_form_parameter ("?", "?") -- question mark
|
||||
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
|
||||
l_ctx.add_form_parameter ("&", "&") -- ampersand
|
||||
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
|
||||
|
||||
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
|
||||
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
if
|
||||
attached sess.post ("", l_ctx, "") as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_file
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- POST REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
if
|
||||
attached sess.post ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_put_with_file
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.put (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
if
|
||||
attached sess.put ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_put_with_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_data ("name=This is a test for http client.%N")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.put (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_data ("name=This is a test for http client.%N")
|
||||
if
|
||||
attached sess.put ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test_post_with_file_and_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
-- l_ctx.add_file_form_parameter ("image", "test.txt", "image/jpeg")
|
||||
l_ctx.add_file_form_parameter ("text", "test.txt", "plain/text")
|
||||
l_ctx.add_form_parameter ("First", "Value")
|
||||
l_ctx.add_form_parameter ("Second", "and last value")
|
||||
if
|
||||
attached sess.post ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
-- sess := new_session ("http://localhost:9090")
|
||||
create l_ctx.make
|
||||
-- l_ctx.set_upload_filename ("logo.jpg")
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
l_ctx.add_form_parameter ("First", "Value")
|
||||
l_ctx.add_form_parameter ("Second", "and last value")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
test_post_with_multiple_file_and_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.add_header ("Content-Type", "multipart/form-data")
|
||||
|
||||
l_ctx.add_file_form_parameter ("first_file", "test.txt", "plain/text")
|
||||
l_ctx.add_file_form_parameter ("image", "logo.jpg", "image/jpeg")
|
||||
l_ctx.add_form_parameter ("First", "Value")
|
||||
l_ctx.add_form_parameter ("Second", "and last value")
|
||||
l_ctx.add_file_form_parameter ("last_file", "test.txt", Void)
|
||||
|
||||
if
|
||||
attached sess.post ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_file_using_chunked_transfer_encoding
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.add_header ("Transfer-Encoding", "chunked")
|
||||
l_ctx.set_upload_filename ("logo.jpg")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.add_header ("Transfer-Encoding", "chunked")
|
||||
l_ctx.set_upload_filename ("logo.jpg")
|
||||
if
|
||||
attached sess.post ("", l_ctx, Void) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
test_get_with_redirection
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
||||
-- check headers received (printed in console)
|
||||
sess := new_session ("http://google.com")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
|
||||
assert("was redirected", res.redirections_count > 0)
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
||||
-- check headers received (printed in console)
|
||||
sess := new_session ("http://google.com")
|
||||
if attached sess.get ("/", Void) as res then
|
||||
check_response (res)
|
||||
assert("was redirected", res.redirections_count > 0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -377,6 +311,7 @@ feature -- Factory
|
||||
sess.set_credentials ("test", "test")
|
||||
create ctx.make_with_credentials_required
|
||||
if attached sess.get ("/password-ok.php", ctx) as res then
|
||||
check_response (res)
|
||||
if attached {READABLE_STRING_8} res.body as l_body then
|
||||
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
||||
else
|
||||
@@ -388,50 +323,58 @@ feature -- Factory
|
||||
test_get_with_query_parameters
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
q: STRING
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- GET REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.add_query_parameter ("?", "?first&arg")
|
||||
l_ctx.add_query_parameter ("title", "Eiffel World!")
|
||||
l_ctx.add_query_parameter ("path", "foo/bar")
|
||||
l_ctx.add_query_parameter ("reserved", "+=&?")
|
||||
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
|
||||
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
|
||||
l_ctx.add_query_parameter ("test", "!$&'()*")
|
||||
l_ctx.add_query_parameter ("a&b", "a&b")
|
||||
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
|
||||
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
|
||||
create q.make_empty
|
||||
l_ctx.append_query_parameters_to_url (q)
|
||||
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
|
||||
-- GET REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_web_session
|
||||
create l_ctx.make
|
||||
l_ctx.add_query_parameter ("?", "?first&arg")
|
||||
l_ctx.add_query_parameter ("title", "Eiffel World!")
|
||||
l_ctx.add_query_parameter ("path", "foo/bar")
|
||||
l_ctx.add_query_parameter ("reserved", "+=&?")
|
||||
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
|
||||
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
|
||||
l_ctx.add_query_parameter ("test", "!$&'()*")
|
||||
l_ctx.add_query_parameter ("a&b", "a&b")
|
||||
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
|
||||
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
|
||||
create q.make_empty
|
||||
l_ctx.append_query_parameters_to_url (q)
|
||||
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
|
||||
|
||||
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.get (requestbin_path, l_ctx) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
if
|
||||
attached sess.get ("", l_ctx) as res
|
||||
then
|
||||
check_response (res)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
check_response (res: HTTP_CLIENT_RESPONSE)
|
||||
local
|
||||
h: STRING
|
||||
do
|
||||
assert ("ok", not res.error_occurred)
|
||||
create h.make_empty
|
||||
if
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
if attached res.body as b then
|
||||
print (b)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -55,12 +55,19 @@ feature -- Secure connection Helpers
|
||||
end
|
||||
|
||||
set_secure_protocol_to_ssl_2_or_3
|
||||
-- Set `ssl_protocol' with `Ssl_23'.
|
||||
do
|
||||
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
|
||||
end
|
||||
-- Set `ssl_protocol' with `Ssl_23'.
|
||||
-- Protocol not supported anymore.
|
||||
obsolete
|
||||
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
|
||||
local
|
||||
err: DEVELOPER_EXCEPTION
|
||||
do
|
||||
create err
|
||||
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
|
||||
err.raise
|
||||
end
|
||||
|
||||
set_secure_protocol_to_tls_1_0
|
||||
set_secure_protocol_to_tls_1_0
|
||||
-- Set `ssl_protocol' with `Tls_1_0'.
|
||||
do
|
||||
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
|
||||
@@ -176,7 +183,14 @@ feature -- Output
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, 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
|
||||
|
||||
26
library/security/jwt/README.md
Normal file
26
library/security/jwt/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
JSON Web Token (JWT)
|
||||
|
||||
http://jwt.io/
|
||||
|
||||
Note: supporting only HS256 and none algorithm for signature.
|
||||
|
||||
# How to use
|
||||
```eiffel
|
||||
local
|
||||
jwt: JWT
|
||||
do
|
||||
create jwt
|
||||
tok := jwt.encoded_string ("[
|
||||
{"iss":"joe", "exp":1200819380,"http://example.com/is_root":true}
|
||||
]", "secret", "HS256")
|
||||
if
|
||||
attached jwt.decoded_string (tok, "secret", Void) as l_tok_payload and
|
||||
not jwt.has_error
|
||||
then
|
||||
check verified: not jwt.has_unverified_token_error end
|
||||
check no_error: not jwt.has_error end
|
||||
print (l_tok_payload)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
15
library/security/jwt/jwt-safe.ecf
Normal file
15
library/security/jwt/jwt-safe.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="jwt" uuid="A75C2D84-D543-4708-BAF3-254C308376CC" library_target="jwt">
|
||||
<target name="jwt">
|
||||
<root all_classes="true"/>
|
||||
<option warning="true" void_safety="all">
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
15
library/security/jwt/jwt.ecf
Normal file
15
library/security/jwt/jwt.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="jwt" uuid="A75C2D84-D543-4708-BAF3-254C308376CC" library_target="jwt">
|
||||
<target name="jwt">
|
||||
<root all_classes="true"/>
|
||||
<option warning="true" void_safety="none">
|
||||
</option>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
16
library/security/jwt/package.iron
Normal file
16
library/security/jwt/package.iron
Normal file
@@ -0,0 +1,16 @@
|
||||
package jwt
|
||||
|
||||
project
|
||||
jwt = "jwt.ecf"
|
||||
|
||||
note
|
||||
title: JSON Web Token
|
||||
description: JSON Web Token
|
||||
tags:jwt,web,jws,jwe,token,jose
|
||||
copyright: 2011-2017, Jocelyn Fiat, Eiffel Software and others
|
||||
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
|
||||
link[license]: http://www.eiffel.com/licensing/forum.txt
|
||||
link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/security/jwt
|
||||
link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/tree/master/library/security/jwt/README.md
|
||||
|
||||
end
|
||||
33
library/security/jwt/src/errors/jwt_claim_validation_error.e
Normal file
33
library/security/jwt/src/errors/jwt_claim_validation_error.e
Normal file
@@ -0,0 +1,33 @@
|
||||
note
|
||||
description: "Summary description for {JWT_CLAIM_VALIDATION_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_CLAIM_VALIDATION_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_claim: READABLE_STRING_8)
|
||||
do
|
||||
claim_name := a_claim
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
claim_name: READABLE_STRING_8
|
||||
|
||||
id: STRING = "CLAIM"
|
||||
|
||||
message: READABLE_STRING_8
|
||||
do
|
||||
Result := "Claim [" + claim_name + "] not validated!"
|
||||
end
|
||||
|
||||
end
|
||||
29
library/security/jwt/src/errors/jwt_dev_error.e
Normal file
29
library/security/jwt/src/errors/jwt_dev_error.e
Normal file
@@ -0,0 +1,29 @@
|
||||
note
|
||||
description: "Summary description for {JWT_DEV_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_DEV_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_id: READABLE_STRING_8; msg: READABLE_STRING_8)
|
||||
do
|
||||
id := a_id
|
||||
message := msg
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
id: STRING
|
||||
|
||||
message: READABLE_STRING_8
|
||||
|
||||
end
|
||||
21
library/security/jwt/src/errors/jwt_invalid_token_error.e
Normal file
21
library/security/jwt/src/errors/jwt_invalid_token_error.e
Normal file
@@ -0,0 +1,21 @@
|
||||
note
|
||||
description: "Summary description for {JWT_INVALID_TOKEN_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_INVALID_TOKEN_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
feature -- Access
|
||||
|
||||
id: STRING = "INVALID"
|
||||
|
||||
message: READABLE_STRING_8
|
||||
do
|
||||
Result := "Invalid token"
|
||||
end
|
||||
|
||||
end
|
||||
36
library/security/jwt/src/errors/jwt_mismatched_alg_error.e
Normal file
36
library/security/jwt/src/errors/jwt_mismatched_alg_error.e
Normal file
@@ -0,0 +1,36 @@
|
||||
note
|
||||
description: "Summary description for {JWT_MISMATCHED_ALG_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_MISMATCHED_ALG_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_alg, a_header_alg: READABLE_STRING_8)
|
||||
do
|
||||
alg := a_alg
|
||||
header_alg := a_header_alg
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
alg: READABLE_STRING_8
|
||||
|
||||
header_alg: READABLE_STRING_8
|
||||
|
||||
id: STRING = "ALG_MISMATCH"
|
||||
|
||||
message: READABLE_STRING_8
|
||||
do
|
||||
Result := "Header alg [" + header_alg + "] does not match given alg [" + alg + "]!"
|
||||
end
|
||||
|
||||
end
|
||||
33
library/security/jwt/src/errors/jwt_unsupported_alg_error.e
Normal file
33
library/security/jwt/src/errors/jwt_unsupported_alg_error.e
Normal file
@@ -0,0 +1,33 @@
|
||||
note
|
||||
description: "Summary description for {JWT_UNSUPPORTED_ALG_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_UNSUPPORTED_ALG_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_alg: READABLE_STRING_8)
|
||||
do
|
||||
alg := a_alg
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
alg: READABLE_STRING_8
|
||||
|
||||
id: STRING = "ALG"
|
||||
|
||||
message: READABLE_STRING_8
|
||||
do
|
||||
Result := "Unsupported alg [" + alg + "]"
|
||||
end
|
||||
|
||||
end
|
||||
21
library/security/jwt/src/errors/jwt_unverified_token_error.e
Normal file
21
library/security/jwt/src/errors/jwt_unverified_token_error.e
Normal file
@@ -0,0 +1,21 @@
|
||||
note
|
||||
description: "Summary description for {JWT_UNVERIFIED_TOKEN_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_UNVERIFIED_TOKEN_ERROR
|
||||
|
||||
inherit
|
||||
JWT_ERROR
|
||||
|
||||
feature -- Access
|
||||
|
||||
id: STRING = "UNVERIFIED"
|
||||
|
||||
message: READABLE_STRING_8
|
||||
do
|
||||
Result := "Unverified token"
|
||||
end
|
||||
|
||||
end
|
||||
105
library/security/jwt/src/jws.e
Normal file
105
library/security/jwt/src/jws.e
Normal file
@@ -0,0 +1,105 @@
|
||||
note
|
||||
description: "Summary description for {JWS}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWS
|
||||
|
||||
inherit
|
||||
JWT
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
JWT_UTILITIES
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
create
|
||||
default_create,
|
||||
make_with_algorithm,
|
||||
make_with_claims,
|
||||
make_with_json_payload
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
default_create
|
||||
do
|
||||
Precursor {JWT}
|
||||
set_algorithm_to_hs256
|
||||
end
|
||||
|
||||
make_with_algorithm (alg: like algorithm)
|
||||
do
|
||||
default_create
|
||||
set_algorithm (alg)
|
||||
end
|
||||
|
||||
make_with_claims (tb: STRING_TABLE [READABLE_STRING_GENERAL])
|
||||
do
|
||||
default_create
|
||||
across
|
||||
tb as ic
|
||||
loop
|
||||
claimset.set_claim (ic.key, ic.item)
|
||||
end
|
||||
end
|
||||
|
||||
make_with_json_payload (a_json: READABLE_STRING_8)
|
||||
do
|
||||
default_create
|
||||
claimset.import_json (a_json)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
algorithm: READABLE_STRING_8
|
||||
do
|
||||
Result := header.algorithm
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
encoded_string (a_secret: READABLE_STRING_8): STRING
|
||||
local
|
||||
alg, sign: READABLE_STRING_8
|
||||
l_enc_payload, l_enc_header: READABLE_STRING_8
|
||||
do
|
||||
reset_error
|
||||
alg := header.algorithm
|
||||
if not is_supporting_signature_algorithm (alg) then
|
||||
report_unsupported_alg_error (alg)
|
||||
alg := alg_hs256 -- Default ...
|
||||
end
|
||||
l_enc_header := base64url_encode (header.string)
|
||||
l_enc_payload := base64url_encode (claimset.string)
|
||||
sign := signature (l_enc_header, l_enc_payload, a_secret, alg)
|
||||
|
||||
create Result.make (l_enc_header.count + 1 + l_enc_payload.count + 1 + sign.count)
|
||||
Result.append (l_enc_header)
|
||||
Result.append_character ('.')
|
||||
Result.append (l_enc_payload)
|
||||
Result.append_character ('.')
|
||||
Result.append (sign)
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_algorithm (alg: detachable READABLE_STRING_8)
|
||||
do
|
||||
header.set_algorithm (alg)
|
||||
end
|
||||
|
||||
set_algorithm_to_hs256
|
||||
do
|
||||
set_algorithm (alg_hs256)
|
||||
end
|
||||
|
||||
set_algorithm_to_none
|
||||
do
|
||||
set_algorithm (alg_none)
|
||||
end
|
||||
|
||||
end
|
||||
148
library/security/jwt/src/jwt.e
Normal file
148
library/security/jwt/src/jwt.e
Normal file
@@ -0,0 +1,148 @@
|
||||
note
|
||||
description: "JSON Web Token"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
JWT
|
||||
|
||||
inherit
|
||||
ANY
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
default_create
|
||||
do
|
||||
create header
|
||||
create claimset
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
header: JWT_HEADER
|
||||
|
||||
claimset: JWT_CLAIMSET
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_expired (dt: detachable DATE_TIME): BOOLEAN
|
||||
-- Is Current token expired?
|
||||
-- See "exp" claim.
|
||||
do
|
||||
if attached claimset.expiration_time as l_exp_time then
|
||||
if dt /= Void then
|
||||
Result := dt > l_exp_time
|
||||
else
|
||||
Result := (create {DATE_TIME}.make_now_utc) > l_exp_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
is_nbf_validated (dt: detachable DATE_TIME): BOOLEAN
|
||||
-- Does `dt` or now verify the "nbf" claim?
|
||||
-- See "nbf" claim.
|
||||
do
|
||||
Result := True
|
||||
if attached claimset.not_before_time as l_time then
|
||||
if dt /= Void then
|
||||
Result := dt >= l_time
|
||||
else
|
||||
Result := (create {DATE_TIME}.make_now_utc) >= l_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
is_iss_validated (a_issuer: detachable READABLE_STRING_8): BOOLEAN
|
||||
do
|
||||
if attached claimset.issuer as iss then
|
||||
Result := a_issuer = Void or else a_issuer.same_string (iss)
|
||||
end
|
||||
end
|
||||
|
||||
is_aud_validated (a_audience: detachable READABLE_STRING_8): BOOLEAN
|
||||
do
|
||||
if attached claimset.audience as aud then
|
||||
Result := a_audience = Void or else a_audience.same_string (aud)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
encoded_string (a_secret: READABLE_STRING_8): STRING
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- status report
|
||||
|
||||
has_error: BOOLEAN
|
||||
do
|
||||
Result := attached errors as errs and then not errs.is_empty
|
||||
end
|
||||
|
||||
has_unsupported_alg_error: BOOLEAN
|
||||
do
|
||||
Result := attached errors as errs and then across errs as ic some attached {JWT_UNSUPPORTED_ALG_ERROR} ic.item end
|
||||
end
|
||||
|
||||
has_unverified_token_error: BOOLEAN
|
||||
do
|
||||
Result := attached errors as errs and then across errs as ic some attached {JWT_UNVERIFIED_TOKEN_ERROR} ic.item end
|
||||
end
|
||||
|
||||
has_invalid_token_error: BOOLEAN
|
||||
do
|
||||
Result := attached errors as errs and then across errs as ic some attached {JWT_INVALID_TOKEN_ERROR} ic.item end
|
||||
end
|
||||
|
||||
errors: detachable ARRAYED_LIST [JWT_ERROR]
|
||||
|
||||
feature {JWT_UTILITIES} -- Error reporting
|
||||
|
||||
reset_error
|
||||
do
|
||||
errors := Void
|
||||
end
|
||||
|
||||
report_error (err: JWT_ERROR)
|
||||
local
|
||||
l_errors: like errors
|
||||
do
|
||||
l_errors := errors
|
||||
if l_errors = Void then
|
||||
create l_errors.make (1)
|
||||
errors := l_errors
|
||||
end
|
||||
l_errors.extend (err)
|
||||
end
|
||||
|
||||
report_mismatched_alg_error (alg, a_header_alg: READABLE_STRING_8)
|
||||
do
|
||||
report_error (create {JWT_MISMATCHED_ALG_ERROR}.make (alg, a_header_alg))
|
||||
end
|
||||
|
||||
report_unsupported_alg_error (alg: READABLE_STRING_8)
|
||||
do
|
||||
report_error (create {JWT_UNSUPPORTED_ALG_ERROR}.make (alg))
|
||||
end
|
||||
|
||||
report_unverified_token_error
|
||||
do
|
||||
report_error (create {JWT_UNVERIFIED_TOKEN_ERROR})
|
||||
end
|
||||
|
||||
report_invalid_token
|
||||
do
|
||||
report_error (create {JWT_INVALID_TOKEN_ERROR})
|
||||
end
|
||||
|
||||
report_claim_validation_error (a_claimname: READABLE_STRING_8)
|
||||
do
|
||||
report_error (create {JWT_CLAIM_VALIDATION_ERROR}.make (a_claimname))
|
||||
end
|
||||
|
||||
invariant
|
||||
|
||||
end
|
||||
292
library/security/jwt/src/jwt_claimset.e
Normal file
292
library/security/jwt/src/jwt_claimset.e
Normal file
@@ -0,0 +1,292 @@
|
||||
note
|
||||
description: "Summary description for {JWT_CLAIMSET}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_CLAIMSET
|
||||
|
||||
inherit
|
||||
ANY
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
create
|
||||
default_create
|
||||
|
||||
convert
|
||||
string: {READABLE_STRING_8, STRING_8}
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
default_create
|
||||
do
|
||||
create json.make_empty
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
import_json (j: READABLE_STRING_8)
|
||||
local
|
||||
jp: JSON_PARSER
|
||||
do
|
||||
create jp.make_with_string (j)
|
||||
jp.parse_content
|
||||
if jp.is_valid and then attached jp.parsed_json_object as jo then
|
||||
across
|
||||
jo as ic
|
||||
loop
|
||||
json.put (ic.item, ic.key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
claim alias "[]" (a_name: READABLE_STRING_GENERAL): detachable ANY
|
||||
do
|
||||
if attached json.item (a_name) as jv then
|
||||
if attached {JSON_STRING} jv as js then
|
||||
Result := js.unescaped_string_32
|
||||
elseif attached {JSON_BOOLEAN} jv as jb then
|
||||
Result := jb.item
|
||||
elseif attached {JSON_NUMBER} jv as jnum then
|
||||
if jnum.is_integer then
|
||||
Result := jnum.integer_64_item
|
||||
elseif jnum.is_natural then
|
||||
Result := jnum.natural_64_item
|
||||
elseif jnum.is_real then
|
||||
Result := jnum.real_64_item
|
||||
else
|
||||
Result := jnum.item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_32_claim (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
do
|
||||
if attached json.item (a_name) as jv then
|
||||
if attached {JSON_STRING} jv as js then
|
||||
Result := js.unescaped_string_32
|
||||
elseif attached {JSON_BOOLEAN} jv as jb then
|
||||
Result := jb.item.out
|
||||
elseif attached {JSON_NUMBER} jv as jnum then
|
||||
Result := jnum.item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_8_claim (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_8
|
||||
do
|
||||
if attached json.item (a_name) as jv then
|
||||
if attached {JSON_STRING} jv as js then
|
||||
Result := js.unescaped_string_8
|
||||
elseif attached {JSON_BOOLEAN} jv as jb then
|
||||
Result := jb.item.out
|
||||
elseif attached {JSON_NUMBER} jv as jnum then
|
||||
Result := jnum.item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
issuer: detachable READABLE_STRING_8 assign set_issuer
|
||||
do
|
||||
Result := string_8_claim ("iss")
|
||||
end
|
||||
|
||||
subjet: detachable READABLE_STRING_32 assign set_subject
|
||||
do
|
||||
Result := string_32_claim ("sub")
|
||||
end
|
||||
|
||||
audience: detachable READABLE_STRING_8 assign set_audience
|
||||
do
|
||||
Result := string_8_claim ("aud")
|
||||
end
|
||||
|
||||
expiration_time: detachable DATE_TIME assign set_expiration_time
|
||||
do
|
||||
if attached {INTEGER_64} claim ("exp") as i64 then
|
||||
Result := numeric_date_value_to_datetime (i64)
|
||||
end
|
||||
end
|
||||
|
||||
not_before_time: detachable DATE_TIME assign set_not_before_time
|
||||
do
|
||||
if attached {INTEGER_64} claim ("nbf") as i64 then
|
||||
Result := numeric_date_value_to_datetime (i64)
|
||||
end
|
||||
end
|
||||
|
||||
issued_at: detachable DATE_TIME assign set_issued_at
|
||||
do
|
||||
if attached {INTEGER_64} claim ("iat") as i then
|
||||
Result := numeric_date_value_to_datetime (i)
|
||||
end
|
||||
end
|
||||
|
||||
jwd_id: detachable READABLE_STRING_8 assign set_jwt_id
|
||||
do
|
||||
Result := string_8_claim ("jti")
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
json: JSON_OBJECT
|
||||
|
||||
string: STRING
|
||||
do
|
||||
Result := json.representation
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_claim (a_name: READABLE_STRING_GENERAL; a_val: detachable ANY)
|
||||
do
|
||||
if a_val = Void then
|
||||
json.remove (a_name)
|
||||
elseif attached {READABLE_STRING_GENERAL} a_val as str then
|
||||
json.put_string (str, a_name)
|
||||
elseif attached {BOOLEAN} a_val as b then
|
||||
json.put_boolean (b, a_name)
|
||||
elseif attached {DATE_TIME} a_val as dt then
|
||||
json.put_integer (datetime_to_numeric_date_value (dt), a_name)
|
||||
elseif attached {DATE} a_val as d then
|
||||
json.put_integer (datetime_to_numeric_date_value (create {DATE_TIME}.make_by_date (d)), a_name)
|
||||
elseif attached {NUMERIC} a_val as num then
|
||||
if attached {INTEGER_64} num as i64 then
|
||||
json.put_integer (i64, a_name)
|
||||
elseif attached {INTEGER_32} num as i32 then
|
||||
json.put_integer (i32.to_integer_64, a_name)
|
||||
elseif attached {NATURAL_64} num as n64 then
|
||||
json.put_natural (n64, a_name)
|
||||
elseif attached {INTEGER_32} num as n32 then
|
||||
json.put_natural (n32.to_natural_64, a_name)
|
||||
elseif attached {REAL_64} num as r64 then
|
||||
json.put_real (r64, a_name)
|
||||
elseif attached {REAL_32} num as r32 then
|
||||
json.put_real (r32, a_name)
|
||||
else
|
||||
json.put_string (a_val.out, a_name)
|
||||
end
|
||||
else
|
||||
json.put_string (a_val.out, a_name)
|
||||
end
|
||||
end
|
||||
|
||||
set_issuer (iss: detachable READABLE_STRING_8)
|
||||
-- The "iss" (issuer) claim identifies the principal that issued the
|
||||
-- JWT. The processing of this claim is generally application specific.
|
||||
-- The "iss" value is a case-sensitive string containing a StringOrURI
|
||||
-- value. Use of this claim is OPTIONAL.
|
||||
do
|
||||
set_claim ("iss", iss)
|
||||
end
|
||||
|
||||
set_subject (sub: detachable READABLE_STRING_32)
|
||||
-- The "sub" (subject) claim identifies the principal that is the
|
||||
-- subject of the JWT. The claims in a JWT are normally statements
|
||||
-- about the subject. The subject value MUST either be scoped to be
|
||||
-- locally unique in the context of the issuer or be globally unique.
|
||||
-- The processing of this claim is generally application specific. The
|
||||
-- "sub" value is a case-sensitive string containing a StringOrURI
|
||||
-- value. Use of this claim is OPTIONAL.
|
||||
do
|
||||
set_claim ("sub", sub)
|
||||
end
|
||||
|
||||
set_audience (aud: detachable READABLE_STRING_8)
|
||||
-- The "aud" (audience) claim identifies the recipients that the JWT is
|
||||
-- intended for. Each principal intended to process the JWT MUST
|
||||
-- identify itself with a value in the audience claim. If the principal
|
||||
-- processing the claim does not identify itself with a value in the
|
||||
-- "aud" claim when this claim is present, then the JWT MUST be
|
||||
-- rejected. In the general case, the "aud" value is an array of case-
|
||||
-- sensitive strings, each containing a StringOrURI value. In the
|
||||
-- special case when the JWT has one audience, the "aud" value MAY be a
|
||||
-- single case-sensitive string containing a StringOrURI value. The
|
||||
-- interpretation of audience values is generally application specific.
|
||||
-- Use of this claim is OPTIONAL.
|
||||
do
|
||||
set_claim ("aud", aud)
|
||||
end
|
||||
|
||||
set_expiration_time (exp: detachable DATE_TIME)
|
||||
-- The "exp" (expiration time) claim identifies the expiration time on
|
||||
-- or after which the JWT MUST NOT be accepted for processing. The
|
||||
-- processing of the "exp" claim requires that the current date/time
|
||||
-- MUST be before the expiration date/time listed in the "exp" claim.
|
||||
-- Implementers MAY provide for some small leeway, usually no more than
|
||||
-- a few minutes, to account for clock skew. Its value MUST be a number
|
||||
-- containing a NumericDate value. Use of this claim is OPTIONAL.
|
||||
do
|
||||
if exp = Void then
|
||||
set_claim ("exp", Void)
|
||||
else
|
||||
set_claim ("exp", datetime_to_numeric_date_value (exp))
|
||||
end
|
||||
end
|
||||
|
||||
set_not_before_time (nbf: detachable DATE_TIME)
|
||||
-- The "nbf" (not before) claim identifies the time before which the JWT
|
||||
-- MUST NOT be accepted for processing. The processing of the "nbf"
|
||||
-- claim requires that the current date/time MUST be after or equal to
|
||||
-- the not-before date/time listed in the "nbf" claim. Implementers MAY
|
||||
-- provide for some small leeway, usually no more than a few minutes, to
|
||||
-- account for clock skew. Its value MUST be a number containing a
|
||||
-- NumericDate value. Use of this claim is OPTIONAL.
|
||||
do
|
||||
if nbf = Void then
|
||||
set_claim ("nbf", Void)
|
||||
else
|
||||
set_claim ("nbf", datetime_to_numeric_date_value (nbf))
|
||||
end
|
||||
end
|
||||
|
||||
set_issued_at (iat: detachable DATE_TIME)
|
||||
-- The "iat" (issued at) claim identifies the time at which the JWT was
|
||||
-- issued. This claim can be used to determine the age of the JWT. Its
|
||||
-- value MUST be a number containing a NumericDate value. Use of this
|
||||
-- claim is OPTIONAL.
|
||||
do
|
||||
if iat = Void then
|
||||
set_claim ("iat", Void)
|
||||
else
|
||||
set_claim ("iat", datetime_to_numeric_date_value (iat))
|
||||
end
|
||||
end
|
||||
|
||||
set_issued_at_now_utc
|
||||
do
|
||||
set_issued_at (create {DATE_TIME}.make_now_utc)
|
||||
end
|
||||
|
||||
set_jwt_id (jti: detachable READABLE_STRING_8)
|
||||
-- The "jti" (JWT ID) claim provides a unique identifier for the JWT.
|
||||
-- The identifier value MUST be assigned in a manner that ensures that
|
||||
-- there is a negligible probability that the same value will be
|
||||
-- accidentally assigned to a different data object; if the application
|
||||
-- uses multiple issuers, collisions MUST be prevented among values
|
||||
-- produced by different issuers as well. The "jti" claim can be used
|
||||
-- to prevent the JWT from being replayed. The "jti" value is a case-
|
||||
-- sensitive string. Use of this claim is OPTIONAL.
|
||||
do
|
||||
set_claim ("jti", jti)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
numeric_date_value_to_datetime (v: INTEGER_64): DATE_TIME
|
||||
do
|
||||
create Result.make_from_epoch (v.to_integer_32)
|
||||
end
|
||||
|
||||
datetime_to_numeric_date_value (dt: DATE_TIME): INTEGER_64
|
||||
do
|
||||
Result := dt.definite_duration (create {DATE_TIME}.make_from_epoch (0)).seconds_count
|
||||
end
|
||||
|
||||
end
|
||||
50
library/security/jwt/src/jwt_context.e
Normal file
50
library/security/jwt/src/jwt_context.e
Normal file
@@ -0,0 +1,50 @@
|
||||
note
|
||||
description: "Summary description for {JWT_CONTEXT}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_CONTEXT
|
||||
|
||||
feature -- Access
|
||||
|
||||
time: detachable DATE_TIME
|
||||
-- Date time to use for validation, if Void, use current date time.
|
||||
|
||||
validation_ignored: BOOLEAN
|
||||
-- Read claimset of JWT without performing validation of the signature
|
||||
-- or any of the regustered claim names.
|
||||
-- Warning: - Use this setting with care, only if you clearly understand
|
||||
-- what you are doing.
|
||||
-- - Without digital signature information, the integrity or authenticity
|
||||
-- of the claimset cannot be trusted.
|
||||
|
||||
issuer: detachable READABLE_STRING_8
|
||||
|
||||
audience: detachable READABLE_STRING_8
|
||||
|
||||
feature -- Element change
|
||||
|
||||
ignore_validation (b: BOOLEAN)
|
||||
-- If `b` then ignore validations.
|
||||
do
|
||||
validation_ignored := b
|
||||
end
|
||||
|
||||
set_time (dt: detachable DATE_TIME)
|
||||
do
|
||||
time := dt
|
||||
end
|
||||
|
||||
set_issuer (iss: like issuer)
|
||||
do
|
||||
issuer := iss
|
||||
end
|
||||
|
||||
set_audience (aud: like audience)
|
||||
do
|
||||
audience := aud
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
276
library/security/jwt/src/jwt_encoder.e
Normal file
276
library/security/jwt/src/jwt_encoder.e
Normal file
@@ -0,0 +1,276 @@
|
||||
note
|
||||
description: "JSON Web Token encoder"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_ENCODER
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
encoded_values (a_values: STRING_TABLE [READABLE_STRING_GENERAL]; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
|
||||
local
|
||||
j: JSON_OBJECT
|
||||
do
|
||||
create j.make_with_capacity (a_values.count)
|
||||
across
|
||||
a_values as ic
|
||||
loop
|
||||
j.put_string (ic.item, ic.key)
|
||||
end
|
||||
Result := encoded_json (j, a_secret, a_algo)
|
||||
end
|
||||
|
||||
encoded_json (a_json: JSON_OBJECT; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
|
||||
local
|
||||
vis: JSON_PRETTY_STRING_VISITOR
|
||||
s: STRING
|
||||
do
|
||||
create s.make_empty
|
||||
create vis.make (s)
|
||||
vis.visit_json_object (a_json)
|
||||
Result := encoded_string (s, a_secret, a_algo)
|
||||
end
|
||||
|
||||
encoded_string (a_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
|
||||
local
|
||||
alg, sign: STRING_8
|
||||
l_enc_payload, l_enc_header: READABLE_STRING_8
|
||||
do
|
||||
reset_error
|
||||
if a_algo.is_case_insensitive_equal_general (alg_hs256) then
|
||||
alg := alg_hs256
|
||||
elseif a_algo.is_case_insensitive_equal_general (alg_none) then
|
||||
alg := alg_none
|
||||
else
|
||||
report_unsupported_alg_error (a_algo)
|
||||
alg := alg_hs256 -- Default ...
|
||||
end
|
||||
l_enc_header := base64url_encode (header ("JWT", alg))
|
||||
l_enc_payload := base64url_encode (a_payload)
|
||||
sign := signature (l_enc_header, l_enc_payload, a_secret, alg)
|
||||
create Result.make (l_enc_header.count + 1 + l_enc_payload.count + 1 + sign.count)
|
||||
Result.append (l_enc_header)
|
||||
Result.append_character ('.')
|
||||
Result.append (l_enc_payload)
|
||||
Result.append_character ('.')
|
||||
Result.append (sign)
|
||||
end
|
||||
|
||||
decoded_string (a_token: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: detachable READABLE_STRING_8): detachable STRING
|
||||
local
|
||||
i,j,n: INTEGER
|
||||
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
|
||||
do
|
||||
reset_error
|
||||
n := a_token.count
|
||||
i := a_token.index_of ('.', 1)
|
||||
if i > 0 then
|
||||
j := a_token.index_of ('.', i + 1)
|
||||
if j > 0 then
|
||||
l_enc_header := a_token.substring (1, i - 1)
|
||||
l_enc_payload := a_token.substring (i + 1, j - 1)
|
||||
l_signature := a_token.substring (j + 1, n)
|
||||
Result := base64url_decode (l_enc_payload)
|
||||
alg := a_algo
|
||||
if alg = Void then
|
||||
alg := signature_algorithm_from_encoded_header (l_enc_header)
|
||||
if alg = Void then
|
||||
-- Use default
|
||||
alg := alg_hs256
|
||||
end
|
||||
end
|
||||
check alg_set: alg /= Void end
|
||||
if alg.is_case_insensitive_equal (alg_hs256) then
|
||||
alg := alg_hs256
|
||||
elseif alg.is_case_insensitive_equal (alg_none) then
|
||||
alg := alg_none
|
||||
else
|
||||
alg := alg_hs256
|
||||
report_unsupported_alg_error (alg)
|
||||
end
|
||||
|
||||
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
|
||||
report_unverified_token_error
|
||||
end
|
||||
else
|
||||
report_invalid_token
|
||||
end
|
||||
else
|
||||
report_invalid_token
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Error status
|
||||
|
||||
error_code: INTEGER
|
||||
-- Last error, if any.
|
||||
|
||||
has_error: BOOLEAN
|
||||
-- Last `encoded_string` reported an error?
|
||||
do
|
||||
Result := error_code /= 0
|
||||
end
|
||||
|
||||
has_unsupported_alg_error: BOOLEAN
|
||||
do
|
||||
Result := error_code = unsupported_alg_error
|
||||
end
|
||||
|
||||
has_unverified_token_error: BOOLEAN
|
||||
do
|
||||
Result := error_code = unverified_token_error
|
||||
end
|
||||
|
||||
has_invalid_token_error: BOOLEAN
|
||||
do
|
||||
Result := error_code = invalid_token_error
|
||||
end
|
||||
|
||||
feature {NONE} -- Error reporting
|
||||
|
||||
reset_error
|
||||
do
|
||||
error_code := 0
|
||||
end
|
||||
|
||||
report_unsupported_alg_error (alg: READABLE_STRING_8)
|
||||
do
|
||||
error_code := unsupported_alg_error
|
||||
end
|
||||
|
||||
report_unverified_token_error
|
||||
do
|
||||
error_code := unverified_token_error
|
||||
end
|
||||
|
||||
report_invalid_token
|
||||
do
|
||||
error_code := invalid_token_error
|
||||
end
|
||||
|
||||
feature {NONE} -- Constants
|
||||
|
||||
unsupported_alg_error: INTEGER = -2
|
||||
|
||||
unverified_token_error: INTEGER = -4
|
||||
|
||||
invalid_token_error: INTEGER = -8
|
||||
|
||||
alg_hs256: STRING = "HS256"
|
||||
-- HMAC SHA256.
|
||||
|
||||
alg_none: STRING = "none"
|
||||
-- for unsecured token.
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
header (a_type: detachable READABLE_STRING_8; alg: READABLE_STRING_8): STRING
|
||||
do
|
||||
create Result.make_empty
|
||||
Result.append ("{%"typ%":%"")
|
||||
if a_type /= Void then
|
||||
Result.append (a_type)
|
||||
else
|
||||
Result.append ("JWT")
|
||||
end
|
||||
Result.append ("%",%"alg%":%"")
|
||||
Result.append (alg)
|
||||
Result.append ("%"}")
|
||||
end
|
||||
|
||||
feature {NONE} -- Conversion
|
||||
|
||||
signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8
|
||||
local
|
||||
jp: JSON_PARSER
|
||||
do
|
||||
create jp.make_with_string (base64url_decode (a_enc_header))
|
||||
jp.parse_content
|
||||
if
|
||||
attached jp.parsed_json_object as jo and then
|
||||
attached {JSON_STRING} jo.item ("alg") as j_alg
|
||||
then
|
||||
Result := j_alg.unescaped_string_8
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Base64
|
||||
|
||||
base64url_encode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
urlencoder: URL_ENCODER
|
||||
base64: BASE64
|
||||
do
|
||||
create urlencoder
|
||||
create base64
|
||||
Result := urlsafe_encode (base64.encoded_string (s))
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
if alg = alg_none then
|
||||
create Result.make_empty
|
||||
else
|
||||
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
|
||||
s.append (a_enc_header)
|
||||
s.append_character ('.')
|
||||
s.append (a_enc_payload)
|
||||
if alg = alg_hs256 then
|
||||
Result := base64_hmacsha256 (s, a_secret)
|
||||
else
|
||||
Result := base64_hmacsha256 (s, a_secret)
|
||||
end
|
||||
Result := urlsafe_encode (Result)
|
||||
end
|
||||
end
|
||||
|
||||
base64url_decode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
urlencoder: URL_ENCODER
|
||||
base64: BASE64
|
||||
do
|
||||
create urlencoder
|
||||
create base64
|
||||
Result := base64.decoded_string (urlsafe_decode (s))
|
||||
end
|
||||
|
||||
urlsafe_encode (s: READABLE_STRING_8): STRING_8
|
||||
do
|
||||
create Result.make_from_string (s)
|
||||
Result.replace_substring_all ("=", "")
|
||||
Result.replace_substring_all ("+", "-")
|
||||
Result.replace_substring_all ("/", "_")
|
||||
end
|
||||
|
||||
urlsafe_decode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
i: INTEGER
|
||||
do
|
||||
create Result.make_from_string (s)
|
||||
Result.replace_substring_all ("-", "+")
|
||||
Result.replace_substring_all ("_", "/")
|
||||
from
|
||||
i := Result.count \\ 4
|
||||
until
|
||||
i = 0
|
||||
loop
|
||||
i := i - 1
|
||||
Result.extend ('=')
|
||||
end
|
||||
end
|
||||
|
||||
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
hs256: HMAC_SHA256
|
||||
do
|
||||
create hs256.make_ascii_key (a_secret)
|
||||
hs256.update_from_string (s)
|
||||
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
|
||||
end
|
||||
|
||||
end
|
||||
19
library/security/jwt/src/jwt_error.e
Normal file
19
library/security/jwt/src/jwt_error.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "Summary description for {JWT_ERROR}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
JWT_ERROR
|
||||
|
||||
feature -- Access
|
||||
|
||||
id: STRING
|
||||
deferred
|
||||
end
|
||||
|
||||
message: READABLE_STRING_8
|
||||
deferred
|
||||
end
|
||||
|
||||
end
|
||||
117
library/security/jwt/src/jwt_header.e
Normal file
117
library/security/jwt/src/jwt_header.e
Normal file
@@ -0,0 +1,117 @@
|
||||
note
|
||||
description: "[
|
||||
JOSE Header
|
||||
|
||||
See https://tools.ietf.org/html/rfc7515
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_HEADER
|
||||
|
||||
inherit
|
||||
ANY
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
create
|
||||
default_create,
|
||||
make_from_json
|
||||
|
||||
convert
|
||||
string: {READABLE_STRING_8, STRING_8}
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
default_create
|
||||
do
|
||||
type := "JWT"
|
||||
algorithm := "HS256"
|
||||
end
|
||||
|
||||
make_from_json (a_json: READABLE_STRING_8)
|
||||
do
|
||||
default_create
|
||||
import_json (a_json)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
type: READABLE_STRING_8
|
||||
-- Token type (typ) - If present, it is recommended to set this to "JWT".
|
||||
|
||||
content_type: detachable READABLE_STRING_8
|
||||
-- Content type (cty)
|
||||
-- If nested signing or encryption is employed, it is recommended to set this to JWT,
|
||||
-- otherwise omit this field.
|
||||
|
||||
algorithm: READABLE_STRING_8
|
||||
-- Message authentication code algorithm (alg)
|
||||
-- The issuer can freely set an algorithm to verify the signature on the token.
|
||||
-- However, some supported algorithms are insecure.
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
string: STRING
|
||||
do
|
||||
create Result.make_empty
|
||||
Result.append ("{%"typ%":%"")
|
||||
Result.append (type)
|
||||
Result.append ("%"")
|
||||
if attached content_type as cty then
|
||||
Result.append (",%"cty%":%"")
|
||||
Result.append (cty)
|
||||
Result.append ("%"")
|
||||
end
|
||||
Result.append (",%"alg%":%"")
|
||||
Result.append (algorithm)
|
||||
Result.append ("%"}")
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_type (typ: READABLE_STRING_8)
|
||||
do
|
||||
type := typ
|
||||
end
|
||||
|
||||
set_content_type (cty: detachable READABLE_STRING_8)
|
||||
do
|
||||
content_type := cty
|
||||
end
|
||||
|
||||
set_algorithm (alg: detachable READABLE_STRING_8)
|
||||
do
|
||||
if alg = Void then
|
||||
algorithm := "none"
|
||||
else
|
||||
algorithm := alg
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
import_json (a_json: READABLE_STRING_8)
|
||||
local
|
||||
jp: JSON_PARSER
|
||||
do
|
||||
create jp.make_with_string (a_json)
|
||||
jp.parse_content
|
||||
if
|
||||
attached jp.parsed_json_object as jo
|
||||
then
|
||||
if attached {JSON_STRING} jo.item ("typ") as j_typ then
|
||||
set_type (j_typ.unescaped_string_8)
|
||||
end
|
||||
if attached {JSON_STRING} jo.item ("cty") as j_cty then
|
||||
set_content_type (j_cty.unescaped_string_8)
|
||||
end
|
||||
if attached {JSON_STRING} jo.item ("alg") as j_alg then
|
||||
set_algorithm (j_alg.unescaped_string_8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
108
library/security/jwt/src/jwt_loader.e
Normal file
108
library/security/jwt/src/jwt_loader.e
Normal file
@@ -0,0 +1,108 @@
|
||||
note
|
||||
description: "Loader and verifier to JWT token."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=Known Critical vulnerabilities in JWT libs", "protocol=URI", "src=https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/"
|
||||
|
||||
class
|
||||
JWT_LOADER
|
||||
|
||||
inherit
|
||||
JWT_UTILITIES
|
||||
|
||||
feature -- Access
|
||||
|
||||
token (a_token_input: READABLE_STRING_8; a_alg: detachable READABLE_STRING_8; a_verification_key: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
|
||||
-- Decoded token from `a_token_input` given the verification key `a_verification_key` and optional (but recommended) signature algorithm `a_alg`, and optional context `ctx`
|
||||
-- used to specify eventual issuer and various parameters.
|
||||
-- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens,
|
||||
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
|
||||
require
|
||||
a_valid_alg: a_alg /= Void implies is_supporting_signature_algorithm (a_alg)
|
||||
local
|
||||
jws: JWS
|
||||
i,j,n: INTEGER
|
||||
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
|
||||
do
|
||||
n := a_token_input.count
|
||||
i := a_token_input.index_of ('.', 1)
|
||||
if i > 0 then
|
||||
j := a_token_input.index_of ('.', i + 1)
|
||||
if j > 0 then
|
||||
l_enc_header := a_token_input.substring (1, i - 1)
|
||||
l_enc_payload := a_token_input.substring (i + 1, j - 1)
|
||||
l_signature := a_token_input.substring (j + 1, n)
|
||||
create jws.make_with_json_payload (base64url_decode (l_enc_payload))
|
||||
alg := signature_algorithm_from_encoded_header (l_enc_header)
|
||||
if a_alg /= Void then
|
||||
if alg /= Void and then not alg.is_case_insensitive_equal_general (a_alg) then
|
||||
jws.report_mismatched_alg_error (a_alg, alg)
|
||||
else
|
||||
alg := a_alg
|
||||
end
|
||||
else
|
||||
if alg = Void then
|
||||
-- Use default
|
||||
alg := alg_hs256
|
||||
end
|
||||
end
|
||||
jws.set_algorithm (alg)
|
||||
check alg_set: alg /= Void end
|
||||
if ctx = Void or else not ctx.validation_ignored then
|
||||
if not is_supporting_signature_algorithm (alg) then
|
||||
jws.report_unsupported_alg_error (alg)
|
||||
alg := alg_hs256
|
||||
end
|
||||
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg)) then
|
||||
jws.report_unverified_token_error
|
||||
end
|
||||
if
|
||||
not jws.has_error and then
|
||||
ctx /= Void
|
||||
then
|
||||
check not ctx.validation_ignored end
|
||||
if jws.is_expired (ctx.time) then
|
||||
jws.report_claim_validation_error ("exp")
|
||||
end
|
||||
if not jws.is_nbf_validated (ctx.time) then
|
||||
jws.report_claim_validation_error ("nbf")
|
||||
end
|
||||
if
|
||||
not jws.is_iss_validated (ctx.issuer)
|
||||
then
|
||||
jws.report_claim_validation_error ("iss")
|
||||
end
|
||||
if
|
||||
not jws.is_aud_validated (ctx.audience)
|
||||
then
|
||||
jws.report_claim_validation_error ("aud")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
else
|
||||
-- jws.report_invalid_token
|
||||
end
|
||||
else
|
||||
-- jws.report_invalid_token
|
||||
end
|
||||
Result := jws
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8
|
||||
local
|
||||
jp: JSON_PARSER
|
||||
do
|
||||
create jp.make_with_string (base64url_decode (a_enc_header))
|
||||
jp.parse_content
|
||||
if
|
||||
attached jp.parsed_json_object as jo and then
|
||||
attached {JSON_STRING} jo.item ("alg") as j_alg
|
||||
then
|
||||
Result := j_alg.unescaped_string_8
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
112
library/security/jwt/src/jwt_utilities.e
Normal file
112
library/security/jwt/src/jwt_utilities.e
Normal file
@@ -0,0 +1,112 @@
|
||||
note
|
||||
description: "Summary description for {JWT_UTILITIES}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
JWT_UTILITIES
|
||||
|
||||
feature -- Constants
|
||||
|
||||
alg_hs256: STRING = "HS256"
|
||||
-- HMAC SHA256.
|
||||
|
||||
alg_none: STRING = "none"
|
||||
-- for unsecured token.
|
||||
|
||||
feature -- Encoding
|
||||
|
||||
base64url_encode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
urlencoder: URL_ENCODER
|
||||
base64: BASE64
|
||||
do
|
||||
create urlencoder
|
||||
create base64
|
||||
Result := urlsafe_encode (base64.encoded_string (s))
|
||||
end
|
||||
|
||||
urlsafe_encode (s: READABLE_STRING_8): STRING_8
|
||||
do
|
||||
create Result.make_from_string (s)
|
||||
Result.replace_substring_all ("=", "")
|
||||
Result.replace_substring_all ("+", "-")
|
||||
Result.replace_substring_all ("/", "_")
|
||||
end
|
||||
|
||||
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
if alg.is_case_insensitive_equal (alg_none) then
|
||||
create Result.make_empty
|
||||
else
|
||||
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
|
||||
s.append (a_enc_header)
|
||||
s.append_character ('.')
|
||||
s.append (a_enc_payload)
|
||||
if alg.is_case_insensitive_equal (alg_hs256) then
|
||||
Result := base64_hmacsha256 (s, a_secret)
|
||||
else
|
||||
Result := base64_hmacsha256 (s, a_secret)
|
||||
end
|
||||
Result := urlsafe_encode (Result)
|
||||
end
|
||||
end
|
||||
|
||||
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
hs256: HMAC_SHA256
|
||||
do
|
||||
create hs256.make_ascii_key (a_secret)
|
||||
hs256.update_from_string (s)
|
||||
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
|
||||
end
|
||||
|
||||
feature -- Decoding
|
||||
|
||||
base64url_decode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
urlencoder: URL_ENCODER
|
||||
base64: BASE64
|
||||
do
|
||||
create urlencoder
|
||||
create base64
|
||||
Result := base64.decoded_string (urlsafe_decode (s))
|
||||
end
|
||||
|
||||
urlsafe_decode (s: READABLE_STRING_8): STRING_8
|
||||
local
|
||||
i: INTEGER
|
||||
do
|
||||
create Result.make_from_string (s)
|
||||
Result.replace_substring_all ("-", "+")
|
||||
Result.replace_substring_all ("_", "/")
|
||||
from
|
||||
i := Result.count \\ 4
|
||||
until
|
||||
i = 0
|
||||
loop
|
||||
i := i - 1
|
||||
Result.extend ('=')
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Signature
|
||||
|
||||
supported_signature_algorithms: LIST [READABLE_STRING_8]
|
||||
-- Supported signature algorithm `alg`?
|
||||
do
|
||||
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (2)
|
||||
Result.extend (alg_hs256)
|
||||
Result.extend (alg_none)
|
||||
end
|
||||
|
||||
is_supporting_signature_algorithm (alg: READABLE_STRING_8): BOOLEAN
|
||||
-- Is supporting signature algorithm `alg`?
|
||||
do
|
||||
Result := across supported_signature_algorithms as ic some alg.is_case_insensitive_equal (ic.item) end
|
||||
end
|
||||
|
||||
end
|
||||
224
library/security/jwt/testing/test_jwt.e
Normal file
224
library/security/jwt/testing/test_jwt.e
Normal file
@@ -0,0 +1,224 @@
|
||||
note
|
||||
description: "Summary description for {TEST_JWT}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
TEST_JWT
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
undefine
|
||||
default_create
|
||||
end
|
||||
|
||||
feature -- Test
|
||||
|
||||
test_jwt_io
|
||||
local
|
||||
jwt: JWS
|
||||
ut: JWT_UTILITIES
|
||||
do
|
||||
create jwt
|
||||
jwt.set_algorithm ("HS256")
|
||||
jwt.claimset.set_subject ("1234567890")
|
||||
jwt.claimset.set_claim ("name", "John Doe")
|
||||
jwt.claimset.set_claim ("admin", True)
|
||||
create ut
|
||||
|
||||
assert ("header", ut.base64url_encode (jwt.header.string).same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"))
|
||||
assert ("payload", ut.base64url_encode (jwt.claimset.string).same_string ("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"))
|
||||
assert ("signature", jwt.encoded_string ("secret").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
|
||||
end
|
||||
|
||||
test_jwt
|
||||
local
|
||||
jwt: JWS
|
||||
jwt_loader: JWT_LOADER
|
||||
payload: STRING
|
||||
tok: STRING
|
||||
do
|
||||
payload := "[
|
||||
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
|
||||
]"
|
||||
|
||||
-- payload := "[
|
||||
-- {"sub":"1234567890","name":"John Doe","admin":true}
|
||||
-- ]"
|
||||
|
||||
create jwt.make_with_json_payload (payload)
|
||||
jwt.set_algorithm ("HS256")
|
||||
tok := jwt.encoded_string ("secret")
|
||||
|
||||
create jwt_loader
|
||||
|
||||
-- Use header alg!
|
||||
if attached jwt_loader.token (tok, Void, "secret", Void) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
|
||||
-- Use given alg!
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
end
|
||||
|
||||
test_jwt_with_claimset
|
||||
local
|
||||
jwt: JWS
|
||||
jwt_loader: JWT_LOADER
|
||||
payload: STRING
|
||||
tok: STRING
|
||||
now, dt: DATE_TIME
|
||||
ctx: JWT_CONTEXT
|
||||
do
|
||||
-- payload := "[
|
||||
-- {"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
|
||||
-- ]"
|
||||
|
||||
payload := "[
|
||||
{"sub":"1234567890","name":"John Doe","admin":true}
|
||||
]"
|
||||
|
||||
create jwt.make_with_json_payload (payload)
|
||||
jwt.set_algorithm ("HS256")
|
||||
create now.make_now_utc
|
||||
jwt.claimset.set_issued_at (now)
|
||||
|
||||
dt := duplicated_time (now)
|
||||
dt.minute_add (60)
|
||||
jwt.claimset.set_expiration_time (dt)
|
||||
|
||||
jwt.claimset.set_issuer ("urn:foo")
|
||||
jwt.claimset.set_audience ("urn:foo")
|
||||
|
||||
tok := jwt.encoded_string ("secret")
|
||||
|
||||
payload := jwt.claimset.string
|
||||
|
||||
create jwt_loader
|
||||
|
||||
-- Test with validation + exp
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
|
||||
create ctx
|
||||
ctx.set_time (now)
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
end
|
||||
|
||||
dt := duplicated_time (now)
|
||||
dt.hour_add (5)
|
||||
ctx.set_time (dt)
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("exp error", l_tok.has_error)
|
||||
end
|
||||
|
||||
-- Test with validation + not before
|
||||
|
||||
dt := duplicated_time (now)
|
||||
dt.second_add (30)
|
||||
jwt.claimset.set_not_before_time (dt)
|
||||
tok := jwt.encoded_string ("secret")
|
||||
|
||||
ctx.set_time (now)
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("has nbf error", l_tok.has_error)
|
||||
end
|
||||
|
||||
dt := duplicated_time (now)
|
||||
dt.second_add (15)
|
||||
ctx.set_time (dt)
|
||||
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("has nbf error", l_tok.has_error)
|
||||
end
|
||||
|
||||
dt := duplicated_time (now)
|
||||
dt.minute_add (45)
|
||||
ctx.set_time (dt)
|
||||
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
end
|
||||
|
||||
-- Test Issuer
|
||||
ctx.set_issuer ("urn:foobar")
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("has iss error", l_tok.has_error)
|
||||
end
|
||||
ctx.set_issuer ("urn:foo")
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
end
|
||||
|
||||
-- Test Audience
|
||||
ctx.set_audience ("urn:foobar")
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("has aud error", l_tok.has_error)
|
||||
end
|
||||
ctx.set_audience ("urn:foo")
|
||||
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
|
||||
assert ("no error", not l_tok.has_error)
|
||||
end
|
||||
end
|
||||
|
||||
test_mismatched_alg_jwt
|
||||
local
|
||||
jwt: JWS
|
||||
payload: STRING
|
||||
tok: STRING
|
||||
do
|
||||
payload := "[
|
||||
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
|
||||
]"
|
||||
|
||||
create jwt.make_with_json_payload (payload)
|
||||
jwt.set_algorithm ("none")
|
||||
tok := jwt.encoded_string ("secret")
|
||||
|
||||
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
|
||||
assert ("no error", not jwt.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
end
|
||||
|
||||
test_unsecured_jwt
|
||||
local
|
||||
jwt: JWS
|
||||
payload: STRING
|
||||
tok: STRING
|
||||
do
|
||||
payload := "[
|
||||
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
|
||||
]"
|
||||
|
||||
create jwt.make_with_json_payload (payload)
|
||||
jwt.set_algorithm ("none")
|
||||
tok := jwt.encoded_string ("secret")
|
||||
|
||||
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
|
||||
assert ("no error", not jwt.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
|
||||
assert ("no error", not jwt.has_error)
|
||||
assert ("same payload", l_tok.claimset.string.same_string (payload))
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Implementation
|
||||
|
||||
duplicated_time (dt: DATE_TIME): DATE_TIME
|
||||
do
|
||||
Result := dt.deep_twin
|
||||
end
|
||||
|
||||
end
|
||||
15
library/security/jwt/testing/testing.ecf
Normal file
15
library/security/jwt/testing/testing.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="testing" uuid="DB49E98A-0048-414A-A469-EE9B5B903BF3">
|
||||
<target name="testing">
|
||||
<root class="ANY" feature="default_create"/>
|
||||
<setting name="console_application" value="true"/>
|
||||
<option warning="true" void_safety="all">
|
||||
</option>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="jwt" location="..\jwt-safe.ecf" readonly="false"/>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<tests name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -167,7 +167,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_socket_timeout (a_nb_seconds: like socket_timeout)
|
||||
-- Set `socket_timeout' with `a_nb_seconds'
|
||||
-- Set `socket_timeout' with `a_nb_seconds'.
|
||||
do
|
||||
socket_timeout := a_nb_seconds
|
||||
ensure
|
||||
@@ -175,7 +175,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_socket_recv_timeout (a_nb_seconds: like socket_recv_timeout)
|
||||
-- Set `socket_recv_timeout' with `a_nb_seconds'
|
||||
-- Set `socket_recv_timeout' with `a_nb_seconds'.
|
||||
do
|
||||
socket_recv_timeout := a_nb_seconds
|
||||
ensure
|
||||
@@ -183,7 +183,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
|
||||
-- Set `keep_alive_timeout' with `a_seconds'
|
||||
-- Set `keep_alive_timeout' with `a_seconds'.
|
||||
do
|
||||
keep_alive_timeout := a_seconds
|
||||
ensure
|
||||
@@ -191,7 +191,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
|
||||
-- Set `max_keep_alive_requests' with `nb'
|
||||
-- Set `max_keep_alive_requests' with `nb'.
|
||||
do
|
||||
max_keep_alive_requests := nb
|
||||
ensure
|
||||
@@ -254,7 +254,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
mark_secure
|
||||
-- Set is_secure in True
|
||||
-- Set is_secure in True.
|
||||
do
|
||||
set_is_secure (True)
|
||||
ensure
|
||||
@@ -287,7 +287,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_secure_protocol (a_version: NATURAL)
|
||||
-- Set `secure_protocol' with `a_version'
|
||||
-- Set `secure_protocol' with `a_version'.
|
||||
do
|
||||
secure_protocol := a_version
|
||||
ensure
|
||||
@@ -295,7 +295,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
set_secure_protocol_from_string (a_ssl_version: READABLE_STRING_GENERAL)
|
||||
-- Set `secure_protocol' with `a_ssl_version'
|
||||
-- Set `secure_protocol' with `a_ssl_version'.
|
||||
do
|
||||
if a_ssl_version.is_case_insensitive_equal ("ssl_2_3") then
|
||||
set_secure_protocol_to_ssl_2_or_3
|
||||
@@ -316,6 +316,8 @@ feature -- SSL Helpers
|
||||
|
||||
set_secure_protocol_to_ssl_2_or_3
|
||||
-- Set `secure_protocol' with `Ssl_23'.
|
||||
obsolete
|
||||
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
|
||||
deferred
|
||||
end
|
||||
|
||||
|
||||
@@ -36,9 +36,16 @@ feature -- Access
|
||||
feature -- SSL Helpers
|
||||
|
||||
set_secure_protocol_to_ssl_2_or_3
|
||||
-- Set `secure_protocol' with `Ssl_23'.
|
||||
-- Set `ssl_protocol' with `Ssl_23'.
|
||||
-- Protocol not supported anymore.
|
||||
obsolete
|
||||
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
|
||||
local
|
||||
err: DEVELOPER_EXCEPTION
|
||||
do
|
||||
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
|
||||
create err
|
||||
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
|
||||
err.raise
|
||||
end
|
||||
|
||||
set_secure_protocol_to_tls_1_0
|
||||
@@ -67,7 +74,7 @@ feature -- SSL Helpers
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -57,7 +57,8 @@ feature -- Query
|
||||
-- if possible
|
||||
do
|
||||
if attached value as v then
|
||||
Result := generating_type.name_32
|
||||
-- FIXME: in the future, use the new `{TYPE}.name_32`
|
||||
Result := generating_type.name.to_string_32
|
||||
else
|
||||
Result := {STRING_32} "Void"
|
||||
end
|
||||
|
||||
@@ -28,17 +28,17 @@ feature -- Access
|
||||
end
|
||||
|
||||
message: detachable READABLE_STRING_32
|
||||
-- Potential error message
|
||||
-- Potential error message.
|
||||
deferred
|
||||
end
|
||||
|
||||
parent: detachable ERROR
|
||||
-- Eventual error prior to Current
|
||||
-- Eventual error prior to Current.
|
||||
|
||||
feature -- String representation
|
||||
|
||||
string_representation: STRING_32
|
||||
-- String representation for Current
|
||||
-- String representation for Current.
|
||||
do
|
||||
create Result.make_from_string (name.as_string_32)
|
||||
Result.append_character (' ')
|
||||
@@ -62,7 +62,7 @@ feature -- Status report
|
||||
feature -- Change
|
||||
|
||||
set_parent (a_parent: like parent)
|
||||
-- Set `parent' to `a_parent'
|
||||
-- Set `parent' to `a_parent'.
|
||||
do
|
||||
parent := a_parent
|
||||
end
|
||||
@@ -80,7 +80,7 @@ invariant
|
||||
name_attached: name /= Void
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -30,28 +30,24 @@ feature -- Access
|
||||
|
||||
name: STRING
|
||||
|
||||
message: detachable STRING_32
|
||||
message: STRING_32
|
||||
do
|
||||
create Result.make_from_string (name)
|
||||
from
|
||||
sub_errors.start
|
||||
until
|
||||
sub_errors.after
|
||||
across
|
||||
sub_errors as s
|
||||
loop
|
||||
if
|
||||
attached sub_errors.item as e and then
|
||||
attached s.item as e and then
|
||||
attached e.message as m
|
||||
then
|
||||
|
||||
Result.append_character ('%N')
|
||||
Result.append_string (m)
|
||||
end
|
||||
sub_errors.forth
|
||||
end
|
||||
end
|
||||
|
||||
sub_errors: LIST [ERROR]
|
||||
-- Error contained by Current
|
||||
-- Error contained by Current.
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
@@ -61,9 +57,8 @@ feature -- Visitor
|
||||
a_visitor.process_group (Current)
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
note
|
||||
description : "[
|
||||
Error handler or receiver.
|
||||
|
||||
]"
|
||||
description : "Error handler or receiver."
|
||||
legal: "See notice at end of class."
|
||||
status: "See notice at end of class."
|
||||
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
|
||||
revision: "$Revision: 97980 $"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
ERROR_HANDLER
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
DEBUG_OUTPUT
|
||||
|
||||
create
|
||||
@@ -46,7 +41,7 @@ feature -- Access
|
||||
-- Optional identifier for Current handler.
|
||||
|
||||
primary_error_code: INTEGER
|
||||
-- Code of first error in `errors'
|
||||
-- Code of first error in `errors'.
|
||||
require
|
||||
at_least_one_error: has_error
|
||||
do
|
||||
@@ -62,7 +57,7 @@ feature -- Status
|
||||
end
|
||||
|
||||
count: INTEGER
|
||||
-- Number of error
|
||||
-- Number of error.
|
||||
do
|
||||
Result := errors.count
|
||||
end
|
||||
@@ -83,7 +78,7 @@ feature -- Status
|
||||
feature {ERROR_HANDLER, ERROR_VISITOR} -- Restricted access
|
||||
|
||||
errors: LIST [ERROR]
|
||||
-- Errors container
|
||||
-- Errors container.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -119,12 +114,12 @@ feature -- Status report
|
||||
feature -- Events
|
||||
|
||||
error_added_actions: ACTION_SEQUENCE [TUPLE [ERROR]]
|
||||
-- Actions triggered when a new error is added
|
||||
-- Actions triggered when a new error is added.
|
||||
|
||||
feature -- Synchronization
|
||||
|
||||
add_synchronization (h: ERROR_HANDLER)
|
||||
-- Add synchronization between `h' and `Current'
|
||||
-- Add synchronization between `h' and `Current`.
|
||||
--| the same handler can be added more than once
|
||||
--| it will be synchronized only once
|
||||
do
|
||||
@@ -133,7 +128,7 @@ feature -- Synchronization
|
||||
end
|
||||
|
||||
remove_synchronization (h: ERROR_HANDLER)
|
||||
-- Remove synchronization between `h' and `Current'
|
||||
-- Remove synchronization between `h' and `Current'.
|
||||
do
|
||||
remove_propagation (h)
|
||||
h.remove_propagation (Current)
|
||||
@@ -347,7 +342,7 @@ feature {NONE} -- Event: implementation
|
||||
end
|
||||
|
||||
on_reset
|
||||
-- `reset' was just called
|
||||
-- `reset' was just called.
|
||||
local
|
||||
sync_list: detachable ARRAYED_LIST [ERROR_HANDLER]
|
||||
lst: detachable LIST [ERROR_HANDLER]
|
||||
@@ -389,7 +384,7 @@ feature {NONE} -- Event: implementation
|
||||
feature -- Basic operation
|
||||
|
||||
add_error (a_error: ERROR)
|
||||
-- Add `a_error' to the stack of error
|
||||
-- Add `a_error' to the stack of error.
|
||||
do
|
||||
errors.force (a_error)
|
||||
on_error_added (a_error)
|
||||
@@ -406,28 +401,22 @@ feature -- Basic operation
|
||||
end
|
||||
|
||||
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_GENERAL)
|
||||
-- Add custom error to the stack of error
|
||||
local
|
||||
e: ERROR_CUSTOM
|
||||
-- Add custom error to the stack of error.
|
||||
do
|
||||
create e.make (a_code, a_name, a_message)
|
||||
add_error (e)
|
||||
add_error (create {ERROR_CUSTOM}.make (a_code, a_name, a_message))
|
||||
end
|
||||
|
||||
append (other: ERROR_HANDLER)
|
||||
-- Append errors from `a_err_handler'
|
||||
-- Append errors from `a_err_handler'.
|
||||
local
|
||||
other_errs: LIST [ERROR]
|
||||
do
|
||||
other_errs := other.errors
|
||||
if other_errs.count > 0 then
|
||||
from
|
||||
other_errs.start
|
||||
until
|
||||
other_errs.after
|
||||
if other_errs /= errors and then other_errs.count > 0 then
|
||||
across
|
||||
other_errs as e
|
||||
loop
|
||||
add_error (other_errs.item)
|
||||
other_errs.forth
|
||||
add_error (e.item)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
@@ -435,7 +424,7 @@ feature -- Basic operation
|
||||
new_count: count = old count + other.count
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
feature -- Conversion
|
||||
|
||||
as_single_error: detachable ERROR
|
||||
-- All error(s) concatenated into one single error.
|
||||
@@ -465,7 +454,7 @@ feature -- Access
|
||||
feature -- Element changes
|
||||
|
||||
concatenate
|
||||
-- Concatenate into a single error if any
|
||||
-- Concatenate into a single error if any.
|
||||
do
|
||||
if count > 1 and then attached as_single_error as e then
|
||||
reset
|
||||
@@ -516,7 +505,7 @@ invariant
|
||||
propagators_not_empty: attached propagators as lst implies not lst.is_empty
|
||||
|
||||
note
|
||||
copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -25,19 +25,16 @@ feature -- Access
|
||||
process_group (g: ERROR_GROUP)
|
||||
do
|
||||
if attached g.sub_errors as err then
|
||||
from
|
||||
err.start
|
||||
until
|
||||
err.after
|
||||
across
|
||||
err as e
|
||||
loop
|
||||
process_error (err.item)
|
||||
err.forth
|
||||
process_error (e.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -16,7 +16,7 @@ inherit
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Initialization
|
||||
feature {NONE} -- Creation
|
||||
|
||||
make (f: like file)
|
||||
require
|
||||
@@ -32,7 +32,7 @@ feature -- Access
|
||||
feature -- Output
|
||||
|
||||
output_string (a_str: detachable READABLE_STRING_GENERAL)
|
||||
-- Output Unicode string
|
||||
-- Output Unicode string.
|
||||
do
|
||||
if a_str /= Void then
|
||||
to_implement ("Convert into UTF-8 or console encoding before output")
|
||||
@@ -51,7 +51,7 @@ feature -- Output
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -14,12 +14,12 @@ inherit
|
||||
feature -- Output
|
||||
|
||||
output_string (a_str: detachable READABLE_STRING_GENERAL)
|
||||
-- Output Unicode string
|
||||
-- Output Unicode string.
|
||||
deferred
|
||||
end
|
||||
|
||||
output_any (obj: detachable ANY)
|
||||
-- Output Unicode string
|
||||
-- Output Unicode string.
|
||||
do
|
||||
if attached {READABLE_STRING_GENERAL} obj as l_str then
|
||||
to_implement ("Convert into UTF-8 or console encoding before output")
|
||||
@@ -42,6 +42,7 @@ feature -- Output
|
||||
feature -- Process
|
||||
|
||||
process_error (e: ERROR)
|
||||
-- <Precursor>
|
||||
do
|
||||
output_string ({STRING_32}"Error Name: ")
|
||||
output_string (e.name)
|
||||
@@ -54,6 +55,7 @@ feature -- Process
|
||||
end
|
||||
|
||||
process_custom (e: ERROR_CUSTOM)
|
||||
-- <Precursor>
|
||||
do
|
||||
output_string ({STRING_32}"Error Name: ")
|
||||
output_string (e.name)
|
||||
@@ -66,22 +68,19 @@ feature -- Process
|
||||
end
|
||||
|
||||
process_group (g: ERROR_GROUP)
|
||||
-- <Precursor>
|
||||
local
|
||||
l_errors: LIST [ERROR]
|
||||
do
|
||||
from
|
||||
l_errors := g.sub_errors
|
||||
l_errors.start
|
||||
until
|
||||
l_errors.after
|
||||
across
|
||||
g.sub_errors as s
|
||||
loop
|
||||
l_errors.item.process (Current)
|
||||
l_errors.forth
|
||||
s.item.process (Current)
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -16,7 +16,7 @@ inherit
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Initialization
|
||||
feature {NONE} -- Creation
|
||||
|
||||
make (buf: like buffer)
|
||||
require
|
||||
@@ -32,7 +32,7 @@ feature -- Access
|
||||
feature -- Output
|
||||
|
||||
output_string (a_str: detachable READABLE_STRING_GENERAL)
|
||||
-- Output Unicode string
|
||||
-- Output Unicode string.
|
||||
do
|
||||
if a_str /= Void then
|
||||
to_implement ("Convert into UTF-8 or console encoding before output")
|
||||
@@ -51,7 +51,7 @@ feature -- Output
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Eiffel Software and others"
|
||||
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -100,6 +100,10 @@ echo Install library: http_authorization
|
||||
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
|
||||
%COPYCMD% %TMP_DIR%\library\server\authentication\http_authorization %TMP_CONTRIB_DIR%\library\web\authentication\http_authorization
|
||||
|
||||
echo Install library: jwt
|
||||
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
|
||||
%COPYCMD% %TMP_DIR%\library\security\jwt %TMP_CONTRIB_DIR%\library\web\authentication\jwt
|
||||
|
||||
echo Install library: openid
|
||||
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
|
||||
%COPYCMD% %TMP_DIR%\library\security\openid %TMP_CONTRIB_DIR%\library\web\authentication\openid
|
||||
|
||||
1
tools/requestbin/README.md
Normal file
1
tools/requestbin/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This example is a simple service that analyze the request and return a formatted output of the request data (query parameters, form parameters, environment variables, and so on). It could be used to debug client, or to experiment the EiffelWeb behavior on various connectors (standalone, apache, iis, ...).
|
||||
19
tools/requestbin/launcher/any/application_launcher.e
Normal file
19
tools/requestbin/launcher/any/application_launcher.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "[
|
||||
Effective class for APPLICATION_LAUNCHER_I
|
||||
|
||||
You can put modification in this class
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
class
|
||||
APPLICATION_LAUNCHER [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
APPLICATION_LAUNCHER_I [G]
|
||||
|
||||
feature -- Custom
|
||||
|
||||
end
|
||||
|
||||
102
tools/requestbin/launcher/any/application_launcher_i.e
Normal file
102
tools/requestbin/launcher/any/application_launcher_i.e
Normal file
@@ -0,0 +1,102 @@
|
||||
note
|
||||
description: "[
|
||||
Specific application launcher
|
||||
|
||||
DO NOT EDIT THIS CLASS
|
||||
|
||||
you can customize APPLICATION_LAUNCHER
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
deferred class
|
||||
APPLICATION_LAUNCHER_I [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
feature -- Execution
|
||||
|
||||
launch (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
nature: like launcher_nature
|
||||
do
|
||||
nature := launcher_nature
|
||||
if nature = Void or else nature = nature_standalone then
|
||||
launch_standalone (opts)
|
||||
elseif nature = nature_cgi then
|
||||
launch_cgi (opts)
|
||||
elseif nature = nature_libfcgi then
|
||||
launch_libfcgi (opts)
|
||||
else
|
||||
-- bye bye
|
||||
(create {EXCEPTIONS}).die (-1)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Access
|
||||
|
||||
launcher_nature: detachable READABLE_STRING_8
|
||||
-- Initialize the launcher nature
|
||||
-- either cgi, libfcgi, or standalone.
|
||||
--| We could extend with more connector if needed.
|
||||
--| and we could use WSF_DEFAULT_SERVICE_LAUNCHER to configure this at compilation time.
|
||||
local
|
||||
p: PATH
|
||||
ext: detachable READABLE_STRING_32
|
||||
do
|
||||
create p.make_from_string (execution_environment.arguments.command_name)
|
||||
if attached p.entry as l_entry then
|
||||
ext := l_entry.extension
|
||||
end
|
||||
if ext /= Void then
|
||||
if ext.same_string (nature_standalone) then
|
||||
Result := nature_standalone
|
||||
end
|
||||
if ext.same_string (nature_cgi) then
|
||||
Result := nature_cgi
|
||||
end
|
||||
if ext.same_string (nature_libfcgi) or else ext.same_string ("fcgi") then
|
||||
Result := nature_libfcgi
|
||||
end
|
||||
end
|
||||
Result := nature_standalone
|
||||
end
|
||||
|
||||
feature {NONE} -- Standalone
|
||||
|
||||
nature_standalone: STRING = "standalone"
|
||||
|
||||
launch_standalone (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature {NONE} -- cgi
|
||||
|
||||
nature_cgi: STRING = "cgi"
|
||||
|
||||
launch_cgi (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_CGI_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature {NONE} -- libfcgi
|
||||
|
||||
nature_libfcgi: STRING = "libfcgi"
|
||||
|
||||
launch_libfcgi (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_LIBFCGI_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
19
tools/requestbin/launcher/default/application_launcher.e
Normal file
19
tools/requestbin/launcher/default/application_launcher.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "[
|
||||
Effective class for APPLICATION_LAUNCHER_I
|
||||
|
||||
You can put modification in this class
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
class
|
||||
APPLICATION_LAUNCHER [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
APPLICATION_LAUNCHER_I [G]
|
||||
|
||||
feature -- Custom
|
||||
|
||||
end
|
||||
|
||||
26
tools/requestbin/launcher/default/application_launcher_i.e
Normal file
26
tools/requestbin/launcher/default/application_launcher_i.e
Normal file
@@ -0,0 +1,26 @@
|
||||
note
|
||||
description: "[
|
||||
Specific application launcher
|
||||
|
||||
DO NOT EDIT THIS CLASS
|
||||
|
||||
you can customize APPLICATION_LAUNCHER
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
deferred class
|
||||
APPLICATION_LAUNCHER_I [G -> WSF_EXECUTION create make end]
|
||||
|
||||
feature -- Execution
|
||||
|
||||
launch (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create {WSF_DEFAULT_SERVICE_LAUNCHER [G]} launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
48
tools/requestbin/requestbin.ecf
Normal file
48
tools/requestbin/requestbin.ecf
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="requestbin" uuid="FF1E47E3-7677-4E1C-9459-2E2C2341325A">
|
||||
<target name="common" abstract="true">
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\library\text\encoder\encoder-safe.ecf" readonly="false"/>
|
||||
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf" readonly="false"/>
|
||||
<library name="wsf_extension" location="..\..\library\server\wsf\wsf_extension-safe.ecf" readonly="false"/>
|
||||
</target>
|
||||
<target name="requestbin_any" extends="common">
|
||||
<root class="EWF_REQUESTBIN_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="cgi" location="..\..\library\server\wsf\connector\cgi-safe.ecf" readonly="false"/>
|
||||
<library name="libfcgi" location="..\..\library\server\wsf\connector\libfcgi-safe.ecf" readonly="false"/>
|
||||
<library name="standalone" location="..\..\library\server\wsf\connector\standalone-safe.ecf" readonly="false"/>
|
||||
<cluster name="launcher" location=".\launcher\any\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="requestbin" extends="common">
|
||||
<root class="EWF_REQUESTBIN_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf" readonly="false"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="requestbin_cgi" extends="common">
|
||||
<root class="EWF_REQUESTBIN_SERVER" feature="make_and_launch"/>
|
||||
<library name="default_cgi" location="..\..\library\server\wsf\default\cgi-safe.ecf" readonly="false"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="requestbin_libfcgi" extends="common">
|
||||
<root class="EWF_REQUESTBIN_SERVER" feature="make_and_launch"/>
|
||||
<library name="default_libfcgi" location="..\..\library\server\wsf\default\libfcgi-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
2
tools/requestbin/server.ini
Normal file
2
tools/requestbin/server.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
port=9090
|
||||
verbose=true
|
||||
27
tools/requestbin/src/ewf_debug_execution.e
Normal file
27
tools/requestbin/src/ewf_debug_execution.e
Normal file
@@ -0,0 +1,27 @@
|
||||
note
|
||||
description: "Summary description for {EWF_DEBUG_EXECUTION}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
EWF_DEBUG_EXECUTION
|
||||
|
||||
inherit
|
||||
WSF_EXECUTION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute
|
||||
local
|
||||
dbg: WSF_DEBUG_HANDLER
|
||||
do
|
||||
response.put_error ("DEBUG uri=" + request.request_uri + "%N")
|
||||
create dbg.make
|
||||
dbg.execute_starts_with ("", request, response)
|
||||
end
|
||||
|
||||
end
|
||||
35
tools/requestbin/src/ewf_requestbin_server.e
Normal file
35
tools/requestbin/src/ewf_requestbin_server.e
Normal file
@@ -0,0 +1,35 @@
|
||||
note
|
||||
description: "[
|
||||
application service
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
EWF_REQUESTBIN_SERVER
|
||||
|
||||
inherit
|
||||
WSF_LAUNCHABLE_SERVICE
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
APPLICATION_LAUNCHER [EWF_DEBUG_EXECUTION]
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
-- Initialize current service.
|
||||
do
|
||||
Precursor
|
||||
-- set_service_option ("verbose", True)
|
||||
set_service_option ("port", 9090)
|
||||
-- set_service_option ("base", "/www-debug/debug_service.fcgi/")
|
||||
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("server.ini"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -65,7 +65,9 @@ echo Uninstall library: content_negotiation
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\network\protocol\content_negotiation
|
||||
echo Uninstall library: http_authorization
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\http_authorization
|
||||
echo Uninstall library: security\openid
|
||||
echo Uninstall library: jwt
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\jwt
|
||||
echo Uninstall library: openid
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\openid
|
||||
echo Uninstall library: uri_template
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\uri_template
|
||||
|
||||
Reference in New Issue
Block a user