Compare commits

...

52 Commits

Author SHA1 Message Date
Jocelyn Fiat
edec837c4e Made interface of wsf forms and widgets a bit more flexible by accepting READABLE_STRING_GENERAL. 2017-10-17 14:34:50 +02:00
Jocelyn Fiat
f1642a444a Improved support of absolute/relative https:// and http:// in http_client. 2017-10-17 14:30:44 +02:00
Jocelyn Fiat
48af63af83 Fixed typo to process relative or absolute url. 2017-10-09 14:23:45 +02:00
Jocelyn Fiat
2f98d7031f Updated a few package.iron files. 2017-10-06 14:06:46 +02:00
Jocelyn Fiat
70f00651c7 update test case. 2017-10-06 13:58:54 +02:00
Jocelyn Fiat
199f84c7ef Updated Readme file with Build Status on Master Branch. 2017-10-06 10:37:11 +02:00
Jocelyn Fiat
9b97627c76 Update package.iron files. 2017-10-06 10:27:32 +02:00
Jocelyn Fiat
72c87cd74d Fixed curl implementation by setting Content-Type to x-www-form-urlencoded (if not set) when POST send data as x-www-form-urlencoded. 2017-10-06 10:24:48 +02:00
Jocelyn Fiat
2ed4d03168 Renamed ciphers_settings as ciphers_setting . 2017-10-06 09:34:08 +02:00
Jocelyn Fiat
18ed92a61d Moved to unique .ecf from ecf version 1-16-0 .
Requires 17.05 or newer.
2017-10-06 09:02:10 +02:00
Jocelyn Fiat
0a6a4281e7 Merge branch 'master' into es17.05 2017-10-06 08:18:50 +02:00
Jocelyn Fiat
38cf5d7a6f Updated requirements, now EiffelWeb requires 17.05 or newer.
Improved support for future 17.11 version with new openssl library.
2017-10-05 22:07:05 +02:00
jvelilla
96648a16dc Updated Readme file with Build Status on Master Branch.
Update code style: refactor rename ciphers_settings to ciphers_setting.
2017-09-25 14:21:35 -03:00
Javier Velilla
6f35ad7b16 Merge pull request #181 from jvelilla/ewf_http_client
Update HTTP Client cURL implementation:
2017-09-25 11:05:45 -03:00
jvelilla
85c8a46c89 Update Readme.md with a note about ciphers implementation. 2017-09-21 08:07:11 -03:00
Jocelyn Fiat
498e4a6ec2 Fixed validation of iss and aud when issuer and audience are not set. 2017-09-21 10:46:08 +02:00
Jocelyn Fiat
ab507d543a Now HTTP_AUTHORIZATION acceps READABLE_STRING_GENERAL for username and password argument. 2017-09-21 10:45:40 +02:00
Jocelyn Fiat
20a90db2e3 If url is relative, use session.url (...) to get valid url.
It could happen with relative url in `Location: ...` header (for redirection).
2017-09-21 10:45:08 +02:00
jvelilla
6ed91699b8 Renamed feature 'set_ciphers' to 'set_ciphers_settings' and added description. 2017-09-19 10:32:17 -03:00
jvelilla
bb334aef80 Updated HTTP client cURL implementation.
Refactor rename cipher_list by ciphers_settings and description.
Updated ciphers_settings representation to STIRNG_8
Refactor rename set_cipher_list by set_ciphers.
2017-09-14 11:58:43 -03:00
jvelilla
c2764e25ff Update HTTP Client cURL implementation:
Added the option to set cipher list used to negotiate security settings
(SSL handshake)
2017-09-14 10:21:32 -03:00
Jocelyn Fiat
6425482070 Fixed ecf by removing the override declaration. 2017-08-08 15:55:52 +02:00
Jocelyn Fiat
818c3fb460 Made compilable with EiffelStudio 17.05 and probably before as well. 2017-08-08 15:54:21 +02:00
Jocelyn Fiat
dac50b490d Added output for the travis CI job. 2017-08-08 14:10:12 +02:00
Jocelyn Fiat
16d5076fe5 Added Travis CI support with 17.05. 2017-08-08 14:03:39 +02:00
Jocelyn Fiat
2748e1d9ee Now JWT_LOADER takes the alg as argument, to avoid security issue where the lib is taking alg from the header (which may be a bad security weakness). 2017-07-11 23:32:11 +02:00
Jocelyn Fiat
27ee20f99b Added convenient get and custom functions on HTTP_CLIENT directly. 2017-07-11 23:29:42 +02:00
Javier Velilla
9a3164df70 Merge pull request #178 from jvelilla/ewf_ssl
Updated EWF  http_network, websocket, httpd to use the latest EiffelN…
2017-06-23 09:53:26 -03:00
jvelilla
02383810b4 Fixed bad identation
Updated date to current date in obsolte message.
2017-06-23 09:51:59 -03:00
jvelilla
dbf5e76047 Updated EWF network and httpd libraries.
Updated features using ssl_2 and ssl_3 as obsolete and raise a
developer exception.
2017-06-22 10:23:56 -03:00
jvelilla
5c31905427 Updated EWF http_network, websocket, httpd to use the latest EiffelNet SSL
version.
2017-06-21 18:34:07 -03:00
Jocelyn Fiat
21adef6998 Merge branch 'master' into es17.05
# Conflicts:
#	library/network/http_client/http_client-safe.ecf
#	library/network/http_client/libcurl_http_client-safe.ecf
#	library/network/http_client/net_http_client-safe.ecf
2017-06-21 09:01:27 +02:00
Jocelyn Fiat
c7ef652322 Made the parameters not hidden implementation classes. 2017-06-21 08:59:07 +02:00
Jocelyn Fiat
46a48625ac Merge branch 'master' into es17.05 2017-06-20 18:15:03 +02:00
Jocelyn Fiat
7feb45b549 Updated error library (cosmetic, and loop iteration). 2017-06-20 18:08:50 +02:00
Jocelyn Fiat
aeb3392ad8 Merge branch 'master' into es17.05
# Conflicts:
#	contrib/library/network/server/nino/example/SimpleWebServer/web_server-safe.ecf
#	contrib/library/network/server/nino/example/SimpleWebServer/web_server.ecf
#	library/network/http_client/libcurl_http_client-safe.ecf
#	library/network/http_client/net_http_client-safe.ecf
2017-06-20 17:52:40 +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
9fc5a7c364 Merge branch 'v1' into es17.05 2017-06-20 09:54:07 +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
c51a0a4788 Merge branch 'v1' into es17.05
# Conflicts:
#	library/network/http_client/tests/test.ecf
2017-06-20 09:49:51 +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
94 changed files with 3127 additions and 1119 deletions

View File

@@ -0,0 +1,19 @@
language: eiffel
before_script:
- export current_dir=$PWD ; echo current_dir=$current_dir ; cd ..
- export ISE_VERSION=17.05; export ISE_BUILD=100416
- curl -sSL http://downloads.sourceforge.net/eiffelstudio/Eiffel_${ISE_VERSION}_gpl_${ISE_BUILD}-linux-x86-64.tar.bz2 | tar -x --bzip2
- export ISE_EIFFEL=$PWD/Eiffel_${ISE_VERSION} ; export ISE_PLATFORM=linux-x86-64
- export PATH=$PATH:$ISE_EIFFEL/studio/spec/$ISE_PLATFORM/bin:$PATH:$ISE_EIFFEL/tools/spec/$ISE_PLATFORM/bin
- echo `ec -version`
- cd $current_dir
- echo Check projects compilation status...
branches:
only:
- master
- v1
script: compile_all -ecb -melt -list_failures -log_verbose -clean -options dotnet=false
group: stable
os: linux

View File

@@ -7,20 +7,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
## [Unreleased]
### Added
- `jwt`: new JSON Web Token (JWT) library (supports for claim exp, iat, nbf, iss, aud).
- `http_client`: added support for ciphers setting in the libcurl implementation only.
- `http_client`: added convenient `get` and `custom` functions on HTTP_CLIENT directly.
### Changed
- http_network:
Integrated changes on SOCKET so that EiffelWeb can be compiled with 16.05 to 17.05 and after.
- adopted ecf version 1-16-0 and use a single .ecf file (the -safe.ecf are now redirection to normal .ecf)
### Deprecated
- removed support for Eiffel version before 17.05 .
- SSL 2 or 3 is obsolete and will raise an exception if used.
### Removed
### Fixed
- http_client:
Improved query and form data encoding (based on a very early version of the general URI percent-encoding rules).
- now correct encoding of space by '%20' in path segment, and '+' in query parameters.
Unify and fixed query parameters handling for libcurl and net implementation.
Fixed file uploading (various issue in libcurl, and net implementation).
Fixed form multipart encoding by using correctly the boundary.
- Code cleaning:
Removed many obsolete calls, and added timestamp on EiffelWeb obsolete features to benefit from upcoming improvement on the EiffelStudio Inspector tool.
- Removed a few obsolete calls.
- `http_client`: Added support for multiple file in form data. Made clear what is the meaning of `upload_filename`, `upload_data` and `form_data`.
- `authentication`: HTTP_AUTHORIZATION acceps now READABLE_STRING_GENERAL for username and password argument.
- `http_client`: fixed curl implementation by setting `Content-Type` to `x-www-form-urlencoded` (if not set) when POST send data as `x-www-form-urlencoded`.
### Security

View File

@@ -1,5 +1,7 @@
# Eiffel Web Framework
[![Build Status](https://api.travis-ci.org/EiffelWebFramework/EWF.svg?branch=master)](https://travis-ci.org/EiffelWebFramework/EWF/)
## Overview

View File

@@ -1,3 +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="B1D3254D-A58E-4259-9796-8A2843A511A9" message="Obsolete: use web_server.ecf !" location="web_server.ecf">
</redirection>
<?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="B1D3254D-A58E-4259-9796-8A2843A511A9" message="Obsolete: use web_server.ecf !" location="web_server.ecf">
</redirection>

View File

@@ -1,7 +1,6 @@
package nino
project
nino = "nino-safe.ecf"
nino = "nino.ecf"
note

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

@@ -10,6 +10,9 @@ It provides simple routine to perform http requests, and get response.
- Eiffel Net library
- and optionally Eiffel NetSSL library to support `https://...`
* Note: set ciphers setting is supported only with libcurl implementation for now, net implementation
set all the ciphers as part of the OpenSSL initialization.
This means on Windows, do not forget to copy the libcurl.dll (and related) either in the same directory of the executable, or ensure the .dll are in the PATH environment.
It is possible to exclude the libcurl implementation xor the Eiffel Net implementation:

View File

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
<condition>
@@ -32,7 +30,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="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

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
@@ -16,6 +14,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

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
<library name="http" location="..\protocol\http\http.ecf"/>
@@ -25,6 +23,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

@@ -1,22 +1,20 @@
package http_client
project
http_client = "http_client-safe.ecf"
http_client = "http_client.ecf"
libcurl_http_client = "libcurl_http_client-safe.ecf"
libcurl_http_client = "libcurl_http_client.ecf"
net_http_client = "net_http_client-safe.ecf"
net_http_client = "net_http_client.ecf"
note
title: HTTP client
description: "[
Provides simple routines to perform http requests, and get associated response.
It has two implementations:
- using Eiffel cURL (i.e libcurl)
- using EiffelNET (and the EiffelNET SSL extension)
]"
collection:EWF
description: "[
Provides simple routines to perform http requests, and get associated response.
It has two implementations:
- using Eiffel cURL (i.e libcurl)
- using EiffelNET (and the EiffelNET SSL extension)
]"
collection: EWF
tags: http,client,network,request,web,curl,EWF
copyright: 1984-2016 Eiffel Software and others
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)

View File

@@ -16,8 +16,19 @@ feature -- Access
deferred
end
get (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := new_session (a_url).get ("", ctx)
end
custom (a_method: READABLE_STRING_8; a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for `a_method' request based on `a_url' and optional `ctx'.
do
Result := new_session (a_url).custom (a_method, "", ctx)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -30,8 +30,19 @@ feature {NONE} -- Initialization
-- Initialize Current with `a_url' and `ctx'.
-- This can be used to reset/reinitialize Current with new url
-- in the case of redirection.
local
i: INTEGER
do
url := a_url
i := a_url.substring_index ("://", 1)
if i > 0 then
check
a_url.substring (1, i).same_string ("http")
or a_url.substring (1, i).same_string ("https")
end
url := a_url
else
url := session.url (a_url, Void)
end
headers := session.headers.twin
if ctx /= Void then
context := ctx

View File

@@ -58,11 +58,11 @@ feature -- Access
-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
--| note: the value from Current context override the one from the session in case of conflict
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
query_parameters: HTTP_CLIENT_REQUEST_QUERY_PARAMETERS
-- Query parameters to be appended to the url
--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url
form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
form_parameters: HTTP_CLIENT_REQUEST_FORM_PARAMETERS
-- Form parameters
upload_data: detachable READABLE_STRING_8
@@ -145,13 +145,25 @@ feature -- Element change
add_query_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
-- Add a query parameter `k=v'.
do
query_parameters.force (v.to_string_32, k.to_string_32)
query_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
end
add_form_parameter (k: READABLE_STRING_GENERAL; v: READABLE_STRING_GENERAL)
-- Add a form parameter `k'= `v'.
do
form_parameters.force (v.to_string_32, k.to_string_32)
form_parameters.force (create {HTTP_CLIENT_REQUEST_STRING_PARAMETER}.make (k, v))
end
add_file_form_parameter (k: READABLE_STRING_GENERAL; a_location: READABLE_STRING_GENERAL; a_content_type: detachable READABLE_STRING_8)
-- Add a form file parameter named `k`, located at `a_location`, with optional content type `a_content_type`.
require
has_no_upload_data_or_filename: not has_upload_data and not has_upload_filename
local
param: HTTP_CLIENT_REQUEST_FILE_PARAMETER
do
create param.make_with_path (k, create {PATH}.make_from_string (a_location))
param.set_content_type (a_content_type)
form_parameters.force (param)
end
set_credentials_required (b: BOOLEAN)
@@ -164,7 +176,8 @@ feature -- Element change
-- Set `upload_data' to `a_data'
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_upload_filename: (a_data /= Void and then not a_data.is_empty) implies not has_upload_filename
has_no_form_data: (a_data /= Void and then not a_data.is_empty) implies not has_form_data
do
if a_data = Void or else a_data.is_empty then
upload_data := Void
@@ -180,6 +193,7 @@ feature -- Element change
--| note: the Current context can have upload_data XOR upload_filename, but not both.
require
has_no_upload_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_upload_data
has_no_form_data: (a_fn /= Void and then not a_fn.is_empty) implies not has_form_data
do
if a_fn = Void or else a_fn.is_empty then
upload_filename := Void
@@ -266,9 +280,9 @@ feature -- URL helpers
a_url.append_character ('&')
end
l_first_param := False
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, a_url)
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, a_url)
a_url.append_character ('=')
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, a_url)
ic.item.append_query_value_encoded_to (a_url)
end
end
end
@@ -315,38 +329,35 @@ feature {NONE} -- Implementation
end
end
parameters_to_uri_percent_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
-- Build query urlencoded string using parameters from `ht'.
parameters_to_uri_percent_encoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
-- Build query urlencoded string using parameters from `a_params'.
do
create Result.make (64)
across
ht as ic
a_params as ic
loop
if not Result.is_empty then
Result.append_character ('&')
end
uri_percent_encoder.append_query_name_encoded_string_to (ic.key, Result)
uri_percent_encoder.append_query_name_encoded_string_to (ic.item.name, Result)
Result.append_character ('=')
uri_percent_encoder.append_query_value_encoded_string_to (ic.item, Result)
ic.item.append_query_value_encoded_to (Result)
end
end
parameters_to_x_www_form_urlencoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
-- Build x-www-form-urlencoded string using parameters from `ht'.
parameters_to_x_www_form_urlencoded_string (a_params: HTTP_CLIENT_REQUEST_PARAMETERS [HTTP_CLIENT_REQUEST_PARAMETER]): STRING_8
-- Build x-www-form-urlencoded string using parameters from `a_params'.
do
create Result.make (64)
from
ht.start
until
ht.after
across
a_params as ic
loop
if not Result.is_empty then
Result.append_character ('&')
end
Result.append (x_www_form_url_encoder.encoded_string (ht.key_for_iteration))
x_www_form_url_encoder.append_percent_encoded_string_to (ic.item.name, Result)
Result.append_character ('=')
Result.append (x_www_form_url_encoder.encoded_string (ht.item_for_iteration))
ht.forth
ic.item.append_form_url_encoded_to (Result)
end
end

View File

