Compare commits

...

36 Commits
v1.0.4 ... v1

Author SHA1 Message Date
Jocelyn Fiat
67bdcfb6ef Made the parameters not hidden implementation classes. 2017-06-21 08:58:15 +02:00
Jocelyn Fiat
ca4043b102 Merge branch 'master' into v1 2017-06-20 18:17:30 +02:00
Jocelyn Fiat
310e96e185 Updated EOL for 2 ecf files. 2017-06-20 18:16:43 +02:00
Jocelyn Fiat
7feb45b549 Updated error library (cosmetic, and loop iteration). 2017-06-20 18:08:50 +02:00
Jocelyn Fiat
5bbd031275 Update EOL on ecf files. 2017-06-20 17:49:28 +02:00
Jocelyn Fiat
90e60fad26 Updated changelog. 2017-06-20 17:47:17 +02:00
Jocelyn Fiat
98c20ee7c1 Fixed specific ecf files for http_client library. 2017-06-20 17:37:24 +02:00
Jocelyn Fiat
64027f56bd Merge branch 'master' into v1 2017-06-20 09:53:29 +02:00
Jocelyn Fiat
0b99e84728 Added installation of JWT into Eiffel installation. 2017-06-20 09:53:13 +02:00
Jocelyn Fiat
a6806c676a Updated ecf files to version 1-16-0 .
Minor cosmetic changes.
2017-06-20 09:48:10 +02:00
Jocelyn Fiat
9e5e8bb1bf Added simple way to set the issued_at claim value to current UTC date time. 2017-06-14 16:27:24 +02:00
Jocelyn Fiat
10a83c6ad8 Added possibility to create JWS object with specific algo hs256 or none easily. 2017-06-14 16:26:31 +02:00
Jocelyn Fiat
1ec3b8e7a4 Added support for multiple file in form data.
Made clear what is the meaning of upload_filename, upload_data and form_data.
2017-06-14 16:19:43 +02:00
Jocelyn Fiat
0783049fb4 updated package.iron 2017-06-08 11:25:26 +02:00
Jocelyn Fiat
7e54825b84 Updated JWT library, add supports for claim exp, iat, nbf, iss, aud . 2017-06-07 23:24:46 +02:00
Jocelyn Fiat
40cbe7dfc9 Merge branch 'master' into dev_jwt 2017-06-06 10:58:38 +02:00
Jocelyn Fiat
d4b9301a57 Reverted to previous TYPE.name to be compilable with version prior to 17.05.
(TYPE.name_32 exists since 17.05).
2017-06-02 17:40:53 +02:00
Jocelyn Fiat
06cda97535 New JSON Web Token (JWT) library. 2017-05-24 11:56:15 +02:00
Jocelyn Fiat
c83b9d4231 Updated CHANGELOG.md 2017-05-17 17:38:25 +02:00
Jocelyn Fiat
69b5ce637e Improved query and form data encoding (based on a very early version of the general URI percent-encoding rules).
- now correct encoding of space by '%20' in path segment, and '+' in query parameters.
Unify and fixed query parameters handling for libcurl and net implementation.
Fixed file uploading (various issue in libcurl, and net implementation).
Fixed form multipart encoding by using correctly the boundary.
Updated autotest cases.
Code cleaning.
2017-05-17 12:16:35 +02:00
Jocelyn Fiat
485a3812d9 Integrated recent changes on SOCKET.socket_buffer splitted as read_socket_buffer and put_socket_buffer. 2017-05-15 12:20:52 +02:00
Jocelyn Fiat
88dec34a1e Made "common" targets as abstract. 2017-05-12 14:39:48 +02:00
Jocelyn Fiat
a928f27b1a Updated or added timestamp to obsolete and fixme messages.
Fixed ecf file exclusion for .svn and .git .
Cosmetic changed.
2017-05-12 14:31:35 +02:00
Jocelyn Fiat
7ba678d726 Added empty .travis.yml for now (see https://travis-ci.com/) 2017-04-14 13:32:37 +02:00
Jocelyn Fiat
2371ad4bd1 Commented unwanted change. 2017-04-14 12:52:08 +02:00
Jocelyn Fiat
146b78e5b0 Updated code regarding to string 32 vs string 8. 2017-04-14 12:49:34 +02:00
Jocelyn Fiat
273a55d93c Merge branch 'pr177' 2017-04-14 11:48:29 +02:00
Jocelyn Fiat
2e920f063a Merge branch 'master' of https://github.com/EiffelWebFramework/EWF 2017-04-14 11:48:14 +02:00
Jocelyn Fiat
3b8261ff08 Updated code regarding to string 32 vs string 8. 2017-04-14 11:45:38 +02:00
Jocelyn Fiat
a530bbebb4 Merge remote-tracking branch 'javier/ewf_get_rid_obsolete_messages' into pr177 2017-04-13 22:58:12 +02:00
Jocelyn Fiat
d41dbb9f47 Syntax in ecf files. 2017-04-13 22:56:40 +02:00
Jocelyn Fiat
a57e041003 Added precondition to WSF_FILTER.set_next (..) to avoid cycle. 2017-04-13 22:55:39 +02:00
jvelilla
5d9752f257 Updated code, get rid of obsolete feature calls in libraries and examples 2017-04-13 16:48:17 -03:00
Jocelyn Fiat
d4d988e532 Update README.md 2017-03-21 15:20:11 +01:00
Jocelyn Fiat
ce11a3c0fc Fixed markdown links related to network libraries. 2017-03-21 15:17:43 +01:00
Jocelyn Fiat
4eb743fa58 Updated CHANGELOG.md
Added full commit log in doc/CommitLog
2017-03-15 18:58:41 +01:00
152 changed files with 9821 additions and 923 deletions

0
.travis.yml Normal file
View File

6091
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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
## Requirements
* Compiling from EiffelStudio 13.11 to 15.05 and more recent version of the compiler.
* Currently being developped using EiffelStudio 15.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')
* 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) )
* Compiling from EiffelStudio 16.05 to 17.05 and more recent version of the compiler.
* Currently being developped using EiffelStudio 17.01 (on Windows, Linux)
* 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](https://www.eiffel.org/doc/eiffel/Void-safe%20programming%20in%20Eiffel)
## 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)
### protocol
* __http__: HTTP related classes, constants for status code, content types, ... [read more](library/protocol/http)
* __uri_template__: URI Template library (parsing and expander) [read more](library/protocol/uri_template)
* __CONNEG__: Content negotiation library (Content-type Negociation) [read more](library/protocol/content_negotiation)
* __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/network/protocol/uri_template)
* __CONNEG__: Content negotiation library (Content-type Negociation) [read more](library/network/protocol/content_negotiation)
### 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
* __encoder__: Various simpler encoders: base64, url-encoder, xml entities, html entities [read more](library/text/encoder)
@@ -78,10 +78,6 @@ Using git
### Others
* error: very simple/basic library to handle error
## External libraries under 'contrib'
* [Eiffel Web Nino](contrib/library/server/nino)
* ..
## Draft folder = call for contribution ##
## 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)
For more information please have a look at the related wiki:
* https://github.com/EiffelWebFramework/EWF/wiki
For more information please have a look at the related [workbook documentation](docs/workbook)

View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
<target name="web_server">
<root class="APPLICATION" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option debug="true" warning="true" void_safety="all">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="nino" location="..\..\nino-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
<target name="web_server">
<root class="APPLICATION" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option debug="true" warning="true" void_safety="all">
<debug name="nino" enabled="true"/>
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="nino" location="..\..\nino-safe.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
<target name="web_server">
<root class="APPLICATION" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" void_safety="none">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="nino" location="..\..\nino.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="web_server" uuid="B1D3254D-A58E-4259-9796-8A2843A511A9">
<target name="web_server">
<root class="APPLICATION" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" void_safety="none">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="thread"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="nino" location="..\..\nino.ecf"/>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -113,14 +113,13 @@ feature -- Implementation
across
l_values as c
loop
s.replace_substring_all ({STRING_32} "${" + c.key.as_string_32 + "}", c.item)
s.replace_substring_all ({STRING_32} "${" + c.key.to_string_32 + "}", c.item.to_string_32)
end
end
end
note
copyright: "2011-2013, 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)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
<target name="common">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>

