Integrated new system to handle form_parameter, input_data in relation with MIME handling

This is not yet clear how to let the user precise its own MIME handler
but it is in progress
This commit is contained in:
Jocelyn Fiat
2011-12-01 19:12:26 +01:00
parent de38f46d1d
commit e70d67aed5
5 changed files with 592 additions and 218 deletions

View File

@@ -0,0 +1,143 @@
note
description: "Summary description for {WSF_MIME_HANDLER_HELPER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_MIME_HANDLER_HELPER
feature {NONE} -- Implementation
read_input_data (a_input: WGI_INPUT_STREAM; nb: NATURAL_64): READABLE_STRING_8
-- All data from input form
local
nb32: INTEGER
n64: NATURAL_64
n: INTEGER
t: STRING
s: STRING_8
do
from
n64 := nb
nb32 := n64.to_integer_32
create s.make (nb32)
Result := s
n := nb32
if n > 1_024 then
n := 1_024
end
until
n64 <= 0
loop
a_input.read_string (n)
t := a_input.last_string
s.append_string (t)
if t.count < n then
n64 := 0
else
n64 := n64 - t.count.as_natural_64
end
end
end
add_value_to_table (a_name: READABLE_STRING_8; a_value: READABLE_STRING_8; a_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_32])
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
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_encoder.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)
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
end

View File

@@ -0,0 +1,64 @@
note
description: "Summary description for {WSF_APPLICATION_X_WWW_FORM_URLENCODED_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_APPLICATION_X_WWW_FORM_URLENCODED_HANDLER
inherit
WSF_MIME_HANDLER
WSF_MIME_HANDLER_HELPER
feature -- Status report
valid_content_type (a_content_type: READABLE_STRING_8): BOOLEAN
do
Result := a_content_type.same_string ({HTTP_MIME_TYPES}.application_x_www_form_encoded)
end
feature -- Execution
handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST;
a_vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8])
local
l_content: READABLE_STRING_8
n, p, i, j: INTEGER
s: READABLE_STRING_8
l_name, l_value: READABLE_STRING_8
do
l_content := read_input_data (req.input, a_content_length)
if a_raw_data /= Void then
a_raw_data.replace (l_content)
end
n := l_content.count
check n_same_as_content_length: n = a_content_length.to_integer_32 end --| FIXME: truncated value
if n > 0 then
from
p := 1
until
p = 0
loop
i := l_content.index_of ('&', p)
if i = 0 then
s := l_content.substring (p, n)
p := 0
else
s := l_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)
add_value_to_table (l_name, l_value, a_vars)
end
end
end
end
end
end

View File

@@ -0,0 +1,27 @@
note
description: "Summary description for {WSF_MIME_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
WSF_MIME_HANDLER
feature -- Status report
valid_content_type (a_content_type: READABLE_STRING_8): BOOLEAN
deferred
end
feature -- Execution
handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST;
a_vars: TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8])
-- Handle MIME content from request `req', eventually fill the `a_vars' (not yet available from `req')
-- and if `a_raw_data' is attached, store any read data inside `a_raw_data'
require
valid_content_type: valid_content_type (a_content_type)
deferred
end
end

View File

@@ -0,0 +1,248 @@
note
description: "Summary description for {WSF_MULTIPART_FORM_DATA_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
WSF_MULTIPART_FORM_DATA_HANDLER
inherit
WSF_MIME_HANDLER
WSF_MIME_HANDLER_HELPER
create
make
feature {NONE} -- Initialization
make (a_err_handler: like error_handler)
-- Instantiate Current
do
error_handler := a_err_handler
end
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 -- Status report
valid_content_type (a_content_type: READABLE_STRING_8): BOOLEAN
do
Result := a_content_type.starts_with ({HTTP_MIME_TYPES}.multipart_form_data)
end
feature -- Execution
handle (a_content_type: READABLE_STRING_8; a_content_length: NATURAL_64; req: WSF_REQUEST;
a_vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32]; a_raw_data: detachable CELL [detachable STRING_8])
local
s: READABLE_STRING_8
do
s := read_input_data (req.input, a_content_length)
if a_raw_data /= Void then
a_raw_data.replace (s)
end
--| FIXME: optimization ... fetch the input data progressively, otherwise we might run out of memory ...
analyze_multipart_form (req, a_content_type, s, a_vars)
end
feature {NONE} -- Implementation: Form analyzer
analyze_multipart_form (req: WSF_REQUEST; t: READABLE_STRING_8; s: READABLE_STRING_8; vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32])
-- 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 (req, 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 (req: WSF_REQUEST; s: STRING; vars: HASH_TABLE [WSF_VALUE, READABLE_STRING_32])
-- 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_8
l_header: detachable STRING_8
l_content: detachable STRING_8
l_line: detachable STRING_8
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)
req.save_uploaded_file (l_content, l_up_file_info)
req.uploaded_files.force (l_up_file_info, l_name)
else
add_value_to_table (l_name, l_content, vars)
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
default_content_type: STRING = "text/plain"
-- Default content type
end

View File