@@ -272,6 +272,14 @@ feature -- Authentication
-- Associated optional credentials value.
-- Computed as `username':`password'.
ciphers_setting: detachable READABLE_STRING_8
-- SSL cipher preference lists
-- examples: DEFAULT, ALL, TLSv1
-- check https://www.openssl.org/docs/man1.1.0/apps/ciphers.html
-- Warning: At the moment only used for LIB_CURL_HTTP_CLIENT
-- Net implementation set all the ciphers using the OpenSSL at
-- initialization time.
feature -- Status setting
set_is_debug (b: BOOLEAN)
@@ -401,6 +409,14 @@ feature -- Element change
chunk_size := a_size
end
set_ciphers_setting (a_ciphers_setting: READABLE_STRING_8)
-- Set 'ciphers_setting' with 'a_ciphers_setting'.
do
create {STRING_8} ciphers_setting.make_from_string (a_ciphers_setting)
ensure
ciphers_setting_set: attached ciphers_setting as c_setting and then c_setting.same_string (a_ciphers_setting)
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)"

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,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
@@ -151,70 +150,19 @@ feature -- Execution
--| Credentials not provided ...
end
end
if ctx.has_upload_data then
l_upload_data := ctx.upload_data
end
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
check non_empty_form_data: not l_form_data.is_empty end
if l_upload_data = Void and l_upload_filename = Void then
-- Send as form-urlencoded
if
attached l_headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") then
l_use_curl_form := True
else
-- Not supported, use libcurl form.
l_use_curl_form := True
end
else
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
else
l_use_curl_form := True
end
if l_use_curl_form then
create l_form.make
create l_last.make
from
l_form_data.start
until
l_form_data.after
loop
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, l_form_data.key_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_copycontents, l_form_data.item_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_end
)
l_form_data.forth
end
if l_upload_filename /= Void then
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, "file",
{CURL_FORM_CONSTANTS}.curlform_file, l_upload_filename,
{CURL_FORM_CONSTANTS}.curlform_end
)
l_upload_filename := Void
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
end
if l_upload_data /= Void then
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
@@ -224,6 +172,7 @@ feature -- Execution
or request_method.is_case_insensitive_equal ("PUT")
or request_method.is_case_insensitive_equal ("PATCH")
end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_readable then
@@ -238,12 +187,62 @@ 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_headers.force ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
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)
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
@@ -376,6 +375,11 @@ feature -- Execution
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifypeer, 0)
end
--| Cipher List
if attached session.ciphers_setting as c_list then
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_cipher_list, c_list )
end
--| Request method
if request_method.is_case_insensitive_equal ("GET") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpget, 1)

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
@@ -113,6 +113,7 @@ feature -- Access
-- Get URL data
l_is_https := url.starts_with_general ("https://")
create l_uri.make_from_string (url)
check valid_url: l_uri.is_valid end
l_port := l_uri.port
if l_port = 0 then
if l_is_https then
@@ -149,7 +150,7 @@ feature -- Access
then
create l_authorization.make_basic_auth (u_name, u_pass)
if attached l_authorization.http_authorization as auth then
headers.extend (auth, "Authorization")
headers.force (auth, "Authorization")
end
check headers.has_key ("Authorization") end
end
@@ -176,7 +177,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 +192,52 @@ feature -- Access
l_upload_data := ctx.upload_data
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
if l_upload_data = Void and l_upload_filename = Void then
if
attached headers.item ("Content-Type") as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif l_ct.starts_with ("multipart/form-data") then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
else
-- not supported !
-- Send as form-urlencoded
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
else
-- Send as form-urlencoded
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
headers.extend (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
elseif l_form_data /= Void then
check l_upload_data = Void end
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_upload_filename, l_boundary)
headers.extend (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
elseif l_upload_data /= Void then
if l_upload_data /= Void then
check ctx.has_upload_data end
check no_form_data: not ctx.has_form_data end
if not headers.has ("Content-Type") then
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
headers.force ("application/x-www-form-urlencoded", "Content-Type")
end
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_data.count.out, "Content-Length")
headers.force (l_upload_data.count.out, "Content-Length")
end
elseif l_upload_filename /= Void then
check ctx.has_upload_filename end
check no_form_data: not ctx.has_form_data end
create l_upload_file.make_with_name (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_access_readable then
if not l_is_chunked_transfer_encoding then
headers.extend (l_upload_file.count.out, "Content-Length")
headers.force (l_upload_file.count.out, "Content-Length")
end
end
check l_upload_file /= Void end
elseif
ctx.has_form_data and
attached ctx.form_parameters as l_form_data
then
l_ct := headers.item ("Content-Type")
if l_ct /= Void and then l_ct.starts_with ("application/x-www-form-urlencoded") then
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
elseif
(l_ct /= Void and then l_ct.starts_with ("multipart/form-data"))
or l_form_data.has_file_parameter
then
-- create form using multipart/form-data encoding
l_boundary := new_mime_boundary (l_form_data)
headers.force ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
l_upload_data := form_date_and_uploaded_files_to_mime_string (l_form_data, l_boundary)
else
-- not supported !
-- Send as form-urlencoded
headers.force ("application/x-www-form-urlencoded", "Content-Type")
l_upload_data := ctx.form_parameters_to_x_www_form_url_encoded_string
end
headers.force (l_upload_data.count.out, "Content-Length")
if l_is_chunked_transfer_encoding then
-- Discard chunked transfer encoding
headers.remove ("Transfer-Encoding")
l_is_chunked_transfer_encoding := False
end
end
end
@@ -482,14 +468,9 @@ feature {NONE} -- Helpers
Result := a_status >= 300 and a_status < 400
end
form_date_and_uploaded_files_to_mime_string (a_form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_upload_filename: detachable READABLE_STRING_GENERAL; a_mime_boundary: READABLE_STRING_8): STRING
form_date_and_uploaded_files_to_mime_string (a_form_parameters: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]; a_mime_boundary: READABLE_STRING_8): STRING
-- Form data and uploaded files converted to mime string.
-- TODO: design a proper MIME... component.
local
l_path: PATH
l_mime_type: READABLE_STRING_8
l_upload_file: detachable RAW_FILE
l_mime_type_mapping: HTTP_FILE_EXTENSION_MIME_MAPPING
do
create Result.make (100)
across
@@ -500,48 +481,26 @@ feature {NONE} -- Helpers
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (ic.key))
Result.append (string_to_mime_encoded_string (ic.item.name))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
Result.append (string_to_mime_encoded_string (ic.item))
Result.append (http_end_of_header_line)
end
if a_upload_filename /= Void then
-- get file extension, otherwise set default
create l_mime_type_mapping.make_default
create l_path.make_from_string (a_upload_filename)
if
attached l_path.extension as ext and then
attached l_mime_type_mapping.mime_type (ext) as l_mt
attached {HTTP_CLIENT_REQUEST_FILE_PARAMETER} ic.item as fileparam and then
attached fileparam.file_name as fn
then
l_mime_type := l_mt
else
l_mime_type := "application/octet-stream"
Result.append ("; filename=")
Result.append_character ('%"')
Result.append (string_to_mime_encoded_string (fn))
Result.append_character ('%"')
end
Result.append ("--")
Result.append (a_mime_boundary)
Result.append (http_end_of_header_line)
Result.append ("Content-Disposition: form-data; name=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append ("; filename=%"")
Result.append (string_to_mime_encoded_string (a_upload_filename))
Result.append_character ('%"')
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (l_mime_type)
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
create l_upload_file.make_with_path (l_path)
if l_upload_file.exists and then l_upload_file.is_access_readable then
append_file_content_to (l_upload_file, l_upload_file.count, Result)
-- Reset l_upload_file to Void, since the related content is already processed.
l_upload_file := Void
if attached ic.item.content_type as ct then
Result.append (http_end_of_header_line)
Result.append ("Content-Type: ")
Result.append (ct)
end
Result.append (http_end_of_header_line)
Result.append (http_end_of_header_line)
ic.item.append_as_mime_encoded_to (Result)
Result.append (http_end_of_header_line)
end
Result.append ("--")
Result.append (a_mime_boundary)
@@ -893,7 +852,7 @@ feature {NONE} -- Helpers
end
end
new_mime_boundary (a_data: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING
new_mime_boundary (a_data: ITERABLE [HTTP_CLIENT_REQUEST_PARAMETER]): STRING
-- New MIME boundary.
local
s: STRING
@@ -904,7 +863,7 @@ feature {NONE} -- Helpers
across
a_data as ic
loop
i := i + ic.item.count + ic.key.count
i := i + ic.item.count + ic.item.name.count
end
create ran.set_seed (i) -- FIXME: use a real random seed.
ran.start

View File

@@ -13,7 +13,8 @@
<capability>
<concurrency support="thread"/>
</capability>
<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.ecf"/>

View File

@@ -59,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
libcurl_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
libcurl_test_get_with_redirection
do
test_get_with_redirection

View File

@@ -59,6 +59,11 @@ feature -- Tests
test_post_with_file_and_form_data
end
net_test_post_with_multiple_file_and_form_data
do
test_post_with_multiple_file_and_form_data
end
net_test_get_with_redirection
do
test_get_with_redirection

View File

@@ -21,8 +21,7 @@ feature -- Initialization
on_prepare
do
Precursor
global_requestbin_path := "/s0jkhhs0"
if global_requestbin_path = Void then
if is_using_requestbin and global_requestbin_path = Void then
global_requestbin_path := new_requestbin_path
end
end
@@ -33,7 +32,13 @@ feature -- Factory
deferred
end
feature -- Requestbin
feature -- Requestbin
is_using_requestbin: BOOLEAN = False
is_using_mockbincom: BOOLEAN
do
Result := not is_using_requestbin
end
global_requestbin_path: detachable READABLE_STRING_8
@@ -42,7 +47,7 @@ feature -- Requestbin
i,j: INTEGER
do
if
attached new_session ("http://requestb.in") as sess and then
attached new_session ("https://requestb.in") as sess and then
attached sess.post ("/api/v1/bins", Void, Void) as resp
then
if resp.error_occurred then
@@ -67,13 +72,30 @@ feature -- Requestbin
if not Result.starts_with ("/") then
Result.prepend_character ('/')
end
print ("new_requestbin_path => http://requestb.in" + Result + "?inspect%N")
print ("new_requestbin_path => " + sess.base_url + Result + "?inspect%N")
end
end
end
end
end
new_web_session: like new_session
do
if is_using_mockbincom then
Result := new_session ("http://mockbin.com/request")
end
if Result = Void and is_using_requestbin then
if attached global_requestbin_path as l_path then
Result := new_session ("https://requestb.in" + l_path)
else
assert ("Has requestbin path", False)
end
end
if Result = Void then
Result := new_session ("http://mockbin.com/request") -- Default
end
end
feature -- Factory
test_post_url_encoded
@@ -81,288 +103,200 @@ feature -- Factory
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- URL ENCODED POST REQUEST
-- check requestbin to ensure the "Hello World" has been received in the raw body
-- also check that User-Agent was sent
create h.make_empty
sess := new_session ("http://requestb.in")
if
attached sess.post (requestbin_path, Void, "Hello World") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- URL ENCODED POST REQUEST
-- check requestbin to ensure the "Hello World" has been received in the raw body
-- also check that User-Agent was sent
create h.make_empty
sess := new_web_session
if
attached sess.post ("", Void, "Hello World") as res
then
check_response (res)
end
end
test_post_with_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_form_parameter ("First Key", "First Value")
l_ctx.add_form_parameter ("Second Key", "Second Value")
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_web_session
create l_ctx.make
l_ctx.add_form_parameter ("First Key", "First Value")
l_ctx.add_form_parameter ("Second Key", "Second Value")
l_ctx.add_form_parameter ("unicode", {STRING_32} "Hello / 你好 !")
l_ctx.add_form_parameter ({STRING_32} "Field 你好 !", "How are you?")
if
attached sess.post ("", l_ctx, "") as res
then
check_response (res)
end
end
test_post_with_uncommon_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_web_session
create l_ctx.make
-- POST REQUEST WITH FORM DATA
-- check requestbin to ensure the form parameters are correctly received
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
l_ctx.add_form_parameter ("test", "!$&'()*") --
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
l_ctx.add_form_parameter ("?", "?") -- question mark
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
l_ctx.add_form_parameter ("&", "&") -- ampersand
l_ctx.add_form_parameter ("title", "Eiffel World!") -- space and !
l_ctx.add_form_parameter ("path", "foo/bar") -- slash
l_ctx.add_form_parameter ("unreserved", ":!@[]{}()*") -- ...
l_ctx.add_form_parameter ("reserved", "+=?&_#_") -- ...
l_ctx.add_form_parameter ("a=b", "a=b") -- equal sign
l_ctx.add_form_parameter ("test", "!$&'()*") --
l_ctx.add_form_parameter ("lst[a][b]", "[123][456]") -- brackets
l_ctx.add_form_parameter ("pos{1,2}", "loc{a,b}") -- curly brackets
l_ctx.add_form_parameter ("?foo", "?bar") -- question mark
l_ctx.add_form_parameter ("?", "?") -- question mark
l_ctx.add_form_parameter ("&bar", "&bar") -- ampersand
l_ctx.add_form_parameter ("&", "&") -- ampersand
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
assert ("form data well generated", l_ctx.form_parameters_to_x_www_form_url_encoded_string.same_string ("title=Eiffel+World!&path=foo%%2Fbar&unreserved=%%3A!%%40%%5B%%5D%%7B%%7D()*&reserved=%%2B%%3D%%3F%%26_%%23_&a%%3Db=a%%3Db&test=!%%24%%26'()*&lst%%5Ba%%5D%%5Bb%%5D=%%5B123%%5D%%5B456%%5D&pos%%7B1%%2C2%%7D=loc%%7Ba%%2Cb%%7D&%%3Ffoo=%%3Fbar&%%3F=%%3F&%%26bar=%%26bar&%%26=%%26"))
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, "") as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
if
attached sess.post ("", l_ctx, "") as res
then
check_response (res)
end
end
test_post_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- POST REQUEST WITH A FILE
-- check requestbin to ensure the form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_put_with_file
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_filename ("test.txt")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_put_with_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.set_upload_data ("name=This is a test for http client.%N")
create h.make_empty
if
attached sess.put (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- PUT REQUEST WITH A FILE
-- check requestbin to ensure the file is correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.set_upload_data ("name=This is a test for http client.%N")
if
attached sess.put ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_post_with_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
-- l_ctx.add_file_form_parameter ("image", "test.txt", "image/jpeg")
l_ctx.add_file_form_parameter ("text", "test.txt", "plain/text")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
-- sess := new_session ("http://localhost:9090")
create l_ctx.make
-- l_ctx.set_upload_filename ("logo.jpg")
l_ctx.set_upload_filename ("test.txt")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
test_post_with_multiple_file_and_form_data
local
sess: HTTP_CLIENT_SESSION
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.add_header ("Content-Type", "multipart/form-data")
l_ctx.add_file_form_parameter ("first_file", "test.txt", "plain/text")
l_ctx.add_file_form_parameter ("image", "logo.jpg", "image/jpeg")
l_ctx.add_form_parameter ("First", "Value")
l_ctx.add_form_parameter ("Second", "and last value")
l_ctx.add_file_form_parameter ("last_file", "test.txt", Void)
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_post_with_file_using_chunked_transfer_encoding
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
do
if attached global_requestbin_path as requestbin_path then
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
create h.make_empty
if
attached sess.post (requestbin_path, l_ctx, Void) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- POST REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.add_header ("Transfer-Encoding", "chunked")
l_ctx.set_upload_filename ("logo.jpg")
if
attached sess.post ("", l_ctx, Void) as res
then
check_response (res)
end
end
test_get_with_redirection
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
do
if attached global_requestbin_path as requestbin_path then
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
create h.make_empty
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
assert("was redirected", res.redirections_count > 0)
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
-- GET REQUEST, Forwarding (google's first answer is a forward)
-- check headers received (printed in console)
sess := new_session ("http://google.com")
if attached sess.get ("/", Void) as res then
check_response (res)
assert("was redirected", res.redirections_count > 0)
end
end
@@ -377,6 +311,7 @@ feature -- Factory
sess.set_credentials ("test", "test")
create ctx.make_with_credentials_required
if attached sess.get ("/password-ok.php", ctx) as res then
check_response (res)
if attached {READABLE_STRING_8} res.body as l_body then
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
else
@@ -388,50 +323,58 @@ feature -- Factory
test_get_with_query_parameters
local
sess: HTTP_CLIENT_SESSION
h: STRING_8
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
q: STRING
do
if attached global_requestbin_path as requestbin_path then
-- GET REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_session ("http://requestb.in")
create l_ctx.make
l_ctx.add_query_parameter ("?", "?first&arg")
l_ctx.add_query_parameter ("title", "Eiffel World!")
l_ctx.add_query_parameter ("path", "foo/bar")
l_ctx.add_query_parameter ("reserved", "+=&?")
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
l_ctx.add_query_parameter ("test", "!$&'()*")
l_ctx.add_query_parameter ("a&b", "a&b")
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
create q.make_empty
l_ctx.append_query_parameters_to_url (q)
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
-- GET REQUEST WITH A FILE AND FORM DATA
-- check requestbin to ensure the file and form parameters are correctly received
-- set filename to a local file
sess := new_web_session
create l_ctx.make
l_ctx.add_query_parameter ("?", "?first&arg")
l_ctx.add_query_parameter ("title", "Eiffel World!")
l_ctx.add_query_parameter ("path", "foo/bar")
l_ctx.add_query_parameter ("reserved", "+=&?")
l_ctx.add_query_parameter ("unreserved", ":!@'()*")
l_ctx.add_query_parameter ("unsafe", "%"[]{}")
l_ctx.add_query_parameter ("test", "!$&'()*")
l_ctx.add_query_parameter ("a&b", "a&b")
l_ctx.add_query_parameter ("lst[a][b]", "[abc][123]")
l_ctx.add_query_parameter ("foo(a,b)", "bar(1,2)*pi")
create q.make_empty
l_ctx.append_query_parameters_to_url (q)
assert("query", q.same_string ("??=?first%%26arg&title=Eiffel+World!&path=foo/bar&reserved=%%2B=%%26?&unreserved=:!@'()*&unsafe=%%22%%5B%%5D%%7B%%7D&test=!$%%26'()*&a%%26b=a%%26b&lst%%5Ba%%5D%%5Bb%%5D=%%5Babc%%5D%%5B123%%5D&foo(a,b)=bar(1,2)*pi"))
create h.make_empty
if
attached sess.get (requestbin_path, l_ctx) as res and then
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
else
assert ("Has requestbin path", False)
if
attached sess.get ("", l_ctx) as res
then
check_response (res)
end
end
feature {NONE} -- Implementation
check_response (res: HTTP_CLIENT_RESPONSE)
local
h: STRING
do
assert ("ok", not res.error_occurred)
create h.make_empty
if
attached res.headers as hds
then
across
hds as c
loop
h.append (c.item.name + ": " + c.item.value + "%R%N")
end
end
print (h)
if attached res.body as b then
print (b)
end
end
end

View File

@@ -7,117 +7,32 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<external_include location="$ECF_CONFIG_PATH/spec/include">
<condition>
<version type="compiler" min="16.9.9.9124"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH/spec/include_until_16_05">
<condition>
<version type="compiler" max="16.9.9.9124"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)/unstable/library/network/socket/netssl/spec/include">
<condition>
<platform excluded_value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_include>
<external_include location="$(ISE_LIBRARY)\unstable\library\network\socket\netssl\spec\include">
<condition>
<platform value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_include>
<external_cflag value="-D_WINSOCKAPI_">
<condition>
<platform value="windows"/>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</external_cflag>
<external_include location="$ECF_CONFIG_PATH/spec/include"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="openssl" location="$ISE_LIBRARY\unstable\library\network\openssl\openssl.ecf">
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" min="17.10.0.0"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<cluster name="network" location=".\src\">
<file_rule>
<exclude>/http_stream_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="17.02"/>
</condition>
</file_rule>
<cluster name="disabled_ssl_network" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="net_ssl_enabled" excluded_value="true"/>
<custom name="httpd_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl_network" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
<file_rule>
<exclude>/http_stream_secure_socket_ext.e$</exclude>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</file_rule>
</cluster>
</cluster>
<cluster name="network_until_16_05" location=".\src\until_16_05\">
<condition>
<version type="compiler" max="16.9.9.9124"/>
</condition>
<cluster name="ssl_network_until_16_05" location="$|ssl\" recursive="true">
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="net_ssl_enabled" value="true"/>
</condition>
<condition>
<version type="compiler" max="16.9.9.9124"/>
<custom name="httpd_ssl_enabled" value="true"/>
</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

@@ -1,52 +0,0 @@
/*
indexing
description: "Functions used by the EiffelWeb http networking classes. "
copyright: "Copyright (c) 2011-2016, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
*/
#ifndef _ew_network_h_
#define _ew_network_h_
#include "eif_config.h"
#ifdef EIF_WINDOWS
# ifndef _WINSOCKAPI_
# define FD_SETSIZE 256
# include <winsock2.h>
# include <Ws2tcpip.h>
# include <stdio.h>
# endif
#else /* unix-specific */
# include <sys/socket.h>
# include <unistd.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/* extern declarations ... */
#ifdef EIF_WINDOWS
extern int setsockopt(int, int, int, char*, int);
extern int recv(int, char *, int, int);
extern int send(int, char *, int, int);
#else
extern int setsockopt(int, int, int, const void*, socklen_t);
extern ssize_t recv(int, void *, size_t, int);
extern ssize_t send(int, const void *, size_t, int);
#endif
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -9,8 +9,6 @@ class
inherit
NETWORK_STREAM_SOCKET
HTTP_STREAM_SOCKET_EXT
create
make, make_empty,
make_client_by_port, make_client_by_address_and_port,

View File

@@ -1,21 +0,0 @@
note
description: "[
Extension to HTTPD_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, When there is no need to support older compilers.
]"
deferred class
HTTP_STREAM_SOCKET_EXT
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

@@ -55,12 +55,19 @@ feature -- Secure connection Helpers
end
set_secure_protocol_to_ssl_2_or_3
-- Set `ssl_protocol' with `Ssl_23'.
do
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
end
-- Set `ssl_protocol' with `Ssl_23'.
-- Protocol not supported anymore.
obsolete
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
local
err: DEVELOPER_EXCEPTION
do
create err
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
err.raise
end
set_secure_protocol_to_tls_1_0
set_secure_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
do
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
@@ -176,7 +183,14 @@ feature -- Output
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -1,174 +0,0 @@
note
description: "[
Until 16.05, the EiffelNet socket interface DOES NOT have
- make_server_by_address_and_port
- recv_timeout
- send_timeout.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTP_STREAM_SOCKET_EXT
inherit
PLATFORM
feature -- Initialization
make
deferred
end
make_server_by_address_and_port (a_address: INET_ADDRESS; a_port: INTEGER)
-- Create server socket on `a_address' and `a_port'.
require
valid_port: a_port >= 0
do
make
set_address (create {like address_type}.make_from_address_and_port (a_address, a_port))
bind
end
feature -- Basic operation
bind
deferred
end
feature -- Access
set_address (addr: detachable like address_type)
deferred
end
address_type: NETWORK_SOCKET_ADDRESS
deferred
end
descriptor: INTEGER
-- Socket descriptor of current socket
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)
-- Set the receive timeout in seconds on Current socket.
-- if `0' the related operations will never timeout.
require
positive_timeout: a_timeout_seconds >= 0
do
c_set_sock_recv_timeout (descriptor, level_sol_socket, a_timeout_seconds)
end
set_send_timeout (a_timeout_seconds: INTEGER)
-- Set the send timeout in milliseconds on Current socket.
-- if `0' the related operations will never timeout.
require
positive_timeout: a_timeout_seconds >= 0
do
c_set_sock_send_timeout (descriptor, level_sol_socket, a_timeout_seconds)
end
feature {NONE} -- Externals
level_sol_socket: INTEGER
-- SOL_SOCKET level of options
deferred
end
c_set_sock_recv_timeout (a_fd, a_level: INTEGER; a_timeout_seconds: INTEGER)
-- C routine to set socket option `SO_RCVTIMEO' with `a_timeout_seconds' seconds.
external
"C inline use %"ew_network.h%""
alias
"[
#ifdef SO_RCVTIMEO
int flag = SO_RCVTIMEO;
#else
int flag = 0x1006;
#endif
#ifdef EIF_WINDOWS
int arg = (int) 1000 * $a_timeout_seconds; /* Timeout in milliseconds */
setsockopt((int) $a_fd, (int) $a_level, flag, (char *) &arg, sizeof(arg));
#else
struct timeval tv;
tv.tv_sec = $a_timeout_seconds; /* Timeout in seconds */
tv.tv_usec = 0;
setsockopt((int) $a_fd, (int) $a_level, flag, (struct timeval *)&tv, sizeof(struct timeval));
#endif
]"
end
c_set_sock_send_timeout (a_fd, a_level: INTEGER; a_timeout_seconds: INTEGER)
-- C routine to set socket option `SO_SNDTIMEO' with `a_timeout_seconds' seconds.
external
"C inline use %"ew_network.h%""
alias
"[
#ifdef SO_RCVTIMEO
int flag = SO_SNDTIMEO;
#else
int flag = 0x1005;
#endif
#ifdef EIF_WINDOWS
int arg = (int) 1000 * $a_timeout_seconds; /* Timeout in milliseconds */
setsockopt((int) $a_fd, (int) $a_level, flag, (char *) &arg, sizeof(arg));
#else
struct timeval tv;
tv.tv_sec = $a_timeout_seconds; /* Timeout in seconds */
tv.tv_usec = 0;
setsockopt((int) $a_fd, (int) $a_level, flag, (struct timeval *)&tv, sizeof(struct timeval));
#endif
]"
end
feature {NONE} -- No-Exception network operation
c_recv_noexception (a_fd: INTEGER; buf: POINTER; len: INTEGER; flags: INTEGER): INTEGER
-- External routine to read a `len' number of characters
-- into buffer `buf' from socket `a_fd' with options `flags'.
external
"C inline use %"ew_network.h%""
alias
"[
recv((int) $a_fd, (char *) $buf, (int) $len, (int) $flags)
]"
end
c_read_stream_noexception (a_fd: INTEGER; len: INTEGER; buf: POINTER): INTEGER
-- External routine to read a `len' number of characters
-- into buffer `buf' from socket `a_fd'.
do
Result := c_recv_noexception (a_fd, buf, len, 0)
end
c_put_stream_noexception (a_fd: INTEGER; buf: POINTER; len: INTEGER): INTEGER
-- External routine to write stream pointed by `s' of
-- length `length' to socket `fd'.
-- Note: does not raise exception on error, but return error value as Result.
external
"C inline use %"ew_network.h%""
alias
"[
send((int) $a_fd, (char *) $buf, (int) $len, (int) 0)
]"
end
end

