Skip to content

Instantly share code, notes, and snippets.

@daurnimator
Created August 26, 2012 14:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daurnimator/3480374 to your computer and use it in GitHub Desktop.
Save daurnimator/3480374 to your computer and use it in GitHub Desktop.
Lpeg pattern to parse a HTTP Accept Header
do
local function helper ( allowed , want )
return ( not want.type or allowed.type == "*" or ( allowed.type == want.type and
( not want.subtype or allowed.subtype == "*" or allowed.subtype == want.subtype ) )
) and allowed.q ~= 0
end
function choose_type ( allowed_list , wanted_list )
-- Remove items from allowed list that have no match (retains client ordering)
local res = { }
local allowed_wanted_map = { }
for i , allowed in ipairs ( allowed_list ) do
for j , want in ipairs ( wanted_list ) do
if helper ( allowed , want ) then
res [ #res + 1 ] = allowed
allowed_wanted_map [ allowed ] = want
break
end
end
end
-- Swap values to the wanted list so the caller knows what to serve
for i , v in ipairs ( res ) do
res [ i ] = allowed_wanted_map [ v ]
end
return res
end
end
local accept_list = header_parsers.accept ( "text/*;q=0.3, text/html;q=0.7, text/html;level=1,\r\n text/html;level=2;q=0.4, */*;q=0.5" )
local content_type = choose_type ( accept_list , {
{ type = "text" ; subtype = "html" } ;
{ type = "application" ; subtype = "json" } ;
} )[1]
if not content_type then
return 404
else
content_type = content_type.type .. "/" .. content_type.subtype
end
ngx.header["Content-Type"] = content_type
if content_type == "application/json" then
.....
else
......
end
local parsers = { }
local lpeg = require "lpeg"
local P = lpeg.P
local R = lpeg.R
local S = lpeg.S
local C = lpeg.C
local Cf = lpeg.Cf
local Cg = lpeg.Cg
local Cs = lpeg.Cs
local Ct = lpeg.Ct
local identity = function(...) return ... end
local CHAR = R("\0\127")
local CTL = R("\0\31")+P("\127")
local LWS = P("\r\n")^-1 * S(" \t")^1
local seperators = S("()<>@,;:\\\"/[]?={} \t")
local token = (CHAR-CTL-seperators)^1
local qdtext = P(1)-P('"')
local quoted_pair = Cs ( P("\\") * C(1) / identity )
local quoted_string = Cs ( P('"') * C ( ( qdtext + quoted_pair )^0 ) * P'"' / identity )
local attribute = token
local value = token + quoted_string
local parameter = C(attribute) *LWS^0* P("=") *LWS^0* C(value)
local type = C(token)
local subtype = C(P("*")+token)
local media_range = ( Cg(C("*"),"type")*P("/")*Cg(C("*"),"subtype") + Cg(type,"type")*LWS^0*P("/")*LWS^0*Cg(subtype,"subtype") ) *
-- Collect parameters in a table
Cg( Cf ( Ct(true)*( P(";") *LWS^0* (
-#(P("q")*LWS^0* P("=")) -- The first "q" parameter (if any) separates the media-range parameter(s) from the accept-params
) * Cg(parameter) )^1 , rawset ) , "parameters" )^-1
local qvalue = ( P("0")*(P(".")*R("09")^-3)^-1 + P("1")*(P(".")*P("0")^-3)^-1 ) / tonumber
local accept_extention = P(";") *LWS^0* C(token) *LWS^0* ( P("=") *LWS^0* C ( token + quoted_string ) )^-1
local accept_params = P(";") *LWS^0* Cg ( P("q") *LWS^0* P("=") *LWS^0* qvalue , "q" ) *
-- Collect extensions in a table
Cg( Cf ( Ct(true)* LWS^0* (Cg(accept_extention)*LWS^0)^1 , rawset ) , "extensions" )^-1
local media_mt
local item = Ct ( media_range *LWS^0* accept_params^-1 ) / function ( m ) return setmetatable ( m , media_mt ) end
local accept = LWS^0* ( item * ( LWS^0* P(",") *LWS^0* item )^0 )^-1
local function count ( t )
local i = 0
for k , v in pairs ( t ) do
i = i + 1
end
return i
end
-- Higher specificity wins
local function item_compare (a,b)
local aq , bq = a.q or 1 , b.q or 1
if aq == bq then
if a.parameters then
if not b.parameters then
return true
end
local acount , bcount = count ( a ) , count ( b )
if acount ~= bcount then
return acount > bcount
end
elseif b.parameters then
return false
end
if a.subtype ~= "*" then
if b.subtype == "*" then
return true
end
-- type can only be "*" if subtype is
if a.type ~= "*" then
if b.type == "*" then
return true
end
elseif b.type ~= "*" then
return false
end
elseif b.subtype ~= "*" then
return false
end
-- Need to give a consistent comparison
return tostring(aq) > tostring(bq)
else
return aq > bq
end
end
media_mt = {
__tostring = function ( m )
return m.type .. "/" .. m.subtype
end ;
__gt = item_compare ;
__lt = function ( a , b ) return item_compare ( b , a ) end ;
}
parsers.accept = function ( h )
local r = { accept:match ( h ) }
table.sort ( r , item_compare )
return r
end
return parsers
local accept = require "header_parsers".accept
for i , test in ipairs {
"audio/*; q=0.2, audio/basic" ;
"text/plain; q=0.5, text/html,\r\n text/x-dvi; q=0.8, text/x-c" ;
"text/*, text/html, text/html;level=1, */*" ;
"text/*;q=0.3, text/html;q=0.7, text/html;level=1,\r\n text/html;level=2;q=0.4, */*;q=0.5" ;
"text/plain; stuff=asd; q=0.00; foo=bar" ;
} do
print("TESTING",test)
for j , v in ipairs { accept:match(test) } do
print(j,v)
for k,vv in pairs(v) do
if _G.type ( vv ) == "table" then
print("",k)
for kk,vvv in pairs(vv) do
print("","",kk,vvv)
end
else
print("",k,vv)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment