1263 lines
33 KiB
Plaintext
1263 lines
33 KiB
Plaintext
note
|
||
description: "[
|
||
Request instanciated from a hash_table of meta variables
|
||
]"
|
||
specification: "EWSGI specification https://github.com/Eiffel-World/Eiffel-Web-Framework/wiki/EWSGI-specification"
|
||
legal: "See notice at end of class."
|
||
status: "See notice at end of class."
|
||
date: "$Date$"
|
||
revision: "$Revision$"
|
||
|
||
class
|
||
WGI_REQUEST_FROM_TABLE
|
||
|
||
inherit
|
||
WGI_REQUEST
|
||
|
||
create
|
||
make
|
||
|
||
feature {NONE} -- Initialization
|
||
|
||
make (a_vars: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]; a_input: like input)
|
||
require
|
||
vars_attached: a_vars /= Void
|
||
do
|
||
create error_handler.make
|
||
input := a_input
|
||
set_meta_parameters (a_vars)
|
||
create uploaded_files.make (0)
|
||
|
||
raw_post_data_recorded := True
|
||
|
||
initialize
|
||
analyze
|
||
end
|
||
|
||
set_meta_parameters (a_vars: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_GENERAL])
|
||
-- Fill with variable from `a_vars'
|
||
local
|
||
s: like meta_string_variable
|
||
table: HASH_TABLE [WGI_STRING_VALUE, READABLE_STRING_GENERAL]
|
||
l_query_string: like query_string
|
||
l_request_uri: detachable STRING_32
|
||
do
|
||
create {STRING_32} empty_string.make_empty
|
||
|
||
create table.make (a_vars.count)
|
||
table.compare_objects
|
||
meta_variables_table := table
|
||
from
|
||
a_vars.start
|
||
until
|
||
a_vars.after
|
||
loop
|
||
table.force (new_string_value (a_vars.key_for_iteration, a_vars.item_for_iteration), a_vars.key_for_iteration)
|
||
a_vars.forth
|
||
end
|
||
|
||
--| QUERY_STRING
|
||
l_query_string := meta_string_variable_or_default ({WGI_META_NAMES}.query_string, empty_string, False)
|
||
query_string := l_query_string
|
||
|
||
--| REQUEST_METHOD
|
||
request_method := meta_string_variable_or_default ({WGI_META_NAMES}.request_method, empty_string, False)
|
||
|
||
--| CONTENT_TYPE
|
||
s := meta_string_variable ({WGI_META_NAMES}.content_type)
|
||
if s /= Void and then not s.is_empty then
|
||
content_type := s
|
||
else
|
||
content_type := Void
|
||
end
|
||
|
||
--| CONTENT_LENGTH
|
||
s := meta_string_variable ({WGI_META_NAMES}.content_length)
|
||
content_length := s
|
||
if s /= Void and then s.is_natural_64 then
|
||
content_length_value := s.to_natural_64
|
||
else
|
||
--| content_length := 0
|
||
end
|
||
|
||
--| PATH_INFO
|
||
path_info := meta_string_variable_or_default ({WGI_META_NAMES}.path_info, empty_string, False)
|
||
|
||
--| SERVER_NAME
|
||
server_name := meta_string_variable_or_default ({WGI_META_NAMES}.server_name, empty_string, False)
|
||
|
||
--| SERVER_PORT
|
||
s := meta_string_variable ({WGI_META_NAMES}.server_port)
|
||
if s /= Void and then s.is_integer then
|
||
server_port := s.to_integer
|
||
else
|
||
server_port := 80
|
||
end
|
||
|
||
--| SCRIPT_NAME
|
||
script_name := meta_string_variable_or_default ({WGI_META_NAMES}.script_name, empty_string, False)
|
||
|
||
--| REMOTE_ADDR
|
||
remote_addr := meta_string_variable_or_default ({WGI_META_NAMES}.remote_addr, empty_string, False)
|
||
|
||
--| REMOTE_HOST
|
||
remote_host := meta_string_variable_or_default ({WGI_META_NAMES}.remote_host, empty_string, False)
|
||
|
||
--| REQUEST_URI
|
||
s := meta_string_variable ({WGI_META_NAMES}.request_uri)
|
||
if s /= Void then
|
||
l_request_uri := s
|
||
else
|
||
--| It might occur that REQUEST_URI is not available, so let's compute it from SCRIPT_NAME
|
||
create l_request_uri.make_from_string (script_name)
|
||
if not l_query_string.is_empty then
|
||
l_request_uri.append_character ('?')
|
||
l_request_uri.append (l_query_string)
|
||
end
|
||
end
|
||
request_uri := single_slash_starting_string (l_request_uri)
|
||
end
|
||
|
||
initialize
|
||
-- Specific initialization
|
||
do
|
||
--| Here one can set its own environment entries if needed
|
||
if meta_variable ({WGI_META_NAMES}.request_time) = Void then
|
||
set_meta_string_variable ({WGI_META_NAMES}.request_time, date_time_utilities.unix_time_stamp (Void).out)
|
||
end
|
||
end
|
||
|
||
feature -- Status
|
||
|
||
raw_post_data_recorded: BOOLEAN assign set_raw_post_data_recorded
|
||
-- Record RAW POST DATA in meta parameters
|
||
-- otherwise just forget about it
|
||
-- Default: true
|
||
--| warning: you might keep in memory big amount of memory ...
|
||
|
||
feature -- Error handling
|
||
|
||
has_error: BOOLEAN
|
||
do
|
||
Result := error_handler.has_error
|
||
end
|
||
|
||
error_handler: ERROR_HANDLER
|
||
-- Error handler
|
||
-- By default initialized to new handler
|
||
|
||
feature -- Access: Input
|
||
|
||
input: WGI_INPUT_STREAM
|
||
-- Server input channel
|
||
|
||
feature -- Access extra information
|
||
|
||
request_time: detachable DATE_TIME
|
||
-- Request time (UTC)
|
||
do
|
||
if
|
||
attached {WGI_STRING_VALUE} meta_variable ({WGI_META_NAMES}.request_time) as t and then
|
||
t.string.is_integer_64
|
||
then
|
||
Result := date_time_utilities.unix_time_stamp_to_date_time (t.string.to_integer_64)
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Access: CGI meta parameters
|
||
|
||
meta_variables_table: HASH_TABLE [WGI_STRING_VALUE, READABLE_STRING_GENERAL]
|
||
-- CGI Environment parameters
|
||
|
||
feature -- Access: CGI meta parameters
|
||
|
||
meta_variables: ITERABLE [WGI_STRING_VALUE]
|
||
do
|
||
Result := meta_variables_table
|
||
end
|
||
|
||
meta_variable (a_name: READABLE_STRING_GENERAL): detachable WGI_STRING_VALUE
|
||
-- CGI meta variable related to `a_name'
|
||
do
|
||
Result := meta_variables_table.item (a_name)
|
||
end
|
||
|
||
meta_string_variable_or_default (a_name: READABLE_STRING_GENERAL; a_default: READABLE_STRING_32; use_default_when_empty: BOOLEAN): READABLE_STRING_32
|
||
-- Value for meta parameter `a_name'
|
||
-- If not found, return `a_default'
|
||
require
|
||
a_name_not_empty: a_name /= Void and then not a_name.is_empty
|
||
do
|
||
if attached meta_variable (a_name) as val then
|
||
Result := val.string
|
||
if use_default_when_empty and then Result.is_empty then
|
||
Result := a_default
|
||
end
|
||
else
|
||
Result := a_default
|
||
end
|
||
end
|
||
|
||
set_meta_string_variable (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_32)
|
||
do
|
||
meta_variables_table.force (new_string_value (a_name, a_value), a_name)
|
||
ensure
|
||
param_set: attached {WGI_STRING_VALUE} meta_variable (a_name) as val and then val ~ a_value
|
||
end
|
||
|
||
unset_meta_variable (a_name: READABLE_STRING_GENERAL)
|
||
do
|
||
meta_variables_table.remove (a_name)
|
||
ensure
|
||
param_unset: meta_variable (a_name) = Void
|
||
end
|
||
|
||
feature -- Access: CGI meta parameters - 1.1
|
||
|
||
auth_type: detachable READABLE_STRING_32
|
||
|
||
content_length: detachable READABLE_STRING_32
|
||
|
||
content_length_value: NATURAL_64
|
||
|
||
content_type: detachable READABLE_STRING_32
|
||
|
||
gateway_interface: READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable_or_default ({WGI_META_NAMES}.gateway_interface, "", False)
|
||
end
|
||
|
||
path_info: READABLE_STRING_32
|
||
-- <Precursor/>
|
||
--
|
||
--| For instance, if the current script was accessed via the URL
|
||
--| http://www.example.com/eiffel/path_info.exe/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain /some/stuff.
|
||
--|
|
||
--| Note that is the PATH_INFO variable does not exists, the `path_info' value will be empty
|
||
|
||
path_translated: detachable READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.path_translated)
|
||
end
|
||
|
||
query_string: READABLE_STRING_32
|
||
|
||
remote_addr: READABLE_STRING_32
|
||
|
||
remote_host: READABLE_STRING_32
|
||
|
||
remote_ident: detachable READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.remote_ident)
|
||
end
|
||
|
||
remote_user: detachable READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.remote_user)
|
||
end
|
||
|
||
request_method: READABLE_STRING_32
|
||
|
||
script_name: READABLE_STRING_32
|
||
|
||
server_name: READABLE_STRING_32
|
||
|
||
server_port: INTEGER
|
||
|
||
server_protocol: READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable_or_default ({WGI_META_NAMES}.server_protocol, "HTTP/1.0", True)
|
||
end
|
||
|
||
server_software: READABLE_STRING_32
|
||
do
|
||
Result := meta_string_variable_or_default ({WGI_META_NAMES}.server_software, "Unknown Server", True)
|
||
end
|
||
|
||
feature -- Access: HTTP_* CGI meta parameters - 1.1
|
||
|
||
http_accept: detachable READABLE_STRING_32
|
||
-- Contents of the Accept: header from the current request, if there is one.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_accept)
|
||
end
|
||
|
||
http_accept_charset: detachable READABLE_STRING_32
|
||
-- Contents of the Accept-Charset: header from the current request, if there is one.
|
||
-- Example: 'iso-8859-1,*,utf-8'.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_accept_charset)
|
||
end
|
||
|
||
http_accept_encoding: detachable READABLE_STRING_32
|
||
-- Contents of the Accept-Encoding: header from the current request, if there is one.
|
||
-- Example: 'gzip'.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_accept_encoding)
|
||
end
|
||
|
||
http_accept_language: detachable READABLE_STRING_32
|
||
-- Contents of the Accept-Language: header from the current request, if there is one.
|
||
-- Example: 'en'.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_accept_language)
|
||
end
|
||
|
||
http_connection: detachable READABLE_STRING_32
|
||
-- Contents of the Connection: header from the current request, if there is one.
|
||
-- Example: 'Keep-Alive'.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_connection)
|
||
end
|
||
|
||
http_host: detachable READABLE_STRING_32
|
||
-- Contents of the Host: header from the current request, if there is one.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_host)
|
||
end
|
||
|
||
http_referer: detachable READABLE_STRING_32
|
||
-- The address of the page (if any) which referred the user agent to the current page.
|
||
-- This is set by the user agent.
|
||
-- Not all user agents will set this, and some provide the ability to modify HTTP_REFERER as a feature.
|
||
-- In short, it cannot really be trusted.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_referer)
|
||
end
|
||
|
||
http_user_agent: detachable READABLE_STRING_32
|
||
-- Contents of the User-Agent: header from the current request, if there is one.
|
||
-- This is a string denoting the user agent being which is accessing the page.
|
||
-- A typical example is: Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586).
|
||
-- Among other things, you can use this value to tailor your page's
|
||
-- output to the capabilities of the user agent.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_user_agent)
|
||
end
|
||
|
||
http_authorization: detachable READABLE_STRING_32
|
||
-- Contents of the Authorization: header from the current request, if there is one.
|
||
do
|
||
Result := meta_string_variable ({WGI_META_NAMES}.http_authorization)
|
||
end
|
||
|
||
feature -- Access: Extension to CGI meta parameters - 1.1
|
||
|
||
request_uri: READABLE_STRING_32
|
||
-- The URI which was given in order to access this page; for instance, '/index.html'.
|
||
|
||
orig_path_info: detachable READABLE_STRING_32
|
||
-- Original version of `path_info' before processed by Current environment
|
||
|
||
feature {NONE} -- Element change: CGI meta parameter related to PATH_INFO
|
||
|
||
set_orig_path_info (s: READABLE_STRING_32)
|
||
-- Set ORIG_PATH_INFO to `s'
|
||
require
|
||
s_attached: s /= Void
|
||
do
|
||
orig_path_info := s
|
||
set_meta_string_variable ({WGI_META_NAMES}.orig_path_info, s)
|
||
end
|
||
|
||
unset_orig_path_info
|
||
-- Unset ORIG_PATH_INFO
|
||
do
|
||
orig_path_info := Void
|
||
unset_meta_variable ({WGI_META_NAMES}.orig_path_info)
|
||
ensure
|
||
unset: attached meta_variable ({WGI_META_NAMES}.orig_path_info)
|
||
end
|
||
|
||
update_path_info
|
||
-- Fix and update PATH_INFO value if needed
|
||
local
|
||
l_path_info: STRING
|
||
do
|
||
l_path_info := path_info
|
||
--| Warning
|
||
--| on IIS: we might have PATH_INFO = /sample.exe/foo/bar
|
||
--| on apache: PATH_INFO = /foo/bar
|
||
--| So, we might need to check with SCRIPT_NAME and remove it on IIS
|
||
--| store original PATH_INFO in ORIG_PATH_INFO
|
||
if l_path_info.is_empty then
|
||
unset_orig_path_info
|
||
else
|
||
set_orig_path_info (l_path_info)
|
||
if attached script_name as l_script_name then
|
||
if l_path_info.starts_with (l_script_name) then
|
||
path_info := l_path_info.substring (l_script_name.count + 1 , l_path_info.count)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Query parameters
|
||
|
||
query_parameters_table: HASH_TABLE [WGI_VALUE, READABLE_STRING_GENERAL]
|
||
-- Variables extracted from QUERY_STRING
|
||
local
|
||
vars: like internal_query_parameters_table
|
||
p,e: INTEGER
|
||
rq_uri: like request_uri
|
||
s: detachable STRING
|
||
do
|
||
vars := internal_query_parameters_table
|
||
if vars = Void then
|
||
s := query_string
|
||
if s = Void then
|
||
rq_uri := request_uri
|
||
p := rq_uri.index_of ('?', 1)
|
||
if p > 0 then
|
||
e := rq_uri.index_of ('#', p + 1)
|
||
if e = 0 then
|
||
e := rq_uri.count
|
||
else
|
||
e := e - 1
|
||
end
|
||
s := rq_uri.substring (p+1, e)
|
||
end
|
||
end
|
||
vars := urlencoded_parameters (s, True)
|
||
vars.compare_objects
|
||
internal_query_parameters_table := vars
|
||
end
|
||
Result := vars
|
||
end
|
||
|
||
feature -- Query parameters
|
||
|
||
query_parameters: ITERABLE [WGI_VALUE]
|
||
do
|
||
Result := query_parameters_table
|
||
end
|
||
|
||
query_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
|
||
-- Parameter for name `n'.
|
||
do
|
||
Result := query_parameters_table.item (a_name)
|
||
end
|
||
|
||
feature {NONE} -- Query parameters: implementation
|
||
|
||
urlencoded_parameters (a_content: detachable READABLE_STRING_8; decoding: BOOLEAN): HASH_TABLE [WGI_VALUE, STRING]
|
||
-- Import `a_content'
|
||
local
|
||
n, p, i, j: INTEGER
|
||
s: STRING
|
||
l_name,l_value: STRING_32
|
||
v: WGI_VALUE
|
||
do
|
||
if a_content = Void then
|
||
create Result.make (0)
|
||
else
|
||
n := a_content.count
|
||
if n = 0 then
|
||
create Result.make (0)
|
||
else
|
||
create Result.make (3)
|
||
from
|
||
p := 1
|
||
until
|
||
p = 0
|
||
loop
|
||
i := a_content.index_of ('&', p)
|
||
if i = 0 then
|
||
s := a_content.substring (p, n)
|
||
p := 0
|
||
else
|
||
s := a_content.substring (p, i - 1)
|
||
p := i + 1
|
||
end
|
||
if not s.is_empty then
|
||
j := s.index_of ('=', 1)
|
||
if j > 0 then
|
||
l_name := s.substring (1, j - 1)
|
||
l_value := s.substring (j + 1, s.count)
|
||
if decoding then
|
||
l_name := url_encoder.decoded_string (l_name)
|
||
l_value := url_encoder.decoded_string (l_value)
|
||
end
|
||
v := new_string_value (l_name, l_value)
|
||
if Result.has_key (l_name) and then attached Result.found_item as l_existing_value then
|
||
if attached {WGI_MULTIPLE_STRING_VALUE} l_existing_value as l_multi then
|
||
l_multi.add_value (v)
|
||
else
|
||
Result.force (create {WGI_MULTIPLE_STRING_VALUE}.make_with_array (<<l_existing_value, v>>), l_name)
|
||
check replaced: Result.found and then Result.found_item ~ l_existing_value end
|
||
end
|
||
else
|
||
Result.force (v, l_name)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Form fields and related
|
||
|
||
form_data_parameters_table: HASH_TABLE [WGI_VALUE, READABLE_STRING_GENERAL]
|
||
-- Variables sent by POST request
|
||
local
|
||
vars: like internal_form_data_parameters_table
|
||
s: STRING
|
||
n: NATURAL_64
|
||
l_type: like content_type
|
||
do
|
||
vars := internal_form_data_parameters_table
|
||
if vars = Void then
|
||
n := content_length_value
|
||
if n > 0 then
|
||
l_type := content_type
|
||
if
|
||
l_type /= Void and then
|
||
l_type.starts_with ({HTTP_CONSTANTS}.multipart_form)
|
||
then
|
||
create vars.make (5)
|
||
vars.compare_objects
|
||
--| FIXME: optimization ... fetch the input data progressively, otherwise we might run out of memory ...
|
||
s := form_input_data (n.to_integer_32) --| FIXME truncated from NAT64 to INT32
|
||
analyze_multipart_form (l_type, s, vars)
|
||
else
|
||
s := form_input_data (n.to_integer_32) --| FIXME truncated from NAT64 to INT32
|
||
vars := urlencoded_parameters (s, True)
|
||
end
|
||
if raw_post_data_recorded then
|
||
set_meta_string_variable ("RAW_POST_DATA", s)
|
||
end
|
||
else
|
||
create vars.make (0)
|
||
vars.compare_objects
|
||
end
|
||
internal_form_data_parameters_table := vars
|
||
end
|
||
Result := vars
|
||
end
|
||
|
||
feature -- Form fields and related
|
||
|
||
form_data_parameters: ITERABLE [WGI_VALUE]
|
||
do
|
||
Result := form_data_parameters_table
|
||
end
|
||
|
||
form_data_parameter (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
|
||
-- Field for name `a_name'.
|
||
do
|
||
Result := form_data_parameters_table.item (a_name)
|
||
end
|
||
|
||
uploaded_files: HASH_TABLE [WGI_UPLOADED_FILE_DATA, STRING]
|
||
-- Table of uploaded files information
|
||
--| name: original path from the user
|
||
--| type: content type
|
||
--| tmp_name: path to temp file that resides on server
|
||
--| tmp_base_name: basename of `tmp_name'
|
||
--| error: if /= 0 , there was an error : TODO ...
|
||
--| size: size of the file given by the http request
|
||
|
||
feature {NONE} -- Cookies
|
||
|
||
cookies_table: HASH_TABLE [WGI_VALUE, READABLE_STRING_GENERAL]
|
||
-- Expanded cookies variable
|
||
local
|
||
i,j,p,n: INTEGER
|
||
l_cookies: like internal_cookies_table
|
||
k,v,s: STRING
|
||
do
|
||
l_cookies := internal_cookies_table
|
||
if l_cookies = Void then
|
||
if attached {WGI_STRING_VALUE} meta_variable ({WGI_META_NAMES}.http_cookie) as val then
|
||
s := val.string
|
||
create l_cookies.make (5)
|
||
l_cookies.compare_objects
|
||
from
|
||
n := s.count
|
||
p := 1
|
||
i := 1
|
||
until
|
||
p < 1
|
||
loop
|
||
i := s.index_of ('=', p)
|
||
if i > 0 then
|
||
j := s.index_of (';', i)
|
||
if j = 0 then
|
||
j := n + 1
|
||
k := s.substring (p, i - 1)
|
||
v := s.substring (i + 1, n)
|
||
|
||
p := 0 -- force termination
|
||
else
|
||
k := s.substring (p, i - 1)
|
||
v := s.substring (i + 1, j - 1)
|
||
p := j + 1
|
||
end
|
||
l_cookies.force (new_string_value (k, v), k)
|
||
end
|
||
end
|
||
else
|
||
create l_cookies.make (0)
|
||
l_cookies.compare_objects
|
||
end
|
||
internal_cookies_table := l_cookies
|
||
end
|
||
Result := l_cookies
|
||
end
|
||
|
||
feature -- Cookies
|
||
|
||
cookies: ITERABLE [WGI_VALUE]
|
||
do
|
||
Result := cookies_table
|
||
end
|
||
|
||
cookie (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
|
||
-- Field for name `a_name'.
|
||
do
|
||
Result := cookies_table.item (a_name)
|
||
end
|
||
|
||
feature {NONE} -- Access: global variable
|
||
|
||
items_table: HASH_TABLE [WGI_VALUE, READABLE_STRING_GENERAL]
|
||
-- Table containing all the various variables
|
||
-- Warning: this is computed each time, if you change the content of other containers
|
||
-- this won't update this Result's content, unless you query it again
|
||
do
|
||
create Result.make (100)
|
||
|
||
across
|
||
meta_variables as vars
|
||
loop
|
||
Result.force (vars.item, vars.item.name)
|
||
end
|
||
|
||
across
|
||
query_parameters as vars
|
||
loop
|
||
Result.force (vars.item, vars.item.name)
|
||
end
|
||
|
||
across
|
||
form_data_parameters as vars
|
||
loop
|
||
Result.force (vars.item, vars.item.name)
|
||
end
|
||
|
||
across
|
||
cookies as vars
|
||
loop
|
||
Result.force (vars.item, vars.item.name)
|
||
end
|
||
|
||
end
|
||
|
||
feature -- Access: global variable
|
||
|
||
items: ITERABLE [WGI_VALUE]
|
||
do
|
||
Result := items_table
|
||
end
|
||
|
||
item (a_name: READABLE_STRING_GENERAL): detachable WGI_VALUE
|
||
-- Variable named `a_name' from any of the variables container
|
||
-- and following a specific order
|
||
-- execution, environment, get, post, cookies
|
||
local
|
||
v: detachable WGI_VALUE
|
||
do
|
||
v := meta_variable (a_name)
|
||
if v = Void then
|
||
v := query_parameter (a_name)
|
||
if v = Void then
|
||
v := form_data_parameter (a_name)
|
||
if v = Void then
|
||
v := cookie (a_name)
|
||
end
|
||
end
|
||
end
|
||
-- if s /= Void then
|
||
-- Result := s.as_string_32
|
||
-- end
|
||
end
|
||
|
||
string_item (a_name: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||
do
|
||
if attached {WGI_STRING_VALUE} item (a_name) as val then
|
||
Result := val.string
|
||
else
|
||
check is_string_value: False end
|
||
end
|
||
end
|
||
|
||
feature -- Uploaded File Handling
|
||
|
||
is_uploaded_file (a_filename: STRING): BOOLEAN
|
||
-- Is `a_filename' a file uploaded via HTTP Form
|
||
local
|
||
l_files: like uploaded_files
|
||
do
|
||
l_files := uploaded_files
|
||
if not l_files.is_empty then
|
||
from
|
||
l_files.start
|
||
until
|
||
l_files.after or Result
|
||
loop
|
||
if attached l_files.item_for_iteration.tmp_name as l_tmp_name and then l_tmp_name.same_string (a_filename) then
|
||
Result := True
|
||
end
|
||
l_files.forth
|
||
end
|
||
end
|
||
end
|
||
|
||
feature -- URL Utility
|
||
|
||
absolute_script_url (a_path: STRING): STRING
|
||
-- Absolute Url for the script if any, extended by `a_path'
|
||
do
|
||
Result := script_url (a_path)
|
||
if attached http_host as h then
|
||
Result.prepend (h)
|
||
Result.prepend ("http://")
|
||
else
|
||
--| Issue ??
|
||
end
|
||
end
|
||
|
||
script_url (a_path: STRING): STRING
|
||
-- Url relative to script name if any, extended by `a_path'
|
||
local
|
||
l_base_url: like internal_url_base
|
||
i,m,n: INTEGER
|
||
l_rq_uri: like request_uri
|
||
do
|
||
l_base_url := internal_url_base
|
||
if l_base_url = Void then
|
||
if attached script_name as l_script_name then
|
||
l_rq_uri := request_uri
|
||
if l_rq_uri.starts_with (l_script_name) then
|
||
l_base_url := l_script_name
|
||
else
|
||
--| Handle Rewrite url engine, to have clean path
|
||
from
|
||
i := 1
|
||
m := l_rq_uri.count
|
||
n := l_script_name.count
|
||
until
|
||
i > m or i > n or l_rq_uri[i] /= l_script_name[i]
|
||
loop
|
||
i := i + 1
|
||
end
|
||
if i > 1 then
|
||
if l_rq_uri[i-1] = '/' then
|
||
i := i -1
|
||
end
|
||
l_base_url := l_rq_uri.substring (1, i - 1)
|
||
end
|
||
end
|
||
end
|
||
if l_base_url = Void then
|
||
create l_base_url.make_empty
|
||
end
|
||
internal_url_base := l_base_url
|
||
end
|
||
Result := l_base_url + a_path
|
||
end
|
||
|
||
feature {NONE} -- Implementation: URL Utility
|
||
|
||
internal_url_base: detachable STRING
|
||
-- URL base of potential script
|
||
|
||
feature -- Element change
|
||
|
||
set_raw_post_data_recorded (b: BOOLEAN)
|
||
-- Set `raw_post_data_recorded' to `b'
|
||
do
|
||
raw_post_data_recorded := b
|
||
end
|
||
|
||
set_error_handler (ehdl: like error_handler)
|
||
-- Set `error_handler' to `ehdl'
|
||
do
|
||
error_handler := ehdl
|
||
end
|
||
|
||
feature {NONE} -- Temporary File handling
|
||
|
||
delete_uploaded_file (uf: WGI_UPLOADED_FILE_DATA)
|
||
-- Delete file `a_filename'
|
||
require
|
||
uf_valid: uf /= Void
|
||
local
|
||
f: RAW_FILE
|
||
do
|
||
if uploaded_files.has_item (uf) then
|
||
if attached uf.tmp_name as fn then
|
||
create f.make (fn)
|
||
if f.exists and then f.is_writable then
|
||
f.delete
|
||
else
|
||
error_handler.add_custom_error (0, "Can not delete uploaded file", "Can not delete file %""+ fn +"%"")
|
||
end
|
||
else
|
||
error_handler.add_custom_error (0, "Can not delete uploaded file", "Can not delete uploaded file %""+ uf.name +"%" Tmp File not found")
|
||
end
|
||
else
|
||
error_handler.add_custom_error (0, "Not an uploaded file", "This file %""+ uf.name +"%" is not an uploaded file.")
|
||
end
|
||
end
|
||
|
||
save_uploaded_file (a_content: STRING; a_up_fn_info: WGI_UPLOADED_FILE_DATA)
|
||
-- Save uploaded file content to `a_filename'
|
||
local
|
||
bn: STRING
|
||
l_safe_name: STRING
|
||
f: RAW_FILE
|
||
dn: STRING
|
||
fn: FILE_NAME
|
||
d: DIRECTORY
|
||
n: INTEGER
|
||
rescued: BOOLEAN
|
||
do
|
||
if not rescued then
|
||
dn := (create {EXECUTION_ENVIRONMENT}).current_working_directory
|
||
create d.make (dn)
|
||
if d.exists and then d.is_writable then
|
||
l_safe_name := safe_filename (a_up_fn_info.name)
|
||
from
|
||
create fn.make_from_string (dn)
|
||
bn := "tmp-" + l_safe_name
|
||
fn.set_file_name (bn)
|
||
create f.make (fn.string)
|
||
n := 0
|
||
until
|
||
not f.exists
|
||
or else n > 1_000
|
||
loop
|
||
n := n + 1
|
||
fn.make_from_string (dn)
|
||
bn := "tmp-" + n.out + "-" + l_safe_name
|
||
fn.set_file_name (bn)
|
||
f.make (fn.string)
|
||
end
|
||
|
||
if not f.exists or else f.is_writable then
|
||
a_up_fn_info.set_tmp_name (f.name)
|
||
a_up_fn_info.set_tmp_basename (bn)
|
||
f.open_write
|
||
f.put_string (a_content)
|
||
f.close
|
||
else
|
||
a_up_fn_info.set_error (-1)
|
||
end
|
||
else
|
||
error_handler.add_custom_error (0, "Directory not writable", "Can not create file in directory %""+ dn +"%"")
|
||
end
|
||
else
|
||
a_up_fn_info.set_error (-1)
|
||
end
|
||
rescue
|
||
rescued := True
|
||
retry
|
||
end
|
||
|
||
safe_filename (fn: STRING): STRING
|
||
local
|
||
c: CHARACTER
|
||
i, n, p: INTEGER
|
||
l_accentued, l_non_accentued: STRING
|
||
do
|
||
l_accentued := "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||
l_non_accentued := "AAAAAACEEEEIIIIOOOOOUUUUYaaaaaaceeeeiiiioooooouuuuyy"
|
||
|
||
--| Compute safe filename, to avoid creating impossible filename, or dangerous one
|
||
from
|
||
i := 1
|
||
n := fn.count
|
||
create Result.make (n)
|
||
until
|
||
i > n
|
||
loop
|
||
c := fn[i]
|
||
inspect c
|
||
when '.', '-', '_' then
|
||
Result.extend (c)
|
||
when 'A' .. 'Z', 'a' .. 'z', '0' .. '9' then
|
||
Result.extend (c)
|
||
else
|
||
p := l_accentued.index_of (c, 1)
|
||
if p > 0 then
|
||
Result.extend (l_non_accentued[p])
|
||
else
|
||
Result.extend ('-')
|
||
end
|
||
end
|
||
i := i + 1
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Implementation: Form analyzer
|
||
|
||
analyze_multipart_form (t: STRING; s: STRING; vars: like form_data_parameters_table)
|
||
-- Analyze multipart form content
|
||
--| FIXME[2011-06-21]: integrate eMIME parser library
|
||
require
|
||
t_attached: t /= Void
|
||
s_attached: s /= Void
|
||
vars_attached: vars /= Void
|
||
local
|
||
p,i,next_b: INTEGER
|
||
l_boundary_prefix: STRING
|
||
l_boundary: STRING
|
||
l_boundary_len: INTEGER
|
||
m: STRING
|
||
is_crlf: BOOLEAN
|
||
do
|
||
p := t.substring_index ("boundary=", 1)
|
||
if p > 0 then
|
||
l_boundary := t.substring (p + 9, t.count)
|
||
p := s.substring_index (l_boundary, 1)
|
||
if p > 1 then
|
||
l_boundary_prefix := s.substring (1, p - 1)
|
||
l_boundary := l_boundary_prefix + l_boundary
|
||
else
|
||
create l_boundary_prefix.make_empty
|
||
end
|
||
l_boundary_len := l_boundary.count
|
||
--| Let's support either %R%N and %N ...
|
||
--| Since both cases might occurs (for instance, our implementation of CGI does not have %R%N)
|
||
--| then let's be as flexible as possible on this.
|
||
is_crlf := s[l_boundary_len + 1] = '%R'
|
||
from
|
||
i := 1 + l_boundary_len + 1
|
||
if is_crlf then
|
||
i := i + 1 --| +1 = CR = %R
|
||
end
|
||
next_b := i
|
||
until
|
||
i = 0
|
||
loop
|
||
next_b := s.substring_index (l_boundary, i)
|
||
if next_b > 0 then
|
||
if is_crlf then
|
||
m := s.substring (i, next_b - 1 - 2) --| 2 = CR LF = %R %N
|
||
else
|
||
m := s.substring (i, next_b - 1 - 1) --| 1 = LF = %N
|
||
end
|
||
analyze_multipart_form_input (m, vars)
|
||
i := next_b + l_boundary_len + 1
|
||
if is_crlf then
|
||
i := i + 1 --| +1 = CR = %R
|
||
end
|
||
else
|
||
if is_crlf then
|
||
i := i + 1
|
||
end
|
||
m := s.substring (i - 1, s.count)
|
||
m.right_adjust
|
||
if not l_boundary_prefix.same_string (m) then
|
||
error_handler.add_custom_error (0, "Invalid form data", "Invalid ending for form data from input")
|
||
end
|
||
i := next_b
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
analyze_multipart_form_input (s: STRING; vars_post: like form_data_parameters_table)
|
||
-- Analyze multipart entry
|
||
require
|
||
s_not_empty: s /= Void and then not s.is_empty
|
||
local
|
||
n, i,p, b,e: INTEGER
|
||
l_name, l_filename, l_content_type: detachable STRING
|
||
l_header: detachable STRING
|
||
l_content: detachable STRING
|
||
l_line: detachable STRING
|
||
l_up_file_info: WGI_UPLOADED_FILE_DATA
|
||
do
|
||
from
|
||
p := 1
|
||
n := s.count
|
||
until
|
||
p > n or l_header /= Void
|
||
loop
|
||
inspect s[p]
|
||
when '%R' then -- CR
|
||
if
|
||
n >= p + 3 and then
|
||
s[p+1] = '%N' and then -- LF
|
||
s[p+2] = '%R' and then -- CR
|
||
s[p+3] = '%N' -- LF
|
||
then
|
||
l_header := s.substring (1, p + 1)
|
||
l_content := s.substring (p + 4, n)
|
||
end
|
||
when '%N' then
|
||
if
|
||
n >= p + 1 and then
|
||
s[p+1] = '%N'
|
||
then
|
||
l_header := s.substring (1, p)
|
||
l_content := s.substring (p + 2, n)
|
||
end
|
||
else
|
||
end
|
||
p := p + 1
|
||
end
|
||
if l_header /= Void and l_content /= Void then
|
||
from
|
||
i := 1
|
||
n := l_header.count
|
||
until
|
||
i = 0 or i > n
|
||
loop
|
||
l_line := Void
|
||
b := i
|
||
p := l_header.index_of ('%N', b)
|
||
if p > 0 then
|
||
if l_header[p - 1] = '%R' then
|
||
p := p - 1
|
||
i := p + 2
|
||
else
|
||
i := p + 1
|
||
end
|
||
end
|
||
if p > 0 then
|
||
l_line := l_header.substring (b, p - 1)
|
||
if l_line.starts_with ("Content-Disposition: form-data") then
|
||
p := l_line.substring_index ("name=", 1)
|
||
if p > 0 then
|
||
p := p + 4 --| 4 = ("name=").count - 1
|
||
if l_line.valid_index (p+1) and then l_line[p+1] = '%"' then
|
||
p := p + 1
|
||
e := l_line.index_of ('"', p + 1)
|
||
else
|
||
e := l_line.index_of (';', p + 1)
|
||
if e = 0 then
|
||
e := l_line.count
|
||
end
|
||
end
|
||
l_name := l_header.substring (p + 1, e - 1)
|
||
end
|
||
|
||
p := l_line.substring_index ("filename=", 1)
|
||
if p > 0 then
|
||
p := p + 8 --| 8 = ("filename=").count - 1
|
||
if l_line.valid_index (p+1) and then l_line[p+1] = '%"' then
|
||
p := p + 1
|
||
e := l_line.index_of ('"', p + 1)
|
||
else
|
||
e := l_line.index_of (';', p + 1)
|
||
if e = 0 then
|
||
e := l_line.count
|
||
end
|
||
end
|
||
l_filename := l_header.substring (p + 1, e - 1)
|
||
end
|
||
elseif l_line.starts_with ("Content-Type: ") then
|
||
l_content_type := l_line.substring (15, l_line.count)
|
||
end
|
||
else
|
||
i := 0
|
||
end
|
||
end
|
||
if l_name /= Void then
|
||
if l_filename /= Void then
|
||
if l_content_type = Void then
|
||
l_content_type := default_content_type
|
||
end
|
||
create l_up_file_info.make (l_filename, l_content_type, l_content.count)
|
||
save_uploaded_file (l_content, l_up_file_info)
|
||
uploaded_files.force (l_up_file_info, l_name)
|
||
else
|
||
vars_post.force (new_string_value (l_name, l_content), l_name)
|
||
end
|
||
else
|
||
error_handler.add_custom_error (0, "unamed multipart entry", Void)
|
||
end
|
||
else
|
||
error_handler.add_custom_error (0, "missformed multipart entry", Void)
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Internal value
|
||
|
||
default_content_type: STRING = "text/plain"
|
||
-- Default content type
|
||
|
||
form_input_data (nb: INTEGER): STRING
|
||
-- data from input form
|
||
local
|
||
n: INTEGER
|
||
t: STRING
|
||
do
|
||
from
|
||
n := nb
|
||
create Result.make (n)
|
||
if n > 1_024 then
|
||
n := 1_024
|
||
end
|
||
until
|
||
n <= 0
|
||
loop
|
||
read_input (n)
|
||
t := last_input_string
|
||
Result.append_string (t)
|
||
if t.count < n then
|
||
n := 0
|
||
end
|
||
n := nb - t.count
|
||
end
|
||
end
|
||
|
||
internal_query_parameters_table: detachable like query_parameters_table
|
||
-- cached value for `query_parameters'
|
||
|
||
internal_form_data_parameters_table: detachable like form_data_parameters_table
|
||
-- cached value for `form_fields'
|
||
|
||
internal_cookies_table: detachable like cookies_table
|
||
-- cached value for `cookies'
|
||
|
||
feature {NONE} -- I/O: implementation
|
||
|
||
read_input (nb: INTEGER)
|
||
-- Read `nb' bytes from `input'
|
||
do
|
||
input.read_stream (nb)
|
||
end
|
||
|
||
last_input_string: STRING
|
||
-- Last string read from `input'
|
||
do
|
||
Result := input.last_string
|
||
end
|
||
|
||
feature {NONE} -- Implementation
|
||
|
||
report_bad_request_error (a_message: detachable STRING)
|
||
-- Report error
|
||
local
|
||
e: EWF_ERROR
|
||
do
|
||
create e.make ({HTTP_STATUS_CODE}.bad_request)
|
||
if a_message /= Void then
|
||
e.set_message (a_message)
|
||
end
|
||
error_handler.add_error (e)
|
||
end
|
||
|
||
analyze
|
||
-- Extract relevant meta parameters
|
||
local
|
||
s: detachable READABLE_STRING_32
|
||
do
|
||
s := request_uri
|
||
if s.is_empty then
|
||
report_bad_request_error ("Missing URI")
|
||
end
|
||
if not has_error then
|
||
s := request_method
|
||
if s.is_empty then
|
||
report_bad_request_error ("Missing request method")
|
||
end
|
||
end
|
||
if not has_error then
|
||
s := http_host
|
||
if s = Void or else s.is_empty then
|
||
report_bad_request_error ("Missing host header")
|
||
end
|
||
end
|
||
if not has_error then
|
||
update_path_info
|
||
end
|
||
end
|
||
|
||
feature {NONE} -- Implementation: utilities
|
||
|
||
single_slash_starting_string (s: READABLE_STRING_32): STRING_32
|
||
-- Return the string `s' (or twin) with one and only one starting slash
|
||
local
|
||
i, n: INTEGER
|
||
do
|
||
n := s.count
|
||
if n > 1 then
|
||
if s[1] /= '/' then
|
||
create Result.make (1 + n)
|
||
Result.append_character ('/')
|
||
Result.append (s)
|
||
elseif s[2] = '/' then
|
||
--| We need to remove all starting slash, except one
|
||
from
|
||
i := 3
|
||
until
|
||
i > n
|
||
loop
|
||
if s[i] /= '/' then
|
||
n := 0 --| exit loop
|
||
else
|
||
i := i + 1
|
||
end
|
||
end
|
||
n := s.count
|
||
check i >= 2 and i <= n end
|
||
Result := s.substring (i - 1, s.count)
|
||
else
|
||
--| starts with one '/' and only one
|
||
Result := s
|
||
end
|
||
elseif n = 1 then
|
||
if s[1] = '/' then
|
||
Result := s
|
||
else
|
||
create Result.make (2)
|
||
Result.append_character ('/')
|
||
Result.append (s)
|
||
end
|
||
else --| n = 0
|
||
create Result.make_filled ('/', 1)
|
||
end
|
||
ensure
|
||
one_starting_slash: Result[1] = '/' and (Result.count = 1 or else Result[2] /= '/')
|
||
end
|
||
|
||
new_string_value (a_name: READABLE_STRING_GENERAL; a_value: READABLE_STRING_32): WGI_STRING_VALUE
|
||
do
|
||
create Result.make (a_name, a_value)
|
||
end
|
||
|
||
empty_string: READABLE_STRING_32
|
||
-- Reusable empty string
|
||
|
||
url_encoder: URL_ENCODER
|
||
once
|
||
create Result
|
||
end
|
||
|
||
date_time_utilities: HTTP_DATE_TIME_UTILITIES
|
||
-- Utilities classes related to date and time.
|
||
once
|
||
create Result
|
||
end
|
||
|
||
invariant
|
||
empty_string_unchanged: empty_string.is_empty
|
||
|
||
note
|
||
copyright: "2011-2011, 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
|