View File

@@ -205,7 +205,7 @@ feature {NONE} -- Implementation
loop
s.append (" - ")
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
else
s.append_character (' ')

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="filter" uuid="52FF4B77-0614-4D8B-9B96-C07EC852793E">
<target name="common">
<target name="common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>

View File

@@ -84,7 +84,7 @@ feature {NONE} -- Initialization
create l_methods
l_methods.enable_options
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
initialize_json
@@ -99,7 +99,7 @@ feature {NONE} -- Implementation
-- Port number
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)"
source: "[
Eiffel Software

View File

@@ -42,7 +42,7 @@ feature -- Basic operations
id : STRING
do
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 l_user ~ req.execution_variable ("user") then
compute_response_get (req, res, l_user)
@@ -92,6 +92,6 @@ feature {NONE} -- Implementation
end
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)"
end

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
<target name="restbucks_common">
<target name="restbucks_common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>

View File

@@ -85,7 +85,6 @@ feature {NONE} -- Initialization
create_order (sess: HTTP_CLIENT_SESSION) : HTTP_CLIENT_RESPONSE
local
s: READABLE_STRING_8
j: JSON_PARSER
context : HTTP_CLIENT_REQUEST_CONTEXT
do
s := "[

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="restbucks" uuid="2773FEAA-448F-410E-BEDE-9298C4749066">
<target name="restbucks_common">
<target name="restbucks_common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>

View File

@@ -87,7 +87,7 @@ feature -- Access: order status
end
Order_states : ARRAY [STRING]
-- List of valid status states
-- List of valid status states.
once
Result := <<
status_unset,

View File

@@ -27,7 +27,7 @@ feature -- Access
end
Milk_types: ARRAY [STRING]
-- List of valid Milk types
-- List of valid Milk types.
once
Result := <<"skim", "semi", "whole">>
end

View File

@@ -4,7 +4,8 @@ note
date: "$Date$"
revision: "$Revision$"
class ORDER_HANDLER
class
ORDER_HANDLER
inherit
@@ -537,7 +538,6 @@ feature {NONE} -- Conversion
order_to_json (obj: ORDER): JSON_OBJECT
local
j_order: JSON_OBJECT
j_item: JSON_OBJECT
ja: JSON_ARRAY
do

View File

@@ -334,7 +334,6 @@ feature {NONE} -- Conversion
order_to_json (obj: ORDER): JSON_OBJECT
local
j_order: JSON_OBJECT
j_item: JSON_OBJECT
ja: JSON_ARRAY
do

View File

@@ -12,8 +12,6 @@ inherit
feature {NONE} -- Initialization
make (a_location: PATH)
local
d: DIRECTORY
do
location := 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])
local
f: RAW_FILE
l_id: detachable READABLE_STRING_GENERAL
p: PATH
do

View File

@@ -19,8 +19,6 @@ create
feature {NONE} -- Initialization
make
local
b: SED_MEMORY_READER_WRITER
do
create collections.make (0)
end
@@ -86,8 +84,6 @@ feature {NONE} -- Implementation
next_identifier (a_entry_type: TYPE [detachable ANY]): STRING_8
local
i: INTEGER
f: RAW_FILE
s: STRING
tb: detachable STRING_TABLE [detachable ANY]
do
tb := collections.item (a_entry_type)

View File

@@ -54,7 +54,7 @@ feature -- Execution
if attached {WSF_STRING} req.item ("user") as u then
--| 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.append ("Display a <a href=%"/users/" + u.url_encoded_value + "/message/%">message</a></p>")

View File

@@ -86,7 +86,7 @@ feature -- Access
html_decoded_string (v: READABLE_STRING_32): READABLE_STRING_32
do
if v.is_valid_as_string_8 then
Result := (create {HTML_ENCODER}).decoded_string (v)
Result := (create {HTML_ENCODER}).general_decoded_string (v)
else
Result := v
end

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="upload_image" uuid="F2400BE8-D8EB-48EB-B4E4-5D4377062A7F">
<target name="upload_image_common">
<target name="upload_image_common" abstract="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/\.git$</exclude>

View File

@@ -33,6 +33,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<condition>

View File

@@ -33,6 +33,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<condition>

View File

@@ -17,6 +17,8 @@
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
<cluster name="default_libcurl" location="$|default\libcurl\"/>
</cluster>

View File

@@ -17,6 +17,8 @@
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
<cluster name="default_libcurl" location="$|default\libcurl\"/>
</cluster>

View File

@@ -26,6 +26,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
</cluster>

View File

@@ -26,6 +26,8 @@
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
<cluster name="src" location=".\src\">
<cluster name="implementation" location="$|implementation" recursive="true" hidden="true"/>
<cluster name="parameters" location="$|parameters" recursive="true"/>
<cluster name="spec_net" location="$|spec\net\">
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
</cluster>

View File

@@ -196,73 +196,8 @@ feature -- Settings
Result := session.is_insecure
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
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)"
source: "[
Eiffel Software

View File