View File

@@ -1,41 +0,0 @@
note
description: "[
Extension to HTTP_STREAM_SOCKET to support backward compatibility.
TO BE REMOVED IN THE FUTURE, WHEN 16.05 IS OLD.
]"
deferred class
HTTP_STREAM_SECURE_SOCKET_EXT
feature {NONE} -- SSL bridge
ssl_write (a_ssl: SSL; a_pointer: POINTER; a_byte_count: INTEGER): INTEGER
do
-- In delivery until 16.05
-- SSL.write does not return any value!
-- So let's use `c_ssl_write' from Current class
-- instead of:
-- a_ssl.write (a_pointer, a_byte_count)
Result := c_ssl_write (a_ssl.ptr, a_pointer, a_byte_count)
if a_ssl.was_error then
-- Until 16.05, there is no error check for `SSL.write'
-- so nothing can be done here.
if Result >= 0 then
Result := -1
end
end
end
c_ssl_write (an_ssl_ptr: POINTER; buffer: POINTER; nb_bytes: INTEGER_32): INTEGER_32
-- External call to SSL_write
-- (export status {NONE})
external
"C use %"eif_openssl.h%""
alias
"SSL_write"
end
end

View File

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

@@ -1,14 +1,14 @@
package content_negotiation
project
conneg = "conneg-safe.ecf"
conneg = "conneg.ecf"
note
title: CONneg Content Negotiation
description: "[
CONneg is a library that provides utilities to select the best repesentation of a resource for a client where there are multiple representations available.
]"
CONneg is a library that provides utilities to select the best repesentation of a resource for a client where there are multiple representations available.
]"
collection: EWF
tags: content,accept,conneg,negotiation,EWF,web,request
copyright: 2011-2016, Javier Velilla, Jocelyn Fiat, Eiffel Software and others

View File

@@ -1,20 +1,20 @@
package http
project
http = "http-safe.ecf"
http = "http.ecf"
note
title: HTTP protocol
description: "[
Collection of interfaces related to HTTP protocol:
- header
- status codes, request methods
- content type, media type, mime type.
- cookie
- date used in web protocol
- file extension mime mapping
]"
Collection of interfaces related to HTTP protocol:
- header
- status codes, request methods
- content type, media type, mime type.
- cookie
- date used in web protocol
- file extension mime mapping
]"
collection: EWF
tags: http,web,header,status,method,type,mime,cookie
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)

View File

@@ -1,25 +1,24 @@
package notification_email
project
notification_email = "notification_email-safe.ecf"
notification_email = "notification_email.ecf"
note
title: Notification Email
description: "[
Abstract interface to send message via various mailers:
- smtp
- sendmail
- external script
- store on local file
- ...
]"
Abstract interface to send message via various mailers:
- smtp
- sendmail
- external script
- store on local file
- ...
]"
collection: EWF
tags: message,smtp,sendmail,mailer
copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, 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/runtime/process/notification_email
end

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,14 @@
<?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>
<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,36 @@
note
description: "Summary description for {JWT_MISMATCHED_ALG_ERROR}."
date: "$Date$"
revision: "$Revision$"
class
JWT_MISMATCHED_ALG_ERROR
inherit
JWT_ERROR
create
make
feature {NONE} -- Initialization
make (a_alg, a_header_alg: READABLE_STRING_8)
do
alg := a_alg
header_alg := a_header_alg
end
feature -- Access
alg: READABLE_STRING_8
header_alg: READABLE_STRING_8
id: STRING = "ALG_MISMATCH"
message: READABLE_STRING_8
do
Result := "Header alg [" + header_alg + "] does not match given alg [" + alg + "]!"
end
end

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,152 @@
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)
else
Result := a_issuer = Void
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)
else
Result := a_audience = Void
end
end
feature -- Conversion
encoded_string (a_secret: READABLE_STRING_8): STRING
deferred
end
feature -- status report
has_error: BOOLEAN
do
Result := attached errors as errs and then not errs.is_empty
end
has_unsupported_alg_error: BOOLEAN
do
Result := attached errors as errs and then across errs as ic some attached {JWT_UNSUPPORTED_ALG_ERROR} ic.item end
end
has_unverified_token_error: BOOLEAN
do
Result := attached errors as errs and then across errs as ic some attached {JWT_UNVERIFIED_TOKEN_ERROR} ic.item end
end
has_invalid_token_error: BOOLEAN
do
Result := attached errors as errs and then across errs as ic some attached {JWT_INVALID_TOKEN_ERROR} ic.item end
end
errors: detachable ARRAYED_LIST [JWT_ERROR]
feature {JWT_UTILITIES} -- Error reporting
reset_error
do
errors := Void
end
report_error (err: JWT_ERROR)
local
l_errors: like errors
do
l_errors := errors
if l_errors = Void then
create l_errors.make (1)
errors := l_errors
end
l_errors.extend (err)
end
report_mismatched_alg_error (alg, a_header_alg: READABLE_STRING_8)
do
report_error (create {JWT_MISMATCHED_ALG_ERROR}.make (alg, a_header_alg))
end
report_unsupported_alg_error (alg: READABLE_STRING_8)
do
report_error (create {JWT_UNSUPPORTED_ALG_ERROR}.make (alg))
end
report_unverified_token_error
do
report_error (create {JWT_UNVERIFIED_TOKEN_ERROR})
end
report_invalid_token
do
report_error (create {JWT_INVALID_TOKEN_ERROR})
end
report_claim_validation_error (a_claimname: READABLE_STRING_8)
do
report_error (create {JWT_CLAIM_VALIDATION_ERROR}.make (a_claimname))
end
invariant
end

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

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,108 @@
note
description: "Loader and verifier to JWT token."
date: "$Date$"
revision: "$Revision$"
EIS: "name=Known Critical vulnerabilities in JWT libs", "protocol=URI", "src=https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/"
class
JWT_LOADER
inherit
JWT_UTILITIES
feature -- Access
token (a_token_input: READABLE_STRING_8; a_alg: detachable READABLE_STRING_8; a_verification_key: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
-- Decoded token from `a_token_input` given the verification key `a_verification_key` and optional (but recommended) signature algorithm `a_alg`, and optional context `ctx`
-- used to specify eventual issuer and various parameters.
-- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens,
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
require
a_valid_alg: a_alg /= Void implies is_supporting_signature_algorithm (a_alg)
local
jws: JWS
i,j,n: INTEGER
alg, l_enc_payload, l_enc_header, l_signature: READABLE_STRING_8
do
n := a_token_input.count
i := a_token_input.index_of ('.', 1)
if i > 0 then
j := a_token_input.index_of ('.', i + 1)
if j > 0 then
l_enc_header := a_token_input.substring (1, i - 1)
l_enc_payload := a_token_input.substring (i + 1, j - 1)
l_signature := a_token_input.substring (j + 1, n)
create jws.make_with_json_payload (base64url_decode (l_enc_payload))
alg := signature_algorithm_from_encoded_header (l_enc_header)
if a_alg /= Void then
if alg /= Void and then not alg.is_case_insensitive_equal_general (a_alg) then
jws.report_mismatched_alg_error (a_alg, alg)
else
alg := a_alg
end
else
if alg = Void then
-- Use default
alg := alg_hs256
end
end
jws.set_algorithm (alg)
check alg_set: alg /= Void end
if ctx = Void or else not ctx.validation_ignored then
if not is_supporting_signature_algorithm (alg) then
jws.report_unsupported_alg_error (alg)
alg := alg_hs256
end
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg)) then
jws.report_unverified_token_error
end
if
not jws.has_error and then
ctx /= Void
then
check not ctx.validation_ignored end
if jws.is_expired (ctx.time) then
jws.report_claim_validation_error ("exp")
end
if not jws.is_nbf_validated (ctx.time) then
jws.report_claim_validation_error ("nbf")
end
if
not jws.is_iss_validated (ctx.issuer)
then
jws.report_claim_validation_error ("iss")
end
if
not jws.is_aud_validated (ctx.audience)
then
jws.report_claim_validation_error ("aud")
end
end
end
else
-- jws.report_invalid_token
end
else
-- jws.report_invalid_token
end
Result := jws
end
feature {NONE} -- Implementation
signature_algorithm_from_encoded_header (a_enc_header: READABLE_STRING_8): detachable STRING_8
local
jp: JSON_PARSER
do
create jp.make_with_string (base64url_decode (a_enc_header))
jp.parse_content
if
attached jp.parsed_json_object as jo and then
attached {JSON_STRING} jo.item ("alg") as j_alg
then
Result := j_alg.unescaped_string_8
end
end
end

View File

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

View File

@@ -0,0 +1,224 @@
note
description: "Summary description for {TEST_JWT}."
date: "$Date$"
revision: "$Revision$"
class
TEST_JWT
inherit
EQA_TEST_SET
SHARED_EXECUTION_ENVIRONMENT
undefine
default_create
end
feature -- Test
test_jwt_io
local
jwt: JWS
ut: JWT_UTILITIES
do
create jwt
jwt.set_algorithm ("HS256")
jwt.claimset.set_subject ("1234567890")
jwt.claimset.set_claim ("name", "John Doe")
jwt.claimset.set_claim ("admin", True)
create ut
assert ("header", ut.base64url_encode (jwt.header.string).same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"))
assert ("payload", ut.base64url_encode (jwt.claimset.string).same_string ("eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9"))
assert ("signature", jwt.encoded_string ("secret").same_string ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.pcHcZspUvuiqIPVB_i_qmcvCJv63KLUgIAKIlXI1gY8"))
end
test_jwt
local
jwt: JWS
jwt_loader: JWT_LOADER
payload: STRING
tok: STRING
do
payload := "[
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
]"
-- payload := "[
-- {"sub":"1234567890","name":"John Doe","admin":true}
-- ]"
create jwt.make_with_json_payload (payload)
jwt.set_algorithm ("HS256")
tok := jwt.encoded_string ("secret")
create jwt_loader
-- Use header alg!
if attached jwt_loader.token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
-- Use given alg!
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
test_jwt_with_claimset
local
jwt: JWS
jwt_loader: JWT_LOADER
payload: STRING
tok: STRING
now, dt: DATE_TIME
ctx: JWT_CONTEXT
do
-- payload := "[
-- {"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
-- ]"
payload := "[
{"sub":"1234567890","name":"John Doe","admin":true}
]"
create jwt.make_with_json_payload (payload)
jwt.set_algorithm ("HS256")
create now.make_now_utc
jwt.claimset.set_issued_at (now)
dt := duplicated_time (now)
dt.minute_add (60)
jwt.claimset.set_expiration_time (dt)
jwt.claimset.set_issuer ("urn:foo")
jwt.claimset.set_audience ("urn:foo")
tok := jwt.encoded_string ("secret")
payload := jwt.claimset.string
create jwt_loader
-- Test with validation + exp
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
create ctx
ctx.set_time (now)
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
dt := duplicated_time (now)
dt.hour_add (5)
ctx.set_time (dt)
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("exp error", l_tok.has_error)
end
-- Test with validation + not before
dt := duplicated_time (now)
dt.second_add (30)
jwt.claimset.set_not_before_time (dt)
tok := jwt.encoded_string ("secret")
ctx.set_time (now)
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has nbf error", l_tok.has_error)
end
dt := duplicated_time (now)
dt.second_add (15)
ctx.set_time (dt)
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has nbf error", l_tok.has_error)
end
dt := duplicated_time (now)
dt.minute_add (45)
ctx.set_time (dt)
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
-- Test Issuer
ctx.set_issuer ("urn:foobar")
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has iss error", l_tok.has_error)
end
ctx.set_issuer ("urn:foo")
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
-- Test Audience
ctx.set_audience ("urn:foobar")
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has aud error", l_tok.has_error)
end
ctx.set_audience ("urn:foo")
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
end
test_mismatched_alg_jwt
local
jwt: JWS
payload: STRING
tok: STRING
do
payload := "[
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
]"
create jwt.make_with_json_payload (payload)
jwt.set_algorithm ("none")
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
test_unsecured_jwt
local
jwt: JWS
payload: STRING
tok: STRING
do
payload := "[
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
]"
create jwt.make_with_json_payload (payload)
jwt.set_algorithm ("none")
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
feature -- Implementation
duplicated_time (dt: DATE_TIME): DATE_TIME
do
Result := dt.deep_twin
end
end

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

@@ -2,13 +2,11 @@ package openid
project
openid = "consumer/openid.ecf"
openid = "consumer/openid-safe.ecf"
note
title: Eiffel OpenID
description: OpenID consumer library
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
@@ -16,4 +14,3 @@ note
link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/tree/master/library/security/http_authorization/README.md
end

View File

@@ -1,14 +1,14 @@
package http_authorization
project
http_authorization = "http_authorization-safe.ecf"
http_authorization = "http_authorization.ecf"
note
title: HTTP Authorization
description: "[
Class to manipulate HTTP 'Authorization' header value.
]"
Class to manipulate HTTP 'Authorization' header value.
]"
collection: EWF
tags: http,authorization,authentication,web
copyright: 2011-2016, Jocelyn Fiat, Eiffel Software and others
@@ -17,4 +17,3 @@ note
link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/server/authentication/http_authorization
end

View File

@@ -76,13 +76,13 @@ feature -- Initialization
a_http_authorization /= Void implies http_authorization /= Void
end
make_basic_auth (u: READABLE_STRING_32; p: READABLE_STRING_32)
make_basic_auth (u: READABLE_STRING_GENERAL; p: READABLE_STRING_GENERAL)
-- Create a Basic authentication.
do
make_custom_auth (u, p, Basic_auth_type)
end
make_custom_auth (u: READABLE_STRING_32; p: READABLE_STRING_32; a_type: READABLE_STRING_8)
make_custom_auth (u: READABLE_STRING_GENERAL; p: READABLE_STRING_GENERAL; a_type: READABLE_STRING_8)
-- Create a custom `a_type' authentication.
require
a_type_accepted: a_type.is_case_insensitive_equal (Basic_auth_type)
@@ -90,15 +90,20 @@ feature -- Initialization
local
t: STRING_8
utf: UTF_CONVERTER
s: STRING_32
do
login := u
password := p
create login.make_from_string_general (u)
create password.make_from_string_general (p)
create t.make_from_string (a_type)
t.left_adjust; t.right_adjust
type := t
if t.is_case_insensitive_equal (Basic_auth_type) then
type := Basic_auth_type
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (utf.string_32_to_utf_8_string_8 (u + {STRING_32} ":" + p)))
create s.make_from_string_general (u)
s.extend (':')
s.append_string_general (p)
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (utf.string_32_to_utf_8_string_8 (s)))
elseif t.is_case_insensitive_equal (Digest_auth_type) then
type := Digest_auth_type
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
@@ -115,9 +120,9 @@ feature -- Access
type: READABLE_STRING_8
login: detachable READABLE_STRING_32
login: detachable IMMUTABLE_STRING_32
password: detachable READABLE_STRING_32
password: detachable IMMUTABLE_STRING_32
feature -- Status report

