Initial import CONNEG library, support server side

content negotiation.
This commit is contained in:
jvelilla
2011-11-17 12:54:35 +01:00
committed by Jocelyn Fiat
parent 6ec37c90a2
commit b58e4e19e1
23 changed files with 2141 additions and 0 deletions

1
library/protocol/CONNEG/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
EIFGENs/

View File

@@ -0,0 +1,5 @@
CONNEG is a library that provides utilities to select the best repesentation of a resource for a client
where there are multiple representations available.
Using this labrary you can retrieve the best Variant for media type, language preference, enconding and compression.
The library is based on eMIME Eiffel MIME library based on Joe Gregorio code

View File

@@ -0,0 +1,4 @@
*~
EIFGEN* # ignore all files in the EIFGENs/ directory

View File

@@ -0,0 +1,282 @@
note
description: "COMMON_ACCEPT_HEADER_PARSER, this class allows to parse Accept-Charset and Accept-Encoding headers"
author: ""
date: "$Date$"
revision: "$Revision$"
description : "[
Charset Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
Encoding Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
]"
class
COMMON_ACCEPT_HEADER_PARSER
feature -- Parser
parse_common (header: STRING): COMMON_RESULTS
-- Parses `header' charset/encoding into its component parts.
-- For example, the charset 'iso-8889-5' would get parsed
-- into:
-- ('iso-8889-5', {'q':'1.0'})
local
l_parts: LIST [STRING]
sub_parts: LIST [STRING]
p: STRING
i: INTEGER
l_header: STRING
do
create Result.make
l_parts := header.split (';')
if l_parts.count = 1 then
Result.put ("1.0", "q")
else
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
end
l_header := trim (l_parts[1])
Result.set_field (trim (l_header))
end
fitness_and_quality_parsed (a_field: STRING; parsed_charsets: LIST [COMMON_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given charset/encoding against a list of charsets/encodings
-- that have already been parsed by parse_common. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed().
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: COMMON_RESULTS
range: COMMON_RESULTS
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_common(a_field)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if attached target.field as l_target_field
then
from
parsed_charsets.start
until
parsed_charsets.after
loop
range := parsed_charsets.item_for_iteration
if attached range.field as l_range_common then
if l_target_field.same_string (l_range_common) or l_target_field.same_string ("*") or l_range_common.same_string ("*") then
if l_range_common.same_string (l_target_field) then
l_fitness := 100
else
l_fitness := 0
end
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
end
parsed_charsets.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_field: STRING; parsed_common: LIST [COMMON_RESULTS]): REAL_64
-- Find the best match for a given charset/encoding against a list of charsets/encodings that
-- have already been parsed by parse_charsets(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality()
do
Result := fitness_and_quality_parsed (a_field, parsed_common).quality
end
quality (a_field: STRING; commons: STRING): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons : LIST [STRING]
res : ARRAYED_LIST [COMMON_RESULTS]
p_res : COMMON_RESULTS
do
l_commons := commons.split (',')
from
create res.make (10);
l_commons.start
until
l_commons.after
loop
p_res := parse_common (l_commons.item_for_iteration)
res.put_left (p_res)
l_commons.forth
end
Result := quality_parsed (a_field, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [COMMON_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: COMMON_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
do
l_res := header.split (',')
create {ARRAYED_LIST [COMMON_RESULTS]} l_header_results.make (l_res.count)
from
l_res.start
until
l_res.after
loop
p_res := parse_common (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
--| TODO extract method
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
if attached l_header_results.item.field as l_field then
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (l_field) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
else
check has_field: False end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature -- Util
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,119 @@
note
description: "Summary description for {COMMON_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMMON_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
end
feature -- Access
field: detachable STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_field (a_field: STRING)
-- Set type with `a_charset'
do
field := a_field
ensure
field_assigned: field ~ field
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached field as t then
Result.append_string ("'" + t + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-7-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-7-0 http://www.eiffel.com/developers/xml/configuration-1-7-0.xsd" name="conneg" uuid="EB2019A3-8E12-43AF-B086-BF9C399507B7" readonly="false" library_target="conneg">
<target name="conneg">
<root all_classes="true"/>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard">
<assertions precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<cluster name="conneg" location=".\" recursive="true">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-7-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-7-0 http://www.eiffel.com/developers/xml/configuration-1-7-0.xsd" name="conneg" uuid="D15DC4C4-D74F-4E4B-B4B1-2B03A35A284D" readonly="false" library_target="conneg">
<target name="emime">
<root all_classes="true"/>
<option warning="true" full_class_checking="true">
<assertions precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<cluster name="conneg" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
<exclude>/.git$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,81 @@
note
description: "Summary description for {FITNESS_AND_QUALITY}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
FITNESS_AND_QUALITY
inherit
COMPARABLE
DEBUG_OUTPUT
undefine
is_equal
end
create
make
feature -- Initialization
make (a_fitness: INTEGER; a_quality: REAL_64)
do
fitness := a_fitness
quality := a_quality
create mime_type.make_empty
ensure
fitness_assigned : fitness = a_fitness
quality_assigned : quality = a_quality
end
feature -- Access
fitness: INTEGER
quality: REAL_64
mime_type: STRING
-- optionally used
-- empty by default
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_from_string (mime_type)
Result.append (" (")
Result.append ("quality=" + quality.out)
Result.append (" ; fitness=" + fitness.out)
Result.append (" )")
end
feature -- Element Change
set_mime_type (a_mime_type: STRING)
-- set mime_type with `a_mime_type'
do
mime_type := a_mime_type
ensure
mime_type_assigned : mime_type.same_string (a_mime_type)
end
feature -- Comparision
is_less alias "<" (other: like Current): BOOLEAN
-- Is current object less than `other'?
do
if fitness = other.fitness then
Result := quality < other.quality
else
Result := fitness < other.fitness
end
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,352 @@
note
description: "Summary description for {LANGUAGE_PARSE}."
author: ""
date: "$Date$"
revision: "$Revision$"
description : "Language Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4"
class
LANGUAGE_PARSE
inherit
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: STRING): LANGUAGE_RESULTS
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*"
end
l_types := l_full_type.split ('-')
if l_types.count = 1 then
Result.set_type (trim (l_types[1]))
else
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
end
end
parse_media_range (a_range: STRING): LANGUAGE_RESULTS
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: LANGUAGE_RESULTS
range: LANGUAGE_RESULTS
keys: LIST [STRING]
param_matches: INTEGER
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if
attached target.type as l_target_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if (
attached range.sub_type as l_range_sub_type and then attached target.sub_type as l_target_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
) then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [LANGUAGE_RESULTS]
p_res : LANGUAGE_RESULTS
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.put_left (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [LANGUAGE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: LANGUAGE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
do
l_res := header.split (',')
create {ARRAYED_LIST [LANGUAGE_RESULTS]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,143 @@
note
description: "Summary description for {LANGUAGE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
create mime_type.make_from_string ("*")
end
feature -- Access
type: detachable STRING
sub_type: detachable STRING
mime_type: STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
mime_type := a_type + "-" + st
else
mime_type := a_type
end
ensure
type_assigned: type ~ a_type
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
mime_type := t + "-" + a_sub_type
else
mime_type := "*"
end
ensure
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached type as t then
Result.append_string ("'" + t + "',")
end
if attached sub_type as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,4 @@
${NOTE_KEYWORD}
copyright: "2011-${YEAR}, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -0,0 +1,349 @@
note
description: "Summary description for {MIME_PARSE}."
author: ""
date: "$Date$"
revision: "$Revision$"
description : "Accept Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"
class
MIME_PARSE
inherit
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: STRING): PARSE_RESULTS
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*/*"
end
l_types := l_full_type.split ('/')
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
end
parse_media_range (a_range: STRING): PARSE_RESULTS
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: PARSE_RESULTS
range: PARSE_RESULTS
keys: LIST [STRING]
param_matches: INTEGER
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if
attached target.type as l_target_type and
attached target.sub_type as l_target_sub_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
) and
(
attached range.sub_type as l_range_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [PARSE_RESULTS]
p_res : PARSE_RESULTS
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.put_left (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [PARSE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: PARSE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
do
l_res := header.split (',')
create {ARRAYED_LIST [PARSE_RESULTS]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,144 @@
note
description: "Summary description for {PARSE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
PARSE_RESULTS
inherit
ANY
redefine
out
end
DEBUG_OUTPUT
redefine
out
end
create
make
feature -- Initialization
make
do
create params.make (2)
create mime_type.make_from_string ("*/*")
end
feature -- Access
type: detachable STRING
sub_type: detachable STRING
mime_type: STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
do
Result := params.item (a_key)
end
keys: LIST [STRING]
-- arrays of currents keys
local
res: ARRAYED_LIST [STRING]
do
create res.make_from_array (params.current_keys)
Result := res
end
has_key (a_key: STRING): BOOLEAN
-- Is there an item in the table with key `a_key'?
do
Result := params.has_key (a_key)
end
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
mime_type := a_type + "/" + st
else
mime_type := a_type + "/*"
end
ensure
type_assigned: type ~ a_type
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
mime_type := t + "/" + a_sub_type
else
mime_type := "*/" + a_sub_type
end
ensure
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
do
if params.has_key (key) then
params.replace (new, key)
else
params.force (new, key)
end
ensure
has_key: params.has_key (key)
has_item: params.has_item (new)
end
feature -- Status Report
out: STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached type as t then
Result.append_string ("'" + t + "',")
end
if attached sub_type as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
params.after
loop
Result.append ("'" + params.key_for_iteration + "':'" + params.item_for_iteration + "',");
params.forth
end
Result.append ("})")
end
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := out
end
feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,30 @@
note
description: "Summary description for {SHARED_MIME}."
date: "$Date$"
revision: "$Revision$"
class
SHARED_CONNEG
feature
mime: MIME_PARSE
once
create Result
end
common: COMMON_ACCEPT_HEADER_PARSER
-- Charset and Encoding
once
create Result
end
language: LANGUAGE_PARSE
once
create Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,76 @@
note
description: "Summary description for {VARIANTS}. Utility class to support Server Side Content Negotiation "
author: ""
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
class
VARIANTS
inherit
SHARED_CONNEG
REFACTORING_HELPER
feature -- Media Type Negotiation
media_type_preference ( mime_types_supported : LIST[STRING]; header : STRING) : STRING
-- mime_types_supported represent media types supported by the server.
-- header represent the Accept header, ie, the client preferences.
-- Return which media type to use for representaion in a response, if the server support
-- one media type, or empty in other case.
-- Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
do
Result := mime.best_match (mime_types_supported, header)
end
feature -- Encoding Negotiation
charset_preference (server_charset_supported : LIST[STRING]; header: STRING) : STRING
-- server_charset_supported represent a list of charset supported by the server.
-- header represent the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server support
-- one Charset, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
do
Result := common.best_match (server_charset_supported, header)
end
feature -- Compression Negotiation
encoding_preference (server_encoding_supported : LIST[STRING]; header: STRING) : STRING
-- server_encoding_supported represent a list of encoding supported by the server.
-- header represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server support
-- one Encoding, or empty in other case.
-- Representation: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
do
Result := common.best_match (server_encoding_supported, header)
end
feature -- Language Negotiation
language_preference (server_language_supported : LIST[STRING]; header: STRING) : STRING
-- server_language_supported represent a list of languages supported by the server.
-- header represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server support
-- one Language, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
do
Result := language.best_match (server_language_supported, header)
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env ruby
# Niklaus Giger, 15.01.2011
# Small ruby-script run all tests using ec (the Eiffel compiler)
# we assumen that ec outputs everything in english!
# For the command line options look at
# http://docs.eiffel.com/book/eiffelstudio/eiffelstudio-command-line-options
# we use often the -batch open.
#
# TODO: Fix problems when compiling takes too long and/or there
# are ec process lingering around from a previous failed build
require 'tempfile'
require 'fileutils'
# Override system command.
# run command. if not successful, complain and exit with error
def system(cmd)
puts cmd
res = Kernel.system(cmd)
if !res
puts "Failed running: #{cmd}"
exit 2
end
end
def runTestForProject(where)
if !File.directory?(where)
puts "Directory #{where} does not exist"
exit 2
end
# create a temporary file with input for the
# interactive mode of ec
commands2run=<<EOF
T
E
q
EOF
file = Tempfile.new('commands2run')
file.puts commands2run
file.close
Dir.chdir(where)
# First we have to remove old compilation
FileUtils.rm_rf("EIFGENs")
# compile the library
cmd = "ec -config library/emime-safe.ecf -target emime -batch -c_compile"
res = system(cmd)
# compile the test
cmd = "ec -config test/test-safe.ecf -target test -batch -c_compile"
res = system(cmd)
logFile = "#{__FILE__}.log"
sleep 1
cmd = "ec -config test/test-safe.ecf -target test -batch -loop 1>#{logFile} 2>#{__FILE__}.auto_test_output <#{file.path}"
res = system(cmd)
m= nil
IO.readlines(logFile).each{
|line|
m = /(\d+) tests total \((\d+) executed, (\d+) failing, (\d+) unresolved/.match(line)
break if m
}
puts
if m[3].to_i == 0 and m[4].to_i == 0 then
puts "#{m[1]} tests completed successfully"
else
puts "Failures while running #{m[1]} failed. #{m[2]} executed #{m[3]} failures #{m[4]} unresolved"
exit 2
end
end
runTestForProject(Dir.pwd)

View File

@@ -0,0 +1,4 @@
*~
EIFGEN* # ignore all files in the EIFGENs/ directory

View File

@@ -0,0 +1,87 @@
note
description : "eMIME application root class"
date : "$Date$"
revision : "$Revision$"
class
APPLICATION
inherit
ARGUMENTS
create
make
feature {NONE} -- Initialization
make
local
mime_parse : MIME_PARSE
accept : STRING
charset_parse : COMMON_ACCEPT_HEADER_PARSER
language : LANGUAGE_PARSE
do
create mime_parse
-- parse_result := mime_parse.parse_mime_type ("application/xhtml;q=0.5")
-- print ("%N"+parse_result.out)
-- parse_result := mime_parse.parse_media_range ("application/xml;q=1")
-- print ("%N"+parse_result.out)
-- check
-- "('application', 'xml', {'q':'1',})" ~ mime_parse.parse_media_range ("application/xml;q=1").out
-- end
-- parse_result := mime_parse.parse_media_range ("application/xml")
-- print ("%N"+parse_result.out)
-- check
-- "('application', 'xml', {'q':'1',})" ~ mime_parse.parse_media_range ("application/xml;q=1").out
-- end
-- assertEquals("('application', 'xml', {'q':'1',})", MIMEParse
-- .parseMediaRange("application/xml").toString());
-- assertEquals("('application', 'xml', {'q':'1',})", MIMEParse
-- .parseMediaRange("application/xml;q=").toString());
-- assertEquals("('application', 'xml', {'q':'1',})", MIMEParse
-- .parseMediaRange("application/xml ; q=").toString());
-- assertEquals("('application', 'xml', {'b':'other','q':'1',})",
-- MIMEParse.parseMediaRange("application/xml ; q=1;b=other")
-- .toString());
-- assertEquals("('application', 'xml', {'b':'other','q':'1',})",
-- MIMEParse.parseMediaRange("application/xml ; q=2;b=other")
-- .toString());
-- // Java URLConnection class sends an Accept header that includes a
-- // single *
-- assertEquals("('*', '*', {'q':'.2',})", MIMEParse.parseMediaRange(
-- " *; q=.2").toString());
accept := "application/atom+xml;q=1.0,application/xml;q=0.6,text/html"
print ("%N"+mime_parse.quality ("text/html;q=1.0", accept).out)
print ("%N"+mime_parse.quality ("application/xml", accept).out)
print ("%N"+mime_parse.quality ("*/*;q=0.1", accept).out)
accept := "application/atom+xml"
print ("%N"+mime_parse.parse_mime_type (accept).out)
create charset_parse
accept := "iso-8859-5"
print ("%N" + charset_parse.parse_common (accept).out)
accept := "unicode-1-1;q=0.8"
print ("%N" + charset_parse.parse_common (accept).out)
accept:= "iso-8859-5, unicode-1-1;q=0.8"
print ("%N"+ charset_parse.quality ("iso-8859-5", accept).out)
print ("%N"+ charset_parse.quality ("unicode-1-1", accept).out)
print ("%N"+ charset_parse.quality ("iso-8859-1", accept).out)
create language
accept :="da, en-gb;q=0.8, en;q=0.7"
print (language.best_match (accept.split (','), "da"))
print (language.best_match (accept.split (','), "en-*"))
print ("%N"+language.parse_media_range ("da").out)
print ("%N"+language.parse_media_range ("en-gb;q=0.8").out)
print ("%N"+language.parse_media_range ("en;q=0.7").out)
print ("%N"+language.parse_media_range ("en-*").out)
end
end

View File

@@ -0,0 +1,57 @@
note
description: "Summary description for {COMMON_ACCEPT_HEADER_PARSER_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMMON_ACCEPT_HEADER_PARSER_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create parser
end
feature -- Test routines
test_parse_charsets
do
assert ("Expected ('iso-8859-5', {'q':'1.0',})", parser.parse_common("iso-8859-5").out.same_string("('iso-8859-5', {'q':'1.0',})") )
assert ("Expected ('unicode-1-1', {'q':'0.8',})", parser.parse_common("unicode-1-1;q=0.8").out.same_string("('unicode-1-1', {'q':'0.8',})") )
assert ("Expected ('*', {'q':'1.0',})", parser.parse_common("*").out.same_string("('*', {'q':'1.0',})") )
end
test_quality_example
local
accept : STRING
do
accept := "iso-8859-5, unicode-1-1;q=0.8";
assert ("Expected 1.0", 1.0 = parser.quality ("iso-8859-5", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("unicode-1-1", accept))
end
test_best_match
local
charset_supported : LIST [STRING]
l_charsets : STRING
do
l_charsets := "iso-8859-5, unicode-1-1;q=0.8"
charset_supported := l_charsets.split(',')
assert ("Expected iso-8859-5", parser.best_match (charset_supported, "*").same_string ("iso-8859-5"))
assert ("Expected unicode-1-1", parser.best_match (charset_supported, "unicode-1-1;q=1").same_string ("unicode-1-1"))
end
parser : COMMON_ACCEPT_HEADER_PARSER
end

View File

@@ -0,0 +1,117 @@
note
description: "Summary description for {LANGUAGE_PARSER_TEST}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_PARSER_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create parser
end
feature -- Test routines
test_parse_media_range
do
assert ("Expected ('da', {'q':'1.0',})", parser.parse_media_range ("da").out.same_string ("('da', {'q':'1.0',})"));
assert ("Expected ('en', 'gb', {'q':'0.8',})", parser.parse_media_range ("en-gb;q=0.8").out.same_string ("('en', 'gb', {'q':'0.8',})"));
assert ("Expected ('en', {'q':'0.7',})", parser.parse_media_range ("en;q=0.7").out.same_string ("('en', {'q':'0.7',})"));
assert ("Expected ('en', '*', {'q':'1.0',})", parser.parse_media_range ("en-*").out.same_string ("('en', '*', {'q':'1.0',})"));
end
test_RFC2616_example
local
accept : STRING
do
accept := "da, en-gb;q=0.8, en;q=0.7";
assert ("Expected 1.0", 1.0 = parser.quality ("da", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-gb", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en", accept))
assert ("Expected 0.8", 0.8 = parser.quality ("en-*", accept))
end
test_best_match
local
mime_types_supported : LIST [STRING]
l_types : STRING
do
l_types := "en-gb,en-us"
mime_types_supported := l_types.split(',')
assert ("Expected en-us", parser.best_match (mime_types_supported, "en-us").same_string ("en-us"))
assert ("Direct match with a q parameter", parser.best_match (mime_types_supported, "en-gb;q=1").same_string ("en-gb"))
assert ("Direct match second choice with a q parameter", parser.best_match (mime_types_supported, "en-us;q=1").same_string ("en-us"))
assert ("Direct match using a subtype wildcard", parser.best_match (mime_types_supported, "en-*;q=1").is_equal ("en-gb"))
assert ("Match using a type wildcard", parser.best_match (mime_types_supported, "*").same_string ("en-gb"))
l_types := "en-gb,es"
mime_types_supported := l_types.split(',')
assert ("Match using a type versus a lower weighted subtype", parser.best_match (mime_types_supported, "es-*;q=0.5,*;q=0.1").same_string ("es"))
assert ("Fail to match anything",parser.best_match (mime_types_supported, "fr; q=0.9").same_string (""))
l_types := "en-gb,en-us"
mime_types_supported := l_types.split(',')
assert ("Test 1 verify fitness ordering", parser.best_match (mime_types_supported, "en-gb,en-us,*").same_string ("en-gb"))
l_types := "es,en-gb;q=1.0,fr;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Match default es at first position", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr;q=0.6,es"
mime_types_supported := l_types.split(',')
assert ("Match default es at last position", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "en-gb;q=1.0,fr,es"
mime_types_supported := l_types.split(',')
assert ("Match first top quality and fitness", parser.best_match (mime_types_supported, "es;q=1.0,*;q=0.1,fr").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "es;q=1.0,*/*;q=0.1,en;q=0.9").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "es,*/*;q=0.1,en").same_string ("es"))
l_types := "fr;q=1.0,en,es"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "en,es,*/*;q=0.1").same_string ("en"))
l_types := "es,en;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "fr;q=1.0, en;q=0.6, es").same_string ("es"))
end
test_support_wildcard
local
mime_types_supported : LIST[STRING]
l_types : STRING
do
l_types := "en-*,fr"
mime_types_supported := l_types.split(',')
assert ("match using a type wildcard", parser.best_match (mime_types_supported, "en-gb").same_string ("en-*"))
end
parser : LANGUAGE_PARSE
end

View File

@@ -0,0 +1,129 @@
note
description: "[
Eiffel tests that can be executed by testing tool.
]"
author: "EiffelStudio test wizard"
date: "$Date$"
revision: "$Revision$"
testing: "type/manual"
class
MIME_PARSER_TEST
inherit
EQA_TEST_SET
redefine
on_prepare
end
feature {NONE} -- Events
on_prepare
-- Called after all initializations in `default_create'.
do
create parser
end
feature -- Test routines
test_parse_media_range
do
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=1").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml ; q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=1;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=2;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
-- Accept header that includes *
assert ("Expected ('*', '*', {'q':'.2',})", parser.parse_media_range(" *; q=.2").out.same_string("('*', '*', {'q':'.2',})"))
end
test_RFC2616_example
local
accept : STRING
do
accept := "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5";
assert ("Expected 1.0", 1.0 = parser.quality ("text/html;level=1", accept))
assert ("Expected 0.3", 0.3 = parser.quality ("text/plain", accept))
assert ("Expected 0.7", 0.7 = parser.quality ("text/html", accept))
assert ("Expected 0.5", 0.5 = parser.quality ("image/jpeg", accept))
assert ("Expected 0.4", 0.4 = parser.quality ("text/html;level=2", accept))
assert ("Expected 0.7", 0.7 = parser.quality ("text/html;level=3", accept))
end
test_best_match
local
mime_types_supported : LIST [STRING]
l_types : STRING
do
l_types := "application/xbel+xml,application/xml"
mime_types_supported := l_types.split(',')
assert ("Expected application/xbel+xml", parser.best_match (mime_types_supported, "application/xbel+xml").same_string ("application/xbel+xml"))
assert ("Direct match with a q parameter", parser.best_match (mime_types_supported, "application/xbel+xml;q=1").same_string ("application/xbel+xml"))
assert ("Direct match second choice with a q parameter", parser.best_match (mime_types_supported, "application/xml;q=1").same_string ("application/xml"))
assert ("Direct match using a subtype wildcard", parser.best_match (mime_types_supported, "application/*;q=1").is_equal ("application/xbel+xml"))
assert ("Match using a type wildcard", parser.best_match (mime_types_supported, "*/*").same_string ("application/xbel+xml"))
l_types := "application/xbel+xml,text/xml"
mime_types_supported := l_types.split(',')
assert ("Match using a type versus a lower weighted subtype", parser.best_match (mime_types_supported, "text/*;q=0.5,*/*;q=0.1").same_string ("text/xml"))
assert ("Fail to match anything",parser.best_match (mime_types_supported, "text/html,application/atom+xml; q=0.9").same_string (""))
l_types := "application/json,text/html"
mime_types_supported := l_types.split(',')
assert ("Common Ajax scenario", parser.best_match (mime_types_supported, "application/json,text/javascript, */*").same_string ("application/json"))
assert ("Common Ajax scenario,verify fitness ordering", parser.best_match (mime_types_supported, "application/json,text/javascript, */*").same_string ("application/json"))
l_types := "text/html,application/atom+xml;q=1.0,application/xml;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Match default text/html at first position", parser.best_match (mime_types_supported, "text/html;q=1.0,*/*;q=0.1,application/xml").same_string ("text/html"))
l_types := "application/atom+xml;q=1.0,application/xml;q=0.6,text/html"
mime_types_supported := l_types.split(',')
assert ("Match default text/html at last position", parser.best_match (mime_types_supported, "text/html;q=1.0,*/*;q=0.1,application/xml").same_string ("text/html"))
l_types := "application/atom+xml;q=1.0,application/xml,text/html"
mime_types_supported := l_types.split(',')
assert ("Match first top quality and fitness", parser.best_match (mime_types_supported, "text/html;q=1.0,*/*;q=0.1,application/xml").same_string ("text/html"))
l_types := "application/atom+xml;q=1.0,application/xml,text/html"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "text/html;q=1.0,*/*;q=0.1,application/xml;q=0.9").same_string ("text/html"))
l_types := "application/atom+xml;q=1.0,application/xml,text/html"
mime_types_supported := l_types.split(',')
assert ("Test 1", parser.best_match (mime_types_supported, "text/html,*/*;q=0.1,application/xml").same_string ("text/html"))
l_types := "application/atom+xml;q=1.0,application/xml,text/html"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "application/xml,text/html,*/*;q=0.1").same_string ("application/xml"))
l_types := "text/html,application/xml;q=0.6"
mime_types_supported := l_types.split(',')
assert ("Test 2", parser.best_match (mime_types_supported, "application/atom+xml;q=1.0, application/xml;q=0.6, text/html").same_string ("text/html"))
end
test_support_wildcard
local
mime_types_supported : LIST[STRING]
l_types : STRING
do
l_types := "image/*,application/xml"
mime_types_supported := l_types.split(',')
assert ("match using a type wildcard", parser.best_match (mime_types_supported, "image/png").same_string ("image/*"))
assert ("match using a wildcard for both requested and supported", parser.best_match (mime_types_supported, "image/*").same_string ("image/*"))
end
parser : MIME_PARSE
end

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-6-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-6-0 http://www.eiffel.com/developers/xml/configuration-1-6-0.xsd" name="test" uuid="EA141B17-6A21-4781-8B5F-E9939BAE968A">
<target name="test">
<root class="APPLICATION" feature="make"/>
<option warning="true">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="conneg" location="..\library\conneg-safe.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<cluster name="test" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
<exclude>/.git$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="test" uuid="EA141B17-6A21-4781-8B5F-E9939BAE968A">
<target name="test">
<root class="APPLICATION" feature="make"/>
<option warning="true">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
<library name="conneg" location="..\library\conneg.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing.ecf"/>
<cluster name="test" location=".\" recursive="true">
<file_rule>
<exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/CVS$</exclude>
<exclude>/.svn$</exclude>
</file_rule>
</cluster>
</target>
</system>