@@ -13,7 +13,7 @@ note
Note that any value set in this context class overrides conflicting value eventually
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
- or upload_data
- 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
--| note: the value from Current context override the one from the session in case of conflict
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
query_parameters: HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
-- Query parameters to be appended to the url
--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url
form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
form_parameters: HTTP_CLIENT_REQUEST_FORM_PARAMETERS
-- Form parameters
upload_data: detachable READABLE_STRING_8
@@ -142,16 +142,28 @@ feature -- Element change
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'.
do
query_parameters.force (v, k)
query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
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'.
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
set_credentials_required (b: BOOLEAN)
@@ -164,7 +176,8 @@ feature -- Element change
-- Set `upload_data' to `a_data'
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_form_data: (a_data /= Void and then not a_data.is_empty) implies not has_form_data
do
if a_data = Void or else a_data.is_empty then
upload_data := Void
@@ -180,6 +193,7 @@ feature -- Element change
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
has_no_form_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_form_data
do
if a_fn = Void or else a_fn.is_empty then
upload_filename := Void
@@ -236,18 +250,62 @@ feature -- Status setting
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
query_parameters_to_url_encoded_string: STRING_8
-- `query_parameters' as url-encoded string.
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
form_parameters_to_url_encoded_string: STRING_8
-- `form_parameters' as url-encoded string.
obsolete "Use form_parameters_to_x_www_form_url_encoded_string [2017-05-31]"
do
Result := parameters_to_url_encoded_string (form_parameters)
Result := form_parameters_to_x_www_form_url_encoded_string
end
feature {NONE} -- Implementation
@@ -271,6 +329,49 @@ feature {NONE} -- Implementation
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
-- Shared URL encoder.
once
@@ -278,7 +379,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -60,28 +60,10 @@ feature -- Access
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
-- Url computed from Current and `ctx' data.
local
s: STRING_8
url_encoder: URL_ENCODER
do
Result := base_url + a_path
if ctx /= Void then
create s.make_empty
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
ctx.append_query_parameters_to_url (Result)
end
end
@@ -420,7 +402,7 @@ feature -- Element change
end
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)"
source: "[
Eiffel Software

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -58,7 +58,6 @@ feature -- Execution
ctx: like context
p_slist: POINTER
retried: BOOLEAN
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
l_upload_data: detachable READABLE_STRING_8
l_upload_filename: detachable READABLE_STRING_GENERAL
l_headers: like headers
@@ -82,9 +81,6 @@ feature -- Execution
--| 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
io.error.put_string ("> Sending:%N")
@@ -154,62 +150,19 @@ feature -- Execution
--| Credentials not provided ...
end
end
if ctx.has_upload_data then
l_upload_data := ctx.upload_data
end
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
check non_empty_form_data: not l_form_data.is_empty end
if l_upload_data = Void and l_upload_filename = Void then
-- Send as form-urlencoded
if
attached l_headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_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
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
@@ -219,6 +172,7 @@ feature -- Execution
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_readable then
@@ -233,12 +187,59 @@ feature -- Execution
l_upload_file.open_read
curl_easy.set_curl_function (l_custom_function)
end
elseif
ctx.has_form_data and
attached ctx.form_parameters as l_form_data
then
check non_empty_form_data: not l_form_data.is_empty end
-- Send as form-urlencoded
if
attached l_headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") or l_form_data.has_file_parameter then
l_use_curl_form := True
else
-- Not supported, use libcurl form.
l_use_curl_form := True
end
else
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
if l_use_curl_form then
create l_form.make
create l_last.make
across
l_form_data as ic
loop
if attached {HTTP_CLIENT_REQUEST_STRING_PARAMETER} ic.item as strparam then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, strparam.name,
{CURL_FORM_CONSTANTS}.curlform_copycontents, strparam.value,
{CURL_FORM_CONSTANTS}.curlform_end
)
elseif attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
{CURL_FORM_CONSTANTS}.curlform_file, fileparam.location.name,
{CURL_FORM_CONSTANTS}.curlform_end
)
else
check supported_parameter_type: False end
end
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
else
check no_upload_data: l_upload_data = Void and l_upload_filename = Void end
-- No form, or upload data to send!
check no_data: not (ctx.has_upload_data or ctx.has_upload_filename or ctx.has_form_data) end
end
end -- ctx /= Void
--| Header
--| Header
across
l_headers as curs
loop

View File

@@ -41,7 +41,7 @@ feature -- Custom
local
req: HTTP_CLIENT_REQUEST
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
end
@@ -154,7 +154,7 @@ feature {NONE} -- Implementation
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
if f /= Void then
@@ -176,7 +176,7 @@ feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
;note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -9,7 +9,7 @@ class
LIBCURL_UPLOAD_FILE_READ_FUNCTION
obsolete
"Use LIBCURL_CUSTOM_FUNCTION [2013-apr-04]"
"Use LIBCURL_CUSTOM_FUNCTION [2017-05-31]"
inherit
LIBCURL_DEFAULT_FUNCTION

View File

@@ -91,8 +91,8 @@ feature -- Access
l_authorization: HTTP_AUTHORIZATION
l_platform: STRING
l_upload_data: detachable READABLE_STRING_8
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
ctx: like context
l_ct: detachable READABLE_STRING_8
l_upload_file: detachable RAW_FILE
l_upload_filename: detachable READABLE_STRING_GENERAL
l_form_string: STRING
@@ -149,14 +149,14 @@ feature -- Access
then
create l_authorization.make_basic_auth (u_name, u_pass)
if attached l_authorization.http_authorization as auth then
headers.extend (auth, "Authorization")
headers.force (auth, "Authorization")
end
check headers.has_key ("Authorization") end
end
end
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 (l_query)
end
@@ -176,7 +176,7 @@ feature -- Access
else
l_platform := "Unknown"
end
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
headers.force ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
end
-- handle sending data
@@ -191,67 +191,52 @@ feature -- Access
l_upload_data := ctx.upload_data
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
if l_upload_data = Void and l_upload_filename = Void then
if
attached headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_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
if l_upload_data /= Void then
check ctx.has_upload_data end
check no_form_data: not ctx.has_form_data end
if not headers.has ("Content-Type") then
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
headers.force ("application/x-www-form-urlencoded", "Content-Type")
end
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_data.count.out, "Content-Length")
headers.force (l_upload_data.count.out, "Content-Length")
end
elseif l_upload_filename /= Void then
check ctx.has_upload_filename end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.readable then
if l_upload_file.exists and then l_upload_file.is_access_readable then
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_file.count.out, "Content-Length")
headers.force (l_upload_file.count.out, "Content-Length")
end
end
check l_upload_file /= Void end
elseif
ctx.has_form_data and
attached ctx.form_parameters as l_form_data
then
l_ct := headers.item ("Content-Type")
if l_ct /= Void and then l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif
(l_ct /= Void and then l_ct.starts_with ("multipart/form-data"))
or l_form_data.has_file_parameter
then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.force ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_boundary)
else
-- not supported !
-- Send as form-urlencoded
headers.force ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
headers.force (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
end
@@ -482,66 +467,41 @@ feature {NONE} -- Helpers
Result := a_status >= 300 and a_status < 400
end
form_date_and_uploaded_files_to_mime_string (a_form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_upload_filename: detachable READABLE_STRING_GENERAL; a_mime_boundary: READABLE_STRING_8): STRING
form_date_and_uploaded_files_to_mime_string (a_form_parameters: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]; a_mime_boundary: READABLE_STRING_8): STRING
-- Form data and uploaded files converted to mime string.
-- TODO: design a proper MIME... component.
local
l_path: PATH
l_mime_type: READABLE_STRING_8
l_upload_file: detachable RAW_FILE
l_mime_type_mapping: HTTP_FILE_EXTENSION_MIME_MAPPING
do
create Result.make (100)
across
a_form_parameters as ic
loop
Result.append ("--")
Result.append (a_mime_boundary)
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (ic.key))
Result.append (string_to_mime_encoded_string (ic.item.name))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
Result.append (string_to_mime_encoded_string (ic.item))
Result.append (http_end_of_header_line)
end
if a_upload_filename /= Void then
-- get file extension, otherwise set default
create l_mime_type_mapping.make_default
create l_path.make_from_string (a_upload_filename)
if
attached l_path.extension as ext and then
attached l_mime_type_mapping.mime_type (ext) as l_mt
attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam and then
attached fileparam.file_name as fn
then
l_mime_type := l_mt
else
l_mime_type := "application/octet-stream"
Result.append ("; filename=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (fn))
Result.append_character ('%"')
end
Result.append (a_mime_boundary)
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append ("; filename=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (l_mime_type)
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
create l_upload_file.make_with_path (l_path)
if l_upload_file.exists and then l_upload_file.is_access_readable then
append_file_content_to (l_upload_file, l_upload_file.count, Result)
-- Reset l_upload_file to Void, since the related content is already processed.
l_upload_file := Void
if attached ic.item.content_type as ct then
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (ct)
end
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
ic.item.append_as_mime_encoded_to (Result)
Result.append (http_end_of_header_line)
end
Result.append ("--")
Result.append (a_mime_boundary)
Result.append ("--") --| end
end
@@ -891,7 +851,7 @@ feature {NONE} -- Helpers
end
end
new_mime_boundary (a_data: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING
new_mime_boundary (a_data: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]): STRING
-- New MIME boundary.
local
s: STRING
@@ -902,12 +862,12 @@ feature {NONE} -- Helpers
across
a_data as ic
loop
i := i + ic.item.count + ic.key.count
i := i + ic.item.count + ic.item.name.count
end
create ran.set_seed (i) -- FIXME: use a real random seed.
ran.start
ran.forth
n := (20 * ran.real_item).truncated_to_integer
n := (10 * ran.real_item).truncated_to_integer
create Result.make_filled ('-', 3 + n)
s := "_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
from

View File

@@ -67,7 +67,7 @@ feature -- Custom
local
req: HTTP_CLIENT_REQUEST
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
end
@@ -167,12 +167,12 @@ feature {NONE} -- Implementation
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
end
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)"
source: "[
Eiffel Software

View File

@@ -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
do
create Result.make (base_url + a_path)
create Result.make (url (a_path, ctx))
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
@@ -98,7 +98,7 @@ feature -- Status report
-- Is interface usable?
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
<target name="test_http_client">
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="testing_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
<target name="testing_http_client">
<root class="TEST" feature="make"/>
<file_rule>
<exclude>/.git$</exclude>
@@ -10,7 +10,8 @@
<option warning="true" void_safety="all">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<variable name="netssl_http_client_enabled" value="false"/>
<variable name="ssl_enabled" value="true"/>
<variable name="netssl_http_client_enabled" value="true"/>
<variable name="net_http_client_disabled" value="false"/>
<variable name="libcurl_http_client_disabled" value="false"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>

View File

@@ -34,6 +34,11 @@ feature -- Tests
test_post_with_form_data
end
libcurl_test_post_with_uncommon_form_data
do
test_post_with_uncommon_form_data
end
libcurl_test_post_with_file
do
test_post_with_file
@@ -54,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
libcurl_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
libcurl_test_get_with_redirection
do
test_get_with_redirection

View File

@@ -34,6 +34,11 @@ feature -- Tests
test_post_with_form_data
end
net_test_post_with_uncommon_form_data
do
test_post_with_uncommon_form_data
end
net_test_post_with_file
do
test_post_with_file
@@ -54,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
net_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
net_test_get_with_redirection
do
test_get_with_redirection
@@ -69,4 +79,9 @@ feature -- Tests
test_post_with_file_using_chunked_transfer_encoding
end
net_test_get_with_query_parameters
do
test_get_with_query_parameters
end
end

View File

@@ -1,4 +1,4 @@
note
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
@@ -21,7 +21,9 @@ feature -- Initialization
on_prepare
do
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
feature -- Factory
@@ -30,7 +32,13 @@ feature -- Factory
deferred
end
feature -- Requestbin
feature -- Requestbin
is_using_requestbin: BOOLEAN = False
is_using_mockbincom: BOOLEAN
do
Result := not is_using_requestbin
end
global_requestbin_path: detachable READABLE_STRING_8
@@ -39,7 +47,7 @@ feature -- Requestbin
i,j: INTEGER
do
if
attached new_session ("http://requestb.in") as sess and then
attached new_session ("https://requestb.in") as sess and then
attached sess.post ("/api/v1/bins", Void, Void) as resp
then
if resp.error_occurred then
@@ -49,6 +57,9 @@ feature -- Requestbin
i := l_content.substring_index ("%"name%":", 1)
if i > 0 then
j := l_content.index_of (',', i + 1)
if j = 0 then
j := l_content.index_of ('}', i + 1)
end
if j > 0 then
Result := l_content.substring (i + 7, j - 1)
Result.adjust
@@ -61,12 +72,30 @@ feature -- Requestbin
if not Result.starts_with ("/") then
Result.prepend_character ('/')
end
print ("new_requestbin_path => " + sess.base_url + Result + "?inspect%N")
end
end
end
end
end
new_web_session: like new_session
do
if is_using_mockbincom then
Result := new_session ("http://mockbin.com/request")
end
if Result = Void and is_using_requestbin then
if attached global_requestbin_path as l_path then
Result := new_session ("https://requestb.in" + l_path)
else
assert ("Has requestbin path", False)
end
end
if Result = Void then
Result := new_session ("http://mockbin.com/request") -- Default
end
end
feature -- Factory
test_post_url_encoded
@@ -74,238 +103,200 @@ feature -- Factory
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- URL ENCODED POST REQUEST
-- check requestbin to ensure the "Hello World" has been received in the raw body
-- also check that User-Agent was sent
create h.make_empty
sess := new_session ("http://requestb.in")
if
attached sess.post (requestbin_path, Void, "Hello World") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- URL ENCODED POST REQUEST
-- check requestbin to ensure the "Hello World" has been received in the raw body
-- also check that User-Agent was sent
create h.make_empty
sess := new_web_session
if
attached sess.post ("", Void, "Hello World") as res
then
check_response (res)
end
end
test_post_with_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_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
-- check requestbin to ensure the form parameters are correctly received
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.form_parameters.extend ("First Value", "First Key")
l_ctx.form_parameters.extend ("Second Value", "Second Key")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
test_post_with_uncommon_form_data
local
sess: HTTP_CLIENT_SESSION
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- 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 ("title", "Eiffel World!") -- space and !
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
l_ctx.add_form_parameter ("test", "!$&'()*") --
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
l_ctx.add_form_parameter ("?", "?") -- question mark
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
l_ctx.add_form_parameter ("&", "&") -- ampersand
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
if
attached sess.post ("", l_ctx, "") as res
then
check_response (res)
end
end
test_post_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_put_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_put_with_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_data ("This is a test for http client.%N")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_data ("name=This is a test for http client.%N")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_post_with_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
-- l_ctx.add_file_form_parameter ("image", "test.txt", "image/jpeg")
l_ctx.add_file_form_parameter ("text", "test.txt", "plain/text")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("logo.jpg")
l_ctx.form_parameters.extend ("First Value", "First Key")
l_ctx.form_parameters.extend ("Second Value", "Second Key")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
test_post_with_multiple_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.add_header ("Content-Type", "multipart/form-data")
l_ctx.add_file_form_parameter ("first_file", "test.txt", "plain/text")
l_ctx.add_file_form_parameter ("image", "logo.jpg", "image/jpeg")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
l_ctx.add_file_form_parameter ("last_file", "test.txt", Void)
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_post_with_file_using_chunked_transfer_encoding
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_get_with_redirection
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
create h.make_empty
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
if attached sess.get ("/", Void) as res then
check_response (res)
assert("was redirected", res.redirections_count > 0)
end
end
@@ -320,6 +311,7 @@ feature -- Factory
sess.set_credentials ("test", "test")
create ctx.make_with_credentials_required
if attached sess.get ("/password-ok.php", ctx) as res then
check_response (res)
if attached {READABLE_STRING_8} res.body as l_body then
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
else
@@ -328,6 +320,61 @@ feature -- Factory
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

View File

@@ -58,7 +58,7 @@
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<version type="compiler" max="17.02"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
@@ -114,6 +114,11 @@
</condition>
</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 name="http_network_ssl" extends="http_network">
<variable name="ssl_enabled" value="true"/>

View File

@@ -58,7 +58,7 @@
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<version type="compiler" max="17.02"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
@@ -114,6 +114,11 @@
</condition>
</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 name="http_network_ssl" extends="http_network">
<variable name="ssl_enabled" value="true"/>

View File

@@ -36,13 +36,13 @@ feature -- Input
-- Make result available in `last_character'.
-- No exception raised!
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
-- Socket error already set.
elseif bytes_read /= character_8_bytes then
socket_error := "Peer closed connection"
else
last_character := socket_buffer.read_character (0)
last_character := read_socket_buffer.read_character (0)
socket_error := Void
end
end
@@ -208,8 +208,8 @@ feature -- Output
put_character_noexception (c: CHARACTER)
-- Write character `c' to socket.
do
socket_buffer.put_character (c, 0)
put_managed_pointer_noexception (socket_buffer, 0, character_8_bytes)
put_socket_buffer.put_character (c, 0)
put_managed_pointer_noexception (put_socket_buffer, 0, character_8_bytes)
end
put_string_8_noexception (s: READABLE_STRING_8)
@@ -244,7 +244,7 @@ feature -- Status report
end
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)"
source: "[
Eiffel Software

View File

@@ -2,12 +2,20 @@ note
description: "[
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
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

View File

@@ -51,6 +51,20 @@ feature -- Access
deferred
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.
set_recv_timeout (a_timeout_seconds: INTEGER)

View File

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

View File

@@ -114,14 +114,14 @@ feature -- Obsolete query
include_max_age: BOOLEAN
obsolete
"Use `max_age > 0' [April-2016]"
"Use `max_age > 0' [2017-05-31]"
do
Result := max_age > 0
end
include_expires: BOOLEAN
obsolete
"Use `expires /= Void' [April-2016]"
"Use `expires /= Void' [2017-05-31]"
do
Result := expiration /= Void
end
@@ -133,7 +133,7 @@ feature -- Obsolete element change
-- Set `expires to void'
-- Set-Cookie will include only Max-Age attribute and not Expires.
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
max_age := 1
expiration := Void
@@ -147,7 +147,7 @@ feature -- Obsolete element change
-- Set `expiration to a default date'
-- Set-Cookie will include only Expires attribute and not Max_Age.
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
max_age := -1
set_expiration_date (create {DATE_TIME}.make_now_utc)
@@ -343,7 +343,7 @@ feature {NONE} -- Constants
end
note
copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -54,7 +54,7 @@ feature {NONE} -- Initialization
create date_time.make_from_epoch (n.as_integer_32)
end
make_from_string (s: READABLE_STRING_8)
make_from_string (s: READABLE_STRING_GENERAL)
-- Create from string representation `s'
-- Supports: RFC 1123 and RFC 850
-- Tolerant with: GMT+offset and GMT-offset
@@ -350,7 +350,7 @@ feature -- Helper routines.
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
-- HTTP-date = rfc1123-date | rfc850-date | asctime-date
-- rfc1123-date = wkday "," SP date1 SP time SP "GMT"
@@ -377,8 +377,8 @@ feature {NONE} -- Implementation
note
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
local
t: STRING_8
l_ddd, l_mmm: detachable STRING_8
t: STRING_32
l_ddd, l_mmm: detachable STRING_32
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
l_mo: INTEGER
l_gmt_offset: INTEGER -- minutes
@@ -565,7 +565,7 @@ feature {NONE} -- Implementation
t.extend (s[i].as_upper)
i := i + 1
end
if
if
t.same_string ("GMT") -- for instance: GMT+0002
or t.same_string ("UTC") -- for instance: UTC+0002
or t.is_empty -- for instance: +0002
@@ -622,7 +622,7 @@ feature {NONE} -- Implementation
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
-- asctime-date = wkday SP date3 SP time SP 4DIGIT
-- date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
@@ -638,8 +638,8 @@ feature {NONE} -- Implementation
note
EIS: "name=RFC2616", "protocol=URI", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html"
local
t: STRING_8
l_ddd, l_mmm: detachable STRING_8
t: STRING_32
l_ddd, l_mmm: detachable STRING_32
l_dd, l_yyyy, l_hh, l_mi, l_ss, l_ff2: INTEGER
l_mo: INTEGER
l_gmt_offset: INTEGER -- minutes
@@ -681,18 +681,18 @@ feature {NONE} -- Implementation
-- Tolerant to full month name ..
l_mmm.keep_head (3)
end
if l_mmm.same_string ("JAN") then l_mo := 01
elseif l_mmm.same_string ("FEB") then l_mo := 02
elseif l_mmm.same_string ("MAR") then l_mo := 03
elseif l_mmm.same_string ("APR") then l_mo := 04
elseif l_mmm.same_string ("MAY") then l_mo := 05
elseif l_mmm.same_string ("JUN") then l_mo := 06
elseif l_mmm.same_string ("JUL") then l_mo := 07
elseif l_mmm.same_string ("AUG") then l_mo := 08
elseif l_mmm.same_string ("SEP") then l_mo := 09
elseif l_mmm.same_string ("OCT") then l_mo := 10
elseif l_mmm.same_string ("NOV") then l_mo := 11
elseif l_mmm.same_string ("DEC") then l_mo := 12
if l_mmm.same_string_general ("JAN") then l_mo := 01
elseif l_mmm.same_string_general ("FEB") then l_mo := 02
elseif l_mmm.same_string_general ("MAR") then l_mo := 03
elseif l_mmm.same_string_general ("APR") then l_mo := 04
elseif l_mmm.same_string_general ("MAY") then l_mo := 05
elseif l_mmm.same_string_general ("JUN") then l_mo := 06
elseif l_mmm.same_string_general ("JUL") then l_mo := 07
elseif l_mmm.same_string_general ("AUG") then l_mo := 08
elseif l_mmm.same_string_general ("SEP") then l_mo := 09
elseif l_mmm.same_string_general ("OCT") then l_mo := 10
elseif l_mmm.same_string_general ("NOV") then l_mo := 11
elseif l_mmm.same_string_general ("DEC") then l_mo := 12
else err := True
end
else
@@ -875,7 +875,7 @@ feature {NONE} -- Implementation
invariant
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)"
source: "[
Eiffel Software

View File

@@ -118,7 +118,7 @@ feature -- Access
header_named_value (a_name: READABLE_STRING_8): like item
-- First header item found for `a_name' if any
obsolete
"Use `item' [2014-03]"
"Use `item' [2017-05-31]"
do
Result := item (a_name)
end

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="ws_client" uuid="AF6EDC56-D7B4-4E1F-A62B-40EBED3D93DF">
<target name="common">
<target name="common" abstract="true">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="echo_websocket_server" uuid="C9B3DA5F-DF0D-4C0F-924A-130B5C1E6604">
<target name="common">
<target name="common" abstract="true">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/.svn$</exclude>

