Change structure of EWF, to follow better categorization

This commit is contained in:
Jocelyn Fiat
2012-06-13 22:32:17 +02:00
parent 3df1a26220
commit db448001a1
134 changed files with 105 additions and 94 deletions

View File

@@ -0,0 +1,94 @@
note
description: "[
Default implementation of CURL_FUNCTION.
Fixing eventual issue from CURL_DEFAULT_FUNCTION
]"
status: "See notice at end of class."
legal: "See notice at end of class."
date: "$Date: 2009-04-09 20:51:20 +0200 (Thu, 09 Apr 2009) $"
revision: "$Revision: 78146 $"
class
LIBCURL_DEFAULT_FUNCTION
inherit
CURL_DEFAULT_FUNCTION
redefine
write_function,
debug_function,
dump
end
create
make
feature -- Command
write_function (a_data_pointer: POINTER; a_size, a_nmemb: INTEGER; a_object_id: POINTER): INTEGER
-- Redefine
local
l_c_string: C_STRING
l_identified: IDENTIFIED
do
Result := a_size * a_nmemb
create l_c_string.make_shared_from_pointer_and_count (a_data_pointer, Result)
create l_identified
if attached {CURL_STRING} l_identified.id_object (a_object_id.to_integer_32) as l_string then
l_string.append (l_c_string.substring (1, Result))
else
check False end
end
end
debug_function (a_curl_handle: POINTER; a_curl_infotype: INTEGER; a_char_pointer: POINTER; a_size: INTEGER; a_object_id: POINTER): INTEGER
-- Redefine
local
l_c_string: C_STRING
do
inspect
a_curl_infotype
when {CURL_INFO_TYPE}.curlinfo_data_in then
dump ("<= Recv data", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_data_out then
dump ("=> Send data", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_header_in then
dump ("<= Recv header", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_header_out then
dump ("=> Send header", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_ssl_data_in then
dump ("<= Recv SSL data", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_ssl_data_out then
dump ("=> Send SSL data", a_char_pointer, a_size)
when {CURL_INFO_TYPE}.curlinfo_text then
create l_c_string.make_by_pointer_and_count (a_char_pointer, a_size)
print ("%N== Info: " + l_c_string.substring (1, a_size))
else
check type_unknow: False end
end
end
feature {NONE} -- Implementation
dump (a_text: STRING; a_char_pointer: POINTER; a_size: INTEGER)
-- Dump debug information
local
l_c_string: C_STRING
do
create l_c_string.make_shared_from_pointer_and_count (a_char_pointer, a_size)
print ("%N" + a_text + "%N" + l_c_string.substring (1, a_size))
end
note
library: "cURL: Library of reusable components for Eiffel."
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,42 @@
note
description : "[
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
]"
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
LIBCURL_HTTP_CLIENT
inherit
HTTP_CLIENT
create
make
feature {NONE} -- Initialization
make
-- Initialize `Current'.
do
end
feature -- Status
new_session (a_base_url: READABLE_STRING_8): LIBCURL_HTTP_CLIENT_SESSION
do
create Result.make (a_base_url)
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,364 @@
note
description: "[
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
]"
date: "$Date$"
revision: "$Revision$"
class
LIBCURL_HTTP_CLIENT_REQUEST
inherit
HTTP_CLIENT_REQUEST
rename
make as make_request
redefine
session
end
create
make
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
apply_workaround
end
apply_workaround
-- Due to issue with Eiffel cURL on Windows 32bits
-- we need to do the following workaround
once
if attached (create {INET_ADDRESS_FACTORY}).create_localhost then
end
end
session: LIBCURL_HTTP_CLIENT_SESSION
feature -- Access
request_method: READABLE_STRING_8
feature -- Execution
execute: HTTP_CLIENT_RESPONSE
local
l_result: INTEGER
l_curl_string: CURL_STRING
l_url: READABLE_STRING_8
l_form: detachable CURL_FORM
l_last: CURL_FORM
l_upload_file: detachable RAW_FILE
l_uploade_file_read_function: detachable LIBCURL_UPLOAD_FILE_READ_FUNCTION
curl: detachable CURL_EXTERNALS
curl_easy: detachable CURL_EASY_EXTERNALS
curl_handle: POINTER
ctx: like context
p_slist: POINTER
retried: BOOLEAN
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
l_upload_data: detachable READABLE_STRING_8
l_upload_filename: detachable READABLE_STRING_8
l_headers: like headers
do
if not retried then
curl := session.curl
curl_easy := session.curl_easy
curl_handle := curl_easy.init
curl.global_init
ctx := context
--| Configure cURL session
initialize_curl_session (ctx, curl, curl_easy, curl_handle)
--| URL
l_url := url
if ctx /= Void then
append_parameters_to_url (ctx.query_parameters, l_url)
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)
l_headers := headers
-- Context
if ctx /= Void then
--| Credential
if ctx.credentials_required then
if attached credentials as l_credentials then
inspect auth_type_id
when {HTTP_CLIENT_CONSTANTS}.Auth_type_none then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_none)
when {HTTP_CLIENT_CONSTANTS}.Auth_type_basic then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_basic)
when {HTTP_CLIENT_CONSTANTS}.Auth_type_digest then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_digest)
when {HTTP_CLIENT_CONSTANTS}.Auth_type_any then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_any)
when {HTTP_CLIENT_CONSTANTS}.Auth_type_anysafe then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_anysafe)
else
end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_userpwd, l_credentials)
else
--| Credentials not provided ...
end
end
if ctx.has_upload_data then
l_upload_data := ctx.upload_data
end
if ctx.has_upload_filename then
l_upload_filename := ctx.upload_filename
end
if ctx.has_form_data then
l_form_data := ctx.form_parameters
check non_empty_form_data: not l_form_data.is_empty end
if l_upload_data = Void and l_upload_filename = Void then
-- Send as form-urlencoded
if
l_headers.has_key ("Content-Type") and then
attached l_headers.found_item as l_ct
then
if l_ct.starts_with ("application/x-www-form-urlencoded") then
-- Content-Type is already application/x-www-form-urlencoded
l_upload_data := ctx.form_parameters_to_url_encoded_string
else
-- Existing Content-Type and not application/x-www-form-urlencoded
end
else
l_upload_data := ctx.form_parameters_to_url_encoded_string
end
else
create l_form.make
create l_last.make
from
l_form_data.start
until
l_form_data.after
loop
curl.formadd_string_string (l_form, l_last,
{CURL_FORM_CONSTANTS}.curlform_copyname, l_form_data.key_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_copycontents, l_form_data.item_for_iteration,
{CURL_FORM_CONSTANTS}.curlform_end
)
l_form_data.forth
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
end
if l_upload_data /= Void then
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
elseif l_upload_filename /= Void then
check
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
or request_method.is_case_insensitive_equal ("PUT")
end
create l_upload_file.make (l_upload_filename)
if l_upload_file.exists and then l_upload_file.is_readable then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_upload, 1)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_infilesize, l_upload_file.count)
-- specify callback read function for upload file
create l_uploade_file_read_function.make_with_file (l_upload_file)
l_upload_file.open_read
curl_easy.set_curl_function (l_uploade_file_read_function)
curl_easy.set_read_function (curl_handle)
end
else
check no_upload_data: l_upload_data = Void and l_upload_filename = Void end
end
end -- ctx /= Void
--| Header
across
l_headers as curs
loop
p_slist := curl.slist_append (p_slist, curs.key + ": " + curs.item)
end
p_slist := curl.slist_append (p_slist, "Expect:")
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p_slist)
--| Execution
curl_easy.set_read_function (curl_handle)
curl_easy.set_write_function (curl_handle)
if is_debug then
curl_easy.set_debug_function (curl_handle)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_verbose, 1)
end
create l_curl_string.make_empty
curl_easy.setopt_curl_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_writedata, l_curl_string)
create Result.make
l_result := curl_easy.perform (curl_handle)
--| Result
if l_result = {CURL_CODES}.curle_ok then
Result.status := response_status_code (curl_easy, curl_handle)
set_header_and_body_to (l_curl_string.string, Result)
else
Result.set_error_occurred (True)
Result.status := response_status_code (curl_easy, curl_handle)
end
--| Cleaning
curl.global_cleanup
curl_easy.cleanup (curl_handle)
else
create Result.make
Result.set_error_occurred (True)
end
--| Remaining cleaning
if l_form /= Void then
l_form.dispose
end
if curl /= Void and then p_slist /= default_pointer then
curl.slist_free_all (p_slist)
end
if l_upload_file /= Void and then not l_upload_file.is_closed then
l_upload_file.close
end
rescue
retried := True
if curl /= Void then
curl.global_cleanup
curl := Void
end
if curl_easy /= Void and curl_handle /= default_pointer then
curl_easy.cleanup (curl_handle)
curl_easy := Void
end
retry
end
initialize_curl_session (ctx: like context; curl: CURL_EXTERNALS; curl_easy: CURL_EASY_EXTERNALS; curl_handle: POINTER)
local
l_proxy: like proxy
do
--| RESPONSE HEADERS
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_header, 1)
--| PROXY ...
if ctx /= Void then
l_proxy := ctx.proxy
end
if l_proxy = Void then
l_proxy := proxy
end
if l_proxy /= Void then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_proxyport, l_proxy.port)
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_proxy, l_proxy.host)
end
--| Timeout
if timeout > 0 then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_timeout, timeout)
end
--| Connect Timeout
if connect_timeout > 0 then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_connecttimeout, timeout)
end
--| Redirection
if max_redirects /= 0 then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_followlocation, 1)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_maxredirs, max_redirects)
else
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_followlocation, 0)
end
--| SSL
if is_insecure then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifyhost, 0)
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifypeer, 0)
end
--| Request method
if request_method.is_case_insensitive_equal ("GET") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpget, 1)
elseif request_method.is_case_insensitive_equal ("POST") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_post, 1)
elseif request_method.is_case_insensitive_equal ("PUT") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_put, 1)
elseif request_method.is_case_insensitive_equal ("HEAD") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_nobody, 1)
elseif request_method.is_case_insensitive_equal ("DELETE") then
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_customrequest, "DELETE")
else
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_customrequest, request_method)
--| ignored
end
end
feature {NONE} -- Implementation
response_status_code (curl_easy: CURL_EASY_EXTERNALS; curl_handle: POINTER): INTEGER
local
l_result: INTEGER
a_data: CELL [detachable ANY]
do
create a_data.put (Void)
l_result := curl_easy.getinfo (curl_handle, {CURL_INFO_CONSTANTS}.curlinfo_response_code, a_data)
if l_result = 0 and then attached {INTEGER} a_data.item as l_http_status then
Result := l_http_status
else
Result := 0
end
end
set_header_and_body_to (a_source: READABLE_STRING_8; res: HTTP_CLIENT_RESPONSE)
-- Parse `a_source' response
-- and set `header' and `body' from HTTP_CLIENT_RESPONSE `res'
local
pos, l_start : INTEGER
do
l_start := a_source.substring_index ("%R%N", 1)
if l_start > 0 then
--| Skip first line which is the status line
--| ex: HTTP/1.1 200 OK%R%N
l_start := l_start + 2
end
if l_start < a_source.count and then a_source[l_start] = '%R' and a_source[l_start + 1] = '%N' then
res.set_body (a_source)
else
pos := a_source.substring_index ("%R%N%R%N", l_start)
if pos > 0 then
res.set_raw_header (a_source.substring (l_start, pos + 1)) --| Keep the last %R%N
res.set_body (a_source.substring (pos + 4, a_source.count))
else
res.set_body (a_source)
end
end
end
note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,160 @@
note
description: "[
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
]"
date: "$Date$"
revision: "$Revision$"
class
LIBCURL_HTTP_CLIENT_SESSION
inherit
HTTP_CLIENT_SESSION
create
make
feature {NONE} -- Initialization
initialize
do
create curl -- cURL externals
create curl_easy -- cURL easy externals
curl_easy.set_curl_function (create {LIBCURL_DEFAULT_FUNCTION}.make)
end
feature -- Basic operation
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current, ctx)
Result := req.execute
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current, ctx)
Result := req.execute
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_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_post (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
local
req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
f: detachable RAW_FILE
l_data: detachable READABLE_STRING_8
do
--| Quick and dirty hack using real file, for PUT uploaded data
--| FIXME [2012-05-23]: better use libcurl for that purpose
ctx := a_ctx
if data /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_data (data)
end
if ctx /= Void then
l_data := ctx.upload_data
end
if l_data /= Void then
create f.make_open_write (create {FILE_NAME}.make_temporary_name)
f.put_string (l_data)
f.close
check ctx /= Void then
ctx.set_upload_data (Void)
ctx.set_upload_filename (f.name)
end
end
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
Result := req.execute
if f /= Void then
f.delete
end
if l_data /= Void and a_ctx /= Void then
a_ctx.set_upload_filename (Void)
a_ctx.set_upload_data (l_data)
end
end
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
do
ctx := a_ctx
if fn /= Void then
if ctx = Void then
create ctx.make
end
ctx.set_upload_filename (fn)
end
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
Result := req.execute
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local
req: HTTP_CLIENT_REQUEST
do
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current, ctx)
Result := req.execute
end
feature {NONE} -- Implementation
impl_post (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
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
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current, ctx)
Result := req.execute
end
feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
curl: CURL_EXTERNALS
-- cURL externals
curl_easy: CURL_EASY_EXTERNALS
-- cURL easy externals
;note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,78 @@
note
description: "[
LIBCURL_UPLOAD_FILE_READ_FUNCTION is used to uploaded file as part of the client request
]"
date: "$Date$"
revision: "$Revision$"
class
LIBCURL_UPLOAD_FILE_READ_FUNCTION
inherit
LIBCURL_DEFAULT_FUNCTION
redefine
read_function
end
create
make_with_file
feature {NONE} -- Initialization
make_with_file (f: FILE)
require
f_is_open: f.is_open_read
do
make
file_to_read := f
end
feature -- Access
file_to_read: detachable FILE
-- File for sending data
feature -- Basic operation
read_function (a_data_pointer: POINTER; a_size, a_nmemb: INTEGER_32; a_object_id: POINTER): INTEGER_32
-- <Precursor>
local
l_pointer: MANAGED_POINTER
l_max_transfer, l_byte_transfered: INTEGER
do
if attached file_to_read as l_file and then not l_file.after then
l_max_transfer := a_size * a_nmemb
if l_max_transfer > l_file.count - l_file.position then
l_max_transfer := l_file.count - l_file.position
end
create l_pointer.share_from_pointer (a_data_pointer, l_max_transfer)
from
until
l_file.after or l_byte_transfered >= l_max_transfer
loop
l_file.read_character
l_pointer.put_character (l_file.last_character, l_byte_transfered)
l_byte_transfered := l_byte_transfered + 1
end
Result := l_max_transfer
else
-- Result is 0 means stop file transfer
Result := 0
end
end
note
copyright: "2011-2012, 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