Compare commits
50 Commits
v1.0.4
...
with_compr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97fe16b4c2 | ||
|
|
cdada71f7e | ||
|
|
a7d0398ec6 | ||
|
|
267655d7bc | ||
|
|
e735da1bcb | ||
|
|
6425482070 | ||
|
|
818c3fb460 | ||
|
|
dac50b490d | ||
|
|
16d5076fe5 | ||
|
|
2748e1d9ee | ||
|
|
27ee20f99b | ||
|
|
9a3164df70 | ||
|
|
02383810b4 | ||
|
|
dbf5e76047 | ||
|
|
5c31905427 | ||
|
|
c7ef652322 | ||
|
|
7feb45b549 | ||
|
|
5bbd031275 | ||
|
|
90e60fad26 | ||
|
|
98c20ee7c1 | ||
|
|
0b99e84728 | ||
|
|
9e5e8bb1bf | ||
|
|
10a83c6ad8 | ||
|
|
1ec3b8e7a4 | ||
|
|
0783049fb4 | ||
|
|
7e54825b84 | ||
|
|
40cbe7dfc9 | ||
|
|
d4b9301a57 | ||
|
|
06cda97535 | ||
|
|
c83b9d4231 | ||
|
|
69b5ce637e | ||
|
|
485a3812d9 | ||
|
|
88dec34a1e | ||
|
|
a928f27b1a | ||
|
|
7ba678d726 | ||
|
|
2371ad4bd1 | ||
|
|
146b78e5b0 | ||
|
|
273a55d93c | ||
|
|
2e920f063a | ||
|
|
3b8261ff08 | ||
|
|
a530bbebb4 | ||
|
|
d41dbb9f47 | ||
|
|
a57e041003 | ||
|
|
5d9752f257 | ||
|
|
d4d988e532 | ||
|
|
ce11a3c0fc | ||
|
|
4eb743fa58 | ||
| e14bb568d2 | |||
| 05d37439bc | |||
|
|
99bf552b89 |
19
.travis.yml
Normal file
19
.travis.yml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
language: eiffel
|
||||||
|
before_script:
|
||||||
|
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
|
||||||
|
- export ISE_VERSION=17.05; export ISE_BUILD=100416
|
||||||
|
- curl -sSL http://downloads.sourceforge.net/eiffelstudio/Eiffel_${ISE_VERSION}_gpl_${ISE_BUILD}-linux-x86-64.tar.bz2 | tar -x --bzip2
|
||||||
|
- export ISE_EIFFEL=$PWD/Eiffel_${ISE_VERSION} ; export ISE_PLATFORM=linux-x86-64
|
||||||
|
- export PATH=$PATH:$ISE_EIFFEL/studio/spec/$ISE_PLATFORM/bin:$PATH:$ISE_EIFFEL/tools/spec/$ISE_PLATFORM/bin
|
||||||
|
- echo `ec -version`
|
||||||
|
- cd $current_dir
|
||||||
|
- echo Check projects compilation status...
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- v1
|
||||||
|
|
||||||
|
script: compile_all -ecb -melt -list_failures -log_verbose -clean -options dotnet=false
|
||||||
|
group: stable
|
||||||
|
os: linux
|
||||||
6091
CHANGELOG.md
Normal file
6091
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
23
README.md
23
README.md
@@ -41,10 +41,10 @@ Tasks and issues are managed with github issue system
|
|||||||
* Forum/group post: https://groups.google.com/forum/#!forum/eiffel-web-framework
|
* Forum/group post: https://groups.google.com/forum/#!forum/eiffel-web-framework
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* Compiling from EiffelStudio 13.11 to 15.05 and more recent version of the compiler.
|
* Compiling from EiffelStudio 16.05 to 17.05 and more recent version of the compiler.
|
||||||
* Currently being developped using EiffelStudio 15.01 (on Windows, Linux)
|
* Currently being developped using EiffelStudio 17.01 (on Windows, Linux)
|
||||||
* Tested using EiffelStudio 15.01 with "jenkins" CI server (not anymore compatible with 6.8 due to use of `TABLE_ITERABLE')
|
* Tested using EiffelStudio 17.01 with "jenkins" CI server.
|
||||||
* The code have to allow __void-safe__ compilation and non void-safe system (see [more about void-safety](http://docs.eiffel.com/book/method/void-safe-programming-eiffel) )
|
* The code have to allow __void-safe__ compilation and non void-safe system (see [more about void-safety](https://www.eiffel.org/doc/eiffel/Void-safe%20programming%20in%20Eiffel)
|
||||||
|
|
||||||
## How to get the source code?
|
## How to get the source code?
|
||||||
|
|
||||||
@@ -65,12 +65,12 @@ Using git
|
|||||||
* __router__: URL dispatching/routing based on uri, uri_template, or custom [read more](library/server/wsf/router)
|
* __router__: URL dispatching/routing based on uri, uri_template, or custom [read more](library/server/wsf/router)
|
||||||
|
|
||||||
### protocol
|
### protocol
|
||||||
* __http__: HTTP related classes, constants for status code, content types, ... [read more](library/protocol/http)
|
* __http__: HTTP related classes, constants for status code, content types, ... [read more](library/network/protocol/http)
|
||||||
* __uri_template__: URI Template library (parsing and expander) [read more](library/protocol/uri_template)
|
* __uri_template__: URI Template library (parsing and expander) [read more](library/network/protocol/uri_template)
|
||||||
* __CONNEG__: Content negotiation library (Content-type Negociation) [read more](library/protocol/content_negotiation)
|
* __CONNEG__: Content negotiation library (Content-type Negociation) [read more](library/network/protocol/content_negotiation)
|
||||||
|
|
||||||
### client
|
### client
|
||||||
* __http_client__: simple HTTP client based on cURL [read more](library/client/http_client)
|
* __http_client__: simple HTTP client based on cURL [read more](library/network/http_client)
|
||||||
|
|
||||||
### text
|
### text
|
||||||
* __encoder__: Various simpler encoders: base64, url-encoder, xml entities, html entities [read more](library/text/encoder)
|
* __encoder__: Various simpler encoders: base64, url-encoder, xml entities, html entities [read more](library/text/encoder)
|
||||||
@@ -78,10 +78,6 @@ Using git
|
|||||||
### Others
|
### Others
|
||||||
* error: very simple/basic library to handle error
|
* error: very simple/basic library to handle error
|
||||||
|
|
||||||
## External libraries under 'contrib'
|
|
||||||
* [Eiffel Web Nino](contrib/library/server/nino)
|
|
||||||
* ..
|
|
||||||
|
|
||||||
## Draft folder = call for contribution ##
|
## Draft folder = call for contribution ##
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -106,5 +102,4 @@ Keep track of development and community news.
|
|||||||
* Have a question that's not a feature request or bug report? [Ask on the mailing list](http://groups.google.com/group/eiffel-web-framework)
|
* Have a question that's not a feature request or bug report? [Ask on the mailing list](http://groups.google.com/group/eiffel-web-framework)
|
||||||
|
|
||||||
|
|
||||||
For more information please have a look at the related wiki:
|
For more information please have a look at the related [workbook documentation](docs/workbook)
|
||||||
* https://github.com/EiffelWebFramework/EWF/wiki
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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">
|
<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">
|
<target name="web_server">
|
||||||
<root class="APPLICATION" feature="make"/>
|
<root class="APPLICATION" feature="make"/>
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/.git$</exclude>
|
<exclude>/.git$</exclude>
|
||||||
<exclude>/.svn$</exclude>
|
<exclude>/.svn$</exclude>
|
||||||
<exclude>/CVS$</exclude>
|
<exclude>/CVS$</exclude>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
<option debug="true" warning="true" void_safety="all">
|
<option debug="true" warning="true" void_safety="all">
|
||||||
<debug name="nino" enabled="true"/>
|
<debug name="nino" enabled="true"/>
|
||||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
</option>
|
</option>
|
||||||
<setting name="concurrency" value="thread"/>
|
<setting name="concurrency" value="thread"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||||
<library name="nino" location="..\..\nino-safe.ecf"/>
|
<library name="nino" location="..\..\nino-safe.ecf"/>
|
||||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
|
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
|
||||||
<cluster name="src" location=".\" recursive="true"/>
|
<cluster name="src" location=".\" recursive="true"/>
|
||||||
</target>
|
</target>
|
||||||
</system>
|
</system>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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">
|
<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">
|
<target name="web_server">
|
||||||
<root class="APPLICATION" feature="make"/>
|
<root class="APPLICATION" feature="make"/>
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/.git$</exclude>
|
<exclude>/.git$</exclude>
|
||||||
<exclude>/.svn$</exclude>
|
<exclude>/.svn$</exclude>
|
||||||
<exclude>/CVS$</exclude>
|
<exclude>/CVS$</exclude>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
<option warning="true" void_safety="none">
|
<option warning="true" void_safety="none">
|
||||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
</option>
|
</option>
|
||||||
<setting name="concurrency" value="thread"/>
|
<setting name="concurrency" value="thread"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||||
<library name="nino" location="..\..\nino.ecf"/>
|
<library name="nino" location="..\..\nino.ecf"/>
|
||||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
|
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
|
||||||
<cluster name="src" location=".\" recursive="true"/>
|
<cluster name="src" location=".\" recursive="true"/>
|
||||||
</target>
|
</target>
|
||||||
</system>
|
</system>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
|
<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="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
|
||||||
<target name="common">
|
<target name="common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
<exclude>/\.git$</exclude>
|
<exclude>/\.git$</exclude>
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ feature {NONE} -- Implementation
|
|||||||
loop
|
loop
|
||||||
s.append (" - ")
|
s.append (" - ")
|
||||||
s.append (c.item.url_encoded_name)
|
s.append (c.item.url_encoded_name)
|
||||||
t := c.item.generating_type
|
t := c.item.generating_type.name
|
||||||
if t.same_string ("WSF_STRING") then
|
if t.same_string ("WSF_STRING") then
|
||||||
else
|
else
|
||||||
s.append_character (' ')
|
s.append_character (' ')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
|
<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="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
|
||||||
<target name="common">
|
<target name="common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
<exclude>/\.git$</exclude>
|
<exclude>/\.git$</exclude>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ feature {NONE} -- Initialization
|
|||||||
create l_methods
|
create l_methods
|
||||||
l_methods.enable_options
|
l_methods.enable_options
|
||||||
l_methods.enable_get
|
l_methods.enable_get
|
||||||
router.handle_with_request_methods ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
|
router.handle ("/user/{userid}", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent l_options_filter.execute), l_methods)
|
||||||
end
|
end
|
||||||
|
|
||||||
initialize_json
|
initialize_json
|
||||||
@@ -99,7 +99,7 @@ feature {NONE} -- Implementation
|
|||||||
-- Port number
|
-- Port number
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2014, Olivier Ligot, Jocelyn Fiat and others"
|
copyright: "2011-2017, Olivier Ligot, Jocelyn Fiat and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ feature -- Basic operations
|
|||||||
id : STRING
|
id : STRING
|
||||||
do
|
do
|
||||||
if attached req.orig_path_info as orig_path then
|
if attached req.orig_path_info as orig_path then
|
||||||
id := get_user_id_from_path (orig_path)
|
id := get_user_id_from_path (orig_path.as_string_32)
|
||||||
if attached retrieve_user (id) as l_user then
|
if attached retrieve_user (id) as l_user then
|
||||||
if l_user ~ req.execution_variable ("user") then
|
if l_user ~ req.execution_variable ("user") then
|
||||||
compute_response_get (req, res, l_user)
|
compute_response_get (req, res, l_user)
|
||||||
@@ -92,6 +92,6 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2013, Olivier Ligot, Jocelyn Fiat and others"
|
copyright: "2011-2017, Olivier Ligot, Jocelyn Fiat and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
|
<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="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
|
||||||
<target name="restbucks_common">
|
<target name="restbucks_common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
<exclude>/\.git$</exclude>
|
<exclude>/\.git$</exclude>
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ feature {NONE} -- Initialization
|
|||||||
create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
|
create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
|
||||||
local
|
local
|
||||||
s: READABLE_STRING_8
|
s: READABLE_STRING_8
|
||||||
j: JSON_PARSER
|
|
||||||
context : HTTP_CLIENT_REQUEST_CONTEXT
|
context : HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
do
|
||||||
s := "[
|
s := "[
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
|
<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="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
|
||||||
<target name="restbucks_common">
|
<target name="restbucks_common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
<exclude>/\.git$</exclude>
|
<exclude>/\.git$</exclude>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ feature -- Access: order status
|
|||||||
end
|
end
|
||||||
|
|
||||||
Order_states : ARRAY [STRING]
|
Order_states : ARRAY [STRING]
|
||||||
-- List of valid status states
|
-- List of valid status states.
|
||||||
once
|
once
|
||||||
Result := <<
|
Result := <<
|
||||||
status_unset,
|
status_unset,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ feature -- Access
|
|||||||
end
|
end
|
||||||
|
|
||||||
Milk_types: ARRAY [STRING]
|
Milk_types: ARRAY [STRING]
|
||||||
-- List of valid Milk types
|
-- List of valid Milk types.
|
||||||
once
|
once
|
||||||
Result := <<"skim", "semi", "whole">>
|
Result := <<"skim", "semi", "whole">>
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ note
|
|||||||
date: "$Date$"
|
date: "$Date$"
|
||||||
revision: "$Revision$"
|
revision: "$Revision$"
|
||||||
|
|
||||||
class ORDER_HANDLER
|
class
|
||||||
|
ORDER_HANDLER
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
|
|
||||||
@@ -537,7 +538,6 @@ feature {NONE} -- Conversion
|
|||||||
|
|
||||||
order_to_json (obj: ORDER): JSON_OBJECT
|
order_to_json (obj: ORDER): JSON_OBJECT
|
||||||
local
|
local
|
||||||
j_order: JSON_OBJECT
|
|
||||||
j_item: JSON_OBJECT
|
j_item: JSON_OBJECT
|
||||||
ja: JSON_ARRAY
|
ja: JSON_ARRAY
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -334,7 +334,6 @@ feature {NONE} -- Conversion
|
|||||||
|
|
||||||
order_to_json (obj: ORDER): JSON_OBJECT
|
order_to_json (obj: ORDER): JSON_OBJECT
|
||||||
local
|
local
|
||||||
j_order: JSON_OBJECT
|
|
||||||
j_item: JSON_OBJECT
|
j_item: JSON_OBJECT
|
||||||
ja: JSON_ARRAY
|
ja: JSON_ARRAY
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ inherit
|
|||||||
feature {NONE} -- Initialization
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
make (a_location: PATH)
|
make (a_location: PATH)
|
||||||
local
|
|
||||||
d: DIRECTORY
|
|
||||||
do
|
do
|
||||||
location := a_location
|
location := a_location
|
||||||
ensure_directory_exists (a_location)
|
ensure_directory_exists (a_location)
|
||||||
@@ -67,7 +65,6 @@ feature -- Access
|
|||||||
|
|
||||||
save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL])
|
save (a_entry_type: TYPE [detachable ANY]; a_entry: detachable ANY; cl_entry_id: CELL [detachable READABLE_STRING_GENERAL])
|
||||||
local
|
local
|
||||||
f: RAW_FILE
|
|
||||||
l_id: detachable READABLE_STRING_GENERAL
|
l_id: detachable READABLE_STRING_GENERAL
|
||||||
p: PATH
|
p: PATH
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ create
|
|||||||
feature {NONE} -- Initialization
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
make
|
make
|
||||||
local
|
|
||||||
b: SED_MEMORY_READER_WRITER
|
|
||||||
do
|
do
|
||||||
create collections.make (0)
|
create collections.make (0)
|
||||||
end
|
end
|
||||||
@@ -86,8 +84,6 @@ feature {NONE} -- Implementation
|
|||||||
next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8
|
next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8
|
||||||
local
|
local
|
||||||
i: INTEGER
|
i: INTEGER
|
||||||
f: RAW_FILE
|
|
||||||
s: STRING
|
|
||||||
tb: detachable STRING_TABLE [detachable ANY]
|
tb: detachable STRING_TABLE [detachable ANY]
|
||||||
do
|
do
|
||||||
tb := collections.item (a_entry_type)
|
tb := collections.item (a_entry_type)
|
||||||
|
|||||||
4
examples/simple_compression/service.ini
Normal file
4
examples/simple_compression/service.ini
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
port=9090
|
||||||
|
verbose=true
|
||||||
|
socket_recv_timeout=15
|
||||||
|
keep_alive_timeout=30
|
||||||
26
examples/simple_compression/service_compression.e
Normal file
26
examples/simple_compression/service_compression.e
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
note
|
||||||
|
description : "simple application root class"
|
||||||
|
date : "$Date$"
|
||||||
|
revision : "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
SERVICE_COMPRESSION
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WSF_DEFAULT_SERVICE [SERVICE_COMPRESSION_EXECUTION]
|
||||||
|
redefine
|
||||||
|
initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
create
|
||||||
|
make_and_launch
|
||||||
|
|
||||||
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
|
initialize
|
||||||
|
do
|
||||||
|
Precursor
|
||||||
|
import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("service.ini"))
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
22
examples/simple_compression/service_compression.ecf
Normal file
22
examples/simple_compression/service_compression.ecf
Normal file
@@ -0,0 +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="service_compression" uuid="C28C4F53-9963-46C0-A080-8F13E94E7486">
|
||||||
|
<target name="service_compression">
|
||||||
|
<root class="SERVICE_COMPRESSION" feature="make_and_launch"/>
|
||||||
|
<option warning="true" void_safety="all">
|
||||||
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
|
</option>
|
||||||
|
<setting name="concurrency" value="scoop"/>
|
||||||
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
|
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
|
||||||
|
<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_compression" location="..\..\library\server\wsf\wsf_compression-safe.ecf" readonly="false"/>
|
||||||
|
<cluster name="service_compression" location=".\" recursive="true">
|
||||||
|
<file_rule>
|
||||||
|
<exclude>/EIFGENs$</exclude>
|
||||||
|
<exclude>/CVS$</exclude>
|
||||||
|
<exclude>/.svn$</exclude>
|
||||||
|
</file_rule>
|
||||||
|
</cluster>
|
||||||
|
</target>
|
||||||
|
</system>
|
||||||
68
examples/simple_compression/service_compression_execution.e
Normal file
68
examples/simple_compression/service_compression_execution.e
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
note
|
||||||
|
description: "Simple file execution, serving home.html, ewf.png and 404.html"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
SERVICE_COMPRESSION_EXECUTION
|
||||||
|
|
||||||
|
inherit
|
||||||
|
WSF_ROUTED_EXECUTION
|
||||||
|
redefine
|
||||||
|
initialize,
|
||||||
|
execute_default
|
||||||
|
end
|
||||||
|
|
||||||
|
create
|
||||||
|
make
|
||||||
|
|
||||||
|
feature {NONE} -- Initialization
|
||||||
|
|
||||||
|
|
||||||
|
initialize
|
||||||
|
-- Initialize current service.
|
||||||
|
do
|
||||||
|
Precursor
|
||||||
|
initialize_router
|
||||||
|
end
|
||||||
|
|
||||||
|
setup_router
|
||||||
|
local
|
||||||
|
fhdl_with_compression: WSF_FILE_SYSTEM_HANDLER_WITH_COMPRESSION
|
||||||
|
fhdl: WSF_FILE_SYSTEM_HANDLER
|
||||||
|
do
|
||||||
|
create fhdl_with_compression.make_hidden ("www")
|
||||||
|
fhdl_with_compression.set_directory_index (<<"index.html">>)
|
||||||
|
fhdl_with_compression.compression.set_default_compression_format
|
||||||
|
fhdl_with_compression.compression.enable_compression_for_media_type ({HTTP_MIME_TYPES}.image_jpg)
|
||||||
|
fhdl_with_compression.set_not_found_handler (agent (ia_uri: READABLE_STRING_8; ia_req: WSF_REQUEST; ia_res: WSF_RESPONSE)
|
||||||
|
do
|
||||||
|
execute_default (ia_req, ia_res)
|
||||||
|
end)
|
||||||
|
router.handle ("/compressed/", fhdl_with_compression, router.methods_GET)
|
||||||
|
|
||||||
|
create fhdl.make_hidden ("www")
|
||||||
|
fhdl.set_directory_index (<<"index.html">>)
|
||||||
|
fhdl.set_not_found_handler (agent (ia_uri: READABLE_STRING_8; ia_req: WSF_REQUEST; ia_res: WSF_RESPONSE)
|
||||||
|
do
|
||||||
|
execute_default (ia_req, ia_res)
|
||||||
|
end)
|
||||||
|
router.handle ("/", fhdl, router.methods_GET)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
execute_default (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||||
|
-- Dispatch requests without a matching handler.
|
||||||
|
local
|
||||||
|
not_found: WSF_NOT_FOUND_RESPONSE
|
||||||
|
mesg: WSF_RESPONSE_MESSAGE
|
||||||
|
do
|
||||||
|
create not_found.make (request)
|
||||||
|
not_found.add_suggested_location (request.absolute_script_url (""), "Home", "Back to home page")
|
||||||
|
mesg := not_found
|
||||||
|
res.send (mesg)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
77705
examples/simple_compression/www/big_file.html
Normal file
77705
examples/simple_compression/www/big_file.html
Normal file
File diff suppressed because it is too large
Load Diff
88332
examples/simple_compression/www/big_file2.html
Normal file
88332
examples/simple_compression/www/big_file2.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
examples/simple_compression/www/eiffel.jpg
Normal file
BIN
examples/simple_compression/www/eiffel.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
examples/simple_compression/www/ewf.png
Normal file
BIN
examples/simple_compression/www/ewf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
27
examples/simple_compression/www/index.html
Normal file
27
examples/simple_compression/www/index.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>EWF simple_file example</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>EWF simple_file example</h1>
|
||||||
|
<p>This is a static html file served by EWF.</p>
|
||||||
|
<p>Try to <a href="nowhere.html">get lost</a>.</p>
|
||||||
|
<div width="45%" style="display: inline-block; border: solid 1px black; padding: 10px; margin: 10px;">
|
||||||
|
<h2>Without any compression</h2>
|
||||||
|
<a href="ewf.png"><img src="ewf.png"/></a>
|
||||||
|
<p>This is the real Eiffel tower.</p>
|
||||||
|
<a href="eiffel.jpg"><img src="eiffel.jpg"/></a>
|
||||||
|
<p>Try to <a href="big_file2.html">load a big file</a>.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div width="45%" style="display: inline-block; border: solid 1px black; padding: 10px; margin: 10px;">
|
||||||
|
<h2>With gzip compression</h2>
|
||||||
|
<a href="compressed/ewf.png"><img src="compressed/ewf.png"/></a>
|
||||||
|
<p>This is the real Eiffel tower.</p>
|
||||||
|
<a href="compressed/eiffel.jpg"><img src="compressed/eiffel.jpg"/></a>
|
||||||
|
<p>Try to <a href="compressed/big_file2.html">load a compressed big file</a>.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -54,7 +54,7 @@ feature -- Execution
|
|||||||
if attached {WSF_STRING} req.item ("user") as u then
|
if attached {WSF_STRING} req.item ("user") as u then
|
||||||
--| If yes, say hello world #name
|
--| If yes, say hello world #name
|
||||||
|
|
||||||
l_user_name := (create {HTML_ENCODER}).decoded_string (u.value)
|
l_user_name := (create {HTML_ENCODER}).general_decoded_string (u.value)
|
||||||
|
|
||||||
s := "<p>Hello " + mesg.html_encoded_string (l_user_name) + "!</p>"
|
s := "<p>Hello " + mesg.html_encoded_string (l_user_name) + "!</p>"
|
||||||
s.append ("Display a <a href=%"/users/" + u.url_encoded_value + "/message/%">message</a></p>")
|
s.append ("Display a <a href=%"/users/" + u.url_encoded_value + "/message/%">message</a></p>")
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ feature -- Access
|
|||||||
html_decoded_string (v: READABLE_STRING_32): READABLE_STRING_32
|
html_decoded_string (v: READABLE_STRING_32): READABLE_STRING_32
|
||||||
do
|
do
|
||||||
if v.is_valid_as_string_8 then
|
if v.is_valid_as_string_8 then
|
||||||
Result := (create {HTML_ENCODER}).decoded_string (v)
|
Result := (create {HTML_ENCODER}).general_decoded_string (v)
|
||||||
else
|
else
|
||||||
Result := v
|
Result := v
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="upload_image" uuid="F2400BE8-D8EB-48EB-B4E4-5D4377062A7F">
|
<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="upload_image" uuid="F2400BE8-D8EB-48EB-B4E4-5D4377062A7F">
|
||||||
<target name="upload_image_common">
|
<target name="upload_image_common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/EIFGENs$</exclude>
|
<exclude>/EIFGENs$</exclude>
|
||||||
<exclude>/\.git$</exclude>
|
<exclude>/\.git$</exclude>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
</library>
|
</library>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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_null" location="$|spec\null\" recursive="true"/>
|
||||||
<cluster name="spec_net" location="$|spec\net\">
|
<cluster name="spec_net" location="$|spec\net\">
|
||||||
<condition>
|
<condition>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
</library>
|
</library>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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_null" location="$|spec\null\" recursive="true"/>
|
||||||
<cluster name="spec_net" location="$|spec\net\">
|
<cluster name="spec_net" location="$|spec\net\">
|
||||||
<condition>
|
<condition>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
</library>
|
</library>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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="spec_net" location="$|spec\net\">
|
||||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
</library>
|
</library>
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<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="spec_net" location="$|spec\net\">
|
||||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
|||||||
@@ -16,8 +16,19 @@ feature -- Access
|
|||||||
deferred
|
deferred
|
||||||
end
|
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
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -196,73 +196,8 @@ feature -- Settings
|
|||||||
Result := session.is_insecure
|
Result := session.is_insecure
|
||||||
end
|
end
|
||||||
|
|
||||||
feature {NONE} -- Utilities
|
|
||||||
|
|
||||||
append_parameters_to_url (a_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_url: STRING)
|
|
||||||
-- Append parameters `a_parameters' to `a_url'
|
|
||||||
require
|
|
||||||
a_url_attached: a_url /= Void
|
|
||||||
local
|
|
||||||
l_first_param: BOOLEAN
|
|
||||||
do
|
|
||||||
if a_parameters.count > 0 then
|
|
||||||
if a_url.index_of ('?', 1) > 0 then
|
|
||||||
l_first_param := False
|
|
||||||
elseif a_url.index_of ('&', 1) > 0 then
|
|
||||||
l_first_param := False
|
|
||||||
else
|
|
||||||
l_first_param := True
|
|
||||||
end
|
|
||||||
|
|
||||||
from
|
|
||||||
a_parameters.start
|
|
||||||
until
|
|
||||||
a_parameters.after
|
|
||||||
loop
|
|
||||||
if l_first_param then
|
|
||||||
a_url.append_character ('?')
|
|
||||||
else
|
|
||||||
a_url.append_character ('&')
|
|
||||||
end
|
|
||||||
a_url.append (urlencode (a_parameters.key_for_iteration))
|
|
||||||
a_url.append_character ('=')
|
|
||||||
a_url.append (urlencode (a_parameters.item_for_iteration))
|
|
||||||
l_first_param := False
|
|
||||||
a_parameters.forth
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
feature {NONE} -- Utilities: encoding
|
|
||||||
|
|
||||||
url_encoder: URL_ENCODER
|
|
||||||
once
|
|
||||||
create Result
|
|
||||||
end
|
|
||||||
|
|
||||||
urlencode (s: READABLE_STRING_32): READABLE_STRING_8
|
|
||||||
-- URL encode `s'
|
|
||||||
do
|
|
||||||
Result := url_encoder.encoded_string (s)
|
|
||||||
end
|
|
||||||
|
|
||||||
urldecode (s: READABLE_STRING_8): READABLE_STRING_32
|
|
||||||
-- URL decode `s'
|
|
||||||
do
|
|
||||||
Result := url_encoder.decoded_string (s)
|
|
||||||
end
|
|
||||||
|
|
||||||
stripslashes (s: STRING): STRING
|
|
||||||
do
|
|
||||||
Result := s.string
|
|
||||||
Result.replace_substring_all ("\%"", "%"")
|
|
||||||
Result.replace_substring_all ("\'", "'")
|
|
||||||
Result.replace_substring_all ("\/", "/")
|
|
||||||
Result.replace_substring_all ("\\", "\")
|
|
||||||
end
|
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ note
|
|||||||
Note that any value set in this context class overrides conflicting value eventually
|
Note that any value set in this context class overrides conflicting value eventually
|
||||||
set in associated HTTP_CLIENT_SESSION.
|
set in associated HTTP_CLIENT_SESSION.
|
||||||
|
|
||||||
Warning: for now [2012-May], you can have only one of the following data
|
Warning: for now [2012-05-31], you can have only one of the following data
|
||||||
- form_parameters
|
- form_parameters
|
||||||
- or upload_data
|
- or upload_data
|
||||||
- or upload_filename
|
- or upload_filename
|
||||||
@@ -58,11 +58,11 @@ feature -- Access
|
|||||||
-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
|
-- 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
|
--| 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
|
-- 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
|
--| 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
|
-- Form parameters
|
||||||
|
|
||||||
upload_data: detachable READABLE_STRING_8
|
upload_data: detachable READABLE_STRING_8
|
||||||
@@ -142,16 +142,28 @@ feature -- Element change
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
add_query_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
|
add_query_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
|
||||||
-- Add a query parameter `k=v'.
|
-- Add a query parameter `k=v'.
|
||||||
do
|
do
|
||||||
query_parameters.force (v, k)
|
query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
|
||||||
end
|
end
|
||||||
|
|
||||||
add_form_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
|
add_form_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
|
||||||
-- Add a form parameter `k'= `v'.
|
-- Add a form parameter `k'= `v'.
|
||||||
do
|
do
|
||||||
form_parameters.force (v, k)
|
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
|
end
|
||||||
|
|
||||||
set_credentials_required (b: BOOLEAN)
|
set_credentials_required (b: BOOLEAN)
|
||||||
@@ -164,7 +176,8 @@ feature -- Element change
|
|||||||
-- Set `upload_data' to `a_data'
|
-- Set `upload_data' to `a_data'
|
||||||
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
||||||
require
|
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
|
do
|
||||||
if a_data = Void or else a_data.is_empty then
|
if a_data = Void or else a_data.is_empty then
|
||||||
upload_data := Void
|
upload_data := Void
|
||||||
@@ -180,6 +193,7 @@ feature -- Element change
|
|||||||
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
--| note: the Current context can have upload_data XOR upload_filename, but not both.
|
||||||
require
|
require
|
||||||
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
|
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
|
do
|
||||||
if a_fn = Void or else a_fn.is_empty then
|
if a_fn = Void or else a_fn.is_empty then
|
||||||
upload_filename := Void
|
upload_filename := Void
|
||||||
@@ -236,18 +250,62 @@ feature -- Status setting
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
feature -- URL helpers
|
||||||
|
|
||||||
|
append_query_parameters_to_url (a_url: STRING)
|
||||||
|
-- Append parameters `a_parameters' to `a_url'
|
||||||
|
require
|
||||||
|
a_url_attached: a_url /= Void
|
||||||
|
local
|
||||||
|
l_first_param: BOOLEAN
|
||||||
|
do
|
||||||
|
if
|
||||||
|
attached query_parameters as l_query_parameters and then
|
||||||
|
not l_query_parameters.is_empty
|
||||||
|
then
|
||||||
|
if a_url.index_of ('?', 1) > 0 then
|
||||||
|
l_first_param := False
|
||||||
|
elseif a_url.index_of ('&', 1) > 0 then
|
||||||
|
l_first_param := False
|
||||||
|
else
|
||||||
|
l_first_param := True
|
||||||
|
end
|
||||||
|
|
||||||
|
across
|
||||||
|
query_parameters as ic
|
||||||
|
loop
|
||||||
|
if l_first_param then
|
||||||
|
a_url.append_character ('?')
|
||||||
|
else
|
||||||
|
a_url.append_character ('&')
|
||||||
|
end
|
||||||
|
l_first_param := False
|
||||||
|
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, a_url)
|
||||||
|
a_url.append_character ('=')
|
||||||
|
ic.item.append_query_value_encoded_to (a_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Conversion helpers
|
feature -- Conversion helpers
|
||||||
|
|
||||||
query_parameters_to_url_encoded_string: STRING_8
|
query_parameters_to_url_encoded_string: STRING_8
|
||||||
-- `query_parameters' as url-encoded string.
|
-- `query_parameters' as url-encoded string.
|
||||||
do
|
do
|
||||||
Result := parameters_to_url_encoded_string (query_parameters)
|
Result := parameters_to_uri_percent_encoded_string (query_parameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
form_parameters_to_x_www_form_url_encoded_string: STRING_8
|
||||||
|
-- `form_parameters' as x-www-form-urlencoded string.
|
||||||
|
do
|
||||||
|
Result := parameters_to_x_www_form_urlencoded_string (form_parameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
form_parameters_to_url_encoded_string: STRING_8
|
form_parameters_to_url_encoded_string: STRING_8
|
||||||
-- `form_parameters' as url-encoded string.
|
-- `form_parameters' as url-encoded string.
|
||||||
|
obsolete "Use form_parameters_to_x_www_form_url_encoded_string [2017-05-31]"
|
||||||
do
|
do
|
||||||
Result := parameters_to_url_encoded_string (form_parameters)
|
Result := form_parameters_to_x_www_form_url_encoded_string
|
||||||
end
|
end
|
||||||
|
|
||||||
feature {NONE} -- Implementation
|
feature {NONE} -- Implementation
|
||||||
@@ -271,6 +329,49 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
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.item.name, Result)
|
||||||
|
Result.append_character ('=')
|
||||||
|
ic.item.append_query_value_encoded_to (Result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
across
|
||||||
|
a_params as ic
|
||||||
|
loop
|
||||||
|
if not Result.is_empty then
|
||||||
|
Result.append_character ('&')
|
||||||
|
end
|
||||||
|
x_www_form_url_encoder.append_percent_encoded_string_to (ic.item.name, Result)
|
||||||
|
Result.append_character ('=')
|
||||||
|
ic.item.append_form_url_encoded_to (Result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
url_encoder: URL_ENCODER
|
url_encoder: URL_ENCODER
|
||||||
-- Shared URL encoder.
|
-- Shared URL encoder.
|
||||||
once
|
once
|
||||||
@@ -278,7 +379,7 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -60,28 +60,10 @@ feature -- Access
|
|||||||
|
|
||||||
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
|
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
|
||||||
-- Url computed from Current and `ctx' data.
|
-- Url computed from Current and `ctx' data.
|
||||||
local
|
|
||||||
s: STRING_8
|
|
||||||
url_encoder: URL_ENCODER
|
|
||||||
do
|
do
|
||||||
Result := base_url + a_path
|
Result := base_url + a_path
|
||||||
if ctx /= Void then
|
if ctx /= Void then
|
||||||
create s.make_empty
|
ctx.append_query_parameters_to_url (Result)
|
||||||
create url_encoder
|
|
||||||
across
|
|
||||||
ctx.query_parameters as q
|
|
||||||
loop
|
|
||||||
if not s.is_empty then
|
|
||||||
s.append_character ('&')
|
|
||||||
end
|
|
||||||
s.append (url_encoder.encoded_string (q.key))
|
|
||||||
s.append_character ('=')
|
|
||||||
s.append (url_encoder.encoded_string (q.item))
|
|
||||||
end
|
|
||||||
if not s.is_empty then
|
|
||||||
Result.append_character ('?')
|
|
||||||
Result.append (s)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -420,7 +402,7 @@ feature -- Element change
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -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,628 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Component to handle percent encoding
|
||||||
|
|
||||||
|
WARNING: THIS IS A COPY FROM $ISE_LIBRARY/library/text/uri library.
|
||||||
|
In the future, http_client will use directly the `uri` library.
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
EIS: "name=Percent-encoding", "protocol=URI", "src=http://en.wikipedia.org/wiki/Percent-encoding"
|
||||||
|
|
||||||
|
class
|
||||||
|
URI_PERCENT_ENCODER
|
||||||
|
|
||||||
|
feature -- Percent encoding
|
||||||
|
|
||||||
|
append_percent_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append `s' as percent-encoded value to `a_result'
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := s.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
append_encoded_character_code_to (s.code (i), a_result)
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_query_name_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append `s' as encoded for URI query name to `a_result'
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := s.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
append_query_name_encoded_character_code_to (s.code (i), a_result)
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_query_value_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append `s' as encoded for URI query value to `a_result'.
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := s.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
append_query_value_encoded_character_code_to (s.code (i), a_result)
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_path_segment_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append `a_string' as encoded for URI path segment to `a_result'
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := s.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
append_path_segment_encoded_character_code_to (s.code (i), a_result)
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_www_form_url_encoded_string_to (s: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append `a_string' as www-form-urlencoded value to `a_result'.
|
||||||
|
-- The main difference with `append_percent_encoded_string_to` is the encoding of space using '+'.
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := s.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
inspect s.code (i)
|
||||||
|
when 32 then -- space: 32 ' '
|
||||||
|
a_result.append_code (43) -- 43 '+'
|
||||||
|
else
|
||||||
|
append_encoded_character_code_to (s.code (i), a_result)
|
||||||
|
end
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- URI building helpers
|
||||||
|
|
||||||
|
append_encoded_character_code_to (c: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append character code `c' as query name encoded content into `a_result'.
|
||||||
|
do
|
||||||
|
if
|
||||||
|
--| unreserved ALPHA / DIGIT
|
||||||
|
(48 <= c and c <= 57) -- DIGIT: 0 .. 9
|
||||||
|
or (65 <= c and c <= 90) -- ALPHA: A .. Z
|
||||||
|
or (97 <= c and c <= 122) -- ALPHA: a .. z
|
||||||
|
then
|
||||||
|
a_result.append_code (c)
|
||||||
|
else
|
||||||
|
inspect c
|
||||||
|
when
|
||||||
|
45, 46, 95, 126 -- unreserved characters: -._~
|
||||||
|
then
|
||||||
|
a_result.append_code (c)
|
||||||
|
when
|
||||||
|
58, 64, -- reserved =+ gen-delims: : @
|
||||||
|
33, 36, 38, 39, 40, 41, 42, -- reserved =+ sub-delims: ! $ & ' ( ) *
|
||||||
|
43, 44, 59, 61, -- reserved = sub-delims: + , ; =
|
||||||
|
37 -- percent encoding: %
|
||||||
|
then
|
||||||
|
append_percent_encoded_character_code_to (c, a_result)
|
||||||
|
else
|
||||||
|
append_percent_encoded_character_code_to (c, a_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_query_name_encoded_character_code_to (c: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append character code `a_code' as query name encoded content into `a_result'.
|
||||||
|
do
|
||||||
|
inspect c
|
||||||
|
when 61 then -- equal sign: =
|
||||||
|
append_percent_encoded_character_code_to (c, a_result)
|
||||||
|
else
|
||||||
|
append_query_value_encoded_character_code_to (c, a_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_query_value_encoded_character_code_to (c: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append character code `a_code' as query value encoded content into `a_result'.
|
||||||
|
do
|
||||||
|
inspect c
|
||||||
|
when 32 then -- Space
|
||||||
|
a_result.append_code (43) -- 43 '+'
|
||||||
|
when
|
||||||
|
39, -- '
|
||||||
|
58, 64, -- reserved =+ gen-delims: : @
|
||||||
|
33, 36, 40, 41, 42, -- reserved =+ sub-delims: ! $ ( ) *
|
||||||
|
44, 59, 61 -- reserved = sub-delims: , ; =
|
||||||
|
then
|
||||||
|
a_result.append_code (c)
|
||||||
|
when
|
||||||
|
47, -- slash: /
|
||||||
|
63 -- question mark ?
|
||||||
|
then
|
||||||
|
a_result.append_code (c)
|
||||||
|
else
|
||||||
|
append_encoded_character_code_to (c, a_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
append_path_segment_encoded_character_code_to (c: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append character code `a_code' as query name encoded content into `a_result'.
|
||||||
|
do
|
||||||
|
append_encoded_character_code_to (c, a_result)
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Percent encoding: character
|
||||||
|
|
||||||
|
append_percent_encoded_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append character code `a_code' as percent-encoded content into `a_result'
|
||||||
|
do
|
||||||
|
if a_code > 0xFF then
|
||||||
|
-- Unicode
|
||||||
|
append_percent_encoded_unicode_character_code_to (a_code, a_result)
|
||||||
|
elseif a_code > 0x7F then
|
||||||
|
-- Extended ASCII
|
||||||
|
-- This requires percent-encoding on UTF-8 converted character.
|
||||||
|
append_percent_encoded_unicode_character_code_to (a_code, a_result)
|
||||||
|
else
|
||||||
|
-- ASCII
|
||||||
|
append_percent_encoded_ascii_character_code_to (a_code, a_result)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
appended: a_result.count > old a_result.count
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation: character encoding
|
||||||
|
|
||||||
|
append_percent_encoded_ascii_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append extended ascii character code `a_code' as percent-encoded content into `a_result'
|
||||||
|
-- Note: it does not UTF-8 convert this extended ASCII.
|
||||||
|
require
|
||||||
|
is_extended_ascii: a_code <= 0xFF
|
||||||
|
local
|
||||||
|
c: INTEGER
|
||||||
|
do
|
||||||
|
if a_code > 0xFF then
|
||||||
|
-- Unicode
|
||||||
|
append_percent_encoded_unicode_character_code_to (a_code, a_result)
|
||||||
|
else
|
||||||
|
-- Extended ASCII
|
||||||
|
c := a_code.to_integer_32
|
||||||
|
a_result.append_code (37) -- 37 '%%'
|
||||||
|
a_result.append_code (hex_digit [c |>> 4])
|
||||||
|
a_result.append_code (hex_digit [c & 0xF])
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
appended: a_result.count > old a_result.count
|
||||||
|
end
|
||||||
|
|
||||||
|
append_percent_encoded_unicode_character_code_to (a_code: NATURAL_32; a_result: STRING_GENERAL)
|
||||||
|
-- Append Unicode character code `a_code' as UTF-8 and percent-encoded content into `a_result'
|
||||||
|
-- Note: it does include UTF-8 conversion of extended ASCII and Unicode.
|
||||||
|
do
|
||||||
|
if a_code <= 0x7F then
|
||||||
|
-- 0xxxxxxx
|
||||||
|
append_percent_encoded_ascii_character_code_to (a_code, a_result)
|
||||||
|
elseif a_code <= 0x7FF then
|
||||||
|
-- 110xxxxx 10xxxxxx
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code |>> 6) | 0xC0, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result)
|
||||||
|
elseif a_code <= 0xFFFF then
|
||||||
|
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code |>> 12) | 0xE0, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to (((a_code |>> 6) & 0x3F) | 0x80, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result)
|
||||||
|
else
|
||||||
|
-- c <= 1FFFFF - there are no higher code points
|
||||||
|
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code |>> 18) | 0xF0, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to (((a_code |>> 12) & 0x3F) | 0x80, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to (((a_code |>> 6) & 0x3F) | 0x80, a_result)
|
||||||
|
append_percent_encoded_ascii_character_code_to ((a_code & 0x3F) | 0x80, a_result)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
appended: a_result.count > old a_result.count
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- Percent decoding
|
||||||
|
|
||||||
|
append_percent_decoded_string_to (v: READABLE_STRING_GENERAL; a_result: STRING_GENERAL)
|
||||||
|
-- Append to `a_result' a string equivalent to the percent-encoded string `v'
|
||||||
|
--| Note that is `a_result' is a STRING_8, any Unicode character will be kept as UTF-8
|
||||||
|
local
|
||||||
|
i,n: INTEGER
|
||||||
|
c: NATURAL_32
|
||||||
|
pr: CELL [INTEGER]
|
||||||
|
a_result_is_string_32: BOOLEAN
|
||||||
|
do
|
||||||
|
a_result_is_string_32 := attached {STRING_32} a_result
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
create pr.put (i)
|
||||||
|
n := v.count
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
c := v.code (i)
|
||||||
|
inspect c
|
||||||
|
when 43 then -- 43 '+'
|
||||||
|
-- Some implementation are replacing spaces with "+" instead of "%20"
|
||||||
|
a_result.append_code (32) -- 32 ' '
|
||||||
|
when 37 then -- 37 '%%'
|
||||||
|
-- An escaped character ?
|
||||||
|
if i = n then -- Error?
|
||||||
|
a_result.append_code (c)
|
||||||
|
else
|
||||||
|
if a_result_is_string_32 then
|
||||||
|
-- Convert UTF-8 to UTF-32
|
||||||
|
pr.replace (i)
|
||||||
|
c := next_percent_decoded_unicode_character_code (v, pr)
|
||||||
|
a_result.append_code (c)
|
||||||
|
i := pr.item
|
||||||
|
else
|
||||||
|
-- Keep UTF-8
|
||||||
|
pr.replace (i)
|
||||||
|
c := next_percent_decoded_character_code (v, pr)
|
||||||
|
a_result.append_code (c)
|
||||||
|
i := pr.item
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if c <= 0x7F then
|
||||||
|
a_result.append_code (c)
|
||||||
|
else
|
||||||
|
if a_result_is_string_32 then
|
||||||
|
a_result.append_code (c)
|
||||||
|
else
|
||||||
|
-- Keep the percent encoded char for non string 32.
|
||||||
|
append_percent_encoded_character_code_to (c, a_result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation: decoding
|
||||||
|
|
||||||
|
next_percent_decoded_character_code (v: READABLE_STRING_GENERAL; a_position: CELL [INTEGER]): NATURAL_32
|
||||||
|
-- Character decoded from string `v' starting from index `a_position.item'
|
||||||
|
-- note: it also updates `a_position.item' to indicate the new index position.
|
||||||
|
require
|
||||||
|
valid_start: a_position.item <= v.count
|
||||||
|
is_percent_char: v.code (a_position.item) = 37 -- 37 '%%'
|
||||||
|
local
|
||||||
|
c: NATURAL_32
|
||||||
|
i, n: INTEGER
|
||||||
|
not_a_digit: BOOLEAN
|
||||||
|
ascii_pos: NATURAL_32
|
||||||
|
ival: NATURAL_32
|
||||||
|
pos: INTEGER
|
||||||
|
c_is_digit: BOOLEAN
|
||||||
|
do
|
||||||
|
--| pos is index in stream of escape character ('%')
|
||||||
|
pos := a_position.item
|
||||||
|
c := v.code (pos + 1)
|
||||||
|
if c = 85 or c = 117 then -- 117 'u' 85 'U'
|
||||||
|
-- NOTE: this is not a standard, but it can occur, so use this for decoding only
|
||||||
|
-- An escaped Unicode (ucs2) value, from ECMA scripts
|
||||||
|
-- has the form: %u<n> where <n> is the UCS value
|
||||||
|
-- of the character (two byte integer, one to 4 chars
|
||||||
|
-- after escape sequence).
|
||||||
|
-- See: http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations
|
||||||
|
-- UTF-8 result can be 1 to 4 characters.
|
||||||
|
from
|
||||||
|
i := pos + 2
|
||||||
|
n := v.count
|
||||||
|
until
|
||||||
|
(i > n) or not_a_digit
|
||||||
|
loop
|
||||||
|
c := v.code (i)
|
||||||
|
c_is_digit := (48 <= c and c <= 57) -- DIGIT: 0 .. 9
|
||||||
|
if
|
||||||
|
c_is_digit
|
||||||
|
or (97 <= c and c <= 102) -- ALPHA: a..f
|
||||||
|
or (65 <= c and c <= 70) -- ALPHA: A..F
|
||||||
|
then
|
||||||
|
ival := ival * 16
|
||||||
|
if c_is_digit then
|
||||||
|
ival := ival + (c - 48) -- 48 '0'
|
||||||
|
else
|
||||||
|
if c > 70 then -- a..f
|
||||||
|
ival := ival + (c - 97) + 10 -- 97 'a'
|
||||||
|
else -- A..F
|
||||||
|
ival := ival + (c - 65) + 10 -- 65 'A'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
i := i + 1
|
||||||
|
else
|
||||||
|
not_a_digit := True
|
||||||
|
i := i - 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
a_position.replace (i)
|
||||||
|
Result := ival
|
||||||
|
else
|
||||||
|
-- ASCII char?
|
||||||
|
ascii_pos := hexadecimal_string_to_natural_32 (v.substring (pos + 1, pos + 2))
|
||||||
|
Result := ascii_pos
|
||||||
|
a_position.replace (pos + 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
next_percent_decoded_unicode_character_code (v: READABLE_STRING_GENERAL; a_position: CELL [INTEGER]): NATURAL_32
|
||||||
|
-- Next decoded character from `v' at position `a_position.item'
|
||||||
|
-- note: it also updates `a_position' to indicate the new index position.
|
||||||
|
require
|
||||||
|
valid_start: a_position.item <= v.count
|
||||||
|
is_percent_char: v.code (a_position.item) = 37 -- 37 '%%'
|
||||||
|
local
|
||||||
|
n, j: INTEGER
|
||||||
|
c: NATURAL_32
|
||||||
|
c1, c2, c3, c4: NATURAL_32
|
||||||
|
pr: CELL [INTEGER]
|
||||||
|
do
|
||||||
|
create pr.put (a_position.item)
|
||||||
|
c1 := next_percent_decoded_character_code (v, pr)
|
||||||
|
|
||||||
|
j := pr.item
|
||||||
|
n := v.count
|
||||||
|
|
||||||
|
Result := c1
|
||||||
|
a_position.replace (j)
|
||||||
|
|
||||||
|
if c1 <= 0x7F then
|
||||||
|
-- 0xxxxxxx
|
||||||
|
Result := c1
|
||||||
|
elseif c1 <= 0xDF then
|
||||||
|
-- 110xxxxx 10xxxxxx
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c2 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
Result := (
|
||||||
|
((c1 & 0x1F) |<< 6) |
|
||||||
|
( c2 & 0x3F )
|
||||||
|
)
|
||||||
|
a_position.replace (j)
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif c1 <= 0xEF then
|
||||||
|
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c2 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c3 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
|
||||||
|
Result := (
|
||||||
|
((c1 & 0xF) |<< 12) |
|
||||||
|
((c2 & 0x3F) |<< 6) |
|
||||||
|
( c3 & 0x3F )
|
||||||
|
)
|
||||||
|
a_position.replace (j)
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif c1 <= 0xF7 then
|
||||||
|
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||||
|
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c2 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c3 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
if j + 2 <= n then
|
||||||
|
c := v.code (j + 1)
|
||||||
|
if c = 37 then -- 37 '%%'
|
||||||
|
pr.replace (j + 1)
|
||||||
|
c4 := next_percent_decoded_character_code (v, pr)
|
||||||
|
j := pr.item
|
||||||
|
|
||||||
|
a_position.replace (j)
|
||||||
|
|
||||||
|
Result := (
|
||||||
|
((c1 & 0x7) |<< 18 ) |
|
||||||
|
((c2 & 0x3F) |<< 12) |
|
||||||
|
((c3 & 0x3F) |<< 6) |
|
||||||
|
( c4 & 0x3F )
|
||||||
|
)
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Do not try to decode
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Result := c1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature -- RFC and characters
|
||||||
|
|
||||||
|
is_hexa_decimal_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- Is hexadecimal character ?
|
||||||
|
do
|
||||||
|
Result := ('a' <= c and c <= 'f') or ('A' <= c and c <= 'F') -- HEXA
|
||||||
|
or ('0' <= c and c <= '9') -- DIGIT
|
||||||
|
end
|
||||||
|
|
||||||
|
is_alpha_or_digit_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- Is ALPHA or DIGIT character ?
|
||||||
|
do
|
||||||
|
Result := ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z') -- ALPHA
|
||||||
|
or ('0' <= c and c <= '9') -- DIGIT
|
||||||
|
end
|
||||||
|
|
||||||
|
is_alpha_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- Is ALPHA character ?
|
||||||
|
do
|
||||||
|
Result := ('a' <= c and c <= 'z') or ('A' <= c and c <= 'Z')
|
||||||
|
end
|
||||||
|
|
||||||
|
is_digit_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- Is DIGIT character ?
|
||||||
|
do
|
||||||
|
Result := ('0' <= c and c <= '9')
|
||||||
|
end
|
||||||
|
|
||||||
|
is_unreserved_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
do
|
||||||
|
if
|
||||||
|
('a' <= c and c <= 'z') -- ALPHA
|
||||||
|
or ('A' <= c and c <= 'Z') -- ALPHA
|
||||||
|
or ('0' <= c and c <= '9') -- DIGIT
|
||||||
|
then
|
||||||
|
Result := True
|
||||||
|
else
|
||||||
|
inspect c
|
||||||
|
when '-', '_', '.', '~' then -- unreserved
|
||||||
|
Result := True
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
is_reserved_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- reserved = gen-delims / sub-delims
|
||||||
|
do
|
||||||
|
Result := is_gen_delims_character (c) or is_sub_delims_character (c)
|
||||||
|
end
|
||||||
|
|
||||||
|
is_gen_delims_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
||||||
|
do
|
||||||
|
inspect c
|
||||||
|
when ':' , '/', '?' , '#' , '[' , ']' , '@' then
|
||||||
|
Result := True
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
is_sub_delims_character (c: CHARACTER_32): BOOLEAN
|
||||||
|
-- sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
|
-- / "*" / "+" / "," / ";" / "="
|
||||||
|
do
|
||||||
|
inspect c
|
||||||
|
when '!' , '$' , '&' , '%'' , '(' , ')' , '*' , '+' , ',' , ';' , '=' then -- sub-delims
|
||||||
|
Result := True
|
||||||
|
else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
|
hex_digit: SPECIAL [NATURAL_32]
|
||||||
|
-- Hexadecimal digits.
|
||||||
|
once
|
||||||
|
create Result.make_filled (0, 16)
|
||||||
|
Result [0] := {NATURAL_32} 48 -- 48 '0'
|
||||||
|
Result [1] := {NATURAL_32} 49 -- 49 '1'
|
||||||
|
Result [2] := {NATURAL_32} 50 -- 50 '2'
|
||||||
|
Result [3] := {NATURAL_32} 51 -- 51 '3'
|
||||||
|
Result [4] := {NATURAL_32} 52 -- 52 '4'
|
||||||
|
Result [5] := {NATURAL_32} 53 -- 53 '5'
|
||||||
|
Result [6] := {NATURAL_32} 54 -- 54 '6'
|
||||||
|
Result [7] := {NATURAL_32} 55 -- 55 '7'
|
||||||
|
Result [8] := {NATURAL_32} 56 -- 56 '8'
|
||||||
|
Result [9] := {NATURAL_32} 57 -- 57 '9'
|
||||||
|
Result [10] := {NATURAL_32} 65 -- 65 'A'
|
||||||
|
Result [11] := {NATURAL_32} 66 -- 66 'B'
|
||||||
|
Result [12] := {NATURAL_32} 67 -- 67 'C'
|
||||||
|
Result [13] := {NATURAL_32} 68 -- 68 'D'
|
||||||
|
Result [14] := {NATURAL_32} 69 -- 69 'E'
|
||||||
|
Result [15] := {NATURAL_32} 70 -- 70 'F'
|
||||||
|
end
|
||||||
|
|
||||||
|
is_hexa_decimal (a_string: READABLE_STRING_GENERAL): BOOLEAN
|
||||||
|
-- Is `a_string' a valid hexadecimal sequence?
|
||||||
|
local
|
||||||
|
l_convertor: like ctoi_convertor
|
||||||
|
do
|
||||||
|
l_convertor := ctoi_convertor
|
||||||
|
l_convertor.parse_string_with_type (a_string, {NUMERIC_INFORMATION}.type_natural_32)
|
||||||
|
Result := l_convertor.is_integral_integer
|
||||||
|
end
|
||||||
|
|
||||||
|
hexadecimal_string_to_natural_32 (a_hex_string: READABLE_STRING_GENERAL): NATURAL_32
|
||||||
|
-- Convert hexadecimal value `a_hex_string' to its corresponding NATURAL_32 value.
|
||||||
|
require
|
||||||
|
is_hexa: is_hexa_decimal (a_hex_string)
|
||||||
|
local
|
||||||
|
l_convertor: like ctoi_convertor
|
||||||
|
do
|
||||||
|
l_convertor := ctoi_convertor
|
||||||
|
l_convertor.parse_string_with_type (a_hex_string, {NUMERIC_INFORMATION}.type_no_limitation)
|
||||||
|
Result := l_convertor.parsed_natural_32
|
||||||
|
end
|
||||||
|
|
||||||
|
ctoi_convertor: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
|
||||||
|
-- Converter used to convert string to integer or natural.
|
||||||
|
once
|
||||||
|
create Result.make
|
||||||
|
Result.set_leading_separators_acceptable (False)
|
||||||
|
Result.set_trailing_separators_acceptable (False)
|
||||||
|
ensure
|
||||||
|
ctoi_convertor_not_void: Result /= Void
|
||||||
|
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,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
|
ctx: like context
|
||||||
p_slist: POINTER
|
p_slist: POINTER
|
||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
|
||||||
l_upload_data: detachable READABLE_STRING_8
|
l_upload_data: detachable READABLE_STRING_8
|
||||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||||
l_headers: like headers
|
l_headers: like headers
|
||||||
@@ -82,9 +81,6 @@ feature -- Execution
|
|||||||
|
|
||||||
--| URL
|
--| URL
|
||||||
l_url := url
|
l_url := url
|
||||||
if ctx /= Void then
|
|
||||||
append_parameters_to_url (ctx.query_parameters, l_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
if session.is_header_sent_verbose then
|
if session.is_header_sent_verbose then
|
||||||
io.error.put_string ("> Sending:%N")
|
io.error.put_string ("> Sending:%N")
|
||||||
@@ -154,62 +150,19 @@ feature -- Execution
|
|||||||
--| Credentials not provided ...
|
--| Credentials not provided ...
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ctx.has_upload_data then
|
if ctx.has_upload_data then
|
||||||
l_upload_data := ctx.upload_data
|
l_upload_data := ctx.upload_data
|
||||||
end
|
end
|
||||||
if ctx.has_upload_filename then
|
if ctx.has_upload_filename then
|
||||||
l_upload_filename := ctx.upload_filename
|
l_upload_filename := ctx.upload_filename
|
||||||
end
|
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_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_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
|
|
||||||
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
|
if l_upload_data /= Void then
|
||||||
check
|
check
|
||||||
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
|
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 ("PUT")
|
||||||
or request_method.is_case_insensitive_equal ("PATCH")
|
or request_method.is_case_insensitive_equal ("PATCH")
|
||||||
end
|
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_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)
|
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
|
||||||
@@ -219,6 +172,7 @@ feature -- Execution
|
|||||||
or request_method.is_case_insensitive_equal ("PUT")
|
or request_method.is_case_insensitive_equal ("PUT")
|
||||||
or request_method.is_case_insensitive_equal ("PATCH")
|
or request_method.is_case_insensitive_equal ("PATCH")
|
||||||
end
|
end
|
||||||
|
check no_form_data: not ctx.has_form_data end
|
||||||
|
|
||||||
create l_upload_file.make_with_name (l_upload_filename)
|
create l_upload_file.make_with_name (l_upload_filename)
|
||||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||||
@@ -233,12 +187,59 @@ feature -- Execution
|
|||||||
l_upload_file.open_read
|
l_upload_file.open_read
|
||||||
curl_easy.set_curl_function (l_custom_function)
|
curl_easy.set_curl_function (l_custom_function)
|
||||||
end
|
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
|
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
|
||||||
end -- ctx /= Void
|
end -- ctx /= Void
|
||||||
|
|
||||||
--| Header
|
--| Header
|
||||||
across
|
across
|
||||||
l_headers as curs
|
l_headers as curs
|
||||||
loop
|
loop
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ feature -- Custom
|
|||||||
local
|
local
|
||||||
req: HTTP_CLIENT_REQUEST
|
req: HTTP_CLIENT_REQUEST
|
||||||
do
|
do
|
||||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (url (a_path, ctx), a_method, Current, ctx)
|
||||||
Result := req.response
|
Result := req.response
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (url (a_path, ctx), a_method, Current, ctx)
|
||||||
Result := req.response
|
Result := req.response
|
||||||
|
|
||||||
if f /= Void then
|
if f /= Void then
|
||||||
@@ -176,7 +176,7 @@ feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
|
|||||||
|
|
||||||
|
|
||||||
;note
|
;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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class
|
|||||||
LIBCURL_UPLOAD_FILE_READ_FUNCTION
|
LIBCURL_UPLOAD_FILE_READ_FUNCTION
|
||||||
|
|
||||||
obsolete
|
obsolete
|
||||||
"Use LIBCURL_CUSTOM_FUNCTION [2013-apr-04]"
|
"Use LIBCURL_CUSTOM_FUNCTION [2017-05-31]"
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
LIBCURL_DEFAULT_FUNCTION
|
LIBCURL_DEFAULT_FUNCTION
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ feature -- Access
|
|||||||
l_authorization: HTTP_AUTHORIZATION
|
l_authorization: HTTP_AUTHORIZATION
|
||||||
l_platform: STRING
|
l_platform: STRING
|
||||||
l_upload_data: detachable READABLE_STRING_8
|
l_upload_data: detachable READABLE_STRING_8
|
||||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
|
||||||
ctx: like context
|
ctx: like context
|
||||||
|
l_ct: detachable READABLE_STRING_8
|
||||||
l_upload_file: detachable RAW_FILE
|
l_upload_file: detachable RAW_FILE
|
||||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||||
l_form_string: STRING
|
l_form_string: STRING
|
||||||
@@ -149,14 +149,14 @@ feature -- Access
|
|||||||
then
|
then
|
||||||
create l_authorization.make_basic_auth (u_name, u_pass)
|
create l_authorization.make_basic_auth (u_name, u_pass)
|
||||||
if attached l_authorization.http_authorization as auth then
|
if attached l_authorization.http_authorization as auth then
|
||||||
headers.extend (auth, "Authorization")
|
headers.force (auth, "Authorization")
|
||||||
end
|
end
|
||||||
check headers.has_key ("Authorization") end
|
check headers.has_key ("Authorization") end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
create l_request_uri.make_from_string (l_uri.path)
|
create l_request_uri.make_from_string (l_uri.path)
|
||||||
if attached l_uri.query as l_query then
|
if attached l_uri.query as l_query and then not l_query.is_empty then
|
||||||
l_request_uri.append_character ('?')
|
l_request_uri.append_character ('?')
|
||||||
l_request_uri.append (l_query)
|
l_request_uri.append (l_query)
|
||||||
end
|
end
|
||||||
@@ -176,7 +176,7 @@ feature -- Access
|
|||||||
else
|
else
|
||||||
l_platform := "Unknown"
|
l_platform := "Unknown"
|
||||||
end
|
end
|
||||||
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
headers.force ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- handle sending data
|
-- handle sending data
|
||||||
@@ -191,67 +191,52 @@ feature -- Access
|
|||||||
l_upload_data := ctx.upload_data
|
l_upload_data := ctx.upload_data
|
||||||
end
|
end
|
||||||
|
|
||||||
if ctx.has_form_data then
|
if l_upload_data /= Void 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_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_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_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
|
|
||||||
check ctx.has_upload_data end
|
check ctx.has_upload_data end
|
||||||
|
check no_form_data: not ctx.has_form_data end
|
||||||
if not headers.has ("Content-Type") then
|
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
|
end
|
||||||
if not l_is_chunked_transfer_encoding then
|
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
|
end
|
||||||
elseif l_upload_filename /= Void then
|
elseif l_upload_filename /= Void then
|
||||||
check ctx.has_upload_filename end
|
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)
|
create l_upload_file.make_with_name (l_upload_filename)
|
||||||
if l_upload_file.exists and then l_upload_file.readable then
|
if l_upload_file.exists and then l_upload_file.is_access_readable then
|
||||||
if not l_is_chunked_transfer_encoding 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
|
||||||
end
|
end
|
||||||
check l_upload_file /= Void 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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -482,66 +467,41 @@ feature {NONE} -- Helpers
|
|||||||
Result := a_status >= 300 and a_status < 400
|
Result := a_status >= 300 and a_status < 400
|
||||||
end
|
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.
|
-- Form data and uploaded files converted to mime string.
|
||||||
-- TODO: design a proper MIME... component.
|
-- 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
|
do
|
||||||
create Result.make (100)
|
create Result.make (100)
|
||||||
across
|
across
|
||||||
a_form_parameters as ic
|
a_form_parameters as ic
|
||||||
loop
|
loop
|
||||||
|
Result.append ("--")
|
||||||
Result.append (a_mime_boundary)
|
Result.append (a_mime_boundary)
|
||||||
Result.append (http_end_of_header_line)
|
Result.append (http_end_of_header_line)
|
||||||
Result.append ("Content-Disposition: form-data; name=")
|
Result.append ("Content-Disposition: form-data; name=")
|
||||||
Result.append_character ('%"')
|
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_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
|
if
|
||||||
attached l_path.extension as ext and then
|
attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam and then
|
||||||
attached l_mime_type_mapping.mime_type (ext) as l_mt
|
attached fileparam.file_name as fn
|
||||||
then
|
then
|
||||||
l_mime_type := l_mt
|
Result.append ("; filename=")
|
||||||
else
|
Result.append_character ('%"')
|
||||||
l_mime_type := "application/octet-stream"
|
Result.append (string_to_mime_encoded_string (fn))
|
||||||
|
Result.append_character ('%"')
|
||||||
end
|
end
|
||||||
|
if attached ic.item.content_type as ct then
|
||||||
Result.append (a_mime_boundary)
|
Result.append (http_end_of_header_line)
|
||||||
Result.append (http_end_of_header_line)
|
Result.append ("Content-Type: ")
|
||||||
Result.append ("Content-Disposition: form-data; name=%"")
|
Result.append (ct)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
Result.append (http_end_of_header_line)
|
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
|
end
|
||||||
|
Result.append ("--")
|
||||||
Result.append (a_mime_boundary)
|
Result.append (a_mime_boundary)
|
||||||
Result.append ("--") --| end
|
Result.append ("--") --| end
|
||||||
end
|
end
|
||||||
@@ -891,7 +851,7 @@ feature {NONE} -- Helpers
|
|||||||
end
|
end
|
||||||
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.
|
-- New MIME boundary.
|
||||||
local
|
local
|
||||||
s: STRING
|
s: STRING
|
||||||
@@ -902,12 +862,12 @@ feature {NONE} -- Helpers
|
|||||||
across
|
across
|
||||||
a_data as ic
|
a_data as ic
|
||||||
loop
|
loop
|
||||||
i := i + ic.item.count + ic.key.count
|
i := i + ic.item.count + ic.item.name.count
|
||||||
end
|
end
|
||||||
create ran.set_seed (i) -- FIXME: use a real random seed.
|
create ran.set_seed (i) -- FIXME: use a real random seed.
|
||||||
ran.start
|
ran.start
|
||||||
ran.forth
|
ran.forth
|
||||||
n := (20 * ran.real_item).truncated_to_integer
|
n := (10 * ran.real_item).truncated_to_integer
|
||||||
create Result.make_filled ('-', 3 + n)
|
create Result.make_filled ('-', 3 + n)
|
||||||
s := "_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
s := "_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
from
|
from
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ feature -- Custom
|
|||||||
local
|
local
|
||||||
req: HTTP_CLIENT_REQUEST
|
req: HTTP_CLIENT_REQUEST
|
||||||
do
|
do
|
||||||
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
create {NET_HTTP_CLIENT_REQUEST} req.make (url (a_path, ctx), a_method, Current, ctx)
|
||||||
Result := req.response
|
Result := req.response
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -167,12 +167,12 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
create {NET_HTTP_CLIENT_REQUEST} req.make (url (a_path, ctx), a_method, Current, ctx)
|
||||||
Result := req.response
|
Result := req.response
|
||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ feature -- Custom
|
|||||||
|
|
||||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||||
do
|
do
|
||||||
create Result.make (base_url + a_path)
|
create Result.make (url (a_path, ctx))
|
||||||
end
|
end
|
||||||
|
|
||||||
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||||
@@ -98,7 +98,7 @@ feature -- Status report
|
|||||||
-- Is interface usable?
|
-- Is interface usable?
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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">
|
<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="test_http_client">
|
<target name="testing_http_client">
|
||||||
<root class="TEST" feature="make"/>
|
<root class="TEST" feature="make"/>
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/.git$</exclude>
|
<exclude>/.git$</exclude>
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
<option warning="true" void_safety="all">
|
<option warning="true" void_safety="all">
|
||||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
</option>
|
</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="net_http_client_disabled" value="false"/>
|
||||||
<variable name="libcurl_http_client_disabled" value="false"/>
|
<variable name="libcurl_http_client_disabled" value="false"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
@@ -34,6 +34,11 @@ feature -- Tests
|
|||||||
test_post_with_form_data
|
test_post_with_form_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
libcurl_test_post_with_uncommon_form_data
|
||||||
|
do
|
||||||
|
test_post_with_uncommon_form_data
|
||||||
|
end
|
||||||
|
|
||||||
libcurl_test_post_with_file
|
libcurl_test_post_with_file
|
||||||
do
|
do
|
||||||
test_post_with_file
|
test_post_with_file
|
||||||
@@ -54,6 +59,11 @@ feature -- Tests
|
|||||||
test_post_with_file_and_form_data
|
test_post_with_file_and_form_data
|
||||||
end
|
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
|
libcurl_test_get_with_redirection
|
||||||
do
|
do
|
||||||
test_get_with_redirection
|
test_get_with_redirection
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ feature -- Tests
|
|||||||
test_post_with_form_data
|
test_post_with_form_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
net_test_post_with_uncommon_form_data
|
||||||
|
do
|
||||||
|
test_post_with_uncommon_form_data
|
||||||
|
end
|
||||||
|
|
||||||
net_test_post_with_file
|
net_test_post_with_file
|
||||||
do
|
do
|
||||||
test_post_with_file
|
test_post_with_file
|
||||||
@@ -54,6 +59,11 @@ feature -- Tests
|
|||||||
test_post_with_file_and_form_data
|
test_post_with_file_and_form_data
|
||||||
end
|
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
|
net_test_get_with_redirection
|
||||||
do
|
do
|
||||||
test_get_with_redirection
|
test_get_with_redirection
|
||||||
@@ -69,4 +79,9 @@ feature -- Tests
|
|||||||
test_post_with_file_using_chunked_transfer_encoding
|
test_post_with_file_using_chunked_transfer_encoding
|
||||||
end
|
end
|
||||||
|
|
||||||
|
net_test_get_with_query_parameters
|
||||||
|
do
|
||||||
|
test_get_with_query_parameters
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
note
|
note
|
||||||
description: "[
|
description: "[
|
||||||
Eiffel tests that can be executed by testing tool.
|
Eiffel tests that can be executed by testing tool.
|
||||||
]"
|
]"
|
||||||
@@ -21,7 +21,9 @@ feature -- Initialization
|
|||||||
on_prepare
|
on_prepare
|
||||||
do
|
do
|
||||||
Precursor
|
Precursor
|
||||||
global_requestbin_path := new_requestbin_path
|
if is_using_requestbin and global_requestbin_path = Void then
|
||||||
|
global_requestbin_path := new_requestbin_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Factory
|
feature -- Factory
|
||||||
@@ -30,7 +32,13 @@ feature -- Factory
|
|||||||
deferred
|
deferred
|
||||||
end
|
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
|
global_requestbin_path: detachable READABLE_STRING_8
|
||||||
|
|
||||||
@@ -39,7 +47,7 @@ feature -- Requestbin
|
|||||||
i,j: INTEGER
|
i,j: INTEGER
|
||||||
do
|
do
|
||||||
if
|
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
|
attached sess.post ("/api/v1/bins", Void, Void) as resp
|
||||||
then
|
then
|
||||||
if resp.error_occurred then
|
if resp.error_occurred then
|
||||||
@@ -49,6 +57,9 @@ feature -- Requestbin
|
|||||||
i := l_content.substring_index ("%"name%":", 1)
|
i := l_content.substring_index ("%"name%":", 1)
|
||||||
if i > 0 then
|
if i > 0 then
|
||||||
j := l_content.index_of (',', i + 1)
|
j := l_content.index_of (',', i + 1)
|
||||||
|
if j = 0 then
|
||||||
|
j := l_content.index_of ('}', i + 1)
|
||||||
|
end
|
||||||
if j > 0 then
|
if j > 0 then
|
||||||
Result := l_content.substring (i + 7, j - 1)
|
Result := l_content.substring (i + 7, j - 1)
|
||||||
Result.adjust
|
Result.adjust
|
||||||
@@ -61,12 +72,30 @@ feature -- Requestbin
|
|||||||
if not Result.starts_with ("/") then
|
if not Result.starts_with ("/") then
|
||||||
Result.prepend_character ('/')
|
Result.prepend_character ('/')
|
||||||
end
|
end
|
||||||
|
print ("new_requestbin_path => " + sess.base_url + Result + "?inspect%N")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
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
|
feature -- Factory
|
||||||
|
|
||||||
test_post_url_encoded
|
test_post_url_encoded
|
||||||
@@ -74,238 +103,200 @@ feature -- Factory
|
|||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
h: STRING_8
|
||||||
do
|
do
|
||||||
if attached global_requestbin_path as requestbin_path then
|
-- URL ENCODED POST REQUEST
|
||||||
-- URL ENCODED POST REQUEST
|
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
||||||
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
-- also check that User-Agent was sent
|
||||||
-- also check that User-Agent was sent
|
create h.make_empty
|
||||||
create h.make_empty
|
sess := new_web_session
|
||||||
sess := new_session ("http://requestb.in")
|
if
|
||||||
if
|
attached sess.post ("", Void, "Hello World") as res
|
||||||
attached sess.post (requestbin_path, Void, "Hello World") as res and then
|
then
|
||||||
attached res.headers as hds
|
check_response (res)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_post_with_form_data
|
test_post_with_form_data
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
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
|
||||||
|
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
|
||||||
|
|
||||||
-- POST REQUEST WITH FORM DATA
|
test_post_with_uncommon_form_data
|
||||||
-- check requestbin to ensure the form parameters are correctly received
|
local
|
||||||
sess := new_session ("http://requestb.in")
|
sess: HTTP_CLIENT_SESSION
|
||||||
create l_ctx.make
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
do
|
||||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
-- POST REQUEST WITH FORM DATA
|
||||||
create h.make_empty
|
-- check requestbin to ensure the form parameters are correctly received
|
||||||
if
|
sess := new_web_session
|
||||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
create l_ctx.make
|
||||||
attached res.headers as hds
|
|
||||||
then
|
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
|
||||||
across
|
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
|
||||||
hds as c
|
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
|
||||||
loop
|
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
|
||||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
|
||||||
end
|
l_ctx.add_form_parameter ("test", "!$&'()*") --
|
||||||
end
|
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
|
||||||
print (h)
|
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
|
||||||
else
|
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
|
||||||
assert ("Has requestbin path", False)
|
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"))
|
||||||
|
|
||||||
|
if
|
||||||
|
attached sess.post ("", l_ctx, "") as res
|
||||||
|
then
|
||||||
|
check_response (res)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_post_with_file
|
test_post_with_file
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
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
|
||||||
-- POST REQUEST WITH A FILE
|
-- set filename to a local file
|
||||||
-- check requestbin to ensure the form parameters are correctly received
|
sess := new_web_session
|
||||||
-- set filename to a local file
|
create l_ctx.make
|
||||||
sess := new_session ("http://requestb.in")
|
l_ctx.set_upload_filename ("test.txt")
|
||||||
create l_ctx.make
|
if
|
||||||
l_ctx.set_upload_filename ("test.txt")
|
attached sess.post ("", l_ctx, Void) as res
|
||||||
create h.make_empty
|
then
|
||||||
if
|
check_response (res)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_put_with_file
|
test_put_with_file
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
do
|
||||||
if attached global_requestbin_path as requestbin_path then
|
-- PUT REQUEST WITH A FILE
|
||||||
|
-- check requestbin to ensure the file is correctly received
|
||||||
-- PUT REQUEST WITH A FILE
|
-- set filename to a local file
|
||||||
-- check requestbin to ensure the file is correctly received
|
sess := new_web_session
|
||||||
-- set filename to a local file
|
create l_ctx.make
|
||||||
sess := new_session ("http://requestb.in")
|
l_ctx.set_upload_filename ("test.txt")
|
||||||
create l_ctx.make
|
if
|
||||||
l_ctx.set_upload_filename ("test.txt")
|
attached sess.put ("", l_ctx, Void) as res
|
||||||
create h.make_empty
|
then
|
||||||
if
|
check_response (res)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_put_with_data
|
test_put_with_data
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
do
|
||||||
if attached global_requestbin_path as requestbin_path then
|
-- PUT REQUEST WITH A FILE
|
||||||
|
-- check requestbin to ensure the file is correctly received
|
||||||
-- PUT REQUEST WITH A FILE
|
-- set filename to a local file
|
||||||
-- check requestbin to ensure the file is correctly received
|
sess := new_web_session
|
||||||
-- set filename to a local file
|
create l_ctx.make
|
||||||
sess := new_session ("http://requestb.in")
|
l_ctx.set_upload_data ("name=This is a test for http client.%N")
|
||||||
create l_ctx.make
|
if
|
||||||
l_ctx.set_upload_data ("This is a test for http client.%N")
|
attached sess.put ("", l_ctx, Void) as res
|
||||||
create h.make_empty
|
then
|
||||||
if
|
check_response (res)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test_post_with_file_and_form_data
|
test_post_with_file_and_form_data
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
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
|
test_post_with_multiple_file_and_form_data
|
||||||
-- check requestbin to ensure the file and form parameters are correctly received
|
local
|
||||||
-- set filename to a local file
|
sess: HTTP_CLIENT_SESSION
|
||||||
sess := new_session ("http://requestb.in")
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
create l_ctx.make
|
do
|
||||||
l_ctx.set_upload_filename ("logo.jpg")
|
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
-- check requestbin to ensure the file and form parameters are correctly received
|
||||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
-- set filename to a local file
|
||||||
create h.make_empty
|
sess := new_web_session
|
||||||
if
|
create l_ctx.make
|
||||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
l_ctx.add_header ("Content-Type", "multipart/form-data")
|
||||||
attached res.headers as hds
|
|
||||||
then
|
l_ctx.add_file_form_parameter ("first_file", "test.txt", "plain/text")
|
||||||
across
|
l_ctx.add_file_form_parameter ("image", "logo.jpg", "image/jpeg")
|
||||||
hds as c
|
l_ctx.add_form_parameter ("First", "Value")
|
||||||
loop
|
l_ctx.add_form_parameter ("Second", "and last value")
|
||||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
l_ctx.add_file_form_parameter ("last_file", "test.txt", Void)
|
||||||
end
|
|
||||||
end
|
if
|
||||||
print (h)
|
attached sess.post ("", l_ctx, Void) as res
|
||||||
else
|
then
|
||||||
assert ("Has requestbin path", False)
|
check_response (res)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_post_with_file_using_chunked_transfer_encoding
|
test_post_with_file_using_chunked_transfer_encoding
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
do
|
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
|
||||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
-- set filename to a local file
|
||||||
-- check requestbin to ensure the file and form parameters are correctly received
|
sess := new_web_session
|
||||||
-- set filename to a local file
|
create l_ctx.make
|
||||||
sess := new_session ("http://requestb.in")
|
l_ctx.add_header ("Transfer-Encoding", "chunked")
|
||||||
create l_ctx.make
|
l_ctx.set_upload_filename ("logo.jpg")
|
||||||
l_ctx.add_header ("Transfer-Encoding", "chunked")
|
if
|
||||||
l_ctx.set_upload_filename ("logo.jpg")
|
attached sess.post ("", l_ctx, Void) as res
|
||||||
create h.make_empty
|
then
|
||||||
if
|
check_response (res)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_get_with_redirection
|
test_get_with_redirection
|
||||||
local
|
local
|
||||||
sess: HTTP_CLIENT_SESSION
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
|
||||||
do
|
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)
|
||||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
sess := new_session ("http://google.com")
|
||||||
-- check headers received (printed in console)
|
if attached sess.get ("/", Void) as res then
|
||||||
sess := new_session ("http://google.com")
|
check_response (res)
|
||||||
create h.make_empty
|
assert("was redirected", res.redirections_count > 0)
|
||||||
if attached sess.get ("/", 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)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -320,6 +311,7 @@ feature -- Factory
|
|||||||
sess.set_credentials ("test", "test")
|
sess.set_credentials ("test", "test")
|
||||||
create ctx.make_with_credentials_required
|
create ctx.make_with_credentials_required
|
||||||
if attached sess.get ("/password-ok.php", ctx) as res then
|
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
|
if attached {READABLE_STRING_8} res.body as l_body then
|
||||||
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
||||||
else
|
else
|
||||||
@@ -328,6 +320,61 @@ feature -- Factory
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test_get_with_query_parameters
|
||||||
|
local
|
||||||
|
sess: HTTP_CLIENT_SESSION
|
||||||
|
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
|
q: STRING
|
||||||
|
do
|
||||||
|
-- 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"))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/http_stream_socket_ext.e$</exclude>
|
<exclude>/http_stream_socket_ext.e$</exclude>
|
||||||
<condition>
|
<condition>
|
||||||
<version type="compiler" max="16.9.9.9124"/>
|
<version type="compiler" max="17.02"/>
|
||||||
</condition>
|
</condition>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
|
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
|
||||||
@@ -114,6 +114,11 @@
|
|||||||
</condition>
|
</condition>
|
||||||
</cluster>
|
</cluster>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
<cluster name="network_until_17_01" location=".\src\until_17_01\">
|
||||||
|
<condition>
|
||||||
|
<version type="compiler" min="16.9.9.9124" max="17.02"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
</target>
|
</target>
|
||||||
<target name="http_network_ssl" extends="http_network">
|
<target name="http_network_ssl" extends="http_network">
|
||||||
<variable name="ssl_enabled" value="true"/>
|
<variable name="ssl_enabled" value="true"/>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/http_stream_socket_ext.e$</exclude>
|
<exclude>/http_stream_socket_ext.e$</exclude>
|
||||||
<condition>
|
<condition>
|
||||||
<version type="compiler" max="16.9.9.9124"/>
|
<version type="compiler" max="17.02"/>
|
||||||
</condition>
|
</condition>
|
||||||
</file_rule>
|
</file_rule>
|
||||||
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
|
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
|
||||||
@@ -114,6 +114,11 @@
|
|||||||
</condition>
|
</condition>
|
||||||
</cluster>
|
</cluster>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
<cluster name="network_until_17_01" location=".\src\until_17_01\">
|
||||||
|
<condition>
|
||||||
|
<version type="compiler" min="16.9.9.9124" max="17.02"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
</target>
|
</target>
|
||||||
<target name="http_network_ssl" extends="http_network">
|
<target name="http_network_ssl" extends="http_network">
|
||||||
<variable name="ssl_enabled" value="true"/>
|
<variable name="ssl_enabled" value="true"/>
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ feature -- Input
|
|||||||
-- Make result available in `last_character'.
|
-- Make result available in `last_character'.
|
||||||
-- No exception raised!
|
-- No exception raised!
|
||||||
do
|
do
|
||||||
read_to_managed_pointer_noexception (socket_buffer, 0, character_8_bytes)
|
read_to_managed_pointer_noexception (read_socket_buffer, 0, character_8_bytes)
|
||||||
if was_error then
|
if was_error then
|
||||||
-- Socket error already set.
|
-- Socket error already set.
|
||||||
elseif bytes_read /= character_8_bytes then
|
elseif bytes_read /= character_8_bytes then
|
||||||
socket_error := "Peer closed connection"
|
socket_error := "Peer closed connection"
|
||||||
else
|
else
|
||||||
last_character := socket_buffer.read_character (0)
|
last_character := read_socket_buffer.read_character (0)
|
||||||
socket_error := Void
|
socket_error := Void
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -208,8 +208,8 @@ feature -- Output
|
|||||||
put_character_noexception (c: CHARACTER)
|
put_character_noexception (c: CHARACTER)
|
||||||
-- Write character `c' to socket.
|
-- Write character `c' to socket.
|
||||||
do
|
do
|
||||||
socket_buffer.put_character (c, 0)
|
put_socket_buffer.put_character (c, 0)
|
||||||
put_managed_pointer_noexception (socket_buffer, 0, character_8_bytes)
|
put_managed_pointer_noexception (put_socket_buffer, 0, character_8_bytes)
|
||||||
end
|
end
|
||||||
|
|
||||||
put_string_8_noexception (s: READABLE_STRING_8)
|
put_string_8_noexception (s: READABLE_STRING_8)
|
||||||
@@ -244,7 +244,7 @@ feature -- Status report
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -2,12 +2,20 @@ note
|
|||||||
description: "[
|
description: "[
|
||||||
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
|
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
|
||||||
|
|
||||||
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
|
TO BE REMOVED IN THE FUTURE, When there is no need to support older compilers.
|
||||||
]"
|
]"
|
||||||
|
|
||||||
deferred class
|
deferred class
|
||||||
HTTP_STREAM_SOCKET_EXT
|
HTTP_STREAM_SOCKET_EXT
|
||||||
|
|
||||||
feature {NONE} -- No-Exception network operation
|
note
|
||||||
|
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
|
end
|
||||||
|
|||||||
@@ -55,12 +55,19 @@ feature -- Secure connection Helpers
|
|||||||
end
|
end
|
||||||
|
|
||||||
set_secure_protocol_to_ssl_2_or_3
|
set_secure_protocol_to_ssl_2_or_3
|
||||||
-- Set `ssl_protocol' with `Ssl_23'.
|
-- Set `ssl_protocol' with `Ssl_23'.
|
||||||
do
|
-- Protocol not supported anymore.
|
||||||
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
|
obsolete
|
||||||
end
|
"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'.
|
-- Set `ssl_protocol' with `Tls_1_0'.
|
||||||
do
|
do
|
||||||
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
|
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
|
||||||
@@ -176,7 +183,14 @@ feature -- Output
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
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)"
|
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
|
end
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ feature -- Access
|
|||||||
deferred
|
deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
|
socket_buffer: MANAGED_POINTER
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
read_socket_buffer: MANAGED_POINTER
|
||||||
|
do
|
||||||
|
Result := socket_buffer
|
||||||
|
end
|
||||||
|
|
||||||
|
put_socket_buffer: MANAGED_POINTER
|
||||||
|
do
|
||||||
|
Result := socket_buffer
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Socket Recv and Send timeout.
|
feature -- Socket Recv and Send timeout.
|
||||||
|
|
||||||
set_recv_timeout (a_timeout_seconds: INTEGER)
|
set_recv_timeout (a_timeout_seconds: INTEGER)
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
|
||||||
|
|
||||||
|
TO BE REMOVED IN THE FUTURE, WHEN 17.01 IS OLD.
|
||||||
|
]"
|
||||||
|
|
||||||
|
deferred class
|
||||||
|
HTTP_STREAM_SOCKET_EXT
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
socket_buffer: MANAGED_POINTER
|
||||||
|
deferred
|
||||||
|
end
|
||||||
|
|
||||||
|
read_socket_buffer: MANAGED_POINTER
|
||||||
|
do
|
||||||
|
Result := socket_buffer
|
||||||
|
end
|
||||||
|
|
||||||
|
put_socket_buffer: MANAGED_POINTER
|
||||||
|
do
|
||||||
|
Result := socket_buffer
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- No-Exception network operation
|
||||||
|
|
||||||
|
end
|
||||||
@@ -114,14 +114,14 @@ feature -- Obsolete query
|
|||||||
|
|
||||||
include_max_age: BOOLEAN
|
include_max_age: BOOLEAN
|
||||||
obsolete
|
obsolete
|
||||||
"Use `max_age > 0' [April-2016]"
|
"Use `max_age > 0' [2017-05-31]"
|
||||||
do
|
do
|
||||||
Result := max_age > 0
|
Result := max_age > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
include_expires: BOOLEAN
|
include_expires: BOOLEAN
|
||||||
obsolete
|
obsolete
|
||||||
"Use `expires /= Void' [April-2016]"
|
"Use `expires /= Void' [2017-05-31]"
|
||||||
do
|
do
|
||||||
Result := expiration /= Void
|
Result := expiration /= Void
|
||||||
end
|
end
|
||||||
@@ -133,7 +133,7 @@ feature -- Obsolete element change
|
|||||||
-- Set `expires to void'
|
-- Set `expires to void'
|
||||||
-- Set-Cookie will include only Max-Age attribute and not Expires.
|
-- Set-Cookie will include only Max-Age attribute and not Expires.
|
||||||
obsolete
|
obsolete
|
||||||
"Uset `set_max_age' and `unset_*' features to add or remove the attributes from the response header [April-2016]"
|
"Uset `set_max_age' and `unset_*' features to add or remove the attributes from the response header [2017-05-31]"
|
||||||
do
|
do
|
||||||
max_age := 1
|
max_age := 1
|
||||||
expiration := Void
|
expiration := Void
|
||||||
@@ -147,7 +147,7 @@ feature -- Obsolete element change
|
|||||||
-- Set `expiration to a default date'
|
-- Set `expiration to a default date'
|
||||||
-- Set-Cookie will include only Expires attribute and not Max_Age.
|
-- Set-Cookie will include only Expires attribute and not Max_Age.
|
||||||
obsolete
|
obsolete
|
||||||
"Use `set_expiration' and `unset_*' features to add or remove the attribute from the response header [April-2016]"
|
"Use `set_expiration' and `unset_*' features to add or remove the attribute from the response header [2017-05-31]"
|
||||||
do
|
do
|
||||||
max_age := -1
|
max_age := -1
|
||||||
set_expiration_date (create {DATE_TIME}.make_now_utc)
|
set_expiration_date (create {DATE_TIME}.make_now_utc)
|
||||||
@@ -343,7 +343,7 @@ feature {NONE} -- Constants
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ feature {NONE} -- Initialization
|
|||||||
create date_time.make_from_epoch (n.as_integer_32)
|
create date_time.make_from_epoch (n.as_integer_32)
|
||||||
end
|
end
|
||||||
|
|
||||||
make_from_string (s: READABLE_STRING_8)
|
make_from_string (s: READABLE_STRING_GENERAL)
|
||||||
-- Create from string representation `s'
|
-- Create from string representation `s'
|
||||||
-- Supports: RFC 1123 and RFC 850
|
-- Supports: RFC 1123 and RFC 850
|
||||||
-- Tolerant with: GMT+offset and GMT-offset
|
-- Tolerant with: GMT+offset and GMT-offset
|
||||||
@@ -350,7 +350,7 @@ feature -- Helper routines.
|
|||||||
|
|
||||||
feature {NONE} -- Implementation
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
string_to_date_time (s: READABLE_STRING_8): detachable DATE_TIME
|
string_to_date_time (s: READABLE_STRING_GENERAL): detachable DATE_TIME
|
||||||
-- String representation of `dt' using the RFC 1123
|
-- String representation of `dt' using the RFC 1123
|
||||||
-- HTTP-date = rfc1123-date | rfc850-date | asctime-date
|
-- HTTP-date = rfc1123-date | rfc850-date | asctime-date
|
||||||
-- rfc1123-date = wkday "," SP date1 SP time SP "GMT"
|
-- rfc1123-date = wkday "," SP date1 SP time SP "GMT"
|
||||||
@@ -377,8 +377,8 @@ feature {NONE} -- Implementation
|
|||||||
note
|
note
|
||||||
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
|
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
|
||||||
local
|
local
|
||||||
t: STRING_8
|
t: STRING_32
|
||||||
l_ddd, l_mmm: detachable STRING_8
|
l_ddd, l_mmm: detachable STRING_32
|
||||||
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
|
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
|
||||||
l_mo: INTEGER
|
l_mo: INTEGER
|
||||||
l_gmt_offset: INTEGER -- minutes
|
l_gmt_offset: INTEGER -- minutes
|
||||||
@@ -565,7 +565,7 @@ feature {NONE} -- Implementation
|
|||||||
t.extend (s[i].as_upper)
|
t.extend (s[i].as_upper)
|
||||||
i := i + 1
|
i := i + 1
|
||||||
end
|
end
|
||||||
if
|
if
|
||||||
t.same_string ("GMT") -- for instance: GMT+0002
|
t.same_string ("GMT") -- for instance: GMT+0002
|
||||||
or t.same_string ("UTC") -- for instance: UTC+0002
|
or t.same_string ("UTC") -- for instance: UTC+0002
|
||||||
or t.is_empty -- for instance: +0002
|
or t.is_empty -- for instance: +0002
|
||||||
@@ -622,7 +622,7 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ansi_c_string_to_date_time (s: READABLE_STRING_8): detachable DATE_TIME
|
ansi_c_string_to_date_time (s: READABLE_STRING_GENERAL): detachable DATE_TIME
|
||||||
-- String representation of `dt' using the RFC 1123
|
-- String representation of `dt' using the RFC 1123
|
||||||
-- asctime-date = wkday SP date3 SP time SP 4DIGIT
|
-- asctime-date = wkday SP date3 SP time SP 4DIGIT
|
||||||
-- date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
|
-- date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
|
||||||
@@ -638,8 +638,8 @@ feature {NONE} -- Implementation
|
|||||||
note
|
note
|
||||||
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
|
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
|
||||||
local
|
local
|
||||||
t: STRING_8
|
t: STRING_32
|
||||||
l_ddd, l_mmm: detachable STRING_8
|
l_ddd, l_mmm: detachable STRING_32
|
||||||
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
|
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
|
||||||
l_mo: INTEGER
|
l_mo: INTEGER
|
||||||
l_gmt_offset: INTEGER -- minutes
|
l_gmt_offset: INTEGER -- minutes
|
||||||
@@ -681,18 +681,18 @@ feature {NONE} -- Implementation
|
|||||||
-- Tolerant to full month name ..
|
-- Tolerant to full month name ..
|
||||||
l_mmm.keep_head (3)
|
l_mmm.keep_head (3)
|
||||||
end
|
end
|
||||||
if l_mmm.same_string ("JAN") then l_mo := 01
|
if l_mmm.same_string_general ("JAN") then l_mo := 01
|
||||||
elseif l_mmm.same_string ("FEB") then l_mo := 02
|
elseif l_mmm.same_string_general ("FEB") then l_mo := 02
|
||||||
elseif l_mmm.same_string ("MAR") then l_mo := 03
|
elseif l_mmm.same_string_general ("MAR") then l_mo := 03
|
||||||
elseif l_mmm.same_string ("APR") then l_mo := 04
|
elseif l_mmm.same_string_general ("APR") then l_mo := 04
|
||||||
elseif l_mmm.same_string ("MAY") then l_mo := 05
|
elseif l_mmm.same_string_general ("MAY") then l_mo := 05
|
||||||
elseif l_mmm.same_string ("JUN") then l_mo := 06
|
elseif l_mmm.same_string_general ("JUN") then l_mo := 06
|
||||||
elseif l_mmm.same_string ("JUL") then l_mo := 07
|
elseif l_mmm.same_string_general ("JUL") then l_mo := 07
|
||||||
elseif l_mmm.same_string ("AUG") then l_mo := 08
|
elseif l_mmm.same_string_general ("AUG") then l_mo := 08
|
||||||
elseif l_mmm.same_string ("SEP") then l_mo := 09
|
elseif l_mmm.same_string_general ("SEP") then l_mo := 09
|
||||||
elseif l_mmm.same_string ("OCT") then l_mo := 10
|
elseif l_mmm.same_string_general ("OCT") then l_mo := 10
|
||||||
elseif l_mmm.same_string ("NOV") then l_mo := 11
|
elseif l_mmm.same_string_general ("NOV") then l_mo := 11
|
||||||
elseif l_mmm.same_string ("DEC") then l_mo := 12
|
elseif l_mmm.same_string_general ("DEC") then l_mo := 12
|
||||||
else err := True
|
else err := True
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -875,7 +875,7 @@ feature {NONE} -- Implementation
|
|||||||
invariant
|
invariant
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2015, 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ feature -- Access
|
|||||||
header_named_value (a_name: READABLE_STRING_8): like item
|
header_named_value (a_name: READABLE_STRING_8): like item
|
||||||
-- First header item found for `a_name' if any
|
-- First header item found for `a_name' if any
|
||||||
obsolete
|
obsolete
|
||||||
"Use `item' [2014-03]"
|
"Use `item' [2017-05-31]"
|
||||||
do
|
do
|
||||||
Result := item (a_name)
|
Result := item (a_name)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="ws_client" uuid="AF6EDC56-D7B4-4E1F-A62B-40EBED3D93DF">
|
<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="ws_client" uuid="AF6EDC56-D7B4-4E1F-A62B-40EBED3D93DF">
|
||||||
<target name="common">
|
<target name="common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/.git$</exclude>
|
<exclude>/.git$</exclude>
|
||||||
<exclude>/.svn$</exclude>
|
<exclude>/.svn$</exclude>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
<?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="echo_websocket_server" uuid="C9B3DA5F-DF0D-4C0F-924A-130B5C1E6604">
|
<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="echo_websocket_server" uuid="C9B3DA5F-DF0D-4C0F-924A-130B5C1E6604">
|
||||||
<target name="common">
|
<target name="common" abstract="true">
|
||||||
<file_rule>
|
<file_rule>
|
||||||
<exclude>/.git$</exclude>
|
<exclude>/.git$</exclude>
|
||||||
<exclude>/.svn$</exclude>
|
<exclude>/.svn$</exclude>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ feature -- Access
|
|||||||
|
|
||||||
body: like content
|
body: like content
|
||||||
obsolete
|
obsolete
|
||||||
"Use `content' [June/2015]"
|
"Use `content'. [2017-05-31]"
|
||||||
do
|
do
|
||||||
Result := body
|
Result := body
|
||||||
end
|
end
|
||||||
@@ -69,6 +69,19 @@ feature -- Status report
|
|||||||
across to_addresses as ic all is_valid_address (ic.item) end
|
across to_addresses as ic all is_valid_address (ic.item) end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
has_header (a_header_name: READABLE_STRING_8): BOOLEAN
|
||||||
|
-- Has additional header `a_header_name'?
|
||||||
|
-- Warning: it checks only `additional_header_lines'!
|
||||||
|
local
|
||||||
|
h_colon: STRING
|
||||||
|
do
|
||||||
|
if attached additional_header_lines as lst then
|
||||||
|
create h_colon.make_from_string (a_header_name)
|
||||||
|
h_colon.append_character (':')
|
||||||
|
Result := across lst as ic some ic.item.starts_with (h_colon) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Change
|
feature -- Change
|
||||||
|
|
||||||
set_date (d: like date)
|
set_date (d: like date)
|
||||||
@@ -158,21 +171,6 @@ feature -- Header manipulation
|
|||||||
lst.force (a_line)
|
lst.force (a_line)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Status report
|
|
||||||
|
|
||||||
has_header (a_header_name: READABLE_STRING_8): BOOLEAN
|
|
||||||
-- Has additional header `a_header_name'?
|
|
||||||
-- Warning: it checks only `additional_header_lines'!
|
|
||||||
local
|
|
||||||
h_colon: STRING
|
|
||||||
do
|
|
||||||
if attached additional_header_lines as lst then
|
|
||||||
create h_colon.make_from_string (a_header_name)
|
|
||||||
h_colon.append_character (':')
|
|
||||||
Result := across lst as ic some ic.item.starts_with (h_colon) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
feature -- Reset
|
feature -- Reset
|
||||||
|
|
||||||
reset
|
reset
|
||||||
@@ -209,8 +207,6 @@ feature -- Conversion
|
|||||||
end
|
end
|
||||||
|
|
||||||
header: STRING_8
|
header: STRING_8
|
||||||
local
|
|
||||||
hdate: HTTP_DATE
|
|
||||||
do
|
do
|
||||||
create Result.make (20)
|
create Result.make (20)
|
||||||
if attached reply_to_address as l_reply_to then
|
if attached reply_to_address as l_reply_to then
|
||||||
@@ -259,8 +255,7 @@ feature -- Conversion
|
|||||||
Result.append (subject)
|
Result.append (subject)
|
||||||
Result.append_character ('%N')
|
Result.append_character ('%N')
|
||||||
Result.append ("Date: ")
|
Result.append ("Date: ")
|
||||||
create hdate.make_from_date_time (date)
|
;(create {HTTP_DATE}.make_from_date_time (date)).append_to_rfc1123_string (Result)
|
||||||
hdate.append_to_rfc1123_string (Result)
|
|
||||||
Result.append_character ('%N')
|
Result.append_character ('%N')
|
||||||
if attached additional_header_lines as l_lines and then
|
if attached additional_header_lines as l_lines and then
|
||||||
not l_lines.is_empty
|
not l_lines.is_empty
|
||||||
@@ -285,11 +280,8 @@ feature -- Helpers
|
|||||||
Result := add.has ('@')
|
Result := add.has ('@')
|
||||||
end
|
end
|
||||||
|
|
||||||
invariant
|
|
||||||
-- invariant_clause: True
|
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -43,11 +43,8 @@ feature {NONE} -- Initialization
|
|||||||
feature -- Status
|
feature -- Status
|
||||||
|
|
||||||
is_available: BOOLEAN
|
is_available: BOOLEAN
|
||||||
local
|
|
||||||
f: RAW_FILE
|
|
||||||
do
|
do
|
||||||
create f.make_with_path (executable_path)
|
Result := (create {RAW_FILE}.make_with_path (executable_path)).exists
|
||||||
Result := f.exists
|
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Change
|
feature -- Change
|
||||||
@@ -108,7 +105,7 @@ feature -- Basic operation
|
|||||||
if attached arguments as l_args then
|
if attached arguments as l_args then
|
||||||
args := l_args.twin
|
args := l_args.twin
|
||||||
else
|
else
|
||||||
if attached {RAW_FILE} new_temporary_file (generator) as f then
|
if attached new_temporary_file (generator) as f then
|
||||||
f.create_read_write
|
f.create_read_write
|
||||||
f.put_string (a_email.message)
|
f.put_string (a_email.message)
|
||||||
f.close
|
f.close
|
||||||
@@ -196,10 +193,8 @@ feature {NONE} -- Implementation
|
|||||||
result_creatable: Result.is_creatable
|
result_creatable: Result.is_creatable
|
||||||
end
|
end
|
||||||
|
|
||||||
invariant
|
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ note
|
|||||||
|
|
||||||
Note: it is based on EiffelNet {SMTP_PROTOCOL} implementation, and may not be complete.
|
Note: it is based on EiffelNet {SMTP_PROTOCOL} implementation, and may not be complete.
|
||||||
]"
|
]"
|
||||||
author: "$Author: jfiat $"
|
date: "$Date$"
|
||||||
date: "$Date: 2015-06-30 11:07:17 +0200 (mar., 30 juin 2015) $"
|
revision: "$Revision$"
|
||||||
revision: "$Revision: 97586 $"
|
|
||||||
|
|
||||||
class
|
class
|
||||||
NOTIFICATION_SMTP_MAILER
|
NOTIFICATION_SMTP_MAILER
|
||||||
@@ -57,15 +56,12 @@ feature {NONE} -- Initialization
|
|||||||
|
|
||||||
initialize
|
initialize
|
||||||
-- Initialize service.
|
-- Initialize service.
|
||||||
local
|
|
||||||
l_address_factory: INET_ADDRESS_FACTORY
|
|
||||||
do
|
do
|
||||||
if attached username as u then
|
if attached username as u then
|
||||||
create smtp_protocol.make (smtp_host, u)
|
create smtp_protocol.make (smtp_host, u)
|
||||||
else
|
else
|
||||||
-- Get local host name needed in creation of SMTP_PROTOCOL.
|
-- Get local host name needed in creation of SMTP_PROTOCOL.
|
||||||
create l_address_factory
|
create smtp_protocol.make (smtp_host, (create {INET_ADDRESS_FACTORY}).create_localhost.host_name)
|
||||||
create smtp_protocol.make (smtp_host, l_address_factory.create_localhost.host_name)
|
|
||||||
end
|
end
|
||||||
if smtp_port > 0 then
|
if smtp_port > 0 then
|
||||||
smtp_protocol.set_default_port (smtp_port)
|
smtp_protocol.set_default_port (smtp_port)
|
||||||
@@ -98,9 +94,7 @@ feature -- Basic operation
|
|||||||
local
|
local
|
||||||
l_email: EMAIL
|
l_email: EMAIL
|
||||||
h: STRING
|
h: STRING
|
||||||
k,v: STRING
|
|
||||||
i: INTEGER
|
i: INTEGER
|
||||||
hdate: HTTP_DATE
|
|
||||||
do
|
do
|
||||||
create l_email.make_with_entry (a_email.from_address, addresses_to_header_line_value (a_email.to_addresses))
|
create l_email.make_with_entry (a_email.from_address, addresses_to_header_line_value (a_email.to_addresses))
|
||||||
if attached a_email.reply_to_address as l_reply_to then
|
if attached a_email.reply_to_address as l_reply_to then
|
||||||
@@ -117,8 +111,7 @@ feature -- Basic operation
|
|||||||
l_email.add_header_entry ({EMAIL_CONSTANTS}.H_subject, a_email.subject)
|
l_email.add_header_entry ({EMAIL_CONSTANTS}.H_subject, a_email.subject)
|
||||||
|
|
||||||
create h.make_empty
|
create h.make_empty
|
||||||
create hdate.make_from_date_time (a_email.date)
|
;(create {HTTP_DATE}.make_from_date_time (a_email.date)).append_to_rfc1123_string (h)
|
||||||
hdate.append_to_rfc1123_string (h)
|
|
||||||
l_email.add_header_entry ("Date", h)
|
l_email.add_header_entry ("Date", h)
|
||||||
|
|
||||||
if attached a_email.additional_header_lines as lst then
|
if attached a_email.additional_header_lines as lst then
|
||||||
@@ -128,9 +121,7 @@ feature -- Basic operation
|
|||||||
h := ic.item
|
h := ic.item
|
||||||
i := h.index_of (':', 1)
|
i := h.index_of (':', 1)
|
||||||
if i > 0 then
|
if i > 0 then
|
||||||
k := h.head (i - 1)
|
l_email.add_header_entry (h.head (i - 1), h.substring (i + 1, h.count))
|
||||||
v := h.substring (i + 1, h.count)
|
|
||||||
l_email.add_header_entry (k, v)
|
|
||||||
else
|
else
|
||||||
check is_header_line: False end
|
check is_header_line: False end
|
||||||
end
|
end
|
||||||
@@ -181,7 +172,7 @@ feature {NONE} -- Implementation
|
|||||||
end
|
end
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
note
|
note
|
||||||
description: "Store emails in specific folder."
|
description: "Store emails in specific folder."
|
||||||
date: "$Date: 2017-03-08 10:34:57 +0100 (mer., 08 mars 2017) $"
|
date: "$Date$"
|
||||||
revision: "$Revision: 99935 $"
|
revision: "$Revision$"
|
||||||
|
|
||||||
class
|
class
|
||||||
NOTIFICATION_EMAIL_DIRECTORY_STORAGE
|
NOTIFICATION_EMAIL_DIRECTORY_STORAGE
|
||||||
@@ -41,7 +41,7 @@ feature -- Storage
|
|||||||
-- Store `a_email'.
|
-- Store `a_email'.
|
||||||
local
|
local
|
||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
f,w: RAW_FILE
|
w: RAW_FILE
|
||||||
dt: DATE_TIME
|
dt: DATE_TIME
|
||||||
p: PATH
|
p: PATH
|
||||||
fn: STRING
|
fn: STRING
|
||||||
@@ -72,8 +72,7 @@ feature -- Storage
|
|||||||
p := p.extended (fn)
|
p := p.extended (fn)
|
||||||
|
|
||||||
from
|
from
|
||||||
create f.make_with_path (p)
|
w := new_file_opened_for_writing (create {RAW_FILE}.make_with_path (p))
|
||||||
w := new_file_opened_for_writing (f)
|
|
||||||
until
|
until
|
||||||
w /= Void or i > 100
|
w /= Void or i > 100
|
||||||
loop
|
loop
|
||||||
@@ -113,14 +112,15 @@ feature -- Storage
|
|||||||
local
|
local
|
||||||
retried: BOOLEAN
|
retried: BOOLEAN
|
||||||
do
|
do
|
||||||
if not retried then
|
if
|
||||||
if not f.exists then
|
not retried and then
|
||||||
f.open_write
|
not f.exists
|
||||||
if f.is_open_write then
|
then
|
||||||
Result := f
|
f.open_write
|
||||||
elseif not f.is_closed then
|
if f.is_open_write then
|
||||||
f.close
|
Result := f
|
||||||
end
|
elseif not f.is_closed then
|
||||||
|
f.close
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
|
|||||||
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
|
||||||
275
library/security/jwt/src/jwt_encoder.e
Normal file
275
library/security/jwt/src/jwt_encoder.e
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
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
|
||||||
|
ut: JWT_UTILITIES
|
||||||
|
do
|
||||||
|
create ut
|
||||||
|
Result := ut.base64_hmacsha256 (s, a_secret)
|
||||||
|
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
|
||||||
138
library/security/jwt/src/jwt_utilities.e
Normal file
138
library/security/jwt/src/jwt_utilities.e
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
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)
|
||||||
|
-- if Version >= EiffelStudio 17.11 then
|
||||||
|
-- Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
|
||||||
|
-- else
|
||||||
|
Result := base64_bytes_encoded_string (hs256.digest)
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Implementation
|
||||||
|
|
||||||
|
base64_bytes_encoded_string (a_bytes: SPECIAL [NATURAL_8]): STRING_8
|
||||||
|
-- Base64 string from `a_bytes`.
|
||||||
|
--| Note: to be removed when 17.11 is not latest release anymore.
|
||||||
|
local
|
||||||
|
s: STRING
|
||||||
|
i,n: INTEGER
|
||||||
|
do
|
||||||
|
from
|
||||||
|
i := 1
|
||||||
|
n := a_bytes.count
|
||||||
|
create s.make (n)
|
||||||
|
until
|
||||||
|
i > n
|
||||||
|
loop
|
||||||
|
s.append_code (a_bytes[i - 1])
|
||||||
|
i := i + 1
|
||||||
|
end
|
||||||
|
Result := (create {BASE64}).encoded_string (s)
|
||||||
|
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>
|
||||||
@@ -47,7 +47,7 @@ feature -- Change
|
|||||||
across
|
across
|
||||||
ax_to_sreg_map as c
|
ax_to_sreg_map as c
|
||||||
loop
|
loop
|
||||||
ask_info (c.key.to_string_32, is_required)
|
ask_info (c.key, is_required)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -204,18 +204,18 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation
|
|||||||
l_types as t
|
l_types as t
|
||||||
loop
|
loop
|
||||||
s := xml_content (t.item)
|
s := xml_content (t.item)
|
||||||
if s.same_string ("http://openid.net/sreg/1.0") then
|
if s.same_string_general ("http://openid.net/sreg/1.0") then
|
||||||
r_sreg_supported := True
|
r_sreg_supported := True
|
||||||
elseif s.same_string ("http://openid.net/extensions/sreg/1.1") then
|
elseif s.same_string_general ("http://openid.net/extensions/sreg/1.1") then
|
||||||
r_sreg_supported := True
|
r_sreg_supported := True
|
||||||
elseif s.same_string ("http://openid.net/srv/ax/1.0") then
|
elseif s.same_string_general ("http://openid.net/srv/ax/1.0") then
|
||||||
r_ax_supported := True
|
r_ax_supported := True
|
||||||
elseif s.same_string ("http://specs.openid.net/auth/2.0/signon") then
|
elseif s.same_string_general ("http://specs.openid.net/auth/2.0/signon") then
|
||||||
r_version := 2
|
r_version := 2
|
||||||
elseif s.same_string ("http://specs.openid.net/auth/2.0/server") then
|
elseif s.same_string_general ("http://specs.openid.net/auth/2.0/server") then
|
||||||
r_version := 2
|
r_version := 2
|
||||||
r_identifier_select := True
|
r_identifier_select := True
|
||||||
elseif s.same_string ("http://openid.net/signon/1.1") then
|
elseif s.same_string_general ("http://openid.net/signon/1.1") then
|
||||||
r_version := 1
|
r_version := 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -494,7 +494,7 @@ feature {NONE} -- Implementation
|
|||||||
|
|
||||||
feature -- Helper
|
feature -- Helper
|
||||||
|
|
||||||
xml_content (e: XML_ELEMENT): STRING_8
|
xml_content (e: XML_ELEMENT): STRING_32
|
||||||
do
|
do
|
||||||
create Result.make_empty
|
create Result.make_empty
|
||||||
if attached e.contents as lst then
|
if attached e.contents as lst then
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ feature -- Basic operation
|
|||||||
create ret.make_from_string (return_url)
|
create ret.make_from_string (return_url)
|
||||||
create tb.make (5)
|
create tb.make (5)
|
||||||
if attached values as q_lst then
|
if attached values as q_lst then
|
||||||
if attached item_by_name ("openid.claimed_id", q_lst) as q_claimed_id then
|
if attached item_by_name ("openid.claimed_id", q_lst) as q_claimed_id and then q_claimed_id.is_valid_as_string_8 then
|
||||||
l_claimed_id := q_claimed_id.as_string_8
|
l_claimed_id := q_claimed_id.to_string_8
|
||||||
elseif attached item_by_name ("openid.identity", q_lst) as l_id then
|
elseif attached item_by_name ("openid.identity", q_lst) as l_id and then l_id.is_valid_as_string_8 then
|
||||||
l_claimed_id := l_id
|
l_claimed_id := l_id.to_string_8
|
||||||
end
|
end
|
||||||
identity := l_claimed_id
|
identity := l_claimed_id
|
||||||
tb.force (item_by_name ("openid.assoc_handle", q_lst), "openid.assoc_handle")
|
tb.force (item_by_name ("openid.assoc_handle", q_lst), "openid.assoc_handle")
|
||||||
@@ -117,7 +117,7 @@ feature -- Basic operation
|
|||||||
|
|
||||||
if
|
if
|
||||||
attached item_by_name ("openid.return_to", q_lst) as q_return_to and then
|
attached item_by_name ("openid.return_to", q_lst) as q_return_to and then
|
||||||
not return_url.same_string (q_return_to)
|
not return_url.same_string_general (q_return_to)
|
||||||
then
|
then
|
||||||
-- The return_to url must match the url of current request.
|
-- The return_to url must match the url of current request.
|
||||||
-- I'm assuing that noone will set the returnUrl to something that doesn't make sense.
|
-- I'm assuing that noone will set the returnUrl to something that doesn't make sense.
|
||||||
@@ -217,7 +217,7 @@ feature -- Basic operation
|
|||||||
if s.same_string ({STRING_32} "ns.ax") and v.same_string ({STRING_32} "http://openid.net/srv/ax/1.0") then
|
if s.same_string ({STRING_32} "ns.ax") and v.same_string ({STRING_32} "http://openid.net/srv/ax/1.0") then
|
||||||
l_alias := "ax."
|
l_alias := "ax."
|
||||||
else
|
else
|
||||||
if v.same_string ("http://openid.net/srv/ax/1.0") then
|
if v.same_string_general ("http://openid.net/srv/ax/1.0") then
|
||||||
l_alias := s.substring (("ns.").count + 1, s.count).to_string_8 + "."
|
l_alias := s.substring (("ns.").count + 1, s.count).to_string_8 + "."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -235,8 +235,8 @@ feature -- Basic operation
|
|||||||
loop
|
loop
|
||||||
s := c.item
|
s := c.item
|
||||||
if
|
if
|
||||||
s.starts_with (k_value)
|
s.starts_with_general (k_value)
|
||||||
or s.starts_with (k_type)
|
or s.starts_with_general (k_type)
|
||||||
then
|
then
|
||||||
ax_keys.force ("openid." + s)
|
ax_keys.force ("openid." + s)
|
||||||
end
|
end
|
||||||
@@ -251,17 +251,17 @@ feature -- Basic operation
|
|||||||
loop
|
loop
|
||||||
s := c.item
|
s := c.item
|
||||||
if attached item_by_name (s, lst) as v then
|
if attached item_by_name (s, lst) as v then
|
||||||
if s.starts_with (k_value) then
|
if s.starts_with_general (k_value) then
|
||||||
k := s.substring (k_value.count + 1, s.count)
|
k := s.substring (k_value.count + 1, s.count)
|
||||||
i := k.index_of ('.', 1)
|
i := k.index_of ('.', 1)
|
||||||
if i > 1 then
|
if i > 1 then
|
||||||
k.keep_head (i - 1)
|
k.keep_head (i - 1)
|
||||||
end
|
end
|
||||||
if attached item_by_name (k_type + k, lst) as l_type then
|
if attached item_by_name (k_type + k, lst) as l_type then
|
||||||
if l_type.starts_with ("http://axschema.org/") then
|
if l_type.starts_with_general ("http://axschema.org/") then
|
||||||
check ("http://axschema.org/").count = 20 end
|
check ("http://axschema.org/").count = 20 end
|
||||||
attributes.force (v, l_type.substring (21, l_type.count))
|
attributes.force (v, l_type.substring (21, l_type.count))
|
||||||
elseif l_type.starts_with ("http://schema.openid.net/") then
|
elseif l_type.starts_with_general ("http://schema.openid.net/") then
|
||||||
check ("http://schema.openid.net/").count = 25 end
|
check ("http://schema.openid.net/").count = 25 end
|
||||||
attributes.force (v, l_type.substring (26, l_type.count))
|
attributes.force (v, l_type.substring (26, l_type.count))
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ feature -- Status report
|
|||||||
-- String that should be displayed in debugger to represent `Current'.
|
-- String that should be displayed in debugger to represent `Current'.
|
||||||
do
|
do
|
||||||
create Result.make_empty
|
create Result.make_empty
|
||||||
Result.append (type)
|
Result.append_string_general (type)
|
||||||
Result.append (" ")
|
Result.append (" ")
|
||||||
if attached login as l_login then
|
if attached login as l_login then
|
||||||
Result.append ("login=[")
|
Result.append ("login=[")
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ note
|
|||||||
description: "[
|
description: "[
|
||||||
Standalone Web Server connector.
|
Standalone Web Server connector.
|
||||||
]"
|
]"
|
||||||
date: "$Date: 2016-08-06 13:34:52 +0200 (sam., 06 août 2016) $"
|
date: "$Date$"
|
||||||
revision: "$Revision: 99106 $"
|
revision: "$Revision$"
|
||||||
|
|
||||||
class
|
class
|
||||||
WGI_STANDALONE_CONNECTOR [G -> WGI_EXECUTION create make end]
|
WGI_STANDALONE_CONNECTOR [G -> WGI_EXECUTION create make end]
|
||||||
@@ -110,7 +110,7 @@ feature -- Callbacks
|
|||||||
|
|
||||||
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
on_launched_actions: ACTION_SEQUENCE [TUPLE [WGI_STANDALONE_CONNECTOR [WGI_EXECUTION]]]
|
||||||
-- Actions triggered when launched.
|
-- Actions triggered when launched.
|
||||||
-- WARNING: only supported for now with SCOOP concurrency mode. [2016-oct-07]
|
-- WARNING: only supported for now with SCOOP concurrency mode. [2016-10-07]
|
||||||
|
|
||||||
feature -- Event
|
feature -- Event
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ feature -- Server
|
|||||||
-- Shutdown web server listening.
|
-- Shutdown web server listening.
|
||||||
do
|
do
|
||||||
if launched then
|
if launched then
|
||||||
-- FIXME jfiat [2015/03/27] : prevent multiple calls (otherwise it hangs)
|
-- FIXME: prevent multiple calls (otherwise it hangs) [2015-03-27]
|
||||||
separate_shutdown_server_on_controller (controller)
|
separate_shutdown_server_on_controller (controller)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -213,7 +213,7 @@ feature -- Events
|
|||||||
require
|
require
|
||||||
obs.started -- SCOOP wait condition.
|
obs.started -- SCOOP wait condition.
|
||||||
do
|
do
|
||||||
-- FIXME: this works only with SCOOP concurrency mode. [2016-oct-07]
|
-- FIXME: this works only with SCOOP concurrency mode. [2016-10-07]
|
||||||
if obs.port > 0 then
|
if obs.port > 0 then
|
||||||
on_launched (obs.port)
|
on_launched (obs.port)
|
||||||
end
|
end
|
||||||
@@ -282,9 +282,8 @@ feature {NONE} -- Implementation: element change
|
|||||||
cfg.set_is_secure (b)
|
cfg.set_is_secure (b)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
note
|
note
|
||||||
copyright: "2011-2016, 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)"
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||||
source: "[
|
source: "[
|
||||||
Eiffel Software
|
Eiffel Software
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user