View File

@@ -1,24 +1,20 @@
package ewsgi
project
ewsgi = "ewsgi-safe.ecf"
ewsgi = "ewsgi.ecf"
connector_cgi = "connectors/cgi/cgi-safe.ecf"
connector_cgi = "connectors/cgi/cgi.ecf"
connector_libfcgi = "connectors/libfcgi/libfcgi-safe.ecf"
connector_libfcgi = "connectors/libfcgi/libfcgi.ecf"
connector_null = "connectors/null/null-safe.ecf"
connector_null = "connectors/null/null.ecf"
connector_standalone = "connectors/standalone/standalone-safe.ecf"
connector_standalone = "connectors/standalone/standalone.ecf"
httpd = "connectors/standalone/src/httpd/httpd-safe.ecf"
httpd = "connectors/standalone/src/httpd/httpd.ecf"
ewsgi_spec = "ewsgi_spec.ecf"
connector_nino = "connectors/nino/nino.ecf"
note
title: EWSGI
description: "[
Eiffel Web Server Gateway Interface (EWSGI) specification, and a few connectors.
]"
Eiffel Web Server Gateway Interface (EWSGI) specification, and a few connectors.
]"
collection: EWF
tags: ewsgi,cgi,web,httpd,ewf
copyright: 2011-2016, Jocelyn Fiat, Eiffel Software and others
@@ -26,5 +22,5 @@ note
link[license]: http://www.eiffel.com/licensing/forum.txt
link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/server/ewsgi
link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/tree/master/library/server/ewsgi/doc
end
end

