Initial import CONNEG library, support server side
content negotiation.
This commit is contained in:
1
library/protocol/CONNEG/.gitignore
vendored
Normal file
1
library/protocol/CONNEG/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
EIFGENs/
|
||||
5
library/protocol/CONNEG/README.md
Normal file
5
library/protocol/CONNEG/README.md
Normal 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
|
||||
4
library/protocol/CONNEG/library/.gitignore
vendored
Normal file
4
library/protocol/CONNEG/library/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*~
|
||||
EIFGEN* # ignore all files in the EIFGENs/ directory
|
||||
|
||||
|
||||
282
library/protocol/CONNEG/library/common_accept_header_parser.e
Normal file
282
library/protocol/CONNEG/library/common_accept_header_parser.e
Normal 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
|
||||
119
library/protocol/CONNEG/library/common_results.e
Normal file
119
library/protocol/CONNEG/library/common_results.e
Normal 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
|
||||
18
library/protocol/CONNEG/library/conneg-safe.ecf
Normal file
18
library/protocol/CONNEG/library/conneg-safe.ecf
Normal 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>
|
||||
19
library/protocol/CONNEG/library/conneg.ecf
Normal file
19
library/protocol/CONNEG/library/conneg.ecf
Normal 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>
|
||||
81
library/protocol/CONNEG/library/fitness_and_quality.e
Normal file
81
library/protocol/CONNEG/library/fitness_and_quality.e
Normal 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
|
||||
|
||||
352
library/protocol/CONNEG/library/language_parse.e
Normal file
352
library/protocol/CONNEG/library/language_parse.e
Normal 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
|
||||
143
library/protocol/CONNEG/library/language_results.e
Normal file
143
library/protocol/CONNEG/library/language_results.e
Normal 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
|
||||
4
library/protocol/CONNEG/library/license.lic
Normal file
4
library/protocol/CONNEG/library/license.lic
Normal 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)"
|
||||
|
||||
349
library/protocol/CONNEG/library/mime_parse.e
Normal file
349
library/protocol/CONNEG/library/mime_parse.e
Normal 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
|
||||
144
library/protocol/CONNEG/library/parse_results.e
Normal file
144
library/protocol/CONNEG/library/parse_results.e
Normal 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
|
||||
30
library/protocol/CONNEG/library/shared_conneg.e
Normal file
30
library/protocol/CONNEG/library/shared_conneg.e
Normal 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
|
||||
76
library/protocol/CONNEG/library/variants.e
Normal file
76
library/protocol/CONNEG/library/variants.e
Normal 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
|
||||
|
||||
|
||||
79
library/protocol/CONNEG/run_test.rb
Normal file
79
library/protocol/CONNEG/run_test.rb
Normal 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)
|
||||
|
||||
4
library/protocol/CONNEG/test/.gitignore
vendored
Normal file
4
library/protocol/CONNEG/test/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*~
|
||||
EIFGEN* # ignore all files in the EIFGENs/ directory
|
||||
|
||||
|
||||
87
library/protocol/CONNEG/test/application.e
Normal file
87
library/protocol/CONNEG/test/application.e
Normal 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
|
||||
@@ -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
|
||||
117
library/protocol/CONNEG/test/language_parser_test.e
Normal file
117
library/protocol/CONNEG/test/language_parser_test.e
Normal 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
|
||||
|
||||
|
||||
129
library/protocol/CONNEG/test/mime_parser_test.e
Normal file
129
library/protocol/CONNEG/test/mime_parser_test.e
Normal 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
|
||||
|
||||
|
||||
20
library/protocol/CONNEG/test/test-safe.ecf
Normal file
20
library/protocol/CONNEG/test/test-safe.ecf
Normal 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>
|
||||
21
library/protocol/CONNEG/test/test.ecf
Normal file
21
library/protocol/CONNEG/test/test.ecf
Normal 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>
|
||||
Reference in New Issue
Block a user