@@ -53,6 +53,8 @@ feature {NONE} -- Initialization
local
s8: detachable READABLE_STRING_8
do
init_mime_handlers
--| Content-Length
if attached content_length as s and then s.is_natural_64 then
content_length_value := s.to_natural_64
@@ -1017,40 +1019,112 @@ feature -- Form fields and related
--| error: if /= 0 , there was an error : TODO ...
--| size: size of the file given by the http request
feature -- Access: MIME handler
has_mime_handler (a_content_type: READABLE_STRING_8): BOOLEAN
-- Has a MIME handler registered for `a_content_type'?
do
if attached mime_handlers as hdls then
from
hdls.start
until
hdls.after or Result
loop
Result := hdls.item_for_iteration.valid_content_type (a_content_type)
hdls.forth
end
end
end
register_mime_handler (a_handler: WSF_MIME_HANDLER)
-- Register `a_handler' for `a_content_type'
local
hdls: like mime_handlers
do
hdls := mime_handlers
if hdls = Void then
create hdls.make (3)
hdls.compare_objects
mime_handlers := hdls
end
hdls.force (a_handler)
end
mime_handler (a_content_type: READABLE_STRING_8): detachable WSF_MIME_HANDLER
-- Mime handler associated with `a_content_type'
do
if attached mime_handlers as hdls then
from
hdls.start
until
hdls.after or Result /= Void
loop
Result := hdls.item_for_iteration
if not Result.valid_content_type (a_content_type) then
Result := Void
end
hdls.forth
end
end
ensure
has_mime_handler_implies_attached: has_mime_handler (a_content_type) implies Result /= Void
end
feature {NONE} -- Implementation: MIME handler
init_mime_handlers
do
register_mime_handler (create {WSF_MULTIPART_FORM_DATA_HANDLER}.make (error_handler))
register_mime_handler (create {WSF_APPLICATION_X_WWW_FORM_URLENCODED_HANDLER})
end
mime_handlers: detachable ARRAYED_LIST [WSF_MIME_HANDLER]
-- Table of mime handles
feature {NONE} -- Form fields and related
form_parameters_table: HASH_TABLE [WSF_VALUE, READABLE_STRING_32]
-- Variables sent by POST request
local
vars: like internal_form_data_parameters_table
s: READABLE_STRING_8
l_raw_data_cell: detachable CELL [detachable STRING_8]
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_MIME_TYPES}.multipart_form_data)
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)
analyze_multipart_form (l_type, s, vars)
else
s := form_input_data (n)
vars := urlencoded_parameters (s)
end
if raw_post_data_recorded then
set_meta_string_variable ("RAW_POST_DATA", s)
end
else
if n = 0 then
create vars.make (0)
vars.compare_objects
else
if raw_post_data_recorded then
create l_raw_data_cell.put (Void)
end
create vars.make (5)
vars.compare_objects
l_type := content_type
if l_type /= Void and then attached mime_handler (l_type) as hdl then
hdl.handle (l_type, n, Current, vars, l_raw_data_cell)
end
if l_raw_data_cell /= Void and then attached l_raw_data_cell.item as l_raw_data then
set_meta_string_variable ("RAW_POST_DATA", l_raw_data)
end
-- if
-- l_type /= Void and then
-- l_type.starts_with ({HTTP_MIME_TYPES}.multipart_form_data)
-- 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)
-- analyze_multipart_form (l_type, s, vars)
-- else
-- s := form_input_data (n)
-- vars := urlencoded_parameters (s)
-- end
end
internal_form_data_parameters_table := vars
end
@@ -1130,7 +1204,8 @@ feature -- URL Utility
end
internal_url_base := l_base_url
end
Result := l_base_url + a_path
create Result.make_from_string (l_base_url)
Result.append (a_path)
end
feature {NONE} -- Implementation: URL Utility
@@ -1152,7 +1227,7 @@ feature -- Element change
error_handler := ehdl
end
feature {NONE} -- Temporary File handling
feature {WSF_MIME_HANDLER} -- Temporary File handling
delete_uploaded_file (uf: WGI_UPLOADED_FILE_DATA)
-- Delete file `a_filename'
@@ -1312,225 +1387,42 @@ feature {NONE} -- Temporary File handling
end
end
feature {NONE} -- Implementation: Form analyzer
analyze_multipart_form (t: STRING; s: STRING; vars: like form_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_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_8
l_header: detachable STRING_8
l_content: detachable STRING_8
l_line: detachable STRING_8
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
add_value_to_table (l_name, l_content, vars_post)
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
feature {WSF_MIME_HANDLER} -- Input data access
form_input_data (nb: NATURAL_64): READABLE_STRING_8
-- data from input form
-- All data from input form
local
nb32: INTEGER
n64: NATURAL_64
n: INTEGER
t: STRING
s: STRING_8
do
from
nb32 := nb.to_integer_32
n := nb32
create s.make (n)
n64 := nb
nb32 := n64.to_integer_32
create s.make (nb32)
Result := s
n := nb32
if n > 1_024 then
n := 1_024
end
until
n <= 0
n64 <= 0
loop
input.read_string (n)
t := input.last_string
s.append_string (t)
if t.count < n then
n := 0
n64 := 0
else
n64 := n64 - t.count.as_natural_64
end
n := nb32 - t.count
end
end
feature {NONE} -- Internal value
internal_query_parameters_table: detachable like query_parameters_table
-- cached value for `query_parameters'