verse: Update bundled verse version

Thu, 23 Mar 2023 15:16:00 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 23 Mar 2023 15:16:00 +0000
changeset 177
3c19b67a1f0f
parent 176
7674fb1dcc41
child 178
e547ddf8b64d

verse: Update bundled verse version

verse.lua file | annotate | diff | comparison | revisions
--- 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={["'"]="&apos;",["\""]="&quot;",["<"]="&lt;",[">"]="&gt;",["&"]="&amp;"};
-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={["'"]="&apos;",["\""]="&quot;",["<"]="&lt;",[">"]="&gt;",["&"]="&amp;"};
+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..">");
+s(i,e,s,t,a.attr.xmlns);
+else
+o(e,t(i));
+end
+end
+o(e,"</"..l..">");
 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,"</")..a(u,"%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<e then
+e=t;
+if o then
+o:close();
+o=nil;
+else
+s=s+1;
+end
+o=r(e-i,h);
+end
+return a;
+end
+local function l(t)
+n[t]=nil;
+local s,i,n=a:remove(t);
+local t=a:peek();
+if t~=e and o then
+e=t;
+o:close();
+if e~=nil then
+o=r(f(e-d(),0),h);
+end
+end
+return s,i,n;
+end
+local function s(o,n)
+local i=d();
+local t=i+n;
+a:reprioritize(o,n);
+if e==nil or t<e then
+e=t;
+r(e-i,h);
+end
+return o;
+end
+return{
+add_task=u;
+stop=l;
+reschedule=s;
+};
+end)
+package.preload['util.termcolours']=(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 s,h=table.concat,table.insert;
+local a,r=string.char,string.format;
+local o=tonumber;
+local m=ipairs;
+local u=io.write;
+local i=math.floor;
+local l=type;
+local d=setmetatable;
+local c=pairs;
+local t;
+if os.getenv("WINDIR")then
+t=require"util.windows";
+end
+local n=t and t.get_consolecolor and t.get_consolecolor();
+local _ENV=nil;
+local e={
+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 w={
+["0"]=n,
+["1"]=7+8,
+["1;33"]=2+4+8,
+["1;31"]=4+8
+}
+local y={
+[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=a(27).."[%sm%s"..a(27).."[0m";
+local function p(e,t)
+if e then
+return r(a,e,t);
+else
+return t;
+end
+end
+local function v(e)
+return i(e*3/32)+232;
+end
+local function f(t,e,a)
+if t==e and e==a then
+return v(t);
+end
+t=i(t*3/128);
+e=i(e*3/128);
+a=i(a*3/128);
+return 16+(t*36)+(e*6)+(a);
+end
+local function i(e)
+local a=o(e:sub(1,2),16);
+local t=o(e:sub(3,4),16);
+local e=o(e:sub(5,6),16);
+return a,t,e;
+end
+d(e,{__index=function(t,e)
+if l(e)=="string"and e:find("%x%x%x%x%x%x")==1 then
+local t=e:sub(7)==" background"and"48;5;"or"38;5;";
+return r("%s%d",t,f(i(e)));
+end
+end});
+local a={
+red="ff0000";fuchsia="ff00ff";green="008000";white="ffffff";
+lime="00ff00";yellow="ffff00";purple="800080";blue="0000ff";
+aqua="00ffff";olive="808000";black="000000";navy="000080";
+teal="008080";silver="c0c0c0";maroon="800000";gray="808080";
+}
+for t,a in c(a)do
+e[t]=e[t]or e[a];
+t,a=t.." background",a.." background"
+e[t]=e[t]or e[a];
+end
+local function r(...)
+local t,a={...},{};
+for o,t in m(t)do
+t=e[t];
+if t then
+h(a,t);
+end
+end
+return s(a,";");
+end
+local a="0";
+local function i(e)
+e=e or"0";
+if e~=a then
+u("\27["..e.."m");
+a=e;
+end
+end
+if t then
+function i(e)
+e=e or"0";
+if e~=a then
+t.set_consolecolor(w[e]or n);
+a=e;
+end
+end
+if not n then
+function i()end
+end
+end
+local function a(t)
+if t=="0"then return"</span>";end
+local e={};
+for t in t:gmatch("[^;]+")do
+h(e,y[o(t)]);
+end
+return"</span><span style='"..s(e,";").."'>";
+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"</span>";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"</span><span style='"..n(t,";").."'>";
-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<w do
+a=a+1;
+local e=o[a];
+if e==nil then
+o[a]="(nil)";
+else
+o[a]=n(e):gsub("[%z\1-\8\11-\31\127]",d):gsub("\n\t?","\n\t");
+end
+h=h.." [%s]"
+end
+return h:format(m(o));
+end
+return{
+format=w;
+};
+end)
+package.preload['util.promise']=(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={};
+local a={__name="promise",__index=o};
+local n=require"util.xpcall".xpcall;
+local l=table.unpack;
+function a:__tostring()
+return"promise ("..(self._state or"invalid")..")";
+end
+local function i(e)
+local e=getmetatable(e);
+return e==a;
+end
+local function e(e,t,o,a)
+if not e then
+return a;
+end
+return function(a)
+local a,e=n(e,debug.traceback,a);
+if a then
+t(e);
+else
+o(e);
+end
+return true;
+end;
+end
+local function u(o,i,n,a,t)
+table.insert(o._pending_on_fulfilled,e(i,a,t,a));
+table.insert(o._pending_on_rejected,e(n,a,t,t));
+end
+local function r(a,o,n,t,i)
+e(o,t,i,t)(a.value);
+end
+local function d(i,n,o,a,t)
+e(o,a,t,t)(i.reason);
+end
+local function h(e,i,o,t,a)
+if e._state~="pending"then
+return;
+end
+e._state=i;
+e._next=o;
+for o,t in ipairs(t)do
+t(a);
+end
+e._pending_on_fulfilled=nil;
+e._pending_on_rejected=nil;
+return true;
+end
+local function s(e)
+local function a(t)
+if i(t)then
+t:next(s(e));
+elseif h(e,"fulfilled",r,e._pending_on_fulfilled,t)then
+e.value=t;
+end
+end
+local function o(t)
+if h(e,"rejected",d,e._pending_on_rejected,t)then
+e.reason=t;
+end
+end
+return a,o;
+end
+local d=function(e)
+e();
+end
+local function e(o)
+local e=setmetatable({_state="pending",_next=u,_pending_on_fulfilled={},_pending_on_rejected={}},a);
+if o then
+d(function()
+local a,t=s(e);
+local o,a=n(o,debug.traceback,a,t);
+if not o and e._state=="pending"then
+t(a);
+end
+end);
+end
+return e;
+end
+local function r(o)
+return e(function(n,r)
+local e,a,s=0,{},false;
 local t=0;
-local function o()
-local e=e();
-if t>=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<t then a(o());end
-local a=e:sub(0,t);
-e=e:sub(t+1);
-return a;
-end
-local function e()
-return("%x"):format(t(1):byte()%4+8);
-end
-function generate()
-return t(8).."-"..t(4).."-4"..t(3).."-"..(e())..t(3).."-"..t(12);
-end
-seed=a;
-return _M;
+return e(function(e)
+e(t);
+end);
+end
+local function a(t)
+return e(function(a,e)
+e(t);
+end);
+end
+local function h(e)
+return t():next(function()return e();end);
+end
+function o:next(o,a)
+return e(function(e,t)
+self:_next(o,a,e,t);
+end);
+end
+function o:catch(e)
+return self:next(nil,e);
+end
+function o:finally(e)
+local function t(t)e();return t;end
+local function o(t)e();return a(t);end
+return self:next(t,o);
+end
+return{
+new=e;
+resolve=t;
+join=n;
+reject=a;
+all=r;
+all_settled=u;
+race=s;
+try=h;
+is_promise=i;
+set_nexttick=function(e)d=e;end;
+}
 end)
-package.preload['net.dns']=(function(...)
+package.preload['net.adns']=(function(...)
 local _ENV=_ENV;
-local function c(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
@@ -763,822 +2101,203 @@
 _M=e;
 return e;
 end
-local s=require"socket";
-local k=require"util.timer";
-local e,b=pcall(require,"util.windows");
-local _=(e and b)or os.getenv("WINDIR");
-local u,E,v,a,i=
-coroutine,io,math,string,table;
-local m,h,o,f,r,p,x,q,t,e,z=
-ipairs,next,pairs,print,setmetatable,tostring,assert,error,unpack,select,type;
-local e={
-get=function(t,...)
-local a=e('#',...);
-for a=1,a do
-t=t[e(a,...)];
-if t==nil then break;end
-end
+local o=setmetatable;
+local c=tostring;
+local k=table.concat;
+local n=string.format;
+local q=string.lower;
+local d=string.upper;
+local i=function()end;
+local w=require"util.logger";
+local l=w.init("unbound");
+local p=require"net.server";
+local m=require"lunbound";
+local g=require"util.promise";
+local b=require"util.id".short;
+local y=require"socket".gettime;
+local e=require"util.dns";
+local h,s,u=e.classes,e.types,e.errors;
+local v=e.parsers;
+local t={hoststxt=false}
+local function r(e)
+e=e or{};
+for t,a in pairs(t)do
+if e[t]==nil then
+e[t]=a;
+end
+end
+for t,a in pairs(m.config)do
+if e[t]==nil then
+e[t]=a;
+end
+end
+return e;
+end
+local a;
+if prosody then
+local e=require"core.configmanager";
+a=r(e.get("*","unbound"));
+prosody.events.add_handler("config-reloaded",function()
+a=r(e.get("*","unbound"));
+end);
+end
+local function r(e,t)
+l("debug","Setting up net.server event handling for %s",e);
+return t.watchfd(e,function()
+l("debug","Processing queries for %s",e);
+e:process()
+end);
+end
+local t,f;
+local function l()
+t=m.new(a);
+f=r(t,p);
+end
+if prosody then
+prosody.events.add_handler("server-started",l);
+end
+local n={
+__tostring=function(e)
+if e._string then return e._string end
+local t=n("Status: %s",u[e.status]);
+if e.secure then
+t=t..", Secure";
+elseif e.bogus then
+t=t..n(", Bogus: %s",e.bogus);
+end
+local t={t};
+for a=1,#e do
+t[a+1]=e.qname.."\t"..h[e.qclass].."\t"..s[e.qtype].."\t"..c(e[a]);
+end
+local t=k(t,"\n");
+e._string=t;
 return t;
 end;
-set=function(a,...)
-local n=e('#',...);
-local s,o=e(n-1,...);
-local t,i;
-for n=1,n-2 do
-local n=e(n,...)
-local e=a[n]
-if o==nil then
-if e==nil then
-return;
-elseif h(e,h(e))then
-t=nil;i=nil;
-elseif t==nil then
-t=a;i=n;
-end
-elseif e==nil then
-e={};
-a[n]=e;
-end
-a=e
-end
-if o==nil and t then
-t[i]=nil;
-else
-a[s]=o;
-return o;
-end
-end;
-};
-local d,l=e.get,e.set;
-local j=15;
-c('dns')
-local t=_M;
-local n=i.insert
-local function c(e)
-return(e-(e%256))/256;
-end
-local function y(e)
-local t={};
-for o,e in o(e)do
-t[o]=e;
-t[e]=e;
-t[a.lower(e)]=e;
-end
-return t;
-end
-local function w(i)
-local e={};
-for o,i in o(i)do
-local t=a.char(c(o),o%256);
-e[o]=t;
-e[i]=t;
-e[a.lower(i)]=t;
-end
-return e;
-end
-t.types={
-'A','NS','MD','MF','CNAME','SOA','MB','MG','MR','NULL','WKS',
-'PTR','HINFO','MINFO','MX','TXT',
-[28]='AAAA',[29]='LOC',[33]='SRV',
-[252]='AXFR',[253]='MAILB',[254]='MAILA',[255]='*'};
-t.classes={'IN','CS','CH','HS',[255]='*'};
-t.type=y(t.types);
-t.class=y(t.classes);
-t.typecode=w(t.types);
-t.classcode=w(t.classes);
-local function g(e,i,o)
-if a.byte(e,-1)~=46 then e=e..'.';end
-e=a.lower(e);
-return e,t.type[i or'A'],t.class[o or'IN'];
-end
-local function y(t,a,n)
-a=a or s.gettime();
-for o,e in m(t)do
-if e.tod then
-e.ttl=v.floor(e.tod-a);
-if e.ttl<=0 then
-t[e[e.type:lower()]]=nil;
-i.remove(t,o);
-return y(t,a,n);
-end
-elseif n=='soft'then
-x(e.ttl==0);
-t[e[e.type:lower()]]=nil;
-i.remove(t,o);
-end
-end
-end
-local e={};
-e.__index=e;
-e.timeout=j;
-local function j(e)
-local e=e.type and e[e.type:lower()];
-if z(e)~="string"then
-return"<UNKNOWN RDATA TYPE>";
-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<t.mx)or(e.pref<t.pref);
-end
-function e:peek(o,a,t,n)
-o,a,t=g(o,a,t);
-local e=d(self.cache,t,a,o);
-if not e then
-if n then if n<=0 then return end else n=3 end
-e=d(self.cache,t,"CNAME",o);
-if not(e and e[1])then return end
-return self:peek(e[1].cname,a,t,n-1);
-end
-if y(e,s.gettime())and a=='*'or not h(e)then
-l(self.cache,t,a,o,nil);
-return nil;
-end
-if self.unsorted[e]then i.sort(e,c);self.unsorted[e]=nil;end
-return e;
-end
-function e:purge(e)
-if e=='soft'then
-self.time=s.gettime();
-for t,e in o(self.cache or{})do
-for t,e in o(e)do
-for t,e in o(e)do
-y(e,self.time,'soft')
-end
-end
-end
-else self.cache=r({},w);end
-end
-function e:query(e,a,t)
-e,a,t=g(e,a,t)
-local n=u.running();
-local o=d(self.wanted,t,a,e);
-if n and o then
-l(self.wanted,t,a,e,n,true);
-return true;
-end
-if not self.server then self:adddefaultnameservers();end
-local h=z(e,a,t);
-local o=self:peek(e,a,t);
-if o then return o;end
-local o,i=v();
-local o={
-packet=o..h,
-server=self.best_server,
-delay=1,
-retry=s.gettime()+self.delays[1]
-};
-self.active[i]=self.active[i]or{};
-self.active[i][h]=o;
+p(e);
+i("debug","Results for %s %s %s: %s (%s, %f sec)",u,a,o,e.rcode==0 and(#e.." items")or e.status,
+e.secure and"Secure"or e.bogus or"Insecure",s-d);
+else
+i("error","Results for %s %s %s: %s",u,a,o,c(t));
+end
+local t,e=pcall(m,e,t);
+if not t then i("error","Error in callback: %s",e);end
+end
+i("debug","Resolve %s %s %s",u,a,o);
+local e;
+n,e=t:resolve_async(s,u,l,h);
 if n then
-l(self.wanted,t,a,e,n,true);
-end
-local i,h=self:getsocket(o.server)
-if not i then
-return nil,h;
-end
-i:send(o.packet)
-if k and self.timeout then
-local r=#self.server;
-local s=1;
-k.add_task(self.timeout,function()
-if d(self.wanted,t,a,e,n)then
-if s<r then
-s=s+1;
-self:servfail(i);
-o.server=self.best_server;
-i,h=self:getsocket(o.server);
-if i then
-i:send(o.packet);
-return self.timeout;
-end
-end
-self:cancel(t,a,e);
-end
-end)
+r[n]=m;
+else
+i("error","Resolver error: %s",e);
+end
+return n,e;
+end
+local function o(o,a,e)
+if not t then l();end
+a=a and d(a)or"A";
+e=e and d(e)or"IN";
+local e,a=s[a],h[e];
+local e,t=t:resolve(o,e,a);
+if not e then return e,t;end
+return p(e);
+end
+local function e(e)
+local a=r[e];
+t:cancel(e);
+if a then
+a(nil,"canceled");
+r[e]=nil;
 end
 return true;
 end
-function e:servfail(t,i)
-local n=self.socketset[t]
-t=self:voidsocket(t);
-self.time=s.gettime();
-for s,a in o(self.active)do
-for o,e in o(a)do
-if e.server==n then
-e.server=e.server+1
-if e.server>#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="<unknown>";
-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()<o then
 e:lock_read(false);
 a=nil;
@@ -2286,197 +3199,208 @@
 end
 e:set_mode("*a");
 end
-ie=function(t,e,d,l,r)
-t=t or"*"
+J=function(e,t,d,h)
+e=e or"*"
+h=h or{}
 local o
-if f(d)~="table"then
+local r=h.tls_ctx;
+local c=h.tls_direct;
+local u=h.read_size;
+if w(d)~="table"then
 o="invalid listener table"
-elseif f(t)~="string"then
+elseif w(e)~="string"then
 o="invalid address"
-elseif f(e)~="number"or not(e>=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("<?xml version='1.0'?>"..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 e<t.range_min then
+return false,"out of bounds";
+end
+return true,e;
+end
+local function t(e)
+if not c.is_stanza(e)then
+return nil,"not a stanza object";
+elseif e.attr.xmlns~="jabber:x:data"or e.name~="x"then
+return nil,"not a dataform element";
+end
+for e in e:childtags("field")do
+if e.attr.var=="FORM_TYPE"then
+return e:get_child_text("value");
+end
+end
+return"";
+end
+return{
+new=w;
+from_stanza=v;
+get_type=t;
+};
 end)
 package.preload['util.caps']=(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
@@ -3305,15 +4469,15 @@
 return e;
 end
 local l=require"util.encodings".base64.encode;
-local d=require"util.hashes".sha1;
+local u=require"util.hashes".sha1;
 local n,s,h=table.insert,table.sort,table.concat;
 local r=ipairs;
-a"caps"
-function calculate_hash(e)
-local a,o,i={},{},{};
+local _ENV=nil;
+local function d(e)
+local i,o,a={},{},{};
 for t,e in r(e)do
 if e.name=="identity"then
-n(a,(e.attr.category or"").."\0"..(e.attr.type or"").."\0"..(e.attr["xml:lang"]or"").."\0"..(e.attr.name or""));
+n(i,(e.attr.category or"").."\0"..(e.attr.type or"").."\0"..(e.attr["xml:lang"]or"").."\0"..(e.attr.name or""));
 elseif e.name=="feature"then
 n(o,e.attr.var or"");
 elseif e.name=="x"and e.attr.xmlns=="jabber:x:data"then
@@ -3339,21 +4503,22 @@
 s(t);
 t=h(t,"<");
 if o then t=o.."\0"..t;end
-n(i,t);
-end
-end
+n(a,t);
+end
+end
+s(i);
+s(o);
 s(a);
-s(o);
-s(i);
-if#a>0 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 <r>...");
 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 o<a then
 e:warn("Received bad ack for "..o.." when last ack was "..a);
 end
 elseif o.name=="enabled"then
+i=0;
+e.pre_smacks_features=nil;
 if o.attr.id then
 e.resumption_token=o.attr.id;
-e:hook("closed",u,100);
-e:hook("disconnected",h,100);
 end
 elseif o.name=="resumed"then
+e.pre_smacks_features=nil;
 local o=tonumber(o.attr.h);
 if o>a 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.priority<e.priority or(t.priority==e.priority and t.weight>e.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<t.priority;
+end
+local o={
 stream_ns=n,
 stream_tag="stream",
 default_ns="jabber:client"};
-function i.streamopened(e,t)
+function o.streamopened(e,t)
 e.stream_id=t.id;
 if not e:event("opened",t)then
 e.notopen=nil;
 end
 return true;
 end
-function i.streamclosed(e)
+function o.streamclosed(e)
 e.notopen=true;
 if not e.closed then
 e:send("</stream:stream>");
@@ -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("</stream:stream>");
 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("</stream:stream>");
 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;

mercurial