View File

@@ -55,7 +55,7 @@ feature -- Access
body: like content
obsolete
"Use `content' [June/2015]"
"Use `content'. [2017-05-31]"
do
Result := body
end
@@ -69,6 +69,19 @@ feature -- Status report
across to_addresses as ic all is_valid_address (ic.item) 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
set_date (d: like date)
@@ -158,21 +171,6 @@ feature -- Header manipulation
lst.force (a_line)
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
reset
@@ -209,8 +207,6 @@ feature -- Conversion
end
header: STRING_8
local
hdate: HTTP_DATE
do
create Result.make (20)
if attached reply_to_address as l_reply_to then
@@ -259,8 +255,7 @@ feature -- Conversion
Result.append (subject)
Result.append_character ('%N')
Result.append ("Date: ")
create hdate.make_from_date_time (date)
hdate.append_to_rfc1123_string (Result)
;(create {HTTP_DATE}.make_from_date_time (date)).append_to_rfc1123_string (Result)
Result.append_character ('%N')
if attached additional_header_lines as l_lines and then
not l_lines.is_empty
@@ -285,11 +280,8 @@ feature -- Helpers
Result := add.has ('@')
end
invariant
-- invariant_clause: True
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)"
source: "[
Eiffel Software

View File

@@ -43,11 +43,8 @@ feature {NONE} -- Initialization
feature -- Status
is_available: BOOLEAN
local
f: RAW_FILE
do
create f.make_with_path (executable_path)
Result := f.exists
Result := (create {RAW_FILE}.make_with_path (executable_path)).exists
end
feature -- Change
@@ -108,7 +105,7 @@ feature -- Basic operation
if attached arguments as l_args then
args := l_args.twin
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.put_string (a_email.message)
f.close
@@ -196,10 +193,8 @@ feature {NONE} -- Implementation
result_creatable: Result.is_creatable
end
invariant
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)"
source: "[
Eiffel Software

