# HG changeset patch # User Matthew Wild # Date 1679584560 0 # Node ID 3c19b67a1f0f4726ae5dcd2b6ffd99a6a2a36c57 # Parent 7674fb1dcc4128b35c8290303026da5b294c0aae verse: Update bundled verse version diff -r 7674fb1dcc41 -r 3c19b67a1f0f verse.lua --- a/verse.lua Thu Mar 23 15:15:01 2023 +0000 +++ b/verse.lua Thu Mar 23 15:16:00 2023 +0000 @@ -15,11 +15,15 @@ end local e=require"mime"; a"encodings" +idna={}; stringprep={}; base64={encode=e.b64,decode=e.unb64}; +utf8={ +valid=(utf8 and utf8.len)and function(e)return not not utf8.len(e);end or function()return true;end; +}; return _M; end) -package.preload['util.hashes']=(function(...) +package.preload['util.sha1']=(function(...) local _ENV=_ENV; local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; @@ -31,45 +35,126 @@ _M=e; return e; end -local function t(t,e) -error("Hash method "..e.." not available",2); -end -local e=setmetatable({},{__index=t}); -local function t(e,o) -local e,a=pcall(require,e); -if e then o(a);end -end -t("bgcrypto.md5",function(t) -e.md5=t.digest; -e.hmac_md5=t.hmac.digest; -end); -t("bgcrypto.sha1",function(t) -e.sha1=t.digest; -e.hmac_sha1=t.hmac.digest; -e.scram_Hi_sha1=function(e,a,o)return t.pbkdf2(e,a,o,20);end; -end); -t("bgcrypto.sha256",function(t) -e.sha256=t.digest; -e.hmac_sha256=t.hmac.digest; -end); -t("bgcrypto.sha512",function(t) -e.sha512=t.digest; -e.hmac_sha512=t.hmac.digest; -end); -t("sha1",function(t) -e.sha1=function(e,a) -if a then -return t.sha1(e); -else -return(t.binary(e)); -end -end; -end); -return e; +local m=string.len +local o=string.char +local k=string.byte +local q=string.sub +local c=math.floor +local t=require"bit" +local j=t.bnot +local e=t.band +local y=t.bor +local n=t.bxor +local a=t.lshift +local i=t.rshift +local u,r,l,d,h +local function p(t,e) +return a(t,e)+i(t,32-e) +end +local function s(i) +local t,a +local t="" +for n=1,8 do +a=e(i,15) +if(a<10)then +t=o(a+48)..t +else +t=o(a+87)..t +end +i=c(i/16) +end +return t +end +local function g(t) +local i,a +local n="" +i=m(t)*8 +t=t..o(128) +a=56-e(m(t),63) +if(a<0)then +a=a+64 +end +for e=1,a do +t=t..o(0) +end +for t=1,8 do +n=o(e(i,255))..n +i=c(i/256) +end +return t..n +end +local function b(w) +local m,t,o,a,f,s,c,v +local i,i +local i={} +while(w~="")do +for e=0,15 do +i[e]=0 +for t=1,4 do +i[e]=i[e]*256+k(w,e*4+t) +end +end +for e=16,79 do +i[e]=p(n(n(i[e-3],i[e-8]),n(i[e-14],i[e-16])),1) +end +m=u +t=r +o=l +a=d +f=h +for h=0,79 do +if(h<20)then +s=y(e(t,o),e(j(t),a)) +c=1518500249 +elseif(h<40)then +s=n(n(t,o),a) +c=1859775393 +elseif(h<60)then +s=y(y(e(t,o),e(t,a)),e(o,a)) +c=2400959708 +else +s=n(n(t,o),a) +c=3395469782 +end +v=p(m,5)+s+f+c+i[h] +f=a +a=o +o=p(t,30) +t=m +m=v +end +u=e(u+m,4294967295) +r=e(r+t,4294967295) +l=e(l+o,4294967295) +d=e(d+a,4294967295) +h=e(h+f,4294967295) +w=q(w,65) +end +end +local function a(e,t) +e=g(e) +u=1732584193 +r=4023233417 +l=2562383102 +d=271733878 +h=3285377520 +b(e) +local e=s(u)..s(r)..s(l) +..s(d)..s(h); +if t then +return e; +else +return(e:gsub("..",function(e) +return string.char(tonumber(e,16)); +end)); +end +end +_G.sha1={sha1=a}; +return _G.sha1; end) package.preload['lib.adhoc']=(function(...) local _ENV=_ENV; -local function r(t,...) +local function d(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -79,21 +164,21 @@ _M=e; return e; end -local s,i=require"util.stanza",require"util.uuid"; -local e="http://jabber.org/protocol/commands"; +local s,l=require"util.stanza",require"util.id".short; +local r="http://jabber.org/protocol/commands"; local n={} local h={}; -local function o(i,o,t,a) -local e=s.stanza("command",{xmlns=e,node=i.node,status=o}); +local function i(o,e,t,a) +local e=s.stanza("command",{xmlns=r,node=o.node,status=e}); if t then e.attr.sessionid=t;end if a then e.attr.action=a;end return e; end -function h.new(a,i,e,t) -return{name=a,node=i,handler=e,cmdtag=o,permission=(t or"user")}; +function h.new(e,o,a,t) +return{name=e,node=o,handler=a,cmdtag=i,permission=(t or"user")}; end function h.handle_cmd(o,h,t) -local e=t.tags[1].attr.sessionid or i.generate(); +local e=t.tags[1].attr.sessionid or l(); local a={}; a.to=t.attr.to; a.from=t.attr.from; @@ -130,7 +215,7 @@ if(e=="prev")or(e=="next")or(e=="complete")then a:tag(e):up(); else -r:log("error",'Command "'..o.name.. +d:log("error",'Command "'..o.name.. '" at node "'..o.node..'" provided an invalid action "'..e..'"'); end end @@ -149,9 +234,23 @@ end return h; end) +package.preload['util.table']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +return{pack=function(...)return{n=select("#",...);...}end;create=function()return{}end} +end) package.preload['util.stanza']=(function(...) local _ENV=_ENV; -local function h(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -161,87 +260,142 @@ _M=e; return e; end -local t=table.insert; -local s=table.remove; -local p=table.concat; -local r=string.format; -local u=string.match; -local w=tostring; -local m=setmetatable; -local n=pairs; -local i=ipairs; -local o=type; -local y=string.gsub; -local l=string.sub; -local f=string.find; -local e=os; -local c=not e.getenv("WINDIR"); -local d,a; -if c then -local t,e=pcall(require,"util.termcolours"); -if t then -d,a=e.getstyle,e.getstring; -else -c=nil; -end -end -local v="urn:ietf:params:xml:ns:xmpp-stanzas"; -h"stanza" -stanza_mt={__type="stanza"}; -stanza_mt.__index=stanza_mt; -local e=stanza_mt; -function stanza(a,t) -local t={name=a,attr=t or{},tags={}}; -return m(t,e); -end -local h=stanza; +local a=error; +local o=table.insert; +local l=table.remove; +local m=table.concat; +local d=string.match; +local f=tostring; +local u=setmetatable; +local x=getmetatable; +local h=pairs; +local s=ipairs; +local t=type; +local j=string.gsub; +local y=string.sub; +local r=string.find; +local c=table.move or require"util.table".move; +local b=require"util.table".create; +local v=require"util.encodings".utf8.valid; +local q,p=pcall(require,"util.termcolours"); +local g="urn:ietf:params:xml:ns:xmpp-stanzas"; +local k={xmlns=g}; +local _ENV=nil; +local e={__name="stanza"}; +e.__index=e; +local function i(t,e) +return not r(t,e and"[^\1\9\10\13\20-\255]"or"[^\9\10\13\20-\255]"); +end +local function n(o,e) +if t(o)~="string"then +a("invalid "..e.." name: expected string, got "..t(o)); +elseif#o==0 then +a("invalid "..e.." name: empty string"); +elseif r(o,"[<>& '\"]")then +a("invalid "..e.." name: contains invalid characters"); +elseif not i(o,e=="attribute")then +a("invalid "..e.." name: contains control characters"); +elseif not v(o)then +a("invalid "..e.." name: contains invalid utf8"); +end +end +local function w(e,o) +if t(e)~="string"then +a("invalid "..o.." value: expected string, got "..t(e)); +elseif not i(e,false)then +a("invalid "..o.." value: contains control characters"); +elseif not v(e)then +a("invalid "..o.." value: contains invalid utf8"); +end +end +local function v(e) +if e~=nil then +if t(e)~="table"then +a("invalid attributes: expected table, got "..t(e)); +end +for t,e in h(e)do +n(t,"attribute"); +w(e,"attribute"); +end +end +end +local function i(t,a,o) +n(t,"tag"); +v(a); +local t={name=t,attr=a or{},namespaces=o,tags={}}; +return u(t,e); +end +local function n(t) +return x(t)==e; +end function e:query(e) return self:tag("query",{xmlns=e}); end function e:body(t,e) -return self:tag("body",e):text(t); -end -function e:tag(a,e) -local a=h(a,e); +return self:text_tag("body",t,e); +end +function e:text_tag(e,t,o,a) +return self:tag(e,o,a):text(t):up(); +end +function e:tag(t,e,a) +local t=i(t,e,a); local e=self.last_add; if not e then e={};self.last_add=e;end -(e[#e]or self):add_direct_child(a); -t(e,a); +(e[#e]or self):add_direct_child(t); +o(e,t); return self; end function e:text(t) +if t~=nil and t~=""then local e=self.last_add; (e and e[#e]or self):add_direct_child(t); +end return self; end function e:up() local e=self.last_add; -if e then s(e);end +if e then l(e);end return self; end +function e:at_top() +return self.last_add==nil or#self.last_add==0 +end function e:reset() self.last_add=nil; return self; end function e:add_direct_child(e) -if o(e)=="table"then -t(self.tags,e); -end -t(self,e); +if n(e)then +o(self.tags,e); +o(self,e); +else +w(e,"text"); +o(self,e); +end end function e:add_child(t) local e=self.last_add; (e and e[#e]or self):add_direct_child(t); return self; end -function e:get_child(t,a) -for o,e in i(self.tags)do -if(not t or e.name==t) -and((not a and self.attr.xmlns==e.attr.xmlns) -or e.attr.xmlns==a)then -return e; -end -end +function e:remove_children(a,e) +e=e or self.attr.xmlns; +return self:maptags(function(t) +if(not a or t.name==a)and t.attr.xmlns==e then +return nil; +end +return t; +end); +end +function e:get_child(a,t) +for o,e in s(self.tags)do +if(not a or e.name==a) +and((not t and self.attr.xmlns==e.attr.xmlns) +or e.attr.xmlns==t)then +return e; +end +end +return nil; end function e:get_child_text(e,t) local e=self:get_child(e,t); @@ -250,15 +404,32 @@ end return nil; end +function e:get_child_attr(e,a,t) +local e=self:get_child(e,a); +if e then +return e.attr[t]; +end +return nil; +end function e:child_with_name(t) -for a,e in i(self.tags)do +for a,e in s(self.tags)do if e.name==t then return e;end end +return nil; end function e:child_with_ns(t) -for a,e in i(self.tags)do +for a,e in s(self.tags)do if e.attr.xmlns==t then return e;end end +return nil; +end +function e:get_child_with_attr(e,i,t,o,a) +for e in self:childtags(e,i)do +if(a and a(e.attr[t])or e.attr[t])==o then +return e; +end +end +return nil; end function e:children() local e=0; @@ -267,42 +438,46 @@ return t[e]; end,self,e; end -function e:childtags(i,o) +function e:childtags(o,a) local e=self.tags; -local a,t=1,#e; +local i,t=1,#e; return function() -for t=a,t do +for t=i,t do local e=e[t]; -if(not i or e.name==i) -and((not o and self.attr.xmlns==e.attr.xmlns) -or e.attr.xmlns==o)then -a=t+1; +if(not o or e.name==o) +and((not a and self.attr.xmlns==e.attr.xmlns) +or e.attr.xmlns==a)then +i=t+1; return e; end end end; end -function e:maptags(i) +function e:maptags(h) local o,t=self.tags,1; -local n,a=#self,#o; +local n,i=#self,#o; +local s=n+1; local e=1; -while t<=a and a>0 do +while t<=i and i>0 do if self[e]==o[t]then -local i=i(self[e]); -if i==nil then -s(self,e); -s(o,t); +local a=h(self[e]); +if a==nil then +l(self,e); +l(o,t); n=n-1; -a=a-1; +i=i-1; e=e-1; t=t-1; else -self[e]=i; -o[t]=i; +self[e]=a; +o[t]=a; end t=t+1; end e=e+1; +if e>s then +a("Invalid stanza state! Please report this error."); +end end return self; end @@ -310,17 +485,17 @@ local e=1; local s=#a+1; repeat -local o,t,n; -local i=l(a,e,e); -if i=="@"then -return self.attr[l(a,e+1)]; -elseif i=="{"then -o,e=u(a,"^([^}]+)}()",e+1); -end -t,n,e=u(a,"^([^@/#]*)([/#]?)()",e); +local o,t,i; +local n=y(a,e,e); +if n=="@"then +return self.attr[y(a,e+1)]; +elseif n=="{"then +o,e=d(a,"^([^}]+)}()",e+1); +end +t,i,e=d(a,"^([^@/#]*)([/#]?)()",e); t=t~=""and t or nil; if e==s then -if n=="#"then +if i=="#"then return self:get_child_text(t,o); end return self:get_child(t,o); @@ -328,212 +503,630 @@ self=self:get_child(t,o); until not self end -local l -do -local e={["'"]="'",["\""]=""",["<"]="<",[">"]=">",["&"]="&"}; -function l(t)return(y(t,"['&<>\"]",e));end -_M.xml_escape=l; -end -local function y(o,e,h,a,r) +local function l(t,s) +local n={}; +for t,e in h(t.attr)do n[t]=e;end +local a,i=t.namespaces; +if a then +i={}; +for e,t in h(a)do i[e]=t;end +end +local o,a; +if s then +o={}; +a={name=t.name,attr=n,namespaces=i,tags=o}; +else +o=b(#t.tags,0); +a=b(#t,4); +a.name=t.name; +a.attr=n; +a.namespaces=i; +a.tags=o; +end +u(a,e); +if not s then +c(t,1,#t,1,a); +c(t.tags,1,#t.tags,1,o); +a:maptags(l); +end +return a; +end +local function u(e,o) +if not n(e)then +a("bad argument to clone: expected stanza, got "..t(e)); +end +return l(e,o); +end +local l={["'"]="'",["\""]=""",["<"]="<",[">"]=">",["&"]="&"}; +local function c(e)return(j(e,"['&<>\"]",l));end +local function l(a,e,s,t,u) local i=0; -local s=o.name -t(e,"<"..s); -for o,n in n(o.attr)do -if f(o,"\1",1,true)then -local s,o=u(o,"^([^\1]*)\1?(.*)$"); +local l=a.name +o(e,"<"..l); +for a,n in h(a.attr)do +if r(a,"\1",1,true)then +local s,a=d(a,"^([^\1]*)\1?(.*)$"); i=i+1; -t(e," xmlns:ns"..i.."='"..a(s).."' ".."ns"..i..":"..o.."='"..a(n).."'"); -elseif not(o=="xmlns"and n==r)then -t(e," "..o.."='"..a(n).."'"); -end -end -local i=#o; +o(e," xmlns:ns"..i.."='"..t(s).."' ".."ns"..i..":"..a.."='"..t(n).."'"); +elseif not(a=="xmlns"and n==u)then +o(e," "..a.."='"..t(n).."'"); +end +end +local i=#a; if i==0 then -t(e,"/>"); -else -t(e,">"); +o(e,"/>"); +else +o(e,">"); for i=1,i do -local i=o[i]; +local i=a[i]; if i.name then -h(i,e,h,a,o.attr.xmlns); -else -t(e,a(i)); -end -end -t(e,""); +s(i,e,s,t,a.attr.xmlns); +else +o(e,t(i)); +end +end +o(e,""); end end function e.__tostring(t) local e={}; -y(t,e,y,l,nil); -return p(e); -end -function e.top_tag(t) -local e=""; -if t.attr then -for t,a in n(t.attr)do if o(t)=="string"then e=e..r(" %s='%s'",t,l(w(a)));end end -end -return r("<%s%s>",t.name,e); +l(t,e,l,c,nil); +return m(e); +end +function e.top_tag(e) +local e=u(e,true); +return f(e):sub(1,-3)..">"; end function e.get_text(e) if#e.tags==0 then -return p(e); -end +return m(e); +end +return nil; end function e.get_error(e) -local o,a,t; +local i,t,a,o; local e=e:get_child("error"); if not e then -return nil,nil,nil; -end -o=e.attr.type; -for o,e in i(e.tags)do -if e.attr.xmlns==v then -if not t and e.name=="text"then -t=e:get_text(); -elseif not a then -a=e.name; -end -if a and t then +return nil,nil,nil,nil; +end +i=e.attr.type; +for i,e in s(e.tags)do +if e.attr.xmlns==g then +if not a and e.name=="text"then +a=e:get_text(); +elseif not t then +t=e.name; +end +else +o=e; +end +if t and a and o then break; end end -end -return o,a or"undefined-condition",t; -end -do -local e=0; -function new_id() -e=e+1; -return"lx"..e; -end -end -function preserialize(e) -local a={name=e.name,attr=e.attr}; -for i,e in i(e)do -if o(e)=="table"then -t(a,preserialize(e)); -else -t(a,e); -end -end -return a; -end -function deserialize(a) +return i,t or"undefined-condition",a,o; +end +function e.add_error(o,a,s,h,i) +local e; +if t(a)=="table"then +if t(a.extra)=="table"then +e=a.extra; +end +if t(a.context)=="table"and t(a.context.by)=="string"then i=a.context.by;end +a,s,h=a.type,a.condition,a.text; +end +if o.attr.from==i then +i=nil; +end +o:tag("error",{type=a,by=i}) +:tag(s,k); +if e and s=="gone"and t(e.uri)=="string"then +o:text(e.uri); +end +o:up(); +if h then o:text_tag("text",h,k);end +if e and n(e.tag)then +o:add_child(e.tag); +elseif e and e.namespace and e.condition then +o:tag(e.condition,{xmlns=e.namespace}):up(); +end +return o:up(); +end +local function l(a) +local i={name=a.name,attr=a.attr}; +for a,e in s(a)do +if t(e)=="table"then +o(i,l(e)); +else +o(i,e); +end +end +return i; +end +e.__freeze=l; +local function m(a) if a then -local s=a.attr; -for e=1,#s do s[e]=nil;end -local h={}; -for e in n(s)do -if f(e,"|",1,true)and not f(e,"\1",1,true)then -local t,a=u(e,"^([^|]+)|(.+)$"); -h[t.."\1"..a]=s[e]; -s[e]=nil; -end -end -for t,e in n(h)do -s[t]=e; -end -m(a,e); -for t,e in i(a)do -if o(e)=="table"then -deserialize(e); -end -end -if not a.tags then -local n={}; -for i,e in i(a)do -if o(e)=="table"then -t(n,e); -end -end -a.tags=n; -end -end -return a; -end -local function s(a) -local i,o={},{}; -for t,e in n(a.attr)do i[t]=e;end -local i={name=a.name,attr=i,tags=o}; -for e=1,#a do -local e=a[e]; -if e.name then -e=s(e); -t(o,e); -end -t(i,e); -end -return m(i,e); -end -clone=s; -function message(t,e) +local e=a.attr; +local o={}; +for e,a in h(e)do +if t(e)=="string"then +if r(e,"|",1,true)and not r(e,"\1",1,true)then +local e,t=d(e,"^([^|]+)|(.+)$"); +o[e.."\1"..t]=a; +else +o[e]=a; +end +end +end +local o=i(a.name,o); +for a,e in s(a)do +if t(e)=="table"then +o:add_direct_child(m(e)); +elseif t(e)=="string"then +o:add_direct_child(e); +end +end +return o; +end +end +local function y(e,t) +if not t then +return i("message",e); +else +return i("message",e):text_tag("body",t); +end +end +local function v(e) if not e then -return h("message",t); -else -return h("message",t):tag("body"):text(e):up(); -end -end -function iq(e) -if e and not e.id then e.id=new_id();end -return h("iq",e or{id=new_id()}); -end -function reply(e) -return h(e.name,e.attr and{to=e.attr.from,from=e.attr.to,id=e.attr.id,type=((e.name=="iq"and"result")or e.attr.type)}); -end -do -local t={xmlns=v}; -function error_reply(e,o,i,a) -local e=reply(e); +a("iq stanzas require id and type attributes"); +end +if not e.id then +a("iq stanzas require an id attribute"); +end +if not e.type then +a("iq stanzas require a type attribute"); +end +return i("iq",e); +end +local function r(e) +if not n(e)then +a("bad argument to reply: expected stanza, got "..t(e)); +end +return i(e.name, +{ +to=e.attr.from, +from=e.attr.to, +id=e.attr.id, +type=((e.name=="iq"and"result")or e.attr.type) +}); +end +local function w(e,s,h,i,o) +if not n(e)then +a("bad argument to error_reply: expected stanza, got "..t(e)); +elseif e.attr.type=="error"then +a("bad argument to error_reply: got stanza of type error which must not be replied to"); +end +local e=r(e); e.attr.type="error"; -e:tag("error",{type=o}) -:tag(i,t):up(); -if(a)then e:tag("text",t):text(a):up();end -return e; -end -end -function presence(e) -return h("presence",e); -end -if c then -local s=d("yellow"); -local h=d("red"); -local u=d("red"); -local t=d("magenta"); -local h=" "..a(s,"%s")..a(t,"=")..a(h,"'%s'"); -local s=a(t,"<")..a(u,"%s").."%s"..a(t,">"); -local d=s.."%s"..a(t,""); +e:add_error(s,h,i,o); +e.last_add={e[1]}; +return e; +end +local function d(e) +return i("presence",e); +end +local s; +if q then +local a,t=p.getstyle,p.getstring; +local n=a("1b3967"); +local r=a("13b5ea"); +local o=a("439639"); +local i=a("a0ce67"); +local h=a("d9541e"); +local a=a("e96d1f"); +local h=( +t(i,"%1").. +t(o,"%2").. +t(h,"%3").. +t(a,"%4").. +t(h,"%5") +); +local d=( +t(o,"%1").. +t(i,"%2").. +t(o,"%3") +); +function s(e) +return(e:gsub("(<[?/]?)([^ >/?]*)(.-)([?/]?>)([^<]*)",function(o,e,a,i,s) +return t(n,o)..t(r,e).. +a:gsub("([^=]+)(=)([\"'])(.-)([\"'])",h).. +t(n,i).. +s:gsub("(&#?)(%w+)(;)",d); +end,100)); +end function e.pretty_print(e) -local t=""; -for a,e in i(e)do -if o(e)=="string"then -t=t..l(e); -else -t=t..e:pretty_print(); -end -end -local a=""; -if e.attr then -for e,t in n(e.attr)do if o(e)=="string"then a=a..r(h,e,w(t));end end -end -return r(d,e.name,a,t,e.name); +return s(f(e)); end function e.pretty_top_tag(e) -local t=""; -if e.attr then -for e,a in n(e.attr)do if o(e)=="string"then t=t..r(h,e,w(a));end end -end -return r(s,e.name,t); +return s(e:top_tag()); end else e.pretty_print=e.__tostring; e.pretty_top_tag=e.top_tag; end -return _M; +function e.indent(i,o,a) +if#i==0 or(#i==1 and t(i[1])=="string")then +return i; +end +a=a or"\t"; +o=o or 1; +local e=u(i,true); +for i in i:children()do +if t(i)=="string"then +if i:find("%S")then +e:text("\n"..a:rep(o)); +e:text(i); +end +elseif n(i)then +e:text("\n"..a:rep(o)); +e:add_direct_child(i:indent(o+1,a)); +end +end +e:text("\n"..a:rep((o-1))); +return e; +end +return{ +stanza_mt=e; +stanza=i; +is_stanza=n; +preserialize=l; +deserialize=m; +clone=u; +message=y; +iq=v; +reply=r; +error_reply=w; +presence=d; +xml_escape=c; +pretty_print=s; +}; end) package.preload['util.timer']=(function(...) local _ENV=_ENV; -local function o(t,...) +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local a=require"util.indexedbheap"; +local t=require"util.logger".init("timer"); +local e=require"net.server"; +local d=require"util.time".now +local l=type; +local h=debug.traceback; +local i=tostring; +local c=require"util.xpcall".xpcall; +local f=math.max; +local m=pairs; +if e.timer then +return e.timer; +end +local _ENV=nil; +local r=e.add_task; +local o; +local s=0; +local a=a.create(); +local n={}; +local e=nil; +local function u(e)t("error","Traceback[timer]: %s",h(i(e),2));end +local function h(i) +local t; +local o; +while true do +t=a:peek(); +if t==nil or t>i then break;end +local t,a,e=a:pop(); +local s=n[e]; +n[e]=nil; +local h,t=c(a,u,i,e,s); +if h and l(t)=="number"then +if o then +o[e]={a,t+i}; +else +o={[e]={a,t+i}}; +end +n[e]=s; +end +end +if o then +for o,e in m(o)do +a:insert(e[1],e[2],o); +end +t=a:peek(); +end +if t~=nil and s>1 and t==e then +t=nil; +else +e=t; +end +if t then +return t-i; +end +s=s-1; +end +local function u(t,u,l) +local i=d(); +local t=i+t; +local a=a:insert(u,t); +n[a]=l; +if e==nil or t";end +local e={}; +for t in t:gmatch("[^;]+")do +h(e,y[o(t)]); +end +return""; +end +local function t(e) +return e:gsub("\027%[(.-)m",a); +end +return{ +getstring=p; +getstyle=r; +setstyle=i; +tohtml=t; +}; +end) +package.preload['util.uuid']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local a=require"util.random"; +local t=a.bytes; +local i=require"util.hex".encode; +local o=math.ceil; +local function e(e) +return i(t(o(e/2))):sub(1,e); +end +local function o() +return("%x"):format(t(1):byte()%4+8); +end +local function t() +return e(8).."-"..e(4).."-4"..e(3).."-"..(o())..e(3).."-"..e(12); +end +return{ +get_nibbles=e; +generate=t; +seed=a.seed; +}; +end) +package.preload['util.time']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local o=require"socket".gettime; +local e; +local t,a=pcall(require,"system"); +if t then +e=a.monotime; +end +return{ +now=o; +monotonic=e; +} +end) +package.preload['util.envload']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local t=load; +local i=io.open; +local function s(o,a,e) +return t(o,a,nil,e); +end +local function h(a,o) +local e,i,n=i(a); +if not e then return e,i,n;end +local a,t=t(e:lines(2048),"@"..a,nil,o); +e:close(); +return a,t; +end +return{envload=s,envloadfile=h}; +end) +package.preload['util.id']=(function(...) +local _ENV=_ENV; +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -543,77 +1136,266 @@ _M=e; return e; end -local a=require"net.server"; -local h=math.min -local u=math.huge -local i=require"socket".gettime; -local d=table.insert; -local l=pairs; -local r=type; -local s={}; +local t=string.gsub; +local o=require"util.random".bytes; +local a=require"util.encodings".base64.encode; +local i={["+"]="-",["/"]="_",["="]=""}; +local function e(e) +return(t(a(o(e)),"[+/=]",i)); +end +return{ +tiny=function()return e(3);end; +short=function()return e(9);end; +medium=function()return e(18);end; +long=function()return e(27);end; +custom=function(t) +return function()return e(t);end; +end; +} +end) +package.preload['util.serialization']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local _=getmetatable; +local t,a=next,type; +local n=string.format; +local z=string.gsub; +local c=string.rep; +local o=string.char; +local k=string.match; +local q=table.concat; +local g=require"util.hex".to; +local p=pcall; +local v=require"util.envload".envload; +if not math.type then +require"util.mathcompat" +end +local b,j=math.huge,-math.huge; +local x=math.type; +local function h(e) +return t,e,nil; +end +local function s(t,e) +error("Can't serialize "..a(t)..(e and": "..e or"")); +end +local function t(e,t) +return n("{__type=%q,__error=%q}",a(e),t or"fail"); +end +local r={ +['\a']=[[\a]];['\b']=[[\b]]; +['\f']=[[\f]];['\n']=[[\n]]; +['\r']=[[\r]];['\t']=[[\t]]; +['\v']=[[\v]];['\\']=[[\\]]; +['\"']=[[\"]];['\'']=[[\']]; +} +for t=0,255 do +local e=o(t); +if not r[e]then +r[e]=n("\\%03d",t); +end +end +local o={ +["do"]=true;["and"]=true;["else"]=true;["break"]=true; +["if"]=true;["end"]=true;["goto"]=true;["false"]=true; +["in"]=true;["for"]=true;["then"]=true;["local"]=true; +["or"]=true;["nil"]=true;["true"]=true;["until"]=true; +["elseif"]=true;["function"]=true;["not"]=true; +["repeat"]=true;["return"]=true;["while"]=true; +}; +local function u(e) +if a(e)~="table"then +e={preset=e}; +end +local i={ +table=true; +string=true; +number=true; +boolean=true; +["nil"]=true; +}; +if e.preset=="debug"then +e.preset="oneline"; +e.freeze=true; +e.fatal=false; +e.fallback=t; +e.unquoted=true; +end +if e.preset=="oneline"then +e.indentwith=e.indentwith or""; +e.itemstart=e.itemstart or" "; +e.itemlast=e.itemlast or""; +e.tend=e.tend or" }"; +elseif e.preset=="compact"then +e.indentwith=e.indentwith or""; +e.itemstart=e.itemstart or""; +e.itemlast=e.itemlast or""; +e.equals=e.equals or"="; +e.unquoted=true; +end +local s=e.fallback or e.fatal==false and t or s; +local function l(e) +return(i[a(e)]or s)(e); +end +local p=e.keywords or o; +local y=e.indentwith or"\t"; +local v=e.itemstart or"\n"; +local I=e.itemsep or";"; +local N=e.itemlast or";\n"; +local O=e.tstart or"{"; +local E=e.tend or"}"; +local T=e.kstart or"["; +local A=e.kend or"]"; +local f=e.equals or" = "; +local w=e.unquoted==true and"^[%a_][%w_]*$"or e.unquoted; +local d=e.hex; +local S=e.freeze; +local m=e.maxdepth or 127; +local H=e.multiref; +local R=e.table_iterator or h; +local function u(o,t,e,i) +if t[o]then +t[e],e=s(o,"table has multiple references"),e+1; +return e; +elseif i>m then +t[e],e=s(o,"max table depth reached"),e+1; +return e; +end +local m=o; +t[o]=true; +t[m]=true; +if S==true then +local i=_(o); +if a(i)=="table"then +local n=i.__name; +local i=i.__freeze; +if a(i)=="function"then +o=i(o); +if a(o)=="string"then +t[e],e=o,e+1; +return e; +end +if a(n)=="string"then +t[e],e=n,e+1; +end +end +end +end +t[e],e=O,e+1; +local b=c(y,i); +local s=1; +local h,r; +local d=false; +for o,n in R(o)do +d=true; +t[e],e=v,e+1; +t[e],e=b,e+1; +h,r=a(o),a(n); +if o==s then +s=s+1; +elseif w and h=="string"and +not p[o]and k(o,w)then +t[e],e=o,e+1; +t[e],e=f,e+1; +else +t[e],e=T,e+1; +if h=="table"then +e=u(o,t,e,i+1); +else +t[e],e=l(o),e+1; +end +t[e],t[e+1],e=A,f,e+2; +end +if r=="table"then +e=u(n,t,e,i+1); +else +t[e],e=l(n),e+1; +end +t[e],e=I,e+1; +end +if d then +t[e-1]=N; +t[e],e=c(y,i-1),e+1; +end +t[e],e=E,e+1; +if H then +t[o]=nil; +t[m]=nil; +end +return e; +end +function i.table(t) local e={}; -o"timer" -local t; -if not a.event then -function t(o,n) -local i=i(); -o=o+i; -if o>=i then -d(e,{o,n}); -else -local e=n(i); -if e and r(e)=="number"then -return t(e,n); -end -end -end -a._addtimer(function() -local a=i(); -if#e>0 then -for a,t in l(e)do -d(s,t); -end -e={}; -end -local e=u; -for n,o in l(s)do -local o,i=o[1],o[2]; -if o<=a then -s[n]=nil; -local a=i(a); -if r(a)=="number"then -t(a,i); -e=h(e,a); -end -else -e=h(e,o-a); -end -end -return e; -end); -else -local e=a.event; -local n=a.event_base; -local a=(e.core and e.core.LEAVE)or-1; -function t(o,e) -local t; -t=n:addevent(nil,0,function() -local e=e(i()); -if e then -return 0,e; -elseif t then -return a; -end -end -,o); -end -end -add_task=t; -return _M; +u(t,e,1,1); +return q(e); +end +local function t(e) +return'"'..z(e,"[%z\1-\31\"\'\\\127-\255]",r)..'"'; +end +if a(d)=="string"then +function i.string(e) +local t=t(e); +if#t>(#e*2+2+#d)then +return d..'"'..g(e)..'"'; +end +return t; +end +else +i.string=t; +end +function i.number(e) +if x(e)=="integer"then +return n("%d",e); +elseif e==b then +return"(1/0)"; +elseif e==j then +return"(-1/0)"; +elseif e~=e then +return"(0/0)"; +end +return n("%.18g",e); +end +i["nil"]=function() +return"nil"; +end +function i.boolean(e) +return e and"true"or"false"; +end +return l; +end +local function o(e) +if a(e)~="string"then return nil;end +e="return "..e; +local e,t=v(e,"=serialized data",{}); +if not e then return nil,t;end +local t,e=p(e); +if not t then return nil,e;end +return e; +end +local a=u(); +return{ +new=u; +serialize=function(e,t) +if t==nil then +return a(e); +else +return u(t)(e); +end +end; +deserialize=o; +}; end) -package.preload['util.termcolours']=(function(...) +package.preload['util.indexedbheap']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -623,91 +1405,384 @@ _M=e; return e; end -local n,i=table.concat,table.insert; -local t,s=string.char,string.format; -local h=tonumber; -local c=ipairs; -local l=io.write; -local e; -if os.getenv("WINDIR")then -e=require"util.windows"; -end -local o=e and e.get_consolecolor and e.get_consolecolor(); -a"termcolours" -local u={ -reset=0;bright=1,dim=2,underscore=4,blink=5,reverse=7,hidden=8; -black=30;red=31;green=32;yellow=33;blue=34;magenta=35;cyan=36;white=37; -["black background"]=40;["red background"]=41;["green background"]=42;["yellow background"]=43;["blue background"]=44;["magenta background"]=45;["cyan background"]=46;["white background"]=47; -bold=1,dark=2,underline=4,underlined=4,normal=0; -} -local d={ -["0"]=o, -["1"]=7+8, -["1;33"]=2+4+8, -["1;31"]=4+8 -} -local r={ -[1]="font-weight: bold",[2]="opacity: 0.5",[4]="text-decoration: underline",[8]="visibility: hidden", -[30]="color:black",[31]="color:red",[32]="color:green",[33]="color:#FFD700", -[34]="color:blue",[35]="color: magenta",[36]="color:cyan",[37]="color: white", -[40]="background-color:black",[41]="background-color:red",[42]="background-color:green", -[43]="background-color:yellow",[44]="background-color:blue",[45]="background-color: magenta", -[46]="background-color:cyan",[47]="background-color: white"; -}; -local a=t(27).."[%sm%s"..t(27).."[0m"; -function getstring(e,t) +local d=setmetatable; +local r=math.floor; +local i=table.remove; +local function l(t,s,o,n,i) +local e=#t+1; +while true do +local a=r(e/2); +if a==0 or s>t[a]then break;end +t[e]=t[a]; +o[e]=o[a]; +i[o[e]]=e; +e=a; +end +t[e]=s; +o[e]=n; +i[n]=e; +end +local function h(a,e,t,i) +local n=a[e]; +local s=t[e]; +while e~=1 do +local o=r(e/2); +if n>=a[o]then break;end +a[e]=a[o]; +t[e]=t[o]; +i[t[e]]=e; +e=o; +end +a[e]=n; +t[e]=s; +i[s]=e; +return e; +end +local function n(a,e,o,h) +local s=a[e]; +local n=o[e]; +local i=#a; +local t=2*e; +while 2*e<=i do +if t~=i and a[t]>a[t+1]then +t=t+1; +end +if s>a[t]then +a[e]=a[t]; +o[e]=o[t]; +h[o[e]]=e; +else +break; +end +e=t; +t=2*e; +end +a[e]=s; +o[e]=n; +h[n]=e; +return e; +end +local function r(e,t,o) +local h=#e; +if h==0 then return nil;end +local s=e[1]; +local a=t[1]; +o[a]=nil; +if h==1 then +e[1]=nil; +t[1]=nil; +return s,a; +end +e[1]=i(e); +t[1]=i(t); +o[t[1]]=1; +n(e,1,t,o); +return s,a; +end +local t={}; +function t:insert(t,a,e) +if e==nil then +e=self.current_id; +self.current_id=e+1; +end +self.items[e]=t; +l(self.priorities,a,self.ids,e,self.index); +return e; +end +function t:pop() +local a,e=r(self.priorities,self.ids,self.index); if e then -return s(a,e,t); -else +local t=self.items[e]; +self.items[e]=nil; +return a,t,e; +end +end +function t:peek() +return self.priorities[1]; +end +function t:reprioritize(e,t) +local e=self.index[e]; +if e==nil then return;end +self.priorities[e]=t; +e=h(self.priorities,e,self.ids,self.index); +n(self.priorities,e,self.ids,self.index); +end +function t:remove_index(e) +local o=self.priorities[e]; +if o==nil then return;end +local t=self.ids[e]; +local s=self.items[t]; +local a=#self.priorities; +self.priorities[e]=self.priorities[a]; +self.ids[e]=self.ids[a]; +self.index[self.ids[e]]=e; +i(self.priorities); +i(self.ids); +self.index[t]=nil; +self.items[t]=nil; +if a>e then +e=h(self.priorities,e,self.ids,self.index); +n(self.priorities,e,self.ids,self.index); +end +return o,s,t; +end +function t:remove(e) +return self:remove_index(self.index[e]); +end +local e={__index=t}; +local e={ +create=function() +return d({ +ids={}; +items={}; +priorities={}; +index={}; +current_id=1.5 +},e); +end +}; +return e; +end) +package.preload['util.xpcall']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local e=xpcall; +if select(2,e(function(e)return e end,function()end,"test"))~="test"then +e=require"util.compat".xpcall; +end +return{ +xpcall=e; +}; +end) +package.preload['util.array']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local r,l,u,f +=table.insert,table.sort,table.remove,table.concat; +local h=require"util.table".move; +local s=setmetatable; +local a=getmetatable; +local d=math.random; +local c=math.floor; +local y,p=pairs,ipairs; +local w=tostring; +local m=type; +local i={}; +local t={}; +local e={}; +local o={ +__index=e; +__name="array"; +__tostring=function(e)return"{"..e:concat(", ").."}";end; +}; +function o:__freeze()return self;end +local function n(i,e,a,t) +if m(e)=="function"then +e=i.collect(e,a,t); +end +return s(e or{},o); +end +function o.__add(t,a) +local e=n(); +return e:append(t):append(a); +end +function o.__eq(e,t) +if a(e)~=o or a(t)~=o then +return false; +end +if#e==#t then +for a=1,#e do +if e[a]~=t[a]then +return false; +end +end +else +return false; +end +return true; +end +function o.__div(t,o) +local a=n(); +local e=0; +for i=1,#t do +local t=o(t[i]); +if t~=nil then +e=e+1; +a[e]=t; +end +end +return a; +end +s(i,{__call=n}); +function e:random() +return self[d(1,#self)]; +end +function e:random_other(t) +local e=#self; +return self[((math.random(1,e-1)+(t-1))%e)+1]; +end +function t.map(e,t,a) +for t,o in p(t)do +e[t]=a(o); +end +return e; +end +function t.filter(t,o,n) +local i,a=o==t,#o; +local e=1; +for a=1,a do +local a=o[a]; +if n(a)then +t[e]=a; +e=e+1; +end +end +if i and e<=a then +for e=e,a do +t[e]=nil; +end +end return t; end -end -function getstyle(...) -local e,t={...},{}; -for a,e in c(e)do -e=u[e]; -if e then -i(t,e); -end -end -return n(t,";"); -end -local a="0"; -function setstyle(e) -e=e or"0"; -if e~=a then -l("\27["..e.."m"); -a=e; -end -end -if e then -function setstyle(t) -t=t or"0"; -if t~=a then -e.set_consolecolor(d[t]or o); -a=t; -end -end -if not o then -function setstyle(e)end -end -end -local function a(e) -if e=="0"then return"";end +function t.slice(a,t,o,e) +if e==nil then +e=-1; +end +if e<0 then +e=#t+(e+1); +end +if o<0 then +o=#t+(o+1); +end +if o<1 then +o=1; +end +if e>#t then +e=#t; +end +if o>e then +for e=1,#a do +a[e]=nil; +end +return a; +end +h(t,o,e,1,a); +if t==a then +h(t,#a+1,#a*2,2+e-o,t); +end +return a; +end +function t.sort(e,t,...) +if t~=e then +e:append(t); +end +l(e,...); +return e; +end +function t.unique(o,a) +local e={}; +return t.filter(o,a,function(t) +if e[t]then +return false; +else +e[t]=true; +return true; +end +end); +end +function t.pluck(a,e,o,i) +for t=1,#e do +local e=e[t][o]; +if e==nil then +e=i; +end +a[t]=e; +end +return a; +end +function t.reverse(e,i) +local t=#i; +if i==e then +local a=c(t/2); +t=t+1; +local o; +for a=1,a do +o=t-a; +e[a],e[o]=e[o],e[a]; +end +else +local a=t+1; +for t=1,t do +e[t]=i[a-t]; +end +end +return e; +end +function e:shuffle() +local t=#self; +for e=1,#self do +local t=d(e,t); +self[e],self[t]=self[t],self[e]; +end +return self; +end +function e:append(e) +h(e,1,#e,#self+1,self); +return self; +end +function e:push(t) +r(self,t); +return self; +end +e.pop=u; +function e:concat(e) +return f(i.map(self,w),e); +end +function e:length() +return#self; +end +function i.collect(i,a,e) local t={}; -for e in e:gmatch("[^;]+")do -i(t,r[h(e)]); -end -return""; -end -function tohtml(e) -return e:gsub("\027%[(.-)m",a); -end -return _M; +while true do +e=i(a,e); +if e==nil then break;end +r(t,e); +end +return s(t,o); +end +for t,a in y(t)do +local a=a; +i[t]=function(t,...) +local o=n(); +return a(o,t,...); +end +e[t]=function(e,...) +return a(e,e,...); +end +end +return i; end) -package.preload['util.uuid']=(function(...) +package.preload['util.format']=(function(...) local _ENV=_ENV; -local function o(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -718,42 +1793,305 @@ return e; end local n=tostring; -local e=os.time; -local a=os.clock; -local i=require"util.hashes".sha1; -o"uuid" +local m=table.unpack; +local e=table.pack; +local c=require"util.encodings".utf8.valid; +local i=type; +local l=require"util.serialization".new("debug"); +local r=math.type; +local u={c=true,d=true,i=true,o=true,u=true,X=true,x=true,}; +local f={o=true;u=true;x=true;X=true}; +local d={ +["\000"]="\226\144\128",["\001"]="\226\144\129",["\002"]="\226\144\130", +["\003"]="\226\144\131",["\004"]="\226\144\132",["\005"]="\226\144\133", +["\006"]="\226\144\134",["\007"]="\226\144\135",["\008"]="\226\144\136", +["\009"]="\226\144\137",["\010"]="\226\144\138",["\011"]="\226\144\139", +["\012"]="\226\144\140",["\013"]="\226\144\141",["\014"]="\226\144\142", +["\015"]="\226\144\143",["\016"]="\226\144\144",["\017"]="\226\144\145", +["\018"]="\226\144\146",["\019"]="\226\144\147",["\020"]="\226\144\148", +["\021"]="\226\144\149",["\022"]="\226\144\150",["\023"]="\226\144\151", +["\024"]="\226\144\152",["\025"]="\226\144\153",["\026"]="\226\144\154", +["\027"]="\226\144\155",["\028"]="\226\144\156",["\029"]="\226\144\157", +["\030"]="\226\144\158",["\031"]="\226\144\159",["\127"]="\226\144\161", +}; +local y=pcall(string.format,"%p",""); +local function w(h,...) +local o=e(...); +local w=o.n; +local a=0; +h=h:gsub("%%[^cdiouxXaAeEfgGpqs%%]*[cdiouxXaAeEfgGpqs%%]",function(s) +if s=="%%"then return end +a=a+1; +local e=o[a]; +if e==nil then +o[a]="nil"; +return"(%s)"; +end +local t=s:sub(-1); +local i=i(e); +if t=="s"and i=="string"and not e:find("[%z\1-\31\128-\255]")then +return +elseif i=="number"then +if t=="g"or(t=="d"and r(e)=="integer")then return end +elseif t=="s"and i~="string"then +e=n(e); +i="string"; +end +if t~="s"and t~="q"and t~="p"then +if i~="number"then +e=n(e); +t="s"; +s="[%s]"; +i="string"; +elseif u[t]and r(e)~="integer"then +o[a]=n(e); +return"[%s]"; +elseif f[t]and e<0 then +o[a]=n(e); +return"[%s]"; +else +return +end +end +if t=="p"and not y then +e=n(e); +t="s"; +s="[%s]"; +i="string"; +end +if i=="string"and t~="p"then +if not c(e)then +t="q"; +elseif t~="q"then +o[a]=e:gsub("[%z\1-\8\11-\31\127]",d):gsub("\n\t?","\n\t"); +return s; +end +end +if t=="q"then +o[a]=l(e); +return"%s"; +end +if t=="p"and(i=="boolean"or i=="number")then +o[a]=n(e); +return"[%s]"; +end +end); +while a=e then e=t+1;end -t=e; -return e; -end -local function t(e) -return i(e..a()..n({}),true); -end -local e=t(o()); -local function a(a) -e=t(e..a); +for h,o in pairs(o)do +if i(o)then +t=t+1; +o:next(function(o) +a[h]=o; +e=e+1; +if e==t and s then +n(a); +end +end,r); +else +a[h]=o; +end +end +s=true; +if e==t then +n(a); +end +end); +end +local function u(n) +return e(function(h) +local e,t,o=0,{},false; +local a=0; +for s,n in pairs(n)do +if i(n)then +a=a+1; +n:next(function(i) +t[s]={status="fulfilled",value=i}; +e=e+1; +if e==a and o then +h(t); +end +end,function(i) +t[s]={status="rejected",reason=i}; +e=e+1; +if e==a and o then +h(t); +end +end); +else +t[s]=n; +end +end +o=true; +if e==a then +h(t); +end +end); +end +local function n(e,...) +local t,a={...},select("#",...); +return r(t):next(function(t) +return e(l(t,1,a)); +end); +end +local function s(t) +return e(function(e,a) +for o=1,#t do +t[o]:next(e,a); +end +end); end local function t(t) -if#e"; -end -return e; -end -local w={ -LOC=e.LOC_tostring; -MX=function(e) -return a.format('%2i %s',e.pref,e.mx); -end; -SRV=function(e) -local e=e.srv; -return a.format('%5d %5d %5d %s',e.priority,e.weight,e.port,e.target); -end; -}; -local x={}; -function x.__tostring(e) -local t=(w[e.type]or j)(e); -return a.format('%2s %-5s %6i %-28s %s',e.class,e.type,e.ttl,e.name,t); -end -local j={}; -function j.__tostring(t) -local e={}; -for a,t in m(t)do -n(e,p(t)..'\n'); -end -return i.concat(e); -end -local w={}; -function w.__tostring(e) -local a=s.gettime(); -local t={}; -for i,e in o(e)do -for i,e in o(e)do -for o,e in o(e)do -y(e,a); -n(t,p(e)); -end -end -end -return i.concat(t); -end -function e:new() -local t={active={},cache={},unsorted={}}; -r(t,e); -r(t.cache,w); -r(t.unsorted,{__mode='kv'}); -return t; -end -function t.random(...) -v.randomseed(v.floor(1e4*s.gettime())%2147483648); -t.random=v.random; -return t.random(...); -end -local function v(e) -e=e or{}; -e.id=e.id or t.random(0,65535); -e.rd=e.rd or 1; -e.tc=e.tc or 0; -e.aa=e.aa or 0; -e.opcode=e.opcode or 0; -e.qr=e.qr or 0; -e.rcode=e.rcode or 0; -e.z=e.z or 0; -e.ra=e.ra or 0; -e.qdcount=e.qdcount or 1; -e.ancount=e.ancount or 0; -e.nscount=e.nscount or 0; -e.arcount=e.arcount or 0; -local t=a.char( -c(e.id),e.id%256, -e.rd+2*e.tc+4*e.aa+8*e.opcode+128*e.qr, -e.rcode+16*e.z+128*e.ra, -c(e.qdcount),e.qdcount%256, -c(e.ancount),e.ancount%256, -c(e.nscount),e.nscount%256, -c(e.arcount),e.arcount%256 -); -return t,e.id; -end -local function c(t) -local e={}; -for t in a.gmatch(t,'[^.]+')do -n(e,a.char(a.len(t))); -n(e,t); -end -n(e,a.char(0)); -return i.concat(e); -end -local function z(o,a,e) -o=c(o); -a=t.typecode[a or'a']; -e=t.classcode[e or'in']; -return o..a..e; -end -function e:byte(e) -e=e or 1; -local t=self.offset; -local o=t+e-1; -if o>#self.packet then -q(a.format('out of bounds: %i>%i',o,#self.packet)); -end -self.offset=t+e; -return a.byte(self.packet,t,o); -end -function e:word() -local t,e=self:byte(2); -return 256*t+e; -end -function e:dword() -local o,t,a,e=self:byte(4); -return 16777216*o+65536*t+256*a+e; -end -function e:sub(e) -e=e or 1; -local t=a.sub(self.packet,self.offset,self.offset+e-1); -self.offset=self.offset+e; -return t; -end -function e:header(t) -local e=self:word(); -if not self.active[e]and not t then return nil;end -local e={id=e}; -local t,a=self:byte(2); -e.rd=t%2; -e.tc=t/2%2; -e.aa=t/4%2; -e.opcode=t/8%16; -e.qr=t/128; -e.rcode=a%16; -e.z=a/16%8; -e.ra=a/128; -e.qdcount=self:word(); -e.ancount=self:word(); -e.nscount=self:word(); -e.arcount=self:word(); -for a,t in o(e)do e[a]=t-t%1;end -return e; -end -function e:name() -local a,t=nil,0; -local e=self:byte(); -local o={}; -if e==0 then return"."end -while e>0 do -if e>=192 then -t=t+1; -if t>=20 then q('dns error: 20 pointers');end; -local e=((e-192)*256)+self:byte(); -a=a or self.offset; -self.offset=e+1; -else -n(o,self:sub(e)..'.'); -end -e=self:byte(); -end -self.offset=a or self.offset; -return i.concat(o); -end -function e:question() -local e={}; -e.name=self:name(); -e.type=t.type[self:word()]; -e.class=t.class[self:word()]; -return e; -end -function e:A(e) -local o,t,n,i=self:byte(4); -e.a=a.format('%i.%i.%i.%i',o,t,n,i); -end -function e:AAAA(a) -local e={}; -for t=1,a.rdlength,2 do -local t,a=self:byte(2); -i.insert(e,("%02x%02x"):format(t,a)); -end -e=i.concat(e,":"):gsub("%f[%x]0+(%x)","%1"); -local t={}; -for e in e:gmatch(":[0:]+:")do -i.insert(t,e) -end -if#t==0 then -a.aaaa=e; -return -elseif#t>1 then -i.sort(t,function(t,e)return#t>#e end); -end -a.aaaa=e:gsub(t[1],"::",1):gsub("^0::","::"):gsub("::0$","::"); -end -function e:CNAME(e) -e.cname=self:name(); -end -function e:MX(e) -e.pref=self:word(); -e.mx=self:name(); -end -function e:LOC_nibble_power() -local e=self:byte(); -return((e-(e%16))/16)*(10^(e%16)); -end -function e:LOC(e) -e.version=self:byte(); -if e.version==0 then -e.loc=e.loc or{}; -e.loc.size=self:LOC_nibble_power(); -e.loc.horiz_pre=self:LOC_nibble_power(); -e.loc.vert_pre=self:LOC_nibble_power(); -e.loc.latitude=self:dword(); -e.loc.longitude=self:dword(); -e.loc.altitude=self:dword(); -end -end -local function c(e,i,t) -e=e-2147483648; -if e<0 then i=t;e=-e;end -local n,o,t; -t=e%6e4; -e=(e-t)/6e4; -o=e%60; -n=(e-o)/60; -return a.format('%3d %2d %2.3f %s',n,o,t/1e3,i); -end -function e.LOC_tostring(e) -local t={}; -n(t,a.format( -'%s %s %.2fm %.2fm %.2fm %.2fm', -c(e.loc.latitude,'N','S'), -c(e.loc.longitude,'E','W'), -(e.loc.altitude-1e7)/100, -e.loc.size/100, -e.loc.horiz_pre/100, -e.loc.vert_pre/100 -)); -return i.concat(t); -end -function e:NS(e) -e.ns=self:name(); -end -function e:SOA(e) -end -function e:SRV(e) -e.srv={}; -e.srv.priority=self:word(); -e.srv.weight=self:word(); -e.srv.port=self:word(); -e.srv.target=self:name(); -end -function e:PTR(e) -e.ptr=self:name(); -end -function e:TXT(e) -e.txt=self:sub(self:byte()); -end -function e:rr() -local e={}; -r(e,x); -e.name=self:name(self); -e.type=t.type[self:word()]or e.type; -e.class=t.class[self:word()]or e.class; -e.ttl=65536*self:word()+self:word(); -e.rdlength=self:word(); -if e.ttl<=0 then -e.tod=self.time+30; -else -e.tod=self.time+e.ttl; -end -local a=self.offset; -local t=self[t.type[e.type]]; -if t then t(self,e);end -self.offset=a; -e.rdata=self:sub(e.rdlength); -return e; -end -function e:rrs(t) -local e={}; -for t=1,t do n(e,self:rr());end -return e; -end -function e:decode(t,o) -self.packet,self.offset=t,1; -local t=self:header(o); -if not t then return nil;end -local t={header=t}; -t.question={}; -local i=self.offset; -for e=1,t.header.qdcount do -n(t.question,self:question()); -end -t.question.raw=a.sub(self.packet,i,self.offset-1); -if not o then -if not self.active[t.header.id]or not self.active[t.header.id][t.question.raw]then -self.active[t.header.id]=nil; -return nil; -end -end -t.answer=self:rrs(t.header.ancount); -t.authority=self:rrs(t.header.nscount); -t.additional=self:rrs(t.header.arcount); -return t; -end -e.delays={1,3}; -function e:addnameserver(e) -self.server=self.server or{}; -n(self.server,e); -end -function e:setnameserver(e) -self.server={}; -self:addnameserver(e); -end -function e:adddefaultnameservers() -if _ then -if b and b.get_nameservers then -for t,e in m(b.get_nameservers())do -self:addnameserver(e); -end -end -if not self.server or#self.server==0 then -self:addnameserver("208.67.222.222"); -self:addnameserver("208.67.220.220"); -end -else -local e=E.open("/etc/resolv.conf"); +}; +local r={}; +local function p(e) +if not e then return end +local a=u[e.rcode]; +local i=h[e.qclass]; +local t=s[e.qtype]; +e.status,e.class,e.type=a,i,t; +local a=q(t); +local i={__index=e,__tostring=function(e)return c(e[a])end}; +local s=v[t]; +for t=1,#e do +if e.bogus then +e[t]=nil; +else +e[t]=o({[a]=s(e[t])},i); +end +end +return o(e,n); +end +local function m(m,u,o,a) +if not t then l();end +o=o and d(o)or"A"; +a=a and d(a)or"IN"; +local l,h=s[o],h[a]; +local d=y(); +local n; +local i=w.init("unbound.query"..b()); +local function s(e,t) +local s=y(); +r[n]=nil; if e then -for e in e:lines()do -e=e:gsub("#.*$","") -:match('^%s*nameserver%s+(.*)%s*$'); -if e then -e:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]",function(e) -self:addnameserver(e) -end); -end -end -end -if not self.server or#self.server==0 then -self:addnameserver("127.0.0.1"); -end -end -end -function e:getsocket(o) -self.socket=self.socket or{}; -self.socketset=self.socketset or{}; -local e=self.socket[o]; -if e then return e;end -local a,t; -e,t=s.udp(); -if e and self.socket_wrapper then e,t=self.socket_wrapper(e,self);end -if not e then -return nil,t; -end -e:settimeout(0); -self.socket[o]=e; -self.socketset[e]=o; -a,t=e:setsockname('*',0); -if not a then return self:servfail(e,t);end -a,t=e:setpeername(self.server[o],53); -if not a then return self:servfail(e,t);end -return e; -end -function e:voidsocket(e) -if self.socket[e]then -self.socketset[self.socket[e]]=nil; -self.socket[e]=nil; -elseif self.socketset[e]then -self.socket[self.socketset[e]]=nil; -self.socketset[e]=nil; -end -e:close(); -end -function e:socket_wrapper_set(e) -self.socket_wrapper=e; -end -function e:closeall() -for t,e in m(self.socket)do -self.socket[t]=nil; -self.socketset[e]=nil; -e:close(); -end -end -function e:remember(e,t) -local a,o,i=g(e.name,e.type,e.class); -if t~='*'then -t=o; -local t=d(self.cache,i,'*',a); -if t then n(t,e);end -end -self.cache=self.cache or r({},w); -local a=d(self.cache,i,t,a)or -l(self.cache,i,t,a,r({},j)); -if not a[e[o:lower()]]then -a[e[o:lower()]]=true; -n(a,e); -end -if t=='MX'then self.unsorted[a]=true;end -end -local function c(e,t) -return(e.pref==t.pref)and(e.mx#self.server then -e.server=1; -end -e.retries=(e.retries or 0)+1; -if e.retries>=#self.server then -a[o]=nil; -else -t,i=self:getsocket(e.server); -if t then t:send(e.packet);end -end -end -end -if h(a)==nil then -self.active[s]=nil; -end -end -if n==self.best_server then -self.best_server=self.best_server+1; -if self.best_server>#self.server then -self.best_server=1; -end -end -return t,i; -end -function e:settimeout(e) -self.timeout=e; -end -function e:receive(t) -self.time=s.gettime(); -t=t or self.socket; -local e; -for a,t in o(t)do -if self.socketset[t]then -local t=t:receive(); -if t then -e=self:decode(t); -if e and self.active[e.header.id] -and self.active[e.header.id][e.question.raw]then -for a,t in o(e.answer)do -self:remember(t,e.question[1].type) -end -local t=self.active[e.header.id]; -t[e.question.raw]=nil; -if not h(t)then self.active[e.header.id]=nil;end -if not h(self.active)then self:closeall();end -local e=e.question[1]; -local t=d(self.wanted,e.class,e.type,e.name); -if t then -for e in o(t)do -if u.status(e)=="suspended"then u.resume(e);end -end -l(self.wanted,e.class,e.type,e.name,nil); -end -end -end -end -end -return e; -end -function e:feed(a,t,e) -self.time=s.gettime(); -local e=self:decode(t,e); -if e and self.active[e.header.id] -and self.active[e.header.id][e.question.raw]then -for a,t in o(e.answer)do -self:remember(t,e.question[1].type); -end -local t=self.active[e.header.id]; -t[e.question.raw]=nil; -if not h(t)then self.active[e.header.id]=nil;end -if not h(self.active)then self:closeall();end -local e=e.question[1]; -if e then -local t=d(self.wanted,e.class,e.type,e.name); -if t then -for e in o(t)do -if u.status(e)=="suspended"then u.resume(e);end -end -l(self.wanted,e.class,e.type,e.name,nil); -end -end -end -return e; -end -function e:cancel(t,a,i) -local e=d(self.wanted,t,a,i); +local function t() +for t in pairs(r)do e(t);end +if f then f:close();end +l(); +return true; +end +local function a() +error"not implemented"; +end +local a={ +lookup=m; +cancel=e; +new_async_socket=a; +dns={ +lookup=o; +cancel=e; +cache=i; +socket_wrapper_set=i; +settimeout=i; +query=i; +purge=t; +random=i; +peek=i; +types=s; +classes=h; +}; +}; +local function n(e,i,a,o) +return g.new(function(n,t) +local function s(a,e) if e then -for e in o(e)do -if u.status(e)=="suspended"then u.resume(e);end -end -l(self.wanted,t,a,i,nil); -end -end -function e:pulse() -while self:receive()do end -if not h(self.active)then return nil;end -self.time=s.gettime(); -for a,t in o(self.active)do -for o,e in o(t)do -if self.time>=e.retry then -e.server=e.server+1; -if e.server>#self.server then -e.server=1; -e.delay=e.delay+1; -end -if e.delay>#self.delays then -t[o]=nil; -if not h(t)then self.active[a]=nil;end -if not h(self.active)then return nil;end -else -local t=self.socket[e.server]; -if t then t:send(e.packet);end -e.retry=self.time+self.delays[e.delay]; -end -end -end -end -if h(self.active)then return true;end -return nil; -end -function e:lookup(a,e,t) -self:query(a,e,t) -while self:pulse()do -local e={} -for t,a in m(self.socket)do -e[t]=a -end -s.select(e,nil,4) -end -return self:peek(a,e,t); -end -function e:lookupex(o,a,t,e) -return self:peek(a,t,e)or self:query(a,t,e); -end -function e:tohostname(e) -return t.lookup(e:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)","%4.%3.%2.%1.in-addr.arpa."),"PTR"); -end -local i={ -qr={[0]='query','response'}, -opcode={[0]='query','inverse query','server status request'}, -aa={[0]='non-authoritative','authoritative'}, -tc={[0]='complete','truncated'}, -rd={[0]='recursion not desired','recursion desired'}, -ra={[0]='recursion not available','recursion available'}, -z={[0]='(reserved)'}, -rcode={[0]='no error','format error','server failure','name error','not implemented'}, -type=t.type, -class=t.class -}; -local function s(t,e) -return(i[e]and i[e][t[e]])or''; -end -function e.print(t) -for o,e in o{'id','qr','opcode','aa','tc','rd','ra','z', -'rcode','qdcount','ancount','nscount','arcount'}do -f(a.format('%-30s','header.'..e),t.header[e],s(t.header,e)); -end -for e,t in m(t.question)do -f(a.format('question[%i].name ',e),t.name); -f(a.format('question[%i].type ',e),t.type); -f(a.format('question[%i].class ',e),t.class); -end -local r={name=1,type=1,class=1,ttl=1,rdlength=1,rdata=1}; -local e; -for n,i in o({'answer','authority','additional'})do -for h,n in o(t[i])do -for o,t in o({'name','type','class','ttl','rdlength'})do -e=a.format('%s[%i].%s',i,h,t); -f(a.format('%-30s',e),n[t],s(n,t)); -end -for t,o in o(n)do -if not r[t]then -e=a.format('%s[%i].%s',i,h,t); -f(a.format('%-30s %s',p(e),p(o))); -end -end -end -end -end -function t.resolver() -local t={active={},cache={},unsorted={},wanted={},best_server=1}; -r(t,e); -r(t.cache,w); -r(t.unsorted,{__mode='kv'}); -return t; -end -local e=t.resolver(); -t._resolver=e; -function t.lookup(...) -return e:lookup(...); -end -function t.tohostname(...) -return e:tohostname(...); -end -function t.purge(...) -return e:purge(...); -end -function t.peek(...) -return e:peek(...); -end -function t.query(...) -return e:query(...); -end -function t.feed(...) -return e:feed(...); -end -function t.cancel(...) -return e:cancel(...); -end -function t.settimeout(...) -return e:settimeout(...); -end -function t.cache() -return e.cache; -end -function t.socket_wrapper_set(...) -return e:socket_wrapper_set(...); -end -return t; +return t(e); +else +return n(a); +end +end +local e,a=m(s,i,a,o) +if not e then t(a);end +end); +end +local e={ +lookup=function(i,e,t,a,o) +return m(e,t,a,o) +end; +lookup_promise=n; +_resolver={ +settimeout=function()end; +closeall=function()end; +}; +} +function a.resolver()return e;end +return a; end) -package.preload['net.adns']=(function(...) +package.preload['util.dns']=(function(...) local _ENV=_ENV; -local function o(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -1588,77 +2307,213 @@ _M=e; return e; end -local c=require"net.server"; -local a=require"net.dns"; -local t=require"util.logger".init("adns"); -local e,e=table.insert,table.remove; -local n,h,l=coroutine,tostring,pcall; -local function u(a,a,t,e)return(e-t)+1;end -o"adns" -function lookup(d,e,s,r) -return n.wrap(function(o) -if o then -t("debug","Records for %s already cached, using those...",e); -d(o); -return; -end -t("debug","Records for %s not in cache, sending query (%s)...",e,h(n.running())); -local i,o=a.query(e,s,r); -if i then -n.yield({r or"IN",s or"A",e,n.running()}); -t("debug","Reply for %s (%s)",e,h(n.running())); -end -if i then -i,o=l(d,a.peek(e,s,r)); -else -t("error","Error sending DNS query: %s",o); -i,o=l(d,nil,o); -end -if not i then -t("error","Error in DNS response handler: %s",h(o)); -end -end)(a.peek(e,s,r)); -end -function cancel(e,o,i) -t("warn","Cancelling DNS lookup for %s",h(e[3])); -a.cancel(e[1],e[2],e[3],e[4],o); -end -function new_async_socket(i,o) -local n=""; -local s={}; +local t=setmetatable; +local e=table; +local s=e.concat; +local n=e.insert; +local o=string.byte; +local i=string.format; +local r=string.sub; +local h=require"util.dnsregistry"; +local c=require"util.hex".encode; +local d=require"util.net".ntop; local e={}; -local h; -function s.onincoming(o,t) -if t then -a.feed(e,t); -end -end -function s.ondisconnect(a,i) -if i then -t("warn","DNS socket for %s disconnected: %s",n,i); -local e=o.server; -if o.socketset[a]==o.best_server and o.best_server==#e then -t("error","Exhausted all %d configured DNS servers, next lookup will try %s again",#e,e[1]); -end -o:servfail(a); -end -end -e,h=c.wrapclient(i,"dns",53,s); -if not e then -return nil,h; -end -e.settimeout=function()end -e.setsockname=function(t,...)return i:setsockname(...);end -e.setpeername=function(o,...)n=(...);local a,t=i:setpeername(...);o:set_send(u);return a,t;end -e.connect=function(t,...)return i:connect(...)end -e.send=function(a,e) -t("debug","Sending DNS query to %s",n); -return i:send(e); -end -return e; -end -a.socket_wrapper_set(new_async_socket); -return _M; +local function a(a,t) +if o(a,t)==0 then return".",t+1;end +local d,h,i=#a,{}; +t=t or 1; +repeat +i=o(a,t)or 0; +n(h,r(a,t+1,t+i)); +t=t+i+1; +until i==0 or t>=d; +return s(h,"."),t; +end +e.CNAME=a; +e.NS=a +e.PTR=a; +local x={ +__tostring=function(e) +return i("%s %s %d %d %d %d %d",e.mname,e.rname,e.serial,e.refresh,e.retry,e.expire,e.minimum); +end; +}; +function e.SOA(n) +local s,h,i; +s,i=a(n,1); +h,i=a(n,i); +local +j,q,g,k, +b,l,r,d, +n,a,o,i, +u,w,c,p, +v,y,m,f +=o(n,i,i+19); +return t({ +mname=s; +rname=h; +serial=j*16777216+q*65536+g*256+k; +refresh=b*16777216+l*65536+r*256+d; +retry=n*16777216+a*65536+o*256+i; +expire=u*16777216+w*65536+c*256+p; +minimum=v*16777216+y*65536+m*256+f; +},x); +end +e.A=d; +e.AAAA=d; +local d={ +__tostring=function(e) +return i("%d %s",e.pref,e.mx) +end +}; +function e.MX(e) +local i=a(e,3); +local a,e=o(e,1,2); +return t({ +pref=a*256+e; +mx=i; +},d); +end +local d={ +__tostring=function(e) +return i("%d %d %d %s",e.priority,e.weight,e.port,e.target); +end +}; +function e.SRV(e) +local i=a(e,7); +local e,a,o,n,h,s=o(e,1,6); +return t({ +priority=e*256+a; +weight=o*256+n; +port=h*256+s; +target=i; +},d); +end +local d={__tostring=s}; +function e.TXT(s) +local l=#s; +local h,a,i={},1; +repeat +i=o(s,a)or 0; +n(h,r(s,a+1,a+i)); +a=a+i+1; +until a>=l; +return t(h,d); +end +e.SPF=e.TXT; +local d={ +[0]="PKIX-CA"; +[1]="PKIX-EE"; +[2]="DANE-TA"; +[3]="DANE-EE"; +[255]="PrivCert"; +}; +local u={ +[0]="Cert", +[1]="SPKI", +[255]="PrivSel", +}; +local l={ +[0]="Full", +[1]="SHA2-256", +[2]="SHA2-512", +[255]="PrivMatch", +}; +local m={ +__tostring=function(e) +return i("%s %s %s %s", +d[e.use]or e.use, +u[e.select]or e.select, +l[e.match]or e.match, +c(e.data)); +end; +__index={ +getUsage=function(e)return d[e.use]end; +getSelector=function(e)return u[e.select]end; +getMatchType=function(e)return l[e.match]end; +} +}; +function e.TLSA(e) +local i,o,a=o(e,1,3); +return t({ +use=i; +select=o; +match=a; +data=r(e,4); +},m); +end +local o={"alpn";"no-default-alpn";"port";"ipv4hint";"ech";"ipv6hint"}; +t(o,{__index=function(t,e)return"key"..tostring(e);end}); +local m={ +__tostring=function(e) +local t={}; +for a=1,#e.fields do +n(t,i("%s=%q",o[e.fields[a].key],tostring(e.fields[a].value))); +end +return i("%d %s %s",e.prio,e.name,s(t," ")); +end; +}; +local r={__tostring=function(e)return s(e,", ");end} +function e.SVCB(i) +local o,s=i:byte(1,2); +local u=o*256+s; +local l,o=a(i,3); +local d={}; +while#i>o do +local s,l=i:byte(o+0,o+1); +local h,a=i:byte(o+2,o+3); +local s=s*256+l; +local h=h*256+a; +local a=i:sub(o+4,o+4-1+h) +if s==1 then +a=t(e.TXT(a),r); +elseif s==3 then +local e,t=a:byte(1,2); +local e=e*256+t; +a=e; +elseif s==4 then +local o={}; +for t=1,#a,4 do +n(o,e.A(a:sub(t,t+3))); +end +a=t(o,r); +elseif s==6 then +local o={}; +for t=1,#a,16 do +n(o,e.AAAA(a:sub(t,t+15))); +end +a=t(o,r); +end +n(d,{key=s,value=a,len=h}); +o=o+h+4; +end +return t({ +prio=u,name=l,fields=d, +},m); +end +e.HTTPS=e.SVCB; +local o={ +TLSA={ +use=d; +select=u; +match=l; +}; +}; +local a={ +__tostring=function(e) +return i([[\# %d %s]],#e.raw,c(e.raw)); +end; +}; +local function i(e) +return t({raw=e},a); +end +t(e,{__index=function()return i end}); +return{ +parsers=e; +classes=h.classes; +types=h.types; +errors=h.errors; +params=o; +}; end) package.preload['net.server']=(function(...) local _ENV=_ENV; @@ -1672,122 +2527,125 @@ _M=e; return e; end -local m=function(e) +local f=function(e) return _G[e] end -local W,e=require("util.logger").init("socket"),table.concat; -local n=function(...)return W("debug",e{...});end -local E=function(...)return W("warn",e{...});end -local ce=1 -local f=m"type" -local b=m"pairs" -local ue=m"ipairs" -local y=m"tonumber" -local l=m"tostring" -local t=m"table" -local a=m"string" -local e=m"coroutine" -local Y=math.min -local re=math.huge -local pe=t.concat -local he=t.insert -local we=a.sub -local fe=e.wrap -local me=e.yield -local O,e=pcall(require,"ssl") -local g=m"socket"or require"socket" -local F=g.gettime -local ee=g.dns.getaddrinfo -local ye=(O and e.wrap) -local te=g.bind -local ve=g.select -local I -local B -local ae +local V,e=require("util.logger").init("socket"),table.concat; +local n=function(...)return V("debug",e{...});end +local j=function(...)return V("warn",e{...});end +local ie=1 +local w=f"type" +local q=f"pairs" +local oe=f"ipairs" +local b=f"tonumber" +local u=f"tostring" +local t=f"table" +local a=f"string" +local e=f"coroutine" +local W=math.min +local te=math.huge +local ye=t.concat +local ae=t.insert +local be=a.sub +local pe=e.wrap +local ke=e.yield +local x=f"socket"or require"socket" +local P=x.gettime +local e=require"util.net"; +local ge=e.pton; +local ve=require"util.sslconfig"; +local Y,fe=pcall(require,"net.tls_luasec"); +local ue=x.bind +local we=x.select +local U +local Z +local ce +local me +local ee +local l +local de +local J +local K +local le +local re +local ne +local Q +local h +local se local G -local J -local u -local ie -local X -local se -local ne -local oe -local Q -local s -local le -local Z -local de +local he local p local i -local P +local F local r -local h +local s +local R +local v +local y +local k local T -local v -local w -local k -local x local a local o -local q +local g local C local M -local A -local j -local N -local K +local D +local _ +local H +local B local d -local H local S -local R -local D +local I +local O +local N local L local z -local _ -local U +local E +local A p={} i={} r={} -P={} -h={} +F={} +s={} v={} -w={} -T={} +y={} +R={} k={} a=0 o=0 -q=0 +g=0 C=0 M=0 -A=1 -j=128 -N=10 -H=51e3*1024 -S=25e3*1024 -R=30 -D=6e4 +D=1 +_=128 +H=10 +S=51e3*1024 +I=25e3*1024 +O=30 +N=6e4 L=14*60 local e=package.config:sub(1,1)=="\\" -_=(e and math.huge)or g._SETSIZE or 1024 -z=g._SETSIZE or 1024 -U=30 -ne=function(y,t,m,c,g,w) -if t:getfd()>=_ then -E("server.lua: Disallowed FD number: "..t:getfd()) +E=(e and math.huge)or x._SETSIZE or 1024 +z=x._SETSIZE or 1024 +A=30 +re=function(w,t,m,c,q,y,v) +if t:getfd()>=E then +j("server.lua: Disallowed FD number: "..t:getfd()) t:close() return nil,"fd-too-large" end local f=0 -local v,e=y.onconnect,y.ondisconnect -local b=t.accept +local b,e=w.onconnect,w.ondisconnect +local g=t.accept local e={} e.shutdown=function()end e.ssl=function() -return w~=nil +return y~=nil end e.sslctx=function() -return w -end +return y +end +e.hosts={} e.remove=function() f=f-1 if e then @@ -1796,19 +2654,19 @@ end e.close=function() t:close() -o=s(r,t,o) -a=s(i,t,a) +o=h(r,t,o) +a=h(i,t,a) p[m..":"..c]=nil; -h[t]=nil +s[t]=nil e=nil t=nil n"server.lua: closed server handler and removed sockets from list" end e.pause=function(o) if not e.paused then -a=s(i,t,a) +a=h(i,t,a) if o then -h[t]=nil +s[t]=nil t:close() t=nil; end @@ -1819,11 +2677,11 @@ e.resume=function() if e.paused then if not t then -t=te(m,c,j); +t=ue(m,c,_); t:settimeout(0) end -a=u(i,t,a) -h[t]=e +a=l(i,t,a) +s[t]=e k[e]=nil e.paused=false; n("server.lua: server [",m,"]:",c," resumed") @@ -1845,21 +2703,21 @@ n("server.lua: refused new client connection: server full") return false end -local t,o=b(t) +local t,o=g(t) if t then -local o,a=t:getpeername() -local t,i,e=Z(e,y,t,o,c,a,g,w) -if e then +local a,o=t:getpeername() +local e,i,t=G(e,w,t,a,c,o,q,y,v) +if t then return false end f=f+1 -n("server.lua: accepted new client connection from ",l(o),":",l(a)," to ",l(c)) -if v and not w then -return v(t); +n("server.lua: accepted new client connection from ",u(a),":",u(o)," to ",u(c)) +if b and not v then +return b(e); end return; elseif o then -n("server.lua: error with new client connection: ",l(o)) +n("server.lua: error with new client connection: ",u(o)) e.pause() k[e]=d return false @@ -1867,113 +2725,141 @@ end return e end -Z=function(p,f,t,z,K,A,N,g) -if t:getfd()>=_ then -E("server.lua: Disallowed FD number: "..t:getfd()) +G=function(q,c,t,H,ee,L,D,w,te,P) +if t:getfd()>=E then +j("server.lua: Disallowed FD number: "..t:getfd()) t:close() -if p then -k[p]=d -p.pause() +if q then +k[q]=d +q.pause() end return nil,nil,"fd-too-large" end t:settimeout(0) -local y -local _ -local q +local b +local N +local z +local B +local x +local K=c.onincoming +local G=c.onstatus +local k=c.ondisconnect +local J=c.onpredrain +local X=c.ondrain +local ae=c.onreadtimeout; +local Q=c.ondetach +local g={} +local m=0 +local Z local V -local P=f.onincoming -local Y=f.onstatus -local k=f.ondisconnect -local D=f.ondrain -local Q=f.onreadtimeout; -local R=f.ondetach -local b={} -local c=0 -local B -local L -local m=0 -local j=false +local f=0 +local p=false local E=false local W,F=0,0 -local H=H -local S=S -local e=b +local _=S +local O=I +local e=g +e.extra=P +if P then +e.servername=P.servername +end e.dispatch=function() -return P +return K end e.disconnect=function() return k end -e.onreadtimeout=Q; -e.setlistener=function(a,t) -if R then -R(a) -end -P=t.onincoming +e.onreadtimeout=ae; +e.setlistener=function(a,t,o) +if Q then +Q(a) +end +K=t.onincoming k=t.ondisconnect -Y=t.onstatus -D=t.ondrain +G=t.onstatus +J=t.onpredrain +X=t.ondrain e.onreadtimeout=t.onreadtimeout -R=t.ondetach +Q=t.ondetach +if t.onattach then +t.onattach(a,o) +end +end +e._setpending=function() +x=true end e.getstats=function() return F,W end e.ssl=function() -return V +return B end e.sslctx=function() -return g -end -e.send=function(n,o,a,i) -return y(t,o,a,i) +return w +end +e.ssl_info=function() +return t.info and t:info() +end +e.ssl_peercertificate=function() +if not t.getpeercertificate then return nil,"not-implemented";end +return t:getpeercertificate() +end +e.ssl_peerverification=function() +if not t.getpeerverification then return nil,{{"Chain verification not supported"}};end +return t:getpeerverification(); +end +e.ssl_peerfinished=function() +if not t.getpeerfinished then return nil,"not-implemented";end +return t:getpeerfinished(); +end +e.send=function(n,i,a,o) +return b(t,i,a,o) end e.receive=function(o,a) -return _(t,o,a) +return N(t,o,a) end e.shutdown=function(a) -return q(t,a) -end -e.setoption=function(i,a,o) +return z(t,a) +end +e.setoption=function(i,o,a) if t.setoption then -return t:setoption(a,o); +return t:setoption(o,a); end return false,"setoption not implemented"; end e.force_close=function(t,a) -if c~=0 then -n("server.lua: discarding unwritten data for ",l(z),":",l(A)) -c=0; +if m~=0 then +n("server.lua: discarding unwritten data for ",u(H),":",u(L)) +m=0; end return t:close(a); end e.close=function(l,d) if not e then return true;end -a=s(i,t,a) +a=h(i,t,a) v[e]=nil -if c~=0 then -e.sendbuffer() -if c~=0 then +if m~=0 then +e:sendbuffer() +if m~=0 then if e then e.write=nil end -B=true +Z=true return false end end if t then -x=q and q(t) +T=z and z(t) t:close() -o=s(r,t,o) -h[t]=nil +o=h(r,t,o) +s[t]=nil t=nil else n"server.lua: socket already closed" end if e then -w[e]=nil -T[e]=nil +y[e]=nil +R[e]=nil local t=e; e=nil if k then @@ -1981,296 +2867,323 @@ k=nil end end -if p then -p.remove() +if q then +q.remove() end n"server.lua: closed client handler and removed socket from list" return true end e.server=function() -return p +return q end e.ip=function() -return z +return H end e.serverport=function() -return K +return ee end e.clientport=function() -return A +return L end e.port=e.clientport -local p=function(i,a) +local k=function(i,a) if not e then return false end -m=m+#a -if m>H then -T[e]="send buffer exceeded" -e.write=G +f=f+#a +if f>_ then +R[e]="send buffer exceeded" return false -elseif t and not r[t]then -o=u(r,t,o) -end -c=c+1 -b[c]=a +elseif not E and t and not r[t]then +o=l(r,t,o) +end +m=m+1 +g[m]=a if e then -w[e]=w[e]or d +y[e]=y[e]or d end return true end -e.write=p +e.write=k e.bufferqueue=function(t) -return b +return g end e.socket=function(a) return t end e.set_mode=function(a,t) -N=t or N -return N +D=t or D +return D end e.set_send=function(a,t) -y=t or y -return y +b=t or b +return b end e.bufferlen=function(o,a,t) -H=t or H -S=a or S -return m,S,H -end -e.lock_read=function(n,o) -if o==true then +_=t or _ +O=a or O +return f,O,_ +end +e.lock_read=function(a,t) +j("server.lua, lock_read() is deprecated, use pause() and resume()") +if t==true then +return a:pause() +elseif t==false then +return a:resume() +end +return p +end +e.pause=function(o) local o=a -a=s(i,t,a) +a=h(i,t,a) v[e]=nil if a~=o then -j=true -end -elseif o==false then -if j then -j=false -a=u(i,t,a) +p=true +end +return p; +end +e.resume=function(o) +if p then +p=false +a=l(i,t,a) v[e]=d end -end -return j -end -e.pause=function(t) -return t:lock_read(true); -end -e.resume=function(t) -return t:lock_read(false); -end -e.lock=function(i,a) -e.lock_read(a) +return p; +end +e.lock=function(t,a) +j("server.lua, lock() is deprecated") +e.lock_read(t,a) if a==true then -e.write=G +e.pause_writes(t) +elseif a==false then +e.resume_writes(t) +end +return p,E +end +e.pause_writes=function(a) local a=o -o=s(r,t,o) -w[e]=nil -if o~=a then +o=h(r,t,o) +y[e]=nil E=true end -elseif a==false then -e.write=p -if E then +e.resume_writes=function(e) E=false -p("") -end -end -return j,E -end -local p=function() -local a,t,o=_(t,N) -if not t or(t=="wantread"or t=="timeout")then -local o=a or o or"" -local a=#o -if a>S then -e:close("receive buffer exceeded") -return false -end -local a=a*ce -F=F+a -M=M+a -v[e]=d -return P(e,o,t) -else -n("server.lua: client ",l(z),":",l(A)," read error: ",l(t)) -x=e and e:force_close(t) -return false +if f>0 and t then +o=l(r,t,o) end end local v=function() -local f,a,h,i,u; +local o,t,a=N(t,D) +if not t or(t=="wantread"or t=="timeout")then +local a=o or a or"" +local o=#a +if o>O then +e:close("receive buffer exceeded") +return false +end +local o=o*ie +F=F+o +M=M+o +v[e]=d +if x then +x=nil +if c.onconnect then +c.onconnect(e) +end +end +return K(e,a,t) +else +n("server.lua: client ",u(H),":",u(L)," read error: ",u(t)) +T=e and e:force_close(t) +return false +end +end +local p=function() +local w,a,i,s,l; if t then -i=pe(b,"",1,c) -f,a,h=y(t,i,1,m) -u=(f or h or 0)*ce -W=W+u -C=C+u -for e=c,1,-1 do -b[e]=nil -end -else -f,a,u=false,"unexpected close",0; -end -if f then -c=0 +if x then +x=nil +if c.onconnect then +c.onconnect(e); +end +end +if J then +J(e); +end +s=ye(g,"",1,m) +w,a,i=b(t,s,1,f) +l=(w or i or 0)*ie +W=W+l +C=C+l +for e=m,1,-1 do +g[e]=nil +end +else +w,a,l=false,"unexpected close",0; +end +if w then m=0 -o=s(r,t,o) -w[e]=nil -if D then -D(e) -end -x=L and e:starttls(nil) -x=B and e:force_close() +f=0 +o=h(r,t,o) +y[e]=nil +if X then +X(e) +end +T=V and e:starttls(nil) +T=Z and e:force_close() return true -elseif h and(a=="timeout"or a=="wantwrite")then -i=we(i,h+1,m) -b[1]=i -c=1 -m=m-h -w[e]=d +elseif i and(a=="timeout"or a=="wantwrite")then +s=be(s,i+1,f) +g[1]=s +m=1 +f=f-i +y[e]=d return true else -n("server.lua: client ",l(z),":",l(A)," write error: ",l(a)) -x=e and e:force_close(a) +n("server.lua: client ",u(H),":",u(L)," write error: ",u(a)) +T=e and e:force_close(a) return false end end local d; -function e.set_sslctx(w,t) -g=t; -local m,l -d=fe(function(h) +function e.set_sslctx(y,t) +w=t; +local u,f +d=pe(function(s) local t -for d=1,U do -o=(l and s(r,h,o))or o -a=(m and s(i,h,a))or a -m,l=nil,nil -d,t=h:dohandshake() +for d=1,A do +o=(f and h(r,s,o))or o +a=(u and h(i,s,a))or a +u,f=nil,nil +d,t=s:dohandshake() if not t then n("server.lua: ssl handshake done") -e.readbuffer=p -e.sendbuffer=v -d=Y and Y(e,"ssl-handshake-complete") -if w.autostart_ssl and f.onconnect then -f.onconnect(w); -if c~=0 then -o=u(r,h,o) -end -end -a=u(i,h,a) +e.readbuffer=v +e.sendbuffer=p +d=G and G(e,"ssl-handshake-complete") +if y.autostart_ssl and c.onconnect then +c.onconnect(y); +if m~=0 then +o=l(r,s,o) +end +end +a=l(i,s,a) return true else if t=="wantwrite"then -o=u(r,h,o) -l=true +o=l(r,s,o) +f=true elseif t=="wantread"then -a=u(i,h,a) -m=true +a=l(i,s,a) +u=true else break; end t=nil; -me() -end -end -t="ssl handshake error: "..(t or"handshake too long"); +ke() +end +end +t=(t or"handshake too long"); n("server.lua: ",t); -x=e and e:force_close(t) +T=e and e:force_close(t) return false,t end ) end -if O then -e.starttls=function(f,m) -if m then -e:set_sslctx(m); -end -if c>0 then +if Y then +e.starttls=function(f,c) +if c then +e:set_sslctx(c); +end +if m>0 then n"server.lua: we need to do tls, but delaying until send buffer empty" -L=true +V=true return end -n("server.lua: attempting to start tls on "..l(t)) -local c,m=t -t,m=ye(t,g) +n("server.lua: attempting to start tls on "..u(t)) +local m,c=t +t,c=w:wrap(t) if not t then -n("server.lua: error while starting tls on client: ",l(m or"unknown error")) -return nil,m +n("server.lua: error while starting tls on client: ",u(c or"unknown error")) +return nil,c +end +if t.sni then +if f.servername then +t:sni(f.servername); +elseif next(w._sni_contexts)~=nil then +t:sni(w._sni_contexts,true); +end end t:settimeout(0) -y=t.send -_=t.receive -q=I -h[t]=e -a=u(i,t,a) -a=s(i,c,a) -o=s(r,c,o) -h[c]=nil +b=t.send +N=t.receive +z=U +s[t]=e +a=l(i,t,a) +a=h(i,m,a) +o=h(r,m,o) +s[m]=nil e.starttls=nil -L=nil -V=true +V=nil +B=true e.readbuffer=d e.sendbuffer=d return d(t) end end -e.readbuffer=p -e.sendbuffer=v -y=t.send -_=t.receive -q=(V and I)or t.shutdown -h[t]=e -a=u(i,t,a) -if g and O then +e.readbuffer=v +e.sendbuffer=p +b=t.send +N=t.receive +z=(B and U)or t.shutdown +s[t]=e +a=l(i,t,a) +if w and te and Y then n"server.lua: auto-starting ssl negotiation..." e.autostart_ssl=true; -local t,e=e:starttls(g); +local t,e=e:starttls(w); if t==false then return nil,nil,e end end return e,t end -I=function() -end -G=function() +U=function() +end +me=function() return false end -u=function(t,a,e) -if not t[a]then +l=function(a,t,e) +if not a[t]then e=e+1 -t[e]=a -t[a]=e -end -return e; -end -s=function(e,i,t) -local a=e[i] -if a then +a[e]=t +a[t]=e +end +return e; +end +h=function(e,i,t) +local o=e[i] +if o then e[i]=nil -local o=e[t] +local a=e[t] e[t]=nil -if o~=i then +if a~=i then +e[a]=o e[o]=a -e[a]=o end return t-1 end return t end Q=function(e) -o=s(r,e,o) -a=s(i,e,a) -h[e]=nil +o=h(r,e,o) +a=h(i,e,a) +s[e]=nil e:close() end -local function x(e,t,o) +local function c(e,t,o) local a; local i=t.sendbuffer; function t.sendbuffer() -i(); +i(t); if a and t.bufferlen()=0 and e<=65535)then +elseif w(t)~="number"or not(t>=0 and t<=65535)then o="invalid port" -elseif p[t..":"..e]then -o="listeners on '["..t.."]:"..e.."' already exist" -elseif r and not O then +elseif p[e..":"..t]then +o="listeners on '["..e.."]:"..t.."' already exist" +elseif r and not Y then o="luasec not found" end if o then -E("server.lua, [",t,"]:",e,": ",o) +j("server.lua, [",e,"]:",t,": ",o) return nil,o end -local o,s=te(t,e,j) -if s then -E("server.lua, [",t,"]:",e,": ",s) -return nil,s -end -local s,d=ne(d,o,t,e,l,r) -if not s then +local o,h=ue(e,t,_) +if h then +j("server.lua, [",e,"]:",t,": ",h) +return nil,h +end +local h,d=re(d,o,e,t,u,r,c) +if not h then o:close() return nil,d end o:settimeout(0) -a=u(i,o,a) -p[t..":"..e]=s -h[o]=s -n("server.lua: new "..(r and"ssl "or"").."server listener on '[",t,"]:",e,"'") -return s -end -se=function(t,e) -return p[t..":"..e]; -end -le=function(t,e) -local a=p[t..":"..e] +a=l(i,o,a) +p[e..":"..t]=h +s[o]=h +n("server.lua: new "..(r and"ssl "or"").."server listener on '[",e,"]:",t,"'") +return h +end +de=function(i,o,t,a,e) +return J(i,o,t,{ +read_size=a; +tls_ctx=e; +tls_direct=e and true or false; +}); +end +le=function(e,t) +return p[e..":"..t]; +end +se=function(e,t) +local a=p[e..":"..t] if not a then -return nil,"no server found on '["..t.."]:"..l(e).."'" +return nil,"no server found on '["..e.."]:"..u(t).."'" end a:close() -p[t..":"..e]=nil +p[e..":"..t]=nil return true end -J=function() -for e,t in b(h)do -t:close() -h[e]=nil +ee=function() +for t,e in q(s)do +e:close() +s[t]=nil end a=0 o=0 -q=0 +g=0 p={} i={} r={} -P={} -h={} -end -oe=function() +F={} +s={} +end +ne=function() return{ -select_timeout=A; -tcp_backlog=j; -max_send_buffer_size=H; -max_receive_buffer_size=S; -select_idle_check_interval=R; -send_timeout=D; +select_timeout=D; +tcp_backlog=_; +max_send_buffer_size=S; +max_receive_buffer_size=I; +select_idle_check_interval=O; +send_timeout=N; read_timeout=L; max_connections=z; -max_ssl_handshake_roundtrips=U; -highest_allowed_fd=_; -accept_retry_interval=N; +max_ssl_handshake_roundtrips=A; +highest_allowed_fd=E; +accept_retry_interval=H; } end -de=function(e) -if f(e)~="table"then +he=function(e) +if w(e)~="table"then return nil,"invalid settings table" end -A=y(e.select_timeout)or A -H=y(e.max_send_buffer_size)or H -S=y(e.max_receive_buffer_size)or S -R=y(e.select_idle_check_interval)or R -j=y(e.tcp_backlog)or j -D=y(e.send_timeout)or D -L=y(e.read_timeout)or L -N=y(e.accept_retry_interval)or N +D=b(e.select_timeout)or D +S=b(e.max_send_buffer_size)or S +I=b(e.max_receive_buffer_size)or I +O=b(e.select_idle_check_interval)or O +_=b(e.tcp_backlog)or _ +N=b(e.send_timeout)or N +L=b(e.read_timeout)or L +H=b(e.accept_retry_interval)or H z=e.max_connections or z -U=e.max_ssl_handshake_roundtrips or U -_=e.highest_allowed_fd or _ +A=e.max_ssl_handshake_roundtrips or A +E=e.highest_allowed_fd or E return true end -X=function(e) -if f(e)~="function"then +K=function(e) +if w(e)~="function"then return nil,"invalid listener function" end -q=q+1 -P[q]=e +g=g+1 +F[g]=e return true end -local l do +local u do local a={}; -local e={}; -function l(t,a) -local o=F(); -t=t+o; -if t>=o then -he(e,{t,a}); +local t={}; +function u(e,a) +local o=P(); +e=e+o; +if e>=o then +ae(t,{e,a}); else local e=a(o); -if e and f(e)=="number"then -return l(e,a); -end -end -end -X(function(t) -if#e>0 then -for o,t in b(e)do -he(a,t); -end -e={}; -end -local e=re; -for n,o in b(a)do -local i,o=o[1],o[2]; -if i<=t then -a[n]=nil; -local t=o(t); -if f(t)=="number"then -l(t,o); -e=Y(e,t); -end -else -e=Y(e,i-t); +if e and w(e)=="number"then +return u(e,a); +end +end +end +K(function(o) +if#t>0 then +for o,e in q(t)do +ae(a,e); +end +t={}; +end +local e=te; +for s,t in q(a)do +local n,i=t[1],t[2]; +if n<=o then +a[s]=nil; +local t=i(o); +if w(t)=="number"then +u(t,i); +e=W(e,t); +end +else +e=W(e,n-o); end end return e; end); end -ae=function() -return M,C,a,o,q +ce=function() +return M,C,a,o,g end local e; -local function p(t) +local function b(t) e=t; end -B=function(t) +Z=function(t) if e then return"quitting";end if t then e="once";end -d=F() +d=P() repeat -local t=re; -for e=1,q do -local e=P[e](d) -if e then t=Y(t,e);end -end -local t,a,o=ve(i,r,Y(A,t)) -for t,e in ue(t)do -local t=h[e] +local t=te; +for e=1,g do +local e=F[e](d) +if e then t=W(t,e);end +end +local t,a,o=we(i,r,W(D,t)) +for t,e in oe(t)do +local t=s[e] if t then -t.readbuffer() +t:readbuffer() else Q(e) n"server.lua: found no handler and closed socket (readlist)" end end -for t,e in ue(a)do -local t=h[e] -if t then -t.sendbuffer() -else -Q(e) +for e,t in oe(a)do +local e=s[t] +if e then +e:sendbuffer() +else +Q(t) n"server.lua: found no handler and closed socket (writelist)" end end -for e,t in b(T)do +for e,t in q(R)do e.disconnect()(e,t) e:force_close() -T[e]=nil; -end -d=F() -if d-K>R then -K=d -for e,t in b(w)do -if d-t>D then +R[e]=nil; +end +d=P() +if d-B>O then +B=d +for e,t in q(y)do +if d-t>N then e.disconnect()(e,"send timeout") e:force_close() end end -for e,t in b(v)do +for e,t in q(v)do if d-t>L then if not(e.onreadtimeout)or e:onreadtimeout()~=true then e.disconnect()(e,"read timeout") @@ -2487,153 +3411,152 @@ end end end -for e,t in b(k)do -if d-t>N then +for e,t in q(k)do +if d-t>H then k[e]=nil; e.resume(); end end until e; if e=="once"then e=nil;return;end -J(); +ee(); return"quitting" end -local function k() -return B(true); -end -local function y() +local function p() +return Z(true); +end +local function m() return"select"; end -local c=function(l,e,t,n,d,s) -local e,t,d=Z(nil,n,l,e,t,"clientport",d,s) -if not e then return nil,d end -h[t]=e -if not s then -a=u(i,t,a) -o=u(r,t,o) -if n.onconnect then -local t=e.sendbuffer; -e.sendbuffer=function() -e.sendbuffer=t; -n.onconnect(e); -return t(); -end -end -end -return e,t -end -local b=function(a,t,n,s,i,o) +local d=function(e,t,h,d,c,n,u) +local t,e,h=G(nil,d,e,t,h,"clientport",c,n,n,u) +if not t then return nil,h end +s[e]=t +if not n then +t._setpending() +a=l(i,e,a) +o=l(r,e,o) +end +return t,e +end +local g=function(o,t,i,r,n,a,s) local e -if f(n)~="table"then +if w(i)~="table"then e="invalid listener table" -elseif f(a)~="string"then +elseif w(o)~="string"then e="invalid address" -elseif f(t)~="number"or not(t>=0 and t<=65535)then +elseif w(t)~="number"or not(t>=0 and t<=65535)then e="invalid port" -elseif i and not O then +elseif n and not Y then e="luasec not found" end -if ee and not o then -local e,t=ee(a) -if not e then return nil,t end -if e[1]and e[1].family=="inet6"then -o="tcp6" -end -end -local o=g[o or"tcp"] -if f(o)~="function"then +if not a then +local e=ge(o); +if not e then return nil,"invalid-ip";end +if#e==16 then +a="tcp6"; +elseif#e==4 then +a="tcp4"; +end +end +local a=x[a]; +if w(a)~="function"then e="invalid socket type" end if e then -E("server.lua, addclient: ",e) +j("server.lua, addclient: ",e) return nil,e end -local e,o=o() -if o then -return nil,o +local e,a=a() +if a then +return nil,a end e:settimeout(0) -local h,o=e:connect(a,t) -if h or o=="timeout"or o=="Operation already in progress"then -return c(e,a,t,n,s,i) -else -return nil,o -end -end -local g=function(e) +local h,a=e:setpeername(o,t) +if h or a=="timeout"or a=="Operation already in progress"then +return d(e,o,t,i,r,n,s) +else +return nil,a +end +end +local k=function(e) local e=e.conn; -o=s(r,e,o) -a=s(i,e,a) -h[e]=nil +o=h(r,e,o) +a=h(i,e,a) +s[e]=nil end; -local t=function(n,t,d) -local e=n.conn -h[e]=n +local i=function(d,n,t) +local e=d.conn +s[e]=d +if n~=nil then +if n then +a=l(i,e,a) +else +o=h(r,e,o) +end +end if t~=nil then if t then -a=u(i,e,a) -else -o=s(r,e,o) -end -end -if d~=nil then -if d then -o=u(r,e,o) -else -a=s(i,e,a) +o=l(r,e,o) +else +a=h(i,e,a) end end end local t=function(e,o,a) -local i=e -if f(e)=="number"then -i={getfd=function()return e;end} +local t=e +if w(e)=="number"then +t={getfd=function()return e;end} end local e={ -conn=i; -readbuffer=o or I; -sendbuffer=a or I; -close=g; -setflags=t; -}; -t(e,o,a) +conn=t; +readbuffer=o or U; +sendbuffer=a or U; +close=k; +setflags=i; +}; +i(e,o,a) return e end -m"setmetatable"(h,{__mode="k"}) -m"setmetatable"(v,{__mode="k"}) -m"setmetatable"(w,{__mode="k"}) -K=F() +f"setmetatable"(s,{__mode="k"}) +f"setmetatable"(v,{__mode="k"}) +f"setmetatable"(y,{__mode="k"}) +B=P() local function a(e) -local t=W; +local t=V; if e then -W=e; +V=e; end return t; end return{ -_addtimer=X, -add_task=l; -addclient=b, -wrapclient=c, +_addtimer=K, +add_task=u; +addclient=g, +wrapclient=d, watchfd=t, -loop=B, -link=x, -step=k, -stats=ae, -closeall=J, -addserver=ie, -getserver=se, +loop=Z, +link=c, +step=p, +stats=ce, +closeall=ee, +addserver=de, +listen=J, +getserver=le, setlogger=a, -getsettings=oe, -setquitting=p, -removeserver=le, -get_backend=y, -changesettings=de, +getsettings=ne, +setquitting=b, +removeserver=se, +get_backend=m, +changesettings=he, +tls_builder=function(e) +return ve._new(fe.new_context,e) +end, } end) package.preload['util.xmppstream']=(function(...) local _ENV=_ENV; -local function o(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -2644,67 +3567,69 @@ return e; end local e=require"lxp"; -local t=require"util.stanza"; -local p=t.stanza_mt; -local f=error; +local g=require"util.stanza"; +local y=g.stanza_mt; +local w=error; local t=tostring; -local l=table.insert; -local w=table.concat; -local T=table.remove; -local y=setmetatable; -local x=pcall(e.new,{StartDoctypeDecl=false}); -local z=pcall(e.new,{XmlDecl=false}); +local d=table.insert; +local b=table.concat; +local E=table.remove; +local p=setmetatable; +local T=pcall(e.new,{StartDoctypeDecl=false}); +local _=pcall(e.new,{XmlDecl=false}); local a=not not e.new({}).getcurrentbytecount; -local _=1024*1024*10; -o"xmppstream" -local k=e.new; -local E={ +local x=1024*1024*1; +local _ENV=nil; +local v=e.new; +local j={ ["http://www.w3.org/XML/1998/namespace\1lang"]="xml:lang"; ["http://www.w3.org/XML/1998/namespace\1space"]="xml:space"; ["http://www.w3.org/XML/1998/namespace\1base"]="xml:base"; ["http://www.w3.org/XML/1998/namespace\1id"]="xml:id"; }; -local o="http://etherx.jabber.org/streams"; -local d="\1"; -local b="^([^"..d.."]*)"..d.."?(.*)$"; -_M.ns_separator=d; -_M.ns_pattern=b; -local function s()end -function new_sax_handlers(n,e,h) +local h="http://etherx.jabber.org/streams"; +local r="\1"; +local k="^([^"..r.."]*)"..r.."?(.*)$"; +local function o()end +local function q(n,e,s) local i={}; local g=e.streamopened; local v=e.streamclosed; -local u=e.error or function(o,a,e)f("XML stream error: "..t(a)..(e and": "..t(e)or""),2);end; -local k=e.handlestanza; -h=h or s; -local t=e.stream_ns or o; +local l=e.error or +function(o,a,e) +w("XML stream error: "..t(a)..(e and": "..t(e)or""),2); +end; +local x=e.handlestanza; +s=s or o; +local t=e.stream_ns or h; local m=e.stream_tag or"stream"; if t~=""then -m=t..d..m; -end -local q=t..d..(e.error_tag or"error"); -local j=e.default_ns; -local d={}; -local s,e={}; +m=t..r..m; +end +local z=t..r..(e.error_tag or"error"); +local q=e.default_ns; +local f="en"; +local u={}; +local h,e={}; local t=0; local r=0; function i:StartElement(c,o) -if e and#s>0 then -l(e,w(s)); -s={}; -end -local s,i=c:match(b); +if e and#h>0 then +d(e,b(h)); +h={}; +end +local h,i=c:match(k); if i==""then -s,i="",s; -end -if s~=j or r>0 then -o.xmlns=s; +h,i="",h; +end +if h~=q or r>0 then +o.xmlns=h; r=r+1; end for t=1,#o do local e=o[t]; o[t]=nil; -local t=E[e]; +local t=j[e]; if t then o[t]=o[e]; o[e]=nil; @@ -2717,38 +3642,32 @@ if n.notopen then if c==m then r=0; +f=o["xml:lang"]or f; if g then if a then -h(t); +s(t); t=0; end g(n,o); end else -u(n,"no-stream",c); +l(n,"no-stream",c); end return; end -if s=="jabber:client"and i~="iq"and i~="presence"and i~="message"then -u(n,"invalid-top-level-element"); -end -e=y({name=i,attr=o,tags={}},p); +if h=="jabber:client"and i~="iq"and i~="presence"and i~="message"then +l(n,"invalid-top-level-element"); +end +e=p({name=i,attr=o,tags={}},y); else if a then t=t+self:getcurrentbytecount(); end -l(d,e); +d(u,e); local t=e; -e=y({name=i,attr=o,tags={}},p); -l(t,e); -l(t.tags,e); -end -end -if z then -function i:XmlDecl(e,e,e) -if a then -h(self:getcurrentbytecount()); -end +e=p({name=i,attr=o,tags={}},y); +d(t,e); +d(t.tags,e); end end function i:StartCdataSection() @@ -2756,7 +3675,7 @@ if e then t=t+self:getcurrentbytecount(); else -h(self:getcurrentbytecount()); +s(self:getcurrentbytecount()); end end end @@ -2765,7 +3684,7 @@ if e then t=t+self:getcurrentbytecount(); else -h(self:getcurrentbytecount()); +s(self:getcurrentbytecount()); end end end @@ -2774,9 +3693,9 @@ if a then t=t+self:getcurrentbytecount(); end -l(s,o); +d(h,o); elseif a then -h(self:getcurrentbytecount()); +s(self:getcurrentbytecount()); end end function i:EndElement(o) @@ -2787,89 +3706,136 @@ r=r-1; end if e then -if#s>0 then -l(e,w(s)); -s={}; -end -if#d==0 then +if#h>0 then +d(e,b(h)); +h={}; +end +if#u==0 then if a then -h(t); +s(t); end t=0; -if o~=q then -k(n,e); -else -u(n,"stream-error",e); +if e.attr["xml:lang"]==nil then +e.attr["xml:lang"]=f; +end +if o~=z then +x(n,e); +else +l(n,"stream-error",e); end e=nil; else -e=T(d); -end -else +e=E(u); +end +else +if a then +s(t); +end if v then v(n); end end end -local function a(e) -u(n,"parse-error","restricted-xml","Restricted XML, see RFC 6120 section 11.1."); +local function o(e) +l(n,"parse-error","restricted-xml","Restricted XML, see RFC 6120 section 11.1."); if not e.stop or not e:stop()then -f("Failed to abort parsing"); -end -end -if x then -i.StartDoctypeDecl=a; -end -i.Comment=a; -i.ProcessingInstruction=a; +w("Failed to abort parsing"); +end +end +if _ then +function i:XmlDecl(e,t,i) +if a then +s(self:getcurrentbytecount()); +end +if(t and t:lower()~="utf-8") +or(i=="no") +or(e and e~="1.0")then +return o(self); +end +end +end +if T then +i.StartDoctypeDecl=o; +end +i.Comment=o; +i.ProcessingInstruction=o; local function a() -e,s,t=nil,{},0; -d={}; +e,h,t=nil,{},0; +u={}; end local function e(t,e) n=e; end return i,{reset=a,set_session=e}; end -function new(n,i,o) -local e=0; -local t; +local function l(e,h,o) +local t=0; +local i; if a then -function t(t) -e=e-t; -end -o=o or _; +function i(e) +t=t-e; +end +o=o or x; elseif o then -f("Stanza size limits are not supported on this version of LuaExpat") -end -local i,n=new_sax_handlers(n,i,t); -local t=k(i,d,false); -local s=t.parse; +w("Stanza size limits are not supported on this version of LuaExpat") +end +local n,d=q(e,h,i); +local i=v(n,r,false); +local s=i.parse; +function e.open_stream(e,o,a) +local i=e.sends2s or e.send; +local t={ +["xmlns:stream"]="http://etherx.jabber.org/streams", +["xml:lang"]="en", +xmlns=h.default_ns, +version=e.version and(e.version>0 and"1.0"or nil), +id=e.streamid or"", +from=o or e.host,to=a, +}; +if e.stream_attrs then +e:stream_attrs(o,a,t) +end +i(""..g.stanza("stream:stream",t):top_tag()); +return true; +end return{ reset=function() -t=k(i,d,false); -s=t.parse; -e=0; -n.reset(); +i=v(n,r,false); +s=i.parse; +t=0; +d.reset(); end, -feed=function(n,i) +feed=function(e,n) if a then -e=e+#i; -end -local i,t=s(t,i); -if a and e>o then +t=t+#n; +end +local e=i; +local n,s=s(e,n); +if a and t>o then return nil,"stanza-too-large"; end -return i,t; +if i~=e then +e:parse(); +e:close(); +end +return n,s; end, -set_session=n.set_session; -}; -end -return _M; +set_session=d.set_session; +set_stanza_size_limit=function(t,e) +o=e; +end; +}; +end +return{ +ns_separator=r; +ns_pattern=k; +new_sax_handlers=q; +new=l; +}; end) package.preload['util.jid']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -2879,98 +3845,112 @@ _M=e; return e; end -local t,s=string.match,string.sub; -local l=require"util.encodings".stringprep.nodeprep; -local d=require"util.encodings".stringprep.nameprep; -local r=require"util.encodings".stringprep.resourceprep; -local n={ +local i=select; +local a,h=string.match,string.sub; +local u=require"util.encodings".stringprep.nodeprep; +local l=require"util.encodings".stringprep.nameprep; +local d=require"util.encodings".stringprep.resourceprep; +local o={ [" "]="\\20";['"']="\\22"; ["&"]="\\26";["'"]="\\27"; ["/"]="\\2f";[":"]="\\3a"; ["<"]="\\3c";[">"]="\\3e"; ["@"]="\\40";["\\"]="\\5c"; }; -local i={}; -for e,t in pairs(n)do i[t]=e;end -a"jid" -local function o(e) -if not e then return;end -local i,a=t(e,"^([^@/]+)@()"); -local a,o=t(e,"^([^@/]+)()",a) -if i and not a then return nil,nil,nil;end -local t=t(e,"^/(.+)$",o); -if(not a)or((not t)and#e>=o)then return nil,nil,nil;end -return i,a,t; -end -split=o; -function bare(e) -local t,e=o(e); -if t and e then +local n={}; +local s={}; +for t,e in pairs(o)do +n[e]=t; +s[e]=e:gsub("\\",o) +end +local _ENV=nil; +local function t(e) +if e==nil then return;end +local i,t=a(e,"^([^@/]+)@()"); +local t,o=a(e,"^([^@/]+)()",t); +local a=t and a(e,"^/(.+)$",o); +if(t==nil)or((a==nil)and#e>=o)then return nil,nil,nil;end +return i,t,a; +end +local function m(e) +local t,e=t(e); +if t~=nil and e~=nil then return t.."@"..e; end return e; end -local function h(e) -local a,e,t=o(e); -if e then -if s(e,-1,-1)=="."then -e=s(e,1,-2); -end -e=d(e); -if not e then return;end -if a then -a=l(a); -if not a then return;end -end -if t then -t=r(t); -if not t then return;end +local function r(e,o) +local a,e,t=t(e); +if e~=nil and e~="."then +if h(e,-1,-1)=="."then +e=h(e,1,-2); +end +e=l(e,o); +if e==nil then return;end +if a~=nil then +a=u(a,o); +if a==nil then return;end +end +if t~=nil then +t=d(t,o); +if t==nil then return;end end return a,e,t; end end -prepped_split=h; -function prep(e) -local a,e,t=h(e); -if e then -if a then -e=a.."@"..e; -end -if t then -e=e.."/"..t; -end -end -return e; -end -function join(a,e,t) -if a and e and t then -return a.."@"..e.."/"..t; -elseif a and e then -return a.."@"..e; -elseif e and t then -return e.."/"..t; -elseif e then -return e; -end -return nil; -end -function compare(t,e) -local n,i,s=o(t); -local a,t,e=o(e); -if((a~=nil and a==n)or a==nil)and -((t~=nil and t==i)or t==nil)and -((e~=nil and e==s)or e==nil)then +local function h(t,e,a) +if e==nil then return end +if t~=nil and a~=nil then +return t.."@"..e.."/"..a; +elseif t~=nil then +return t.."@"..e; +elseif a~=nil then +return e.."/"..a; +end +return e; +end +local function c(e,t) +local e,a,t=r(e,t); +return h(e,a,t); +end +local function f(a,e) +local i,n,o=t(a); +local a,t,e=t(e); +if(a==nil or a==i)and +(t==nil or t==n)and +(e==nil or e==o)then return true end return false end -function escape(e)return e and(e:gsub(".",n));end -function unescape(e)return e and(e:gsub("\\%x%x",i));end -return _M; +local function u(e) +return(i(1,t(e))); +end +local function l(e) +return(i(2,t(e))); +end +local function d(e) +return(i(3,t(e))); +end +local function a(e)return e and(e:gsub("\\%x%x",s):gsub("[\"&'/:<>@ ]",o));end +local function o(e)return e and(e:gsub("\\%x%x",n));end +return{ +split=t; +bare=m; +prepped_split=r; +join=h; +prep=c; +compare=f; +node=u; +host=l; +resource=d; +escape=a; +unescape=o; +}; end) package.preload['util.events']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -2980,81 +3960,166 @@ _M=e; return e; end -local i=pairs; -local r=table.insert; -local s=table.sort; -local h=setmetatable; -local n=next; -a"events" -function new() +local a=pairs; +local d=table.insert; +local w=table.remove; +local u=table.sort; +local l=setmetatable; +local h=next; +local _ENV=nil; +local function f() +local o={}; +local t; +local n={}; +local i={}; +local s=nil; +local function r(n,o) +local e=i[o]; +if not e or h(e)==nil then return;end local t={}; -local e={}; -local function o(o,a) -local e=e[a]; -if not e or n(e)==nil then return;end -local t={}; -for e in i(e)do -r(t,e); -end -s(t,function(a,t)return e[a]>e[t];end); -o[a]=t; +for e in a(e)do +d(t,e); +end +u(t,function(a,t)return e[a]>e[t];end); +n[o]=t; return t; end; -h(t,{__index=o}); -local function h(o,i,n) -local a=e[o]; -if a then -a[i]=n or 0; -else -a={[i]=n or 0}; -e[o]=a; -end -t[o]=nil; +l(o,{__index=r}); +local function l(t,n,a) +local e=i[t]; +if e then +e[n]=a or 0; +else +e={[n]=a or 0}; +i[t]=e; +end +o[t]=nil; end; -local function s(o,i) -local a=e[o]; -if a then -a[i]=nil; -t[o]=nil; -if n(a)==nil then -e[o]=nil; +local function d(e,a) +local t=i[e]; +if t then +t[a]=nil; +o[e]=nil; +if h(t)==nil then +i[e]=nil; end end end; -local function n(e) -for t,e in i(e)do -h(t,e); +local function c(e) +return o[e]; +end; +local function u(e) +for e,t in a(e)do +l(e,t); end end; -local function a(e) -for t,e in i(e)do -s(t,e); +local function m(e) +for t,e in a(e)do +d(t,e); end end; -local function o(e,...) -local e=t[e]; -if e then -for t=1,#e do -local e=e[t](...); +local function r(a,t) +local e=o[a]; +if e and not s then +for a=1,#e do +local e=e[a](t); +if e~=nil then return e;end +end +elseif e and s then +for o=1,#e do +local e=s(e[o],a,t); if e~=nil then return e;end end end end; +local function f(s,h) +local e=n[s]or t; +if e then +local a=#e; +local function i(o,n) +a=a-1; +if a==0 then +if t==nil or e==t then +return r(o,n); +end +e,a=t,#t; +return e[a](i,o,n); +else +return e[a](i,o,n); +end +end +return e[a](i,s,h); +end +return r(s,h); +end +local function r(a,o) +local e; +if a==false then +e=t; +if not e then +e={}; +t=e; +end +else +e=n[a]; +if not e then +e={}; +n[a]=e; +end +end +e[#e+1]=o; +end +local function h(a,o) +local e; +if a==false then +e=t; +else +e=n[a]; +end +if not e then return;end +for t=#e,1,-1 do +if e[t]==o then +w(e,t); +end +end +if#e==0 then +if a==false then +t=nil; +else +n[a]=nil; +end +end +end +local function a(t) +local e=s; +s=t; +return e; +end return{ -add_handler=h; -remove_handler=s; -add_handlers=n; -remove_handlers=a; -fire_event=o; -_handlers=t; -_event_map=e; -}; -end -return _M; +add_handler=l; +remove_handler=d; +add_handlers=u; +remove_handlers=m; +get_handlers=c; +wrappers={ +add_handler=r; +remove_handler=h; +}; +add_wrapper=r; +remove_wrapper=h; +set_debug_hook=a; +fire_event=f; +_handlers=o; +_event_map=i; +}; +end +return{ +new=f; +}; end) package.preload['util.dataforms']=(function(...) local _ENV=_ENV; -local function o(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -3064,237 +4129,336 @@ _M=e; return e; end -local a=setmetatable; -local e,i=pairs,ipairs; -local h,r,c=tostring,type,next; -local s=table.concat; -local u=require"util.stanza"; -local d=require"util.jid".prep; -o"dataforms" -local l='jabber:x:data'; -local n={}; -local e={__index=n}; -function new(t) -return a(t,e); -end -function from_stanza(e) +local e=setmetatable; +local n=ipairs; +local s,p=type,next; +local u=tonumber; +local r=tostring; +local l=table.concat; +local c=require"util.stanza"; +local m=require"util.jid".prep; +local _ENV=nil; +local y='jabber:x:data'; +local f='http://jabber.org/protocol/xdata-validate'; +local d={}; +local t={__index=d}; +local function w(a) +return e(a,t); +end +local function v(e) local o={ title=e:get_child_text("title"); instructions=e:get_child_text("instructions"); }; for t in e:childtags("field")do -local a={ +local e={ name=t.attr.var; label=t.attr.label; type=t.attr.type; required=t:get_child("required")and true or nil; value=t:get_child_text("value"); }; -o[#o+1]=a; -if a.type then -local e={}; -if a.type:match"list%-"then -for t in t:childtags("option")do -e[#e+1]={label=t.attr.label,value=t:get_child_text("value")}; -end -for t in t:childtags("value")do -e[#e+1]={label=t.attr.label,value=t:get_text(),default=true}; -end -elseif a.type:match"%-multi"then -for t in t:childtags("value")do -e[#e+1]=t.attr.label and{label=t.attr.label,value=t:get_text()}or t:get_text(); -end -if a.type=="text-multi"then -a.value=s(e,"\n"); -else -a.value=e; -end -end -end -end -return new(o); -end -function n.form(t,n,e) -local e=u.stanza("x",{xmlns=l,type=e or"form"}); +o[#o+1]=e; +if e.type then +local a={}; +if e.type:match"list%-"then +for e in t:childtags("option")do +a[#a+1]={label=e.attr.label,value=e:get_child_text("value")}; +end +for e in t:childtags("value")do +a[#a+1]={label=e.attr.label,value=e:get_text(),default=true}; +end +elseif e.type:match"%-multi"then +for e in t:childtags("value")do +a[#a+1]=e.attr.label and{label=e.attr.label,value=e:get_text()}or e:get_text(); +end +if e.type=="text-multi"then +e.value=l(a,"\n"); +else +e.value=a; +end +end +end +local t=t:get_child("validate",f); +if t then +e.datatype=datatype.attr.datatype; +local t=t:get_child("range"); +if t then +e.range_min=u(t.attr.min); +e.range_max=u(t.attr.max); +end +end +end +return w(o); +end +function d.form(t,h,i) +if not i then i="form"end +local e=c.stanza("x",{xmlns=y,type=i}); +if i=="cancel"then +return e; +end +if i~="submit"then if t.title then e:tag("title"):text(t.title):up(); end if t.instructions then e:tag("instructions"):text(t.instructions):up(); end -for t,o in i(t)do -local a=o.type or"text-single"; -e:tag("field",{type=a,var=o.name,label=o.label}); -local t=(n and n[o.name])or o.value; -if t then -if a=="hidden"then -if r(t)=="table"then +end +for t,a in n(t)do +local o=a.type or"text-single"; +e:tag("field",{type=o,var=a.var or a.name,label=i~="submit"and a.label or nil}); +if i~="submit"then +if a.desc then +e:text_tag("desc",a.desc); +end +end +if i=="form"and a.datatype then +e:tag("validate",{xmlns=f,datatype=a.datatype}); +if a.range_min or a.range_max then +e:tag("range",{ +min=a.range_min and r(a.range_min), +max=a.range_max and r(a.range_max), +}):up(); +end +e:up(); +end +local t=a.value; +local r=a.options; +if h and h[a.name]~=nil then +t=h[a.name]; +if i=="form"and s(t)=="table" +and(o=="list-single"or o=="list-multi")then +r,t=t,nil; +end +end +if i=="form"and r then +local a={}; +for o,t in n(r)do +if s(t)=="table"then +e:tag("option",{label=t.label}):tag("value"):text(t.value):up():up(); +if t.default then +a[#a+1]=t.value; +end +else +e:tag("option",{label=t}):tag("value"):text(t):up():up(); +end +end +if not t then +if o=="list-single"then +t=a[1]; +elseif o=="list-multi"then +t=a; +end +end +end +if t~=nil then +if s(t)=="number"then +t=("%g"):format(t); +end +if o=="hidden"then +if s(t)=="table"then e:tag("value") :add_child(t) :up(); else -e:tag("value"):text(h(t)):up(); -end -elseif a=="boolean"then +e:tag("value"):text(t):up(); +end +elseif o=="boolean"then e:tag("value"):text((t and"1")or"0"):up(); -elseif a=="fixed"then -elseif a=="jid-multi"then -for a,t in i(t)do +elseif o=="fixed"then +e:tag("value"):text(t):up(); +elseif o=="jid-multi"then +for a,t in n(t)do e:tag("value"):text(t):up(); end -elseif a=="jid-single"then +elseif o=="jid-single"then e:tag("value"):text(t):up(); -elseif a=="text-single"or a=="text-private"then +elseif o=="text-single"or o=="text-private"then e:tag("value"):text(t):up(); -elseif a=="text-multi"then +elseif o=="text-multi"then for t in t:gmatch("([^\r\n]+)\r?\n*")do e:tag("value"):text(t):up(); end -elseif a=="list-single"then -local a=false; -for o,t in i(t)do -if r(t)=="table"then -e:tag("option",{label=t.label}):tag("value"):text(t.value):up():up(); -if t.default and(not a)then -e:tag("value"):text(t.value):up(); -a=true; -end -else -e:tag("option",{label=t}):tag("value"):text(h(t)):up():up(); -end -end -elseif a=="list-multi"then -for a,t in i(t)do -if r(t)=="table"then -e:tag("option",{label=t.label}):tag("value"):text(t.value):up():up(); -if t.default then -e:tag("value"):text(t.value):up(); -end -else -e:tag("option",{label=t}):tag("value"):text(h(t)):up():up(); -end -end -end -end -if o.required then +elseif o=="list-single"then +e:tag("value"):text(t):up(); +elseif o=="list-multi"then +for a,t in n(t)do +e:tag("value"):text(t):up(); +end +end +end +local t=a.media; +if t then +e:tag("media",{xmlns="urn:xmpp:media-element",height=("%g"):format(t.height),width=("%g"):format(t.width)}); +for a,t in n(t)do +e:tag("uri",{type=t.type}):text(t.uri):up() +end +e:up(); +end +if i=="form"and a.required then e:tag("required"):up(); end e:up(); end return e; end -local e={}; -function n.data(t,n) +local t={}; +local h={}; +function d.data(e,r,i) local o={}; local a={}; -for i,t in i(t)do -local i; -for e in n:childtags()do -if t.name==e.attr.var then -i=e; +local s={}; +for n,e in n(e)do +local n; +for t in r:childtags("field")do +if(e.var or e.name)==t.attr.var then +n=t; break; end end -if not i then -if t.required then -a[t.name]="Required value missing"; -end -else -local e=e[t.type]; -if e then -o[t.name],a[t.name]=e(i,t.required); -end -end -end -if c(a)then -return o,a; -end -return o; -end -e["text-single"]= -function(t,a) -local t=t:get_child_text("value"); -if t and#t>0 then -return t -elseif a then +if not n then +if i and i[e.name]~=nil then +o[e.name]=i[e.name]; +elseif e.required then +a[e.name]="Required value missing"; +end +elseif e.name then +s[e.name]=true; +local t=t[e.type]; +if t then +local t,i=t(n,e.required); +local n=e.datatype and h[e.datatype]; +if t~=nil and n then +local o,a=n(t,e); +if o then +t=a; +else +t,i=nil,a or("Invalid value for data of type "..e.datatype); +end +end +o[e.name],a[e.name]=t,i; +end +end +end +if p(a)then +return o,a,s; +end +return o,nil,s; +end +local function e(e,a) +local e=e:get_child_text("value"); +if a and(e==nil or e=="")then return nil,"Required value missing"; end -end -e["text-private"]= -e["text-single"]; -e["jid-single"]= -function(t,o) -local t=t:get_child_text("value") -local a=d(t); -if a and#a>0 then -return a -elseif t then -return nil,"Invalid JID: "..t; -elseif o then -return nil,"Required value missing"; -end -end -e["jid-multi"]= +return e; +end +t["text-single"]=e; +t["text-private"]=e; +t["jid-single"]= +function(o,a) +local e,a=e(o,a); +if not e then return e,a;end +local a=m(e); +if not a then +return nil,"Invalid JID: "..e; +end +return a; +end +t["jid-multi"]= function(o,i) local a={}; -local t={}; -for e in o:childtags("value")do -local e=e:get_text(); -local o=d(e); +local e={}; +for t in o:childtags("value")do +local t=t:get_text(); +local o=m(t); a[#a+1]=o; -if e and not o then -t[#t+1]=("Invalid JID: "..e); +if t and not o then +e[#e+1]=("Invalid JID: "..t); end end if#a>0 then -return a,(#t>0 and s(t,"\n")or nil); +return a,(#e>0 and l(e,"\n")or nil); elseif i then return nil,"Required value missing"; end end -e["list-multi"]= +t["list-multi"]= function(o,a) -local t={}; -for e in o:childtags("value")do -t[#t+1]=e:get_text(); -end -return t,(a and#t==0 and"Required value missing"or nil); -end -e["text-multi"]= -function(t,a) -local t,a=e["list-multi"](t,a); -if t then -t=s(t,"\n"); -end -return t,a; -end -e["list-single"]= -e["text-single"]; +local e={}; +for t in o:childtags("value")do +e[#e+1]=t:get_text(); +end +if#e>0 then +return e; +elseif a then +return nil,"Required value missing"; +end +end +t["text-multi"]= +function(a,e) +local e,a=t["list-multi"](a,e); +if e then +e=l(e,"\n"); +end +return e,a; +end +t["list-single"]=e; local a={ ["1"]=true,["true"]=true, ["0"]=false,["false"]=false, }; -e["boolean"]= -function(t,o) -local t=t:get_child_text("value"); -local a=a[t~=nil and t]; -if a~=nil then +t["boolean"]= +function(o,i) +local e,o=e(o,i); +if not e then return e,o;end +local a=a[e]; +if a==nil then +return nil,"Invalid boolean representation:"..e; +end return a; -elseif t then -return nil,"Invalid boolean representation"; -elseif o then -return nil,"Required value missing"; -end -end -e["hidden"]= +end +t["hidden"]= function(e) return e:get_child_text("value"); end -return _M; +h["xs:integer"]= +function(e,t) +local e=u(e); +if not e then +return false,"not a number"; +elseif e%1~=0 then +return false,"not an integer"; +end +if t.range_max and e>t.range_max then +return false,"out of bounds"; +elseif t.range_min and e0 then a=h(a,"<"):gsub("%z","/").."<";else a="";end +if#i>0 then i=h(i,"<"):gsub("%z","/").."<";else i="";end if#o>0 then o=h(o,"<").."<";else o="";end -if#i>0 then i=h(i,"<"):gsub("%z","<").."<";else i="";end -local e=a..o..i; -local t=l(d(e)); -print(("CAPS %q %q"):format(t,e)); +if#a>0 then a=h(a,"<"):gsub("%z","<").."<";else a="";end +local e=i..o..a; +local t=l(u(e)); return t,e; end -return _M; +return{ +calculate_hash=d; +}; end) package.preload['util.vcard']=(function(...) local _ENV=_ENV; @@ -3367,23 +4532,23 @@ _M=e; return e; end -local i=require"util.stanza"; -local a,h=table.insert,table.concat; -local s=type; -local e,n,c=next,pairs,ipairs; -local d,r,l,u; -local m="\n"; -local o; +local o=require"util.stanza"; +local a,c=table.insert,table.concat; +local h=type; +local e,s,m=next,pairs,ipairs; +local u,l,d,r; +local f="\n"; +local i; local function e() error"Not implemented" end local function e() error"Not implemented" end -local function y(e) +local function w(e) return e:gsub("[,:;\\]","\\%1"):gsub("\n","\\n"); end -local function p(e) +local function y(e) return e:gsub("\\?[\\nt:;,]",{ ["\\\\"]="\\", ["\\n"]="\n", @@ -3397,104 +4562,104 @@ [","]="\31", }); end -local function w(e) -local a=i.stanza(e.name,{xmlns="vcard-temp"}); -local t=o[e.name]; -if t=="text"then -a:text(e[1]); -elseif s(t)=="table"then -if t.types and e.TYPE then -if s(e.TYPE)=="table"then -for o,t in n(t.types)do -for o,e in n(e.TYPE)do -if e:upper()==t then -a:tag(t):up(); +local function p(t) +local a=o.stanza(t.name,{xmlns="vcard-temp"}); +local e=i[t.name]; +if e=="text"then +a:text(t[1]); +elseif h(e)=="table"then +if e.types and t.TYPE then +if h(t.TYPE)=="table"then +for o,e in s(e.types)do +for o,t in s(t.TYPE)do +if t:upper()==e then +a:tag(e):up(); break; end end end else -a:tag(e.TYPE:upper()):up(); -end -end -if t.props then -for o,t in n(t.props)do -if e[t]then -a:tag(t):up(); -end -end -end -if t.value then -a:tag(t.value):text(e[1]):up(); -elseif t.values then -local o=t.values; +a:tag(t.TYPE:upper()):up(); +end +end +if e.props then +for o,e in s(e.props)do +if t[e]then +a:tag(e):up(); +end +end +end +if e.value then +a:tag(e.value):text(t[1]):up(); +elseif e.values then +local o=e.values; local i=o.behaviour=="repeat-last"and o[#o]; -for o=1,#e do -a:tag(t.values[o]or i):text(e[o]):up(); +for o=1,#t do +a:tag(e.values[o]or i):text(t[o]):up(); end end end return a; end -local function f(t) -local e=i.stanza("vCard",{xmlns="vcard-temp"}); +local function n(t) +local e=o.stanza("vCard",{xmlns="vcard-temp"}); for a=1,#t do -e:add_child(w(t[a])); -end -return e; -end -function u(e) +e:add_child(p(t[a])); +end +return e; +end +function r(e) if not e[1]or e[1].name then -return f(e) -else -local t=i.stanza("xCard",{xmlns="vcard-temp"}); +return n(e) +else +local t=o.stanza("xCard",{xmlns="vcard-temp"}); for a=1,#e do -t:add_child(f(e[a])); +t:add_child(n(e[a])); end return t; end end -function d(t) +function u(t) t=t :gsub("\r\n","\n") :gsub("\n ","") :gsub("\n\n+","\n"); -local s={}; +local h={}; local e; for t in t:gmatch("[^\n]+")do -local t=p(t); -local n,t,i=t:match("^([-%a]+)(\30?[^\29]*)\29(.*)$"); -i=i:gsub("\29",":"); +local t=y(t); +local s,t,n=t:match("^([-%a]+)(\30?[^\29]*)\29(.*)$"); +n=n:gsub("\29",":"); if#t>0 then -local o={}; -for a,n,i in t:gmatch("\30([^=]+)(=?)([^\30]*)")do -a=a:upper(); -local e={}; -for t in i:gmatch("[^\31]+")do -e[#e+1]=t -e[t]=true; -end -if n=="="then -o[a]=e; -else -o[a]=true; -end -end -t=o; -end -if n=="BEGIN"and i=="VCARD"then +local a={}; +for e,o,i in t:gmatch("\30([^=]+)(=?)([^\30]*)")do +e=e:upper(); +local t={}; +for e in i:gmatch("[^\31]+")do +t[#t+1]=e +t[e]=true; +end +if o=="="then +a[e]=t; +else +a[e]=true; +end +end +t=a; +end +if s=="BEGIN"and n=="VCARD"then e={}; -s[#s+1]=e; -elseif n=="END"and i=="VCARD"then +h[#h+1]=e; +elseif s=="END"and n=="VCARD"then e=nil; -elseif e and o[n]then -local o=o[n]; -local n={name=n}; -e[#e+1]=n; +elseif e and i[s]then +local o=i[s]; +local i={name=s}; +e[#e+1]=i; local s=e; -e=n; +e=i; if o.types then -for o,a in c(o.types)do +for o,a in m(o.types)do local a=a:lower(); if(t.TYPE and t.TYPE[a]==true) or t[a]==true then @@ -3503,12 +4668,12 @@ end end if o.props then -for o,a in c(o.props)do +for o,a in m(o.props)do if t[a]then if t[a]==true then e[a]=true; else -for o,t in c(t[a])do +for o,t in m(t[a])do e[a]=t; end end @@ -3516,9 +4681,9 @@ end end if o=="text"or o.value then -a(e,i); +a(e,n); elseif o.values then -local t="\30"..i; +local t="\30"..n; for t in t:gmatch("\30([^\30]*)")do a(e,t); end @@ -3526,113 +4691,113 @@ e=s; end end -return s; -end -local function i(t) +return h; +end +local function n(t) local e={}; for a=1,#t do -e[a]=y(t[a]); -end -e=h(e,";"); +e[a]=w(t[a]); +end +e=c(e,";"); local a=""; -for t,e in n(t)do -if s(t)=="string"and t~="name"then -a=a..(";%s=%s"):format(t,s(e)=="table"and h(e,",")or e); +for t,e in s(t)do +if h(t)=="string"and t~="name"then +a=a..(";%s=%s"):format(t,h(e)=="table"and c(e,",")or e); end end return("%s%s:%s"):format(t.name,a,e) end -local function t(t) +local function o(t) local e={}; a(e,"BEGIN:VCARD") for o=1,#t do -a(e,i(t[o])); +a(e,n(t[o])); end a(e,"END:VCARD") -return h(e,m); -end -function r(e) +return c(e,f); +end +function l(e) if e[1]and e[1].name then -return t(e) -else -local o={}; +return o(e) +else +local t={}; for a=1,#e do -o[a]=t(e[a]); -end -return h(o,m); -end -end -local function h(i) -local t=i.name; -local e=o[t]; -local t={name=t}; -if e=="text"then -t[1]=i:get_text(); -elseif s(e)=="table"then -if e.value then -t[1]=i:get_child_text(e.value)or""; -elseif e.values then -local e=e.values; -if e.behaviour=="repeat-last"then -for e=1,#i.tags do -a(t,i.tags[e]:get_text()or""); -end -else -for o=1,#e do -a(t,i:get_child_text(e[o])or""); -end -end -elseif e.names then -local e=e.names; -for a=1,#e do -if i:get_child(e[a])then -t[1]=e[a]; +t[a]=o(e[a]); +end +return c(t,f); +end +end +local function n(o) +local e=o.name; +local t=i[e]; +local e={name=e}; +if t=="text"then +e[1]=o:get_text(); +elseif h(t)=="table"then +if t.value then +e[1]=o:get_child_text(t.value)or""; +elseif t.values then +local t=t.values; +if t.behaviour=="repeat-last"then +for t=1,#o.tags do +a(e,o.tags[t]:get_text()or""); +end +else +for i=1,#t do +a(e,o:get_child_text(t[i])or""); +end +end +elseif t.names then +local t=t.names; +for a=1,#t do +if o:get_child(t[a])then +e[1]=t[a]; break; end end end -if e.props_verbatim then -for a,e in n(e.props_verbatim)do -t[a]=e; -end -end -if e.types then -local e=e.types; -t.TYPE={}; -for o=1,#e do -if i:get_child(e[o])then -a(t.TYPE,e[o]:lower()); -end -end -if#t.TYPE==0 then -t.TYPE=nil; -end -end -if e.props then -local e=e.props; -for o=1,#e do -local e=e[o] -local o=i:get_child_text(e); +if t.props_verbatim then +for a,t in s(t.props_verbatim)do +e[a]=t; +end +end +if t.types then +local t=t.types; +e.TYPE={}; +for i=1,#t do +if o:get_child(t[i])then +a(e.TYPE,t[i]:lower()); +end +end +if#e.TYPE==0 then +e.TYPE=nil; +end +end +if t.props then +local t=t.props; +for i=1,#t do +local t=t[i] +local o=o:get_child_text(t); if o then -t[e]=t[e]or{}; -a(t[e],o); +e[t]=e[t]or{}; +a(e[t],o); end end end else return nil end -return t; -end -local function i(e) +return e; +end +local function o(e) local e=e.tags; local t={}; for o=1,#e do -a(t,h(e[o])); +a(t,n(e[o])); end return t end -function l(e) +function d(e) if e.attr.xmlns~="vcard-temp"then return nil,"wrong-xmlns"; end @@ -3640,14 +4805,14 @@ local a={}; local t=e.tags; for e=1,#t do -a[e]=i(t[e]); +a[e]=o(t[e]); end return a elseif e.name=="vCard"then -return i(e) -end -end -o={ +return o(e) +end +end +i={ VERSION="text", FN="text", N={ @@ -3769,19 +4934,19 @@ }, DESC="text", }; -o.LOGO=o.PHOTO; -o.SOUND=o.PHOTO; +i.LOGO=i.PHOTO; +i.SOUND=i.PHOTO; return{ -from_text=d; -to_text=r; -from_xep54=l; -to_xep54=u; -lua_to_text=r; -lua_to_xep54=u; -text_to_lua=d; -text_to_xep54=function(...)return u(d(...));end; -xep54_to_lua=l; -xep54_to_text=function(...)return r(l(...))end; +from_text=u; +to_text=l; +from_xep54=d; +to_xep54=r; +lua_to_text=l; +lua_to_xep54=r; +text_to_lua=u; +text_to_xep54=function(...)return r(u(...));end; +xep54_to_lua=d; +xep54_to_text=function(...)return l(d(...))end; }; end) package.preload['util.logger']=(function(...) @@ -3796,13 +4961,14 @@ _M=e; return e; end -local e=pcall; -local e=string.find; -local e,o,e=ipairs,pairs,setmetatable; -local a={}; +local o=pairs; +local c=ipairs; +local u=require; +local l=table.remove; +local _ENV=nil; local e={}; local t; -function a.init(e) +local function h(e) local a=t(e,"debug"); local o=t(e,"info"); local i=t(e,"warn"); @@ -3832,26 +4998,55 @@ end return e; end -function a.reset() +local function d() for t,e in o(e)do for t=1,#e do e[t]=nil; end end end -function a.add_level_sink(t,o) +local function i(t,a) if not e[t]then -e[t]={o}; -else -e[t][#e[t]+1]=o; -end -end -a.new=t; +e[t]={a}; +else +e[t][#e[t]+1]=a; +end +end +local function r(s,n) +local a=u"util.format".format; +local function e(o,t,e,...) +return s(o,t,a(e,...)); +end +for a,t in c(n or{"debug","info","warn","error"})do +i(t,e); +end +return e; +end +local function n(i) +local a; +for t,e in o(e)do +for t=#e,1,-1 do +if e[t]==i then +l(e,t); +a=true; +end +end +end return a; +end +return{ +init=h; +make_logger=t; +reset=d; +add_level_sink=i; +add_simple_sink=r; +new=t; +remove_sink=n; +}; end) package.preload['util.datetime']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -3863,43 +5058,60 @@ end local e=os.date; local i=os.time; -local u=os.difftime; -local t=error; +local r=os.difftime; +local t=math.floor; local s=tonumber; -a"datetime" -function date(t) -return e("!%Y-%m-%d",t); -end -function datetime(t) -return e("!%Y-%m-%dT%H:%M:%SZ",t); -end -function time(t) -return e("!%H:%M:%S",t); -end -function legacy(t) -return e("!%Y%m%dT%H:%M:%S",t); -end -function parse(o) -if o then -local n,h,l,d,r,t,a; -n,h,l,d,r,t,a=o:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$"); -if n then -local u=u(i(e("*t")),i(e("!*t"))); +local _ENV=nil; +local function d(a) +return e("!%Y-%m-%d",a and t(a)or nil); +end +local function l(a) +if a==nil or a%1==0 then +return e("!%Y-%m-%dT%H:%M:%SZ",a); +end +local o=a%1; +local a=t(a); +return e("!%Y-%m-%dT%H:%M:%S.%%06dZ",a):format(t(o*1e6)); +end +local function c(a) +if a==nil or a%1==0 then +return e("!%H:%M:%S",a); +end +local o=a%1; +local a=t(a); +return e("!%H:%M:%S.%%06d",a):format(t(o*1e6)); +end +local function u(a) +return e("!%Y%m%dT%H:%M:%S",a and t(a)or nil); +end +local function m(a) +if a then +local h,l,d,u,c,a,n=a:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d%.?%d*)([Z+%-]?.*)$"); +if h then +local o=i(); +local r=r(i(e("*t",o)),i(e("!*t",o))); local o=0; -if a~=""and a~="Z"then -local a,t,e=a:match("([+%-])(%d%d):?(%d*)"); +if n~=""and n~="Z"then +local a,t,e=n:match("([+%-])(%d%d):?(%d*)"); if not a then return;end if#e~=2 then e="0";end t,e=s(t),s(e); o=t*60*60+e*60; if a=="-"then o=-o;end end -t=(t+u)-o; -return i({year=n,month=h,day=l,hour=d,min=r,sec=t,isdst=false}); -end -end -end -return _M; +local e=a%1; +a=t(a+r)-o; +return i({year=h,month=l,day=d,hour=u,min=c,sec=a,isdst=false})+e; +end +end +end +return{ +date=d; +datetime=l; +time=c; +legacy=u; +parse=m; +}; end) package.preload['util.json']=(function(...) local _ENV=_ENV; @@ -3913,145 +5125,127 @@ _M=e; return e; end -local w=type; -local t,v,p,q=table.insert,table.concat,table.remove,table.sort; -local h=string.char; -local j,l=tostring,tonumber; -local u,s=pairs,ipairs; +local p=type; +local t,y,c=table.insert,table.concat,table.remove; +local n=string.char; +local z,d=tostring,tonumber; +local u,r,j=pairs,ipairs,require"util.iterators".sorted_pairs; local i=next; -local e=error; -local e,r,b=newproxy,getmetatable,setmetatable; -local c=print; -local a,o=pcall(require,"util.array"); -local f=a and r(o())or{}; +local b,m=getmetatable,setmetatable; +local l=print; +local a,e=pcall(require,"util.array"); +local v=a and b(e())or{}; local a={}; -local n=e and e(true)or{}; -if r and r(n)then -r(n).__tostring=function()return"null";end; -end -a.null=n; -local y={ +local h=m({},{__tostring=function()return"null";end;}); +a.null=h; +local f={ ["\""]="\\\"",["\\"]="\\\\",["\b"]="\\b", ["\f"]="\\f",["\n"]="\\n",["\r"]="\\r",["\t"]="\\t"}; -local e={ -["\""]="\"",["\\"]="\\",["/"]="/", -b="\b",f="\f",n="\n",r="\r",t="\t"}; -for e=0,31 do -local t=h(e); -if not y[t]then y[t]=("\\u%.4X"):format(e);end -end -local function x(e) -if e<128 then return h(e);end +for t=0,31 do +local e=n(t); +if not f[e]then f[e]=("\\u%.4X"):format(t);end +end +local function q(e) +if e<128 then return n(e);end local t=e%64; if e<2048 then local e=(e-t)/64; -return h(128+64+e,128+t); +return n(128+64+e,128+t); end local a=e%4096; local o=(a-t)/64; local e=(e-a)/4096; -return h(128+64+32+e,128+o,128+t); -end -local g={ +return n(128+64+32+e,128+o,128+t); +end +local k={ number=true, string=true, table=true, boolean=true }; -local z={ +local x={ __array=true; __hash=true; }; -local o,k,d,m; -function m(a,e) -t(e,"\""..(a:gsub(".",y)).."\""); -end -function d(a,e) +local o,g,s,w; +function w(a,e) +t(e,"\""..(a:gsub(".",f)).."\""); +end +function s(a,e) t(e,"["); if i(a)then -for i,a in s(a)do +for i,a in r(a)do o(a,e); t(e,","); end -p(e); +c(e); end t(e,"]"); end -function k(l,e) +function g(l,e) local a={}; -local r={}; -local h={}; -for t,e in s(l)do -a[t]=e; +local n={}; +local d={}; +for e,t in r(l)do +a[e]=t; end for e,t in u(l)do -local o,i=w(e),w(t); -if g[i]or t==n then -if o=="string"and not z[e]then -h[e]=t; -elseif(g[o]or e==n)and a[e]==nil then -r[e]=t; -end -end -end -if i(r)~=nil or i(h)~=nil or i(a)==nil then +local o,i=p(e),p(t); +if k[i]or t==h then +if o=="string"and not x[e]then +d[e]=t; +elseif(k[o]or e==h)and a[e]==nil then +n[e]=t; +end +end +end +if i(n)~=nil or i(d)~=nil or i(a)==nil then t(e,"{"); -local n=#e; -if e.ordered then -local a={}; -for e in u(h)do -t(a,e); -end -q(a); -for i,a in s(a)do -m(a,e); -t(e,":"); -o(h[a],e); -t(e,","); -end -else -for a,i in u(h)do -m(a,e); +local r=#e; +local h=e.ordered and j or u; +for a,i in h(d)do +w(a,e); t(e,":"); o(i,e); t(e,","); end -end -if i(r)~=nil then +if i(n)~=nil then t(e,"\"__hash\":["); -for i,a in u(r)do +for a,i in u(n)do +o(a,e); +t(e,","); o(i,e); t(e,","); -o(a,e); -t(e,","); -end -p(e); +end +c(e); t(e,"]"); t(e,","); end if i(a)then t(e,"\"__array\":"); -d(a,e); +s(a,e); t(e,","); end -if n~=#e then p(e);end +if r~=#e then c(e);end t(e,"}"); else -d(a,e); +s(a,e); end end function o(e,a) -local o=w(e); -if o=="number"then -t(a,j(e)); +local o=p(e); +if e==h then +t(a,"null"); +elseif o=="number"then +t(a,z(e)); elseif o=="string"then -m(e,a); +w(e,a); elseif o=="table"then -local t=r(e); -if t==f then -d(e,a); -else -k(e,a); +local t=b(e); +if t==v then +s(e,a); +else +g(e,a); end elseif o=="boolean"then t(a,(e and"true"or"false")); @@ -4062,26 +5256,26 @@ function a.encode(t) local e={}; o(t,e); -return v(e); +return y(e); end function a.encode_ordered(t) local e={ordered=true}; o(t,e); -return v(e); +return y(e); end function a.encode_array(t) local e={}; -d(t,e); -return v(e); +s(t,e); +return y(e); end local function o(t,e) return t:find("[^ \t\r\n]",e)or e; end -local function d(e) +local function c(e) local a=e.__array; if a then e.__array=nil; -for o,a in s(a)do +for o,a in r(a)do t(e,a); end end @@ -4089,7 +5283,7 @@ if a then e.__hash=nil; local t; -for o,a in s(a)do +for o,a in r(a)do if t~=nil then e[t]=a;t=nil; else @@ -4099,62 +5293,68 @@ end return e; end -local i,r; -local function m(t,e) -local s={}; +local s,r; +local function u(t,e) +local i={}; while true do local n,a; e=o(t,e+1); if t:byte(e)~=34 then -if t:byte(e)==125 then return s,e+1;end +if t:byte(e)==125 then return i,e+1;end return nil,"key expected"; end n,e=r(t,e); if n==nil then return nil,e;end e=o(t,e); if t:byte(e)~=58 then return nil,"colon expected";end -a,e=i(t,e+1); +a,e=s(t,e+1); if a==nil then return nil,e;end -s[n]=a; +i[n]=a; e=o(t,e); local t=t:byte(e); -if t==125 then return d(s),e+1;end +if t==125 then return c(i),e+1;end if t~=44 then return nil,"object eof";end end end -local function u(n,e) -local s={}; -local h=e; +local function c(n,e) +local i={}; while true do -local a; -a,e=i(n,e+1); +local a,h; +a,e,h=s(n,e+1,93); if a==nil then -if n:byte(h+1)==93 then return b(s,f),h+2;end +if h then +if#i~=0 then +return nil,"value expected"; +end +a,e=m(i,v),e+1; +end return a,e; end -t(s,a); +t(i,a); e=o(n,e); local t=n:byte(e); -if t==93 then return b(s,f),e+1;end +if t==93 then return m(i,v),e+1;end if t~=44 then return nil,"array eof";end end end local t; -local function e(e) -local e,t=l(e:sub(3,6),16),l(e:sub(9,12),16); -local e=e*1024+t-56613888; -local a=e%64; -e=(e-a)/64; +local function i(e) +local t,e=d(e:sub(3,6),16),d(e:sub(9,12),16); +local e=t*1024+e-56613888; +local o=e%64; +e=(e-o)/64; local t=e%64; e=(e-t)/64; -local o=e%64; -e=(e-o)/64; -return h(240+e,128+o,128+t,128+a); -end -local function s(e) +local a=e%64; +e=(e-a)/64; +return n(240+e,128+a,128+t,128+o); +end +local function n(e) e=e:match("%x%x%x%x",3); if e then -return x(l(e,16)); +local e=d(e,16) +if e>=55296 and e<=57343 then t=true;end +return q(e); end t=true; end @@ -4164,54 +5364,57 @@ if a then local e=o:sub(e,a-1); t=nil; -e=e:gsub("\\u.?.?.?.?",s); +e=e:gsub("\\u[dD][89abAB]%x%x\\u[dD][cdefCDEF]%x%x",i); +e=e:gsub("\\u.?.?.?.?",n); if t then return nil,"invalid escape";end return e,a+1; end return nil,"string eof"; end +local function i(e,t) +local e=e:match("[0-9%.%-eE%+]+",t); +return d(e),t+#e; +end local function d(t,e) -local t=t:match("[0-9%.%-eE%+]+",e); -return l(t),e+#t; -end -local function s(t,e) local o,a,t=t:byte(e+1,e+3); if o==117 and a==108 and t==108 then -return n,e+4; +return h,e+4; end return nil,"null parse failed"; end local function n(t,e) -local a,t,o=t:byte(e+1,e+3); -if a==114 and t==117 and o==101 then +local o,t,a=t:byte(e+1,e+3); +if o==114 and t==117 and a==101 then return true,e+4; end return nil,"true parse failed"; end local function h(t,e) -local t,a,o,i=t:byte(e+1,e+4); -if t==97 and a==108 and o==115 and i==101 then +local o,a,t,i=t:byte(e+1,e+4); +if o==97 and a==108 and t==115 and i==101 then return false,e+5; end return nil,"false parse failed"; end -function i(a,t) -t=o(a,t); -local e=a:byte(t); -if e==123 then -return m(a,t); -elseif e==91 then -return u(a,t); -elseif e==34 then -return r(a,t); -elseif e~=nil and e>=48 and e<=57 or e==45 then -return d(a,t); -elseif e==110 then -return s(a,t); -elseif e==116 then -return n(a,t); -elseif e==102 then -return h(a,t); +function s(a,e,s) +e=o(a,e); +local t=a:byte(e); +if t==123 then +return u(a,e); +elseif t==91 then +return c(a,e); +elseif t==34 then +return r(a,e); +elseif t~=nil and t>=48 and t<=57 or t==45 then +return i(a,e); +elseif t==110 then +return d(a,e); +elseif t==116 then +return n(a,e); +elseif t==102 then +return h(a,e); +elseif t==s then +return nil,e,true; else return nil,"value expected"; end @@ -4229,7 +5432,7 @@ }; function a.decode(e) e=e:gsub("\\.",t) -local t,a=i(e,1); +local t,a=s(e,1); if t==nil then return t,a;end if e:find("[^ \t\r\n]",a)then return nil,"garbage at eof";end return t; @@ -4239,11 +5442,11 @@ local t=a.decode(e); local t=a.encode(t); if e~=t then -c("FAILED"); -c("encoded:",e); -c("recoded:",t); -else -c(e); +l("FAILED"); +l("encoded:",e); +l("recoded:",t); +else +l(e); end return e==t; end @@ -4251,7 +5454,7 @@ end) package.preload['util.xml']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -4262,57 +5465,91 @@ return e; end local t=require"util.stanza"; -local h=require"lxp"; -a("xml") +local u=require"lxp"; +local i=table.insert; +local r=table.remove; +local c=error; +local _ENV=nil; local e=(function() -local n={ +local d={ ["http://www.w3.org/XML/1998/namespace"]="xml"; }; -local e="\1"; -local i="^([^"..e.."]*)"..e.."?(.*)$"; -return function(s) -local o={}; +local n="\1"; +local h="^([^"..n.."]*)"..n.."?(.*)$"; +return function(l,o) +local e={}; local a=t.stanza("root"); -function o:StartElement(t,e) -local t,o=t:match(i); -if o==""then -t,o="",t; -end -if t~=""then -e.xmlns=t; +local t={}; +local s={}; +function e:StartNamespaceDecl(e,a) +if e~=nil then +i(t,a); +i(s,e); +end +end +function e:EndNamespaceDecl(e) +if e~=nil then +r(t); +r(s); +end +end +function e:StartElement(o,e) +local o,i=o:match(h); +if i==""then +o,i="",o; +end +if o~=""then +e.xmlns=o; end for t=1,#e do local a=e[t]; e[t]=nil; -local t,o=a:match(i); +local t,o=a:match(h); if o~=""then -t=n[t]; +t=d[t]; if t then e[t..":"..o]=e[a]; e[a]=nil; end end end -a:tag(o,e); -end -function o:CharacterData(e) +local o={} +for e=1,#t do +o[s[e]]=t[e]; +end +a:tag(i,e,o); +end +function e:CharacterData(e) a:text(e); end -function o:EndElement(e) +function e:EndElement() a:up(); end -local n=h.new(o,"\1"); -local e,i,t,o=n:parse(s); -if e then e,i,t,o=n:parse();end +local function t(t) +if not t.stop or not t:stop()then +c("Failed to abort parsing"); +end +end +e.StartDoctypeDecl=t; +if not o or not o.allow_comments then +e.Comment=t; +end +if not o or not o.allow_processing_instructions then +e.ProcessingInstruction=t; +end +local t=u.new(e,n); +local e,i,o,n=t:parse(l); +if e then e,i,o,n=t:parse();end if e then return a.tags[1]; else -return e,i.." (line "..t..", col "..o..")"; +return e,("%s (line %d, col %d))"):format(i,o,n); end end; end)(); +return{ parse=e; -return _M; +}; end) package.preload['util.rsm']=(function(...) local _ENV=_ENV; @@ -4326,10 +5563,10 @@ _M=e; return e; end -local h=require"util.stanza".stanza; +local s=require"util.stanza".stanza; local t,o=tostring,tonumber; -local n=type; -local s=pairs; +local h=type; +local n=pairs; local i='http://jabber.org/protocol/rsm'; local a={}; do @@ -4353,44 +5590,44 @@ e.last=a; e.count=t; end -local r=setmetatable({ +local h=setmetatable({ first=function(a,e) -if n(e)=="table"then +if h(e)=="table"then a:tag("first",{index=e.index}):text(e[1]):up(); else a:tag("first"):text(t(e)):up(); end end; -before=function(e,a) -if a==true then -e:tag("before"):up(); -else -e:tag("before"):text(t(a)):up(); +before=function(a,e) +if e==true then +a:tag("before"):up(); +else +a:tag("before"):text(t(e)):up(); end end },{ -__index=function(e,o) -return function(e,a) -e:tag(o):text(t(a)):up(); +__index=function(a,e) +return function(a,o) +a:tag(e):text(t(o)):up(); end end; }); local function t(e) -local t={}; -for o in e:childtags()do -local e=o.name; +local o={}; +for t in e:childtags()do +local e=t.name; local a=e and a[e]; if a then -t[e]=a(o); -end -end -return t; -end -local function n(t) -local e=h("set",{xmlns=i}); -for t,o in s(t)do +o[e]=a(t); +end +end +return o; +end +local function o(t) +local e=s("set",{xmlns=i}); +for t,o in n(t)do if a[t]then -r[t](e,o); +h[t](e,o); end end return e; @@ -4401,7 +5638,7 @@ return t(e); end end -return{parse=t,generate=n,get=a}; +return{parse=t,generate=o,get=a}; end) package.preload['util.random']=(function(...) local _ENV=_ENV; @@ -4437,187 +5674,728 @@ _M=e; return e; end -local o={}; -local i={__index=function(t,e)return(o[e])(t);end, +local s=require"util.net"; +local n=require"util.hex"; +local a={}; +local o={ +__index=function(o,t) +local e=a[t]; +if not e then return nil;end +local e=e(o); +o[t]=e; +return e; +end, __tostring=function(e)return e.addr;end, -__eq=function(t,e)return t.addr==e.addr;end}; -local n={["0"]="0000",["1"]="0001",["2"]="0010",["3"]="0011",["4"]="0100",["5"]="0101",["6"]="0110",["7"]="0111",["8"]="1000",["9"]="1001",["A"]="1010",["B"]="1011",["C"]="1100",["D"]="1101",["E"]="1110",["F"]="1111"}; -local function e(e,t) -if not t then -local a=e:match("^%x+(.)"); -if a==":"or(not(a)and e:sub(1,1)==":")then -t="IPv6" -elseif a=="."then -t="IPv4" -end -if not t then -return nil,"invalid address"; -end -elseif t~="IPv4"and t~="IPv6"then +}; +o.__eq=function(t,e) +if getmetatable(t)~=o or getmetatable(e)~=o then +return false; +end +return t.packed==e.packed; +end +local h={ +["0"]="0000",["1"]="0001",["2"]="0010",["3"]="0011", +["4"]="0100",["5"]="0101",["6"]="0110",["7"]="0111", +["8"]="1000",["9"]="1001",["A"]="1010",["B"]="1011", +["C"]="1100",["D"]="1101",["E"]="1110",["F"]="1111", +}; +local function t(a,e) +local i; +if(not e or e=="IPv6")and a:find('%',1,true)then +a,i=a:match("^(.-)%%(.*)"); +end +local t,n=s.pton(a); +if not t then return t,n end +if e=="IPv6"and#t~=16 then +return nil,"invalid-ipv6"; +elseif e=="IPv4"and#t~=4 then +return nil,"invalid-ipv4"; +elseif not e then +if#t==16 then +e="IPv6"; +elseif#t==4 then +e="IPv4"; +else +return nil,"unknown protocol"; +end +elseif e~="IPv6"and e~="IPv4"then return nil,"invalid protocol"; end -local a; -if t=="IPv6"and e:find('%',1,true)then -e,a=e:match("^(.-)%%(.*)"); -end -if t=="IPv6"and e:find('.',1,true)then -local t; -e,t=e:gsub(":(%d+)%.(%d+)%.(%d+)%.(%d+)$",function(t,e,a,o) -return(":%04X:%04X"):format(t*256+e,a*256+o); -end); -if t~=1 then return nil,"invalid-address";end -end -return setmetatable({addr=e,proto=t,zone=a},i); -end -local function i(a) -local t=""; -local e={}; -if a.proto=="IPv4"then -a=a.toV4mapped; -end -a=(a.addr):upper(); -a:gsub("([^:]*):?",function(t)e[#e+1]=t end); -if not a:match(":$")then e[#e]=nil;end -for o,a in ipairs(e)do -if a:len()==0 and o~=1 and o~=#e then -for e=1,16*(9-#e)do -t=t.."0"; -end -else -for e=1,4-a:len()do -t=t.."0000"; -end -for e=1,a:len()do -t=t..n[a:sub(e,e)]; -end -end -end -return t; -end -local function t(a,t) -a,t=i(a),i(t); +return setmetatable({addr=a,packed=t,proto=e,zone=i},o); +end +function a:normal() +return s.ntop(self.packed); +end +function a.bits(e) +return n.encode(e.packed):upper():gsub(".",h); +end +function a.bits_full(e) +if e.proto=="IPv4"then +e=e.toV4mapped; +end +return e.bits; +end +local e; +local function v(t,a) +t,a=t.bits_full,a.bits_full; for e=1,128 do -if a:sub(e,e)~=t:sub(e,e)then +if t:sub(e,e)~=a:sub(e,e)then return e-1; end end return 128; end -local function h(t) -local e={}; -t:gsub("([^.]*).?",function(t)e[#e+1]=tonumber(t)end); -if e[1]==127 then +local n=t("::1"); +local w=t("127.0.0.0"); +local h=t("2002::"); +local d=t("2001::"); +local p=t("fe80::"); +local m=t("169.254.0.0"); +local r=t("fc00::"); +local i=t("fec0::"); +local l=t("3ffe::"); +local u=t("::"); +local f=t("ff00::"); +local c=t("::ffff:0:0"); +local function y(t) +if e(t,w,8)then return 2; -elseif e[1]==169 and e[2]==254 then +elseif e(t,m,16)then return 2; else return 14; end end -local function r(e) -if e:match("^[0:]*1$")then +local function w(t) +if t==n then return 2; -elseif e:match("^[Ff][Ee][89ABab]")then +elseif e(t,p,10)then return 2; -elseif e:match("^[Ff][Ee][CcDdEeFf]")then +elseif e(t,i,10)then return 5; -elseif e:match("^[Ff][Ff]")then -return tonumber("0x"..e:sub(4,4)); +elseif e(t,f,10)then +return t.packed:byte(2)%16; else return 14; end end -local function i(a) -if t(a,e("::1","IPv6"))==128 then +local function m(t) +if t==n then return 0; -elseif t(a,e("2002::","IPv6"))>=16 then +elseif e(t,h,16)then return 2; -elseif t(a,e("2001::","IPv6"))>=32 then +elseif e(t,d,32)then return 5; -elseif t(a,e("fc00::","IPv6"))>=7 then +elseif e(t,r,7)then return 13; -elseif t(a,e("fec0::","IPv6"))>=10 then +elseif e(t,i,10)then return 11; -elseif t(a,e("3ffe::","IPv6"))>=16 then +elseif e(t,l,16)then return 12; -elseif t(a,e("::","IPv6"))>=96 then +elseif e(t,u,96)then return 3; -elseif t(a,e("::ffff:0:0","IPv6"))>=96 then +elseif e(t,c,96)then return 4; else return 1; end end -local function n(a) -if t(a,e("::1","IPv6"))==128 then +local function f(t) +if t==n then return 50; -elseif t(a,e("2002::","IPv6"))>=16 then +elseif e(t,h,16)then return 30; -elseif t(a,e("2001::","IPv6"))>=32 then +elseif e(t,d,32)then return 5; -elseif t(a,e("fc00::","IPv6"))>=7 then +elseif e(t,r,7)then return 3; -elseif t(a,e("fec0::","IPv6"))>=10 then +elseif e(t,i,10)then return 1; -elseif t(a,e("3ffe::","IPv6"))>=16 then +elseif e(t,l,16)then return 1; -elseif t(a,e("::","IPv6"))>=96 then +elseif e(t,u,96)then return 1; -elseif t(a,e("::ffff:0:0","IPv6"))>=96 then +elseif e(t,c,96)then return 35; else return 40; end end -local function s(o) -local a={}; -local t="::ffff:"; -o:gsub("([^.]*).?",function(e)a[#a+1]=tonumber(e)end); -t=t..("%02x"):format(a[1]); -t=t..("%02x"):format(a[2]); -t=t..":" -t=t..("%02x"):format(a[3]); -t=t..("%02x"):format(a[4]); -return e(t,"IPv6"); -end -function o:toV4mapped() +function a:toV4mapped() if self.proto~="IPv4"then return nil,"No IPv4 address"end -local e=s(self.addr); -self.toV4mapped=e; -return e; -end -function o:label() -local e; +local e=t("::ffff:"..self.normal); +return e; +end +function a:label() +if self.proto=="IPv4"then +return m(self.toV4mapped); +else +return m(self); +end +end +function a:precedence() +if self.proto=="IPv4"then +return f(self.toV4mapped); +else +return f(self); +end +end +function a:scope() if self.proto=="IPv4"then -e=i(self.toV4mapped); -else -e=i(self); -end -self.label=e; -return e; -end -function o:precedence() -local e; -if self.proto=="IPv4"then -e=n(self.toV4mapped); -else -e=n(self); -end -self.precedence=e; -return e; -end -function o:scope() -local e; -if self.proto=="IPv4"then -e=h(self.addr); -else -e=r(self.addr); -end -self.scope=e; -return e; -end -return{new_ip=e, -commonPrefixLength=t}; +return y(self); +else +return w(self); +end +end +local r=t("10.0.0.0"); +local h=t("172.16.0.0"); +local i=t("192.168.0.0"); +local n=t("100.64.0.0"); +function a:private() +local t=self.scope~=14; +if not t and self.proto=="IPv4"then +return e(self,r,8)or e(self,h,12)or e(self,i,16)or e(self,n,10); +end +return t; +end +local function n(e) +local o; +local a=e:find("/",1,true); +if a then +o=tonumber(e:sub(a+1,-1)); +e=e:sub(1,a-1); +end +return t(e),o; +end +function e(a,t,e) +if not e or e>=128 or t.proto=="IPv4"and e>=32 then +return a==t; +elseif e<1 then +return true; +end +if a.proto~=t.proto then +if a.proto=="IPv4"then +a=a.toV4mapped; +elseif t.proto=="IPv4"then +t=t.toV4mapped; +e=e+(128-32); +end +end +return a.bits:sub(1,e)==t.bits:sub(1,e); +end +local function i(e) +return getmetatable(e)==o; +end +local function o(e,a) +if a%8~=0 then +return error("ip.truncate() only supports multiples of 8 bits"); +end +local a=a/8; +if not i(e)then +e=t(e); +end +return t(s.ntop(e.packed:sub(1,a)..("\0"):rep(#e.packed-a))) +end +return{ +new_ip=t, +commonPrefixLength=v, +parse_cidr=n, +match=e, +is_ip=i; +truncate=o; +}; +end) +package.preload['util.hex']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local h=string.char; +local s=string.format; +local o=string.gsub; +local n=string.lower; +local a={}; +local i={}; +do +local e,t; +for o=0,255 do +e,t=h(o),s("%02x",o); +a[e]=t; +i[t]=e; +end +end +local function t(e) +return(o(e,".",a)); +end +local function e(e) +return(o(n(e),"%X*(%x%x)%X*",i)); +end +return{ +encode=t,decode=e; +to=t,from=e; +}; +end) +package.preload['util.net']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +return{ +pton=function(e) +if e:find":"then +return"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +else +return"\0\0\0\0" +end +end +} +end) +package.preload['util.sslconfig']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local n=type; +local o=pairs; +local t=rawset; +local r=rawget; +local c=error; +local d=table.concat; +local l=table.insert; +local u=setmetatable; +local m=require"util.paths".resolve_relative_path; +local _ENV=nil; +local i={}; +local e={}; +local f=function(e)return e end +function i.options(h,s,e) +local a=h[s]or{}; +if n(e)~="table"then e={e}end +for o,e in o(e)do +if e==true or e==false then +a[o]=e; +else +a[e]=true; +end +end +t(h,s,a) +end +i.verifyext=i.options; +function e.options(a) +local t={}; +for e,a in o(a)do +if a then +t[#t+1]=e; +end +end +return t; +end +e.verifyext=e.options; +function e.ciphers(t) +if n(t)=="table"then +return d(t,":"); +end +return t; +end +e.curveslist=e.ciphers; +e.ciphersuites=e.ciphers; +function e.key(t,a) +if n(t)=="string"then +return m(a._basedir,t); +else +return nil +end +end +e.certificate=e.key; +e.cafile=e.key; +e.capath=e.key; +e.dhparam=e.key; +local a={"sslv2","sslv3","tlsv1","tlsv1_1","tlsv1_2","tlsv1_3"}; +for e=1,#a do a[a[e].."+"]=e-1;end +local function h(e) +local t=a[e.protocol]; +if t then +e.protocol="sslv23"; +for t=1,t do +l(e.options,"no_"..a[t]); +end +end +end +local function s(e,a) +t(e,"_cache",nil); +if n(a)=="table"then +for a,o in o(a)do +if a:sub(1,1)~="_"then +(i[a]or t)(e,a,o); +end +end +end +return e +end +local function n(i) +local a={}; +for t,o in o(i)do +if t:sub(1,1)~="_"then +a[t]=(e[t]or f)(o,i); +end +end +h(a); +return a; +end +local function i(e) +local a=r(e,"_cache"); +if a then +return a,nil +end +local a,o=r(e,"_context_factory")(e:final(),e); +if a then +t(e,"_cache",a); +end +return a,o +end +local e={ +__index={ +apply=s; +final=n; +build=i; +}; +__newindex=function() +c("SSL config objects cannot be modified directly. Use :apply()") +end; +}; +local function a(t,a) +return u({ +_context_factory=t, +_basedir=a, +options={}, +},e); +end +local function h(i) +local a=a(); +for e,o in o(i)do +t(a,e,o); +end +return a +end +e.__index.clone=h; +return{ +apply=s; +final=n; +_new=a; +}; +end) +package.preload['util.paths']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local i=table.concat; +local t=package.config:sub(1,1); +local a={} +function a.resolve_relative_path(a,e) +if e then +a=a:gsub("%"..t.."+$",""); +e=e:gsub("^%.%"..t.."+",""); +local o; +if t=="/"and e:sub(1,1)~="/"then +o=true; +elseif t=="\\"and(e:sub(1,1)~="/"and(e:sub(2,3)~=":\\"and e:sub(2,3)~=":/"))then +o=true; +end +if o then +return a..t..e; +end +end +return e; +end +function a.glob_to_pattern(e) +return"^"..e:gsub("[%p*?]",function(e) +if e=="*"then +return".*"; +elseif e=="?"then +return"."; +else +return"%"..e; +end +end).."$"; +end +function a.join(a,e,o,...) +if e then +if o then +if...then +return i({a,e,o,...},t); +end +return a..t..e..t..o; +end +return a..t..e; +end +return a; +end +function a.complement_lua_path(t) +local a=_VERSION:match(" (.+)$"); +local o=package.config:sub(3,3); +local e=package.config:sub(1,1); +local a=e.."lua"..e..a..e; +if not string.find(package.path,t,1,true)then +package.path=package.path..o..t..e.."share"..a.."?.lua"; +package.path=package.path..o..t..e.."share"..a.."?"..e.."init.lua"; +end +if not string.find(package.path,t,1,true)then +package.cpath=package.cpath..o..t..e.."lib"..a.."?.so"; +end +end +return a; +end) +package.preload['util.mathcompat']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +if not math.type then +local function t(e) +if type(e)=="number"then +if e%1==0 and e~=e+1 and e~=e-1 then +return"integer" +else +return"float" +end +end +end +_G.math.type=t +end +end) +package.preload['util.dnsregistry']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +return{ +classes={ +["IN"]=1;[1]="IN"; +["CH"]=3;[3]="CH"; +["HS"]=4;[4]="HS"; +["ANY"]=255;[255]="ANY"; +}; +types={ +["A"]=1;[1]="A"; +["NS"]=2;[2]="NS"; +["MD"]=3;[3]="MD"; +["MF"]=4;[4]="MF"; +["CNAME"]=5;[5]="CNAME"; +["SOA"]=6;[6]="SOA"; +["MB"]=7;[7]="MB"; +["MG"]=8;[8]="MG"; +["MR"]=9;[9]="MR"; +["NULL"]=10;[10]="NULL"; +["WKS"]=11;[11]="WKS"; +["PTR"]=12;[12]="PTR"; +["HINFO"]=13;[13]="HINFO"; +["MINFO"]=14;[14]="MINFO"; +["MX"]=15;[15]="MX"; +["TXT"]=16;[16]="TXT"; +["RP"]=17;[17]="RP"; +["AFSDB"]=18;[18]="AFSDB"; +["X25"]=19;[19]="X25"; +["ISDN"]=20;[20]="ISDN"; +["RT"]=21;[21]="RT"; +["NSAP"]=22;[22]="NSAP"; +["NSAP-PTR"]=23;[23]="NSAP-PTR"; +["SIG"]=24;[24]="SIG"; +["KEY"]=25;[25]="KEY"; +["PX"]=26;[26]="PX"; +["GPOS"]=27;[27]="GPOS"; +["AAAA"]=28;[28]="AAAA"; +["LOC"]=29;[29]="LOC"; +["NXT"]=30;[30]="NXT"; +["EID"]=31;[31]="EID"; +["NIMLOC"]=32;[32]="NIMLOC"; +["SRV"]=33;[33]="SRV"; +["ATMA"]=34;[34]="ATMA"; +["NAPTR"]=35;[35]="NAPTR"; +["KX"]=36;[36]="KX"; +["CERT"]=37;[37]="CERT"; +["A6"]=38;[38]="A6"; +["DNAME"]=39;[39]="DNAME"; +["SINK"]=40;[40]="SINK"; +["OPT"]=41;[41]="OPT"; +["APL"]=42;[42]="APL"; +["DS"]=43;[43]="DS"; +["SSHFP"]=44;[44]="SSHFP"; +["IPSECKEY"]=45;[45]="IPSECKEY"; +["RRSIG"]=46;[46]="RRSIG"; +["NSEC"]=47;[47]="NSEC"; +["DNSKEY"]=48;[48]="DNSKEY"; +["DHCID"]=49;[49]="DHCID"; +["NSEC3"]=50;[50]="NSEC3"; +["NSEC3PARAM"]=51;[51]="NSEC3PARAM"; +["TLSA"]=52;[52]="TLSA"; +["SMIMEA"]=53;[53]="SMIMEA"; +["HIP"]=55;[55]="HIP"; +["NINFO"]=56;[56]="NINFO"; +["RKEY"]=57;[57]="RKEY"; +["TALINK"]=58;[58]="TALINK"; +["CDS"]=59;[59]="CDS"; +["CDNSKEY"]=60;[60]="CDNSKEY"; +["OPENPGPKEY"]=61;[61]="OPENPGPKEY"; +["CSYNC"]=62;[62]="CSYNC"; +["ZONEMD"]=63;[63]="ZONEMD"; +["SVCB"]=64;[64]="SVCB"; +["HTTPS"]=65;[65]="HTTPS"; +["SPF"]=99;[99]="SPF"; +["NID"]=104;[104]="NID"; +["L32"]=105;[105]="L32"; +["L64"]=106;[106]="L64"; +["LP"]=107;[107]="LP"; +["EUI48"]=108;[108]="EUI48"; +["EUI64"]=109;[109]="EUI64"; +["TKEY"]=249;[249]="TKEY"; +["TSIG"]=250;[250]="TSIG"; +["IXFR"]=251;[251]="IXFR"; +["AXFR"]=252;[252]="AXFR"; +["MAILB"]=253;[253]="MAILB"; +["MAILA"]=254;[254]="MAILA"; +["*"]=255;[255]="*"; +["URI"]=256;[256]="URI"; +["CAA"]=257;[257]="CAA"; +["AVC"]=258;[258]="AVC"; +["DOA"]=259;[259]="DOA"; +["AMTRELAY"]=260;[260]="AMTRELAY"; +["TA"]=32768;[32768]="TA"; +["DLV"]=32769;[32769]="DLV"; +}; +errors={ +[0]="NoError";["NoError"]="No Error"; +[1]="FormErr";["FormErr"]="Format Error"; +[2]="ServFail";["ServFail"]="Server Failure"; +[3]="NXDomain";["NXDomain"]="Non-Existent Domain"; +[4]="NotImp";["NotImp"]="Not Implemented"; +[5]="Refused";["Refused"]="Query Refused"; +[6]="YXDomain";["YXDomain"]="Name Exists when it should not"; +[7]="YXRRSet";["YXRRSet"]="RR Set Exists when it should not"; +[8]="NXRRSet";["NXRRSet"]="RR Set that should exist does not"; +[9]="NotAuth";["NotAuth"]="Server Not Authoritative for zone"; +[10]="NotZone";["NotZone"]="Name not contained in zone"; +[11]="DSOTYPENI";["DSOTYPENI"]="DSO-TYPE Not Implemented"; +[16]="BADVERS";["BADVERS"]="Bad OPT Version"; +[17]="BADKEY";["BADKEY"]="Key not recognized"; +[18]="BADTIME";["BADTIME"]="Signature out of time window"; +[19]="BADMODE";["BADMODE"]="Bad TKEY Mode"; +[20]="BADNAME";["BADNAME"]="Duplicate key name"; +[21]="BADALG";["BADALG"]="Algorithm not supported"; +[22]="BADTRUNC";["BADTRUNC"]="Bad Truncation"; +[23]="BADCOOKIE";["BADCOOKIE"]="Bad/missing Server Cookie"; +}; +}; +end) +package.preload['net.tls_luasec']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local t=require"ssl"; +local h=t.newcontext; +local s=t.context or require"ssl.context"; +local o=io.open; +local e={}; +local n={__index=e}; +function e:set_sni_host(a,t,e) +local e,t=self._builder:clone():apply({ +certificate=t, +key=e, +}):build(); +if not e then +return false,t +end +self._sni_contexts[a]=e._inner +return true,nil +end +function e:remove_sni_host(e) +self._sni_contexts[e]=nil +end +function e:wrap(e) +local e,t,a=pcall(t.wrap,e,self._inner); +if not e then +return nil,a +end +return t,nil +end +local function i(e,i) +if type(e.dhparam)=="string"then +local t,a=o(e.dhparam); +if not t then return nil,"Could not open DH parameters: "..a end +local a=t:read("*a"); +t:close(); +e.dhparam=function()return a;end +end +local t,a=h(e); +if not t then +return nil,a +end +if t and e.ciphers then +local o; +o,a=s.setcipher(t,e.ciphers); +if not o then +return nil,a +end +end +return setmetatable({ +_inner=t, +_builder=i, +_sni_contexts={}, +},n),nil +end +return{ +new_context=i, +}; end) package.preload['util.sasl.scram']=(function(...) local _ENV=_ENV; @@ -4631,64 +6409,77 @@ _M=e; return e; end -local n,u=require"mime".b64,require"mime".unb64; -local e=require"util.hashes"; +local n,m=require"mime".b64,require"mime".unb64; +local t=require"util.hashes"; local a=require"bit"; -local s=require"util.random"; -local d=tonumber; -local h,t=string.char,string.byte; +local d=require"util.random"; +local p=tonumber; +local s,e=string.char,string.byte; local i=string.gsub; -local r=a.bxor; -local function l(e,o) -return(i(e,"()(.)",function(a,e) -return h(r(t(e),t(o,a))) +local h=a.bxor; +local function r(a,t) +return(i(a,"()(.)",function(a,o) +return s(h(e(o),e(t,a))) end)); end -local y,t=e.sha1,e.hmac_sha1; +local f,t=t.sha1,t.hmac_sha1; local function w(o,e,i) local e=t(o,e.."\0\0\0\1"); local a=e; for i=2,i do e=t(o,e); -a=l(a,e); +a=r(a,e); end return a; end -local function p(e) +local function y(e) return e; end local function a(e) return(i(e,"[,=]",{[","]="=2C",["="]="=3D"})); end -local function f(e,i) +local function s(e) +if e:ssl()then +local e=e:socket(); +if e.info and e:info().protocol=="TLSv1.3"then +if e.exportkeyingmaterial then +return"p=tls-exporter",e:exportkeyingmaterial("EXPORTER-Channel-Binding",32,""); +end +elseif e.getfinished then +return"p=tls-unique",e:getfinished(); +end +end +end +local function h(e,o) local a="n="..a(e.username); -local h=n(s.bytes(15)); -local c="r="..h; -local m=a..","..c; -local o=""; -local a=e.conn:ssl()and"y"or"n"; -if i=="SCRAM-SHA-1-PLUS"then -o=e.conn:socket():getfinished(); -a="p=tls-unique"; +local i=n(d.bytes(15)); +local h="r="..i; +local l=a..","..h; +local d=""; +local a="n"; +if o=="SCRAM-SHA-1-PLUS"then +a,d=s(e.conn); +elseif s(e.conn)then +a="y"; end local s=a..",,"; -local a=s..m; -local a,r=coroutine.yield(a); +local a=s..l; +local a,u=coroutine.yield(a); if a~="challenge"then return false end -local a,i,f=r:match("(r=[^,]+),s=([^,]*),i=(%d+)"); -local d=d(f); -i=u(i); -if not a or not i or not d then +local a,o,c=u:match("(r=[^,]+),s=([^,]*),i=(%d+)"); +local c=p(c); +o=m(o); +if not a or not o or not c then return false,"Could not parse server_first_message"; -elseif a:find(h,3,true)~=3 then +elseif a:find(i,3,true)~=3 then return false,"nonce sent by server does not match our nonce"; -elseif a==c then +elseif a==h then return false,"server did not append s-nonce to nonce"; end -local o=s..o; -local o="c="..n(o); -local h=o..","..a; -local o; +local i=s..d; +local i="c="..n(i); +local h=i..","..a; +local i; local a; local s; if e.client_key and e.server_key then @@ -4696,24 +6487,24 @@ s=e.server_key; else if e.salted_password then -o=e.salted_password; +i=e.salted_password; elseif e.password then -o=w(p(e.password),i,d); -end -s=t(o,"Server Key"); -a=t(o,"Client Key"); -end -local o=y(a); -local e=m..","..r..","..h; +i=w(y(e.password),o,c); +end +s=t(i,"Server Key"); +a=t(i,"Client Key"); +end +local o=f(a); +local e=l..","..u..","..h; local o=t(o,e); -local a=l(a,o); +local a=r(a,o); local t=t(s,e); local e="p="..n(a); local e=h..","..e; local e,a=coroutine.yield(e); if e~="success"then return false,"success-expected"end local e=a:match("v=([^,]+)"); -if u(e)~=t then +if m(e)~=t then return false,"server signature did not match"; end return true; @@ -4721,11 +6512,10 @@ return function(e,t) if e.username and(e.password or(e.client_key or e.server_key))then if t=="SCRAM-SHA-1"then -return f,99; +return h,99; elseif t=="SCRAM-SHA-1-PLUS"then -local e=e.conn:ssl()and e.conn:socket(); -if e and e.getfinished then -return f,100; +if s(e.conn)then +return h,100; end end end @@ -4771,6 +6561,38 @@ end end end) +package.preload['util.sasl.oauthbearer']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +return function(a,e) +if e=="OAUTHBEARER"and a.username then +return function(e) +local t=e.bearer_token and("Bearer "..e.bearer_token)or""; +local t,a=coroutine.yield("n,a="..e.username.."@"..e.host..",\001auth="..t.."\001"); +if t=="success"then +return true; +elseif t=="challenge"then +e:event("oauth-failure",{ +json=a; +}); +if coroutine.yield("\001")~="failure"then +error("Unexpected SASL state: expected failure after challenge"); +end +return false; +end +end,a.bearer_token and 6 or 4; +end +end +end) package.preload['verse.plugins.tls']=(function(...) local _ENV=_ENV; local function e(t,...) @@ -4793,7 +6615,7 @@ e:send(a.stanza("starttls",{xmlns=t})); return true; elseif not e.conn.starttls and not e.secure then -e:warn("SSL libary (LuaSec) not loaded, so TLS not available"); +e:warn("SSL library (LuaSec) not loaded, so TLS not available"); elseif not e.secure then e:debug("Server doesn't offer TLS :("); end @@ -4801,7 +6623,10 @@ local function o(t) if t.name=="proceed"then e:debug("Server says proceed, handshake starting..."); -e.conn:starttls(e.ssl or{mode="client",protocol="sslv23",options="no_sslv2",capath="/etc/ssl/certs"},true); +local t=a.tls_builder(".") +:apply({mode="client",protocol="sslv23",options="no_sslv2",capath="/etc/ssl/certs"}) +:apply(e.ssl or{}); +e.conn:starttls(t:build(),true); end end local function a(t) @@ -4830,52 +6655,54 @@ return e; end local i=require"verse"; -local s,h=require"mime".b64,require"mime".unb64; -local a="urn:ietf:params:xml:ns:xmpp-sasl"; +local h,r=require"mime".b64,require"mime".unb64; +local o="urn:ietf:params:xml:ns:xmpp-sasl"; function i.plugins.sasl(e) -local function r(t) +local function d(t) if e.authenticated then return;end e:debug("Authenticating with SASL..."); -local t=t:get_child("mechanisms",a); +local t=t:get_child("mechanisms",o); if not t then return end -local o={}; +local a={}; local n={}; +local s={}; for t in t:childtags("mechanism")do t=t:get_text(); e:debug("Server offers %s",t); -if not o[t]then +s[t]=true; +if not a[t]then local i=t:match("[^-]+"); -local s,a=pcall(require,"util.sasl."..i:lower()); +local s,o=pcall(require,"util.sasl."..i:lower()); if s then e:debug("Loaded SASL %s module",i); -o[t],n[t]=a(e,t); -elseif not tostring(a):match("not found")then -e:debug("Loading failed: %s",tostring(a)); +a[t],n[t]=o(e,t); +elseif not tostring(o):match("not found")then +e:debug("Loading failed: %s",tostring(o)); end end end local t={}; -for e in pairs(o)do +for e in pairs(a)do table.insert(t,e); end if not t[1]then -e:event("authentication-failure",{condition="no-supported-sasl-mechanisms"}); +e:event("authentication-failure",{condition="no-supported-sasl-mechanisms",mechanisms=s}); e:close(); return; end table.sort(t,function(t,e)return n[t]>n[e];end); local t,n=t[1]; e:debug("Selecting %s mechanism...",t); -e.sasl_mechanism=coroutine.wrap(o[t]); +e.sasl_mechanism=coroutine.wrap(a[t]); n=e:sasl_mechanism(t); -local t=i.stanza("auth",{xmlns=a,mechanism=t}); +local t=i.stanza("auth",{xmlns=o,mechanism=t}); if n then -t:text(s(n)); +t:text(h(n)); end e:send(t); return true; end -local function o(t) +local function a(t) if t.name=="failure"then local a=t.tags[1]; local t=t:get_child_text("text"); @@ -4883,9 +6710,9 @@ e:close(); return false; end -local t,o=e.sasl_mechanism(t.name,h(t:get_text())); +local t,a=e.sasl_mechanism(t.name,r(t:get_text())); if not t then -e:event("authentication-failure",{condition=o}); +e:event("authentication-failure",{condition=a}); e:close(); return false; elseif t==true then @@ -4893,12 +6720,12 @@ e.authenticated=true e:reopen(); else -e:send(i.stanza("response",{xmlns=a}):text(s(t))); +e:send(i.stanza("response",{xmlns=o}):text(h(t))); end return true; end -e:hook("stream-features",r,300); -e:hook("stream/"..a,o); +e:hook("stream-features",d,300); +e:hook("stream/"..o,a); return true; end end) @@ -4921,7 +6748,7 @@ local function o(o) if e.bound then return;end e:debug("Binding resource..."); -e:send_iq(t.iq({type="set"}):tag("bind",{xmlns=a}):tag("resource"):text(e.resource), +e:send_iq(t.iq({id="bind",type="set"}):tag("bind",{xmlns=a}):tag("resource"):text(e.resource), function(t) if t.attr.type=="result"then local t=t @@ -4932,8 +6759,8 @@ e:event("bind-success",{jid=t}); elseif t.attr.type=="error"then local a=t:child_with_name("error"); -local o,a,t=t:get_error(); -e:event("bind-failure",{error=a,text=t,type=o}); +local t,a,o=t:get_error(); +e:event("bind-failure",{error=a,text=o,type=t}); end end); end @@ -4941,222 +6768,6 @@ return true; end end) -package.preload['verse.plugins.session']=(function(...) -local _ENV=_ENV; -local function e(t,...) -local e=package.loaded[t]or _ENV[t]or{_NAME=t}; -package.loaded[t]=e; -for t=1,select("#",...)do -(select(t,...))(e); -end -_ENV=e; -_M=e; -return e; -end -local a=require"verse"; -local o="urn:ietf:params:xml:ns:xmpp-session"; -function a.plugins.session(e) -local function n(t) -local t=t:get_child("session",o); -if t and not t:get_child("optional")then -local function i(t) -e:debug("Establishing Session..."); -e:send_iq(a.iq({type="set"}):tag("session",{xmlns=o}), -function(t) -if t.attr.type=="result"then -e:event("session-success"); -elseif t.attr.type=="error"then -local t,a,o=t:get_error(); -e:event("session-failure",{error=a,text=o,type=t}); -end -end); -return true; -end -e:hook("bind-success",i); -end -end -e:hook("stream-features",n); -return true; -end -end) -package.preload['verse.plugins.legacy']=(function(...) -local _ENV=_ENV; -local function e(t,...) -local e=package.loaded[t]or _ENV[t]or{_NAME=t}; -package.loaded[t]=e; -for t=1,select("#",...)do -(select(t,...))(e); -end -_ENV=e; -_M=e; -return e; -end -local o=require"verse"; -local n=require"util.uuid".generate; -local i="jabber:iq:auth"; -function o.plugins.legacy(e) -local function s(t) -local a=t:get_child("query",i); -if t.attr.type~="result"or not a then -local o,a,t=t:get_error(); -e:debug("warn","%s %s: %s",o,a,t); -end -local t={ -username=e.username; -password=e.password; -resource=e.resource or n(); -digest=false,sequence=false,token=false; -}; -local o=o.iq({to=e.host,type="set"}) -:tag("query",{xmlns=i}); -if#a>0 then -for a in a:childtags()do -local a=a.name; -local i=t[a]; -if i then -o:tag(a):text(t[a]):up(); -elseif i==nil then -local t="feature-not-implemented"; -e:event("authentication-failure",{condition=t}); -return false; -end -end -else -for t,e in pairs(t)do -if e then -o:tag(t):text(e):up(); -end -end -end -e:send_iq(o,function(a) -if a.attr.type=="result"then -e.resource=t.resource; -e.jid=t.username.."@"..e.host.."/"..t.resource; -e:event("authentication-success"); -e:event("bind-success",e.jid); -else -local a,t,a=a:get_error(); -e:event("authentication-failure",{condition=t}); -end -end); -end -local function a(t) -if not t.version then -e:send_iq(o.iq({type="get"}) -:tag("query",{xmlns="jabber:iq:auth"}) -:tag("username"):text(e.username), -s); -end -end -e:hook("opened",a); -end -end) -package.preload['verse.plugins.compression']=(function(...) -local _ENV=_ENV; -local function e(t,...) -local e=package.loaded[t]or _ENV[t]or{_NAME=t}; -package.loaded[t]=e; -for t=1,select("#",...)do -(select(t,...))(e); -end -_ENV=e; -_M=e; -return e; -end -local t=require"verse"; -local e=require"zlib"; -local a="http://jabber.org/features/compress" -local a="http://jabber.org/protocol/compress" -local o="http://etherx.jabber.org/streams"; -local n=9; -local function i(o) -local i,e=pcall(e.deflate,n); -if i==false then -local t=t.stanza("failure",{xmlns=a}):tag("setup-failed"); -o:send(t); -o:error("Failed to create zlib.deflate filter: %s",tostring(e)); -return -end -return e -end -local function s(o) -local i,e=pcall(e.inflate); -if i==false then -local t=t.stanza("failure",{xmlns=a}):tag("setup-failed"); -o:send(t); -o:error("Failed to create zlib.inflate filter: %s",tostring(e)); -return -end -return e -end -local function h(e,o) -function e:send(i) -local i,o,n=pcall(o,tostring(i),'sync'); -if i==false then -e:close({ -condition="undefined-condition"; -text=o; -extra=t.stanza("failure",{xmlns=a}):tag("processing-failed"); -}); -e:warn("Compressed send failed: %s",tostring(o)); -return; -end -e.conn:write(o); -end; -end -local function r(e,i) -local s=e.data -e.data=function(n,o) -e:debug("Decompressing data..."); -local i,o,h=pcall(i,o); -if i==false then -e:close({ -condition="undefined-condition"; -text=o; -extra=t.stanza("failure",{xmlns=a}):tag("processing-failed"); -}); -stream:warn("%s",tostring(o)); -return; -end -return s(n,o); -end; -end -function t.plugins.compression(e) -local function n(o) -if not e.compressed then -local o=o:child_with_name("compression"); -if o then -for o in o:children()do -local o=o[1] -if o=="zlib"then -e:send(t.stanza("compress",{xmlns=a}):tag("method"):text("zlib")) -e:debug("Enabled compression using zlib.") -return true; -end -end -session:debug("Remote server supports no compression algorithm we support.") -end -end -end -local function o(a) -if a.name=="compressed"then -e:debug("Activating compression...") -local a=i(e); -if not a then return end -local t=s(e); -if not t then return end -h(e,a); -r(e,t); -e.compressed=true; -e:reopen(); -elseif a.name=="failure"then -e:warn("Failed to establish compression"); -end -end -e:hook("stream-features",n,250); -e:hook("stream/"..a,o); -end -end) package.preload['verse.plugins.smacks']=(function(...) local _ENV=_ENV; local function e(t,...) @@ -5169,64 +6780,63 @@ _M=e; return e; end -local i=require"verse"; -local h=require"socket".gettime; +local n=require"verse"; +local r=require"socket".gettime; local s="urn:xmpp:sm:3"; -function i.plugins.smacks(e) -local t={}; -local a=0; -local r=h(); +function n.plugins.smacks(e) +local t=nil; +local a=nil; +local h=nil; local o; -local n=0; -local function d(t) -if t.attr.xmlns=="jabber:client"or not t.attr.xmlns then -n=n+1; -e:debug("Increasing handled stanzas to %d for %s",n,t:top_tag()); -end -end -local function l(a) -if a.name and not a.attr.xmlns then +local i=nil; +local function m(t) +if i and(t.attr.xmlns=="jabber:client"or not t.attr.xmlns)then +i=i+1; +e:debug("Increasing handled stanzas to %d for %s",i,t:top_tag()); +end +end +local function c(a) +if t and(a.name and not a.attr.xmlns)then t[#t+1]=tostring(a); -r=h(); +h=r(); if not o then o=true; e:debug("Waiting to send ack request..."); -i.add_task(1,function() +n.add_task(1,function() if#t==0 then o=false; return; end -local a=h()-r; +local a=r()-h; if a<1 and#t<10 then return 1-a; end e:debug("Time up, sending ..."); o=false; -e:send(i.stanza("r",{xmlns=s})); +e:send(n.stanza("r",{xmlns=s})); end); end end end -local function h() +local function u() e:debug("smacks: connection lost"); e.stream_management_supported=nil; if e.resumption_token then e:debug("smacks: have resumption token, reconnecting in 1s..."); e.authenticated=nil; -i.add_task(1,function() +n.add_task(1,function() e:connect(e.connect_host or e.host,e.connect_port or 5222); end); return true; end end -local function u() +local function d() e.resumption_token=nil; -e:unhook("disconnected",h); -end -local function r(o) +end +local function l(o) if o.name=="r"then -e:debug("Ack requested... acking %d handled stanzas",n); -e:send(i.stanza("a",{xmlns=s,h=tostring(n)})); +e:debug("Ack requested... acking %d handled stanzas",i); +e:send(n.stanza("a",{xmlns=s,h=tostring(i)})); elseif o.name=="a"then local o=tonumber(o.attr.h); if o>a then @@ -5236,16 +6846,17 @@ end e:debug("Received ack: New ack: "..o.." Last ack: "..a.." Unacked stanzas now: "..#t.." (was "..i..")"); a=o; -else +elseif oa then local i=#t; @@ -5261,34 +6872,49 @@ t={}; e:debug("Resumed successfully"); e:event("resumed"); +elseif o.name=="failed"then +e.bound=nil +e.smacks=nil +a=nil +i=nil +t={}; +local t=e.pre_smacks_features; +e.pre_smacks_features=nil; +e:event("stream-features",t); else e:warn("Don't know how to handle "..s.."/"..o.name); end end -local function t() -if not e.smacks then +local function o() +if e.stream_management_supported and not e.smacks then e:debug("smacks: sending enable"); -e:send(i.stanza("enable",{xmlns=s,resume="true"})); +t={}; +a=0; +h=r(); +e:send(n.stanza("enable",{xmlns=s,resume="true"})); e.smacks=true; -e:hook("stanza",d); -e:hook("outgoing",l); -end -end -local function a(a) -if a:get_child("sm",s)then +end +end +local function a(t) +if t:get_child("sm",s)then +e.pre_smacks_features=t; e.stream_management_supported=true; if e.smacks and e.bound then -e:debug("Resuming stream with %d handled stanzas",n); -e:send(i.stanza("resume",{xmlns=s, -h=n,previd=e.resumption_token})); +e:debug("Resuming stream with %d handled stanzas",i); +e:send(n.stanza("resume",{xmlns=s, +h=tostring(i),previd=e.resumption_token})); return true; else -e:hook("bind-success",t,1); end end end e:hook("stream-features",a,250); -e:hook("stream/"..s,r); +e:hook("stream/"..s,l); +e:hook("bind-success",o,1); +e:hook("stanza",m); +e:hook("outgoing",c); +e:hook("closed",d,100); +e:hook("disconnected",u,100); end end) package.preload['verse.plugins.keepalive']=(function(...) @@ -5324,17 +6950,17 @@ _M=e; return e; end -local a=require"verse"; +local t=require"verse"; local e=require("mime").b64; local e=require("util.hashes").sha1; -local r=require"util.caps".calculate_hash; -local s="http://jabber.org/protocol/caps"; +local s=require"util.caps".calculate_hash; +local h="http://jabber.org/protocol/caps"; local e="http://jabber.org/protocol/disco"; -local i=e.."#info"; -local o=e.."#items"; -function a.plugins.disco(e) +local o=e.."#info"; +local i=e.."#items"; +function t.plugins.disco(e) e:add_plugin("presence"); -local n={ +local a={ __index=function(t,e) local a={identities={},features={}}; if e=="identities"or e=="features"then @@ -5344,10 +6970,10 @@ return a; end, }; -local t={ -__index=function(a,t) +local n={ +__index=function(t,a) local e={}; -a[t]=e; +t[a]=e; return e; end, }; @@ -5359,64 +6985,60 @@ {category='client',type='pc',name='Verse'}, }, features={ -[s]=true, +[h]=true, +[o]=true, [i]=true, -[o]=true, }, }, -},n); -items=setmetatable({[false]={}},t); +},a); +items=setmetatable({[false]={}},n); }; e.caps={} e.caps.node='http://code.matthewwild.co.uk/verse/' -local function h(t) -local o=e.disco.info[t or false]; -if t and t==e.caps.node.."#"..e.caps.hash then -o=e.disco.info[false]; -end -local n,o=o.identities,o.features -local e=a.stanza("query",{ -xmlns=i, -node=t, +local function r(a) +local i=e.disco.info[a or false]; +if a and a==e.caps.node.."#"..e.caps.hash then +i=e.disco.info[false]; +end +local n,i=i.identities,i.features +local e=t.stanza("query",{ +xmlns=o, +node=a, }); for a,t in pairs(n)do e:tag('identity',t):up() end -for t in pairs(o)do +for t in pairs(i)do e:tag('feature',{var=t}):up() end return e; end setmetatable(e.caps,{ __call=function(...) -local t=r(h()) -e.caps.hash=t; -return a.stanza('c',{ -xmlns=s, +local a=s(r()) +e.caps.hash=a; +return t.stanza('c',{ +xmlns=h, hash='sha-1', node=e.caps.node, -ver=t +ver=a }) end }) -function e:set_identity(a,t) -self.disco.info[t or false].identities={a}; -e:resend_presence(); -end -function e:add_identity(a,t) -local t=self.disco.info[t or false].identities; -t[#t+1]=a; -e:resend_presence(); -end -function e:add_disco_feature(t,a) -local t=t.var or t; -self.disco.info[a or false].features[t]=true; -e:resend_presence(); -end -function e:remove_disco_feature(t,a) -local t=t.var or t; -self.disco.info[a or false].features[t]=nil; -e:resend_presence(); +function e:set_identity(e,t) +self.disco.info[t or false].identities={e}; +end +function e:add_identity(t,e) +local e=self.disco.info[e or false].identities; +e[#e+1]=t; +end +function e:add_disco_feature(e,t) +local e=e.var or e; +self.disco.info[t or false].features[e]=true; +end +function e:remove_disco_feature(e,t) +local e=e.var or e; +self.disco.info[t or false].features[e]=nil; end function e:add_disco_item(t,e) local e=self.disco.items[e or false]; @@ -5430,17 +7052,17 @@ end end end -function e:jid_has_identity(t,a,e) +function e:jid_has_identity(t,e,a) local o=self.disco.cache[t]; if not o then return nil,"no-cache"; end local t=self.disco.cache[t].identities; -if e then -return t[a.."/"..e]or false; -end -for e in pairs(t)do -if e:match("^(.*)/")==a then +if a then +return t[e.."/"..a]or false; +end +for t in pairs(t)do +if t:match("^(.*)/")==e then return true; end end @@ -5452,14 +7074,14 @@ end return e.features[t]or false; end -function e:get_local_services(a,o) +function e:get_local_services(o,a) local e=self.disco.cache[self.host]; if not(e)or not(e.items)then return nil,"no-cache"; end local t={}; for i,e in ipairs(e.items)do -if self:jid_has_identity(e.jid,a,o)then +if self:jid_has_identity(e.jid,o,a)then table.insert(t,e.jid); end end @@ -5488,84 +7110,96 @@ end end); end -function e:disco_info(e,t,s) -local a=a.iq({to=e,type="get"}) -:tag("query",{xmlns=i,node=t}); -self:send_iq(a,function(o) -if o.attr.type=="error"then -return s(nil,o:get_error()); -end -local n,a={},{}; -for e in o:get_child("query",i):childtags()do +function e:disco_info(e,a,h) +local t=t.iq({to=e,type="get"}) +:tag("query",{xmlns=o,node=a}); +self:send_iq(t,function(t) +if t.attr.type=="error"then +return h(nil,t:get_error()); +end +local i,s,n={},{},{}; +for e in t:get_child("query",o):childtags()do +self:debug("DISCO {%s}%s",e.attr.xmlns or"",e.name); if e.name=="identity"then -n[e.attr.category.."/"..e.attr.type]=e.attr.name or true; +i[e.attr.category.."/"..e.attr.type]=e.attr.name or true; elseif e.name=="feature"then -a[e.attr.var]=true; +s[e.attr.var]=true; +end +end +for e in t:get_child("query",o):childtags("x","jabber:x:data")do +self:debug("DISCO {%s}%s",e.attr.xmlns or"",e.name); +local t=e:get_child_with_attr("field",nil,"var","FORM_TYPE"); +local a=t and t:get_child_text("value"); +self:debug("FORM: %s (%s)",a,t); +if a then +n[a]=e; end end if not self.disco.cache[e]then self.disco.cache[e]={nodes={}}; end -if t then -if not self.disco.cache[e].nodes[t]then -self.disco.cache[e].nodes[t]={nodes={}}; -end -self.disco.cache[e].nodes[t].identities=n; -self.disco.cache[e].nodes[t].features=a; -else -self.disco.cache[e].identities=n; -self.disco.cache[e].features=a; -end -return s(self.disco.cache[e]); +if a then +if not self.disco.cache[e].nodes[a]then +self.disco.cache[e].nodes[a]={nodes={}}; +end +self.disco.cache[e].nodes[a].identities=i; +self.disco.cache[e].nodes[a].features=s; +self.disco.cache[e].nodes[a].extended=n; +else +self.disco.cache[e].identities=i; +self.disco.cache[e].features=s; +self.disco.cache[e].extended=n; +end +return h(self.disco.cache[e]); end); end -function e:disco_items(t,i,n) -local a=a.iq({to=t,type="get"}) -:tag("query",{xmlns=o,node=i}); -self:send_iq(a,function(e) -if e.attr.type=="error"then -return n(nil,e:get_error()); -end -local a={}; -for e in e:get_child("query",o):childtags()do -if e.name=="item"then -table.insert(a,{ -name=e.attr.name; -jid=e.attr.jid; -node=e.attr.node; +function e:disco_items(a,o,n) +local t=t.iq({to=a,type="get"}) +:tag("query",{xmlns=i,node=o}); +self:send_iq(t,function(t) +if t.attr.type=="error"then +return n(nil,t:get_error()); +end +local e={}; +for t in t:get_child("query",i):childtags()do +if t.name=="item"then +table.insert(e,{ +name=t.attr.name; +jid=t.attr.jid; +node=t.attr.node; }); end end -if not self.disco.cache[t]then -self.disco.cache[t]={nodes={}}; -end -if i then -if not self.disco.cache[t].nodes[i]then -self.disco.cache[t].nodes[i]={nodes={}}; -end -self.disco.cache[t].nodes[i].items=a; -else -self.disco.cache[t].items=a; -end -return n(a); +if not self.disco.cache[a]then +self.disco.cache[a]={nodes={}}; +end +if o then +if not self.disco.cache[a].nodes[o]then +self.disco.cache[a].nodes[o]={nodes={}}; +end +self.disco.cache[a].nodes[o].items=e; +else +self.disco.cache[a].items=e; +end +return n(e); end); end -e:hook("iq/"..i,function(t) -local o=t.tags[1]; -if t.attr.type=='get'and o.name=="query"then -local o=h(o.attr.node); -local t=a.reply(t):add_child(o); +e:hook("iq/"..o,function(a) +local o=a.tags[1]; +if a.attr.type=='get'and o.name=="query"then +local o=r(o.attr.node); +local t=t.reply(a):add_child(o); e:send(t); return true end end); -e:hook("iq/"..o,function(i) -local t=i.tags[1]; -if i.attr.type=='get'and t.name=="query"then -local n=e.disco.items[t.attr.node or false]; -local t=a.reply(i):tag('query',{ -xmlns=o, -node=t.attr.node +e:hook("iq/"..i,function(a) +local o=a.tags[1]; +if a.attr.type=='get'and o.name=="query"then +local n=e.disco.items[o.attr.node or false]; +local t=t.reply(a):tag('query',{ +xmlns=i, +node=o.attr.node }) for a=1,#n do t:tag('item',n[a]):up() @@ -5578,7 +7212,7 @@ e:hook("ready",function() if t then return;end t=true; -local function i(t) +local function o(t) local a=e.disco.cache[t]; if a then for a in pairs(a.identities)do @@ -5591,20 +7225,19 @@ end end e:disco_info(e.host,nil,function() -i(e.host); +o(e.host); end); e:disco_local_services(function(t) for a,t in ipairs(t)do -i(t.jid); +o(t.jid); end e:event("ready"); end); return true; end,50); e:hook("presence-out",function(t) -if not t:get_child("c",s)then +t:remove_children("c",h); t:reset():add_child(e:caps()):reset(); -end end,10); end end) @@ -5621,18 +7254,18 @@ return e; end local o=require"verse"; -local a="jabber:iq:version"; -local function i(e,t) -e.name=t.name; -e.version=t.version; -e.platform=t.platform; +local t="jabber:iq:version"; +local function a(t,e) +t.name=e.name; +t.version=e.version; +t.platform=e.platform; end function o.plugins.version(e) -e.version={set=i}; -e:hook("iq/"..a,function(t) -if t.attr.type~="get"then return;end -local t=o.reply(t) -:tag("query",{xmlns=a}); +e.version={set=a}; +e:hook("iq/"..t,function(a) +if a.attr.type~="get"then return;end +local t=o.reply(a) +:tag("query",{xmlns=t}); if e.version.name then t:tag("name"):text(tostring(e.version.name)):up(); end @@ -5645,28 +7278,28 @@ e:send(t); return true; end); -function e:query_version(e,t) -t=t or function(e)return self:event("version/response",e);end -self:send_iq(o.iq({type="get",to=e}) -:tag("query",{xmlns=a}), +function e:query_version(i,a) +a=a or function(e)return self:event("version/response",e);end +self:send_iq(o.iq({type="get",to=i}) +:tag("query",{xmlns=t}), function(o) if o.attr.type=="result"then -local e=o:get_child("query",a); -local a=e and e:get_child_text("name"); +local e=o:get_child("query",t); +local t=e and e:get_child_text("name"); local o=e and e:get_child_text("version"); local e=e and e:get_child_text("os"); -t({ -name=a; +a({ +name=t; version=o; platform=e; }); else -local a,e,o=o:get_error(); -t({ +local o,e,t=o:get_error(); +a({ error=true; condition=e; -text=o; -type=a; +text=t; +type=o; }); end end); @@ -5687,22 +7320,26 @@ return e; end local a=require"verse"; -local n=require"socket".gettime; +local s=require"socket".gettime; +local o=require"util.id".short; local i="urn:xmpp:ping"; function a.plugins.ping(e) -function e:ping(t,o) -local s=n(); -e:send_iq(a.iq{to=t,type="get"}:tag("ping",{xmlns=i}), +function e:ping(t,n) +local h=s(); +local o=o(); +local a=a.iq{id=o,to=t,type="get"}:tag("ping",{xmlns=i}); +e:send_iq(a, function(e) if e.attr.type=="error"then -local i,e,a=e:get_error(); +local o,e,a=e:get_error(); if e~="service-unavailable"and e~="feature-not-implemented"then -o(nil,t,{type=i,condition=e,text=a}); +n(nil,t,{type=o,condition=e,text=a}); return; end end -o(n()-s,t); +n(s()-h,t); end); +return o; end e:hook("iq/"..i,function(t) return e:send(a.reply(t)); @@ -5748,10 +7385,55 @@ seconds=e or nil; }); else -local o,t,e=e:get_error(); +local o,e,t=e:get_error(); a({ error=true; -condition=t; +condition=e; +text=t; +type=o; +}); +end +end); +end +return true; +end +end) +package.preload['verse.plugins.time']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local e=require"verse"; +local i=require"util.datetime"; +local o="urn:xmpp:time"; +function e.plugins.time(t) +function t:query_time(a,t) +t=t or function(e)return self:event("time/response",e);end +self:send_iq(e.iq({type="get",to=a}) +:tag("time",{xmlns=o}), +function(a) +if a.attr.type=="result"then +local e=a:get_child("time",o); +local e={ +tzo=e:get_child_text("tzo"); +utc=e:get_child_text("utc"); +}; +if e.utc then +e.timestamp=i.parse(e.utc); +end +t(e); +else +local o,a,e=a:get_error(); +t({ +error=true; +condition=a; text=e; type=o; }); @@ -5773,38 +7455,38 @@ _M=e; return e; end -local o=require"verse"; -local a="urn:xmpp:blocking"; -function o.plugins.blocking(e) +local a=require"verse"; +local o="urn:xmpp:blocking"; +function a.plugins.blocking(e) e.blocking={}; function e.blocking:block_jid(i,t) -e:send_iq(o.iq{type="set"} -:tag("block",{xmlns=a}) +e:send_iq(a.iq{type="set"} +:tag("block",{xmlns=o}) :tag("item",{jid=i}) ,function()return t and t(true);end ,function()return t and t(false);end ); end function e.blocking:unblock_jid(i,t) -e:send_iq(o.iq{type="set"} -:tag("unblock",{xmlns=a}) +e:send_iq(a.iq{type="set"} +:tag("unblock",{xmlns=o}) :tag("item",{jid=i}) ,function()return t and t(true);end ,function()return t and t(false);end ); end function e.blocking:unblock_all_jids(t) -e:send_iq(o.iq{type="set"} -:tag("unblock",{xmlns=a}) +e:send_iq(a.iq{type="set"} +:tag("unblock",{xmlns=o}) ,function()return t and t(true);end ,function()return t and t(false);end ); end function e.blocking:get_blocked_jids(t) -e:send_iq(o.iq{type="get"} -:tag("blocklist",{xmlns=a}) +e:send_iq(a.iq{type="get"} +:tag("blocklist",{xmlns=o}) ,function(e) -local a=e:get_child("blocklist",a); +local a=e:get_child("blocklist",o); if not a then return t and t(false);end local e={}; for t in a:childtags()do @@ -5829,23 +7511,23 @@ _M=e; return e; end -local o=require"verse"; +local a=require"verse"; local e=require"util.timer"; -local n=require"util.uuid".generate; +local n=require"util.id".short; local i="urn:xmpp:jingle:1"; local r="urn:xmpp:jingle:errors:1"; local t={}; t.__index=t; local e={}; local e={}; -function o.plugins.jingle(e) +function a.plugins.jingle(e) e:hook("ready",function() e:add_disco_feature(i); end,10); -function e:jingle(a) -return o.eventable(setmetatable(base or{ +function e:jingle(o) +return a.eventable(setmetatable(base or{ role="initiator"; -peer=a; +peer=o; sid=n(); stream=e; },t)); @@ -5856,70 +7538,70 @@ end local function u(n) local s=n:get_child("jingle",i); -local a=s.attr.sid; -local h=s.attr.action; -local a=e:event("jingle/"..a,n); -if a==true then -e:send(o.reply(n)); +local h=s.attr.sid; +local o=s.attr.action; +local h=e:event("jingle/"..h,n); +if h==true then +e:send(a.reply(n)); return true; end -if h~="session-initiate"then -local t=o.error_reply(n,"cancel","item-not-found") +if o~="session-initiate"then +local t=a.error_reply(n,"cancel","item-not-found") :tag("unknown-session",{xmlns=r}):up(); e:send(t); return; end local l=s.attr.sid; -local a=o.eventable{ +local o=a.eventable{ role="receiver"; peer=n.attr.from; sid=l; stream=e; }; -setmetatable(a,t); +setmetatable(o,t); local h; -local d,r; +local r,d; for t in s:childtags()do if t.name=="content"and t.attr.xmlns==i then -local o=t:child_with_name("description"); -local i=o.attr.xmlns; -if i then -local e=e:event("jingle/content/"..i,a,o); +local i=t:child_with_name("description"); +local a=i.attr.xmlns; +if a then +local e=e:event("jingle/content/"..a,o,i); if e then -d=e; -end -end -local o=t:child_with_name("transport"); -local i=o.attr.xmlns; -r=e:event("jingle/transport/"..i,a,o); -if d and r then +r=e; +end +end +local a=t:child_with_name("transport"); +local i=a.attr.xmlns; +d=e:event("jingle/transport/"..i,o,a); +if r and d then h=t; break; end end end -if not d then -e:send(o.error_reply(n,"cancel","feature-not-implemented","The specified content is not supported")); +if not r then +e:send(a.error_reply(n,"cancel","feature-not-implemented","The specified content is not supported")); return true; end -if not r then -e:send(o.error_reply(n,"cancel","feature-not-implemented","The specified transport is not supported")); +if not d then +e:send(a.error_reply(n,"cancel","feature-not-implemented","The specified transport is not supported")); return true; end -e:send(o.reply(n)); -a.content_tag=h; -a.creator,a.name=h.attr.creator,h.attr.name; -a.content,a.transport=d,r; -function a:decline() +e:send(a.reply(n)); +o.content_tag=h; +o.creator,o.name=h.attr.creator,h.attr.name; +o.content,o.transport=r,d; +function o:decline() end e:hook("jingle/"..l,function(e) -if e.attr.from~=a.peer then +if e.attr.from~=o.peer then return false; end local e=e:get_child("jingle",i); -return a:handle_command(e); +return o:handle_command(e); end); -e:event("jingle",a); +e:event("jingle",o); return true; end function t:handle_command(a) @@ -5940,23 +7622,23 @@ end return true; end -function t:send_command(a,t,e) -local t=o.iq({to=self.peer,type="set"}) +function t:send_command(e,o,t) +local e=a.iq({to=self.peer,type="set"}) :tag("jingle",{ xmlns=i, sid=self.sid, -action=a, +action=e, initiator=self.role=="initiator"and self.stream.jid or nil, responder=self.role=="responder"and self.jid or nil, -}):add_child(t); -if not e then -self.stream:send(t); -else -self.stream:send_iq(t,e); -end -end -function t:accept(a) -local t=o.iq({to=self.peer,type="set"}) +}):add_child(o); +if not t then +self.stream:send(e); +else +self.stream:send_iq(e,t); +end +end +function t:accept(t) +local a=a.iq({to=self.peer,type="set"}) :tag("jingle",{ xmlns=i, sid=self.sid, @@ -5964,33 +7646,33 @@ responder=e.jid, }) :tag("content",{creator=self.creator,name=self.name}); -local o=self.content:generate_accept(self.content_tag:child_with_name("description"),a); -t:add_child(o); -local a=self.transport:generate_accept(self.content_tag:child_with_name("transport"),a); -t:add_child(a); -local a=self; -e:send_iq(t,function(t) -if t.attr.type=="error"then -local a,t,a=t:get_error(); +local o=self.content:generate_accept(self.content_tag:child_with_name("description"),t); +a:add_child(o); +local t=self.transport:generate_accept(self.content_tag:child_with_name("transport"),t); +a:add_child(t); +local t=self; +e:send_iq(a,function(a) +if a.attr.type=="error"then +local a,t,a=a:get_error(); e:error("session-accept rejected: %s",t); return false; end -a.transport:connect(function(t) +t.transport:connect(function(a) e:warn("CONNECTED (receiver)!!!"); -a.state="active"; -a:event("connected",t); +t.state="active"; +t:event("connected",a); end); end); end e:hook("iq/"..i,u); return true; end -function t:offer(t,a) -local e=o.iq({to=self.peer,type="set"}) +function t:offer(t,o) +local e=a.iq({to=self.peer,type="set"}) :tag("jingle",{xmlns=i,action="session-initiate", initiator=self.stream.jid,sid=self.sid}); e:tag("content",{creator=self.role,name=t}); -local t=self.stream:event("jingle/describe/"..t,a); +local t=self.stream:event("jingle/describe/"..t,o); if not t then return false,"Unknown content type"; end @@ -6016,7 +7698,7 @@ self.state="pending"; end function t:terminate(e) -local e=o.stanza("reason"):tag(e or"success"); +local e=a.stanza("reason"):tag(e or"success"); self:send_command("session-terminate",e,function(e) self.state="terminated"; self.transport:disconnect(); @@ -6077,24 +7759,24 @@ return e; end local i=require"verse"; -local o=require"ltn12"; +local n=require"ltn12"; local s=package.config:sub(1,1); local a="urn:xmpp:jingle:apps:file-transfer:4"; function i.plugins.jingle_ft(t) t:hook("ready",function() t:add_disco_feature(a); end,10); -local n={type="file"}; -function n:generate_accept(t,e) +local o={type="file"}; +function o:generate_accept(t,e) if e and e.save_file then self.jingle:hook("connected",function() -local e=o.sink.file(io.open(e.save_file,"w+")); +local e=n.sink.file(io.open(e.save_file,"w+")); self.jingle:set_sink(e); end); end return t; end -local n={__index=n}; +local o={__index=o}; t:hook("jingle/content/"..a,function(t,e) local e=e:get_child("file"); local e={ @@ -6103,7 +7785,7 @@ desc=e:get_child_text("desc"); date=e:get_child_text("date"); }; -return setmetatable({jingle=t,file=e},n); +return setmetatable({jingle=t,file=e},o); end); t:hook("jingle/describe/file",function(e) local t; @@ -6121,16 +7803,16 @@ function t:send_file(i,t) local e,a=io.open(t); if not e then return e,a;end -local a=e:seek("end",0); +local o=e:seek("end",0); e:seek("set",0); -local o=o.source.file(e); +local a=n.source.file(e); local e=self:jingle(i); e:offer("file",{ filename=t:match("[^"..s.."]+$"); -size=a; +size=o; }); e:hook("connected",function() -e:set_source(o,true); +e:set_source(a,true); end); return e; end @@ -6148,14 +7830,14 @@ _M=e; return e; end -local t=require"verse"; +local a=require"verse"; local o="urn:xmpp:jingle:transports:s5b:1"; -local r="http://jabber.org/protocol/bytestreams"; +local d="http://jabber.org/protocol/bytestreams"; local h=require"util.hashes".sha1; -local s=require"util.uuid".generate; -local function d(e,t) -local function a() -e:unhook("connected",a); +local l=require"util.id".short; +local function r(e,s) +local function n() +e:unhook("connected",n); return true; end local function i(t) @@ -6166,87 +7848,87 @@ e:event("connected"); return true; end -local function o(a) +local function o(t) e:unhook("incoming-raw",o); -if a~="\005\000"then -local t="version-mismatch"; -if a:sub(1,1)=="\005"then -t="authentication-failure"; -end -return e:event("error",t); -end -e:send(string.char(5,1,0,3,#t)..t.."\0\0"); +if t~="\005\000"then +local a="version-mismatch"; +if t:sub(1,1)=="\005"then +a="authentication-failure"; +end +return e:event("error",a); +end +e:send(string.char(5,1,0,3,#s)..s.."\0\0"); e:hook("incoming-raw",i,100); return true; end -e:hook("connected",a,200); +e:hook("connected",n,200); e:hook("incoming-raw",o,100); e:send("\005\001\000"); end -local function n(a,e,i) -local e=t.new(nil,{ +local function s(o,e,i) +local e=a.new(nil,{ streamhosts=e, current_host=0; }); -local function t(o) -if o then -return a(nil,o.reason); +local function t(a) +if a then +return o(nil,a.reason); end if e.current_host<#e.streamhosts then e.current_host=e.current_host+1; e:debug("Attempting to connect to "..e.streamhosts[e.current_host].host..":"..e.streamhosts[e.current_host].port.."..."); -local t,a=e:connect( +local a,t=e:connect( e.streamhosts[e.current_host].host, e.streamhosts[e.current_host].port ); -if not t then +if not a then e:debug("Error connecting to proxy (%s:%s): %s", e.streamhosts[e.current_host].host, e.streamhosts[e.current_host].port, -a +t ); else e:debug("Connecting..."); end -d(e,i); +r(e,i); return true; end e:unhook("disconnected",t); -return a(nil); +return o(nil); end e:hook("disconnected",t,100); e:hook("connected",function() e:unhook("disconnected",t); -a(e.streamhosts[e.current_host],e); +o(e.streamhosts[e.current_host],e); end,100); t(); return e; end -function t.plugins.jingle_s5b(e) +function a.plugins.jingle_s5b(e) e:hook("ready",function() e:add_disco_feature(o); end,10); -local a={}; -function a:generate_initiate() -self.s5b_sid=s(); -local a=t.stanza("transport",{xmlns=o, +local t={}; +function t:generate_initiate() +self.s5b_sid=l(); +local i=a.stanza("transport",{xmlns=o, mode="tcp",sid=self.s5b_sid}); local t=0; -for i,o in pairs(e.proxy65.available_streamhosts)do +for a,o in pairs(e.proxy65.available_streamhosts)do t=t+1; -a:tag("candidate",{jid=i,host=o.host, -port=o.port,cid=i,priority=t,type="proxy"}):up(); +i:tag("candidate",{jid=a,host=o.host, +port=o.port,cid=a,priority=t,type="proxy"}):up(); end e:debug("Have %d proxies",t) -return a; -end -function a:generate_accept(e) -local a={}; -self.s5b_peer_candidates=a; +return i; +end +function t:generate_accept(e) +local t={}; +self.s5b_peer_candidates=t; self.s5b_mode=e.attr.mode or"tcp"; self.s5b_sid=e.attr.sid or self.jingle.sid; for e in e:childtags()do -a[e.attr.cid]={ +t[e.attr.cid]={ type=e.attr.type; jid=e.attr.jid; host=e.attr.host; @@ -6255,47 +7937,47 @@ cid=e.attr.cid; }; end -local e=t.stanza("transport",{xmlns=o}); -return e; -end -function a:connect(i) +local e=a.stanza("transport",{xmlns=o}); +return e; +end +function t:connect(i) e:warn("Connecting!"); -local a={}; -for t,e in pairs(self.s5b_peer_candidates or{})do -a[#a+1]=e; -end -if#a>0 then +local t={}; +for a,e in pairs(self.s5b_peer_candidates or{})do +t[#t+1]=e; +end +if#t>0 then self.connecting_peer_candidates=true; -local function s(e,a) -self.jingle:send_command("transport-info",t.stanza("content",{creator=self.creator,name=self.name}) +local function n(e,t) +self.jingle:send_command("transport-info",a.stanza("content",{creator=self.creator,name=self.name}) :tag("transport",{xmlns=o,sid=self.s5b_sid}) :tag("candidate-used",{cid=e.cid})); self.onconnect_callback=i; -self.conn=a; +self.conn=t; end local e=h(self.s5b_sid..self.peer..e.jid,true); -n(s,a,e); +s(n,t,e); else e:warn("Actually, I'm going to wait for my peer to tell me its streamhost..."); self.onconnect_callback=i; end end -function a:info_received(a) +function t:info_received(t) e:warn("Info received"); -local s=a:child_with_name("content"); -local i=s:child_with_name("transport"); +local n=t:child_with_name("content"); +local i=n:child_with_name("transport"); if i:get_child("candidate-used")and not self.connecting_peer_candidates then -local a=i:child_with_name("candidate-used"); -if a then -local function i(i,e) +local t=i:child_with_name("candidate-used"); +if t then +local function r(i,e) if self.jingle.role=="initiator"then -self.jingle.stream:send_iq(t.iq({to=i.jid,type="set"}) -:tag("query",{xmlns=r,sid=self.s5b_sid}) +self.jingle.stream:send_iq(a.iq({to=i.jid,type="set"}) +:tag("query",{xmlns=d,sid=self.s5b_sid}) :tag("activate"):text(self.jingle.peer),function(i) if i.attr.type=="result"then -self.jingle:send_command("transport-info",t.stanza("content",s.attr) +self.jingle:send_command("transport-info",a.stanza("content",n.attr) :tag("transport",{xmlns=o,sid=self.s5b_sid}) -:tag("activated",{cid=a.attr.cid})); +:tag("activated",{cid=t.attr.cid})); self.conn=e; self.onconnect_callback(e); else @@ -6304,25 +7986,25 @@ end); end end -self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[a.attr.cid]); +self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]); local t={ -self.jingle.stream.proxy65.available_streamhosts[a.attr.cid]; +self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]; }; local e=h(self.s5b_sid..e.jid..self.peer,true); -n(i,t,e); +s(r,t,e); end elseif i:get_child("activated")then self.onconnect_callback(self.conn); end end -function a:disconnect() +function t:disconnect() if self.conn then self.conn:close(); end end -function a:handle_accepted(e) -end -local t={__index=a}; +function t:handle_accepted(e) +end +local t={__index=t}; e:hook("jingle/transport/"..o,function(e) return setmetatable({ role=e.role, @@ -6346,7 +8028,7 @@ return e; end local a=require"verse"; -local d=require"util.uuid"; +local h=require"util.id".short; local r=require"util.hashes".sha1; local n={}; n.__index=n; @@ -6417,31 +8099,31 @@ t:event("proxy65/request",e); end); end -function n:new(t,h) +function n:new(t,n) local e=a.new(nil,{ target_jid=t; -bytestream_sid=d.generate(); +bytestream_sid=h(); }); local o=a.iq{type="set",to=t} :tag("query",{xmlns=i,mode="tcp",sid=e.bytestream_sid}); -for t,e in ipairs(h or self.proxies)do +for t,e in ipairs(n or self.proxies)do o:tag("streamhost",e):up(); end self.stream:send_iq(o,function(o) if o.attr.type=="error"then -local a,t,o=o:get_error(); -e:event("connection-failed",{conn=e,type=a,condition=t,text=o}); +local t,a,o=o:get_error(); +e:event("connection-failed",{conn=e,type=t,condition=a,text=o}); else local o=o.tags[1]:get_child("streamhost-used"); e.streamhost_jid=o.attr.jid; -local n,o; -for a,t in ipairs(h or self.proxies)do +local h,o; +for a,t in ipairs(n or self.proxies)do if t.jid==e.streamhost_jid then -n,o=t.host,t.port; +h,o=t.host,t.port; break; end end -e:connect(n,o); +e:connect(h,o); local function o() e:unhook("connected",o); local t=a.iq{to=e.streamhost_jid,type="set"} @@ -6460,10 +8142,10 @@ end); return e; end -function s(i,e,a,t,o) -local a=r(a..t..o); -local function t() -e:unhook("connected",t); +function s(i,e,t,a,o) +local t=r(t..a..o); +local function a() +e:unhook("connected",a); return true; end local function o(t) @@ -6474,20 +8156,20 @@ e:event("connected"); return true; end -local function i(t) +local function i(a) e:unhook("incoming-raw",i); -if t~="\005\000"then -local a="version-mismatch"; -if t:sub(1,1)=="\005"then -a="authentication-failure"; -end -return e:event("error",a); -end -e:send(string.char(5,1,0,3,#a)..a.."\0\0"); +if a~="\005\000"then +local t="version-mismatch"; +if a:sub(1,1)=="\005"then +t="authentication-failure"; +end +return e:event("error",t); +end +e:send(string.char(5,1,0,3,#t)..t.."\0\0"); e:hook("incoming-raw",o,100); return true; end -e:hook("connected",t,200); +e:hook("connected",a,200); e:hook("incoming-raw",i,100); e:send("\005\001\000"); end @@ -6506,31 +8188,30 @@ end local e=require"verse"; local i=require"util.encodings".base64; -local s=require"util.uuid".generate; +local h=require"util.id".short; local n="urn:xmpp:jingle:transports:ibb:1"; local o="http://jabber.org/protocol/ibb"; assert(i.encode("This is a test.")=="VGhpcyBpcyBhIHRlc3Qu","Base64 encoding failed"); assert(i.decode("VGhpcyBpcyBhIHRlc3Qu")=="This is a test.","Base64 decoding failed"); -local t=table.concat local a={}; local t={__index=a}; -local function h(a) +local function s(a) local t=setmetatable({stream=a},t) t=e.eventable(t); return t; end -function a:initiate(e,t,a) +function a:initiate(t,e,a) self.block=2048; self.stanza=a or'iq'; -self.peer=e; -self.sid=t or tostring(self):match("%x+$"); +self.peer=t; +self.sid=e or tostring(self):match("%x+$"); self.iseq=0; self.oseq=0; local e=function(e) return self:feed(e) end self.feeder=e; -print("Hooking incomming IQs"); +print("Hooking incoming IQs"); local t=self.stream; t:hook("iq/"..o,e) if a=="message"then @@ -6620,7 +8301,7 @@ end,10); local t={}; function t:_setup() -local e=h(self.stream); +local e=s(self.stream); e.sid=self.sid or e.sid; e.stanza=self.stanza or e.stanza; e.block=self.block or e.block; @@ -6629,7 +8310,7 @@ end function t:generate_initiate() print("ibb:generate_initiate() as "..self.role); -local t=s(); +local t=h(); self.sid=t; self.stanza='iq'; self.block=2048; @@ -6694,24 +8375,31 @@ return e; end local i=require"verse"; -local n=table.insert; +local h=table.insert; local o="http://jabber.org/protocol/pubsub"; -local h="http://jabber.org/protocol/pubsub#owner"; -local a="http://jabber.org/protocol/pubsub#event"; +local s="http://jabber.org/protocol/pubsub#owner"; +local r="http://jabber.org/protocol/pubsub#event"; local e={}; -local s={__index=e}; +local n={__index=e}; function i.plugins.pubsub(e) -e.pubsub=setmetatable({stream=e},s); +e.pubsub=setmetatable({stream=e},n); e:hook("message",function(t) -local o=t.attr.from; -for t in t:childtags("event",a)do +local a=t.attr.from; +for t in t:childtags("event",r)do local t=t:get_child("items"); if t then -local a=t.attr.node; +local o=t.attr.node; for t in t:childtags("item")do e:event("pubsub/event",{ -from=o; -node=a; +from=a; +node=o; +item=t; +}); +end +for t in t:childtags("retract")do +e:event("pubsub/retraction",{ +from=a; +node=o; item=t; }); end @@ -6720,45 +8408,53 @@ end); return true; end -function e:create(e,a,t) -return self:service(e):node(a):create(nil,t); -end -function e:subscribe(a,e,o,t) -return self:service(a):node(e):subscribe(o,nil,t); -end -function e:publish(e,a,o,i,t) -return self:service(e):node(a):publish(o,nil,i,t); +function e:create(a,e,t) +return self:service(a):node(e):create(nil,t); +end +function e:subscribe(o,t,a,e) +return self:service(o):node(t):subscribe(a,nil,e); +end +function e:publish(e,t,o,a,i) +return self:service(e):node(t):publish(o,nil,a,i); end local a={}; local t={__index=a}; function e:service(e) return setmetatable({stream=self.stream,service=e},t) end -local function t(h,e,r,a,s,n,t) -local e=i.iq{type=h or"get",to=e} -:tag("pubsub",{xmlns=r or o}) -if a then e:tag(a,{node=s,jid=n});end -if t then e:tag("item",{id=t~=true and t or nil});end +local function t(d,r,e,n,h,s,t,a) +local e=i.iq{type=d or"get",to=r} +:tag("pubsub",{xmlns=e or o}) +local o={node=h,jid=s}; +if a then +for t,e in pairs(a)do +o[t]=e; +end +end +if n then e:tag(n,o);end +if t then +e:tag("item",{id=t~=true and t or nil}); +end return e; end function a:subscriptions(a) self.stream:send_iq(t(nil,self.service,nil,"subscriptions") -,a and function(t) -if t.attr.type=="result"then -local e=t:get_child("pubsub",o); +,a and function(e) +if e.attr.type=="result"then +local e=e:get_child("pubsub",o); local e=e and e:get_child("subscriptions"); local o={}; if e then -for t in e:childtags("subscription")do -local e=self:node(t.attr.node) -e.subscription=t; -e.subscribed_jid=t.attr.jid; -n(o,e); +for e in e:childtags("subscription")do +local t=self:node(e.attr.node) +t.subscription=e; +t.subscribed_jid=e.attr.jid; +h(o,t); end end a(o); else -a(false,t:get_error()); +a(false,e:get_error()); end end or nil); end @@ -6768,15 +8464,15 @@ if e.attr.type=="result"then local e=e:get_child("pubsub",o); local e=e and e:get_child("affiliations")or{}; -local t={}; +local o={}; if e then for e in e:childtags("affiliation")do -local a=self:node(e.attr.node) -a.affiliation=e; -n(t,a); -end -end -a(t); +local t=self:node(e.attr.node) +t.affiliation=e; +h(o,t); +end +end +a(o); else a(false,e:get_error()); end @@ -6797,7 +8493,7 @@ function a:node(e) return setmetatable({stream=self.stream,service=self.service,node=e},o) end -function s:__call(t,e) +function n:__call(t,e) local t=self:service(t); return e and t:node(e)or t; end @@ -6822,11 +8518,11 @@ end end end -function e:create(a,e) -if a~=nil then +function e:create(e,a) +if e~=nil then error("Not implemented yet."); else -self.stream:send_iq(t("set",self.service,nil,"create",self.node),e); +self.stream:send_iq(t("set",self.service,nil,"create",self.node),a); end end function e:configure(e,a) @@ -6835,21 +8531,21 @@ end self.stream:send_iq(t("set",self.service,nil,e==nil and"default"or"configure",self.node),a); end -function e:publish(a,e,o,i) -if e~=nil then +function e:publish(i,a,e,o) +if a~=nil then error("Node configuration is not implemented yet."); end -self.stream:send_iq(t("set",self.service,nil,"publish",self.node,nil,a or true) -:add_child(o) -,i); -end -function e:subscribe(e,o,a) +self.stream:send_iq(t("set",self.service,nil,"publish",self.node,nil,i or true) +:add_child(e) +,o); +end +function e:subscribe(e,a,o) e=e or self.stream.jid; -if o~=nil then +if a~=nil then error("Subscription configuration is not implemented yet."); end self.stream:send_iq(t("set",self.service,nil,"subscribe",self.node,e) -,a); +,o); end function e:subscription(e) error("Not implemented yet."); @@ -6865,10 +8561,9 @@ function e:configure_subscription(e,e) error("Not implemented yet."); end -function e:items(a,e) -if a then -self.stream:send_iq(t("get",self.service,nil,"items",self.node) -,e); +function e:items(t,e) +if t then +return self:item(nil,e); else self.stream:disco_items(self.service,self.node,e); end @@ -6877,19 +8572,43 @@ self.stream:send_iq(t("get",self.service,nil,"items",self.node,nil,e) ,a); end -function e:retract(a,e) -self.stream:send_iq(t("set",self.service,nil,"retract",self.node,nil,a) -,e); -end -function e:purge(a,e) -assert(not a,"Not implemented yet."); -self.stream:send_iq(t("set",self.service,h,"purge",self.node) -,e); -end -function e:delete(a,e) -assert(not a,"Not implemented yet."); -self.stream:send_iq(t("set",self.service,h,"delete",self.node) -,e); +function e:retract(o,e,a) +if type(e)=="function"then +e,a=false,e; +end +self.stream:send_iq( +t( +"set", +self.service, +nil, +"retract", +self.node, +nil, +o, +{notify=e and"1"or nil} +), +a +); +end +function e:purge(e,a) +self.stream:send_iq( +t( +"set", +self.service, +s, +"purge", +self.node, +nil, +nil, +{notify=e and"1"or nil} +), +a +); +end +function e:delete(e,a) +assert(not e,"Not implemented yet."); +self.stream:send_iq(t("set",self.service,s,"delete",self.node) +,a); end end) package.preload['verse.plugins.pep']=(function(...) @@ -6904,15 +8623,15 @@ _M=e; return e; end -local e=require"verse"; -local t="http://jabber.org/protocol/pubsub"; -local t=t.."#event"; -function e.plugins.pep(e) +local t=require"verse"; +local e="http://jabber.org/protocol/pubsub"; +local e=e.."#event"; +function t.plugins.pep(e) e:add_plugin("disco"); e:add_plugin("pubsub"); e.pep={}; e:hook("pubsub/event",function(t) -return e:event("pep/"..t.node,{from=t.from,item=t.item.tags[1]}); +return e:event("pep/"..t.node,{from=t.from,id=t.item.attr.id,item=t.item.tags[1]}); end); function e:hook_pep(t,i,o) local a=e.events._handlers["pep/"..t]; @@ -6948,7 +8667,7 @@ local o=require"verse"; local n=require"lib.adhoc"; local t="http://jabber.org/protocol/commands"; -local s="jabber:x:data"; +local r="jabber:x:data"; local a={}; a.__index=a; local i={}; @@ -6966,14 +8685,14 @@ return o(t); end); end -function e:execute_command(t,i,o) +function e:execute_command(o,i,t) local e=setmetatable({ -stream=e,jid=t, -command=i,callback=o +stream=e,jid=o, +command=i,callback=t },a); return e:execute(); end -local function h(t,e) +local function s(t,e) if not(e)or e=="user"then return true;end if type(e)=="function"then return e(t); @@ -6984,24 +8703,24 @@ e:add_disco_item({jid=e.jid,node=a,name=o},t); return i[a]; end -local function s(a) -local t=a.tags[1]; -local t=t.attr.node; -local t=i[t]; -if not t then return;end -if not h(a.attr.from,t.permission)then -e:send(o.error_reply(a,"auth","forbidden","You don't have permission to execute this command"):up() -:add_child(t:cmdtag("canceled") +local function h(t) +local a=t.tags[1]; +local a=a.attr.node; +local a=i[a]; +if not a then return;end +if not s(t.attr.from,a.permission)then +e:send(o.error_reply(t,"auth","forbidden","You don't have permission to execute this command"):up() +:add_child(a:cmdtag("canceled") :tag("note",{type="error"}):text("You don't have permission to execute this command"))); return true end -return n.handle_cmd(t,{send=function(t)return e:send(t)end},a); +return n.handle_cmd(a,{send=function(t)return e:send(t)end},t); end e:hook("iq/"..t,function(e) -local a=e.attr.type; -local t=e.tags[1].name; -if a=="set"and t=="command"then -return s(e); +local t=e.attr.type; +local a=e.tags[1].name; +if t=="set"and a=="command"then +return h(e); end end); end @@ -7014,7 +8733,7 @@ local e=e:get_child("command",t); self.status=e.attr.status; self.sessionid=e.attr.sessionid; -self.form=e:get_child("x",s); +self.form=e:get_child("x",r); self.note=e:get_child("note"); self.callback(self); end @@ -7051,11 +8770,12 @@ return e; end local a=require"verse"; +local o=require"util.stanza"; function a.plugins.presence(t) t.last_presence=nil; t:hook("presence-out",function(e) if not e.attr.to then -t.last_presence=e; +t.last_presence=o.clone(e); end end,1); function t:resend_presence() @@ -7094,12 +8814,12 @@ _M=e; return e; end -local a=require"verse"; -local t="jabber:iq:private"; -function a.plugins.private(o) +local t=require"verse"; +local a="jabber:iq:private"; +function t.plugins.private(o) function o:private_set(i,o,e,n) -local t=a.iq({type="set"}) -:tag("query",{xmlns=t}); +local t=t.iq({type="set"}) +:tag("query",{xmlns=a}); if e then if e.name==i and e.attr and e.attr.xmlns==o then t:add_child(e); @@ -7110,14 +8830,14 @@ end self:send_iq(t,n); end -function o:private_get(o,e,i) -self:send_iq(a.iq({type="get"}) -:tag("query",{xmlns=t}) -:tag(o,{xmlns=e}), -function(a) -if a.attr.type=="result"then -local t=a:get_child("query",t); -local e=t:get_child(o,e); +function o:private_get(e,o,i) +self:send_iq(t.iq({type="get"}) +:tag("query",{xmlns=a}) +:tag(e,{xmlns=o}), +function(t) +if t.attr.type=="result"then +local t=t:get_child("query",a); +local e=t:get_child(e,o); i(e); end end); @@ -7153,18 +8873,18 @@ h=true; end end); -local function s(t) -local e=i.stanza("item",{xmlns=a}); -for a,t in pairs(t)do +local function s(e) +local t=i.stanza("item",{xmlns=a}); +for a,e in pairs(e)do if a~="groups"then -e.attr[a]=t; -else -for a=1,#t do -e:tag("group"):text(t[a]):up(); -end -end -end -return e; +t.attr[a]=e; +else +for a=1,#e do +t:tag("group"):text(e[a]):up(); +end +end +end +return t; end local function d(a) local e={}; @@ -7189,8 +8909,8 @@ items=e.items, }; end -function e:add_contact(n,o,h,e) -local o={jid=n,name=o,groups=h}; +function e:add_contact(h,o,n,e) +local o={jid=h,name=o,groups=n}; local a=i.iq({type="set"}) :tag("query",{xmlns=a}) :add_child(s(o)); @@ -7249,22 +8969,22 @@ t:hook("iq/"..a,function(o) local s,n=o.attr.type,o.attr.from; if s=="set"and(not n or n==l(t.jid))then -local n=o:get_child("query",a); -local a=n and n:get_child("item"); +local s=o:get_child("query",a); +local n=s and s:get_child("item"); +if n then +local o,a; +local i=n.attr.jid; +if n.attr.subscription=="remove"then +o="removed" +a=d(i); +else +o=e.items[i]and"changed"or"added"; +r(n) +a=e.items[i]; +end +e.ver=s.attr.ver; if a then -local i,o; -local s=a.attr.jid; -if a.attr.subscription=="remove"then -i="removed" -o=d(s); -else -i=e.items[s]and"changed"or"added"; -r(a) -o=e.items[s]; -end -e.ver=n.attr.ver; -if o then -t:event("roster/item-"..i,o); +t:event("roster/item-"..o,a); end end t:send(i.reply(o)) @@ -7363,7 +9083,7 @@ return t or(e.name=="message")or nil; end end,500); -function o:join_room(n,h,t) +function o:join_room(n,h,t,r) if not h then return false,"no nickname supplied" end @@ -7428,6 +9148,9 @@ end end,2e3); local t=i.presence():tag("x",{xmlns=s}):reset(); +if r then +t:get_child("x",s):tag("password"):text(r):reset(); +end self:event("pre-groupchat/joining",t); e:send(t) self:event("groupchat/joining",e); @@ -7471,23 +9194,23 @@ end self:send(e); end -function a:admin_set(a,t,o,e) +function a:admin_set(e,t,a,o) self:send(i.iq({type="set"}) :query(s.."#admin") -:tag("item",{nick=a,[t]=o}) -:tag("reason"):text(e or"")); -end -function a:set_role(t,e,a) -self:admin_set(t,"role",e,a); -end -function a:set_affiliation(a,e,t) -self:admin_set(a,"affiliation",e,t); -end -function a:kick(e,t) -self:set_role(e,"none",t); -end -function a:ban(t,e) -self:set_affiliation(t,"outcast",e); +:tag("item",{nick=e,[t]=a}) +:tag("reason"):text(o or"")); +end +function a:set_role(a,e,t) +self:admin_set(a,"role",e,t); +end +function a:set_affiliation(t,a,e) +self:admin_set(t,"affiliation",a,e); +end +function a:kick(t,e) +self:set_role(t,"none",e); +end +function a:ban(e,t) +self:set_affiliation(e,"outcast",t); end end) package.preload['verse.plugins.vcard']=(function(...) @@ -7549,7 +9272,7 @@ end local n=require"verse"; local i="vcard-temp:x:update"; -local s=require("util.hashes").sha1; +local r=require("util.hashes").sha1; local e,t=pcall(function() local e=require("util.encodings").base64.decode; assert(e("SGVsbG8=")=="Hello") @@ -7566,7 +9289,7 @@ e:add_plugin("vcard"); e:add_plugin("presence"); local t; -local function r(a) +local function s(a) local o; for e=1,#a do if a[e].name=="PHOTO"then @@ -7575,7 +9298,7 @@ end end if o then -local a=s(h(o),true); +local a=r(h(o),true); t=n.stanza("x",{xmlns=i}) :tag("photo"):text(a); e:resend_presence() @@ -7589,7 +9312,7 @@ a=true; e:get_vcard(nil,function(t) if t then -r(t) +s(t) end e:event("ready"); end); @@ -7614,19 +9337,19 @@ _M=e; return e; end -local a=require"verse"; -local o="urn:xmpp:carbons:2"; +local o=require"verse"; +local a="urn:xmpp:carbons:2"; local n="urn:xmpp:forward:0"; -local s=os.time; -local r=require"util.datetime".parse; -local h=require"util.jid".bare; -function a.plugins.carbons(e) +local h=os.time; +local s=require"util.datetime".parse; +local r=require"util.jid".bare; +function o.plugins.carbons(e) local t={}; t.enabled=false; e.carbons=t; function t:enable(i) -e:send_iq(a.iq{type="set"} -:tag("enable",{xmlns=o}) +e:send_iq(o.iq{type="set"} +:tag("enable",{xmlns=a}) ,function(e) local e=e.attr.type=="result"; if e then @@ -7638,8 +9361,8 @@ end or nil); end function t:disable(i) -e:send_iq(a.iq{type="set"} -:tag("disable",{xmlns=o}) +e:send_iq(o.iq{type="set"} +:tag("disable",{xmlns=a}) ,function(e) local e=e.attr.type=="result"; if e then @@ -7652,22 +9375,22 @@ end local i; e:hook("bind-success",function() -i=h(e.jid); +i=r(e.jid); end); -e:hook("message",function(a) -local t=a:get_child(nil,o); -if a.attr.from==i and t then +e:hook("message",function(o) +local t=o:get_child(nil,a); +if o.attr.from==i and t then local o=t.name; local t=t:get_child("forwarded",n); local a=t and t:get_child("message","jabber:client"); local t=t:get_child("delay","urn:xmpp:delay"); local t=t and t.attr.stamp; -t=t and r(t); +t=t and s(t); if a then return e:event("carbon",{ dir=o, stanza=a, -timestamp=t or s(), +timestamp=t or h(), }); end end @@ -7689,71 +9412,71 @@ local a=require"verse"; local t=require"util.stanza"; local e="urn:xmpp:mam:2" -local n="urn:xmpp:forward:0"; -local c="urn:xmpp:delay"; -local d=require"util.uuid".generate; +local c="urn:xmpp:forward:0"; +local u="urn:xmpp:delay"; +local i=require"util.id".short; local m=require"util.datetime".parse; -local s=require"util.datetime".datetime; +local r=require"util.datetime".datetime; local o=require"util.dataforms".new; local h=require"util.rsm"; -local l={}; -local u=o{ +local d={}; +local l=o{ {name="FORM_TYPE";type="hidden";value=e;}; {name="with";type="jid-single";}; {name="start";type="text-single"}; {name="end";type="text-single";}; }; -function a.plugins.archive(i) -function i:query_archive(o,a,r) -local d=d(); -local o=t.iq{type="set",to=o} -:tag("query",{xmlns=e,queryid=d}); -local i,t=tonumber(a["start"]),tonumber(a["end"]); -a["start"]=i and s(i); -a["end"]=t and s(t); -o:add_child(u:form(a,"submit")); +function a.plugins.archive(o) +function o:query_archive(o,a,n) +local i=i(); +local o=t.iq{id=i,type="set",to=o} +:tag("query",{xmlns=e,queryid=i}); +local t,s=tonumber(a["start"]),tonumber(a["end"]); +a["start"]=t and r(t); +a["end"]=s and r(s); +o:add_child(l:form(a,"submit")); o:add_child(h.generate(a)); local t={}; -local function i(o) -local a=o:get_child("result",e); -if a and a.attr.queryid==d then -local e=a:get_child("forwarded",n); -e=e or o:get_child("forwarded",n); -local o=a.attr.id; -local a=e:get_child("delay",c); -local a=a and m(a.attr.stamp)or nil; -local e=e:get_child("message","jabber:client") -t[#t+1]={id=o,stamp=a,message=e}; +local function s(a) +local e=a:get_child("result",e); +if e and e.attr.queryid==i then +local a=e:get_child("forwarded",c); +local i=e.attr.id; +local e=a:get_child("delay",u); +local o=e and m(e.attr.stamp)or nil; +local e=a:get_child("message","jabber:client") +t[#t+1]={id=i,stamp=o,message=e}; return true end end -self:hook("message",i,1); +self:hook("message",s,1); self:send_iq(o,function(a) -self:unhook("message",i); +self:unhook("message",s); if a.attr.type=="error"then self:warn(table.concat({a:get_error()}," ")) -r(false,a:get_error()) +n(false,a:get_error()) return true; end local e=a:get_child("fin",e) if e then +t.complete=e.attr.complete=="true"or e.attr.complete=="1"; local e=h.get(e); -for a,e in pairs(e or l)do t[a]=e;end -end -r(t); +for e,a in pairs(e or d)do t[e]=a;end +end +n(t); return true end); end -local n={ +local i={ always=true,[true]="always", never=false,[false]="never", roster="roster", } -local function s(t) +local function n(t) local e={}; local a=t.attr.default; if a then -e[false]=n[a]; +e[false]=i[a]; end local a=t:get_child("always"); if a then @@ -7771,34 +9494,72 @@ end return e; end -local function h(o) +local function s(o) local a a,o[false]=o[false],nil; if a~=nil then -a=n[a]; +a=i[a]; end local i=t.stanza("prefs",{xmlns=e,default=a}) local a=t.stanza("always"); local e=t.stanza("never"); -for o,t in pairs(o)do -(t and a or e):tag("jid"):text(o):up(); +for t,o in pairs(o)do +(o and a or e):tag("jid"):text(t):up(); end return i:add_child(a):add_child(e); end -function i:archive_prefs_get(a) +function o:archive_prefs_get(a) self:send_iq(t.iq{type="get"}:tag("prefs",{xmlns=e}), function(e) if e and e.attr.type=="result"and e.tags[1]then -local t=s(e.tags[1]); +local t=n(e.tags[1]); a(t,e); else a(nil,e); end end); end -function i:archive_prefs_set(e,a) -self:send_iq(t.iq{type="set"}:add_child(h(e)),a); -end +function o:archive_prefs_set(e,a) +self:send_iq(t.iq{type="set"}:add_child(s(e)),a); +end +end +end) +package.preload['verse.plugins.browsing']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local a=require"verse"; +local o="urn:xmpp:browsing:0"; +function a.plugins.browsing(e) +e:add_plugin("pep"); +function e:browsing(t,i) +if type(t)=="string"then +t={uri=t}; +end +local a=a.stanza("page",{xmlns=o}) +for e,t in pairs(t)do +a:tag(e):text(t):up(); +end +return e:publish_pep(a,i); +end +e:hook_pep(o,function(a) +local t=a.item; +return e:event("browsing",{ +from=a.from; +description=t:get_child_text"description"; +keywords=t:get_child_text"keywords"; +title=t:get_child_text"title"; +uri=t:get_child_text"uri"; +}); +end); end end) package.preload['util.http']=(function(...) @@ -7813,56 +9574,68 @@ _M=e; return e; end -local t,n=string.format,string.char; -local o,s,h=pairs,ipairs,tonumber; -local i,d=table.insert,table.concat; -local function r(e) -return e and(e:gsub("[^a-zA-Z0-9.~_-]",function(e)return t("%%%02x",e:byte());end)); -end -local function a(e) -return e and(e:gsub("%%(%x%x)",function(e)return n(h(e,16));end)); -end -local function e(e) -return e and(e:gsub("%W",function(e) -if e~=" "then -return t("%%%02x",e:byte()); -else -return"+"; -end -end)); +local n,t=string.format,string.char; +local r,h=pairs,ipairs; +local i,o=table.insert,table.concat; +local e={}; +for a=0,255 do +local t=t(a); +local a=n("%%%02x",a); +e[t]=a; +e[a]=t; +e[a:upper()]=t; end local function n(t) -local a={}; -if t[1]then -for o,t in s(t)do -i(a,e(t.name).."="..e(t.value)); -end -else -for o,t in o(t)do -i(a,e(o).."="..e(t)); -end -end -return d(a,"&"); -end -local function s(e) +return t and(t:gsub("[^a-zA-Z0-9.~_-]",e)); +end +local function a(t) +return t and(t:gsub("%%%x%x",e)); +end +local function e(e) +return e and(n(e):gsub("%%20","+")); +end +local function s(a) +local t={}; +if a[1]then +for o,a in h(a)do +i(t,e(a.name).."="..e(a.value)); +end +else +for a,o in r(a)do +i(t,e(a).."="..e(o)); +end +end +return o(t,"&"); +end +local function h(e) if not e:match("=")then return a(e);end local o={}; -for e,t in e:gmatch("([^=&]*)=([^&]*)")do -e,t=e:gsub("%+","%%20"),t:gsub("%+","%%20"); -e,t=a(e),a(t); -i(o,{name=e,value=t}); -o[e]=t; +for t,e in e:gmatch("([^=&]*)=([^&]*)")do +t,e=t:gsub("%+","%%20"),e:gsub("%+","%%20"); +t,e=a(t),a(e); +i(o,{name=t,value=e}); +o[t]=e; end return o; end -local function o(e,t) +local function i(e,t) e=","..e:gsub("[ \t]",""):lower()..","; return e:find(","..t:lower()..",",1,true)~=nil; end +local function o(e,t) +if t then +if e:sub(-1,-1)~="/"then e=e.."/";end +else +if e:sub(-1,-1)=="/"then e=e:sub(1,-2);end +end +if e:sub(1,1)~="/"then e="/"..e;end +return e; +end return{ -urlencode=r,urldecode=a; -formencode=n,formdecode=s; -contains_token=o; +urlencode=n,urldecode=a; +formencode=s,formdecode=h; +contains_token=i; +normalize_path=o; }; end) package.preload['net.http.parser']=(function(...) @@ -7877,11 +9650,12 @@ _M=e; return e; end -local m=tonumber; -local a=assert; -local v=require"socket.url".parse; +local u=tonumber; +local g=assert; +local z=require"socket.url".parse; local t=require"util.http".urldecode; -local function b(e) +local j=require"util.dbuffer"; +local function x(e) e=t((e:gsub("//+","/"))); if e:sub(1,1)~="/"then e="/"..e; @@ -7899,145 +9673,188 @@ end return e; end -local y={}; -function y.new(c,h,e,y) -local d=true; -if not e or e=="server"then d=false;else a(e=="client","Invalid parser type");end -local e=""; -local p,o,r; -local s=nil; +local q={}; +function q.new(l,s,e,h) +local f=true; +if not e or e=="server"then f=false;else g(e=="client","Invalid parser type");end +local v=u(h and h().body_size_limit)or 10*1024*1024; +local n=u(h and h().head_size_limit)or 10*1024; +local k=u(h and h().buffer_size_limit)or v*2; +local a=j.new(k); +local d; +local r=nil; +local e; local t; -local a; -local u; -local n; +local w; +local o; return{ -feed=function(l,i) -if n then return nil,"parse has failed";end +feed=function(c,i) +if o then return nil,"parse has failed";end if not i then -if s and d and not a then -t.body=e; -c(t); -elseif e~=""then -n=true;return h(); +if r and f and not t then +a:collapse(); +e.body=a:read_chunk()or""; +e.partial=nil; +l(e); +r=nil; +elseif a:length()~=0 then +o=true;return s("unexpected-eof"); end return; end -e=e..i; -while#e>0 do -if s==nil then -local f=e:find("\r\n\r\n",nil,true); -if not f then return;end -local w,r,l,i,g; -local c; -local o={}; -for t in e:sub(1,f+1):gmatch("([^\r\n]+)\r\n")do -if c then +if not a:write(i)then o=true;return s("max-buffer-size-exceeded");end +while a:length()>0 do +if r==nil then +local b=a:sub(1,n):find("\r\n\r\n",nil,true); +if not b then return;end +local p,c,m,i,g; +local y; +local n={}; +for t in a:read(b+3):gmatch("([^\r\n]+)\r\n")do +if y then local e,t=t:match("^([^%s:]+): *(.*)$"); -if not e then n=true;return h("invalid-header-line");end +if not e then o=true;return s("invalid-header-line");end e=e:lower(); -o[e]=o[e]and o[e]..","..t or t; -else -c=t; -if d then -l,i,g=t:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); -i=m(i); -if not i then n=true;return h("invalid-status-line");end -u=not -((y and y().method=="HEAD") +n[e]=n[e]and n[e]..","..t or t; +else +y=t; +if f then +m,i,g=t:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$"); +i=u(i); +if not i then o=true;return s("invalid-status-line");end +w=not +((h and h().method=="HEAD") or(i==204 or i==304 or i==301) or(i>=100 and i<200)); else -w,r,l=t:match("^(%w+) (%S+) HTTP/(1%.[01])$"); -if not w then n=true;return h("invalid-status-line");end -end -end -end -if not c then n=true;return h("invalid-status-line");end -p=u and o["transfer-encoding"]=="chunked"; -a=m(o["content-length"]); -if d then -if not u then a=0;end -t={ +p,c,m=t:match("^(%w+) (%S+) HTTP/(1%.[01])$"); +if not p then o=true;return s("invalid-status-line");end +end +end +end +if not y then o=true;return s("invalid-status-line");end +d=w and n["transfer-encoding"]=="chunked"; +t=u(n["content-length"]); +if f then +if not w then t=0;end +e={ code=i; -httpversion=l; -headers=o; -body=u and""or nil; -responseversion=l; -responseheaders=o; -}; -else -local e; -if r:byte()==47 then -local a,t=r:match("([^?]*).?(.*)"); -if t==""then t=nil;end -e={path=a,query=t}; -else -e=v(r); -if not(e and e.path)then n=true;return h("invalid-url");end -end -r=b(e.path); -o.host=e.host or o.host; -a=a or 0; -t={ -method=w; -url=e; -path=r; -httpversion=l; -headers=o; -body=nil; -}; -end -e=e:sub(f+4); -s=true; -end -if s then +httpversion=m; +headers=n; +body=false; +body_length=t; +chunked=d; +partial=true; +responseversion=m; +responseheaders=n; +}; +else +local a; +if c:byte()==47 then +local t,e=c:match("([^?]*).?(.*)"); +if e==""then e=nil;end +a={path=t,query=e}; +else +a=z(c); +if not(a and a.path)then o=true;return s("invalid-url");end +end +c=x(a.path); +n.host=a.host or n.host; +t=t or 0; +e={ +method=p; +url=a; +path=c; +httpversion=m; +headers=n; +body=false; +body_sink=nil; +chunked=d; +partial=true; +}; +end +if not t or t>v then +l(e); +if not e.body_sink and(t and t>v)then +o=true; +return s("content-length-limit-exceeded"); +end +end +if d and not e.body_sink then +l(e); +if not e.body_sink then +e.body_buffer=j.new(k); +end +end +r=true; +end +if r then if d then -if p then -if not e:find("\r\n",nil,true)then -return; -end -if not o then -o,r=e:match("^(%x+)[^\r\n]*\r\n()"); -o=o and m(o,16); -if not o then n=true;return h("invalid-chunk-size");end -end -if o==0 and e:find("\r\n\r\n",r-2,true)then -s,o=nil,nil; -e=e:gsub("^.-\r\n\r\n",""); -c(t); -elseif#e-r-2>=o then -t.body=t.body..e:sub(r,r+(o-1)); -e=e:sub(r+o+2); -o,r=nil,nil; +local n=a:sub(1,512); +local t,i=n:match("^(%x+)[^\r\n]*\r\n()"); +if not t then return;end +t=t and u(t,16); +if not t then o=true;return s("invalid-chunk-size");end +if t==0 and n:find("\r\n\r\n",i-2,true)then +local t=e.body_buffer; +if t then +e.body_buffer=nil; +t:collapse(); +e.body=t:read_chunk()or""; +end +a:collapse(); +local t=a:read_chunk(); +t=t:gsub("^.-\r\n\r\n",""); +a:write(t); +r,d=nil,nil; +e.partial=nil; +l(e); +elseif a:length()-i-1>=t then +a:discard(i-1); +(e.body_sink or e.body_buffer):write(a:read(t)); +a:discard(2); else break; end -elseif a and#e>=a then -if t.code==101 then -t.body,e=e,""; -else -t.body,e=e:sub(1,a),e:sub(a+1); -end -s=nil;c(t); +elseif e.body_sink then +local i=a:read_chunk(t); +while i and(not t or t>0)do +if e.body_sink:write(i)then +if t then +t=t-#i; +end +i=a:read_chunk(t); +else +o=true; +return s("body-sink-write-failure"); +end +end +if t==0 then +r=nil; +e.partial=nil; +l(e); +end +elseif not t or a:length()>=t then +g(not d) +e.body=t and a:read(t)or a:read_chunk()or""; +r=nil; +e.partial=nil; +l(e); else break; end -elseif#e>=a then -t.body,e=e:sub(1,a),e:sub(a+1); -s=nil;c(t); else break; end end -end end; }; end -return y; +return q; end) package.preload['net.http']=(function(...) local _ENV=_ENV; -local function a(t,...) +local function e(t,...) local e=package.loaded[t]or _ENV[t]or{_NAME=t}; package.loaded[t]=e; for t=1,select("#",...)do @@ -8047,163 +9864,517 @@ _M=e; return e; end -local g=require"socket" -local b=require"util.encodings".base64.encode; -local l=require"socket.url" -local u=require"net.http.parser".new; -local s=require"util.http"; -local k=pcall(require,"ssl"); -local q=require"net.server" -local d,o=table.insert,table.concat; -local m=pairs; -local y,h,w,f,r= -tonumber,tostring,xpcall,select,debug.traceback; -local p,v=assert,error -local c=require"util.logger".init("http"); -a"http" -local i={}; -local n={default_port=80,default_mode="*a"}; -function n.onconnect(t) -local e=i[t]; -local a={e.method or"GET"," ",e.path," HTTP/1.1\r\n"}; -if e.query then -d(a,4,"?"..e.query); -end -t:write(o(a)); -local a={[2]=": ",[4]="\r\n"}; -for e,i in m(e.headers)do -a[1],a[3]=e,i; -t:write(o(a)); -end -t:write("\r\n"); -if e.body then -t:write(e.body); -end -end -function n.onincoming(a,t) -local e=i[a]; -if not e then -c("warn","Received response from connection %s with no request attached!",h(a)); -return; -end -if t and e.reader then -e:reader(t); -end -end -function n.ondisconnect(t,a) -local e=i[t]; -if e and e.conn then -e:reader(nil,a); -end -i[t]=nil; -end -function n.ondetach(e) -i[e]=nil; -end -local function x(e,a,i) +local O=require"util.encodings".base64.encode; +local A=require"socket.url" +local E=require"net.http.parser".new; +local d=require"util.http"; +local T=require"util.events"; +local _=require"util.x509".verify_identity; +local k=require"util.promise"; +local g=require"net.http.errors"; +local b=require"net.resolvers.basic"; +local v=require"net.connect".connect; +local y=pcall(require,"ssl"); +local s,f=table.insert,table.concat; +local l=pairs; +local p,c,t= +tonumber,tostring,debug.traceback; +local q=os.time; +local z=require"util.xpcall".xpcall; +local w=error +local o=require"util.logger".init("http"); +local _ENV=nil; +local a={}; +local function x(e)return(c(e):match("%x+$"));end +local i={default_port=80,default_mode="*a"}; +local function j(e)o("error","Traceback[http]: %s",t(c(e),2));return e;end +local function h(e,t,...) +if not t then +o("error","Request '%s': error in callback: %s",e.id,(...)); +if not e.suppress_errors then +w(...); +end +end +return...; +end +local function r(e) +local t=e.conn; +if t then +e.conn=nil; +t:close() +end +end +local function m(e,t) +if e.callback then +e.callback(t or"cancelled",0,e); +e.callback=nil; +end +if e.conn then +r(e); +end +end +local function u(e,n,t) if not e.parser then -local function o(t) +local function i(t) if e.callback then e.callback(t or"connection-closed",0,e); e.callback=nil; end -destroy_request(e); -end -if not a then -o(i); +r(e); +end +if not n then +i(t); return; end -local function a(t) +local a; +local function n(t) +if t.partial then +o("debug","Request '%s': partial response (%s%s)", +e.id, +t.chunked and"chunked, "or"", +t.body_length and("%d bytes"):format(t.body_length)or"unknown length" +); +if e.streaming_handler then +o("debug","Request '%s': Streaming via handler",e.id); +t.body_sink,a=e.streaming_handler(t); +end +return; +elseif a then +o("debug","Request '%s': Finalizing response stream"); +a(t); +end if e.callback then e.callback(t.body,t.code,t,e); e.callback=nil; end -destroy_request(e); +r(e); end local function t() return e; end -e.parser=u(a,o,"client",t); -end -e.parser:feed(a); -end -local function j(e)c("error","Traceback[http]: %s",r(h(e),2));end -function request(e,t,r) -local e=l.parse(e); +e.parser=E(n,i,"client",t); +end +e.parser:feed(n); +end +function i.onconnect(t) +local e=a[t]; +e.write=function(...)return e.conn:write(...);end +local r=e.callback; +e.callback=function(i,a,n,s) +do +local t={http=e.http,url=e.url,request=e,response=n,content=i,code=a,callback=e.callback}; +e.http.events.fire_event("response",t); +i,a,n=t.content,t.code,t.response; +end +o("debug","Request '%s': Calling callback, status %s",e.id,a or"---"); +return h(e.id,z(r,j,i,a,n,s)); +end +e.reader=u; +e.state="status"; +e.cancel=m; +a[e.conn]=e; +if not e.insecure and t:ssl()then +local a=t:socket(); +local o=a.getpeerverification and a:getpeerverification(); +if not o then +e.callback("certificate-chain-invalid",0,e); +e.callback=nil; +t:close(); +return; +end +local a=a.getpeercertificate and a:getpeercertificate(); +if not a or not _(e.host,false,a)then +e.callback("certificate-verify-failed",0,e); +e.callback=nil; +t:close(); +return; +end +end +local a={e.method or"GET"," ",e.path," HTTP/1.1\r\n"}; +if e.query then +s(a,4,"?"..e.query); +end +for e,t in l(e.headers)do +s(a,e..": "..t.."\r\n"); +end +s(a,"\r\n") +t:write(f(a)); +if e.body then +t:write(e.body); +end +end +function i.onincoming(t,i) +local e=a[t]; +if not e then +o("warn","Received response from connection %s with no request attached!",t); +return; +end +if i and e.reader then +e:reader(i); +end +end +function i.ondisconnect(t,o) +local e=a[t]; +if e and e.conn then +e:reader(nil,o or"closed"); +end +a[t]=nil; +end +function i.onattach(e,t) +a[e]=t; +t.conn=e; +end +function i.ondetach(e) +a[e]=nil; +end +function i.onfail(e,t) +e.http.events.fire_event("request-connection-error",{http=e.http,request=e,url=e.url,err=t}); +e.callback(t or"connection failed",0,e); +end +local function f(a,s,t,n) +local e=A.parse(s); if not(e and e.host)then -r(nil,0,e); +n("invalid-url",0,e); return nil,"invalid-url"; end +e.url=s; +e.http=a; +e.time=q(); if not e.path then e.path="/"; end -local l,o,s; -local u,a=e.host,e.port; -local d=u; -if(a=="80"and e.scheme=="http") -or(a=="443"and e.scheme=="https")then -a=nil; -elseif a then -d=d..":"..a; -end -o={ -["Host"]=d; +e.id=t and t.id or x(e); +do +local o={http=a,url=s,request=e,options=t,callback=n}; +local a=a.events.fire_event("pre-request",o); +if a then +return a; +end +e,s,t,e.callback=o.request,o.url,o.options,o.callback; +end +local d,h,u; +local m,n=e.host,e.port; +local r=m; +if(n=="80"and e.scheme=="http") +or(n=="443"and e.scheme=="https")then +n=nil; +elseif n then +r=r..":"..n; +end +h={ +["Host"]=r; ["User-Agent"]="Prosody XMPP Server"; }; if e.userinfo then -o["Authorization"]="Basic "..b(e.userinfo); +h["Authorization"]="Basic "..O(e.userinfo); end if t then e.onlystatus=t.onlystatus; -s=t.body; -if s then -l="POST"; -o["Content-Length"]=h(#s); -o["Content-Type"]="application/x-www-form-urlencoded"; -end -if t.method then l=t.method;end +u=t.body; +if u then +d="POST"; +h["Content-Length"]=c(#u); +h["Content-Type"]="application/x-www-form-urlencoded"; +end +if t.method then d=t.method;end if t.headers then -for e,t in m(t.headers)do -o[e]=t; -end -end -end -e.method,e.headers,e.body=l,o,s; +for t,e in l(t.headers)do +h[t]=e; +end +end +e.insecure=t.insecure; +e.suppress_errors=t.suppress_errors; +e.streaming_handler=t.streaming_handler; +end +o("debug","Making %s %s request '%s' to %s",e.scheme:upper(),d or"GET",e.id,(t and t.suppress_url and r)or s); +e.method,e.headers,e.body=d,h,u; local o=e.scheme=="https"; -if o and not k then -v("SSL not available, unable to contact https URL"); -end -local h=a and y(a)or(o and 443 or 80); -local a=g.tcp(); -a:settimeout(10); -local d,s=a:connect(u,h); -if not d and s~="timeout"then -r(nil,0,e); -return nil,s; -end -local s=false; +if o and not y then +w("SSL not available, unable to contact https URL"); +end +local r=n and p(n)or(o and 443 or 80); +local n=a.options and a.options.use_dane; +local h=false; +if o then +h=t and t.sslctx or a.options and a.options.sslctx; +if t and t.use_dane~=nil then +n=t.use_dane; +end +end +local t=b.new(m,r,"tcp",{servername=e.host;use_dane=n}); +v(t,i,{sslctx=h},e); +a.events.fire_event("request",{http=a,request=e,url=s}); +return e; +end +local function e(t) +local e={ +options=t; +request=function(o,a,t,e) +if e~=nil then +return f(o,a,t,e); +else +return k.new(function(i,n) +f(o,a,t,function(t,a,e,o) +if a==0 then +n(g.new(t,{request=e})); +else +e.request=o; +i(e); +end +end); +end); +end +end; +new=t and function(o) +local a={}; +for e,t in l(t)do a[e]=t;end if o then -s=t and t.sslctx or{mode="client",protocol="sslv23",options={"no_sslv2","no_sslv3"}}; -end -e.handler,e.conn=p(q.wrapclient(a,u,h,n,"*a",s)); -e.write=function(...)return e.handler:write(...);end -e.callback=function(i,t,o,a)c("debug","Calling callback, status %s",t or"---");return f(2,w(function()return r(i,t,o,a)end,j));end -e.reader=x; -e.state="status"; -i[e.handler]=e; -return e; -end -function destroy_request(e) -if e.conn then -e.conn=nil; -e.handler:close() -end -end -local e,t=s.urlencode,s.urldecode; -local o,a=s.formencode,s.formdecode; -_M.urlencode,_M.urldecode=e,t; -_M.formencode,_M.formdecode=o,a; -return _M; +for t,e in l(o)do a[t]=e;end +end +return e(a); +end or e; +events=T.new(); +}; +return e; +end +local t=e({ +sslctx={mode="client",protocol="sslv23",options={"no_sslv2","no_sslv3"},alpn="http/1.1",verify="peer"}; +suppress_errors=true; +}); +return{ +request=function(e,a,o) +return t:request(e,a,o); +end; +default=t; +new=e; +events=t.events; +urlencode=d.urlencode; +urldecode=d.urldecode; +formencode=d.formencode; +formdecode=d.formdecode; +destroy_request=r; +features={ +sni=true; +}; +}; +end) +package.preload['util.x509']=(function(...) +local _ENV=_ENV; +local function e(t,...) +local e=package.loaded[t]or _ENV[t]or{_NAME=t}; +package.loaded[t]=e; +for t=1,select("#",...)do +(select(t,...))(e); +end +_ENV=e; +_M=e; +return e; +end +local o=require"util.encodings".stringprep.nameprep; +local r=require"util.encodings".idna.to_ascii; +local y=require"util.encodings".idna.to_unicode; +local c=require"util.encodings".base64; +local e=require"util.logger".init("x509"); +local p=require"util.multitable"; +local v=string.format; +local d=ipairs; +local _ENV=nil; +local u="2.5.4.3"; +local h="2.5.29.17"; +local s="1.3.6.1.5.5.7.8.5"; +local n="1.3.6.1.5.5.7.8.7"; +local function l(o,a) +local t=r(o) +if t==nil then +e("info","Host %s failed IDNA ToASCII operation",o) +return false +end +t=t:lower() +local o=t:gsub("^[^.]+%.","") +for i=1,#a do +local a=a[i] +if t==a:lower()then +e("debug","Cert dNSName %s matched hostname",a); +return true +end +if a:match("^%*%.")then +local t=a:gsub("^[^.]+%.","") +if o==t:lower()then +e("debug","Cert dNSName %s matched hostname",a); +return true +end +end +end +return false +end +local function w(a,t) +local i=o(a) +for a=1,#t do +local t=t[a] +if t:match("[@/]")then +e("debug","Ignoring xmppAddr %s because it's not a bare domain",t) +else +local a=o(t) +if a==nil then +e("info","Ignoring xmppAddr %s, failed nameprep!",t) +else +if i==a then +e("debug","Cert xmppAddr %s matched hostname",t) +return true +end +end +end +end +return false +end +local function f(i,o,t) +local a=r(i) +if a==nil then +e("info","Host %s failed IDNA ToASCII operation",i); +return false +end +if o:match("^_")==nil then o="_"..o end +a=a:lower(); +local n=a:gsub("^[^.]+%.","") +for i=1,#t do +local i,t=t[i]:match("^(_[^.]+)%.(.*)"); +if o==i then +if a==t:lower()then +e("debug","Cert SRVName %s matched hostname",t); +return true; +end +if t:match("^%*%.")then +local a=t:gsub("^[^.]+%.","") +if n==a:lower()then +e("debug","Cert SRVName %s matched hostname",t) +return true +end +end +if a==t:lower()then +e("debug","Cert SRVName %s matched hostname",t); +return true +end +end +end +return false +end +local function m(o,a,i) +if i.setencode then +i:setencode("utf8"); +end +local t=i:extensions() +if t[h]then +local e=t[h]; +local t=false +if e[s]then +t=true +if a=="_xmpp-client"or a=="_xmpp-server"then +if w(o,e[s])then return true end +end +end +if e[n]then +t=true +if a and f(o,a,e[n])then return true end +end +if e["dNSName"]then +t=true +if l(o,e["dNSName"])then return true end +end +if e["uniformResourceIdentifier"]then +t=true +end +if t then return false end +end +local a=i:subject() +local t=nil +for o=1,#a do +local a=a[o] +if a["oid"]==u then +if t then +e("info","Certificate has multiple common names") +return false +end +t=a["value"]; +end +end +if t then +return l(o,{t}) +end +return false +end +local function l(i) +if i.setencode then +i:setencode("utf8"); +end +local a=p.new(); +local e=i:extensions(); +local t=e[h]; +if t then +if t["dNSName"]then +for t,e in d(t["dNSName"])do +local t=e:sub(1,2)=="*."; +if t then e=e:sub(3);end +e=y(o(e)); +if e then +if t then e="*."..e;end +a:set(e,"*",true); +end +end +end +if t[s]then +for t,e in d(t[s])do +e=o(e); +if e then +a:set(e,"xmpp-client",true); +a:set(e,"xmpp-server",true); +end +end +end +if t[n]then +for t,e in d(t[n])do +local t,e=e:match("^_([^.]+)%.(.*)"); +if t then +e=o(e); +if e then +a:set(e,t,true); +end +end +end +end +end +local e=i:subject(); +for t=1,#e do +local e=e[t]; +if e.oid==u then +local e=o(e.value); +if e and r(e)then +a:set(e,"*",true); +end +end +end +return a.data; +end +local t="%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. +"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-"; +local function a(e) +local e,t=e:match(t); +if e and t then +return c.decode(t),e; +end +end +local n=('.'):rep(64); +local i="-----BEGIN %s-----\n%s\n-----END %s-----\n" +local function o(t,e) +e=e and e:upper()or"CERTIFICATE"; +t=c.encode(t); +return v(i,e,t:gsub(n,'%0\n',(#t-1)/64),e); +end +return{ +verify_identity=m; +get_identities=l; +pem2der=a; +der2pem=o; +}; end) package.preload['verse.bosh']=(function(...) local _ENV=_ENV; @@ -8217,15 +10388,15 @@ _M=e; return e; end -local h=require"util.xmppstream".new; -local i=require"util.stanza"; +local n=require"util.xmppstream".new; +local o=require"util.stanza"; require"net.httpclient_listener"; -local o=require"net.http"; +local a=require"net.http"; local e=setmetatable({},{__index=verse.stream_mt}); e.__index=e; -local n="http://etherx.jabber.org/streams"; +local h="http://etherx.jabber.org/streams"; local s="http://jabber.org/protocol/httpbind"; -local a=5; +local t=5; function verse.new_bosh(a,t) local t={ bosh_conn_pool={}; @@ -8247,7 +10418,7 @@ end function e:send(e) self:debug("Putting into BOSH send buffer: %s",tostring(e)); -self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1]=i.clone(e); +self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1]=o.clone(e); self:flush(); end function e:flush() @@ -8259,20 +10430,20 @@ self:debug("Flushing..."); local e=self:_make_body(); local t=self.bosh_outgoing_buffer; -for a,o in ipairs(t)do -e:add_child(o); -t[a]=nil; +for o,a in ipairs(t)do +e:add_child(a); +t[o]=nil; end self:_make_request(e); else self:debug("Decided not to flush."); end end -function e:_make_request(i) -local e,t=o.request(self.bosh_url,{body=tostring(i)},function(o,e,t) +function e:_make_request(o) +local e,t=a.request(self.bosh_url,{body=tostring(o)},function(i,e,a) if e~=0 then self.inactive_since=nil; -return self:_handle_response(o,e,t); +return self:_handle_response(i,e,a); end local e=os.time(); if not self.inactive_since then @@ -8281,17 +10452,17 @@ return self:_disconnected(); else self:debug("%d seconds left to reconnect, retrying in %d seconds...", -self.bosh_max_inactivity-(e-self.inactive_since),a); -end -timer.add_task(a,function() +self.bosh_max_inactivity-(e-self.inactive_since),t); +end +timer.add_task(t,function() self:debug("Retrying request..."); -for e,a in ipairs(self.bosh_waiting_requests)do -if a==t then -table.remove(self.bosh_waiting_requests,e); +for t,e in ipairs(self.bosh_waiting_requests)do +if e==a then +table.remove(self.bosh_waiting_requests,t); break; end end -self:_make_request(i); +self:_make_request(o); end); end); if e then @@ -8313,7 +10484,7 @@ e.attr.from=self.jid; e.attr.to=self.host; e.attr.secure='true'; -o.request(self.bosh_url,{body=tostring(e)},function(e,t) +a.request(self.bosh_url,{body=tostring(e)},function(e,t) if t==0 then return self:_disconnected(); end @@ -8333,19 +10504,19 @@ self:_handle_response_payload(e); end); end -function e:_handle_response(o,t,e) +function e:_handle_response(a,t,e) if self.bosh_waiting_requests[1]~=e then self:warn("Server replied to request that wasn't the oldest"); -for a,t in ipairs(self.bosh_waiting_requests)do -if t==e then -self.bosh_waiting_requests[a]=nil; +for t,a in ipairs(self.bosh_waiting_requests)do +if a==e then +self.bosh_waiting_requests[t]=nil; break; end end else table.remove(self.bosh_waiting_requests,1); end -local e=self:_parse_response(o); +local e=self:_parse_response(a); if e then self:_handle_response_payload(e); end @@ -8355,7 +10526,7 @@ local e=t.tags; for t=1,#e do local e=e[t]; -if e.attr.xmlns==n then +if e.attr.xmlns==h then self:event("stream-"..e.name,e); elseif e.attr.xmlns then self:event("stream/"..e.attr.xmlns,e); @@ -8381,7 +10552,7 @@ return; end local t={notopen=true,stream=self}; -local a=h(t,a); +local a=n(t,a); a:feed(e); return t.payload; end @@ -8413,30 +10584,46 @@ return e; end local t=require"verse"; -local o=t.stream_mt; +local i=t.stream_mt; local d=require"util.jid".split; -local h=require"net.adns"; -local e=require"lxp"; +local r=require"net.adns"; local a=require"util.stanza"; +local o=require"util.id".short; +math.randomseed((require"socket".gettime()*1e6)%2147483648); t.message,t.presence,t.iq,t.stanza,t.reply,t.error_reply= a.message,a.presence,a.iq,a.stanza,a.reply,a.error_reply; -local r=require"util.xmppstream".new; +function t.iq(e) +if not e.id then +e.id=o(); +end +return a.iq(e); +end +local s=require"util.xmppstream".new; local n="http://etherx.jabber.org/streams"; -local function s(t,e) -return t.prioritye.weight); -end -local i={ +local function h(e,t) +if e.priority==t.priority then +if not e.weight_r then +e.weight_r=math.random(); +end +if not t.weight_r then +t.weight_r=math.random(); +end +return(1+e.weight)*e.weight_r>(1+t.weight)*t.weight_r; +end +return e.priority"); @@ -8445,7 +10632,7 @@ e:event("closed"); return e:close("stream closed") end -function i.handlestanza(t,e) +function o.handlestanza(t,e) if e.attr.xmlns==n then return t:event("stream-"..e.name,e); elseif e.attr.xmlns then @@ -8453,7 +10640,7 @@ end return t:event("stanza",e); end -function i.error(a,t,e) +function o.error(a,t,e) if a:event(t,e)==nil then if e then local t=e:get_child(nil,"urn:ietf:params:xml:ns:xmpp-streams"); @@ -8464,22 +10651,22 @@ end end end -function o:reset() +function i:reset() if self.stream then self.stream:reset(); else -self.stream=r(self,i); +self.stream=s(self,o); end self.notopen=true; return true; end -function o:connect_client(e,a) -self.jid,self.password=e,a; +function i:connect_client(e,i,a,o) +self.jid,self.password=e,i; +self.client_key,self.server_key=a,o; self.username,self.host,self.resource=d(e); self:add_plugin("tls"); self:add_plugin("sasl"); self:add_plugin("bind"); -self:add_plugin("session"); function self.data(t,e) local t,a=self.stream:feed(e); if t then return;end @@ -8488,13 +10675,14 @@ end self:hook("connected",function()self:reopen();end); self:hook("incoming-raw",function(e)return self.data(self.conn,e);end); +self:hook("read-timeout",function()self:send(" ");return true;end,-1); self.curr_id=0; self.tracked_iqs={}; -self:hook("stanza",function(t) -local e,a=t.attr.id,t.attr.type; -if e and t.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[e]then -self.tracked_iqs[e](t); -self.tracked_iqs[e]=nil; +self:hook("stanza",function(e) +local t,a=e.attr.id,e.attr.type; +if t and e.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[t]then +self.tracked_iqs[t](e); +self.tracked_iqs[t]=nil; return true; end end); @@ -8534,28 +10722,28 @@ end self:hook("session-success",e,-1) self:hook("bind-success",e,-1); -local t=self.close; -function self:close(e) -self.close=t; +local e=self.close; +function self:close(t) +self.close=e; if not self.closed then self:send(""); self.closed=true; else -return self:close(e); +return self:close(t); end end local function a() self:connect(self.connect_host or self.host,self.connect_port or 5222); end if not(self.connect_host or self.connect_port)then -h.lookup(function(t) +r.lookup(function(t) if t then local e={}; self.srv_hosts=e; for a,t in ipairs(t)do table.insert(e,t.srv); end -table.sort(e,s); +table.sort(e,h); local t=e[1]; self.srv_choice=1; if t then @@ -8581,20 +10769,16 @@ a(); end end -function o:reopen() +function i:reopen() self:reset(); self:send(a.stanza("stream:stream",{to=self.host,["xmlns:stream"]='http://etherx.jabber.org/streams', -xmlns="jabber:client",version="1.0"}):top_tag()); -end -function o:send_iq(t,a) -local e=self:new_id(); -self.tracked_iqs[e]=a; -t.attr.id=e; -self:send(t); -end -function o:new_id() -self.curr_id=self.curr_id+1; -return tostring(self.curr_id); +xmlns="jabber:client",version="1.0",["xml:lang"]=self.lang}):top_tag()); +end +function i:send_iq(e,a) +local t=e.attr.id or uuid.generate(); +self.tracked_iqs[t]=a; +e.attr.id=t; +self:send(e); end end) package.preload['verse.component']=(function(...) @@ -8609,15 +10793,15 @@ _M=e; return e; end -local t=require"verse"; -local a=t.stream_mt; -local h=require"util.jid".split; +local a=require"verse"; +local t=a.stream_mt; +local d=require"util.jid".split; local e=require"lxp"; local o=require"util.stanza"; -local d=require"util.hashes".sha1; -t.message,t.presence,t.iq,t.stanza,t.reply,t.error_reply= +local r=require"util.hashes".sha1; +a.message,a.presence,a.iq,a.stanza,a.reply,a.error_reply= o.message,o.presence,o.iq,o.stanza,o.reply,o.error_reply; -local r=require"util.xmppstream".new; +local h=require"util.xmppstream".new; local s="http://etherx.jabber.org/streams"; local i="jabber:component:accept"; local n={ @@ -8642,59 +10826,59 @@ end return t:event("stanza",e); end -function a:reset() +function t:reset() if self.stream then self.stream:reset(); else -self.stream=r(self,n); +self.stream=h(self,n); end self.notopen=true; return true; end -function a:connect_component(e,n) +function t:connect_component(e,n) self.jid,self.password=e,n; -self.username,self.host,self.resource=h(e); -function self.data(t,e) -local t,o=self.stream:feed(e); -if t then return;end -a:debug("Received invalid XML (%s) %d bytes: %s",tostring(o),#e,e:sub(1,300):gsub("[\r\n]+"," ")); -a:close("xml-not-well-formed"); +self.username,self.host,self.resource=d(e); +function self.data(a,e) +local a,o=self.stream:feed(e); +if a then return;end +t:debug("Received invalid XML (%s) %d bytes: %s",tostring(o),#e,e:sub(1,300):gsub("[\r\n]+"," ")); +t:close("xml-not-well-formed"); end self:hook("incoming-raw",function(e)return self.data(self.conn,e);end); self.curr_id=0; self.tracked_iqs={}; -self:hook("stanza",function(e) -local t,a=e.attr.id,e.attr.type; -if t and e.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[t]then -self.tracked_iqs[t](e); -self.tracked_iqs[t]=nil; +self:hook("stanza",function(t) +local e,a=t.attr.id,t.attr.type; +if e and t.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[e]then +self.tracked_iqs[e](t); +self.tracked_iqs[e]=nil; return true; end end); self:hook("stanza",function(e) -local a; +local t; if e.attr.xmlns==nil or e.attr.xmlns=="jabber:client"then if e.name=="iq"and(e.attr.type=="get"or e.attr.type=="set")then local o=e.tags[1]and e.tags[1].attr.xmlns; if o then -a=self:event("iq/"..o,e); -if not a then -a=self:event("iq",e); -end -end -if a==nil then -self:send(t.error_reply(e,"cancel","service-unavailable")); +t=self:event("iq/"..o,e); +if not t then +t=self:event("iq",e); +end +end +if t==nil then +self:send(a.error_reply(e,"cancel","service-unavailable")); return true; end else -a=self:event(e.name,e); -end -end -return a; +t=self:event(e.name,e); +end +end +return t; end,-1); self:hook("opened",function(e) print(self.jid,self.stream_id,e.id); -local e=d(self.stream_id..n,true); +local e=r(self.stream_id..n,true); self:send(o.stanza("handshake",{xmlns=i}):text(e)); self:hook("stream/"..i,function(e) if e.name=="handshake"then @@ -8709,12 +10893,12 @@ self:connect(self.connect_host or self.host,self.connect_port or 5347); self:reopen(); end -function a:reopen() +function t:reopen() self:reset(); self:send(o.stanza("stream:stream",{to=self.jid,["xmlns:stream"]='http://etherx.jabber.org/streams', xmlns=i,version="1.0"}):top_tag()); end -function a:close(t) +function t:close(t) if not self.notopen then self:send(""); end @@ -8722,13 +10906,13 @@ self.conn:close(); e(conn,t); end -function a:send_iq(t,a) -local e=self:new_id(); -self.tracked_iqs[e]=a; -t.attr.id=e; -self:send(t); -end -function a:new_id() +function t:send_iq(e,a) +local t=self:new_id(); +self.tracked_iqs[t]=a; +e.attr.id=t; +self:send(e); +end +function t:new_id() self.curr_id=self.curr_id+1; return tostring(self.curr_id); end @@ -8739,6 +10923,7 @@ local a=require"net.server"; local s=require"util.events"; local o=require"util.logger"; +local n=require"util.format".format; local e={}; e.server=a; local t={}; @@ -8769,30 +10954,26 @@ e.logger=o.init; e.new_logger=o.init; e.log=e.logger("verse"); -local function n(t,...) -local e,a,o=0,{...},select('#',...); -return(t:gsub("%%(.)",function(t)if e<=o then e=e+1;return tostring(a[e]);end end)); -end function e.set_log_handler(e,t) t=t or{"debug","info","warn","error"}; o.reset(); if io.type(e)=="file"then local o=e; -function e(t,a,e) -o:write(t,"\t",a,"\t",e,"\n"); +function e(a,t,e) +o:write(a,"\t",t,"\t",e,"\n"); end end if e then -local function i(o,a,t,...) -return e(o,a,n(t,...)); +local function a(t,a,o,...) +return e(t,a,n(o,...)); end for t,e in ipairs(t)do -o.add_level_sink(e,i); -end -end -end -function e._default_log_handler(a,t,o) -return io.stderr:write(a,"\t",t,"\t",o,"\n"); +o.add_level_sink(e,a); +end +end +end +function e._default_log_handler(a,o,t) +return io.stderr:write(a,"\t",o,"\t",t,"\n"); end e.set_log_handler(e._default_log_handler,{"error"}); local function o(t) @@ -8811,6 +10992,9 @@ function e.quit() return a.setquitting("once"); end +function e.tls_builder(...) +return a.tls_builder(...); +end function t:listen(t,o) t=t or"localhost"; o=o or 0; @@ -8821,18 +11005,18 @@ end return e,a; end -function t:connect(i,o) -i=i or"localhost"; -o=tonumber(o)or 5222; +function t:connect(o,i) +o=o or"localhost"; +i=tonumber(i)or 5222; local n=h.tcp() n:settimeout(0); n:setoption("keepalive",true); -local s,t=n:connect(i,o); +local s,t=n:connect(o,i); if not s and t~="timeout"then -self:warn("connect() to %s:%d failed: %s",i,o,t); +self:warn("connect() to %s:%d failed: %s",o,i,t); return self:event("disconnected",{reason=t})or false,t; end -local e=a.wrapclient(n,i,o,e.new_listener(self),"*a"); +local e=a.wrapclient(n,o,i,e.new_listener(self),"*a"); if not e then self:warn("connection initialisation failed: %s",t); return self:event("disconnected",{reason=t})or false,t; @@ -8855,6 +11039,7 @@ return; end local e=self.conn.disconnect(); +self:event("shutdown"); self.conn:close(); e(self.conn,t); end @@ -8884,20 +11069,18 @@ e.events=s.new(); e.hook,e.unhook=t.hook,t.unhook; local t=e.events.fire_event; -function e:event(e,...) -return t(e,...); -end +function e:event(e,...)return t(e,...);end return e; end function t:add_plugin(t) if self.plugins[t]then return true;end if require("verse.plugins."..t)then -local a,e=e.plugins[t](self); -if a~=false then +local e,a=e.plugins[t](self); +if e~=false then self:debug("Loaded %s plugin",t); self.plugins[t]=true; else -self:warn("Failed to load %s plugin: %s",t,e); +self:warn("Failed to load %s plugin: %s",t,a); end end return self; @@ -8929,6 +11112,9 @@ function a.onstatus(a,e) t:event("status",e); end +function a.onreadtimeout(e) +return t:event("read-timeout"); +end return a; end return e;