Files
EWF/library/server/ewsgi/src/wgi_request_from_table.e
2011-10-05 14:47:30 +02:00

1263 lines
33 KiB
Plaintext
Raw Blame History

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