View File

@@ -4,9 +4,8 @@ note
Note: it is based on EiffelNet {SMTP_PROTOCOL} implementation, and may not be complete.
]"
author: "$Author: jfiat $"
date: "$Date: 2015-06-30 11:07:17 +0200 (mar., 30 juin 2015) $"
revision: "$Revision: 97586 $"
date: "$Date$"
revision: "$Revision$"
class
NOTIFICATION_SMTP_MAILER
@@ -57,15 +56,12 @@ feature {NONE} -- Initialization
initialize
-- Initialize service.
local
l_address_factory: INET_ADDRESS_FACTORY
do
if attached username as u then
create smtp_protocol.make (smtp_host, u)
else
-- Get local host name needed in creation of SMTP_PROTOCOL.
create l_address_factory
create smtp_protocol.make (smtp_host, l_address_factory.create_localhost.host_name)
create smtp_protocol.make (smtp_host, (create {INET_ADDRESS_FACTORY}).create_localhost.host_name)
end
if smtp_port > 0 then
smtp_protocol.set_default_port (smtp_port)
@@ -98,9 +94,7 @@ feature -- Basic operation
local
l_email: EMAIL
h: STRING
k,v: STRING
i: INTEGER
hdate: HTTP_DATE
do
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
@@ -117,8 +111,7 @@ feature -- Basic operation
l_email.add_header_entry ({EMAIL_CONSTANTS}.H_subject, a_email.subject)
create h.make_empty
create hdate.make_from_date_time (a_email.date)
hdate.append_to_rfc1123_string (h)
;(create {HTTP_DATE}.make_from_date_time (a_email.date)).append_to_rfc1123_string (h)
l_email.add_header_entry ("Date", h)
if attached a_email.additional_header_lines as lst then
@@ -128,9 +121,7 @@ feature -- Basic operation
h := ic.item
i := h.index_of (':', 1)
if i > 0 then
k := h.head (i - 1)
v := h.substring (i + 1, h.count)
l_email.add_header_entry (k, v)
l_email.add_header_entry (h.head (i - 1), h.substring (i + 1, h.count))
else
check is_header_line: False end
end
@@ -181,7 +172,7 @@ feature {NONE} -- Implementation
end
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)"
source: "[
Eiffel Software

View File