View File

@@ -167,7 +167,7 @@ feature -- Element change
end
set_socket_timeout (a_nb_seconds: like socket_timeout)
-- Set `socket_timeout' with `a_nb_seconds'
-- Set `socket_timeout' with `a_nb_seconds'.
do
socket_timeout := a_nb_seconds
ensure
@@ -175,7 +175,7 @@ feature -- Element change
end
set_socket_recv_timeout (a_nb_seconds: like socket_recv_timeout)
-- Set `socket_recv_timeout' with `a_nb_seconds'
-- Set `socket_recv_timeout' with `a_nb_seconds'.
do
socket_recv_timeout := a_nb_seconds
ensure
@@ -183,7 +183,7 @@ feature -- Element change
end
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
-- Set `keep_alive_timeout' with `a_seconds'
-- Set `keep_alive_timeout' with `a_seconds'.
do
keep_alive_timeout := a_seconds
ensure
@@ -191,7 +191,7 @@ feature -- Element change
end
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
-- Set `max_keep_alive_requests' with `nb'
-- Set `max_keep_alive_requests' with `nb'.
do
max_keep_alive_requests := nb
ensure
@@ -254,7 +254,7 @@ feature -- Element change
end
mark_secure
-- Set is_secure in True
-- Set is_secure in True.
do
set_is_secure (True)
ensure
@@ -287,7 +287,7 @@ feature -- Element change
end
set_secure_protocol (a_version: NATURAL)
-- Set `secure_protocol' with `a_version'
-- Set `secure_protocol' with `a_version'.
do
secure_protocol := a_version
ensure
@@ -295,18 +295,19 @@ feature -- Element change
end
set_secure_protocol_from_string (a_ssl_version: READABLE_STRING_GENERAL)
-- Set `secure_protocol' with `a_ssl_version'
-- Set `secure_protocol' with `a_ssl_version'.
do
if a_ssl_version.is_case_insensitive_equal ("ssl_2_3") then
set_secure_protocol_to_ssl_2_or_3
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_0") then
set_secure_protocol_to_tls_1_0
if a_ssl_version.is_case_insensitive_equal ("tls_1_2") then
set_secure_protocol_to_tls_1_2
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_1") then
set_secure_protocol_to_tls_1_1
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_2") then
set_secure_protocol_to_tls_1_2
elseif a_ssl_version.is_case_insensitive_equal ("tls_1_0") then
set_secure_protocol_to_tls_1_0
elseif a_ssl_version.is_case_insensitive_equal ("dtls_1_0") then
set_secure_protocol_to_dtls_1_0
elseif a_ssl_version.is_case_insensitive_equal ("ssl_2_3") then
-- Obsolete!
set_secure_protocol_to_ssl_2_or_3
else -- Default
set_secure_protocol_to_tls_1_2
end
@@ -316,6 +317,8 @@ feature -- SSL Helpers
set_secure_protocol_to_ssl_2_or_3
-- Set `secure_protocol' with `Ssl_23'.
obsolete
"Use `set_secure_protocol_to_tls_1_2` [2017-06-23]."
deferred
end

View File

@@ -7,8 +7,6 @@
<exclude>/\.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http_network" location="..\..\network\http_network\http_network.ecf" readonly="false"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
@@ -16,8 +14,11 @@
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="openssl" location="$ISE_LIBRARY\unstable\library\network\openssl\openssl.ecf">
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
<version type="compiler" min="17.10.0.0"/>
<custom name="ssl_enabled" value="true"/>
</condition>
</library>
<library name="thread" location="$ISE_LIBRARY\library\thread\thread.ecf">
@@ -35,16 +36,12 @@
<cluster name="no_ssl" location="$|no_ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" excluded_value="true"/>
<custom name="httpd_ssl_enabled" excluded_value="true"/>
</condition>
</cluster>
<cluster name="ssl" location="$|ssl\" recursive="true">
<condition>
<custom name="ssl_enabled" value="true"/>
</condition>
<condition>
<custom name="httpd_ssl_enabled" value="true"/>
</condition>
</cluster>
<cluster name="concurrency_none" location="$|concurrency\none\" recursive="true">
<condition>

View File

@@ -1,14 +1,14 @@
package httpd
project
httpd = "httpd-safe.ecf"
httpd = "httpd.ecf"
note
title: HTTP server
description: "[
Simple HTTP listener and handler, that can be extended easily.
]"
Simple HTTP listener and handler, that can be extended easily.
]"
tags: http,httpd,server,web
collection: EWF
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others

View File

@@ -36,9 +36,16 @@ feature -- Access
feature -- SSL Helpers
set_secure_protocol_to_ssl_2_or_3
-- Set `secure_protocol' with `Ssl_23'.
-- Set `ssl_protocol' with `Ssl_23'.
-- Protocol not supported anymore.
obsolete
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
local
err: DEVELOPER_EXCEPTION
do
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
create err
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
err.raise
end
set_secure_protocol_to_tls_1_0
@@ -67,7 +74,7 @@ feature -- SSL Helpers
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -4,16 +4,16 @@ setup
compile_library = Clib
project
libfcgi = "libfcgi-safe.ecf"
libfcgi = "libfcgi.ecf"
note
title: Eiffel libfcgi wrapper
description: "[
Wrapper on modified libfcgi.
(modification: added 64 bits support)
It brings implementation for FCGI protocol.
]"
Wrapper on modified libfcgi.
(modification: added 64 bits support)
It brings implementation for FCGI protocol.
]"
collection: EWF
tags: fcgi,libfcgi,cgi,http,web,ewf
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others

View File

@@ -1,40 +1,32 @@
package wsf
project
wsf = "wsf-safe.ecf"
wsf = "wsf.ecf"
wsf_extension = "wsf_extension-safe.ecf"
wsf_extension = "wsf_extension.ecf"
wsf_policy_driven = "wsf_policy_driven-safe.ecf"
wsf_policy_driven = "wsf_policy_driven.ecf"
wsf_router_context = "wsf_router_context-safe.ecf"
wsf_router_context = "wsf_router_context.ecf"
wsf_session = "wsf_session-safe.ecf"
wsf_session = "wsf_session.ecf"
wsf_cgi = "connector/cgi-safe.ecf"
wsf_cgi = "connector/cgi.ecf"
wsf_libfcgi = "connector/libfcgi-safe.ecf"
wsf_libfcgi = "connector/libfcgi.ecf"
wsf_openshift = "connector/openshift-safe.ecf"
default_cgi = "default/cgi-safe.ecf"
default_cgi = "default/cgi.ecf"
default_libfcgi = "default/libfcgi-safe.ecf"
default_libfcgi = "default/libfcgi.ecf"
default_openshift = "default/openshift-safe.ecf"
wsf_standalone = "connector/standalone-safe.ecf"
default_openshift = "default/openshift.ecf"
wsf_standalone = "connector/standalone.ecf"
default_standalone = "default/standalone-safe.ecf"
default_standalone = "default/standalone.ecf"
wsf_all = "connector/all.ecf"
wsf_nino = "connector/nino.ecf"
wsf_openshift = "connector/openshift.ecf"
wsf_standalone_websocket = "connector/standalone_websocket.ecf"
default_nino = "default/nino.ecf"
note
title: Web Server Foundation
description: "[
Core of the Eiffel Web Framework (EWF).
Provide the request, response, router, ... interfaces.
The foundation to build web server application.
]"
Core of the Eiffel Web Framework (EWF).
Provide the request, response, router, ... interfaces.
The foundation to build web server application.
]"
tags: ewf,server,httpd,request,connector,web
collection: EWF
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others
@@ -44,4 +36,3 @@ note
link[doc]: "Documentation" http://eiffelwebframework.github.io/EWF/
end

View File

@@ -57,7 +57,8 @@ feature -- Query
-- if possible
do
if attached value as v then
Result := generating_type.name_32
-- FIXME: in the future, use the new `{TYPE}.name_32`
Result := generating_type.name.to_string_32
else
Result := {STRING_32} "Void"
end

View File

@@ -22,7 +22,7 @@ feature {NONE} -- Initialization
feature -- Access
default_value: detachable READABLE_STRING_GENERAL
default_value: detachable READABLE_STRING_32
rows: INTEGER
@@ -54,9 +54,13 @@ feature -- Element change
end
end
set_default_value (v: like default_value)
set_default_value (v: detachable READABLE_STRING_GENERAL)
do
default_value := v
if v = Void then
default_value := Void
else
default_value := v.as_string_32
end
end
feature -- Conversion

View File

@@ -1,8 +1,7 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
description : "Objects containing widget WSF_WIDGET objects, and add specific form support (notion of form fields)."
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_FORM_COMPOSITE

View File

@@ -48,7 +48,9 @@ feature -- Access
item (a_name: READABLE_STRING_GENERAL): detachable WSF_VALUE
do
Result := items.item (a_name.as_string_8)
if a_name.is_valid_as_string_8 then
Result := items.item (a_name.to_string_8)
end
end
string_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32

View File

