Compare commits
46 Commits
es_15_08
...
es_rev9833
| Author | SHA1 | Date | |
|---|---|---|---|
| b5d6a75155 | |||
| 4fc4b02449 | |||
| 5276bd1479 | |||
|
|
81ab31b19a | ||
|
|
e21e30ff74 | ||
|
|
3a9ba75717 | ||
| 7d94413297 | |||
| 35855941e6 | |||
| 50ba8ca703 | |||
| dde6a0b7de | |||
| b64a281d75 | |||
| b69b8aaaf9 | |||
| 65b28ed877 | |||
| 6c7637716b | |||
| ff9a238f5c | |||
| eec3cbdba1 | |||
| 29c4931dc0 | |||
|
|
9cd0f0b117 | ||
|
|
aa0eb4fc43 | ||
|
|
dbdc594b59 | ||
|
|
4176a8c68b | ||
|
|
0557d1ee2d | ||
|
|
eed8af9a0a | ||
|
|
1b881c4f60 | ||
|
|
770488dbd3 | ||
| 3f69081d32 | |||
| 7033db7dc4 | |||
| a1a16b4a22 | |||
| 98e92ee0fe | |||
| 29b55f36cf | |||
| 061e88c9fe | |||
| 66f204b1f2 | |||
| c92b1b8c3b | |||
| 98c12b8fb9 | |||
| 5fee483fd9 | |||
| f7a7afccd6 | |||
| e2c70e6d70 | |||
| a5e150d1c0 | |||
| 39887c8bdb | |||
|
|
1f1e2abbda | ||
| 1796d9631f | |||
| 389975e409 | |||
|
|
cc65bae644 | ||
| f0cba1d536 | |||
| ed891546bc | |||
| d0836d49a4 |
163
doc/workbook/deployment.md
Normal file
163
doc/workbook/deployment.md
Normal file
@@ -0,0 +1,163 @@
|
||||
EWF Deployment
|
||||
==============
|
||||
|
||||
#Apache on Windows#
|
||||
|
||||
1. Apache Install
|
||||
2. Deploying EWF CGI
|
||||
3. CGI overview
|
||||
1. Build EWF application
|
||||
2. Copy the generated exe file and the www content .htaccess CGI
|
||||
4. Deploying EWF FCGI
|
||||
5. FCGI overview
|
||||
1. Build EWF application
|
||||
2. Copy the generated exe file and the www content.htaccess CGI
|
||||
|
||||
|
||||
|
||||
##Apache on Windows
|
||||
|
||||
###Apache Install
|
||||
|
||||
>Check the correct version (Win 32 or Win64)
|
||||
>Apache Version: Apache 2.4.4
|
||||
>Windows: http://www.apachelounge.com/download/
|
||||
|
||||
####Deploying EWF CGI
|
||||
|
||||
####CGI overview
|
||||
>A new process is started for each HTTP request. So if there are N requests to the same >CGI program, the code of the CGI program is loaded into memory N times.
|
||||
>When a CGI program finishes handling a request, the program terminates.
|
||||
|
||||
* Build EWF application
|
||||
|
||||
ec -config [app.ecf] -target [app_cgi] -finalize -c_compile -project_path
|
||||
|
||||
|
||||
>Note: change app.ecf and target app_cgi based on your own configuration.
|
||||
|
||||
* Copy the generated exe file and the www content
|
||||
|
||||
Copy the app.exe and the folder _www_ into a folder served by apache2, for example under.
|
||||
|
||||
|
||||
<APACHE_PATH>/htdocs.
|
||||
|
||||
<APACHE_PATH> = path to your apache installation
|
||||
|
||||
Edit httpd.conf under c:/<APACHE_PATH>/conf
|
||||
|
||||
DocumentRoot "c:/<APACHE_PATH>/htdocs"
|
||||
|
||||
<Directory "c:/<APACHE_PATH>/htdocs">
|
||||
AllowOverride All --
|
||||
Require all granted -- this is required in Apache 2.4.4
|
||||
</Directory>
|
||||
|
||||
Check that you have the following modules enabled
|
||||
|
||||
LoadModule cgi_module modules/mod_cgi.so
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
|
||||
####Tip:
|
||||
>To check the syntax of your httpd.conf file. From command line run the following
|
||||
|
||||
$>httpd - t
|
||||
|
||||
|
||||
>.htaccess CGI
|
||||
http://perishablepress.com/stupid-htaccess-tricks/
|
||||
|
||||
####.htaccess
|
||||
|
||||
Options +ExecCGI +Includes +FollowSymLinks -Indexes
|
||||
AddHandler cgi-script exe
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
|
||||
RewriteRule ^$ $service [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !$service
|
||||
RewriteRule ^(.*)$ $service/$1
|
||||
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
|
||||
</IfModule
|
||||
|
||||
>Replace $service with the name of your executable service, for example app_service.exe
|
||||
|
||||
|
||||
####Deploying EWF FCGI
|
||||
>To deploy FCGI you will need to download the mod_fcgi module.
|
||||
>You can get it from here http://www.apachelounge.com/download/
|
||||
|
||||
####FCGI overview
|
||||
>FastCGI allows a single, long-running process to handle more than one user request while keeping close to the CGI programming model, retaining the simplicity while eliminating the overhead of creating a new process for each request. Unlike converting an application to a web server plug-in, FastCGI applications remain independent of the web server.
|
||||
|
||||
* Build EWF application
|
||||
|
||||
ec -config [app.ecf] -target [app_fcgi] -finalize -c_compile -project_path .
|
||||
|
||||
>Note: change app.ecf and target app_fcgi based on your own configuration.
|
||||
|
||||
* Copy the generated exe file and the www content
|
||||
|
||||
Copy the app.exe and the folder "www" into a folder served by apache2, for example under
|
||||
|
||||
<APACHE_PATH>/htdocs.
|
||||
|
||||
<APACHE_PATH> = path to your apache installation
|
||||
|
||||
Edit httpd.conf under c:/<APACHE_PATH>/conf
|
||||
|
||||
DocumentRoot "c:/<APACHE_PATH>/htdocs"
|
||||
|
||||
<Directory "c:/<APACHE_PATH>/htdocs">
|
||||
AllowOverride All --
|
||||
Require all granted -- this is required in Apache 2.4.4
|
||||
</Directory>
|
||||
|
||||
>Check that you have the following modules enabled
|
||||
|
||||
LoadModule rewrite_module modules/mod_rewrite.so
|
||||
LoadModule fcgid_module modules/mod_fcgid.so
|
||||
|
||||
>NOTE: By default Apache does not come with fcgid module, so you will need to download it, and put the module under Apache2/modules
|
||||
|
||||
#.htaccess FCGI
|
||||
>http://perishablepress.com/stupid-htaccess-tricks/
|
||||
|
||||
####.htaccess
|
||||
|
||||
Options +ExecCGI +Includes +FollowSymLinks -Indexes
|
||||
|
||||
<IfModule mod_fcgid.c>
|
||||
AddHandler fcgid-script .ews
|
||||
FcgidWrapper $FULL_PATH/$service .ews
|
||||
</IfModule>
|
||||
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
|
||||
RewriteBase /
|
||||
RewriteRule ^$ service.ews [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !=/favicon.ico
|
||||
RewriteCond %{REQUEST_URI} !service.ews
|
||||
RewriteRule ^(.*)$ service.ews/$1
|
||||
|
||||
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
|
||||
</IfModule>
|
||||
|
||||
Replace $service with the name of your executable $service, for example app_service.exe
|
||||
You will need to create an service.ews file, this file will be located at the same place where you copy your app service executable.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* [EWF Introduction](#introduction)
|
||||
* [Handling Requests: Form/Query Parameter](#form_query_parameters)
|
||||
* [Handling Requests: Header Fields](#header_fields)
|
||||
* [Generating Responses](/workbook/generating_response/generating_response.md)
|
||||
* [Handling Cookies](/workbook/handling_cookies/handling_cookies.md)
|
||||
*
|
||||
* [Generating Responses](#generating responses)
|
||||
* [Handling Cookies](#handling_cookies)
|
||||
* [EWF Deployment](#deployment)
|
||||
|
||||
<a name="core"></a>
|
||||
# EWF Core
|
||||
@@ -29,10 +29,14 @@ Before reading (or walking throught) the workbook, to get a quick overview of EW
|
||||
## Handling Requests: Header Fields
|
||||
[Handling Requests: Header Fields](/doc/workbook/handling_request/headers.md).
|
||||
|
||||
<a name="header_fields"></a>
|
||||
<a name="generating_responses"></a>
|
||||
## Generating Response
|
||||
[Generating Responses](/doc/workbook/generating_response/generating_response.md)
|
||||
|
||||
<a name="handling_cookies"></a>
|
||||
## Handling Cookies
|
||||
[Handling Cookies](/doc/workbook/handling_cookies/handling_cookies.md)
|
||||
|
||||
<a name="deployment"/>
|
||||
## EWF Deployment
|
||||
[EWF Deployment](/doc/workbook/deployment.md)
|
||||
|
||||
@@ -1,13 +1,56 @@
|
||||
# simple HTTP client
|
||||
|
||||
## Overview
|
||||
It provides simple routine to perform http requests, and get response.
|
||||
|
||||
## Requirements
|
||||
* Eiffel cURL library
|
||||
* cURL dynamic libraries in the PATH or the current directory (.dll or .so)
|
||||
* One of the following
|
||||
- Eiffel cURL library
|
||||
- cURL dynamic libraries in the PATH or the current directory (.dll or .so)
|
||||
- Eiffel Net library
|
||||
- and optionally Eiffel NetSSL library to support https://
|
||||
|
||||
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:
|
||||
In the .ecf configuration file of your project, you can use the following custom variables:
|
||||
|
||||
* Disable the libcurl implementation
|
||||
```
|
||||
<variable name="libcurl_http_client_disabled" value="True"/>
|
||||
```
|
||||
|
||||
* Disable the net implementation
|
||||
```
|
||||
<variable name="net_http_client_disabled" value="True"/>
|
||||
```
|
||||
|
||||
* If you disabled both, the http client will not work as expected.
|
||||
|
||||
For the net implementation (using EiffelNet), if you need https:// support, you need to enabled the ssl support with the custom variables :
|
||||
```
|
||||
<variable name="netssl_http_client_enabled" value="True"/>
|
||||
```
|
||||
* By default, SSL is not included (mostly because it is sometime a pain to get the needed dynamic libraries .dll or .so)
|
||||
|
||||
## Usage
|
||||
* To build code that is portable across the libcurl or net implementation of http_client library, use the DEFAULT_HTTP_CLIENT
|
||||
|
||||
```
|
||||
cl: DEFAULT_HTTP_CLIENT
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
create cl
|
||||
sess := cl.new_session ("http://example.com")
|
||||
if attached sess.get ("/path-to-test") as l_response then
|
||||
if not l_response.error_occurred then
|
||||
if attached l_response.body as l_body then
|
||||
print (l_body)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
* See the tests/test-safe.ecf project to see how to use.
|
||||
* Examples will come in the future.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-12-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-12-0 http://www.eiffel.com/developers/xml/configuration-1-12-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<target name="http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
@@ -7,12 +7,78 @@
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_disabled" location="$|no_ssl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_enabled" location="$|ssl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_null" location="$|default\null\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_net" location="$|default\net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_libcurl_or_net" location="$|default\libcurl_or_net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<target name="http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
@@ -9,10 +9,76 @@
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http.ecf"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
|
||||
<library name="encoder" location="../../text/encoder/encoder.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl.ecf">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_null" location="$|spec\null\" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_disabled" location="$|no_ssl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_enabled" location="$|ssl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_null" location="$|default\null\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_net" location="$|default\net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="default_libcurl_or_net" location="$|default\libcurl_or_net\">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
23
library/network/http_client/libcurl_http_client-safe.ecf
Normal file
23
library/network/http_client/libcurl_http_client-safe.ecf
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="libcurl_http_client" uuid="FA686EE7-D01D-43A3-8E95-A00120658040" library_target="libcurl_http_client">
|
||||
<target name="libcurl_http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
23
library/network/http_client/libcurl_http_client.ecf
Normal file
23
library/network/http_client/libcurl_http_client.ecf
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="libcurl_http_client" uuid="FA686EE7-D01D-43A3-8E95-A00120658040" library_target="libcurl_http_client">
|
||||
<target name="libcurl_http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="false" warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
|
||||
</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"/>
|
||||
<library name="http" location="..\protocol\http\http.ecf"/>
|
||||
<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="spec_libcurl" location="$|spec\libcurl\" recursive="true"/>
|
||||
<cluster name="default_libcurl" location="$|default\libcurl\"/>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
40
library/network/http_client/net_http_client-safe.ecf
Normal file
40
library/network/http_client/net_http_client-safe.ecf
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="7897B317-7AD3-44E4-A933-0544A169AB1B" library_target="net_http_client">
|
||||
<target name="net_http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="false" warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
|
||||
<condition>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||
<cluster name="net_ssl_disabled" location="$|no_ssl\">
|
||||
<condition>
|
||||
<custom name="netssl_http_client_enabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_enabled" location="$|ssl\">
|
||||
<condition>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="default_net" location="$|default\net\"/>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
40
library/network/http_client/net_http_client.ecf
Normal file
40
library/network/http_client/net_http_client.ecf
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="7897B317-7AD3-44E4-A933-0544A169AB1B" library_target="net_http_client">
|
||||
<target name="net_http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option debug="false" warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
|
||||
</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"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.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="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_net" location="$|spec\net\">
|
||||
<cluster name="net_implementation" location="$|implementation\" hidden="true"/>
|
||||
<cluster name="net_ssl_disabled" location="$|no_ssl\">
|
||||
<condition>
|
||||
<custom name="netssl_http_client_enabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="net_ssl_enabled" location="$|ssl\">
|
||||
<condition>
|
||||
<custom name="netssl_http_client_enabled" value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
<cluster name="default_net" location="$|default\net\"/>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -0,0 +1,15 @@
|
||||
note
|
||||
description: "[
|
||||
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
DEFAULT_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
LIBCURL_HTTP_CLIENT
|
||||
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
note
|
||||
description: "[
|
||||
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT or NET_HTTP_CLIENT.
|
||||
|
||||
The preference goes to libcurl implementation for now,
|
||||
since the net implementation has currently less functionalities.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
DEFAULT_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Access
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
local
|
||||
libcurl: LIBCURL_HTTP_CLIENT
|
||||
net: NET_HTTP_CLIENT
|
||||
do
|
||||
--| For now, try libcurl first, and then net
|
||||
--| the reason is the net implementation is still in progress.
|
||||
create libcurl
|
||||
Result := libcurl.new_session (a_base_url)
|
||||
if not Result.is_available then
|
||||
create net
|
||||
Result := net.new_session (a_base_url)
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
note
|
||||
description: "[
|
||||
Default HTTP_CLIENT based on NET_HTTP_CLIENT.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
DEFAULT_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
NET_HTTP_CLIENT
|
||||
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
note
|
||||
description: "[
|
||||
Default HTTP_CLIENT based on NULL_HTTP_CLIENT.
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
DEFAULT_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Access
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
do
|
||||
create {NULL_HTTP_CLIENT_SESSION} Result.make (a_base_url)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, 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
|
||||
@@ -9,7 +9,7 @@ note
|
||||
deferred class
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Status
|
||||
feature -- Access
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
@@ -17,7 +17,7 @@ feature -- Status
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -14,24 +14,41 @@ inherit
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: READABLE_STRING_8; a_session: like session; ctx: like context)
|
||||
-- Initialize `Current'.
|
||||
make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context)
|
||||
-- Initialize `Current' with request url `a_url', method `a_request_method' within the session `a_session'
|
||||
-- and optional context `ctx' which can be used to pass additional parameters.
|
||||
do
|
||||
request_method := a_request_method
|
||||
session := a_session
|
||||
url := a_url
|
||||
headers := session.headers.twin
|
||||
if ctx /= Void then
|
||||
context := ctx
|
||||
import (ctx)
|
||||
end
|
||||
initialize (a_url, ctx)
|
||||
ensure
|
||||
context_set: context = ctx
|
||||
ctx_header_set: ctx /= Void implies across ctx.headers as ctx_h all attached headers.item (ctx_h.key) as v and then v.same_string (ctx_h.item) end
|
||||
end
|
||||
|
||||
initialize (a_url: READABLE_STRING_8; ctx: like context)
|
||||
-- Initialize Current with `a_url' and `ctx'.
|
||||
-- This can be used to reset/reinitialize Current with new url
|
||||
-- in the case of redirection.
|
||||
do
|
||||
url := a_url
|
||||
headers := session.headers.twin
|
||||
if ctx /= Void then
|
||||
context := ctx
|
||||
import (ctx)
|
||||
else
|
||||
context := Void
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Internal
|
||||
|
||||
session: HTTP_CLIENT_SESSION
|
||||
-- Session related to Current request.
|
||||
-- It provides a few parameters related to session.
|
||||
|
||||
context: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
-- Potential additional parameters for this specific request.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -44,16 +61,27 @@ feature -- Status report
|
||||
feature -- Access
|
||||
|
||||
request_method: READABLE_STRING_8
|
||||
deferred
|
||||
end
|
||||
-- Request method associated with Current request.
|
||||
|
||||
url: READABLE_STRING_8
|
||||
-- URL associated with current request.
|
||||
|
||||
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
|
||||
-- Specific headers to be used for current request.
|
||||
|
||||
response: HTTP_CLIENT_RESPONSE
|
||||
-- Response received from request execution.
|
||||
-- Check `error_occurred' for eventual error.
|
||||
-- note: two consecutive calls will trigger two executions!
|
||||
deferred
|
||||
ensure
|
||||
Result_set: Result /= Void
|
||||
end
|
||||
|
||||
feature {HTTP_CLIENT_SESSION} -- Execution
|
||||
|
||||
import (ctx: HTTP_CLIENT_REQUEST_CONTEXT)
|
||||
-- Import `ctx' parameters.
|
||||
local
|
||||
l_headers: like headers
|
||||
do
|
||||
@@ -67,10 +95,6 @@ feature {HTTP_CLIENT_SESSION} -- Execution
|
||||
end
|
||||
end
|
||||
|
||||
execute: HTTP_CLIENT_RESPONSE
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Authentication
|
||||
|
||||
auth_type: STRING
|
||||
@@ -88,21 +112,26 @@ feature -- Authentication
|
||||
end
|
||||
|
||||
username: detachable READABLE_STRING_32
|
||||
-- Username specified for the `session'.
|
||||
do
|
||||
Result := session.username
|
||||
end
|
||||
|
||||
password: detachable READABLE_STRING_32
|
||||
-- Password specified for the `session'.
|
||||
do
|
||||
Result := session.password
|
||||
end
|
||||
|
||||
credentials: detachable READABLE_STRING_32
|
||||
-- Credentials specified for the `session'.
|
||||
--| Usually `username':`password'
|
||||
do
|
||||
Result := session.credentials
|
||||
end
|
||||
|
||||
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
|
||||
-- Optional proxy settings.
|
||||
do
|
||||
Result := session.proxy
|
||||
end
|
||||
@@ -220,7 +249,7 @@ feature {NONE} -- Utilities: encoding
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -81,7 +81,10 @@ feature -- Access
|
||||
-- Optional output file to get downloaded content and header
|
||||
|
||||
output_content_file: detachable FILE
|
||||
-- Optional output file to get downloaded content
|
||||
-- Optional output file to get downloaded content
|
||||
|
||||
http_version: detachable IMMUTABLE_STRING_8
|
||||
-- Overwrite default http version if set.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -209,6 +212,17 @@ feature -- Element change
|
||||
output_content_file := f
|
||||
end
|
||||
|
||||
set_http_version (v: detachable READABLE_STRING_8)
|
||||
require
|
||||
valid_version: v = Void or else v.starts_with_general ("HTTP/")
|
||||
do
|
||||
if v = Void then
|
||||
http_version := Void
|
||||
else
|
||||
create http_version.make_from_string (v)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status setting
|
||||
|
||||
set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
|
||||
@@ -264,7 +278,7 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -13,7 +13,7 @@ create
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: like url)
|
||||
make (a_url: READABLE_STRING_8)
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
--| Default values
|
||||
@@ -54,12 +54,23 @@ feature -- Access
|
||||
|
||||
status_line: detachable READABLE_STRING_8
|
||||
|
||||
http_version: detachable READABLE_STRING_8
|
||||
-- http version associated with `status_line'.
|
||||
|
||||
raw_header: READABLE_STRING_8
|
||||
-- Raw http header of the response.
|
||||
|
||||
redirections: detachable ARRAYED_LIST [TUPLE [status_line: detachable READABLE_STRING_8; raw_header: READABLE_STRING_8; body: detachable READABLE_STRING_8]]
|
||||
-- Header of previous redirection if any.
|
||||
|
||||
redirections_count: INTEGER
|
||||
-- Number of redirections.
|
||||
do
|
||||
if attached redirections as lst then
|
||||
Result := lst.count
|
||||
end
|
||||
end
|
||||
|
||||
header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
|
||||
-- Header entry value related to `a_name'
|
||||
-- if multiple entries, just concatenate them using comma character
|
||||
@@ -92,6 +103,24 @@ feature -- Access
|
||||
Result := s
|
||||
end
|
||||
|
||||
multiple_header (a_name: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
|
||||
-- Header multiple entries related to `a_name'
|
||||
local
|
||||
k: READABLE_STRING_8
|
||||
do
|
||||
across
|
||||
headers as hds
|
||||
loop
|
||||
k := hds.item.name
|
||||
if k.same_string (a_name) then
|
||||
if Result = Void then
|
||||
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (1)
|
||||
end
|
||||
Result.force (hds.item.value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
|
||||
-- Computed table of http headers of the response.
|
||||
--| We use a LIST since one might have multiple message-header fields with the same field-name
|
||||
@@ -115,7 +144,7 @@ feature -- Access
|
||||
loop
|
||||
l_start := pos
|
||||
--| Left justify
|
||||
from until not h[l_start].is_space loop
|
||||
from until not h [l_start].is_space loop
|
||||
l_start := l_start + 1
|
||||
end
|
||||
pos := h.index_of ('%N', l_start)
|
||||
@@ -129,7 +158,7 @@ feature -- Access
|
||||
end
|
||||
if l_end > 0 then
|
||||
--| Right justify
|
||||
from until not h[l_end].is_space loop
|
||||
from until not h [l_end].is_space loop
|
||||
l_end := l_end - 1
|
||||
end
|
||||
c := h.index_of (':', l_start)
|
||||
@@ -188,17 +217,72 @@ feature -- Access
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_http_1_0: BOOLEAN
|
||||
-- Is response using HTTP/1.0 protocole?
|
||||
--| Note: it is relevant once the raw header are set.
|
||||
do
|
||||
Result := attached http_version as v and then v.same_string ("1.0")
|
||||
end
|
||||
|
||||
is_http_1_1: BOOLEAN
|
||||
-- Is response using HTTP/1.1 protocole?
|
||||
--| Note: it is relevant once the raw header are set.
|
||||
do
|
||||
Result := attached http_version as v and then v.same_string ("1.1")
|
||||
end
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_http_version (v: like http_version)
|
||||
-- Set `http_version' to `v'.
|
||||
do
|
||||
http_version := v
|
||||
end
|
||||
|
||||
set_status (s: INTEGER)
|
||||
-- Set response `status' code to `s'
|
||||
do
|
||||
status := s
|
||||
end
|
||||
|
||||
set_status_line (a_line: detachable READABLE_STRING_8)
|
||||
-- Set status line to `a_line',
|
||||
-- and also `status' extracted from `a_line' if possible.
|
||||
local
|
||||
i,j: INTEGER
|
||||
s: READABLE_STRING_8
|
||||
do
|
||||
status_line := a_line
|
||||
http_version := Void
|
||||
|
||||
if a_line /= Void then
|
||||
if a_line.starts_with ("HTTP/") then
|
||||
i := a_line.index_of (' ', 1)
|
||||
if i > 0 then
|
||||
http_version := a_line.substring (1 + 5, i - 1) -- ("HTTP/").count = 5
|
||||
i := i + 1
|
||||
end
|
||||
else
|
||||
i := 1
|
||||
end
|
||||
-- Get status code token.
|
||||
if i > 0 then
|
||||
j := a_line.index_of (' ', i)
|
||||
if j > i then
|
||||
s := a_line.substring (i, j - 1)
|
||||
if s.is_integer then
|
||||
set_status (s.to_integer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT)
|
||||
-- Parse `a_source' response message
|
||||
-- and set `header' and `body'.
|
||||
-- and set `status_line', `status', `header' and `body'.
|
||||
--| ctx is the context associated with the request
|
||||
--| it might be useful to deal with redirection customization...
|
||||
local
|
||||
@@ -234,7 +318,7 @@ feature -- Change
|
||||
|
||||
j := i + 2
|
||||
pos := j
|
||||
status_line := l_status_line
|
||||
set_status_line (l_status_line)
|
||||
set_raw_header (h)
|
||||
|
||||
-- libcURL does not cache redirection content.
|
||||
@@ -277,10 +361,23 @@ feature -- Change
|
||||
|
||||
set_raw_header (h: READABLE_STRING_8)
|
||||
-- Set http header `raw_header' to `h'
|
||||
local
|
||||
i: INTEGER
|
||||
s: STRING_8
|
||||
do
|
||||
raw_header := h
|
||||
--| Reset internal headers
|
||||
internal_headers := Void
|
||||
|
||||
--| Set status line, right away.
|
||||
i := h.index_of ('%N', 1)
|
||||
if i > 0 then
|
||||
s := h.substring (1, i - 1)
|
||||
if s.starts_with ("HTTP/") then
|
||||
s.right_adjust
|
||||
set_status_line (s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_redirection (s: detachable READABLE_STRING_8; h: READABLE_STRING_8; a_body: detachable READABLE_STRING_8)
|
||||
@@ -308,7 +405,7 @@ feature {NONE} -- Implementation
|
||||
-- Internal cached value for the headers
|
||||
|
||||
;note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -50,6 +50,14 @@ feature {NONE} -- Initialization
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
close
|
||||
-- Close session.
|
||||
--| useful to disconnect persistent connection.
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
|
||||
-- Url computed from Current and `ctx' data.
|
||||
local
|
||||
@@ -77,6 +85,61 @@ feature -- Basic operation
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Access: verbose
|
||||
|
||||
verbose_mode: INTEGER
|
||||
-- Internal verbose mode.
|
||||
|
||||
verbose_header_sent_mode: INTEGER = 1 --| 0001
|
||||
verbose_header_received_mode: INTEGER = 2 --| 0010
|
||||
verbose_debug_mode: INTEGER = 4 --| 0100
|
||||
|
||||
feature -- Access: verbose
|
||||
|
||||
is_header_sent_verbose: BOOLEAN
|
||||
do
|
||||
Result := verbose_mode & verbose_header_sent_mode = verbose_header_sent_mode
|
||||
end
|
||||
|
||||
is_header_received_verbose: BOOLEAN
|
||||
do
|
||||
Result := verbose_mode & verbose_header_received_mode = verbose_header_received_mode
|
||||
end
|
||||
|
||||
is_debug_verbose: BOOLEAN
|
||||
do
|
||||
Result := verbose_mode & verbose_debug_mode = verbose_debug_mode
|
||||
end
|
||||
|
||||
feature -- Element change: verbose
|
||||
|
||||
set_header_sent_verbose (b: BOOLEAN)
|
||||
do
|
||||
if b then
|
||||
verbose_mode := verbose_mode | verbose_header_sent_mode
|
||||
else
|
||||
verbose_mode := verbose_mode & verbose_header_sent_mode.bit_not
|
||||
end
|
||||
end
|
||||
|
||||
set_header_received_verbose (b: BOOLEAN)
|
||||
do
|
||||
if b then
|
||||
verbose_mode := verbose_mode | verbose_header_received_mode
|
||||
else
|
||||
verbose_mode := verbose_mode & verbose_header_received_mode.bit_not
|
||||
end
|
||||
end
|
||||
|
||||
set_debug_verbose (b: BOOLEAN)
|
||||
do
|
||||
if b then
|
||||
verbose_mode := verbose_mode | verbose_debug_mode
|
||||
else
|
||||
verbose_mode := verbose_mode & verbose_debug_mode.bit_not
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Custom
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
@@ -88,17 +151,23 @@ feature -- Helper
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for GET request based on Current, `a_path' and `ctx'.
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for HEAD request based on Current, `a_path' and `ctx'.
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for POST request based on Current, `a_path' and `ctx'
|
||||
-- with input `data'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
@@ -111,29 +180,39 @@ feature -- Helper
|
||||
patch (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PATCH request based on Current, `a_path' and `ctx'
|
||||
-- with input `data'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
patch_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PATCH request based on Current, `a_path' and `ctx'
|
||||
-- with uploaded data file `fn'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PUT request based on Current, `a_path' and `ctx'
|
||||
-- with input `data'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PUT request based on Current, `a_path' and `ctx'
|
||||
-- with uploaded file `fn'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for DELETE request based on Current, `a_path' and `ctx'
|
||||
require
|
||||
is_available: is_available
|
||||
deferred
|
||||
end
|
||||
|
||||
@@ -185,6 +264,9 @@ feature -- Access
|
||||
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
|
||||
-- Headers common to any request created by Current session.
|
||||
|
||||
cookie: detachable READABLE_STRING_8
|
||||
-- Cookie for the current base_url
|
||||
|
||||
feature -- Authentication
|
||||
|
||||
auth_type: STRING
|
||||
@@ -194,11 +276,15 @@ feature -- Authentication
|
||||
auth_type_id: INTEGER
|
||||
-- See {HTTP_CLIENT_CONSTANTS}.Auth_type_*
|
||||
|
||||
username,
|
||||
username: detachable READABLE_STRING_32
|
||||
-- Associated optional username value.
|
||||
|
||||
password: detachable READABLE_STRING_32
|
||||
-- Associated optional password value.
|
||||
|
||||
credentials: detachable READABLE_STRING_32
|
||||
|
||||
-- Associated optional credentials value.
|
||||
-- Computed as `username':`password'.
|
||||
|
||||
feature -- Status setting
|
||||
|
||||
@@ -212,6 +298,12 @@ feature -- Element change
|
||||
set_base_url (u: like base_url)
|
||||
do
|
||||
base_url := u
|
||||
cookie := Void
|
||||
end
|
||||
|
||||
set_cookie (a_cookie: detachable READABLE_STRING_8)
|
||||
do
|
||||
cookie := a_cookie
|
||||
end
|
||||
|
||||
set_timeout (n_seconds: like timeout)
|
||||
@@ -244,12 +336,26 @@ feature -- Element change
|
||||
headers.prune (k)
|
||||
end
|
||||
|
||||
set_credentials (u: like username; p: like password)
|
||||
set_credentials (u,p: detachable READABLE_STRING_GENERAL)
|
||||
local
|
||||
s: STRING_32
|
||||
do
|
||||
username := u
|
||||
password := p
|
||||
if u = Void then
|
||||
username := Void
|
||||
else
|
||||
create {STRING_32} username.make_from_string_general (u)
|
||||
end
|
||||
if p = Void then
|
||||
password := Void
|
||||
else
|
||||
create {STRING_32} password.make_from_string_general (p)
|
||||
end
|
||||
if u /= Void and p /= Void then
|
||||
credentials := u + ":" + p
|
||||
create s.make (u.count + 1 + p.count)
|
||||
s.append_string_general (u)
|
||||
s.append_character (':')
|
||||
s.append_string_general (p)
|
||||
credentials := s
|
||||
else
|
||||
credentials := Void
|
||||
end
|
||||
@@ -300,7 +406,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
note
|
||||
description : "[
|
||||
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
|
||||
|
||||
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
|
||||
and related accessible to the executable (i.e in same directory, or in the PATH)
|
||||
]"
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
@@ -13,6 +16,7 @@ inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
create
|
||||
default_create,
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
@@ -20,6 +24,7 @@ feature {NONE} -- Initialization
|
||||
make
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
default_create
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
@@ -30,7 +35,7 @@ feature -- Status
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
|
||||
|
||||
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
|
||||
and related accessible to the executable (i.e in same directory, or in the PATH)
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
@@ -10,9 +13,8 @@ class
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST
|
||||
rename
|
||||
make as make_request
|
||||
redefine
|
||||
make,
|
||||
session
|
||||
end
|
||||
|
||||
@@ -23,8 +25,7 @@ feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context)
|
||||
do
|
||||
make_request (a_url, a_session, ctx)
|
||||
request_method := a_request_method
|
||||
Precursor (a_url, a_request_method, a_session, ctx)
|
||||
apply_workaround
|
||||
end
|
||||
|
||||
@@ -38,13 +39,10 @@ feature {NONE} -- Initialization
|
||||
|
||||
session: LIBCURL_HTTP_CLIENT_SESSION
|
||||
|
||||
feature -- Access
|
||||
|
||||
request_method: READABLE_STRING_8
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute: HTTP_CLIENT_RESPONSE
|
||||
response: HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
local
|
||||
l_result: INTEGER
|
||||
l_curl_string: detachable CURL_STRING
|
||||
@@ -63,6 +61,8 @@ feature -- Execution
|
||||
l_upload_data: detachable READABLE_STRING_8
|
||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||
l_headers: like headers
|
||||
l_is_http_1_0: BOOLEAN
|
||||
l_uri: URI
|
||||
do
|
||||
if not retried then
|
||||
curl := session.curl
|
||||
@@ -72,6 +72,10 @@ feature -- Execution
|
||||
|
||||
ctx := context
|
||||
|
||||
if ctx /= Void then
|
||||
l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
|
||||
end
|
||||
|
||||
--| Configure cURL session
|
||||
initialize_curl_session (ctx, curl, curl_easy, curl_handle)
|
||||
|
||||
@@ -81,12 +85,48 @@ feature -- Execution
|
||||
append_parameters_to_url (ctx.query_parameters, l_url)
|
||||
end
|
||||
|
||||
if session.is_header_sent_verbose then
|
||||
io.error.put_string ("> Sending:%N")
|
||||
create l_uri.make_from_string (l_url)
|
||||
io.error.put_string ("> ")
|
||||
io.error.put_string (request_method + " " + l_uri.path)
|
||||
if attached l_uri.query as q then
|
||||
io.error.put_string (q)
|
||||
end
|
||||
if l_is_http_1_0 then
|
||||
io.error.put_string (" HTTP/1.0")
|
||||
else
|
||||
io.error.put_string (" HTTP/1.1")
|
||||
end
|
||||
io.error.put_new_line
|
||||
if attached l_uri.host as l_host then
|
||||
io.error.put_string ("> ")
|
||||
io.error.put_string ("Host: " + l_host)
|
||||
io.error.put_new_line
|
||||
end
|
||||
across
|
||||
headers as ic
|
||||
loop
|
||||
io.error.put_string ("> ")
|
||||
io.error.put_string (ic.key)
|
||||
io.error.put_string (": ")
|
||||
io.error.put_string (ic.item)
|
||||
io.error.put_new_line
|
||||
end
|
||||
io.error.put_string ("> ... ")
|
||||
io.error.put_new_line
|
||||
end
|
||||
|
||||
debug ("service")
|
||||
io.put_string ("SERVICE: " + l_url)
|
||||
io.put_new_line
|
||||
end
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
|
||||
|
||||
if l_is_http_1_0 then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_http_version, {CURL_OPT_CONSTANTS}.curl_http_version_1_0)
|
||||
else
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_http_version, {CURL_OPT_CONSTANTS}.curl_http_version_none)
|
||||
end
|
||||
l_headers := headers
|
||||
|
||||
-- Context
|
||||
@@ -241,6 +281,10 @@ feature -- Execution
|
||||
Result.status := response_status_code (curl_easy, curl_handle)
|
||||
if l_curl_string /= Void then
|
||||
Result.set_response_message (l_curl_string.string, ctx)
|
||||
if session.is_header_received_verbose then
|
||||
io.error.put_string ("< Receiving:%N")
|
||||
io.error.put_string (Result.raw_header)
|
||||
end
|
||||
end
|
||||
else
|
||||
Result.set_error_message ("Error: cURL Error[" + l_result.out + "]")
|
||||
@@ -390,7 +434,7 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
|
||||
|
||||
WARNING: Do not forget to have the dynamic libraries libcurl (.dll or .so)
|
||||
and related accessible to the executable (i.e in same directory, or in the PATH)
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
@@ -34,19 +37,22 @@ feature -- Status report
|
||||
feature -- Custom
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
do
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
||||
Result := req.execute
|
||||
Result := req.response
|
||||
end
|
||||
|
||||
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Same as `custom' but including upload data `a_data'.
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Same as `custom' but including upload file `fn'.
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
|
||||
end
|
||||
@@ -54,46 +60,55 @@ feature -- Custom
|
||||
feature -- Helper
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("GET", a_path, ctx)
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("HEAD", a_path, ctx)
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("DELETE", a_path, ctx)
|
||||
end
|
||||
@@ -140,7 +155,7 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
||||
Result := req.execute
|
||||
Result := req.response
|
||||
|
||||
if f /= Void then
|
||||
f.delete
|
||||
@@ -161,7 +176,7 @@ feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
|
||||
|
||||
|
||||
;note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
note
|
||||
description: "Socket connection information, used for peristent connection implementation."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT_CONNECTION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_socket: NETWORK_STREAM_SOCKET; a_host: READABLE_STRING_GENERAL; a_port: INTEGER)
|
||||
do
|
||||
socket := a_socket
|
||||
host := a_host
|
||||
port := a_port
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
socket: NETWORK_STREAM_SOCKET
|
||||
-- Persistent connection socket.
|
||||
|
||||
host: READABLE_STRING_GENERAL
|
||||
-- Host used for this connection.
|
||||
|
||||
port: INTEGER
|
||||
-- Port used for this connection.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_reusable (a_host: READABLE_STRING_GENERAL; a_port: INTEGER): BOOLEAN
|
||||
-- Is Current connection reusable for new connection `a_host:a_port'?
|
||||
do
|
||||
if a_host.same_string (host) and port = a_port then
|
||||
Result := socket.is_connected
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, 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
|
||||
34
library/network/http_client/src/spec/net/net_http_client.e
Normal file
34
library/network/http_client/src/spec/net/net_http_client.e
Normal file
@@ -0,0 +1,34 @@
|
||||
note
|
||||
description : "[
|
||||
Specific implementation of HTTP_CLIENT based on Eiffel NET library
|
||||
|
||||
WARNING: this is work in progress
|
||||
]"
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Status
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): NET_HTTP_CLIENT_SESSION
|
||||
do
|
||||
create Result.make (a_base_url)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,739 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel NET library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT_REQUEST
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST
|
||||
redefine
|
||||
session
|
||||
end
|
||||
|
||||
TRANSFER_COMMAND_CONSTANTS
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Internal
|
||||
|
||||
session: NET_HTTP_CLIENT_SESSION
|
||||
net_http_client_version: STRING = "0.1"
|
||||
|
||||
session_socket (a_host: READABLE_STRING_8; a_port: INTEGER; a_is_https: BOOLEAN; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): NETWORK_STREAM_SOCKET
|
||||
-- Session socket to use for connection.
|
||||
-- Eventually reuse the persistent connection if any.
|
||||
local
|
||||
l_socket: detachable NETWORK_STREAM_SOCKET
|
||||
do
|
||||
if
|
||||
attached session.persistent_connection as l_persistent_connection and then
|
||||
l_persistent_connection.is_reusable (a_host, a_port)
|
||||
then
|
||||
l_socket := l_persistent_connection.socket
|
||||
if a_is_https then
|
||||
if attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
|
||||
Result := l_ssl_socket
|
||||
else
|
||||
l_socket := Void
|
||||
end
|
||||
elseif attached {SSL_NETWORK_STREAM_SOCKET} l_socket as l_ssl_socket then
|
||||
l_socket := Void
|
||||
end
|
||||
if l_socket /= Void and then not l_socket.is_connected then
|
||||
-- Reset persistent connection
|
||||
l_socket := Void
|
||||
end
|
||||
end
|
||||
if l_socket /= Void then
|
||||
-- Reuse persistent connection.
|
||||
Result := l_socket
|
||||
else
|
||||
session.set_persistent_connection (Void)
|
||||
if a_is_https then
|
||||
create {SSL_NETWORK_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
|
||||
else
|
||||
create Result.make_client_by_port (a_port, a_host)
|
||||
end
|
||||
Result.set_connect_timeout (connect_timeout)
|
||||
Result.set_timeout (timeout)
|
||||
Result.connect
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
response: HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
local
|
||||
redirection_response: detachable like response
|
||||
l_uri: URI
|
||||
l_header_key: READABLE_STRING_8
|
||||
l_host: READABLE_STRING_8
|
||||
l_cookie: detachable READABLE_STRING_8
|
||||
l_request_uri: STRING
|
||||
l_url: HTTP_URL
|
||||
l_socket: NETWORK_STREAM_SOCKET
|
||||
s: STRING
|
||||
l_message: STRING
|
||||
l_content_length: INTEGER
|
||||
l_location: detachable READABLE_STRING_8
|
||||
l_port: INTEGER
|
||||
l_is_https: BOOLEAN
|
||||
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_upload_file: detachable RAW_FILE
|
||||
l_upload_filename: detachable READABLE_STRING_GENERAL
|
||||
l_form_string: STRING
|
||||
l_prev_header: READABLE_STRING_8
|
||||
l_boundary: READABLE_STRING_8
|
||||
l_is_http_1_0_request: BOOLEAN
|
||||
l_is_keep_alive: BOOLEAN
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if not retried then
|
||||
ctx := context
|
||||
if ctx /= Void then
|
||||
l_is_http_1_0_request := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
|
||||
end
|
||||
create Result.make (url)
|
||||
|
||||
-- Get URL data
|
||||
l_is_https := url.starts_with_general ("https://")
|
||||
create l_uri.make_from_string (url)
|
||||
l_port := l_uri.port
|
||||
if l_port = 0 then
|
||||
if l_is_https then
|
||||
l_port := 443
|
||||
else
|
||||
l_port := 80
|
||||
end
|
||||
end
|
||||
if attached l_uri.host as h then
|
||||
l_host := h
|
||||
else
|
||||
create l_url.make (url)
|
||||
l_host := l_url.host
|
||||
end
|
||||
|
||||
if attached session.proxy as l_proxy_settings then
|
||||
-- For now, so proxy support.
|
||||
check
|
||||
not_supported: False
|
||||
end
|
||||
end
|
||||
|
||||
-- Connect
|
||||
l_socket := session_socket (l_host, l_port, l_is_https, ctx)
|
||||
if l_socket.is_connected then
|
||||
|
||||
create l_form_string.make_empty
|
||||
|
||||
-- add headers for authorization
|
||||
if not headers.has ("Authorization") then
|
||||
if
|
||||
attached username as u_name and
|
||||
attached password as u_pass
|
||||
then
|
||||
create l_authorization.make_basic_auth (u_name, u_pass)
|
||||
if attached l_authorization.http_authorization as auth then
|
||||
headers.extend (auth, "Authorization")
|
||||
end
|
||||
check headers.has_key ("Authorization") end
|
||||
end
|
||||
end
|
||||
|
||||
create l_request_uri.make_from_string (l_uri.path)
|
||||
if attached l_uri.query as l_query then
|
||||
l_request_uri.append_character ('?')
|
||||
l_request_uri.append (l_query)
|
||||
end
|
||||
|
||||
-- add computed header User-Agent if not yet set.
|
||||
if not headers.has ("User-Agent") then
|
||||
if {PLATFORM}.is_unix then
|
||||
l_platform := "Unix"
|
||||
elseif {PLATFORM}.is_windows then
|
||||
l_platform := "Windows"
|
||||
elseif {PLATFORM}.is_mac then
|
||||
l_platform := "Mac"
|
||||
elseif {PLATFORM}.is_vms then
|
||||
l_platform := "VMS"
|
||||
elseif {PLATFORM}.is_vxworks then
|
||||
l_platform := "VxWorks"
|
||||
else
|
||||
l_platform := "Unknown"
|
||||
end
|
||||
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
||||
end
|
||||
|
||||
-- handle sending data
|
||||
if ctx /= Void then
|
||||
if ctx.has_upload_filename then
|
||||
l_upload_filename := ctx.upload_filename
|
||||
end
|
||||
|
||||
if ctx.has_upload_data then
|
||||
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
|
||||
-- Send as form-urlencoded
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_url_encoded_string
|
||||
headers.force (l_upload_data.count.out, "Content-Length")
|
||||
|
||||
else
|
||||
-- create form using multipart/form-data encoding
|
||||
l_boundary := new_mime_boundary
|
||||
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
if l_form_data /= Void then
|
||||
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")
|
||||
end
|
||||
end
|
||||
elseif l_upload_data /= Void then
|
||||
check ctx.has_upload_data end
|
||||
if not headers.has ("Content-Type") then
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
end
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
elseif l_upload_filename /= Void then
|
||||
check ctx.has_upload_filename end
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.readable then
|
||||
headers.extend (l_upload_file.count.out, "Content-Length")
|
||||
end
|
||||
check l_upload_file /= Void end
|
||||
end
|
||||
end
|
||||
|
||||
-- FIXME: check usage of headers and specific header variable.
|
||||
--| only one Cookie: is allowed, so merge multiple into one;
|
||||
--| if Host is in header, use that one.
|
||||
-- Compute Request line.
|
||||
create s.make_from_string (request_method.as_upper)
|
||||
s.append_character (' ')
|
||||
s.append (l_request_uri)
|
||||
s.append_character (' ')
|
||||
if l_is_http_1_0_request then
|
||||
s.append ("HTTP/1.0")
|
||||
else
|
||||
s.append ("HTTP/1.1")
|
||||
end
|
||||
s.append (Http_end_of_header_line)
|
||||
|
||||
-- Compute Header Host:
|
||||
s.append (Http_host_header)
|
||||
s.append (": ")
|
||||
if attached headers [Http_host_header] as h_host then
|
||||
s.append (h_host)
|
||||
else
|
||||
s.append (l_host)
|
||||
end
|
||||
s.append (http_end_of_header_line)
|
||||
if not headers.has ("Connection") then
|
||||
if l_is_http_1_0_request then
|
||||
s.append ("Connection: keep-alive")
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
end
|
||||
|
||||
-- Append the given request headers
|
||||
l_cookie := Void
|
||||
if not headers.is_empty then
|
||||
across
|
||||
headers as ic
|
||||
loop
|
||||
l_header_key := ic.key
|
||||
if l_header_key.same_string_general ("Host") then
|
||||
-- FIXME: already handled elsewhere!
|
||||
elseif l_header_key.same_string_general ("Cookie") then
|
||||
-- FIXME: need cookie merging.
|
||||
l_cookie := ic.item
|
||||
else
|
||||
s.append (ic.key)
|
||||
s.append (": ")
|
||||
s.append (ic.item)
|
||||
s.append (Http_end_of_header_line)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Compute Header Cookie: if needed
|
||||
-- Use session cookie
|
||||
if l_cookie = Void then
|
||||
l_cookie := session.cookie
|
||||
else
|
||||
-- Overwrite potential session cookie, if specified by the user.
|
||||
end
|
||||
if l_cookie /= Void then
|
||||
s.append ("Cookie: ")
|
||||
s.append (l_cookie)
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
|
||||
--| End of client header.
|
||||
s.append (Http_end_of_header_line)
|
||||
|
||||
if l_upload_data /= Void then
|
||||
s.append (l_upload_data)
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
|
||||
--| Note that any remaining file to upload will be done directly via the socket
|
||||
--| to optimize memory usage
|
||||
|
||||
|
||||
--|-----------------------------|--
|
||||
--| Request preparation is done |--
|
||||
--|-----------------------------|--
|
||||
|
||||
if l_socket.ready_for_writing then
|
||||
--| Socket is ready for writing, so let's send the request.
|
||||
|
||||
--|-------------------------|--
|
||||
--| Send request |--
|
||||
--|-------------------------|--
|
||||
|
||||
if session.is_header_sent_verbose then
|
||||
log ("> Sending:%N")
|
||||
log (s)
|
||||
end
|
||||
l_socket.put_string (s)
|
||||
--| Send remaining payload data, if needed.
|
||||
if l_upload_file /= Void then
|
||||
-- i.e: not yet processed
|
||||
append_file_content_to_socket (l_upload_file, l_upload_file.count, l_socket)
|
||||
end
|
||||
|
||||
--|-------------------------|--
|
||||
--| Get response. |--
|
||||
--| Get header message |--
|
||||
--|-------------------------|--
|
||||
if is_ready_for_reading (l_socket) then
|
||||
create l_message.make_empty
|
||||
append_socket_header_content_to (Result, l_socket, l_message)
|
||||
if session.is_header_received_verbose then
|
||||
log ("< Receiving:%N")
|
||||
log (l_message)
|
||||
end
|
||||
l_prev_header := Result.raw_header
|
||||
Result.set_raw_header (l_message.string)
|
||||
l_message.append (http_end_of_header_line)
|
||||
|
||||
if not Result.error_occurred then
|
||||
-- Get information from header
|
||||
l_content_length := -1
|
||||
if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then
|
||||
l_content_length := s_len.to_integer
|
||||
end
|
||||
l_location := Result.header ("Location")
|
||||
if attached Result.header ("Set-Cookie") as s_cookies then
|
||||
session.set_cookie (s_cookies)
|
||||
end
|
||||
|
||||
-- Keep-alive connection?
|
||||
-- with HTTP/1.1, this is the default, and could be changed by Connection: close
|
||||
-- with HTTP/1.0, it requires "Connection: keep-alive" header line.
|
||||
if attached Result.header ("Connection") as s_connection then
|
||||
l_is_keep_alive := s_connection.same_string ("keep-alive")
|
||||
else
|
||||
l_is_keep_alive := not Result.is_http_1_0
|
||||
end
|
||||
|
||||
-- Get content if any.
|
||||
append_socket_content_to (Result, l_socket, l_content_length, l_message)
|
||||
-- Restore previous header
|
||||
Result.set_raw_header (l_prev_header)
|
||||
-- Set message
|
||||
Result.set_response_message (l_message, ctx)
|
||||
-- Check status code.
|
||||
check status_coherent: attached Result.status_line as l_status_line implies l_status_line.has_substring (Result.status.out) end
|
||||
|
||||
if l_is_keep_alive then
|
||||
session.set_persistent_connection (create {NET_HTTP_CLIENT_CONNECTION}.make (l_socket, l_host, l_port))
|
||||
else
|
||||
session.set_persistent_connection (Void)
|
||||
end
|
||||
|
||||
-- follow redirect
|
||||
if l_location /= Void then
|
||||
if Result.redirections_count < max_redirects then
|
||||
initialize (l_location, ctx)
|
||||
redirection_response := response
|
||||
redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body)
|
||||
Result := redirection_response
|
||||
end
|
||||
end
|
||||
if not l_is_keep_alive then
|
||||
l_socket.cleanup
|
||||
end
|
||||
end
|
||||
else
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: Read Timeout!%N")
|
||||
end
|
||||
Result.set_error_message ("Read Timeout")
|
||||
end
|
||||
else
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: Write Timeout!%N")
|
||||
end
|
||||
Result.set_error_message ("Write Timeout")
|
||||
end
|
||||
else
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: Could not connect!%N")
|
||||
end
|
||||
Result.set_error_message ("Could not connect")
|
||||
end
|
||||
else
|
||||
create Result.make (url)
|
||||
Result.set_error_message ("Error: internal error")
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
retry
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
log (m: READABLE_STRING_8)
|
||||
-- Output log messages.
|
||||
do
|
||||
io.error.put_string (m)
|
||||
end
|
||||
|
||||
is_ready_for_reading (a_socket: NETWORK_STREAM_SOCKET): BOOLEAN
|
||||
-- Is `a_socket' ready for reading?
|
||||
do
|
||||
Result := a_socket.ready_for_reading
|
||||
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 data and uploaded files converted to mime string.
|
||||
-- TODO: design a proper MIME... component.
|
||||
local
|
||||
l_path: PATH
|
||||
l_mime_type: READABLE_STRING_8
|
||||
l_upload_file: detachable RAW_FILE
|
||||
l_mime_type_mapping: HTTP_FILE_EXTENSION_MIME_MAPPING
|
||||
do
|
||||
create Result.make (100)
|
||||
across
|
||||
a_form_parameters as ic
|
||||
loop
|
||||
Result.append (a_mime_boundary)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Disposition: form-data; name=")
|
||||
Result.append_character ('%"')
|
||||
Result.append (string_to_mime_encoded_string (ic.key))
|
||||
Result.append_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
|
||||
then
|
||||
l_mime_type := l_mt
|
||||
else
|
||||
l_mime_type := "application/octet-stream"
|
||||
end
|
||||
|
||||
Result.append (a_mime_boundary)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Disposition: form-data; name=%"")
|
||||
Result.append (string_to_mime_encoded_string (a_upload_filename))
|
||||
Result.append_character ('%"')
|
||||
Result.append ("; filename=%"")
|
||||
Result.append (string_to_mime_encoded_string (a_upload_filename))
|
||||
Result.append_character ('%"')
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append ("Content-Type: ")
|
||||
Result.append (l_mime_type)
|
||||
Result.append (http_end_of_header_line)
|
||||
Result.append (http_end_of_header_line)
|
||||
|
||||
create l_upload_file.make_with_path (l_path)
|
||||
if l_upload_file.exists and then l_upload_file.is_access_readable then
|
||||
append_file_content_to (l_upload_file, l_upload_file.count, Result)
|
||||
-- Reset l_upload_file to Void, since the related content is already processed.
|
||||
l_upload_file := Void
|
||||
end
|
||||
Result.append (http_end_of_header_line)
|
||||
end
|
||||
Result.append (a_mime_boundary)
|
||||
Result.append ("--") --| end
|
||||
end
|
||||
|
||||
string_to_mime_encoded_string (s: READABLE_STRING_GENERAL): STRING
|
||||
-- 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!
|
||||
Result := utf.utf_32_string_to_utf_8_string_8 (s)
|
||||
end
|
||||
|
||||
append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: NETWORK_STREAM_SOCKET)
|
||||
-- Append `a_file' content to `a_output'.
|
||||
-- If `a_len' >= 0 then read only `a_len' characters.
|
||||
require
|
||||
a_file_readable: a_file.exists and then a_file.is_access_readable
|
||||
local
|
||||
l_was_open: BOOLEAN
|
||||
l_count: INTEGER
|
||||
do
|
||||
if a_len >= 0 then
|
||||
l_count := a_len
|
||||
else
|
||||
l_count := a_file.count
|
||||
end
|
||||
if l_count > 0 then
|
||||
l_was_open := a_file.is_open_read
|
||||
if a_file.is_open_read then
|
||||
l_was_open := True
|
||||
else
|
||||
a_file.open_read
|
||||
end
|
||||
from
|
||||
until
|
||||
l_count = 0 or a_file.exhausted
|
||||
loop
|
||||
a_file.read_stream_thread_aware (l_count.min (2_048))
|
||||
a_output.put_string (a_file.last_string)
|
||||
l_count := l_count - a_file.bytes_read
|
||||
end
|
||||
if not l_was_open then
|
||||
a_file.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_file_content_to (a_file: FILE; a_len: INTEGER; a_output: STRING)
|
||||
-- Append `a_file' content to `a_output'.
|
||||
-- If `a_len' >= 0 then read only `a_len' characters.
|
||||
require
|
||||
a_file_readable: a_file.exists and then a_file.is_access_readable
|
||||
local
|
||||
l_was_open: BOOLEAN
|
||||
l_count: INTEGER
|
||||
do
|
||||
if a_len >= 0 then
|
||||
l_count := a_len
|
||||
else
|
||||
l_count := a_file.count
|
||||
end
|
||||
if l_count > 0 then
|
||||
l_was_open := a_file.is_open_read
|
||||
if a_file.is_open_read then
|
||||
l_was_open := True
|
||||
else
|
||||
a_file.open_read
|
||||
end
|
||||
from
|
||||
|
||||
until
|
||||
l_count = 0 or a_file.exhausted
|
||||
loop
|
||||
a_file.read_stream_thread_aware (l_count.min (2_048))
|
||||
a_output.append (a_file.last_string)
|
||||
l_count := l_count - a_file.bytes_read
|
||||
end
|
||||
if not l_was_open then
|
||||
a_file.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_socket_header_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
|
||||
-- Get header from `a_socket' into `a_output'.
|
||||
local
|
||||
s: READABLE_STRING_8
|
||||
do
|
||||
from
|
||||
s := ""
|
||||
until
|
||||
s.same_string ("%R") or not a_socket.readable or a_response.error_occurred
|
||||
loop
|
||||
a_socket.read_line_thread_aware
|
||||
s := a_socket.last_string
|
||||
if s.is_empty then
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: ERROR: zero byte read when receiving header.%N")
|
||||
end
|
||||
a_response.set_error_message ("Read zero byte, expecting header line")
|
||||
elseif s.same_string ("%R") then
|
||||
-- Reach end of header
|
||||
else
|
||||
a_output.append (s)
|
||||
a_output.append_character ('%N')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_len: INTEGER; a_output: STRING)
|
||||
-- Get content from `a_socket' and append it to `a_output'.
|
||||
-- If `a_len' is negative, try to get as much as possible,
|
||||
-- this is probably HTTP/1.0 without any Content-Length.
|
||||
local
|
||||
s: STRING_8
|
||||
r: INTEGER -- remaining count
|
||||
n,l_chunk_size, l_count: INTEGER
|
||||
do
|
||||
if a_socket.readable then
|
||||
if a_len >= 0 then
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: Content-Length="+ a_len.out +"%N")
|
||||
end
|
||||
from
|
||||
r := a_len
|
||||
until
|
||||
r = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||
loop
|
||||
a_socket.read_stream_thread_aware (r)
|
||||
l_count := l_count + a_socket.bytes_read
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
|
||||
log ("Debug: - current count=" + l_count.out + "%N")
|
||||
end
|
||||
r := r - a_socket.bytes_read
|
||||
a_output.append (a_socket.last_string)
|
||||
end
|
||||
check full_content_read: not a_response.error_occurred implies l_count = a_len end
|
||||
elseif attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then
|
||||
append_socket_chunked_content_to (a_response, a_socket, a_output)
|
||||
else
|
||||
-- No Content-Length and no chunked transfer encoding!
|
||||
-- maybe HTTP/1.0 ?
|
||||
-- FIXME: check solution!
|
||||
from
|
||||
l_count := 0
|
||||
l_chunk_size := 1_024
|
||||
n := l_chunk_size --| value to satisfy until condition on first loop.
|
||||
until
|
||||
n < l_chunk_size or not a_socket.readable
|
||||
loop
|
||||
a_socket.read_stream_thread_aware (l_chunk_size)
|
||||
s := a_socket.last_string
|
||||
n := a_socket.bytes_read
|
||||
l_count := l_count + n
|
||||
a_output.append (s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_socket_chunked_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
|
||||
-- Get chunked content from `a_socket' and append it to `a_output'.
|
||||
require
|
||||
socket_readable: a_socket.readable
|
||||
has_chunked_transfer_encoding: attached a_response.header ("Transfer-Encoding") as l_enc and then
|
||||
l_enc.is_case_insensitive_equal ("chunked")
|
||||
local
|
||||
s: STRING_8
|
||||
r: INTEGER -- remaining count
|
||||
n,pos, l_count: INTEGER
|
||||
hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
|
||||
do
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: Chunked encoding%N")
|
||||
end
|
||||
from
|
||||
create hexa2int.make
|
||||
n := 1
|
||||
until
|
||||
n = 0 or not a_socket.readable
|
||||
loop
|
||||
a_socket.read_line_thread_aware -- Read chunk info
|
||||
s := a_socket.last_string
|
||||
s.right_adjust
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: - chunk info='" + s + "'%N")
|
||||
end
|
||||
pos := s.index_of (';', 1)
|
||||
if pos > 0 then
|
||||
s.keep_head (pos - 1)
|
||||
end
|
||||
if s.is_empty then
|
||||
n := 0
|
||||
else
|
||||
hexa2int.parse_string_with_type (s, hexa2int.type_integer)
|
||||
if hexa2int.parse_successful then
|
||||
n := hexa2int.parsed_integer
|
||||
else
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: - chunk size=" + n.out + "%N")
|
||||
end
|
||||
if n > 0 then
|
||||
from
|
||||
r := n
|
||||
until
|
||||
r = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||
loop
|
||||
a_socket.read_stream_thread_aware (r)
|
||||
l_count := l_count + a_socket.bytes_read
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: - byte read=" + a_socket.bytes_read.out + "%N")
|
||||
log ("Debug: - current count=" + l_count.out + "%N")
|
||||
end
|
||||
r := r - a_socket.bytes_read
|
||||
a_output.append (a_socket.last_string)
|
||||
end
|
||||
|
||||
a_socket.read_character
|
||||
check a_socket.last_character = '%R' end
|
||||
a_socket.read_character
|
||||
check a_socket.last_character = '%N' end
|
||||
if session.is_debug_verbose then
|
||||
log ("Debug: - Found CRNL %N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_mime_boundary: STRING
|
||||
-- New MIME boundary.
|
||||
do
|
||||
-- FIXME: better boundary creation
|
||||
Result := "----------------------------5eadfcf3bb3e"
|
||||
end
|
||||
|
||||
invariant
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,184 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel NET library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT_SESSION
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_SESSION
|
||||
redefine
|
||||
close
|
||||
end
|
||||
|
||||
NET_HTTP_CLIENT_INFO
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_available: BOOLEAN
|
||||
-- Is interface usable?
|
||||
do
|
||||
Result := True
|
||||
if base_url.starts_with_general ("https://") then
|
||||
Result := has_https_support
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Access: persistent connection
|
||||
|
||||
persistent_connection: detachable NET_HTTP_CLIENT_CONNECTION
|
||||
-- Socket used for persistent connection purpose.
|
||||
|
||||
feature -- Element change: persistent connection
|
||||
|
||||
set_persistent_connection (a_connection: like persistent_connection)
|
||||
-- Set `persistent_connection' to `a_connection'.
|
||||
do
|
||||
persistent_connection := a_connection
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
close
|
||||
-- <Precursor>
|
||||
do
|
||||
if attached persistent_connection as l_connection then
|
||||
persistent_connection := Void
|
||||
l_connection.socket.cleanup
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Custom
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
do
|
||||
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
||||
Result := req.response
|
||||
end
|
||||
|
||||
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Same as `custom' but including upload data `a_data'.
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Same as `custom' but including upload file `fn'.
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("GET", a_path, ctx)
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("HEAD", a_path, ctx)
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := custom ("DELETE", a_path, ctx)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
l_data: detachable READABLE_STRING_8
|
||||
do
|
||||
ctx := a_ctx
|
||||
if data /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_data (data)
|
||||
end
|
||||
if fn /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_filename (fn)
|
||||
end
|
||||
if ctx /= Void then
|
||||
l_data := ctx.upload_data
|
||||
if l_data /= Void and a_method.is_case_insensitive_equal_general ("PUT") then
|
||||
check put_conflict_file_and_data: not ctx.has_upload_filename end
|
||||
end
|
||||
end
|
||||
|
||||
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
||||
Result := req.response
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
note
|
||||
description: "Additional information related to NET HTTP Client.."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT_INFO
|
||||
|
||||
feature -- Access
|
||||
|
||||
has_https_support: BOOLEAN = False
|
||||
-- Is HTTPS supported?
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
note
|
||||
description: "[
|
||||
A fake SSL network stream socket... when SSL is disabled at compilation time.
|
||||
Its behavior is similar to NETWORK_STREAM_SOCKET.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
SSL_NETWORK_STREAM_SOCKET
|
||||
|
||||
inherit
|
||||
NETWORK_STREAM_SOCKET
|
||||
|
||||
create
|
||||
make, make_empty, make_client_by_port, make_client_by_address_and_port, make_server_by_port, make_loopback_server_by_port
|
||||
|
||||
create {SSL_NETWORK_STREAM_SOCKET}
|
||||
make_from_descriptor_and_address, create_from_descriptor
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
note
|
||||
description: "Additional information related to NET HTTP Client.."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NET_HTTP_CLIENT_INFO
|
||||
|
||||
feature -- Access
|
||||
|
||||
has_https_support: BOOLEAN = True
|
||||
-- Is HTTPS supported?
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,110 @@
|
||||
note
|
||||
description : "[
|
||||
NULL version of HTTP_CLIENT_SESSION.
|
||||
It is used if no implementation is available (libcurl or net)
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NULL_HTTP_CLIENT_SESSION
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_SESSION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Custom
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
create Result.make (base_url + a_path)
|
||||
end
|
||||
|
||||
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("GET", a_path, ctx)
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("HEAD", a_path, ctx)
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("DELETE", a_path, ctx)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom (a_method, a_path, a_ctx)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_available: BOOLEAN = False
|
||||
-- Is interface usable?
|
||||
|
||||
note
|
||||
copyright: "2011-2015, 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
|
||||
BIN
library/network/http_client/tests/logo.jpg
Normal file
BIN
library/network/http_client/tests/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-10-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-10-0 http://www.eiffel.com/developers/xml/configuration-1-10-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
|
||||
<target name="test_http_client">
|
||||
<root class="TEST" feature="make"/>
|
||||
<file_rule>
|
||||
@@ -10,6 +10,9 @@
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<variable name="netssl_http_client_enabled" value="false"/>
|
||||
<variable name="net_http_client_disabled" value="false"/>
|
||||
<variable name="libcurl_http_client_disabled" value="false"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http_client" location="..\http_client-safe.ecf" readonly="false" use_application_options="true">
|
||||
<option>
|
||||
@@ -17,6 +20,19 @@
|
||||
</option>
|
||||
</library>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||
<tests name="tests" location=".\"/>
|
||||
<tests name="tests" location=".\">
|
||||
<file_rule>
|
||||
<exclude>.*libcurl_.*.e$</exclude>
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</file_rule>
|
||||
<file_rule>
|
||||
<exclude>.*net_.*.e$</exclude>
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" value="true"/>
|
||||
</condition>
|
||||
</file_rule>
|
||||
</tests>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -6,17 +6,46 @@ create
|
||||
feature -- Init
|
||||
|
||||
make
|
||||
local
|
||||
null: NULL_HTTP_CLIENT_SESSION
|
||||
do
|
||||
create null.make ("http://example.com/")
|
||||
check not null.is_available end
|
||||
|
||||
test_get_with_authentication
|
||||
test_http_client
|
||||
end
|
||||
|
||||
test_get_with_authentication
|
||||
local
|
||||
cl: DEFAULT_HTTP_CLIENT
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php
|
||||
-- check header WWW-Authenticate is received (authentication successful)
|
||||
create cl
|
||||
sess := cl.new_session ("http://browserspy.dk")
|
||||
sess.set_credentials ("test", "test")
|
||||
create ctx.make_with_credentials_required
|
||||
if attached sess.get ("/password-ok.php", ctx) as res then
|
||||
if attached {READABLE_STRING_8} res.body as l_body then
|
||||
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
||||
else
|
||||
assert ("has body", False)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test_http_client
|
||||
-- New test routine
|
||||
local
|
||||
sess: LIBCURL_HTTP_CLIENT_SESSION
|
||||
cl: DEFAULT_HTTP_CLIENT
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
create sess.make ("http://www.google.com")
|
||||
create cl
|
||||
sess := cl.new_session ("http://www.google.com")
|
||||
if attached sess.get ("/search?q=eiffel", Void) as res then
|
||||
assert ("Get returned without error", not res.error_occurred)
|
||||
create h.make_empty
|
||||
@@ -42,7 +71,7 @@ feature -- Init
|
||||
do
|
||||
if not b then
|
||||
create e
|
||||
e.set_message (m)
|
||||
e.set_description (m)
|
||||
e.raise
|
||||
end
|
||||
end
|
||||
|
||||
1
library/network/http_client/tests/test.txt
Normal file
1
library/network/http_client/tests/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a text sample for testing HTTP Client library.
|
||||
@@ -7,21 +7,27 @@ note
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
TEST_HTTP_CLIENT
|
||||
deferred class
|
||||
TEST_HTTP_CLIENT_I
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Test routines
|
||||
|
||||
test_http_client
|
||||
-- New test routine
|
||||
local
|
||||
sess: LIBCURL_HTTP_CLIENT_SESSION
|
||||
sess: like new_session
|
||||
h: STRING_8
|
||||
do
|
||||
create sess.make ("http://www.google.com")
|
||||
sess := new_session ("http://www.google.com")
|
||||
if attached sess.get ("/search?q=eiffel", Void) as res then
|
||||
assert ("Get returned without error", not res.error_occurred)
|
||||
create h.make_empty
|
||||
@@ -38,8 +44,33 @@ feature -- Test routines
|
||||
assert ("missing body", False)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
else
|
||||
assert ("Not found", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_http_client_ssl
|
||||
-- New test routine
|
||||
local
|
||||
sess: like new_session
|
||||
h: STRING_8
|
||||
do
|
||||
sess := new_session ("https://www.eiffel.org")
|
||||
sess.set_is_insecure (True)
|
||||
if attached sess.get ("/welcome", Void) as res then
|
||||
assert ("Get returned without error", 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
|
||||
if attached res.body as l_body then
|
||||
assert ("body not empty", not l_body.is_empty)
|
||||
else
|
||||
assert ("missing body", False)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
end
|
||||
end
|
||||
|
||||
43
library/network/http_client/tests/test_libcurl_http_client.e
Normal file
43
library/network/http_client/tests/test_libcurl_http_client.e
Normal file
@@ -0,0 +1,43 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
TEST_LIBCURL_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
TEST_HTTP_CLIENT_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
do
|
||||
create {LIBCURL_HTTP_CLIENT_SESSION} Result.make (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
test_libcurl_http_client
|
||||
do
|
||||
test_http_client
|
||||
end
|
||||
|
||||
test_libcurl_http_client_ssl
|
||||
do
|
||||
test_http_client_ssl
|
||||
end
|
||||
|
||||
test_libcurl_headers
|
||||
do
|
||||
test_headers
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
67
library/network/http_client/tests/test_libcurl_with_web.e
Normal file
67
library/network/http_client/tests/test_libcurl_with_web.e
Normal file
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "[
|
||||
Objects that ...
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
TEST_LIBCURL_WITH_WEB
|
||||
|
||||
inherit
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
local
|
||||
cl: LIBCURL_HTTP_CLIENT
|
||||
do
|
||||
create cl
|
||||
Result := cl.new_session (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
libcurl_test_post_url_encoded
|
||||
do
|
||||
test_post_url_encoded
|
||||
end
|
||||
|
||||
libcurl_test_post_with_form_data
|
||||
do
|
||||
test_post_with_form_data
|
||||
end
|
||||
|
||||
libcurl_test_post_with_file
|
||||
do
|
||||
test_post_with_file
|
||||
end
|
||||
|
||||
libcurl_test_put_with_file
|
||||
do
|
||||
test_put_with_file
|
||||
end
|
||||
|
||||
libcurl_test_put_with_data
|
||||
do
|
||||
test_put_with_data
|
||||
end
|
||||
|
||||
libcurl_test_post_with_file_and_form_data
|
||||
do
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
libcurl_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
end
|
||||
|
||||
libcurl_test_get_with_authentication
|
||||
do
|
||||
test_get_with_authentication
|
||||
end
|
||||
|
||||
end
|
||||
86
library/network/http_client/tests/test_net_http_client.e
Normal file
86
library/network/http_client/tests/test_net_http_client.e
Normal file
@@ -0,0 +1,86 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
TEST_NET_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
TEST_HTTP_CLIENT_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
do
|
||||
create {NET_HTTP_CLIENT_SESSION} Result.make (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
test_net_http_client
|
||||
do
|
||||
test_http_client
|
||||
end
|
||||
|
||||
test_net_http_client_ssl
|
||||
do
|
||||
test_http_client_ssl
|
||||
end
|
||||
|
||||
test_net_headers
|
||||
do
|
||||
test_headers
|
||||
end
|
||||
|
||||
test_persistent_connection
|
||||
local
|
||||
sess: like new_session
|
||||
h: STRING_8
|
||||
do
|
||||
sess := new_session ("http://www.google.fr")
|
||||
if attached sess.get ("/", Void) as res then
|
||||
assert ("Get returned without error", 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
|
||||
if attached res.body as l_body then
|
||||
assert ("body not empty", not l_body.is_empty)
|
||||
else
|
||||
assert ("missing body", False)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
end
|
||||
if attached sess.get ("/", Void) as res then
|
||||
assert ("Get returned without error", 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
|
||||
if attached res.body as l_body then
|
||||
assert ("body not empty", not l_body.is_empty)
|
||||
else
|
||||
assert ("missing body", False)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
end
|
||||
sess.close
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
67
library/network/http_client/tests/test_net_with_web.e
Normal file
67
library/network/http_client/tests/test_net_with_web.e
Normal file
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "[
|
||||
Objects that ...
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
TEST_NET_WITH_WEB
|
||||
|
||||
inherit
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
local
|
||||
cl: NET_HTTP_CLIENT
|
||||
do
|
||||
create cl
|
||||
Result := cl.new_session (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
net_test_post_url_encoded
|
||||
do
|
||||
test_post_url_encoded
|
||||
end
|
||||
|
||||
net_test_post_with_form_data
|
||||
do
|
||||
test_post_with_form_data
|
||||
end
|
||||
|
||||
net_test_post_with_file
|
||||
do
|
||||
test_post_with_file
|
||||
end
|
||||
|
||||
net_test_put_with_file
|
||||
do
|
||||
test_put_with_file
|
||||
end
|
||||
|
||||
net_test_put_with_data
|
||||
do
|
||||
test_put_with_data
|
||||
end
|
||||
|
||||
net_test_post_with_file_and_form_data
|
||||
do
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
net_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
end
|
||||
|
||||
net_test_get_with_authentication
|
||||
do
|
||||
test_get_with_authentication
|
||||
end
|
||||
|
||||
end
|
||||
302
library/network/http_client/tests/test_with_web_i.e
Normal file
302
library/network/http_client/tests/test_with_web_i.e
Normal file
@@ -0,0 +1,302 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
deferred class
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
redefine
|
||||
on_prepare
|
||||
end
|
||||
|
||||
feature -- Initialization
|
||||
|
||||
on_prepare
|
||||
do
|
||||
Precursor
|
||||
global_requestbin_path := new_requestbin_path
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Requestbin
|
||||
|
||||
global_requestbin_path: detachable READABLE_STRING_8
|
||||
|
||||
new_requestbin_path: detachable STRING
|
||||
local
|
||||
i,j: INTEGER
|
||||
do
|
||||
if
|
||||
attached new_session ("http://requestb.in") as sess and then
|
||||
attached sess.post ("/api/v1/bins", Void, Void) as resp
|
||||
then
|
||||
if resp.error_occurred then
|
||||
print ("Error occurred!%N")
|
||||
elseif attached resp.body as l_content then
|
||||
|
||||
i := l_content.substring_index ("%"name%":", 1)
|
||||
if i > 0 then
|
||||
j := l_content.index_of (',', i + 1)
|
||||
if j > 0 then
|
||||
Result := l_content.substring (i + 7, j - 1)
|
||||
Result.adjust
|
||||
if Result.starts_with ("%"") then
|
||||
Result.remove_head (1)
|
||||
end
|
||||
if Result.ends_with ("%"") then
|
||||
Result.remove_tail (1)
|
||||
end
|
||||
if not Result.starts_with ("/") then
|
||||
Result.prepend_character ('/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
test_post_url_encoded
|
||||
local
|
||||
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)
|
||||
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.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_file
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
test_put_with_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_data ("This is a test for http client.%N")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.put (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
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_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("logo.jpg")
|
||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_get_with_redirection
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
||||
-- check headers received (printed in console)
|
||||
sess := new_session ("http://google.com")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_get_with_authentication
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php
|
||||
-- check header WWW-Authenticate is received (authentication successful)
|
||||
sess := new_session ("http://browserspy.dk")
|
||||
sess.set_credentials ("test", "test")
|
||||
create ctx.make_with_credentials_required
|
||||
if attached sess.get ("/password-ok.php", ctx) as res then
|
||||
if attached {READABLE_STRING_8} res.body as l_body then
|
||||
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
||||
else
|
||||
assert ("has body", False)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -122,8 +122,8 @@ feature {NONE} -- Internal
|
||||
feature -- Conversion to string
|
||||
|
||||
yyyy_mmm_dd_string: STRING
|
||||
-- String representation YYYY mmm dd
|
||||
-- 2012 Dec 25
|
||||
-- String representation [YYYY mmm dd]
|
||||
-- ex: 2012 Dec 25
|
||||
do
|
||||
create Result.make (11)
|
||||
append_date_time_to_yyyy_mmm_dd_string (date_time, Result)
|
||||
@@ -131,7 +131,8 @@ feature -- Conversion to string
|
||||
|
||||
rfc1123_string: STRING
|
||||
-- String representation following RFC 1123.
|
||||
--| Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
|
||||
-- format: [ddd, dd mmm yyyy hh:mi:ss GMT]
|
||||
-- ex: Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
|
||||
local
|
||||
s: like internal_rfc1123_string
|
||||
do
|
||||
@@ -145,7 +146,8 @@ feature -- Conversion to string
|
||||
end
|
||||
|
||||
rfc850_string: STRING
|
||||
-- String representation following RFC 850
|
||||
-- String representation following RFC 850.
|
||||
-- format: [mmm, dd-mmm-yy hh:mi:ss GMT]
|
||||
do
|
||||
create Result.make (32)
|
||||
append_date_time_to_rfc850_string (date_time, Result)
|
||||
@@ -153,6 +155,7 @@ feature -- Conversion to string
|
||||
|
||||
ansi_c_string: STRING
|
||||
-- ANSI C's asctime() format
|
||||
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
do
|
||||
create Result.make (32)
|
||||
append_date_time_to_ansi_c_string (date_time, Result)
|
||||
@@ -161,74 +164,34 @@ feature -- Conversion to string
|
||||
feature -- Conversion into string
|
||||
|
||||
append_to_yyyy_mmm_dd_string (s: STRING_GENERAL)
|
||||
local
|
||||
dt: DATE_TIME
|
||||
-- Append `datetime' as [yyyy mmm dd] format to `s'.
|
||||
do
|
||||
dt := date_time
|
||||
append_integer_to (dt.year, s) -- yyyy
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_month_mmm_to (dt.month, s) -- mmm
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_integer_to (dt.day, s) -- dd
|
||||
append_date_time_to_yyyy_mmm_dd_string (date_time, s)
|
||||
end
|
||||
|
||||
append_to_rfc1123_string (s: STRING_GENERAL)
|
||||
local
|
||||
dt: DATE_TIME
|
||||
-- Append `date_time' as [ddd, dd mmm yyyy hh:mi:ss GMT] format to `s'.
|
||||
do
|
||||
dt := date_time
|
||||
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
|
||||
s.append_code (44) -- 44 ',' -- ','
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_integer_to (dt.day, s) -- dd
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_month_mmm_to (dt.month, s) -- mmm
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_integer_to (dt.year, s) -- YYYY
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
|
||||
s.append (" GMT") -- SPace + GMT
|
||||
append_date_time_to_rfc1123_string (date_time, s)
|
||||
end
|
||||
|
||||
append_rfc850_string (s: STRING_GENERAL)
|
||||
local
|
||||
dt: DATE_TIME
|
||||
-- Append `date_time' as [mmm, dd-mmm-yy hh:mi:ss GMT] format to `s'.
|
||||
do
|
||||
dt := date_time
|
||||
append_day_name_to (dt.date.day_of_the_week, s) -- mmm
|
||||
s.append_code (44) -- 44 ',' -- ','
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_integer_to (dt.day, s) -- dd
|
||||
s.append_code (45) -- 45 '-' -- '-'
|
||||
append_month_mmm_to (dt.month, s) -- mmm
|
||||
s.append_code (45) -- 45 '-' -- '-'
|
||||
append_integer_to (dt.year \\ 100, s) -- yy
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
|
||||
s.append (" GMT") -- SPace + GMT
|
||||
append_date_time_to_rfc850_string (date_time, s)
|
||||
end
|
||||
|
||||
append_to_ansi_c_string (s: STRING_GENERAL)
|
||||
-- Append `date_time' as ANSI C's asctime format to `s'.
|
||||
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
local
|
||||
dt: DATE_TIME
|
||||
do
|
||||
dt := date_time
|
||||
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_month_mmm_to (dt.month, s) -- mmm
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_integer_to (dt.day, s) -- d
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_2_digits_time_to (dt.time, s) -- hh:mi:ss
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
append_integer_to (dt.year, s) -- yyyy
|
||||
append_date_time_to_ansi_c_string (date_time, s)
|
||||
end
|
||||
|
||||
feature -- Conversion into string
|
||||
|
||||
append_date_time_to_yyyy_mmm_dd_string (dt: DATE_TIME; s: STRING_GENERAL)
|
||||
-- Append `dt' as [yyyy mmm dd] format to `s'.
|
||||
do
|
||||
append_integer_to (dt.year, s) -- yyyy
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
@@ -238,6 +201,7 @@ feature -- Conversion into string
|
||||
end
|
||||
|
||||
append_date_time_to_rfc1123_string (dt: DATE_TIME; s: STRING_GENERAL)
|
||||
-- Append `dt' as [ddd, dd mmm yyyy hh:mi:ss GMT] format to `s'.
|
||||
do
|
||||
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
|
||||
s.append_code (44) -- 44 ',' -- ','
|
||||
@@ -253,6 +217,7 @@ feature -- Conversion into string
|
||||
end
|
||||
|
||||
append_date_time_to_rfc850_string (dt: DATE_TIME; s: STRING_GENERAL)
|
||||
-- Append `dt' as [mmm, dd-mmm-yy hh:mi:ss GMT] format to `s'.
|
||||
do
|
||||
append_day_name_to (dt.date.day_of_the_week, s) -- mmm
|
||||
s.append_code (44) -- 44 ',' -- ','
|
||||
@@ -268,7 +233,8 @@ feature -- Conversion into string
|
||||
end
|
||||
|
||||
append_date_time_to_ansi_c_string (dt: DATE_TIME; s: STRING_GENERAL)
|
||||
--| Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
-- Append `dt' as ANSI C's asctime format to `s'.
|
||||
-- Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
|
||||
do
|
||||
append_day_ddd_to (dt.date.day_of_the_week, s) -- ddd
|
||||
s.append_code (32) -- 32 ' ' -- SPace
|
||||
@@ -294,7 +260,7 @@ feature -- Status report
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
feature -- Helper routines.
|
||||
|
||||
append_2_digits_integer_to (i: INTEGER; s: STRING_GENERAL)
|
||||
require
|
||||
@@ -599,7 +565,11 @@ feature {NONE} -- Implementation
|
||||
t.extend (s[i].as_upper)
|
||||
i := i + 1
|
||||
end
|
||||
if t.same_string ("GMT") or t.same_string ("UTC") then
|
||||
if
|
||||
t.same_string ("GMT") -- for instance: GMT+0002
|
||||
or t.same_string ("UTC") -- for instance: UTC+0002
|
||||
or t.is_empty -- for instance: +0002
|
||||
then
|
||||
from until i > n or else not s[i].is_space loop i := i + 1 end
|
||||
if i <= n then
|
||||
t.wipe_out
|
||||
|
||||
@@ -81,7 +81,7 @@ feature -- Element change
|
||||
-- Unset `http_server_name' value.
|
||||
do
|
||||
http_server_name := Void
|
||||
ensure
|
||||
ensure
|
||||
unset_http_server_name: http_server_name = Void
|
||||
end
|
||||
|
||||
@@ -202,11 +202,6 @@ feature -- SSL Helpers
|
||||
deferred
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_ssl_3
|
||||
-- Set `ssl_protocol' with `Ssl_3'.
|
||||
deferred
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_tls_1_0
|
||||
-- Set `ssl_protocol' with `Tls_1_0'.
|
||||
deferred
|
||||
|
||||
@@ -27,11 +27,6 @@ feature -- SSL Helpers
|
||||
-- Ignored
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_ssl_3
|
||||
-- Set `ssl_protocol' with `Ssl_3'.
|
||||
do
|
||||
-- Ignored
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_tls_1_0
|
||||
-- Set `ssl_protocol' with `Tls_1_0'.
|
||||
|
||||
@@ -41,12 +41,6 @@ feature -- SSL Helpers
|
||||
set_ssl_protocol ({SSL_PROTOCOL}.Ssl_23)
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_ssl_3
|
||||
-- Set `ssl_protocol' with `Ssl_3'.
|
||||
do
|
||||
set_ssl_protocol ({SSL_PROTOCOL}.Ssl_3)
|
||||
end
|
||||
|
||||
set_ssl_protocol_to_tls_1_0
|
||||
-- Set `ssl_protocol' with `Tls_1_0'.
|
||||
do
|
||||
|
||||
@@ -59,6 +59,7 @@ feature -- Access
|
||||
|
||||
create dbg.make
|
||||
dbg.set_is_verbose (True)
|
||||
dbg.set_unicode_output_enabled (True)
|
||||
dbg.append_cgi_variables_to (req, res, s)
|
||||
dbg.append_information_to (req, res, s)
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ feature -- Settings
|
||||
is_verbose: BOOLEAN
|
||||
-- Has verbose output (default: True)?
|
||||
|
||||
unicode_output_enabled: BOOLEAN
|
||||
-- Display names and values as unicode.
|
||||
|
||||
feature -- Settings change
|
||||
|
||||
set_is_verbose (b: BOOLEAN)
|
||||
@@ -50,6 +53,12 @@ feature -- Settings change
|
||||
is_verbose := b
|
||||
end
|
||||
|
||||
set_unicode_output_enabled (b: BOOLEAN)
|
||||
-- if `b' then enable unicode output, otherwise disable.
|
||||
do
|
||||
unicode_output_enabled := b
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
append_connector_informations_to (req: WSF_REQUEST; res: WSF_RESPONSE; a_output: STRING)
|
||||
@@ -232,7 +241,7 @@ feature {NONE} -- Implementation
|
||||
it as c
|
||||
loop
|
||||
s.append (" - ")
|
||||
s.append (utf.escaped_utf_32_string_to_utf_8_string_8 (c.key))
|
||||
append_unicode (c.key, s)
|
||||
s.append_character (' ')
|
||||
if attached c.item as l_item then
|
||||
s.append_character ('{')
|
||||
@@ -302,7 +311,11 @@ feature {NONE} -- Implementation
|
||||
it as c
|
||||
loop
|
||||
a_output.append (" - ")
|
||||
a_output.append (c.item.url_encoded_name)
|
||||
if unicode_output_enabled then
|
||||
append_unicode (c.item.name, a_output)
|
||||
else
|
||||
a_output.append (c.item.url_encoded_name)
|
||||
end
|
||||
t := c.item.generating_type
|
||||
if t.same_string ("WSF_STRING") then
|
||||
else
|
||||
@@ -313,15 +326,15 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
a_output.append_character ('=')
|
||||
if attached {WSF_STRING} c.item as l_str then
|
||||
s := l_str.url_encoded_value
|
||||
if unicode_output_enabled then
|
||||
s := l_str.value
|
||||
else
|
||||
s := l_str.url_encoded_value
|
||||
end
|
||||
else
|
||||
s := c.item.string_representation
|
||||
end
|
||||
if s.is_valid_as_string_8 then
|
||||
v := s.as_string_8
|
||||
else
|
||||
v := utf.escaped_utf_32_string_to_utf_8_string_8 (s)
|
||||
end
|
||||
v := utf.utf_32_string_to_utf_8_string_8 (s)
|
||||
if v.has ('%N') then
|
||||
a_output.append (eol)
|
||||
across
|
||||
@@ -349,7 +362,6 @@ feature {NONE} -- Implementation
|
||||
local
|
||||
n: INTEGER
|
||||
v: READABLE_STRING_8
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
if is_verbose then
|
||||
a_output.append (a_title)
|
||||
@@ -368,11 +380,7 @@ feature {NONE} -- Implementation
|
||||
it as c
|
||||
loop
|
||||
a_output.append (" - ")
|
||||
if c.key.is_valid_as_string_8 then
|
||||
a_output.append (c.key.as_string_8)
|
||||
else
|
||||
a_output.append (utf.utf_32_string_to_utf_8_string_8 (c.key))
|
||||
end
|
||||
append_unicode (c.key, a_output)
|
||||
a_output.append_character ('=')
|
||||
v := c.item
|
||||
if v.has ('%N') then
|
||||
@@ -398,6 +406,13 @@ feature {NONE} -- Implementation
|
||||
end
|
||||
end
|
||||
|
||||
append_unicode (s: READABLE_STRING_GENERAL; a_output: STRING)
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
a_output.append (utf.utf_32_string_to_utf_8_string_8 (s))
|
||||
end
|
||||
|
||||
feature -- Constants
|
||||
|
||||
eol: STRING = "%N"
|
||||
|
||||
@@ -58,13 +58,15 @@ feature -- Status
|
||||
do
|
||||
p := a_path
|
||||
l_uri := based_uri (uri, a_router)
|
||||
if l_uri.ends_with ("/") then
|
||||
if not p.ends_with ("/") then
|
||||
p := p + "/"
|
||||
end
|
||||
else
|
||||
if p.ends_with ("/") then
|
||||
p := p.substring (1, p.count - 1)
|
||||
if trailing_slash_ignored then
|
||||
if l_uri.ends_with ("/") then
|
||||
if not p.ends_with ("/") then
|
||||
p := p + "/"
|
||||
end
|
||||
else
|
||||
if p.ends_with ("/") then
|
||||
p := p.substring (1, p.count - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
if p.same_string (l_uri) then
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
note
|
||||
description: "Summary description for {WSF_APPLICATION_X_WWW_FORM_URLENCODED_HANDLER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
@@ -12,6 +11,11 @@ inherit
|
||||
|
||||
WSF_MIME_HANDLER_HELPER
|
||||
|
||||
WSF_VALUE_UTILITIES
|
||||
export
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
valid_content_type (a_content_type: HTTP_CONTENT_TYPE): BOOLEAN
|
||||
@@ -54,7 +58,7 @@ feature -- Execution
|
||||
if j > 0 then
|
||||
l_name := s.substring (1, j - 1)
|
||||
l_value := s.substring (j + 1, s.count)
|
||||
add_string_value_to_table (l_name, l_value, a_vars)
|
||||
add_percent_encoded_string_value_to_table (l_name, l_value, a_vars)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -62,7 +66,7 @@ feature -- Execution
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
note
|
||||
description: "Summary description for {WSF_MULTIPART_FORM_DATA_HANDLER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
@@ -139,10 +138,12 @@ feature {NONE} -- Implementation: Form analyzer
|
||||
local
|
||||
n, i,p, b,e: INTEGER
|
||||
l_name, l_filename, l_content_type: detachable STRING_8
|
||||
l_unicode_name: READABLE_STRING_32
|
||||
l_header: detachable STRING_8
|
||||
l_content: detachable STRING_8
|
||||
l_line: detachable STRING_8
|
||||
l_up_file: WSF_UPLOADED_FILE
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
from
|
||||
p := 1
|
||||
@@ -235,12 +236,13 @@ feature {NONE} -- Implementation: Form analyzer
|
||||
if l_content_type = Void then
|
||||
l_content_type := default_content_type
|
||||
end
|
||||
create l_up_file.make (l_name, l_filename, l_content_type, l_content.count)
|
||||
add_value_to_table (l_name, l_up_file, vars)
|
||||
l_unicode_name := utf.utf_8_string_8_to_string_32 (l_name)
|
||||
create l_up_file.make (l_unicode_name, utf.utf_8_string_8_to_escaped_string_32 (l_filename), l_content_type, l_content.count)
|
||||
add_value_to_table (l_unicode_name, l_up_file, vars)
|
||||
--| `l_up_file' might have a new name
|
||||
req.save_uploaded_file (l_up_file, l_content)
|
||||
else
|
||||
add_string_value_to_table (l_name, l_content, vars)
|
||||
add_utf_8_string_value_to_table (l_name, l_content, vars)
|
||||
end
|
||||
else
|
||||
req.error_handler.add_custom_error (0, "unamed multipart entry", Void)
|
||||
@@ -254,7 +256,7 @@ feature {NONE} -- Implementation: Form analyzer
|
||||
-- Default content type
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -34,10 +34,10 @@ feature -- Access
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- <Precursor>
|
||||
do
|
||||
name := url_decoded_string (a_name)
|
||||
ensure then
|
||||
a_name.same_string (url_encoded_name)
|
||||
name := a_name
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
@@ -71,7 +71,7 @@ feature -- Visitor
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -89,10 +89,9 @@ feature -- Access
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- <Precursor>
|
||||
do
|
||||
name := a_name
|
||||
ensure then
|
||||
a_name.same_string (name)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
@@ -180,7 +179,7 @@ invariant
|
||||
string_values_not_empty: values.count >= 1
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -16,17 +16,24 @@ inherit
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
make,
|
||||
make_with_percent_encoded_values
|
||||
|
||||
convert
|
||||
string_representation: {READABLE_STRING_32, STRING_32}
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_8; a_string: READABLE_STRING_8)
|
||||
make (a_name: READABLE_STRING_GENERAL; a_string: READABLE_STRING_GENERAL)
|
||||
do
|
||||
url_encoded_name := utf_8_percent_encoded_string (a_name)
|
||||
url_encoded_value := utf_8_percent_encoded_string (a_string)
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
url_encoded_value := url_encoded_string (a_string)
|
||||
end
|
||||
|
||||
make_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8)
|
||||
do
|
||||
url_encoded_name := a_encoded_name
|
||||
url_encoded_value := a_encoded_value
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
@@ -105,11 +112,10 @@ feature -- Status report
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- <Precursor>
|
||||
do
|
||||
internal_name := Void
|
||||
url_encoded_name := a_name
|
||||
ensure then
|
||||
a_name.same_string (url_encoded_name)
|
||||
internal_name := a_name
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
@@ -147,89 +153,8 @@ feature -- Visitor
|
||||
vis.process_string (Current)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
utf_8_percent_encoded_string (s: READABLE_STRING_8): READABLE_STRING_8
|
||||
-- Percent-encode the UTF-8 sequence characters from UTF-8 encoded `s' and
|
||||
-- return the Result.
|
||||
local
|
||||
s8: STRING_8
|
||||
i, n, nb: INTEGER
|
||||
do
|
||||
-- First check if there are such UTF-8 character
|
||||
-- If it has, convert them and return a new object as Result
|
||||
-- otherwise return `s' directly to avoid creating a new object
|
||||
from
|
||||
i := 1
|
||||
n := s.count
|
||||
nb := 0
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if s.code (i) > 0x7F then -- >= 128
|
||||
nb := nb + 1
|
||||
end
|
||||
i := i + 1
|
||||
end
|
||||
if nb > 0 then
|
||||
create s8.make (s.count + nb * 3)
|
||||
utf_8_string_8_into_percent_encoded_string_8 (s, s8)
|
||||
Result := s8
|
||||
else
|
||||
Result := s
|
||||
end
|
||||
end
|
||||
|
||||
utf_8_string_8_into_percent_encoded_string_8 (s: READABLE_STRING_8; a_result: STRING_8)
|
||||
-- Copy STRING_32 corresponding to UTF-8 sequence `s' appended into `a_result'.
|
||||
local
|
||||
i: INTEGER
|
||||
n: INTEGER
|
||||
c: NATURAL_32
|
||||
do
|
||||
from
|
||||
n := s.count
|
||||
a_result.grow (a_result.count + n)
|
||||
until
|
||||
i >= n
|
||||
loop
|
||||
i := i + 1
|
||||
c := s.code (i)
|
||||
if c <= 0x7F then
|
||||
-- 0xxxxxxx
|
||||
a_result.append_code (c)
|
||||
elseif c <= 0xDF then
|
||||
-- 110xxxxx 10xxxxxx
|
||||
url_encoder.append_percent_encoded_character_code_to (c, a_result)
|
||||
i := i + 1
|
||||
if i <= n then
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
|
||||
end
|
||||
elseif c <= 0xEF then
|
||||
-- 1110xxxx 10xxxxxx 10xxxxxx
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
|
||||
i := i + 2
|
||||
if i <= n then
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i - 1), a_result)
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
|
||||
end
|
||||
elseif c <= 0xF7 then
|
||||
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
|
||||
i := i + 3
|
||||
if i <= n then
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i - 2), a_result)
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i - 1), a_result)
|
||||
url_encoder.append_percent_encoded_character_code_to (s.code (i), a_result)
|
||||
end
|
||||
else
|
||||
a_result.append_code (c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -2,8 +2,8 @@ note
|
||||
description: "[
|
||||
Table which can contain value indexed by a key
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
date: "$Date: 2015-11-05 21:52:56 +0100 (jeu., 05 nov. 2015) $"
|
||||
revision: "$Revision: 98081 $"
|
||||
|
||||
class
|
||||
WSF_TABLE
|
||||
@@ -14,26 +14,37 @@ inherit
|
||||
as_string
|
||||
end
|
||||
|
||||
ITERABLE [WSF_VALUE]
|
||||
TABLE_ITERABLE [WSF_VALUE, READABLE_STRING_32]
|
||||
|
||||
create
|
||||
make
|
||||
make,
|
||||
make_with_percent_encoded_name
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_8)
|
||||
make (a_name: READABLE_STRING_GENERAL)
|
||||
do
|
||||
name := url_decoded_string (a_name)
|
||||
url_encoded_name := a_name
|
||||
create values.make (5)
|
||||
name := a_name.as_string_32
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
create values.make (3)
|
||||
end
|
||||
|
||||
make_with_percent_encoded_name (a_encoded_name: READABLE_STRING_8)
|
||||
do
|
||||
url_encoded_name := a_encoded_name
|
||||
name := url_decoded_string (a_encoded_name)
|
||||
create values.make (3)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: READABLE_STRING_32
|
||||
-- Parameter name
|
||||
-- <Precursor>
|
||||
--| Note that the value might be html encoded as well
|
||||
--| this is the application responsibility to html decode it
|
||||
|
||||
url_encoded_name: READABLE_STRING_8
|
||||
-- URL encoded string of `name'.
|
||||
|
||||
first_value: detachable WSF_VALUE
|
||||
-- First value if any.
|
||||
@@ -59,13 +70,16 @@ feature -- Access
|
||||
end
|
||||
|
||||
values: HASH_TABLE [WSF_VALUE, STRING_32]
|
||||
-- Associated items values, indexed by unicode name.
|
||||
|
||||
value (k: READABLE_STRING_GENERAL): detachable WSF_VALUE
|
||||
-- Value associated with name `k'.
|
||||
do
|
||||
Result := values.item (k.to_string_32)
|
||||
end
|
||||
|
||||
count: INTEGER
|
||||
-- Number of values.
|
||||
do
|
||||
Result := values.count
|
||||
end
|
||||
@@ -77,14 +91,15 @@ feature -- Access
|
||||
Result := first_name
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- <Precursor>
|
||||
do
|
||||
name := url_decoded_string (a_name)
|
||||
url_encoded_name := a_name
|
||||
ensure then
|
||||
a_name.same_string (url_encoded_name)
|
||||
name := a_name
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
@@ -178,7 +193,7 @@ feature -- Element change
|
||||
|
||||
feature -- Traversing
|
||||
|
||||
new_cursor: ITERATION_CURSOR [WSF_VALUE]
|
||||
new_cursor: TABLE_ITERATION_CURSOR [WSF_VALUE, READABLE_STRING_32]
|
||||
do
|
||||
Result := values.new_cursor
|
||||
end
|
||||
@@ -217,7 +232,7 @@ feature -- Visitor
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -15,17 +15,27 @@ inherit
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
make,
|
||||
make_with_percent_encoded_values
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_8; n: like filename; t: like content_type; s: like size)
|
||||
make (a_name: READABLE_STRING_GENERAL; a_filename: READABLE_STRING_GENERAL; a_content_type: like content_type; a_size: like size)
|
||||
do
|
||||
name := url_decoded_string (a_name)
|
||||
url_encoded_name := a_name
|
||||
filename := n
|
||||
content_type := t
|
||||
size := s
|
||||
name := a_name.as_string_32
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
filename := a_filename.as_string_32
|
||||
content_type := a_content_type
|
||||
size := a_size
|
||||
end
|
||||
|
||||
make_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_filename: READABLE_STRING_GENERAL; a_content_type: like content_type; a_size: like size)
|
||||
do
|
||||
name := url_decoded_string (a_encoded_name)
|
||||
url_encoded_name := a_encoded_name
|
||||
filename := a_filename.as_string_32
|
||||
content_type := a_content_type
|
||||
size := a_size
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
@@ -65,11 +75,10 @@ feature -- Status report
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- <Precursor>
|
||||
do
|
||||
name := url_decoded_string (a_name)
|
||||
url_encoded_name := a_name
|
||||
ensure then
|
||||
a_name.same_string (url_encoded_name)
|
||||
name := a_name
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
@@ -99,7 +108,7 @@ feature -- Visitor
|
||||
|
||||
feature -- Access: Uploaded File
|
||||
|
||||
filename: STRING
|
||||
filename: STRING_32
|
||||
-- original filename
|
||||
|
||||
safe_filename: STRING
|
||||
@@ -182,7 +191,7 @@ feature -- Basic operation
|
||||
if f.exists then
|
||||
f.rename_file (a_destination)
|
||||
Result := True
|
||||
end
|
||||
end
|
||||
end
|
||||
ensure
|
||||
removed: not exists
|
||||
@@ -241,7 +250,7 @@ feature -- Element change
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -36,8 +36,10 @@ feature -- Access
|
||||
feature -- Element change
|
||||
|
||||
change_name (a_name: like name)
|
||||
-- Change parameter name
|
||||
-- Change parameter `name'.
|
||||
deferred
|
||||
ensure
|
||||
name_changed: a_name.same_string (name)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
@@ -128,7 +130,7 @@ feature -- Visitor
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
@@ -7,122 +7,25 @@ note
|
||||
deferred class
|
||||
WSF_MIME_HANDLER_HELPER
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
WSF_VALUE_UTILITIES
|
||||
export
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
full_input_data (req: WSF_REQUEST): STRING_8
|
||||
-- Read or reused full input data from `req'.
|
||||
do
|
||||
create Result.make (0)
|
||||
req.read_input_data_into (Result)
|
||||
end
|
||||
|
||||
add_string_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
|
||||
do
|
||||
add_value_to_table (a_name, new_string_value (a_name, a_value), a_table)
|
||||
end
|
||||
|
||||
add_value_to_table (a_name: READABLE_STRING_8; a_value: WSF_VALUE; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
|
||||
local
|
||||
l_decoded_name: STRING_32
|
||||
v: detachable WSF_VALUE
|
||||
n,k,r: STRING_32
|
||||
-- k32: STRING_32
|
||||
p,q: INTEGER
|
||||
tb,ptb: detachable WSF_TABLE
|
||||
do
|
||||
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
|
||||
l_decoded_name := url_encoder.decoded_string (a_name)
|
||||
p := l_decoded_name.index_of ({CHARACTER_32}'[', 1)
|
||||
if p > 0 then
|
||||
q := l_decoded_name.index_of ({CHARACTER_32}']', p + 1)
|
||||
if q > p then
|
||||
n := l_decoded_name.substring (1, p - 1)
|
||||
r := l_decoded_name.substring (q + 1, l_decoded_name.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
|
||||
create tb.make (n)
|
||||
if a_table.has_key (tb.name) and then attached {WSF_TABLE} a_table.found_item as l_existing_table then
|
||||
tb := l_existing_table
|
||||
end
|
||||
|
||||
k := l_decoded_name.substring (p + 1, q - 1)
|
||||
k.left_adjust; k.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
v := tb
|
||||
n.append_character ({CHARACTER_32}'[')
|
||||
n.append (k)
|
||||
n.append_character ({CHARACTER_32}']')
|
||||
|
||||
from
|
||||
until
|
||||
r.is_empty
|
||||
loop
|
||||
ptb := tb
|
||||
p := r.index_of ({CHARACTER_32} '[', 1)
|
||||
if p > 0 then
|
||||
q := r.index_of ({CHARACTER_32} ']', p + 1)
|
||||
if q > p then
|
||||
if attached {WSF_TABLE} ptb.value (k) as l_tb_value then
|
||||
tb := l_tb_value
|
||||
else
|
||||
create tb.make (n)
|
||||
ptb.add_value (tb, k)
|
||||
end
|
||||
|
||||
k := r.substring (p + 1, q - 1)
|
||||
r := r.substring (q + 1, r.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
n.append_character ('[')
|
||||
n.append (k)
|
||||
n.append_character (']')
|
||||
end
|
||||
else
|
||||
r.wipe_out
|
||||
--| Ignore bad value
|
||||
end
|
||||
end
|
||||
a_value.change_name (n)
|
||||
tb.add_value (a_value, k)
|
||||
else
|
||||
--| Missing end bracket
|
||||
end
|
||||
end
|
||||
if v = Void then
|
||||
a_value.change_name (a_name)
|
||||
v := a_value
|
||||
end
|
||||
if a_table.has_key (v.name) and then attached a_table.found_item as l_existing_value then
|
||||
if tb /= Void then
|
||||
--| Already done in previous part
|
||||
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
|
||||
l_multi.add_value (v)
|
||||
else
|
||||
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
|
||||
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
|
||||
end
|
||||
else
|
||||
a_table.force (v, v.name)
|
||||
end
|
||||
end
|
||||
|
||||
new_string_value (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8): WSF_STRING
|
||||
do
|
||||
create Result.make (a_name, a_value)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
url_encoder: URL_ENCODER
|
||||
once
|
||||
create {UTF8_URL_ENCODER} Result
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2013, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Eiffel Software and others"
|
||||
copyright: "2011-2015, 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
|
||||
|
||||
163
library/server/wsf/src/support/wsf_value_utilities.e
Normal file
163
library/server/wsf/src/support/wsf_value_utilities.e
Normal file
@@ -0,0 +1,163 @@
|
||||
note
|
||||
description: "[
|
||||
Utility routines to handle parameters and convert them as string, list or table values.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_VALUE_UTILITIES
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
SHARED_WSF_PERCENT_ENCODER
|
||||
rename
|
||||
percent_encoder as url_encoder
|
||||
export
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
feature -- Smart parameter identification
|
||||
|
||||
add_utf_8_string_value_to_table (a_utf_8_name: READABLE_STRING_8; a_utf_8_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
|
||||
-- Add a utf-8 string value `a_utf_8_value' associated with name `a_utf_8_name' to `a_table'.
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
n,v: READABLE_STRING_32
|
||||
do
|
||||
n := utf.utf_8_string_8_to_string_32 (a_utf_8_name)
|
||||
v := utf.utf_8_string_8_to_string_32 (a_utf_8_value)
|
||||
add_value_to_table (n, new_string_value (n, v), a_table)
|
||||
end
|
||||
|
||||
add_percent_encoded_string_value_to_table (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
|
||||
-- Add a percent-encoded string value `a_encoded_value' associated with name `a_encoded_name' to `a_table'.
|
||||
local
|
||||
v: WSF_STRING
|
||||
do
|
||||
v := new_string_value_with_percent_encoded_values (a_encoded_name, a_encoded_value)
|
||||
add_value_to_table (v.name, v, a_table)
|
||||
end
|
||||
|
||||
add_value_to_table (a_name: READABLE_STRING_GENERAL; a_value: WSF_VALUE; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_GENERAL])
|
||||
-- Add value `a_value' associated with unicode name `a_name' to `a_table'.
|
||||
local
|
||||
l_decoded_name: STRING_32
|
||||
l_encoded_name: READABLE_STRING_8
|
||||
v: detachable WSF_VALUE
|
||||
n,k,r,vn: STRING_32
|
||||
p,q: INTEGER
|
||||
tb,ptb: detachable WSF_TABLE
|
||||
do
|
||||
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
|
||||
l_decoded_name := a_name.as_string_32
|
||||
l_encoded_name := a_value.url_encoded_name
|
||||
p := l_decoded_name.index_of ({CHARACTER_32}'[', 1)
|
||||
n := l_decoded_name
|
||||
if p > 0 then
|
||||
q := l_decoded_name.index_of ({CHARACTER_32}']', p + 1)
|
||||
if q > p then
|
||||
n := l_decoded_name.substring (1, p - 1)
|
||||
r := l_decoded_name.substring (q + 1, l_decoded_name.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
|
||||
create tb.make (n)
|
||||
if attached {WSF_TABLE} a_table.item (tb.name) as l_existing_table then
|
||||
tb := l_existing_table
|
||||
end
|
||||
|
||||
k := l_decoded_name.substring (p + 1, q - 1)
|
||||
k.left_adjust; k.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
v := tb
|
||||
create vn.make_from_string (n)
|
||||
vn.append_character ({CHARACTER_32}'[')
|
||||
vn.append (k)
|
||||
vn.append_character ({CHARACTER_32}']')
|
||||
|
||||
from
|
||||
until
|
||||
r.is_empty
|
||||
loop
|
||||
ptb := tb
|
||||
p := r.index_of ({CHARACTER_32} '[', 1)
|
||||
if p > 0 then
|
||||
q := r.index_of ({CHARACTER_32} ']', p + 1)
|
||||
if q > p then
|
||||
if attached {WSF_TABLE} ptb.value (k) as l_tb_value then
|
||||
tb := l_tb_value
|
||||
else
|
||||
create tb.make (n)
|
||||
ptb.add_value (tb, k)
|
||||
end
|
||||
|
||||
k := r.substring (p + 1, q - 1)
|
||||
r := r.substring (q + 1, r.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
vn.append_character ('[')
|
||||
vn.append (k)
|
||||
vn.append_character (']')
|
||||
end
|
||||
else
|
||||
r.wipe_out
|
||||
--| Ignore bad value
|
||||
end
|
||||
end
|
||||
a_value.change_name (vn)
|
||||
tb.add_value (a_value, k)
|
||||
else
|
||||
--| Missing end bracket
|
||||
end
|
||||
end
|
||||
if v = Void then
|
||||
a_value.change_name (l_decoded_name)
|
||||
v := a_value
|
||||
end
|
||||
if attached a_table.item (n) as l_existing_value then
|
||||
if tb /= Void then
|
||||
--| Already done in previous part
|
||||
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
|
||||
l_multi.add_value (v)
|
||||
elseif attached {WSF_TABLE} l_existing_value as l_table then
|
||||
-- Keep previous values (most likely we have table[1]=foo, table[2]=bar ..and table=/foo/bar
|
||||
-- Anyway for this case, we keep the previous version, and ignore this "conflict"
|
||||
else
|
||||
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
|
||||
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
|
||||
end
|
||||
else
|
||||
a_table.force (v, n)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_string_value (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_GENERAL): WSF_STRING
|
||||
-- New WSF_STRING value built from unicode `a_name' and `a_value'.
|
||||
do
|
||||
create Result.make (a_name, a_value)
|
||||
end
|
||||
|
||||
new_string_value_with_percent_encoded_values (a_encoded_name: READABLE_STRING_8; a_encoded_value: READABLE_STRING_8): WSF_STRING
|
||||
-- New WSF_STRING value built from utf8+percent encoded `a_encoded_name' and `a_encoded_value'.
|
||||
do
|
||||
create Result.make_with_percent_encoded_values (a_encoded_name, a_encoded_value)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015, 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
|
||||
@@ -20,8 +20,8 @@ note
|
||||
About https support: `is_https' indicates if the request is made through an https connection or not.
|
||||
|
||||
]"
|
||||
date: "$Date: 2014-05-14 16:52:42 +0200 (mer., 14 mai 2014) $"
|
||||
revision: "$Revision: 95057 $"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WSF_REQUEST
|
||||
@@ -41,6 +41,11 @@ inherit
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
WSF_VALUE_UTILITIES
|
||||
export
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
create {WSF_EXECUTION, WGI_EXPORTER}
|
||||
make_from_wgi
|
||||
|
||||
@@ -846,7 +851,7 @@ feature -- Access: CGI meta parameters - 1.1
|
||||
local
|
||||
l_result: like internal_percent_encoded_path_info
|
||||
r: READABLE_STRING_8
|
||||
i: INTEGER
|
||||
i,m,n,spos: INTEGER
|
||||
do
|
||||
l_result := internal_percent_encoded_path_info
|
||||
if l_result = Void then
|
||||
@@ -860,6 +865,28 @@ feature -- Access: CGI meta parameters - 1.1
|
||||
if attached script_name as s then
|
||||
if l_result.starts_with (s) then
|
||||
l_result := l_result.substring (s.count + 1, l_result.count)
|
||||
else
|
||||
--| Handle Rewrite url engine, to have clean path
|
||||
from
|
||||
i := 1
|
||||
m := l_result.count
|
||||
n := s.count
|
||||
until
|
||||
i > m or i > n or l_result[i] /= s[i]
|
||||
loop
|
||||
if l_result[i] = '/' then
|
||||
spos := i
|
||||
end
|
||||
i := i + 1
|
||||
end
|
||||
if i > 1 then
|
||||
if l_result[i-1] = '/' then
|
||||
i := i -1
|
||||
elseif spos > 0 then
|
||||
i := spos
|
||||
end
|
||||
l_result := l_result.substring (i, m)
|
||||
end
|
||||
end
|
||||
end
|
||||
internal_percent_encoded_path_info := l_result
|
||||
@@ -1399,7 +1426,7 @@ feature {NONE} -- Cookies
|
||||
k.left_adjust
|
||||
k.right_adjust
|
||||
if not k.is_empty then
|
||||
add_value_to_table (k, v, l_cookies)
|
||||
add_percent_encoded_string_value_to_table (k, v, l_cookies)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1463,7 +1490,7 @@ feature {WSF_REQUEST_PATH_PARAMETERS_SOURCE} -- Path parameters: Element change
|
||||
across
|
||||
lst as c
|
||||
loop
|
||||
add_value_to_table (c.key, c.item, l_table)
|
||||
add_percent_encoded_string_value_to_table (c.key, c.item, l_table)
|
||||
end
|
||||
src.update_path_parameters (l_table)
|
||||
end
|
||||
@@ -1494,7 +1521,7 @@ feature {NONE} -- Query parameters: implementation
|
||||
vars: like internal_query_parameters_table
|
||||
p,e: INTEGER
|
||||
rq_uri: like request_uri
|
||||
s: detachable STRING
|
||||
s: detachable READABLE_STRING_8
|
||||
do
|
||||
vars := internal_query_parameters_table
|
||||
if vars = Void then
|
||||
@@ -1556,7 +1583,7 @@ feature {NONE} -- Query parameters: implementation
|
||||
l_name := s
|
||||
l_value := empty_string_8
|
||||
end
|
||||
add_value_to_table (l_name, l_value, Result)
|
||||
add_percent_encoded_string_value_to_table (l_name, l_value, Result)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1715,100 +1742,6 @@ feature {NONE} -- Form fields and related
|
||||
Result := vars
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation: smart parameter identification
|
||||
|
||||
add_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: STRING_TABLE [WSF_VALUE])
|
||||
-- Add urlencoded parameter `a_name'=`a_value' to `a_table'
|
||||
-- following smart computation such as handling the "..[..]" as table
|
||||
local
|
||||
v: detachable WSF_VALUE
|
||||
n, k, r: STRING_8
|
||||
k32: STRING_32
|
||||
p, q: INTEGER
|
||||
tb, ptb: detachable WSF_TABLE
|
||||
do
|
||||
--| Check if this is a list format such as choice[] or choice[a] or even choice[a][] or choice[a][b][c]...
|
||||
p := a_name.index_of ('[', 1)
|
||||
if p > 0 then
|
||||
q := a_name.index_of (']', p + 1)
|
||||
if q > p then
|
||||
n := a_name.substring (1, p - 1)
|
||||
r := a_name.substring (q + 1, a_name.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
|
||||
create tb.make (n)
|
||||
if a_table.has_key (tb.name) and then attached {WSF_TABLE} a_table.found_item as l_existing_table then
|
||||
tb := l_existing_table
|
||||
end
|
||||
|
||||
k := a_name.substring (p + 1, q - 1)
|
||||
k.left_adjust; k.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
v := tb
|
||||
create n.make_from_string (n)
|
||||
n.append_character ('[')
|
||||
n.append (k)
|
||||
n.append_character (']')
|
||||
|
||||
from
|
||||
until
|
||||
r.is_empty
|
||||
loop
|
||||
ptb := tb
|
||||
p := r.index_of ({CHARACTER_8} '[', 1)
|
||||
if p > 0 then
|
||||
q := r.index_of ({CHARACTER_8} ']', p + 1)
|
||||
if q > p then
|
||||
k32 := url_decoded_string (k)
|
||||
if attached {WSF_TABLE} ptb.value (k32) as l_tb_value then
|
||||
tb := l_tb_value
|
||||
else
|
||||
create tb.make (n)
|
||||
ptb.add_value (tb, k32)
|
||||
end
|
||||
|
||||
k := r.substring (p + 1, q - 1)
|
||||
r := r.substring (q + 1, r.count)
|
||||
r.left_adjust; r.right_adjust
|
||||
if k.is_empty then
|
||||
k.append_integer (tb.count + 1)
|
||||
end
|
||||
n.append_character ('[')
|
||||
n.append (k)
|
||||
n.append_character (']')
|
||||
end
|
||||
else
|
||||
r.wipe_out
|
||||
--| Ignore bad value
|
||||
end
|
||||
end
|
||||
tb.add_value (new_string_value (n, a_value), k)
|
||||
else
|
||||
--| Missing end bracket
|
||||
end
|
||||
end
|
||||
if v = Void then
|
||||
v := new_string_value (a_name, a_value)
|
||||
end
|
||||
if a_table.has_key (v.name) and then attached a_table.found_item as l_existing_value then
|
||||
if tb /= Void then
|
||||
--| Already done in previous part
|
||||
elseif attached {WSF_MULTIPLE_STRING} l_existing_value as l_multi then
|
||||
l_multi.add_value (v)
|
||||
elseif attached {WSF_TABLE} l_existing_value as l_table then
|
||||
-- Keep previous values (most likely we have table[1]=foo, table[2]=bar ..and table=/foo/bar
|
||||
-- Anyway for this case, we keep the previous version, and ignore this "conflict"
|
||||
else
|
||||
a_table.force (create {WSF_MULTIPLE_STRING}.make_with_array (<<l_existing_value, v>>), v.name)
|
||||
check replaced: a_table.found and then a_table.found_item ~ l_existing_value end
|
||||
end
|
||||
else
|
||||
a_table.force (v, v.name)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Uploaded File Handling
|
||||
|
||||
is_uploaded_file (a_filename: READABLE_STRING_GENERAL): BOOLEAN
|
||||
@@ -2133,11 +2066,6 @@ feature {NONE} -- Implementation: utilities
|
||||
one_starting_slash: Result[1] = '/' and (Result.count = 1 or else Result[2] /= '/')
|
||||
end
|
||||
|
||||
new_string_value (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8): WSF_STRING
|
||||
do
|
||||
create Result.make (a_name, a_value)
|
||||
end
|
||||
|
||||
empty_string_32: IMMUTABLE_STRING_32
|
||||
-- Reusable empty string
|
||||
once
|
||||
|
||||
@@ -319,7 +319,7 @@ feature -- Header output operation: helpers
|
||||
feature -- Header add cookie
|
||||
|
||||
add_cookie (a_cookie: WSF_COOKIE)
|
||||
-- Add a Set-Cookie header field to the response,
|
||||
-- Add a Set-Cookie header field to the response,
|
||||
-- if no Set-Cookie header field already use same cookie-name.
|
||||
--| Servers SHOULD NOT include more than one Set-Cookie header field in
|
||||
--| the same response with the same cookie-name.
|
||||
@@ -328,7 +328,7 @@ feature -- Header add cookie
|
||||
do
|
||||
across
|
||||
internal_header.headers as ic
|
||||
until
|
||||
until
|
||||
l_same_cookie_name
|
||||
loop
|
||||
if ic.item.starts_with ("Set-Cookie:") then
|
||||
@@ -564,7 +564,7 @@ feature {NONE} -- Implemenation
|
||||
loop
|
||||
j := j + 1
|
||||
end
|
||||
if j > n then
|
||||
if j <= n then
|
||||
Result := a_cookie_name.same_characters (a_cookie_line, j, i, 1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,7 +93,7 @@ feature -- Change
|
||||
set_checked_by_value (v)
|
||||
Precursor (v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ feature -- Conversion
|
||||
a_html.append ("<td")
|
||||
append_css_class_to (a_html, Void)
|
||||
append_css_style_to (a_html)
|
||||
append_html_attributes_to (a_html)
|
||||
a_html.append_character ('>')
|
||||
content.append_to_html (a_theme, a_html)
|
||||
a_html.append ("</td>")
|
||||
|
||||
15
library/text/parser/feed/feed-safe.ecf
Normal file
15
library/text/parser/feed/feed-safe.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="feed" uuid="71364A69-1549-472E-AF78-FDA4FDA016EB" library_target="feed">
|
||||
<target name="feed">
|
||||
<root all_classes="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
|
||||
<library name="encoders" location="..\..\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid-safe.ecf"/>
|
||||
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>
|
||||
<library name="xml_tree" location="$ISE_LIBRARY\library\text\parser\xml\tree\xml_tree-safe.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
17
library/text/parser/feed/feed.ecf
Normal file
17
library/text/parser/feed/feed.ecf
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="feed" uuid="71364A69-1549-472E-AF78-FDA4FDA016EB" library_target="feed">
|
||||
<target name="feed">
|
||||
<root all_classes="true"/>
|
||||
<option void_safety="none">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
|
||||
<library name="encoders" location="..\..\encoder\encoder.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time.ecf"/>
|
||||
<library name="uuid" location="$ISE_LIBRARY\library\uuid\uuid.ecf"/>
|
||||
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser.ecf"/>
|
||||
<library name="xml_tree" location="$ISE_LIBRARY\library\text\parser\xml\tree\xml_tree.ecf"/>
|
||||
<cluster name="src" location="src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
186
library/text/parser/feed/src/atom/atom_feed_generator.e
Normal file
186
library/text/parser/feed/src/atom/atom_feed_generator.e
Normal file
@@ -0,0 +1,186 @@
|
||||
note
|
||||
description: "Convert a FEED into an ATOM content."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
ATOM_FEED_GENERATOR
|
||||
|
||||
inherit
|
||||
FEED_VISITOR
|
||||
|
||||
FEED_GENERATOR
|
||||
rename
|
||||
process_feed as visit_feed
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
visit_feed (a_feed: FEED)
|
||||
do
|
||||
buffer.append ("[
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
]")
|
||||
buffer.append_character ('%N')
|
||||
indent
|
||||
append_content_tag_to ("title", Void, a_feed.title, buffer)
|
||||
append_content_tag_to ("subtitle", Void, a_feed.description, buffer)
|
||||
if attached a_feed.id as l_id then
|
||||
append_content_tag_to ("id", Void, l_id, buffer)
|
||||
else
|
||||
append_content_tag_to ("id", Void, "urn:uuid:" + new_uuid, buffer)
|
||||
end
|
||||
|
||||
across
|
||||
a_feed.links as tb
|
||||
loop
|
||||
tb.item.accept (Current)
|
||||
end
|
||||
if attached a_feed.date as dt then
|
||||
append_content_tag_to ("updated", Void, date_to_string (dt), buffer)
|
||||
end
|
||||
across
|
||||
a_feed.items as ic
|
||||
loop
|
||||
ic.item.accept (Current)
|
||||
end
|
||||
|
||||
exdent
|
||||
buffer.append ("</feed>")
|
||||
end
|
||||
|
||||
visit_item (a_entry: FEED_ITEM)
|
||||
do
|
||||
buffer.append (indentation)
|
||||
buffer.append ("<entry>%N")
|
||||
indent
|
||||
append_content_tag_to ("title", Void, a_entry.title, buffer)
|
||||
across
|
||||
a_entry.links as tb
|
||||
loop
|
||||
tb.item.accept (Current)
|
||||
end
|
||||
if attached a_entry.id as l_id then
|
||||
append_content_tag_to ("id", Void, l_id, buffer)
|
||||
else
|
||||
append_content_tag_to ("id", Void, "urn:uuid:" + new_uuid, buffer)
|
||||
end
|
||||
if attached a_entry.date as dt then
|
||||
append_content_tag_to ("updated", Void, date_to_string (dt), buffer)
|
||||
end
|
||||
|
||||
append_content_tag_to ("summary", Void, a_entry.description, buffer)
|
||||
if attached a_entry.content as l_content then
|
||||
if attached a_entry.content_type_or_default ("xhtml").is_case_insensitive_equal_general ("xhtml") then
|
||||
-- if l_content.has_substring ("<div xmlns=%"http://www.w3.org/1999/xhtml%">") then
|
||||
append_content_tag_to ("content", <<["type", "xhtml"]>>, l_content, buffer)
|
||||
-- else
|
||||
-- append_content_tag_to ("content", <<["type", "xhtml"]>>, {STRING_32} "<div xmlns=%"http://www.w3.org/1999/xhtml%">" + l_content + {STRING_32} "</div>", buffer)
|
||||
-- end
|
||||
else
|
||||
append_content_tag_to ("content", <<["type", a_entry.content_type]>>, a_entry.content, buffer)
|
||||
end
|
||||
end
|
||||
|
||||
if attached a_entry.author as u then
|
||||
u.accept (Current)
|
||||
end
|
||||
exdent
|
||||
buffer.append (indentation)
|
||||
buffer.append ("</entry>%N")
|
||||
end
|
||||
|
||||
visit_link (a_link: FEED_LINK)
|
||||
local
|
||||
attr: detachable ARRAYED_LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]
|
||||
tu: TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]
|
||||
do
|
||||
create attr.make (2)
|
||||
if attached a_link.relation as rel and then not rel.is_whitespace then
|
||||
tu := ["rel", rel]
|
||||
attr.force (tu)
|
||||
end
|
||||
if attached a_link.type as t and then not t.is_whitespace then
|
||||
tu := ["type", t.as_string_32]
|
||||
attr.force (tu)
|
||||
end
|
||||
tu := ["href", a_link.href.as_string_32]
|
||||
attr.force (tu)
|
||||
if attr.is_empty then
|
||||
attr := Void
|
||||
end
|
||||
append_content_tag_to ("link", attr, Void, buffer)
|
||||
end
|
||||
|
||||
visit_author (a_author: FEED_AUTHOR)
|
||||
do
|
||||
buffer.append (indentation)
|
||||
buffer.append ("<author>%N")
|
||||
indent
|
||||
append_content_tag_to ("name", Void, a_author.name, buffer)
|
||||
append_content_tag_to ("email", Void, a_author.email, buffer)
|
||||
exdent
|
||||
buffer.append (indentation)
|
||||
buffer.append ("</author>%N")
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
new_uuid: STRING
|
||||
local
|
||||
gen: UUID_GENERATOR
|
||||
do
|
||||
create gen
|
||||
Result := gen.generate_uuid.out.as_lower
|
||||
end
|
||||
|
||||
date_to_string (dt: DATE_TIME): STRING
|
||||
do
|
||||
Result := date_to_rfc3339_string (dt)
|
||||
end
|
||||
|
||||
date_to_rfc3339_string (d: DATE_TIME): STRING
|
||||
-- 2003-12-13T18:30:02Z
|
||||
local
|
||||
i: INTEGER
|
||||
do
|
||||
create Result.make_empty
|
||||
Result.append_integer (d.year)
|
||||
Result.append_character ('-')
|
||||
i := d.month
|
||||
if i < 10 then
|
||||
Result.append_integer (0)
|
||||
end
|
||||
Result.append_integer (i)
|
||||
Result.append_character ('-')
|
||||
i := d.day
|
||||
if i < 10 then
|
||||
Result.append_integer (0)
|
||||
end
|
||||
Result.append_integer (i)
|
||||
Result.append_character ('T')
|
||||
i := d.hour
|
||||
if i < 10 then
|
||||
Result.append_integer (0)
|
||||
end
|
||||
Result.append_integer (i)
|
||||
Result.append_character (':')
|
||||
i := d.minute
|
||||
if i < 10 then
|
||||
Result.append_integer (0)
|
||||
end
|
||||
Result.append_integer (i)
|
||||
Result.append_character (':')
|
||||
i := d.second
|
||||
if i < 10 then
|
||||
Result.append_integer (0)
|
||||
end
|
||||
Result.append_integer (i)
|
||||
Result.append_character ('Z')
|
||||
end
|
||||
|
||||
end
|
||||
104
library/text/parser/feed/src/atom/atom_feed_parser.e
Normal file
104
library/text/parser/feed/src/atom/atom_feed_parser.e
Normal file
@@ -0,0 +1,104 @@
|
||||
note
|
||||
description: "[
|
||||
ATOM Parser.
|
||||
|
||||
Warning: the implementation may not support the full ATOM specification.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=ATOM at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/Atom_(standard)"
|
||||
EIS: "name=RSS at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/RSS"
|
||||
EIS: "name=ATOM 1.0 RFC4287", "protocol=URI", "src=https://tools.ietf.org/html/rfc4287"
|
||||
|
||||
class
|
||||
ATOM_FEED_PARSER
|
||||
|
||||
inherit
|
||||
FEED_PARSER
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING = "atom1"
|
||||
-- Associated name.
|
||||
|
||||
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
|
||||
-- Is `xdoc' an ATOM feed representation?
|
||||
do
|
||||
Result := attached {XML_ELEMENT} xdoc.element_by_name ("feed") as x_feed and then
|
||||
(
|
||||
not attached xml_attribute_text (x_feed, "xmlns") as l_xmlns
|
||||
or else l_xmlns.same_string ("http://www.w3.org/2005/Atom")
|
||||
)
|
||||
end
|
||||
|
||||
feed (xdoc: XML_DOCUMENT): detachable FEED
|
||||
-- Feed from `xdoc' XML document.
|
||||
local
|
||||
l_title: READABLE_STRING_32
|
||||
x_entry, x_link: detachable XML_ELEMENT
|
||||
e: FEED_ITEM
|
||||
l_author: FEED_AUTHOR
|
||||
lnk: FEED_LINK
|
||||
s: STRING_32
|
||||
do
|
||||
if
|
||||
attached xdoc.element_by_name ("feed") as x_feed and then
|
||||
-- (not attached xml_attribute_text (x_feed, "xmlns") as l_xmlns or else l_xmlns.same_string ("http://www.w3.org/2005/Atom"))
|
||||
attached xml_element_text (x_feed, "title") as t
|
||||
then
|
||||
l_title := t
|
||||
create Result.make (l_title)
|
||||
Result.set_description (xml_element_text (x_feed, "subtitle"), "plain")
|
||||
Result.set_id (xml_element_text (x_feed, "id"))
|
||||
Result.set_updated_date_with_text (xml_element_text (x_feed, "updated"))
|
||||
if attached links_from_xml (x_feed, "link") as l_links then
|
||||
across
|
||||
l_links as link_ic
|
||||
loop
|
||||
lnk := link_ic.item
|
||||
Result.links.force (lnk, lnk.relation)
|
||||
end
|
||||
end
|
||||
if attached x_feed.elements_by_name ("entry") as x_entries then
|
||||
across
|
||||
x_entries as ic
|
||||
loop
|
||||
x_entry := ic.item
|
||||
if attached xml_element_text (x_entry, "title") as e_title then
|
||||
create e.make (e_title)
|
||||
e.set_description (xml_element_text (x_entry, "summary"))
|
||||
e.set_id (xml_element_text (x_entry, "id"))
|
||||
e.set_updated_date_with_text (xml_element_text (x_entry, "updated"))
|
||||
|
||||
if attached links_from_xml (x_entry, "link") as l_links then
|
||||
across
|
||||
l_links as link_ic
|
||||
loop
|
||||
lnk := link_ic.item
|
||||
e.links.force (lnk, lnk.relation)
|
||||
end
|
||||
end
|
||||
if attached x_entry.element_by_name ("content") as x_content then
|
||||
e.set_content (xml_element_code (x_content), xml_attribute_text (x_content, "type"))
|
||||
end
|
||||
if attached x_entry.element_by_name ("author") as x_author then
|
||||
if attached x_author.element_by_name ("name") as x_name and then
|
||||
attached x_name.text as l_author_name
|
||||
then
|
||||
create l_author.make (l_author_name)
|
||||
if attached x_author.element_by_name ("email") as x_email then
|
||||
l_author.set_email (x_email.text)
|
||||
end
|
||||
e.set_author (l_author)
|
||||
end
|
||||
end
|
||||
Result.extend (e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
69
library/text/parser/feed/src/feed_default_parsers.e
Normal file
69
library/text/parser/feed/src/feed_default_parsers.e
Normal file
@@ -0,0 +1,69 @@
|
||||
note
|
||||
description: "[
|
||||
Collection of default feed parsers provided by the current library.
|
||||
A new parser can be easily added via `parsers.extend (...)'.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_DEFAULT_PARSERS
|
||||
|
||||
inherit
|
||||
ANY
|
||||
redefine
|
||||
default_create
|
||||
end
|
||||
|
||||
create
|
||||
default_create
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
default_create
|
||||
do
|
||||
Precursor
|
||||
create {ARRAYED_LIST [FEED_PARSER]} parsers.make (2)
|
||||
parsers.force (create {RSS_2_FEED_PARSER})
|
||||
parsers.force (create {ATOM_FEED_PARSER})
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
parsers: LIST [FEED_PARSER]
|
||||
-- Available Feed parsers.
|
||||
|
||||
feature -- Access
|
||||
|
||||
feed_from_string (a_content: READABLE_STRING_8): detachable FEED
|
||||
-- Feed object from `a_content' string, if a parser is able to parse.it.
|
||||
local
|
||||
p: XML_STANDARD_PARSER
|
||||
cb_tree: XML_CALLBACKS_FILTER_DOCUMENT
|
||||
xdoc: XML_DOCUMENT
|
||||
do
|
||||
create p.make
|
||||
create cb_tree.make_null
|
||||
p.set_callbacks (cb_tree)
|
||||
p.parse_from_string_8 (a_content)
|
||||
if p.is_correct then
|
||||
xdoc := cb_tree.document
|
||||
Result := feed (xdoc)
|
||||
end
|
||||
end
|
||||
|
||||
feed (xdoc: XML_DOCUMENT): like feed_from_string
|
||||
-- Feed from `xdoc' XML document.
|
||||
do
|
||||
across
|
||||
parsers as ic
|
||||
until
|
||||
Result /= Void
|
||||
loop
|
||||
if ic.item.is_detected (xdoc) then
|
||||
Result := ic.item.feed (xdoc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
62
library/text/parser/feed/src/feed_parser.e
Normal file
62
library/text/parser/feed/src/feed_parser.e
Normal file
@@ -0,0 +1,62 @@
|
||||
note
|
||||
description: "[
|
||||
Interface common to any FEED parser.
|
||||
|
||||
Usage:
|
||||
create parser
|
||||
if attached parser.feed_from_string (l_feed_content) as l_feed then
|
||||
...
|
||||
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
FEED_PARSER
|
||||
|
||||
inherit
|
||||
FEED_PARSER_UTILITIES
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING
|
||||
-- Associated name.
|
||||
deferred
|
||||
ensure
|
||||
not_blanc: not Result.is_whitespace
|
||||
end
|
||||
|
||||
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
|
||||
-- Is `xdoc' an feed representation or Current supported format?
|
||||
deferred
|
||||
end
|
||||
|
||||
feed (xdoc: XML_DOCUMENT): detachable FEED
|
||||
-- Feed from `xdoc' XML document.
|
||||
require
|
||||
is_detected: is_detected (xdoc)
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
feed_from_string (a_content: READABLE_STRING_8): like feed
|
||||
-- Feed from `a_content' document.
|
||||
local
|
||||
p: XML_STANDARD_PARSER
|
||||
cb_tree: XML_CALLBACKS_FILTER_DOCUMENT
|
||||
xdoc: XML_DOCUMENT
|
||||
do
|
||||
create p.make
|
||||
create cb_tree.make_null
|
||||
p.set_callbacks (cb_tree)
|
||||
p.parse_from_string_8 (a_content)
|
||||
if p.is_correct then
|
||||
xdoc := cb_tree.document
|
||||
if is_detected (xdoc) then
|
||||
Result := feed (xdoc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
159
library/text/parser/feed/src/kernel/feed.e
Normal file
159
library/text/parser/feed/src/kernel/feed.e
Normal file
@@ -0,0 +1,159 @@
|
||||
note
|
||||
description: "FEED interface, could be RSS, ATOM, ..."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED
|
||||
|
||||
inherit
|
||||
FEED_HELPERS
|
||||
|
||||
ITERABLE [FEED_ITEM]
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_title: READABLE_STRING_GENERAL)
|
||||
do
|
||||
create title.make_from_string_general (a_title)
|
||||
create items.make (1)
|
||||
create links.make (1)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
title: IMMUTABLE_STRING_32
|
||||
-- Title of the feed/channel.
|
||||
|
||||
description: detachable IMMUTABLE_STRING_32
|
||||
-- Associated description/subtitle.
|
||||
|
||||
description_content_type: detachable READABLE_STRING_8
|
||||
-- Optional content type for `description'.
|
||||
-- By default, this should be text/plain.
|
||||
|
||||
id: detachable IMMUTABLE_STRING_32
|
||||
-- Id associated with Current feed if any.
|
||||
|
||||
date: detachable DATE_TIME
|
||||
-- Build date.
|
||||
|
||||
links: STRING_TABLE [FEED_LINK]
|
||||
-- Url indexed by relation
|
||||
|
||||
items: ARRAYED_LIST [FEED_ITEM]
|
||||
-- List of feed items.
|
||||
|
||||
feature -- Access
|
||||
|
||||
new_cursor: ITERATION_CURSOR [FEED_ITEM]
|
||||
-- <Precursor>
|
||||
do
|
||||
Result := items.new_cursor
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_description (a_description: detachable READABLE_STRING_GENERAL; a_description_content_type: like description_content_type)
|
||||
-- Set `description' with `a_description' and optional content type `text:$a_description_content_type'.
|
||||
do
|
||||
if a_description = Void then
|
||||
description := Void
|
||||
description_content_type := Void
|
||||
else
|
||||
create description.make_from_string_general (a_description)
|
||||
description_content_type := a_description_content_type
|
||||
end
|
||||
end
|
||||
|
||||
set_id (a_id: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_id = Void then
|
||||
id := Void
|
||||
else
|
||||
create id.make_from_string_general (a_id)
|
||||
end
|
||||
end
|
||||
|
||||
set_updated_date_with_text (a_date_text: detachable READABLE_STRING_32)
|
||||
-- Set `date' from date string representation `a_date_text'.
|
||||
obsolete
|
||||
"Use set_date_with_text [oct/2015]"
|
||||
do
|
||||
set_date_with_text (a_date_text)
|
||||
end
|
||||
|
||||
set_date_with_text (a_date_text: detachable READABLE_STRING_32)
|
||||
-- Set `date' from date string representation `a_date_text'.
|
||||
do
|
||||
if a_date_text = Void then
|
||||
set_date (Void)
|
||||
else
|
||||
set_date (date_time (a_date_text))
|
||||
end
|
||||
end
|
||||
|
||||
set_date (a_date: detachable DATE_TIME)
|
||||
-- Set `date' from `a_date'.
|
||||
do
|
||||
date := a_date
|
||||
end
|
||||
|
||||
extend (a_item: FEED_ITEM)
|
||||
-- Add item `a_item' to feed `items'.
|
||||
do
|
||||
items.force (a_item)
|
||||
end
|
||||
|
||||
prune (a_item: FEED_ITEM)
|
||||
-- Remove feed item `a_item' from Current list of feed items.
|
||||
do
|
||||
items.prune (a_item)
|
||||
end
|
||||
|
||||
extended alias "+" (a_feed: FEED): FEED
|
||||
-- New feed object made from Current merged with a_feed.
|
||||
local
|
||||
l_title: STRING_32
|
||||
do
|
||||
create l_title.make (title.count + a_feed.title.count)
|
||||
l_title.append_character ('(')
|
||||
l_title.append (title)
|
||||
l_title.append_character (')')
|
||||
l_title.append_character ('+')
|
||||
l_title.append_character ('(')
|
||||
l_title.append (a_feed.title)
|
||||
l_title.append_character (')')
|
||||
create Result.make (l_title)
|
||||
Result.items.append (items)
|
||||
across
|
||||
a_feed.items as ic
|
||||
loop
|
||||
-- FIXME jfiat [2015/10/07] : check there is no duplication! (same id, or link, ...)
|
||||
Result.extend (ic.item)
|
||||
end
|
||||
Result.sort
|
||||
end
|
||||
|
||||
sort
|
||||
-- Sort `items', (recent first).
|
||||
local
|
||||
s: QUICK_SORTER [FEED_ITEM]
|
||||
comp: COMPARABLE_COMPARATOR [FEED_ITEM]
|
||||
do
|
||||
create comp
|
||||
create s.make (comp)
|
||||
s.reverse_sort (items)
|
||||
end
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
accept (vis: FEED_VISITOR)
|
||||
do
|
||||
vis.visit_feed (Current)
|
||||
end
|
||||
|
||||
end
|
||||
48
library/text/parser/feed/src/kernel/feed_author.e
Normal file
48
library/text/parser/feed/src/kernel/feed_author.e
Normal file
@@ -0,0 +1,48 @@
|
||||
note
|
||||
description: "[
|
||||
Author of feed or feed entry.
|
||||
- name and email information.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_AUTHOR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_GENERAL)
|
||||
do
|
||||
create name.make_from_string_general (a_name)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: IMMUTABLE_STRING_32
|
||||
|
||||
email: detachable READABLE_STRING_8
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_email (a_email: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_email = Void then
|
||||
email := Void
|
||||
elseif a_email.is_valid_as_string_8 then
|
||||
email := a_email.as_string_8
|
||||
else
|
||||
email := Void
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
accept (vis: FEED_VISITOR)
|
||||
do
|
||||
vis.visit_author (Current)
|
||||
end
|
||||
|
||||
end
|
||||
208
library/text/parser/feed/src/kernel/feed_item.e
Normal file
208
library/text/parser/feed/src/kernel/feed_item.e
Normal file
@@ -0,0 +1,208 @@
|
||||
note
|
||||
description: "[
|
||||
A feed contains a list of items.
|
||||
This FEED_ITEM interface provides
|
||||
- title, description, content, id, date, ...
|
||||
- could be compared with other item to sort by date+title.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_ITEM
|
||||
|
||||
inherit
|
||||
FEED_HELPERS
|
||||
undefine
|
||||
is_equal
|
||||
end
|
||||
|
||||
COMPARABLE
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_title: READABLE_STRING_GENERAL)
|
||||
do
|
||||
create title.make_from_string_general (a_title)
|
||||
create links.make (1)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
title: IMMUTABLE_STRING_32
|
||||
-- Title of associated feed item.
|
||||
|
||||
description: detachable IMMUTABLE_STRING_32
|
||||
-- Optional description (or summary).
|
||||
|
||||
content: detachable IMMUTABLE_STRING_32
|
||||
-- Content of Current feed item.
|
||||
|
||||
content_type: detachable READABLE_STRING_8
|
||||
-- Optional content type for `content'.
|
||||
-- By default, this should be text/html.
|
||||
|
||||
content_type_or_default (dft: READABLE_STRING_8): READABLE_STRING_8
|
||||
-- Associated content type, and if none, return given value `dft'.
|
||||
do
|
||||
if attached content_type as l_type then
|
||||
Result := l_type
|
||||
else
|
||||
Result := dft
|
||||
end
|
||||
end
|
||||
|
||||
id: detachable IMMUTABLE_STRING_32
|
||||
-- Identifier of current feed item, if any/
|
||||
|
||||
date: detachable DATE_TIME
|
||||
-- Publishing date.
|
||||
|
||||
link: detachable FEED_LINK
|
||||
-- Main link for the entry, if any.
|
||||
do
|
||||
if attached links as l_links then
|
||||
Result := l_links.item ("")
|
||||
across
|
||||
l_links as ic
|
||||
until
|
||||
Result /= Void
|
||||
loop
|
||||
Result := ic.item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
links: STRING_TABLE [FEED_LINK]
|
||||
-- Url indexed by relation
|
||||
|
||||
categories: detachable LIST [READABLE_STRING_32]
|
||||
-- Categories
|
||||
|
||||
author: detachable FEED_AUTHOR
|
||||
-- Author information.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
has_category (cat: READABLE_STRING_GENERAL): BOOLEAN
|
||||
-- Has category `cat'?
|
||||
--| note: case insensitive.
|
||||
do
|
||||
if attached categories as cats then
|
||||
Result := across cats as ic some cat.is_case_insensitive_equal (ic.item) end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Comparison
|
||||
|
||||
is_less alias "<" (other: like Current): BOOLEAN
|
||||
-- Is current object less than `other'?
|
||||
local
|
||||
d1,d2: like date
|
||||
do
|
||||
d1 := date
|
||||
d2 := other.date
|
||||
if d1 = Void and d2 = Void then
|
||||
Result := title < other.title
|
||||
elseif d1 = Void then
|
||||
Result := True
|
||||
elseif d2 = Void then
|
||||
Result := False
|
||||
else
|
||||
if d1 ~ d2 then
|
||||
Result := title < other.title
|
||||
else
|
||||
Result := d1 < d2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_id (a_id: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_id = Void then
|
||||
id := Void
|
||||
else
|
||||
create id.make_from_string_general (a_id)
|
||||
end
|
||||
end
|
||||
|
||||
set_description (a_description: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_description = Void then
|
||||
description := Void
|
||||
else
|
||||
create description.make_from_string_general (a_description)
|
||||
end
|
||||
end
|
||||
|
||||
set_content (a_content: detachable READABLE_STRING_GENERAL; a_type: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_content = Void then
|
||||
content := Void
|
||||
content_type := Void
|
||||
else
|
||||
create content.make_from_string_general (a_content)
|
||||
if a_type = Void then
|
||||
content_type := Void
|
||||
else
|
||||
content_type := a_type.as_string_8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
set_updated_date_with_text (a_date_text: detachable READABLE_STRING_32)
|
||||
-- Set `date' from date string representation `a_date_text'.
|
||||
obsolete
|
||||
"Use set_date_with_text [oct/2015]"
|
||||
do
|
||||
set_date_with_text (a_date_text)
|
||||
end
|
||||
|
||||
set_date_with_text (a_date_text: detachable READABLE_STRING_32)
|
||||
-- Set `date' from date string representation `a_date_text'.
|
||||
do
|
||||
if a_date_text = Void then
|
||||
set_date (Void)
|
||||
else
|
||||
set_date (date_time (a_date_text))
|
||||
end
|
||||
end
|
||||
|
||||
set_date (a_date: detachable DATE_TIME)
|
||||
-- Set `date' from `a_date'.
|
||||
do
|
||||
date := a_date
|
||||
end
|
||||
|
||||
set_author (a_author: detachable FEED_AUTHOR)
|
||||
do
|
||||
author := a_author
|
||||
end
|
||||
|
||||
set_category (cat: READABLE_STRING_GENERAL)
|
||||
local
|
||||
cats: like categories
|
||||
do
|
||||
cats := categories
|
||||
if cats = Void then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} cats.make (1)
|
||||
categories := cats
|
||||
end
|
||||
cats.force (cat.as_string_32)
|
||||
ensure
|
||||
cat_set: has_category (cat)
|
||||
end
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
accept (vis: FEED_VISITOR)
|
||||
do
|
||||
vis.visit_item (Current)
|
||||
end
|
||||
|
||||
end
|
||||
58
library/text/parser/feed/src/kernel/feed_link.e
Normal file
58
library/text/parser/feed/src/kernel/feed_link.e
Normal file
@@ -0,0 +1,58 @@
|
||||
note
|
||||
description: "Link mentioned in feed and feed entry."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_LINK
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_href: READABLE_STRING_8)
|
||||
do
|
||||
href := a_href
|
||||
set_relation (Void)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
href: READABLE_STRING_8
|
||||
-- Location of Current link.
|
||||
|
||||
relation: READABLE_STRING_32
|
||||
-- Relation associated with Current link.
|
||||
|
||||
type: detachable READABLE_STRING_8
|
||||
-- Optional type of link.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_relation (rel: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if rel = Void then
|
||||
relation := ""
|
||||
else
|
||||
relation := rel.as_string_8
|
||||
end
|
||||
end
|
||||
|
||||
set_type (a_type: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_type = Void then
|
||||
type := Void
|
||||
else
|
||||
type := a_type.as_string_8
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
accept (vis: FEED_VISITOR)
|
||||
do
|
||||
vis.visit_link (Current)
|
||||
end
|
||||
|
||||
end
|
||||
126
library/text/parser/feed/src/rss/rss_2_feed_generator.e
Normal file
126
library/text/parser/feed/src/rss/rss_2_feed_generator.e
Normal file
@@ -0,0 +1,126 @@
|
||||
note
|
||||
description: "Convert a FEED into an RSS 2.0 content."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
RSS_2_FEED_GENERATOR
|
||||
|
||||
inherit
|
||||
FEED_VISITOR
|
||||
|
||||
FEED_GENERATOR
|
||||
rename
|
||||
process_feed as visit_feed
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
visit_feed (a_feed: FEED)
|
||||
do
|
||||
buffer.append ("[
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
version="2.0">
|
||||
<channel>
|
||||
]")
|
||||
buffer.append_character ('%N')
|
||||
indent
|
||||
indent
|
||||
append_content_tag_to ("title", Void, a_feed.title, buffer)
|
||||
append_content_tag_to ("description", Void, a_feed.description, buffer)
|
||||
across
|
||||
a_feed.links as tb
|
||||
loop
|
||||
tb.item.accept (Current)
|
||||
end
|
||||
if attached a_feed.date as dt then
|
||||
append_content_tag_to ("lastBuildDate", Void, date_to_string (dt), buffer)
|
||||
end
|
||||
across
|
||||
a_feed.items as ic
|
||||
loop
|
||||
ic.item.accept (Current)
|
||||
end
|
||||
exdent
|
||||
exdent
|
||||
buffer.append ("[
|
||||
</channel>
|
||||
</rss>
|
||||
]")
|
||||
end
|
||||
|
||||
visit_item (a_item: FEED_ITEM)
|
||||
do
|
||||
buffer.append (indentation)
|
||||
buffer.append ("<item>%N")
|
||||
indent
|
||||
append_content_tag_to ("title", Void, a_item.title, buffer)
|
||||
if attached a_item.date as dt then
|
||||
append_content_tag_to ("pubDate", Void, date_to_string (dt), buffer)
|
||||
end
|
||||
across
|
||||
a_item.links as tb
|
||||
loop
|
||||
tb.item.accept (Current)
|
||||
end
|
||||
if attached a_item.author as u then
|
||||
u.accept (Current)
|
||||
end
|
||||
if attached a_item.categories as cats then
|
||||
across
|
||||
cats as ic
|
||||
loop
|
||||
append_content_tag_to ("category", Void, ic.item, buffer)
|
||||
end
|
||||
end
|
||||
append_content_tag_to ("guid", Void, a_item.id, buffer)
|
||||
append_content_tag_to ("description", Void, a_item.description, buffer)
|
||||
append_cdata_content_tag_to ("content:encoded", Void, a_item.content, buffer)
|
||||
|
||||
exdent
|
||||
buffer.append (indentation)
|
||||
buffer.append ("</item>%N")
|
||||
end
|
||||
|
||||
visit_link (a_link: FEED_LINK)
|
||||
local
|
||||
attr: detachable ARRAYED_LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]
|
||||
tu: TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]
|
||||
do
|
||||
create attr.make (2)
|
||||
if attached a_link.relation as rel and then not rel.is_whitespace then
|
||||
tu := ["rel", rel]
|
||||
attr.force (tu)
|
||||
end
|
||||
if attached a_link.type as t and then not t.is_whitespace then
|
||||
tu := ["type", t.as_string_32]
|
||||
attr.force (tu)
|
||||
end
|
||||
if attr.is_empty then
|
||||
attr := Void
|
||||
end
|
||||
append_content_tag_to ("link", attr, a_link.href, buffer)
|
||||
end
|
||||
|
||||
visit_author (a_author: FEED_AUTHOR)
|
||||
do
|
||||
append_content_tag_to ("dc:creator", Void, a_author.name, buffer)
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
date_to_string (dt: DATE_TIME): STRING
|
||||
local
|
||||
htdate: HTTP_DATE
|
||||
do
|
||||
create htdate.make_from_date_time (dt)
|
||||
Result := htdate.rfc850_string
|
||||
end
|
||||
|
||||
end
|
||||
125
library/text/parser/feed/src/rss/rss_2_feed_parser.e
Normal file
125
library/text/parser/feed/src/rss/rss_2_feed_parser.e
Normal file
@@ -0,0 +1,125 @@
|
||||
note
|
||||
description: "[
|
||||
RSS 2.0 Parser.
|
||||
|
||||
Warning: the implementation may not support the full RSS 2.0 specification.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
EIS: "name=RSS at wikipedia", "protocol=URI", "src=https://en.wikipedia.org/wiki/RSS"
|
||||
EIS: "name=RDF Site Summary (RSS) 1.0", "protocol=URI", "src=http://purl.org/rss/1.0/spec"
|
||||
|
||||
class
|
||||
RSS_2_FEED_PARSER
|
||||
|
||||
inherit
|
||||
FEED_PARSER
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING = "rss2"
|
||||
-- Associated name.
|
||||
|
||||
is_detected (xdoc: XML_DOCUMENT): BOOLEAN
|
||||
-- Is `xdoc' an ATOM feed representation?
|
||||
do
|
||||
if attached {XML_ELEMENT} xdoc.element_by_name ("rss") as x_rss then
|
||||
if attached xml_attribute_text (x_rss, "version") as l_version and then
|
||||
l_version.starts_with ("2.")
|
||||
then
|
||||
Result := True
|
||||
else
|
||||
-- Let's default to RSS 2.0 for now.
|
||||
Result := True
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feed (xdoc: XML_DOCUMENT): detachable FEED
|
||||
-- Feed from `xdoc' XML RSS 2.0 document.
|
||||
local
|
||||
lnk: FEED_LINK
|
||||
x_item, x_content, x_author: detachable XML_ELEMENT
|
||||
e: FEED_ITEM
|
||||
l_author: FEED_AUTHOR
|
||||
do
|
||||
if attached xdoc.element_by_name ("rss") as x_rss then
|
||||
if
|
||||
attached xml_attribute_text (x_rss, "version") as l_version and then
|
||||
l_version.starts_with ("2.")
|
||||
then
|
||||
if attached x_rss.element_by_name ("channel") as x_channel then
|
||||
if attached xml_element_text (x_channel, "title") as x_title then
|
||||
create Result.make (x_title)
|
||||
Result.set_description (xml_element_text (x_channel, "description"), "xhtml")
|
||||
Result.set_updated_date_with_text (xml_element_text (x_channel, "lastBuildDate"))
|
||||
if attached links_from_xml (x_channel, "link") as l_links then
|
||||
across
|
||||
l_links as link_ic
|
||||
loop
|
||||
lnk := link_ic.item
|
||||
Result.links.force (lnk, lnk.relation)
|
||||
end
|
||||
end
|
||||
if attached x_channel.elements_by_name ("item") as x_items then
|
||||
across
|
||||
x_items as ic
|
||||
loop
|
||||
x_item := ic.item
|
||||
if attached xml_element_text (x_item, "title") as e_title then
|
||||
create e.make (e_title)
|
||||
e.set_description (xml_element_text (x_item, "description"))
|
||||
e.set_updated_date_with_text (xml_element_text (x_item, "pubDate"))
|
||||
|
||||
e.set_id (xml_element_text (x_item, "guid"))
|
||||
|
||||
x_author := x_item.element_by_name ("creator")
|
||||
if x_author = Void then
|
||||
x_author := element_by_prefixed_name (x_item, "dc" , "creator")
|
||||
end
|
||||
|
||||
if
|
||||
x_author /= Void and then
|
||||
attached x_author.text as l_author_name
|
||||
then
|
||||
create l_author.make (l_author_name)
|
||||
e.set_author (l_author)
|
||||
end
|
||||
|
||||
if attached links_from_xml (x_item, "link") as l_links then
|
||||
across
|
||||
l_links as link_ic
|
||||
loop
|
||||
lnk := link_ic.item
|
||||
e.links.force (lnk, lnk.relation)
|
||||
end
|
||||
end
|
||||
if attached x_item.elements_by_name ("category") as x_categories then
|
||||
across
|
||||
x_categories as cats
|
||||
loop
|
||||
if attached cats.item.text as cat then
|
||||
e.set_category (cat)
|
||||
end
|
||||
end
|
||||
end
|
||||
x_content := x_item.element_by_name ("content")
|
||||
if x_content = Void then
|
||||
x_content := element_by_prefixed_name (x_item, "content" , "encoded")
|
||||
if x_content /= Void then
|
||||
e.set_content (x_content.text, Void)
|
||||
end
|
||||
else
|
||||
e.set_content (xml_element_code (x_content), Void)
|
||||
end
|
||||
Result.extend (e)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
119
library/text/parser/feed/src/support/feed_generator.e
Normal file
119
library/text/parser/feed/src/support/feed_generator.e
Normal file
@@ -0,0 +1,119 @@
|
||||
note
|
||||
description: "Common ancestor for feed generator."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
FEED_GENERATOR
|
||||
|
||||
inherit
|
||||
XML_UTILITIES
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_buffer: STRING_8)
|
||||
do
|
||||
buffer := a_buffer
|
||||
create indentation.make_empty
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
buffer: STRING_8
|
||||
-- Output of feed conversion.
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
process_feed (a_feed: FEED)
|
||||
-- Convert `a_feed' into string representation in `buffer'.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
indent
|
||||
do
|
||||
indentation.append ("%T")
|
||||
end
|
||||
|
||||
exdent
|
||||
require
|
||||
has_indentation: indentation.count > 0
|
||||
do
|
||||
indentation.remove_tail (1)
|
||||
end
|
||||
|
||||
indentation: STRING
|
||||
|
||||
append_content_tag_to (a_tagname: READABLE_STRING_8; a_attr: detachable ITERABLE [TUPLE [name: READABLE_STRING_8; value: detachable READABLE_STRING_GENERAL]]; a_content: detachable READABLE_STRING_GENERAL; a_output: STRING)
|
||||
do
|
||||
if a_content /= Void or a_attr /= Void then
|
||||
a_output.append (indentation)
|
||||
a_output.append ("<")
|
||||
a_output.append (a_tagname)
|
||||
if a_attr /= Void then
|
||||
across
|
||||
a_attr as ic
|
||||
loop
|
||||
if attached ic.item.value as l_att_value then
|
||||
a_output.append_character (' ')
|
||||
a_output.append (ic.item.name)
|
||||
a_output.append_character ('=')
|
||||
a_output.append_character ('%"')
|
||||
a_output.append (escaped_unicode_xml (l_att_value.as_string_32))
|
||||
a_output.append_character ('%"')
|
||||
end
|
||||
end
|
||||
end
|
||||
if a_content = Void then
|
||||
a_output.append ("/>")
|
||||
else
|
||||
a_output.append (">")
|
||||
a_output.append (escaped_unicode_xml (a_content.as_string_32))
|
||||
a_output.append ("</" + a_tagname + ">%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_cdata_content_tag_to (a_tagname: READABLE_STRING_8; a_attr: detachable ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_32]]; a_content: detachable READABLE_STRING_32; a_output: STRING)
|
||||
do
|
||||
if a_content /= Void then
|
||||
a_output.append (indentation)
|
||||
a_output.append ("<")
|
||||
a_output.append (a_tagname)
|
||||
if a_attr /= Void then
|
||||
across
|
||||
a_attr as ic
|
||||
loop
|
||||
a_output.append_character (' ')
|
||||
a_output.append (ic.item.name)
|
||||
a_output.append_character ('=')
|
||||
a_output.append_character ('%"')
|
||||
a_output.append (escaped_unicode_xml (ic.item.value))
|
||||
a_output.append_character ('%"')
|
||||
end
|
||||
end
|
||||
a_output.append (">")
|
||||
a_output.append (to_cdata_element (a_content))
|
||||
a_output.append ("</" + a_tagname + ">%N")
|
||||
end
|
||||
end
|
||||
|
||||
to_cdata_element (a_value: READABLE_STRING_GENERAL): STRING
|
||||
local
|
||||
cdata: XML_CHARACTER_DATA
|
||||
xdoc: XML_DOCUMENT
|
||||
pprinter: XML_NODE_PRINTER
|
||||
l_output: XML_STRING_8_OUTPUT_STREAM
|
||||
do
|
||||
create xdoc.make
|
||||
create cdata.make (xdoc.root_element, a_value.as_string_32)
|
||||
create pprinter.make
|
||||
create Result.make (cdata.content_count)
|
||||
create l_output.make (Result)
|
||||
pprinter.set_output (l_output)
|
||||
pprinter.process_character_data (cdata)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
86
library/text/parser/feed/src/support/feed_helpers.e
Normal file
86
library/text/parser/feed/src/support/feed_helpers.e
Normal file
@@ -0,0 +1,86 @@
|
||||
note
|
||||
description: "Helpers routine for feed library."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_HELPERS
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
date_time (a_date_string: READABLE_STRING_32): DATE_TIME
|
||||
-- "2015-08-14T10:34:13.493740Z"
|
||||
-- "Sat, 07 Sep 2002 00:00:01 GMT"
|
||||
local
|
||||
i,j: INTEGER
|
||||
s: READABLE_STRING_GENERAL
|
||||
y,m,d,h,min: INTEGER
|
||||
sec: REAL_64
|
||||
htdate: HTTP_DATE
|
||||
str: STRING_32
|
||||
do
|
||||
if a_date_string.count > 0 and then a_date_string.item (1).is_digit then
|
||||
i := a_date_string.index_of ('-', 1)
|
||||
if i > 0 then
|
||||
s := a_date_string.substring (1, i - 1)
|
||||
y := s.to_integer_32 -- Year
|
||||
j := i + 1
|
||||
i := a_date_string.index_of ('-', j)
|
||||
if i > 0 then
|
||||
s := a_date_string.substring (j, i - 1)
|
||||
m := s.to_integer_32 -- Month
|
||||
j := i + 1
|
||||
i := a_date_string.index_of ('T', j)
|
||||
if i = 0 then
|
||||
i := a_date_string.index_of (' ', j)
|
||||
end
|
||||
if i = 0 then
|
||||
i := a_date_string.count + 1
|
||||
end
|
||||
if i > 0 then
|
||||
s := a_date_string.substring (j, i - 1)
|
||||
if s.is_integer then
|
||||
d := s.to_integer_32 -- Day
|
||||
j := i + 1
|
||||
i := a_date_string.index_of (':', j)
|
||||
if i > 0 then
|
||||
s := a_date_string.substring (j, i - 1)
|
||||
h := s.to_integer
|
||||
j := i + 1
|
||||
i := a_date_string.index_of (':', j)
|
||||
if i > 0 then
|
||||
s := a_date_string.substring (j, i - 1)
|
||||
min := s.to_integer
|
||||
j := i + 1
|
||||
i := a_date_string.index_of ('Z', j)
|
||||
if i = 0 then
|
||||
i := a_date_string.count + 1
|
||||
end
|
||||
s := a_date_string.substring (j, i - 1)
|
||||
sec := s.to_double
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
create Result.make (y,m,d,h,m,0)
|
||||
Result.fine_second_add (sec)
|
||||
else
|
||||
i := a_date_string.index_of ('+', 1)
|
||||
if i > 0 then
|
||||
str := a_date_string.substring (1, i - 1)
|
||||
str.append (" GMT")
|
||||
create htdate.make_from_string (str)
|
||||
Result := htdate.date_time
|
||||
if a_date_string.substring (i + 1, a_date_string.count).is_case_insensitive_equal ("0000") then
|
||||
|
||||
end
|
||||
else
|
||||
create htdate.make_from_string (a_date_string)
|
||||
Result := htdate.date_time
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
83
library/text/parser/feed/src/support/feed_parser_utilities.e
Normal file
83
library/text/parser/feed/src/support/feed_parser_utilities.e
Normal file
@@ -0,0 +1,83 @@
|
||||
note
|
||||
description: "Helpers routine for feed xml parsers."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
FEED_PARSER_UTILITIES
|
||||
|
||||
feature -- Access
|
||||
|
||||
xml_element_text (a_parent: XML_ELEMENT; a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
do
|
||||
if attached a_parent.element_by_name (a_name) as elt then
|
||||
if attached elt.text as t then
|
||||
t.left_adjust
|
||||
t.right_adjust
|
||||
Result := t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
xml_attribute_text (a_elt: XML_ELEMENT; a_att_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
do
|
||||
if attached a_elt.attribute_by_name (a_att_name) as att then
|
||||
Result := att.value
|
||||
end
|
||||
end
|
||||
|
||||
xml_element_code (elt: XML_ELEMENT): STRING_32
|
||||
local
|
||||
xprinter: XML_NODE_PRINTER
|
||||
do
|
||||
create xprinter.make
|
||||
create Result.make_empty
|
||||
xprinter.set_output (create {XML_STRING_32_OUTPUT_STREAM}.make (Result))
|
||||
xprinter.process_element (elt)
|
||||
end
|
||||
|
||||
links_from_xml (elt: XML_ELEMENT; a_link_elt_name: READABLE_STRING_GENERAL): detachable ARRAYED_LIST [FEED_LINK]
|
||||
local
|
||||
x_link: XML_ELEMENT
|
||||
lnk: FEED_LINK
|
||||
do
|
||||
if attached elt.elements_by_name (a_link_elt_name) as x_links then
|
||||
create Result.make (0)
|
||||
across
|
||||
x_links as ic
|
||||
loop
|
||||
x_link := ic.item
|
||||
if attached xml_attribute_text (x_link, "href") as l_href and then
|
||||
l_href.is_valid_as_string_8
|
||||
then
|
||||
create lnk.make (l_href.as_string_8)
|
||||
lnk.set_relation (xml_attribute_text (x_link, "rel"))
|
||||
lnk.set_type (xml_attribute_text (x_link, "type"))
|
||||
Result.force (lnk)
|
||||
elseif attached x_link.text as l_url and then not l_url.is_whitespace then
|
||||
create lnk.make (l_url)
|
||||
Result.force (lnk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
element_by_prefixed_name (elt: XML_ELEMENT; a_ns_prefix: READABLE_STRING_GENERAL; a_name: READABLE_STRING_GENERAL): detachable XML_ELEMENT
|
||||
do
|
||||
across
|
||||
elt as ic
|
||||
until
|
||||
Result /= Void
|
||||
loop
|
||||
if attached {XML_ELEMENT} ic.item as x_item then
|
||||
if
|
||||
attached x_item.ns_prefix as l_ns_prefix and then a_ns_prefix.same_string (l_ns_prefix) and then
|
||||
a_name.same_string (x_item.name)
|
||||
then
|
||||
Result := x_item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,200 @@
|
||||
note
|
||||
description: "[
|
||||
Convert a FEED to STRING_32 representation.
|
||||
Mostly for debug output!
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_TO_STRING_32_DEBUG_VISITOR
|
||||
|
||||
inherit
|
||||
FEED_VISITOR
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_buffer: STRING_32)
|
||||
do
|
||||
buffer := a_buffer
|
||||
create indentation.make_empty
|
||||
end
|
||||
|
||||
buffer: STRING_32
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
visit_feed (a_feed: FEED)
|
||||
do
|
||||
if attached a_feed.id as l_id then
|
||||
append_text ("#")
|
||||
append (l_id)
|
||||
append_new_line
|
||||
end
|
||||
if attached a_feed.date as dt then
|
||||
append_text ("date:")
|
||||
append (dt.out)
|
||||
append_new_line
|
||||
end
|
||||
|
||||
append_text (a_feed.title)
|
||||
append_new_line
|
||||
indent
|
||||
if attached a_feed.description as l_desc then
|
||||
append_text (l_desc)
|
||||
append_new_line
|
||||
end
|
||||
|
||||
across
|
||||
a_feed.links as ic
|
||||
loop
|
||||
ic.item.accept (Current)
|
||||
append_new_line
|
||||
end
|
||||
|
||||
append_new_line
|
||||
|
||||
across
|
||||
a_feed.items as ic
|
||||
loop
|
||||
exdent
|
||||
append_text (create {STRING_32}.make_filled ('-', 40))
|
||||
append_new_line
|
||||
indent
|
||||
ic.item.accept (Current)
|
||||
append_new_line
|
||||
end
|
||||
end
|
||||
|
||||
visit_item (a_entry: FEED_ITEM)
|
||||
do
|
||||
if attached a_entry.id as l_id then
|
||||
append_text ("#")
|
||||
append (l_id)
|
||||
append_new_line
|
||||
end
|
||||
if attached a_entry.date as dt then
|
||||
append_text ("date:")
|
||||
append (dt.out)
|
||||
append_new_line
|
||||
end
|
||||
append_text (a_entry.title)
|
||||
append_new_line
|
||||
indent
|
||||
if attached a_entry.author as l_author then
|
||||
l_author.accept (Current)
|
||||
append_new_line
|
||||
end
|
||||
if attached a_entry.categories as cats then
|
||||
append_text ("Categories: ")
|
||||
from
|
||||
cats.start
|
||||
until
|
||||
cats.after
|
||||
loop
|
||||
if not cats.isfirst then
|
||||
append (", ")
|
||||
end
|
||||
append (cats.item)
|
||||
cats.forth
|
||||
end
|
||||
append_new_line
|
||||
end
|
||||
if attached a_entry.description as l_summary then
|
||||
append_text (l_summary)
|
||||
append_new_line
|
||||
end
|
||||
|
||||
across
|
||||
a_entry.links as ic
|
||||
loop
|
||||
ic.item.accept (Current)
|
||||
append_new_line
|
||||
end
|
||||
|
||||
if attached a_entry.content as l_content then
|
||||
append_text (l_content)
|
||||
append_new_line
|
||||
end
|
||||
exdent
|
||||
end
|
||||
|
||||
visit_link (a_link: FEED_LINK)
|
||||
local
|
||||
s: STRING_32
|
||||
do
|
||||
create s.make_empty
|
||||
s.append_string_general ("@")
|
||||
s.append_string (a_link.relation)
|
||||
s.append_string (" -> ")
|
||||
s.append_string (a_link.href)
|
||||
append_text (s)
|
||||
end
|
||||
|
||||
visit_author (a_author: FEED_AUTHOR)
|
||||
local
|
||||
s: STRING_32
|
||||
do
|
||||
create s.make_empty
|
||||
s.append_string_general ("by ")
|
||||
s.append_string (a_author.name)
|
||||
if attached a_author.email as l_email then
|
||||
s.append_character (' ')
|
||||
s.append_character ('(')
|
||||
s.append_string_general (l_email)
|
||||
s.append_character (')')
|
||||
end
|
||||
append_text (s)
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
|
||||
indentation: STRING_32
|
||||
|
||||
indent
|
||||
do
|
||||
indentation.append (" ")
|
||||
end
|
||||
|
||||
exdent
|
||||
do
|
||||
indentation.remove_tail (2)
|
||||
end
|
||||
|
||||
append_new_line
|
||||
do
|
||||
append ("%N")
|
||||
end
|
||||
|
||||
append_text (s: READABLE_STRING_GENERAL)
|
||||
local
|
||||
lst: LIST [READABLE_STRING_GENERAL]
|
||||
do
|
||||
if indentation.is_empty then
|
||||
append (s)
|
||||
else
|
||||
lst := s.split ('%N')
|
||||
from
|
||||
lst.start
|
||||
until
|
||||
lst.after
|
||||
loop
|
||||
append (indentation)
|
||||
append (lst.item)
|
||||
if not lst.islast then
|
||||
append ("%N")
|
||||
end
|
||||
lst.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append (s: READABLE_STRING_GENERAL)
|
||||
do
|
||||
buffer.append_string_general (s)
|
||||
end
|
||||
|
||||
end
|
||||
229
library/text/parser/feed/src/support/feed_to_xhtml_visitor.e
Normal file
229
library/text/parser/feed/src/support/feed_to_xhtml_visitor.e
Normal file
@@ -0,0 +1,229 @@
|
||||
note
|
||||
description: "[
|
||||
Convert a FEED to XHTML representation.
|
||||
]"
|
||||
date: "$Date: 2015-10-08 10:45:13 +0200 (jeu., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97964 $"
|
||||
|
||||
class
|
||||
FEED_TO_XHTML_VISITOR
|
||||
|
||||
inherit
|
||||
FEED_VISITOR
|
||||
|
||||
SHARED_HTML_ENCODER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_buffer: STRING_8)
|
||||
do
|
||||
buffer := a_buffer
|
||||
create today.make_now_utc
|
||||
description_enabled := False
|
||||
limit := -1
|
||||
end
|
||||
|
||||
buffer: STRING_8
|
||||
-- Output buffer.
|
||||
|
||||
today: DATE_TIME
|
||||
-- Current date.
|
||||
|
||||
feature -- Access
|
||||
|
||||
header: detachable READABLE_STRING_8
|
||||
-- Optional header.
|
||||
|
||||
footer: detachable READABLE_STRING_8
|
||||
-- Optional footer.
|
||||
|
||||
feature -- Settings
|
||||
|
||||
limit: INTEGER
|
||||
-- Number of item to include in XHTML generation.
|
||||
-- Default: -1 => No limit
|
||||
|
||||
description_enabled: BOOLEAN
|
||||
-- Generate description?
|
||||
-- Default: False
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_limit (nb: INTEGER)
|
||||
-- Set `limit' to `nb'.
|
||||
do
|
||||
limit := nb
|
||||
end
|
||||
|
||||
set_description_enabled (b: BOOLEAN)
|
||||
-- Set `description_enabled' to `b'.
|
||||
do
|
||||
description_enabled := b
|
||||
end
|
||||
|
||||
set_header (h: like header)
|
||||
do
|
||||
header := h
|
||||
end
|
||||
|
||||
set_footer (f: like footer)
|
||||
do
|
||||
footer := f
|
||||
end
|
||||
|
||||
feature -- Visitor
|
||||
|
||||
visit_feed (a_feed: FEED)
|
||||
local
|
||||
nb: INTEGER
|
||||
do
|
||||
append ("<div class=%"feed%">")
|
||||
if attached header as h then
|
||||
append (h)
|
||||
append ("%N")
|
||||
end
|
||||
if attached a_feed.date as dt then
|
||||
append ("<!-- date:")
|
||||
append (dt.out)
|
||||
append (" -->%N")
|
||||
end
|
||||
|
||||
append ("<ul>%N")
|
||||
if
|
||||
description_enabled and then
|
||||
attached a_feed.description as l_desc and then
|
||||
l_desc.is_valid_as_string_8
|
||||
then
|
||||
append ("<div class=%"description%">")
|
||||
append (l_desc.to_string_8)
|
||||
append ("</div>")
|
||||
end
|
||||
|
||||
nb := limit
|
||||
across
|
||||
a_feed as ic
|
||||
until
|
||||
nb = 0
|
||||
loop
|
||||
ic.item.accept (Current)
|
||||
nb := nb - 1
|
||||
end
|
||||
|
||||
if attached footer as f then
|
||||
append (f)
|
||||
append ("%N")
|
||||
end
|
||||
append ("</ul>%N")
|
||||
end
|
||||
|
||||
visit_item (a_entry: FEED_ITEM)
|
||||
local
|
||||
lnk: detachable FEED_LINK
|
||||
do
|
||||
append ("<li>%N")
|
||||
lnk := a_entry.link
|
||||
|
||||
if attached a_entry.date as dt then
|
||||
append ("<div class=%"date%">")
|
||||
append_date_time_to (dt, today.date, buffer)
|
||||
append ("</div>%N")
|
||||
end
|
||||
if lnk /= Void then
|
||||
append ("<a href=%"" + lnk.href + "%">")
|
||||
else
|
||||
check has_link: False end
|
||||
append ("<a href=%"#%">")
|
||||
end
|
||||
append_as_html_encoded (a_entry.title)
|
||||
append ("</a>%N")
|
||||
debug
|
||||
if attached a_entry.categories as l_categories and then not l_categories.is_empty then
|
||||
append ("<div class=%"category%">")
|
||||
across
|
||||
l_categories as cats_ic
|
||||
loop
|
||||
append_as_html_encoded (cats_ic.item)
|
||||
append (" ")
|
||||
end
|
||||
append ("</div>%N")
|
||||
end
|
||||
end
|
||||
if
|
||||
description_enabled and then
|
||||
attached a_entry.description as l_entry_desc
|
||||
then
|
||||
if l_entry_desc.is_valid_as_string_8 then
|
||||
append ("<div class=%"description%">")
|
||||
append (l_entry_desc.as_string_8)
|
||||
append ("</div>%N")
|
||||
else
|
||||
check is_html: False end
|
||||
end
|
||||
end
|
||||
append ("</li>%N")
|
||||
end
|
||||
|
||||
visit_link (a_link: FEED_LINK)
|
||||
do
|
||||
append ("<a href=%"" + a_link.href + "%">")
|
||||
append_as_html_encoded (a_link.relation)
|
||||
append ("</a>%N")
|
||||
end
|
||||
|
||||
visit_author (a_author: FEED_AUTHOR)
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
|
||||
append_as_html_encoded (s: READABLE_STRING_GENERAL)
|
||||
do
|
||||
buffer.append (html_encoder.general_encoded_string (s))
|
||||
end
|
||||
|
||||
append (s: READABLE_STRING_8)
|
||||
do
|
||||
buffer.append (s)
|
||||
end
|
||||
|
||||
append_date_time_to (dt: DATE_TIME; a_today: DATE; a_output: STRING_GENERAL)
|
||||
do
|
||||
if dt.year /= a_today.year then
|
||||
a_output.append (dt.year.out)
|
||||
a_output.append (",")
|
||||
end
|
||||
a_output.append (" ")
|
||||
append_month_mmm_to (dt.month, a_output)
|
||||
a_output.append (" ")
|
||||
if dt.day < 10 then
|
||||
a_output.append ("0")
|
||||
end
|
||||
a_output.append (dt.day.out)
|
||||
end
|
||||
|
||||
append_month_mmm_to (m: INTEGER; s: STRING_GENERAL)
|
||||
require
|
||||
1 <= m and m <= 12
|
||||
do
|
||||
inspect m
|
||||
when 1 then s.append ("Jan")
|
||||
when 2 then s.append ("Feb")
|
||||
when 3 then s.append ("Mar")
|
||||
when 4 then s.append ("Apr")
|
||||
when 5 then s.append ("May")
|
||||
when 6 then s.append ("Jun")
|
||||
when 7 then s.append ("Jul")
|
||||
when 8 then s.append ("Aug")
|
||||
when 9 then s.append ("Sep")
|
||||
when 10 then s.append ("Oct")
|
||||
when 11 then s.append ("Nov")
|
||||
when 12 then s.append ("Dec")
|
||||
else
|
||||
-- Error
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
27
library/text/parser/feed/src/support/feed_visitor.e
Normal file
27
library/text/parser/feed/src/support/feed_visitor.e
Normal file
@@ -0,0 +1,27 @@
|
||||
note
|
||||
description: "Interface to visit Feed objects."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
FEED_VISITOR
|
||||
|
||||
feature -- Visit
|
||||
|
||||
visit_feed (a_feed: FEED)
|
||||
deferred
|
||||
end
|
||||
|
||||
visit_link (a_link: FEED_LINK)
|
||||
deferred
|
||||
end
|
||||
|
||||
visit_item (a_item: FEED_ITEM)
|
||||
deferred
|
||||
end
|
||||
|
||||
visit_author (a_author: FEED_AUTHOR)
|
||||
deferred
|
||||
end
|
||||
|
||||
end
|
||||
91
library/text/parser/feed/tests/application.e
Normal file
91
library/text/parser/feed/tests/application.e
Normal file
@@ -0,0 +1,91 @@
|
||||
note
|
||||
description: "Summary description for {APPLICATION}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Initialization
|
||||
|
||||
make
|
||||
-- New test routine
|
||||
do
|
||||
test_file ("data_rss_1_0.rss")
|
||||
test_web ("https://bertrandmeyer.com/feed/")
|
||||
end
|
||||
|
||||
test_file (fn: READABLE_STRING_GENERAL)
|
||||
local
|
||||
t: STRING
|
||||
f: PLAIN_TEXT_FILE
|
||||
do
|
||||
create f.make_with_name (fn)
|
||||
f.open_read
|
||||
create t.make_empty
|
||||
from
|
||||
f.read_stream_thread_aware (1_024)
|
||||
until
|
||||
f.last_string.count < 1024
|
||||
loop
|
||||
t.append (f.last_string)
|
||||
f.read_stream_thread_aware (1_024)
|
||||
end
|
||||
t.append (f.last_string)
|
||||
f.close
|
||||
test_feed (t)
|
||||
end
|
||||
|
||||
test_feed (t: READABLE_STRING_8)
|
||||
local
|
||||
feed_parser: FEED_DEFAULT_PARSERS
|
||||
vis: FEED_TO_STRING_32_DEBUG_VISITOR
|
||||
gen: RSS_2_FEED_GENERATOR
|
||||
atom_gen: ATOM_FEED_GENERATOR
|
||||
s: STRING_32
|
||||
s8: STRING_8
|
||||
pp: XML_PRETTY_PRINT_FILTER
|
||||
do
|
||||
create feed_parser
|
||||
if attached feed_parser.feed_from_string (t) as l_feed then
|
||||
create s.make_empty
|
||||
create vis.make (s)
|
||||
l_feed.accept (vis)
|
||||
print (s)
|
||||
|
||||
create s8.make_empty
|
||||
create gen.make (s8)
|
||||
l_feed.accept (gen)
|
||||
print (s8)
|
||||
|
||||
create s8.make_empty
|
||||
create atom_gen.make (s8)
|
||||
l_feed.accept (atom_gen)
|
||||
print (s8)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
test_web (a_url: READABLE_STRING_8)
|
||||
local
|
||||
cl: LIBCURL_HTTP_CLIENT
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
do
|
||||
create cl.make
|
||||
sess := cl.new_session (a_url)
|
||||
sess.set_is_insecure (True)
|
||||
if attached sess.get ("", Void) as resp then
|
||||
if
|
||||
not resp.error_occurred and then
|
||||
attached resp.body as l_feed
|
||||
then
|
||||
test_feed (l_feed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
74
library/text/parser/feed/tests/atom_test_set.e
Normal file
74
library/text/parser/feed/tests/atom_test_set.e
Normal file
@@ -0,0 +1,74 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
ATOM_TEST_SET
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
feature -- Test routines
|
||||
|
||||
test_atom
|
||||
-- New test routine
|
||||
local
|
||||
feed_parser: FEED_DEFAULT_PARSERS
|
||||
vis: FEED_TO_STRING_32_DEBUG_VISITOR
|
||||
s: STRING_32
|
||||
do
|
||||
create feed_parser
|
||||
if attached feed_parser.feed_from_string (atom_string_1) as l_feed then
|
||||
create s.make_empty
|
||||
create vis.make (s)
|
||||
l_feed.accept (vis)
|
||||
print (s)
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
|
||||
feature {NONE} -- Data
|
||||
|
||||
atom_string_1: STRING = "[
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<subtitle>A subtitle.</subtitle>
|
||||
<link href="http://example.org/feed/" rel="self" />
|
||||
<link href="http://example.org/" />
|
||||
<id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
|
||||
|
||||
<entry>
|
||||
<title>Atom-Powered Robots Run Amok</title>
|
||||
<link href="http://example.org/2003/12/13/atom03" />
|
||||
<link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
|
||||
<link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<summary>Some text.</summary>
|
||||
<content type="xhtml">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>This is the entry content.</p>
|
||||
</div>
|
||||
</content>
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
<email>johndoe@example.com</email>
|
||||
</author>
|
||||
</entry>
|
||||
|
||||
</feed>
|
||||
]"
|
||||
end
|
||||
|
||||
|
||||
60
library/text/parser/feed/tests/rss_test_set.e
Normal file
60
library/text/parser/feed/tests/rss_test_set.e
Normal file
@@ -0,0 +1,60 @@
|
||||
note
|
||||
description: "Summary description for {RSS_TEST_SET}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
RSS_TEST_SET
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
feature -- Test routines
|
||||
|
||||
test_rss_2
|
||||
-- New test routine
|
||||
local
|
||||
feed_parser: FEED_DEFAULT_PARSERS
|
||||
vis: FEED_TO_STRING_32_DEBUG_VISITOR
|
||||
s: STRING_32
|
||||
do
|
||||
create feed_parser
|
||||
if attached feed_parser.feed_from_string (rss_2_string_1) as l_feed then
|
||||
create s.make_empty
|
||||
create vis.make (s)
|
||||
l_feed.accept (vis)
|
||||
print (s)
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
|
||||
feature {NONE} -- Data
|
||||
|
||||
rss_2_string_1: STRING = "[
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>Mon site</title>
|
||||
<description>Ceci est un exemple de flux RSS 2.0</description>
|
||||
<lastBuildDate>Sat, 07 Sep 2002 00:00:01 GMT</lastBuildDate>
|
||||
<link>http://www.example.org</link>
|
||||
<item>
|
||||
<title>Post N1</title>
|
||||
<description>This is my first post</description>
|
||||
<pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
|
||||
<link>http://www.example.org/actu1</link>
|
||||
</item>
|
||||
<item>
|
||||
<title>Post N2</title>
|
||||
<description>This is my second post</description>
|
||||
<pubDate>Sat, 07 Sep 2002 00:00:01 GMT</pubDate>
|
||||
<link>http://www.example.org/actu2</link>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
]"
|
||||
end
|
||||
|
||||
|
||||
14
library/text/parser/feed/tests/tests-safe.ecf
Normal file
14
library/text/parser/feed/tests/tests-safe.ecf
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="tests" uuid="C68BD5DC-F756-484E-A9FE-F2D1FD432B2A">
|
||||
<target name="tests">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<setting name="console_application" value="false"/>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="feed" location="..\feed-safe.ecf" readonly="false"/>
|
||||
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-safe.ecf"/>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||
<library name="xml_parser" location="$ISE_LIBRARY\library\text\parser\xml\parser\xml_parser-safe.ecf"/>
|
||||
<tests name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -2,8 +2,8 @@ note
|
||||
description : "Objects that represent a custom error"
|
||||
legal: "See notice at end of class."
|
||||
status: "See notice at end of class."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
|
||||
revision: "$Revision: 97980 $"
|
||||
|
||||
class
|
||||
ERROR_CUSTOM
|
||||
@@ -24,7 +24,7 @@ feature {NONE} -- Initialization
|
||||
if a_message /= Void then
|
||||
message := a_message
|
||||
else
|
||||
message := "Error: " + a_name + " (code=" + a_code.out + ")"
|
||||
message := {STRING_32} "Error: " + a_name + " (code=" + a_code.out + ")"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ note
|
||||
description : "Objects that handle error..."
|
||||
legal: "See notice at end of class."
|
||||
status: "See notice at end of class."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
|
||||
revision: "$Revision: 97980 $"
|
||||
|
||||
class
|
||||
ERROR_HANDLER
|
||||
@@ -216,7 +216,7 @@ feature -- Basic operation
|
||||
on_error_added (a_error)
|
||||
end
|
||||
|
||||
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable STRING_32)
|
||||
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_32)
|
||||
-- Add custom error to the stack of error
|
||||
local
|
||||
e: ERROR_CUSTOM
|
||||
|
||||
@@ -64,4 +64,9 @@
|
||||
<root all_classes="true"/>
|
||||
<setting name="platform" value="unix"/>
|
||||
</target>
|
||||
<target name="all_stable_with_ssl" extends="all_stable">
|
||||
<description>Compiling with ssl enabled</description>
|
||||
<root all_classes="true"/>
|
||||
<variable name="httpd_ssl_enabled" value="true"/>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -98,6 +98,8 @@ echo Install library: openid
|
||||
|
||||
echo Install library: uri_template
|
||||
%COPYCMD% %TMP_DIR%\library\text\parser\uri_template %TMP_CONTRIB_DIR%\library\text\parser\uri_template
|
||||
echo Install library: feed
|
||||
%COPYCMD% %TMP_DIR%\library\text\parser\feed %TMP_CONTRIB_DIR%\library\text\parser\feed
|
||||
|
||||
echo Install library: notification_email
|
||||
%SAFE_MD% %TMP_CONTRIB_DIR%\library\runtime
|
||||
|
||||
@@ -65,6 +65,8 @@ echo Uninstall library: security\openid
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\web\authentication\openid
|
||||
echo Uninstall library: uri_template
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\uri_template
|
||||
echo Uninstall library: feed
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\text\parser\feed
|
||||
echo Uninstall library: runtime\process\notification_email
|
||||
%RDCMD% %TMP_CONTRIB_DIR%\library\runtime\process\notification_email
|
||||
|
||||
|
||||
Reference in New Issue
Block a user