@@ -1,7 +1,7 @@
note
description: "Store emails in specific folder."
date: "$Date: 2017-03-08 10:34:57 +0100 (mer., 08 mars 2017) $"
revision: "$Revision: 99935 $"
date: "$Date$"
revision: "$Revision$"
class
NOTIFICATION_EMAIL_DIRECTORY_STORAGE
@@ -41,7 +41,7 @@ feature -- Storage
-- Store `a_email'.
local
retried: BOOLEAN
f,w: RAW_FILE
w: RAW_FILE
dt: DATE_TIME
p: PATH
fn: STRING
@@ -72,8 +72,7 @@ feature -- Storage
p := p.extended (fn)
from
create f.make_with_path (p)
w := new_file_opened_for_writing (f)
w := new_file_opened_for_writing (create {RAW_FILE}.make_with_path (p))
until
w /= Void or i > 100
loop
@@ -113,14 +112,15 @@ feature -- Storage
local
retried: BOOLEAN
do
if not retried then
if not f.exists then
f.open_write
if f.is_open_write then
Result := f
elseif not f.is_closed then
f.close
end
if
not retried and then
not f.exists
then
f.open_write
if f.is_open_write then
Result := f
elseif not f.is_closed then
f.close
end
end
ensure

View 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
```

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<redirection xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" uuid="A75C2D84-D543-4708-BAF3-254C308376CC" message="Obsolete: use jwt.ecf !" location="jwt.ecf">
</redirection>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="jwt" uuid="A75C2D84-D543-4708-BAF3-254C308376CC" library_target="jwt">
<target name="jwt">
<root all_classes="true"/>
<option warning="true">
</option>
<capability>
<concurrency support="scoop" use="scoop"/>
<void_safety support="all" use="all"/>
</capability>
<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>

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,143 @@
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_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

View 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

View 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

View File

@@ -0,0 +1,276 @@
note
description: "JSON Web Token encoder"
date: "$Date$"
revision: "$Revision$"
class
JWT_ENCODER
feature -- Basic operations
encoded_values (a_values: STRING_TABLE [READABLE_STRING_GENERAL]; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
j: JSON_OBJECT
do
create j.make_with_capacity (a_values.count)
across
a_values as ic
loop
j.put_string (ic.item, ic.key)
end
Result := encoded_json (j, a_secret, a_algo)
end
encoded_json (a_json: JSON_OBJECT; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
vis: JSON_PRETTY_STRING_VISITOR
s: STRING
do
create s.make_empty
create vis.make (s)
vis.visit_json_object (a_json)
Result := encoded_string (s, a_secret, a_algo)
end
encoded_string (a_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: READABLE_STRING_8): STRING
local
alg, sign: STRING_8
l_enc_payload, l_enc_header: READABLE_STRING_8
do
reset_error
if a_algo.is_case_insensitive_equal_general (alg_hs256) then
alg := alg_hs256
elseif a_algo.is_case_insensitive_equal_general (alg_none) then
alg := alg_none
else
report_unsupported_alg_error (a_algo)
alg := alg_hs256 -- Default ...
end
l_enc_header := base64url_encode (header ("JWT", alg))
l_enc_payload := base64url_encode (a_payload)
sign := signature (l_enc_header, l_enc_payload, a_secret, alg)
create Result.make (l_enc_header.count + 1 + l_enc_payload.count + 1 + sign.count)
Result.append (l_enc_header)
Result.append_character ('.')
Result.append (l_enc_payload)
Result.append_character ('.')
Result.append (sign)
end
decoded_string (a_token: READABLE_STRING_8; a_secret: READABLE_STRING_8; a_algo: detachable READABLE_STRING_8): detachable STRING
local
i,j,n: INTEGER
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
do
reset_error
n := a_token.count
i := a_token.index_of ('.', 1)
if i > 0 then
j := a_token.index_of ('.', i + 1)
if j > 0 then
l_enc_header := a_token.substring (1, i - 1)
l_enc_payload := a_token.substring (i + 1, j - 1)
l_signature := a_token.substring (j + 1, n)
Result := base64url_decode (l_enc_payload)
alg := a_algo
if alg = Void then
alg := signature_algorithm_from_encoded_header (l_enc_header)
if alg = Void then
-- Use default
alg := alg_hs256
end
end
check alg_set: alg /= Void end
if alg.is_case_insensitive_equal (alg_hs256) then
alg := alg_hs256
elseif alg.is_case_insensitive_equal (alg_none) then
alg := alg_none
else
alg := alg_hs256
report_unsupported_alg_error (alg)
end
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
report_unverified_token_error
end
else
report_invalid_token
end
else
report_invalid_token
end
end
feature -- Error status
error_code: INTEGER
-- Last error, if any.
has_error: BOOLEAN
-- Last `encoded_string` reported an error?
do
Result := error_code /= 0
end
has_unsupported_alg_error: BOOLEAN
do
Result := error_code = unsupported_alg_error
end
has_unverified_token_error: BOOLEAN
do
Result := error_code = unverified_token_error
end
has_invalid_token_error: BOOLEAN
do
Result := error_code = invalid_token_error
end
feature {NONE} -- Error reporting
reset_error
do
error_code := 0
end
report_unsupported_alg_error (alg: READABLE_STRING_8)
do
error_code := unsupported_alg_error
end
report_unverified_token_error
do
error_code := unverified_token_error
end
report_invalid_token
do
error_code := invalid_token_error
end
feature {NONE} -- Constants
unsupported_alg_error: INTEGER = -2
unverified_token_error: INTEGER = -4
invalid_token_error: INTEGER = -8
alg_hs256: STRING = "HS256"
-- HMAC SHA256.
alg_none: STRING = "none"
-- for unsecured token.
feature -- Conversion
header (a_type: detachable READABLE_STRING_8; alg: READABLE_STRING_8): STRING
do
create Result.make_empty
Result.append ("{%"typ%":%"")
if a_type /= Void then
Result.append (a_type)
else
Result.append ("JWT")
end
Result.append ("%",%"alg%":%"")
Result.append (alg)
Result.append ("%"}")
end
feature {NONE} -- Conversion
signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8
local
jp: JSON_PARSER
do
create jp.make_with_string (base64url_decode (a_enc_header))
jp.parse_content
if
attached jp.parsed_json_object as jo and then
attached {JSON_STRING} jo.item ("alg") as j_alg
then
Result := j_alg.unescaped_string_8
end
end
feature -- Base64
base64url_encode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := urlsafe_encode (base64.encoded_string (s))
end
feature {NONE} -- Implementation
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
local
s: STRING
do
if alg = alg_none then
create Result.make_empty
else
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
s.append (a_enc_header)
s.append_character ('.')
s.append (a_enc_payload)
if alg = alg_hs256 then
Result := base64_hmacsha256 (s, a_secret)
else
Result := base64_hmacsha256 (s, a_secret)
end
Result := urlsafe_encode (Result)
end
end
base64url_decode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := base64.decoded_string (urlsafe_decode (s))
end
urlsafe_encode (s: READABLE_STRING_8): STRING_8
do
create Result.make_from_string (s)
Result.replace_substring_all ("=", "")
Result.replace_substring_all ("+", "-")
Result.replace_substring_all ("/", "_")
end
urlsafe_decode (s: READABLE_STRING_8): STRING_8
local
i: INTEGER
do
create Result.make_from_string (s)
Result.replace_substring_all ("-", "+")
Result.replace_substring_all ("_", "/")
from
i := Result.count \\ 4
until
i = 0
loop
i := i - 1
Result.extend ('=')
end
end
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
hs256: HMAC_SHA256
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
end
end

View 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

View 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

View File

@@ -0,0 +1,97 @@
note
description: "Summary description for {JWT_LOADER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JWT_LOADER
inherit
JWT_UTILITIES
feature -- Access
token (a_token_input: READABLE_STRING_8; a_secret: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
-- Decoded token from `a_token_input` given the secret `a_secret`, and optional context `ctx`
-- used to specify eventual issuer and various parameters.
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)
jws.set_algorithm (alg)
if alg = Void then
-- Use default
alg := alg_hs256
end
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_secret, 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

View File

@@ -0,0 +1,112 @@
note
description: "Summary description for {JWT_UTILITIES}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
JWT_UTILITIES
feature -- Constants
alg_hs256: STRING = "HS256"
-- HMAC SHA256.
alg_none: STRING = "none"
-- for unsecured token.
feature -- Encoding
base64url_encode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := urlsafe_encode (base64.encoded_string (s))
end
urlsafe_encode (s: READABLE_STRING_8): STRING_8
do
create Result.make_from_string (s)
Result.replace_substring_all ("=", "")
Result.replace_substring_all ("+", "-")
Result.replace_substring_all ("/", "_")
end
signature (a_enc_header, a_enc_payload: READABLE_STRING_8; a_secret: READABLE_STRING_8; alg: READABLE_STRING_8): STRING_8
local
s: STRING
do
if alg.is_case_insensitive_equal (alg_none) then
create Result.make_empty
else
create s.make (a_enc_header.count + 1 + a_enc_payload.count)
s.append (a_enc_header)
s.append_character ('.')
s.append (a_enc_payload)
if alg.is_case_insensitive_equal (alg_hs256) then
Result := base64_hmacsha256 (s, a_secret)
else
Result := base64_hmacsha256 (s, a_secret)
end
Result := urlsafe_encode (Result)
end
end
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
hs256: HMAC_SHA256
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
end
feature -- Decoding
base64url_decode (s: READABLE_STRING_8): STRING_8
local
urlencoder: URL_ENCODER
base64: BASE64
do
create urlencoder
create base64
Result := base64.decoded_string (urlsafe_decode (s))
end
urlsafe_decode (s: READABLE_STRING_8): STRING_8
local
i: INTEGER
do
create Result.make_from_string (s)
Result.replace_substring_all ("-", "+")
Result.replace_substring_all ("_", "/")
from
i := Result.count \\ 4
until
i = 0
loop
i := i - 1
Result.extend ('=')
end
end
feature -- Signature
supported_signature_algorithms: LIST [READABLE_STRING_8]
-- Supported signature algorithm `alg`?
do
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (2)
Result.extend (alg_hs256)
Result.extend (alg_none)
end
is_supporting_signature_algorithm (alg: READABLE_STRING_8): BOOLEAN
-- Is supporting signature algorithm `alg`?
do
Result := across supported_signature_algorithms as ic some alg.is_case_insensitive_equal (ic.item) end
end
end

View File

@@ -0,0 +1,193 @@
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
if attached jwt_loader.token (tok, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
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, "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

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-16-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-16-0 http://www.eiffel.com/developers/xml/configuration-1-16-0.xsd" name="testing" uuid="DB49E98A-0048-414A-A469-EE9B5B903BF3">
<target name="testing">
<root class="ANY" feature="default_create"/>
<option warning="true">
</option>
<setting name="console_application" value="true"/>
<capability>
<concurrency support="none" use="none"/>
<void_safety support="all" use="all"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="jwt" location="..\jwt.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>

View File

@@ -47,7 +47,7 @@ feature -- Change
across
ax_to_sreg_map as c
loop
ask_info (c.key.to_string_32, is_required)
ask_info (c.key, is_required)
end
end
@@ -204,18 +204,18 @@ feature {OPENID_CONSUMER_VALIDATION} -- Implementation
l_types as t
loop
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
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
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
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
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_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
end
end
@@ -494,7 +494,7 @@ feature {NONE} -- Implementation
feature -- Helper
xml_content (e: XML_ELEMENT): STRING_8
xml_content (e: XML_ELEMENT): STRING_32
do
create Result.make_empty
if attached e.contents as lst then

View File

@@ -89,10 +89,10 @@ feature -- Basic operation
create ret.make_from_string (return_url)
create tb.make (5)
if attached values as q_lst then
if attached item_by_name ("openid.claimed_id", q_lst) as q_claimed_id then
l_claimed_id := q_claimed_id.as_string_8
elseif attached item_by_name ("openid.identity", q_lst) as l_id then
l_claimed_id := l_id
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.to_string_8
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.to_string_8
end
identity := l_claimed_id
tb.force (item_by_name ("openid.assoc_handle", q_lst), "openid.assoc_handle")
@@ -117,7 +117,7 @@ feature -- Basic operation
if
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
-- 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.
@@ -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
l_alias := "ax."
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 + "."
end
end
@@ -235,8 +235,8 @@ feature -- Basic operation
loop
s := c.item
if
s.starts_with (k_value)
or s.starts_with (k_type)
s.starts_with_general (k_value)
or s.starts_with_general (k_type)
then
ax_keys.force ("openid." + s)
end
@@ -251,17 +251,17 @@ feature -- Basic operation
loop
s := c.item
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)
i := k.index_of ('.', 1)
if i > 1 then
k.keep_head (i - 1)
end
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
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
attributes.force (v, l_type.substring (26, l_type.count))
else

View File

@@ -8,7 +8,6 @@ note
title: Eiffel OpenID
description: OpenID consumer library
tags: openid,security,web,authentication,sso
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)
copyright: 2011-2016, 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

View File

@@ -137,7 +137,7 @@ feature -- Status report
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_empty
Result.append (type)
Result.append_string_general (type)
Result.append (" ")
if attached login as l_login then
Result.append ("login=[")

View File

@@ -2,8 +2,8 @@ note
description: "[
Standalone Web Server connector.
]"
date: "$Date: 2016-08-06 13:34:52 +0200 (sam., 06 août 2016) $"
revision: "$Revision: 99106 $"
date: "$Date$"
revision: "$Revision$"
class
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]]]
-- 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
@@ -201,7 +201,7 @@ feature -- Server
-- Shutdown web server listening.
do
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)
end
end
@@ -213,7 +213,7 @@ feature -- Events
require
obs.started -- SCOOP wait condition.
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
on_launched (obs.port)
end
@@ -282,9 +282,8 @@ feature {NONE} -- Implementation: element change
cfg.set_is_secure (b)
end
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)"
source: "[
Eiffel Software