@@ -39,7 +39,7 @@ feature -- Element Change
set_max (a_val: INTEGER)
-- Set `max' with `a_val'.
do
set_max_string(a_val.out)
set_max_string (a_val.out)
ensure
max_set: attached max as l_max implies l_max.same_string (a_val.out)
end
@@ -94,7 +94,6 @@ feature -- Element Change
step_set: attached step as l_step implies l_step.same_string_general (a_val)
end
feature {NONE} -- Conversion
append_numeric_input_attributes_to (a_target: STRING)
@@ -103,7 +102,7 @@ feature {NONE} -- Conversion
--min
if attached min as l_min then
a_target.append (" min=%"")
a_target.append(l_min)
a_target.append (l_min)
a_target.append_character ('%"')
end

View File

@@ -19,7 +19,7 @@ feature {NONE} -- Initialization
name := a_name
end
make_with_text (a_name: like name; a_text: READABLE_STRING_32)
make_with_text (a_name: like name; a_text: READABLE_STRING_GENERAL)
do
make (a_name)
set_text_value (a_text)
@@ -44,7 +44,7 @@ feature -- Access
feature -- Element change
set_text_value (s: detachable READABLE_STRING_32)
set_text_value (s: detachable READABLE_STRING_GENERAL)
do
set_default_value (s)
end
@@ -73,9 +73,13 @@ feature -- Element change
end
end
set_default_value (v: like default_value)
set_default_value (v: detachable READABLE_STRING_GENERAL)
do
default_value := v
if v = Void then
default_value := Void
else
default_value := v.as_string_32
end
end
feature -- Conversion

View File

@@ -35,12 +35,17 @@ feature -- Access
feature -- Change element
set_placeholder (a_placeholder: READABLE_STRING_32)
set_placeholder (a_placeholder: detachable READABLE_STRING_GENERAL)
-- Set `placeholder' with `a_placeholder'.
do
placeholder := a_placeholder
if a_placeholder = Void then
placeholder := Void
else
placeholder := a_placeholder.as_string_32
end
ensure
placeholder_set: attached placeholder as l_placeholder implies l_placeholder = a_placeholder
placeholder_set: (a_placeholder = Void implies placeholder = Void)
or (a_placeholder /= Void implies (attached placeholder as l_placeholder and then a_placeholder.same_string (l_placeholder)))
end
enable_autofocus
@@ -91,16 +96,21 @@ feature -- Change element
required_flase: not required
end
set_pattern (a_pattern: READABLE_STRING_32)
set_pattern (a_pattern: READABLE_STRING_GENERAL)
-- Set `pattern' with `a_pattern'.
-- Example:[0-9][A-Z]{3}
-- Check HTML5 patterns site.
note
EIS: "name=HTML5 Patterns", "src=http://html5pattern.com/"
do
pattern := a_pattern
if a_pattern = Void then
pattern := Void
else
pattern := a_pattern.as_string_32
end
ensure
pattern_set: attached pattern as l_pattern implies l_pattern = a_pattern
pattern_set: (a_pattern = Void implies pattern = Void) or
a_pattern /= Void implies attached pattern as l_pattern and then a_pattern.same_string (l_pattern)
end

View File

@@ -43,7 +43,6 @@ feature -- Access
feature -- Element Change
set_formnovalidate
-- Set formnovalidate to True.
do
@@ -60,49 +59,69 @@ feature -- Element Change
formnovalidate_false: not formnovalidate
end
set_formaction (a_action: READABLE_STRING_GENERAL)
set_formaction (a_action: detachable READABLE_STRING_GENERAL)
-- Set `formaction' with `a_action'.
-- Example:<input type="submit" value="Submit" formaction="/users">
require
is_valid_as_string_8: a_action.is_valid_as_string_8
is_valid_as_string_8: a_action /= Void implies a_action.is_valid_as_string_8
do
formaction := a_action.to_string_8
if a_action = Void then
formaction := Void
else
formaction := a_action.to_string_8
end
ensure
formaction_set: attached formaction as l_action implies l_action = a_action
formaction_set: (a_action = Void implies formaction = Void)
or (a_action /= Void implies (attached formaction as l_action and then a_action.same_string (l_action)))
end
set_formenctype (a_enctype: READABLE_STRING_GENERAL)
set_formenctype (a_enctype: detachable READABLE_STRING_GENERAL)
-- Set `formenctype' with `a_enctype'.
-- Example: <input type="submit" value="Submit" formenctype="application/x-www-form-urlencoded">
require
is_valid_as_string_8: a_enctype.is_valid_as_string_8
is_valid_as_string_8: a_enctype /= Void implies a_enctype.is_valid_as_string_8
do
formenctype := a_enctype.to_string_8
if a_enctype = Void then
formenctype := Void
else
formenctype := a_enctype.to_string_8
end
ensure
formenctype_set: attached formenctype as l_enctype implies l_enctype = a_enctype
formenctype_set: (a_enctype = Void implies formenctype = Void)
or (a_enctype /= Void implies (attached formenctype as l_enctype and then a_enctype.same_string (l_enctype)))
end
set_formmethod (a_method: READABLE_STRING_GENERAL)
set_formmethod (a_method: detachable READABLE_STRING_GENERAL)
-- Set `formmethod' with `a_method'.
-- Example: <input type="submit" value="Submit" formmethod="POST">
--! require is_valid_method: [PUT, POST, DELETE, GET, ...]
require
is_valid_as_string_8: a_method.is_valid_as_string_8
is_valid_as_string_8: a_method /= Void implies a_method.is_valid_as_string_8
do
formmethod := a_method.to_string_8
if a_method = Void then
formmethod := Void
else
formmethod := a_method.to_string_8
end
ensure
formmethod_set: attached formmethod as l_method implies l_method = a_method
formmethod_set: (a_method = Void implies formmethod = Void)
or (a_method /= Void implies (attached formmethod as l_method and then a_method.same_string (l_method)))
end
set_formtarget (a_target: READABLE_STRING_GENERAL)
set_formtarget (a_target: detachable READABLE_STRING_GENERAL)
-- Set `formtarget' with `a_target'.
-- Example: <input type="submit" value="Submit" formtarget="_self">
require
is_valid_as_string_8: a_target.is_valid_as_string_8
is_valid_as_string_8: a_target /= Void implies a_target.is_valid_as_string_8
do
formtarget := a_target.to_string_8
if a_target = Void then
formtarget := Void
else
formtarget := a_target.to_string_8
end
ensure
formtarget_set: attached formtarget as l_target implies l_target = a_target
formtarget_set: (a_target = Void implies formtarget = Void)
or (a_target /= Void implies (attached formtarget as l_target and then a_target.same_string (l_target)))
end

View File

@@ -1,8 +1,7 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
description : "Objects containing WSF_WIDGET objects."
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_WIDGET_COMPOSITE

View File

@@ -1,19 +1,19 @@
package encoder
project
encoder = "encoder-safe.ecf"
encoder = "encoder.ecf"
note
title: Text encoders
description: "[
Text encoders used in web technologies:
- HTML encoder
- XML encoder
- JSON encoder
- UTF8
- BASE64
]"
Text encoders used in web technologies:
- HTML encoder
- XML encoder
- JSON encoder
- UTF8
- BASE64
]"
collection: EWF
tags: html,xml,percent encoding,web,json,utf
copyright: 2011-2016, Jocelyn Fiat, Eiffel Software and others
@@ -23,4 +23,3 @@ note
link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/blob/master/library/text/encoder/README.md
end

View File

@@ -1,15 +1,15 @@
package feed
project
feed = "feed-safe.ecf"
feed = "feed.ecf"
note
title: Eiffel FEED parser
description: "[
RSS2.0 and ATOM feed parser.
Feed visitor including HTML generation from FEED Eiffel objects.
]"
RSS2.0 and ATOM feed parser.
Feed visitor including HTML generation from FEED Eiffel objects.
]"
tags: rss,atom,feed,html,generator,parser
copyright: 2011-2016, Jocelyn Fiat, Eiffel Software and others
license: Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)

View File

@@ -1,17 +1,17 @@
package uri_template
project
uri_template = "uri_template-safe.ecf"
uri_template = "uri_template.ecf"
note
title: URI Template
description: "[
Implement URI Template as described at http://tools.ietf.org/rfc/rfc6570.txt
Implement URI Template as described at http://tools.ietf.org/rfc/rfc6570.txt
Support for URI template string expansion
But also partial URI Template matching
]"
Support for URI template string expansion
But also partial URI Template matching
]"
collection: EWF
tags: uri template,router,generator,url
copyright: 2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others
@@ -21,4 +21,3 @@ note
link[doc]: "Documentation" https://github.com/EiffelWebFramework/EWF/tree/master/library/text/parser/uri_template/README.md
end

View File

@@ -1,21 +1,20 @@
package error
project
error = "error-safe.ecf"
error = "error.ecf"
note
title: Error framework
description: "[
Errors and associated handler, to manage errors and also provides a way to synchronize one or many error handlers.
This is convenient to propagate error from a layer to another without adding unwanted dependencies.
]"
Errors and associated handler, to manage errors and also provides a way to synchronize one or many error handlers.
This is convenient to propagate error from a layer to another without adding unwanted dependencies.
]"
collection: EWF
tags: error,framework
license: Eiffel Forum License v2
copyright: Jocelyn Fiat, Eiffel Software and others.
link[license]: http://www.eiffel.com/licensing/forum.txt
link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/utility/general/error
link[source]: "github" https://github.com/EiffelWebFramework/EWF/tree/master/library/utility/general/error
end

View File

