Added class HTTP_CONTENT_TYPE to help manipulation of Content-Type value

Now WSF_REQUEST return a HTTP_CONTENT_TYPE if available
Adapted WSF_MIME_HANDLER to use this new class
Added one manual autotest to test MIME handler
This commit is contained in:
Jocelyn Fiat
2012-03-23 16:40:13 +01:00
parent ac9cbb0bd2
commit 40c6aff423
14 changed files with 741 additions and 72 deletions

View File

@@ -0,0 +1,318 @@
note
description: "[
CGI Meta variable define the CONTENT_TYPE entity
This class is to represent it as an object
the Internet Media Type [9] of the attached entity if the type
was provided via a "Content-type" field in the wgi_request header,
or if the server can determine it in the absence of a supplied
"Content-type" field. The syntax is the same as for the HTTP
"Content-Type" header field.
CONTENT_TYPE = "" | media-type
media-type = type "/" subtype *( ";" parameter)
type = token
subtype = token
parameter = attribute "=" value
attribute = token
value = token | quoted-string
The type, subtype, and parameter attribute names are not
case-sensitive. Parameter values MAY be case sensitive. Media
types and their use in HTTP are described in section 3.7 of
the HTTP/1.1 specification [8].
Example:
application/x-www-form-urlencoded
application/x-www-form-urlencoded; charset=UTF8
]"
date: "$Date$"
revision: "$Revision$"
class
HTTP_CONTENT_TYPE
inherit
DEBUG_OUTPUT
create
make,
make_from_content_type_header
convert
make_from_content_type_header ({READABLE_STRING_8, STRING_8, IMMUTABLE_STRING_8}),
string: {READABLE_STRING_8}
feature {NONE} -- Initialization
make (a_type, a_subtype: READABLE_STRING_8)
-- Create Current based on `a_type/a_subtype'
require
not a_type.is_empty
not a_subtype.is_empty
do
type := a_type
subtype := a_subtype
ensure
has_no_error: not has_error
end
make_from_content_type_header (s: READABLE_STRING_8)
-- Create Current from `s'
-- if `s' does not respect the expected syntax, has_error is True
local
t: STRING_8
i,n: INTEGER
p: INTEGER
pn,pv: STRING_8
do
-- Ignore starting space (should not be any)
from
i := 1
n := s.count
until
i > n or not s[i].is_space
loop
i := i + 1
end
if i < n then
-- Look for semi colon as parameter separation
p := s.index_of (';', i)
if p > 0 then
t := s.substring (i, p - 1)
-- Skip eventual space
i := p + 1
from
until
i > n or not s[i].is_space
loop
i := i + 1
end
if i < n then
p := s.index_of ('=', i)
if p > 0 then
pn := s.substring (i, p - 1)
pv := s.substring (p + 1, n)
pv.right_adjust
if pv.count > 0 and pv [1] = '%"' then
if pv [pv.count] = '%"' then
pv := pv.substring (2, pv.count - 1)
else
has_error := True
-- missing closing double quote.
end
end
if not has_error then
set_parameter (pn, pv)
end
else
-- expecting: attribute "=" value
has_error := True
end
end
else
t := s.substring (i, n)
end
-- Remove eventual trailing space
t.right_adjust
-- Extract type and subtype
p := t.index_of ('/', 1)
if p = 0 then
has_error := True
type := t
subtype := ""
else
subtype := t.substring (p + 1, t.count)
type := t
t.keep_head (p - 1)
end
else
has_error := True
type := ""
subtype := type
end
ensure
not has_error implies (create {HTTP_CONTENT_TYPE}.make_from_content_type_header (string)).same_string (string)
end
feature -- Access
has_error: BOOLEAN
-- Current has error?
--| Mainly in relation with `make_from_content_type_header'
type: READABLE_STRING_8
-- Main type
subtype: READABLE_STRING_8
-- Sub type
has_parameter: BOOLEAN
-- Has Current a parameter?
do
Result := parameter_information /= Void
end
parameter (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
-- Value for eventual parameter named `a_name'.
do
if has_parameter and then parameter_name.same_string (a_name) then
Result := parameter_value
end
end
parameter_name: READABLE_STRING_8
-- Parameter's name if any.
require
has_parameter: has_parameter
do
if attached parameter_information as l_info then
Result := l_info.name
else
Result := ""
check has_parameter: False end
end
end
parameter_value: READABLE_STRING_8
-- Parameter's value if any.
require
has_parameter: has_parameter
do
if attached parameter_information as l_info then
Result := l_info.value
else
Result := ""
check has_parameter: False end
end
end
feature -- Conversion
string: READABLE_STRING_8
-- String representation of type/subtype; attribute=value
local
res: like internal_string
do
res := internal_string
if res = Void then
create res.make_from_string (type_and_subtype_string)
if has_parameter then
res.append_character (';')
res.append_character (' ')
res.append (parameter_name)
res.append_character ('=')
res.append_character ('%"')
res.append (parameter_value)
res.append_character ('%"')
end
internal_string := res
end
Result := res
end
type_and_subtype_string: READABLE_STRING_8
-- String representation of type/subtype
local
res: like internal_type_and_subtype_string
s: like subtype
do
res := internal_type_and_subtype_string
if res = Void then
create res.make_from_string (type)
s := subtype
if not s.is_empty then
check has_error: has_error end
-- Just in case not is_valid, we keep in `type' the original string
res.append_character ('/')
res.append (s)
end
internal_type_and_subtype_string := res
end
Result := res
end
feature {NONE} -- Internal
internal_string: detachable STRING_8
internal_type_and_subtype_string: detachable STRING_8
feature -- Status report
same_as (other: HTTP_CONTENT_TYPE): BOOLEAN
do
Result := other.type.same_string (other.type) and then
other.subtype.same_string (other.subtype)
if Result then
Result := has_parameter = other.has_parameter
if has_parameter then
Result := parameter_name.same_string (other.parameter_name) and then
parameter_value.same_string (other.parameter_value)
end
end
end
same_type_and_subtype (s: READABLE_STRING_8): BOOLEAN
do
Result := type_and_subtype_string.same_string (s)
end
same_string (s: READABLE_STRING_8): BOOLEAN
do
Result := string.same_string (s)
end
feature -- Element change
set_parameter (a_name: like parameter_name; a_value: like parameter_value)
-- Set parameter for `a_name' to `a_value'
do
parameter_information := [a_name, a_value]
internal_string := Void
end
remove_parameter
-- Remove parameter
do
parameter_information := Void
internal_string := Void
end
feature {NONE} -- Implementation
parameter_information: detachable TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
if type /= Void and subtype /= Void then
Result := type + "/" + subtype
if attached parameter_information as p_info then
Result.append ("; " + p_info.name + "=" + "%"" + p_info.value + "%"")
end
else
Result := ""
end
end
invariant
has_parameter implies parameter_name /= Void and parameter_value /= Void
type_and_subtype_not_empty: not has_error implies not type.is_empty and not subtype.is_empty
note
copyright: "2011-2012, Jocelyn Fiat, 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

@@ -28,7 +28,8 @@ create
make,
make_with_count,
make_from_array,
make_from_header
make_from_header,
make_from_raw_header_data
convert
make_from_array ({ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]}),
@@ -66,6 +67,27 @@ feature {NONE} -- Initialization
append_header_object (a_header)
end
make_from_raw_header_data (h: READABLE_STRING_8)
-- Create Current from raw header data
local
line : detachable STRING
lines: LIST [READABLE_STRING_8]
do
lines := h.split ('%N')
make_with_count (lines.count)
across
lines as c
loop
line := c.item
if not line.is_empty then
if line[line.count] = '%R' then
line.remove_tail (1)
end
add_header (line)
end
end
end
feature -- Recycle
recycle
@@ -100,6 +122,21 @@ feature -- Access
result_has_single_ending_cr_lf: Result.count >= 4 implies not Result.substring (Result.count - 3, Result.count).same_string ("%R%N%R%N")
end
to_name_value_iterable: ITERABLE [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
local
res: ARRAYED_LIST [TUPLE [READABLE_STRING_8, READABLE_STRING_8]]
do
create res.make (headers.count)
across
headers as c
loop
if attached header_name_value (c.item) as tu then
res.extend (tu)
end
end
Result := res
end
feature -- Header: filling
append_array (a_headers: ARRAY [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]])
@@ -141,7 +178,7 @@ feature -- Header change: general
require
h_not_empty: not h.is_empty
do
force_header_by_name (header_name (h), h)
force_header_by_name (header_name_colon (h), h)
end
add_header_key_value (k,v: READABLE_STRING_8)
@@ -529,7 +566,7 @@ feature {NONE} -- Implementation: Header
force_header_by_name (n: detachable READABLE_STRING_8; h: READABLE_STRING_8)
-- Add header `h' or replace existing header of same header name `n'
require
h_has_name_n: (n /= Void and attached header_name (h) as hn) implies n.same_string (hn)
h_has_name_n: (n /= Void and attached header_name_colon (h) as hn) implies n.same_string (hn)
local
l_headers: like headers
do
@@ -552,7 +589,7 @@ feature {NONE} -- Implementation: Header
end
end
header_name (h: READABLE_STRING_8): detachable READABLE_STRING_8
header_name_colon (h: READABLE_STRING_8): detachable STRING_8
-- If any, header's name with colon
--| ex: for "Foo-bar: something", this will return "Foo-bar:"
local
@@ -581,6 +618,36 @@ feature {NONE} -- Implementation: Header
Result := s
end
header_name_value (h: READABLE_STRING_8): detachable TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]
-- If any, header's [name,value]
--| ex: for "Foo-bar: something", this will return ["Foo-bar", "something"]
local
s: detachable STRING_8
i,n: INTEGER
c: CHARACTER
do
from
i := 1
n := h.count
create s.make (10)
until
i > n or c = ':' or s = Void
loop
c := h[i]
inspect c
when ':' then
when '-', 'a' .. 'z', 'A' .. 'Z' then
s.extend (c)
else
s := Void
end
i := i + 1
end
if s /= Void then
Result := [s, h.substring (i, n)]
end
end
feature {NONE} -- Implementation
append_line_to (s: READABLE_STRING_8; h: STRING_8)
@@ -595,7 +662,7 @@ feature {NONE} -- Implementation
h.append_character ('%N')
end
date_to_rfc1123_http_date_format (dt: DATE_TIME): READABLE_STRING_8
date_to_rfc1123_http_date_format (dt: DATE_TIME): STRING_8
-- String representation of `dt' using the RFC 1123
do
Result := dt.formatted_out ("ddd,[0]dd mmm yyyy [0]hh:[0]mi:[0]ss.ff2") + " GMT"