View File

@@ -94,7 +94,7 @@ feature {NONE} -- Implementation
require
a_value_not_is_empty: a_value /= Void
do
a_output.append (a_name)
a_output.append_string_general (a_name)
a_output.append_character ('=')
a_output.append_string_general (a_value)
a_output.append_character ('%N')
@@ -103,7 +103,7 @@ feature {NONE} -- Implementation
append_variable_to_debug_output (a_name: READABLE_STRING_8; a_value: detachable READABLE_STRING_GENERAL; a_output: STRING_32)
do
if a_value /= Void then
a_output.append (a_name)
a_output.append_string_general (a_name)
a_output.append_character ('=')
a_output.append_string_general (a_value)
a_output.append_character ('%N')
@@ -354,7 +354,7 @@ invariant
request_method_set: not request_method.is_empty
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -54,7 +54,7 @@ feature -- Access
force_single_threaded: BOOLEAN assign set_force_single_threaded
obsolete
"Use directly `max_concurrent_connections = 1` [Feb/2017]"
"Use directly `max_concurrent_connections = 1` [2017-05-31]"
do
Result := max_concurrent_connections <= 1
end
@@ -202,7 +202,7 @@ feature -- Element change
-- Force server to handle incoming request in a single thread.
-- i.e set max_concurrent_connections to 1!
obsolete
"Use set_max_concurrent_connections (1) [June/2016]"
"Use set_max_concurrent_connections (1) [2017-05-31]"
do
if v then
set_max_concurrent_connections (1)
@@ -210,8 +210,8 @@ feature -- Element change
set_max_concurrent_connections (default_max_concurrent_connections)
end
ensure
force_single_threaded_set: v implies max_concurrent_connections <= 1
not_single_threaded: not v implies max_concurrent_connections > 1
force_single_threaded_set: v implies max_concurrent_connections <= 1
not_single_threaded: not v implies max_concurrent_connections > 1
end
set_is_verbose (b: BOOLEAN)
@@ -340,7 +340,7 @@ feature -- SSL Helpers
end
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)"
source: "[
Eiffel Software

View File