@@ -28,17 +28,17 @@ feature -- Access
end
message: detachable READABLE_STRING_32
-- Potential error message
-- Potential error message.
deferred
end
parent: detachable ERROR
-- Eventual error prior to Current
-- Eventual error prior to Current.
feature -- String representation
string_representation: STRING_32
-- String representation for Current
-- String representation for Current.
do
create Result.make_from_string (name.as_string_32)
Result.append_character (' ')
@@ -62,7 +62,7 @@ feature -- Status report
feature -- Change
set_parent (a_parent: like parent)
-- Set `parent' to `a_parent'
-- Set `parent' to `a_parent'.
do
parent := a_parent
end
@@ -80,7 +80,7 @@ invariant
name_attached: name /= Void
note
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -30,28 +30,24 @@ feature -- Access
name: STRING
message: detachable STRING_32
message: STRING_32
do
create Result.make_from_string (name)
from
sub_errors.start
until
sub_errors.after
across
sub_errors as s
loop
if
attached sub_errors.item as e and then
attached s.item as e and then
attached e.message as m
then
Result.append_character ('%N')
Result.append_string (m)
end
sub_errors.forth
end
end
sub_errors: LIST [ERROR]
-- Error contained by Current
-- Error contained by Current.
feature -- Visitor
@@ -61,9 +57,8 @@ feature -- Visitor
a_visitor.process_group (Current)
end
note
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -1,19 +1,14 @@
note
description : "[
Error handler or receiver.
]"
description : "Error handler or receiver."
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
revision: "$Revision: 97980 $"
date: "$Date$"
revision: "$Revision$"
class
ERROR_HANDLER
inherit
ANY
DEBUG_OUTPUT
create
@@ -46,7 +41,7 @@ feature -- Access
-- Optional identifier for Current handler.
primary_error_code: INTEGER
-- Code of first error in `errors'
-- Code of first error in `errors'.
require
at_least_one_error: has_error
do
@@ -62,7 +57,7 @@ feature -- Status
end
count: INTEGER
-- Number of error
-- Number of error.
do
Result := errors.count
end
@@ -83,7 +78,7 @@ feature -- Status
feature {ERROR_HANDLER, ERROR_VISITOR} -- Restricted access
errors: LIST [ERROR]
-- Errors container
-- Errors container.
feature -- Status report
@@ -119,12 +114,12 @@ feature -- Status report
feature -- Events
error_added_actions: ACTION_SEQUENCE [TUPLE [ERROR]]
-- Actions triggered when a new error is added
-- Actions triggered when a new error is added.
feature -- Synchronization
add_synchronization (h: ERROR_HANDLER)
-- Add synchronization between `h' and `Current'
-- Add synchronization between `h' and `Current`.
--| the same handler can be added more than once
--| it will be synchronized only once
do
@@ -133,7 +128,7 @@ feature -- Synchronization
end
remove_synchronization (h: ERROR_HANDLER)
-- Remove synchronization between `h' and `Current'
-- Remove synchronization between `h' and `Current'.
do
remove_propagation (h)
h.remove_propagation (Current)
@@ -347,7 +342,7 @@ feature {NONE} -- Event: implementation
end
on_reset
-- `reset' was just called
-- `reset' was just called.
local
sync_list: detachable ARRAYED_LIST [ERROR_HANDLER]
lst: detachable LIST [ERROR_HANDLER]
@@ -389,7 +384,7 @@ feature {NONE} -- Event: implementation
feature -- Basic operation
add_error (a_error: ERROR)
-- Add `a_error' to the stack of error
-- Add `a_error' to the stack of error.
do
errors.force (a_error)
on_error_added (a_error)
@@ -406,28 +401,22 @@ feature -- Basic operation
end
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_GENERAL)
-- Add custom error to the stack of error
local
e: ERROR_CUSTOM
-- Add custom error to the stack of error.
do
create e.make (a_code, a_name, a_message)
add_error (e)
add_error (create {ERROR_CUSTOM}.make (a_code, a_name, a_message))
end
append (other: ERROR_HANDLER)
-- Append errors from `a_err_handler'
-- Append errors from `a_err_handler'.
local
other_errs: LIST [ERROR]
do
other_errs := other.errors
if other_errs.count > 0 then
from
other_errs.start
until
other_errs.after
if other_errs /= errors and then other_errs.count > 0 then
across
other_errs as e
loop
add_error (other_errs.item)
other_errs.forth
add_error (e.item)
end
end
ensure
@@ -435,7 +424,7 @@ feature -- Basic operation
new_count: count = old count + other.count
end
feature -- Access
feature -- Conversion
as_single_error: detachable ERROR
-- All error(s) concatenated into one single error.
@@ -465,7 +454,7 @@ feature -- Access
feature -- Element changes
concatenate
-- Concatenate into a single error if any
-- Concatenate into a single error if any.
do
if count > 1 and then attached as_single_error as e then
reset
@@ -516,7 +505,7 @@ invariant
propagators_not_empty: attached propagators as lst implies not lst.is_empty
note
copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -25,19 +25,16 @@ feature -- Access
process_group (g: ERROR_GROUP)
do
if attached g.sub_errors as err then
from
err.start
until
err.after
across
err as e
loop
process_error (err.item)
err.forth
process_error (e.item)
end
end
end
note
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -16,7 +16,7 @@ inherit
create
make
feature -- Initialization
feature {NONE} -- Creation
make (f: like file)
require
@@ -32,7 +32,7 @@ feature -- Access
feature -- Output
output_string (a_str: detachable READABLE_STRING_GENERAL)
-- Output Unicode string
-- Output Unicode string.
do
if a_str /= Void then
to_implement ("Convert into UTF-8 or console encoding before output")
@@ -51,7 +51,7 @@ feature -- Output
end
note
copyright: "2011-2012, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -14,12 +14,12 @@ inherit
feature -- Output
output_string (a_str: detachable READABLE_STRING_GENERAL)
-- Output Unicode string
-- Output Unicode string.
deferred
end
output_any (obj: detachable ANY)
-- Output Unicode string
-- Output Unicode string.
do
if attached {READABLE_STRING_GENERAL} obj as l_str then
to_implement ("Convert into UTF-8 or console encoding before output")
@@ -42,6 +42,7 @@ feature -- Output
feature -- Process
process_error (e: ERROR)
-- <Precursor>
do
output_string ({STRING_32}"Error Name: ")
output_string (e.name)
@@ -54,6 +55,7 @@ feature -- Process
end
process_custom (e: ERROR_CUSTOM)
-- <Precursor>
do
output_string ({STRING_32}"Error Name: ")
output_string (e.name)
@@ -66,22 +68,19 @@ feature -- Process
end
process_group (g: ERROR_GROUP)
-- <Precursor>
local
l_errors: LIST [ERROR]
do
from
l_errors := g.sub_errors
l_errors.start
until
l_errors.after
across
g.sub_errors as s
loop
l_errors.item.process (Current)
l_errors.forth
s.item.process (Current)
end
end
note
copyright: "Copyright (c) 1984-2011, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -16,7 +16,7 @@ inherit
create
make
feature -- Initialization
feature {NONE} -- Creation
make (buf: like buffer)
require
@@ -32,7 +32,7 @@ feature -- Access
feature -- Output
output_string (a_str: detachable READABLE_STRING_GENERAL)
-- Output Unicode string
-- Output Unicode string.
do
if a_str /= Void then
to_implement ("Convert into UTF-8 or console encoding before output")
@@ -51,7 +51,7 @@ feature -- Output
end
note
copyright: "2011-2012, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -26,7 +26,6 @@
</option>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone.ecf" readonly="false" use_application_options="true"/>
<cluster name="src" location="src\" recursive="true"/>
<override name="override" location="override\" recursive="true"/>
</target>
<target name="hello_cgi" extends="hello_dev">
<library name="default_cgi" location="..\..\library\server\wsf\default\cgi.ecf"/>

View File

@@ -6,8 +6,8 @@
<capability>
<catcall_detection use="none"/>
</capability>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<cluster name="src" location=".\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,253 @@
note
description: "[
Enter class description here!
]"
class
TEST_APP
inherit
SHARED_EXECUTION_ENVIRONMENT
create
make
feature {NONE} -- Initialization
make
-- Instantiate Current object.
local
h, res: STRING
i: INTEGER
l_test_name: detachable READABLE_STRING_GENERAL
choices: HASH_TABLE [READABLE_STRING_GENERAL, INTEGER]
do
create h.make_empty
h.append_character ((0).to_character_8)
h.append_character ((0).to_character_8)
h.append_character ((255).to_character_8)
h.append_character ((255).to_character_8)
i := 0
if attached execution_environment.arguments.argument (1) as tn then
l_test_name := tn
else
create choices.make (tests.count)
across
tests as ic
loop
i := i + 1
choices.force (ic.key, i)
print (i.out + " - " + ic.key.out + "%N")
end
print (" > ")
io.read_line
res := io.last_string
res.adjust
if
res.is_integer and then
attached choices [res.to_integer] as tn
then
l_test_name := tn
end
end
if
l_test_name /= Void and then
attached tests [l_test_name] as proc
then
proc.call ()
else
print ("Quit...%N")
end
end
port_number: INTEGER = 9090
hostname: STRING = "localhost"
has_error: BOOLEAN
tests: STRING_TABLE [PROCEDURE]
once
create Result.make (10)
Result.force (agent cli_execute_get_request, "get_request")
Result.force (agent execute_get_request (1, 0), "get_request (1,0)")
Result.force (agent execute_get_request (10, 0), "get_request (10,0)")
Result.force (agent execute_get_request (1, 10_000), "get_request (1, 10000)")
Result.force (agent execute_get_request (10, 10_000), "get_request (10, 10000)")
Result.force (agent execute_wait_for_ever, "wait_for_ever")
end
feature -- Execution
wait_ms (a_delay_ms: INTEGER; m: detachable READABLE_STRING_8)
local
i64: INTEGER_64
do
if a_delay_ms > 0 then
if has_error then
print ("[ERROR/WAIT] Skipped due to previous error%N")
else
i64 := a_delay_ms.as_integer_64 * {INTEGER_64} 1_000_000
if m /= Void then
print ("[WAIT] " + i64.out + " nanoseconds -> " + m + "%N")
else
print ("[WAIT] " + i64.out + " nanoseconds.%N")
end
execution_environment.sleep (i64) -- nanoseconds
print ("[WAIT] done.%N")
end
end
end
cli_execute_get_request
local
i,n: INTEGER
rq_nb: INTEGER
sl_val: INTEGER
do
if attached execution_environment.arguments as args then
n := args.argument_count
rq_nb := 1
sl_val := 0
i := 1
if n > i then
if args.argument (i).is_case_insensitive_equal_general ("get_request") then
if n >= i + 1 then
rq_nb := args.argument (i + 1).to_integer
if n >= i + 2 then
sl_val := args.argument (i + 2).to_integer
end
end
execute_get_request (rq_nb, sl_val)
end
end
end
end
execute_get_request (rq_nb: INTEGER; a_delay_ms: INTEGER)
require
rq_nb > 0
local
l_socket: NETWORK_STREAM_SOCKET
l_packet: PACKET
l_header_done, l_done: BOOLEAN
line, txt: READABLE_STRING_8
h: STRING
len: INTEGER
i: INTEGER
do
create l_socket.make_client_by_port (port_number, hostname)
l_socket.connect
from
i := rq_nb
until
i <= 0
loop
i := i - 1
print ("GET /test/"+ i.out +" HTTP/1.1%N")
-- socket_put_string (l_socket, "GET /test/"+ i.out +" HTTP/1.1%R%N")
socket_put_string (l_socket, "GET /test/"+ i.out)
wait_ms (a_delay_ms, "inside GET request line")
socket_put_string (l_socket, " HTTP/1.1%R%N")
wait_ms (a_delay_ms, "before Host")
socket_put_string (l_socket, "Host: localhost:9090%R%N")
wait_ms (a_delay_ms, "before Accept")
socket_put_string (l_socket, "Accept: */*%R%N")
wait_ms (a_delay_ms, "before CRNL")
socket_put_string (l_socket, "%R%N")
wait_ms (a_delay_ms, "before reading!")
if not has_error then
from
l_done := False
l_header_done := False
create h.make_empty
until
l_done
loop
if l_header_done then
l_socket.read_stream (len)
txt := l_socket.last_string
print ("BODY:%N")
print (txt)
print ("%N")
if txt.count /= len then
print ("BAD len: " + txt.count.out + " /= " + len.out + "%N")
end
l_done := True
else
l_socket.read_line
line := l_socket.last_string
if l_socket.was_error then
l_done := True
elseif line.is_empty or (line.count = 1 and line[1] = '%R') then
l_header_done := True
else
if line.starts_with_general ("Content-Length:") then
len := line.substring (16, line.count).to_integer
end
h.append (line)
h.append ("%R%N")
print ("HEADER:")
print (line)
print ("%N")
end
end
end
end
end
end
execute_wait_for_ever
local
l_socket: NETWORK_STREAM_SOCKET
l_packet: PACKET
do
create l_socket.make_client_by_port(9090, "localhost")
l_socket.connect
create l_packet.make(1)
l_packet.put_element('a', 0)
l_socket.send(l_packet, 0)
from
until
not l_socket.is_connected
loop
end
end
socket_put_string (a_socket: NETWORK_STREAM_SOCKET; s: STRING_8)
local
retried: BOOLEAN
t: STRING
i: INTEGER
do
if has_error then
create t.make_from_string (s)
i := t.index_of ('%N', 1)
if i > 0 then
t.keep_head (i - 1)
end
t.adjust
print ("[ERROR] Skip put_string ("+ s +"..)%N")
elseif retried then
has_error := True
else
a_socket.put_string (s)
end
rescue
retried := True
retry
end
end

View File

@@ -100,6 +100,10 @@ echo Install library: http_authorization
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
%COPYCMD% %TMP_DIR%\library\server\authentication\http_authorization %TMP_CONTRIB_DIR%\library\web\authentication\http_authorization
echo Install library: jwt
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
%COPYCMD% %TMP_DIR%\library\security\jwt %TMP_CONTRIB_DIR%\library\web\authentication\jwt
echo Install library: openid
%SAFE_MD% %TMP_CONTRIB_DIR%\library\web\authentication
%COPYCMD% %TMP_DIR%\library\security\openid %TMP_CONTRIB_DIR%\library\web\authentication\openid

View File

@@ -65,7 +65,9 @@ echo Uninstall library: content_negotiation
%RDCMD% %TMP_CONTRIB_DIR%\library\network\protocol\content_negotiation
echo Uninstall library: http_authorization
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\http_authorization
echo Uninstall library: security\openid
echo Uninstall library: jwt
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\jwt
echo Uninstall library: openid
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\openid
echo Uninstall library: uri_template
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\uri_template