@@ -60,6 +60,7 @@ feature -- Execution
req: WGI_REQUEST_FROM_TABLE
res: detachable WGI_RESPONSE_STREAM
rescued: BOOLEAN
utf: UTF_CONVERTER
do
if not rescued then
a_input.reset
@@ -68,14 +69,14 @@ feature -- Execution
service.execute (req, res)
res.push
else
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.exception_trace as l_trace then
if attached (create {EXCEPTION_MANAGER}).last_exception as e and then attached e.trace as l_trace then
if res /= Void then
if not res.status_is_set then
res.set_status_code ({HTTP_STATUS_CODE}.internal_server_error, Void)
end
if res.message_writable then
res.put_string ("<pre>")
res.put_string (l_trace)
res.put_string (utf.string_32_to_utf_8_string_8 (l_trace))
res.put_string ("</pre>")
end
res.push
@@ -105,7 +106,7 @@ invariant
fcgi_attached: fcgi /= Void
note
copyright: "2011-2013, Eiffel Software and others"
copyright: "2011-2017, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -122,7 +122,7 @@ feature {NONE} -- Initialization
then
secure_settings := [l_secure_prot, opts.option_string_32_value ("secure_certificate", Void), opts.option_string_32_value ("secure_certificate_key", Void)]
elseif
-- OBSOLETE: backward compatible with old settings name [oct/2016].
-- OBSOLETE: backward compatible with old settings name [2017-05-31].
opts.option_boolean_value ("ssl_enabled", is_secure) and then
attached opts.option_string_32_value ("ssl_protocol", "tls_1_2") as ssl_prot
then
@@ -141,7 +141,7 @@ feature {NONE} -- Initialization
force_single_threaded
-- Set `single_threaded' to True.
obsolete
"Use set_max_concurrent_connections (1) [Feb/2017]"
"Use set_max_concurrent_connections (1) [2017-05-31]"
do
set_max_concurrent_connections (1)
ensure
@@ -238,7 +238,7 @@ feature {NONE} -- Implementation
single_threaded: BOOLEAN
obsolete
"Use max_concurrent_connections <= 1 [Feb/2017]"
"Use max_concurrent_connections <= 1 [2017-05-31]"
do
Result := max_concurrent_connections <= 1
end
@@ -277,7 +277,7 @@ feature -- Status report
end
;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)"
source: "[
Eiffel Software

View File

@@ -166,7 +166,8 @@ feature -- Basic Operation
if is_verbose then
log ("%NReceive <====================", debug_level)
if attached req.raw_header_data as rhd then
log (rhd, debug_level)
check raw_header_is_valid_as_string_8: rhd.is_valid_as_string_8 end
log (rhd.to_string_8, debug_level)
end
end
if
@@ -189,7 +190,8 @@ feature -- Basic Operation
end
-- Sending the server's opening handshake
create l_sha1.make
l_sha1.update_from_string (l_ws_key + magic_guid)
check l_ws_key_is_valid_as_string_8: l_ws_key.is_valid_as_string_8 end
l_sha1.update_from_string (l_ws_key.to_string_8 + magic_guid)
l_key := Base64_encoder.encoded_string (digest (l_sha1))
res.header.add_header_key_value ("Upgrade", "websocket")
res.header.add_header_key_value ("Connection", "Upgrade")
@@ -795,7 +797,7 @@ feature {NONE} -- Debug
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)"
source: "[
Eiffel Software

View File

@@ -69,7 +69,7 @@ feature -- Basic operations
s.append (c.item.url_encoded_name)
s.append_character (' ')
s.append_character ('{')
s.append (c.item.generating_type)
s.append (c.item.generating_type.name)
s.append_character ('}')
s.append_character ('=')
s.append (c.item.debug_output.as_string_8)
@@ -80,7 +80,7 @@ feature -- Basic operations
end
note
copyright: "2011-2014, 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)"
licensing_options: "http://www.eiffel.com/licensing"
copying: "[

View File

@@ -245,7 +245,7 @@ feature {NONE} -- Implementation
s.append_character (' ')
if attached c.item as l_item then
s.append_character ('{')
s.append (l_item.generating_type)
s.append (l_item.generating_type.name)
s.append_character ('}')
s.append_character (' ')
@@ -316,7 +316,7 @@ feature {NONE} -- Implementation
else
a_output.append (c.item.url_encoded_name)
end
t := c.item.generating_type
t := c.item.generating_type.name
if t.same_string ("WSF_STRING") then
else
a_output.append_character (' ')
@@ -420,7 +420,7 @@ feature -- Constants
invariant
note
copyright: "2011-2015, 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)"
source: "[
Eiffel Software

View File

@@ -108,13 +108,11 @@ feature -- Basic operations
handle_precondition_failed (req, res)
else
if attached req.http_if_unmodified_since as l_if_unmodified_since then
if l_if_unmodified_since.is_string_8 then
create l_date.make_from_string (l_if_unmodified_since.as_string_8)
if not l_date.has_error then
if a_handler.modified_since (req, l_date.date_time) then
handle_precondition_failed (req, res)
l_failed := True
end
create l_date.make_from_string (l_if_unmodified_since)
if not l_date.has_error then
if a_handler.modified_since (req, l_date.date_time) then
handle_precondition_failed (req, res)
l_failed := True
end
end
end
@@ -128,13 +126,11 @@ feature -- Basic operations
handle_if_none_match_failed (req, res, a_handler)
else
if attached req.http_if_modified_since as l_if_modified_since then
if l_if_modified_since.is_string_8 then
create l_date.make_from_string (l_if_modified_since.as_string_8)
if not l_date.has_error then
if not a_handler.modified_since (req, l_date.date_time) then
handle_not_modified (req, res, a_handler)
l_failed := True
end
create l_date.make_from_string (l_if_modified_since)
if not l_date.has_error then
if not a_handler.modified_since (req, l_date.date_time) then
handle_not_modified (req, res, a_handler)
l_failed := True
end
end
end
@@ -408,7 +404,7 @@ feature -- Error reporting
if req.is_content_type_accepted ({HTTP_MIME_TYPES}.text_html) then
s := "<html lang=%"en%"><head>"
s.append ("<title>")
s.append (html_encoder.encoded_string (req.request_uri))
s.append (html_encoder.general_encoded_string (req.request_uri))
s.append ("Error " + a_status_code.out + " (" + l_msg + ")")
s.append ("</title>%N")
s.append ("[
@@ -434,7 +430,7 @@ feature -- Error reporting
s.append ("</div>")
s.append ("The current location for this resource is <a href=%"" + a_locations.first.string + "%">here</a>")
s.append ("Error " + a_status_code.out + " (" + l_msg + ")</div>")
s.append ("<div id=%"message%">Error " + a_status_code.out + " (" + l_msg + "): <code>" + html_encoder.encoded_string (req.request_uri) + "</code></div>")
s.append ("<div id=%"message%">Error " + a_status_code.out + " (" + l_msg + "): <code>" + html_encoder.general_encoded_string (req.request_uri) + "</code></div>")
s.append ("<div id=%"footer%"></div>")
s.append ("</body>%N")
s.append ("</html>%N")
@@ -587,7 +583,7 @@ feature -- Error reporting
end
note
copyright: "2011-2014, Colin Adams, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: "2011-2017, Colin Adams, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -158,7 +158,7 @@ feature -- Access
deferred
end
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_32; a_strong: BOOLEAN): BOOLEAN
matching_etag (req: WSF_REQUEST; a_etag: READABLE_STRING_GENERAL; a_strong: BOOLEAN): BOOLEAN
-- Is `a_etag' a match for resource requested in `req'?
-- If `a_strong' then the strong comparison function must be used.
require

View File

@@ -147,8 +147,11 @@ feature {WSF_RESPONSE} -- Output
l_description.append ("</ul>")
end
if doc_url_supported and then attached {WSF_STRING} request.query_parameter ("api") as l_api then
l_api_resource := l_api.value
if
doc_url_supported and then attached {WSF_STRING} request.query_parameter ("api") as l_api and then
l_api.value.is_valid_as_string_8
then
l_api_resource := l_api.value.to_string_8
if l_api_resource.is_empty then
l_api_resource := Void
end
@@ -324,7 +327,7 @@ feature {NONE} -- Implementation
end
note
copyright: "2011-2014, 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)"
source: "[
Eiffel Software

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