spamscanner 7.0.10 → 7.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.js
2
- import { Buffer as Buffer13 } from "node:buffer";
2
+ import { Buffer as Buffer14 } from "node:buffer";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { debuglog as debuglog2 } from "node:util";
5
5
 
@@ -1107,103 +1107,285 @@ var HomographDetector = class {
1107
1107
  }
1108
1108
  };
1109
1109
 
1110
+ // src/entities.js
1111
+ var NAMED = [
1112
+ "AElig=c6,AMP=26,Aacute=c1,Abreve=102,Acirc=c2,Acy=410,Afr=1d504,Agrave=c0,Alpha=391,Amacr=100,And=2a",
1113
+ "53,Aogon=104,Aopf=1d538,ApplyFunction=2061,Aring=c5,Ascr=1d49c,Assign=2254,Atilde=c3,Auml=c4,Backsla",
1114
+ "sh=2216,Barv=2ae7,Barwed=2306,Bcy=411,Because=2235,Bernoullis=212c,Beta=392,Bfr=1d505,Bopf=1d539,Bre",
1115
+ "ve=2d8,Bscr=212c,Bumpeq=224e,CHcy=427,COPY=a9,Cacute=106,Cap=22d2,CapitalDifferentialD=2145,Cayleys=",
1116
+ "212d,Ccaron=10c,Ccedil=c7,Ccirc=108,Cconint=2230,Cdot=10a,Cedilla=b8,CenterDot=b7,Cfr=212d,Chi=3a7,C",
1117
+ "ircleDot=2299,CircleMinus=2296,CirclePlus=2295,CircleTimes=2297,ClockwiseContourIntegral=2232,CloseC",
1118
+ "urlyDoubleQuote=201d,CloseCurlyQuote=2019,Colon=2237,Colone=2a74,Congruent=2261,Conint=222f,ContourI",
1119
+ "ntegral=222e,Copf=2102,Coproduct=2210,CounterClockwiseContourIntegral=2233,Cross=2a2f,Cscr=1d49e,Cup",
1120
+ "=22d3,CupCap=224d,DD=2145,DDotrahd=2911,DJcy=402,DScy=405,DZcy=40f,Dagger=2021,Darr=21a1,Dashv=2ae4,",
1121
+ "Dcaron=10e,Dcy=414,Del=2207,Delta=394,Dfr=1d507,DiacriticalAcute=b4,DiacriticalDot=2d9,DiacriticalDo",
1122
+ "ubleAcute=2dd,DiacriticalGrave=60,DiacriticalTilde=2dc,Diamond=22c4,DifferentialD=2146,Dopf=1d53b,Do",
1123
+ "t=a8,DotDot=20dc,DotEqual=2250,DoubleContourIntegral=222f,DoubleDot=a8,DoubleDownArrow=21d3,DoubleLe",
1124
+ "ftArrow=21d0,DoubleLeftRightArrow=21d4,DoubleLeftTee=2ae4,DoubleLongLeftArrow=27f8,DoubleLongLeftRig",
1125
+ "htArrow=27fa,DoubleLongRightArrow=27f9,DoubleRightArrow=21d2,DoubleRightTee=22a8,DoubleUpArrow=21d1,",
1126
+ "DoubleUpDownArrow=21d5,DoubleVerticalBar=2225,DownArrow=2193,DownArrowBar=2913,DownArrowUpArrow=21f5",
1127
+ ",DownBreve=311,DownLeftRightVector=2950,DownLeftTeeVector=295e,DownLeftVector=21bd,DownLeftVectorBar",
1128
+ "=2956,DownRightTeeVector=295f,DownRightVector=21c1,DownRightVectorBar=2957,DownTee=22a4,DownTeeArrow",
1129
+ "=21a7,Downarrow=21d3,Dscr=1d49f,Dstrok=110,ENG=14a,ETH=d0,Eacute=c9,Ecaron=11a,Ecirc=ca,Ecy=42d,Edot",
1130
+ "=116,Efr=1d508,Egrave=c8,Element=2208,Emacr=112,EmptySmallSquare=25fb,EmptyVerySmallSquare=25ab,Eogo",
1131
+ "n=118,Eopf=1d53c,Epsilon=395,Equal=2a75,EqualTilde=2242,Equilibrium=21cc,Escr=2130,Esim=2a73,Eta=397",
1132
+ ",Euml=cb,Exists=2203,ExponentialE=2147,Fcy=424,Ffr=1d509,FilledSmallSquare=25fc,FilledVerySmallSquar",
1133
+ "e=25aa,Fopf=1d53d,ForAll=2200,Fouriertrf=2131,Fscr=2131,GJcy=403,GT=3e,Gamma=393,Gammad=3dc,Gbreve=1",
1134
+ "1e,Gcedil=122,Gcirc=11c,Gcy=413,Gdot=120,Gfr=1d50a,Gg=22d9,Gopf=1d53e,GreaterEqual=2265,GreaterEqual",
1135
+ "Less=22db,GreaterFullEqual=2267,GreaterGreater=2aa2,GreaterLess=2277,GreaterSlantEqual=2a7e,GreaterT",
1136
+ "ilde=2273,Gscr=1d4a2,Gt=226b,HARDcy=42a,Hacek=2c7,Hat=5e,Hcirc=124,Hfr=210c,HilbertSpace=210b,Hopf=2",
1137
+ "10d,HorizontalLine=2500,Hscr=210b,Hstrok=126,HumpDownHump=224e,HumpEqual=224f,IEcy=415,IJlig=132,IOc",
1138
+ "y=401,Iacute=cd,Icirc=ce,Icy=418,Idot=130,Ifr=2111,Igrave=cc,Im=2111,Imacr=12a,ImaginaryI=2148,Impli",
1139
+ "es=21d2,Int=222c,Integral=222b,Intersection=22c2,InvisibleComma=2063,InvisibleTimes=2062,Iogon=12e,I",
1140
+ "opf=1d540,Iota=399,Iscr=2110,Itilde=128,Iukcy=406,Iuml=cf,Jcirc=134,Jcy=419,Jfr=1d50d,Jopf=1d541,Jsc",
1141
+ "r=1d4a5,Jsercy=408,Jukcy=404,KHcy=425,KJcy=40c,Kappa=39a,Kcedil=136,Kcy=41a,Kfr=1d50e,Kopf=1d542,Ksc",
1142
+ "r=1d4a6,LJcy=409,LT=3c,Lacute=139,Lambda=39b,Lang=27ea,Laplacetrf=2112,Larr=219e,Lcaron=13d,Lcedil=1",
1143
+ "3b,Lcy=41b,LeftAngleBracket=27e8,LeftArrow=2190,LeftArrowBar=21e4,LeftArrowRightArrow=21c6,LeftCeili",
1144
+ "ng=2308,LeftDoubleBracket=27e6,LeftDownTeeVector=2961,LeftDownVector=21c3,LeftDownVectorBar=2959,Lef",
1145
+ "tFloor=230a,LeftRightArrow=2194,LeftRightVector=294e,LeftTee=22a3,LeftTeeArrow=21a4,LeftTeeVector=29",
1146
+ "5a,LeftTriangle=22b2,LeftTriangleBar=29cf,LeftTriangleEqual=22b4,LeftUpDownVector=2951,LeftUpTeeVect",
1147
+ "or=2960,LeftUpVector=21bf,LeftUpVectorBar=2958,LeftVector=21bc,LeftVectorBar=2952,Leftarrow=21d0,Lef",
1148
+ "trightarrow=21d4,LessEqualGreater=22da,LessFullEqual=2266,LessGreater=2276,LessLess=2aa1,LessSlantEq",
1149
+ "ual=2a7d,LessTilde=2272,Lfr=1d50f,Ll=22d8,Lleftarrow=21da,Lmidot=13f,LongLeftArrow=27f5,LongLeftRigh",
1150
+ "tArrow=27f7,LongRightArrow=27f6,Longleftarrow=27f8,Longleftrightarrow=27fa,Longrightarrow=27f9,Lopf=",
1151
+ "1d543,LowerLeftArrow=2199,LowerRightArrow=2198,Lscr=2112,Lsh=21b0,Lstrok=141,Lt=226a,Map=2905,Mcy=41",
1152
+ "c,MediumSpace=205f,Mellintrf=2133,Mfr=1d510,MinusPlus=2213,Mopf=1d544,Mscr=2133,Mu=39c,NJcy=40a,Nacu",
1153
+ "te=143,Ncaron=147,Ncedil=145,Ncy=41d,NegativeMediumSpace=200b,NegativeThickSpace=200b,NegativeThinSp",
1154
+ "ace=200b,NegativeVeryThinSpace=200b,NestedGreaterGreater=226b,NestedLessLess=226a,NewLine=a,Nfr=1d51",
1155
+ "1,NoBreak=2060,NonBreakingSpace=a0,Nopf=2115,Not=2aec,NotCongruent=2262,NotCupCap=226d,NotDoubleVert",
1156
+ "icalBar=2226,NotElement=2209,NotEqual=2260,NotEqualTilde=2242.338,NotExists=2204,NotGreater=226f,Not",
1157
+ "GreaterEqual=2271,NotGreaterFullEqual=2267.338,NotGreaterGreater=226b.338,NotGreaterLess=2279,NotGre",
1158
+ "aterSlantEqual=2a7e.338,NotGreaterTilde=2275,NotHumpDownHump=224e.338,NotHumpEqual=224f.338,NotLeftT",
1159
+ "riangle=22ea,NotLeftTriangleBar=29cf.338,NotLeftTriangleEqual=22ec,NotLess=226e,NotLessEqual=2270,No",
1160
+ "tLessGreater=2278,NotLessLess=226a.338,NotLessSlantEqual=2a7d.338,NotLessTilde=2274,NotNestedGreater",
1161
+ "Greater=2aa2.338,NotNestedLessLess=2aa1.338,NotPrecedes=2280,NotPrecedesEqual=2aaf.338,NotPrecedesSl",
1162
+ "antEqual=22e0,NotReverseElement=220c,NotRightTriangle=22eb,NotRightTriangleBar=29d0.338,NotRightTria",
1163
+ "ngleEqual=22ed,NotSquareSubset=228f.338,NotSquareSubsetEqual=22e2,NotSquareSuperset=2290.338,NotSqua",
1164
+ "reSupersetEqual=22e3,NotSubset=2282.20d2,NotSubsetEqual=2288,NotSucceeds=2281,NotSucceedsEqual=2ab0.",
1165
+ "338,NotSucceedsSlantEqual=22e1,NotSucceedsTilde=227f.338,NotSuperset=2283.20d2,NotSupersetEqual=2289",
1166
+ ",NotTilde=2241,NotTildeEqual=2244,NotTildeFullEqual=2247,NotTildeTilde=2249,NotVerticalBar=2224,Nscr",
1167
+ "=1d4a9,Ntilde=d1,Nu=39d,OElig=152,Oacute=d3,Ocirc=d4,Ocy=41e,Odblac=150,Ofr=1d512,Ograve=d2,Omacr=14",
1168
+ "c,Omega=3a9,Omicron=39f,Oopf=1d546,OpenCurlyDoubleQuote=201c,OpenCurlyQuote=2018,Or=2a54,Oscr=1d4aa,",
1169
+ "Oslash=d8,Otilde=d5,Otimes=2a37,Ouml=d6,OverBar=203e,OverBrace=23de,OverBracket=23b4,OverParenthesis",
1170
+ "=23dc,PartialD=2202,Pcy=41f,Pfr=1d513,Phi=3a6,Pi=3a0,PlusMinus=b1,Poincareplane=210c,Popf=2119,Pr=2a",
1171
+ "bb,Precedes=227a,PrecedesEqual=2aaf,PrecedesSlantEqual=227c,PrecedesTilde=227e,Prime=2033,Product=22",
1172
+ "0f,Proportion=2237,Proportional=221d,Pscr=1d4ab,Psi=3a8,QUOT=22,Qfr=1d514,Qopf=211a,Qscr=1d4ac,RBarr",
1173
+ "=2910,REG=ae,Racute=154,Rang=27eb,Rarr=21a0,Rarrtl=2916,Rcaron=158,Rcedil=156,Rcy=420,Re=211c,Revers",
1174
+ "eElement=220b,ReverseEquilibrium=21cb,ReverseUpEquilibrium=296f,Rfr=211c,Rho=3a1,RightAngleBracket=2",
1175
+ "7e9,RightArrow=2192,RightArrowBar=21e5,RightArrowLeftArrow=21c4,RightCeiling=2309,RightDoubleBracket",
1176
+ "=27e7,RightDownTeeVector=295d,RightDownVector=21c2,RightDownVectorBar=2955,RightFloor=230b,RightTee=",
1177
+ "22a2,RightTeeArrow=21a6,RightTeeVector=295b,RightTriangle=22b3,RightTriangleBar=29d0,RightTriangleEq",
1178
+ "ual=22b5,RightUpDownVector=294f,RightUpTeeVector=295c,RightUpVector=21be,RightUpVectorBar=2954,Right",
1179
+ "Vector=21c0,RightVectorBar=2953,Rightarrow=21d2,Ropf=211d,RoundImplies=2970,Rrightarrow=21db,Rscr=21",
1180
+ "1b,Rsh=21b1,RuleDelayed=29f4,SHCHcy=429,SHcy=428,SOFTcy=42c,Sacute=15a,Sc=2abc,Scaron=160,Scedil=15e",
1181
+ ",Scirc=15c,Scy=421,Sfr=1d516,ShortDownArrow=2193,ShortLeftArrow=2190,ShortRightArrow=2192,ShortUpArr",
1182
+ "ow=2191,Sigma=3a3,SmallCircle=2218,Sopf=1d54a,Sqrt=221a,Square=25a1,SquareIntersection=2293,SquareSu",
1183
+ "bset=228f,SquareSubsetEqual=2291,SquareSuperset=2290,SquareSupersetEqual=2292,SquareUnion=2294,Sscr=",
1184
+ "1d4ae,Star=22c6,Sub=22d0,Subset=22d0,SubsetEqual=2286,Succeeds=227b,SucceedsEqual=2ab0,SucceedsSlant",
1185
+ "Equal=227d,SucceedsTilde=227f,SuchThat=220b,Sum=2211,Sup=22d1,Superset=2283,SupersetEqual=2287,Supse",
1186
+ "t=22d1,THORN=de,TRADE=2122,TSHcy=40b,TScy=426,Tab=9,Tau=3a4,Tcaron=164,Tcedil=162,Tcy=422,Tfr=1d517,",
1187
+ "Therefore=2234,Theta=398,ThickSpace=205f.200a,ThinSpace=2009,Tilde=223c,TildeEqual=2243,TildeFullEqu",
1188
+ "al=2245,TildeTilde=2248,Topf=1d54b,TripleDot=20db,Tscr=1d4af,Tstrok=166,Uacute=da,Uarr=219f,Uarrocir",
1189
+ "=2949,Ubrcy=40e,Ubreve=16c,Ucirc=db,Ucy=423,Udblac=170,Ufr=1d518,Ugrave=d9,Umacr=16a,UnderBar=5f,Und",
1190
+ "erBrace=23df,UnderBracket=23b5,UnderParenthesis=23dd,Union=22c3,UnionPlus=228e,Uogon=172,Uopf=1d54c,",
1191
+ "UpArrow=2191,UpArrowBar=2912,UpArrowDownArrow=21c5,UpDownArrow=2195,UpEquilibrium=296e,UpTee=22a5,Up",
1192
+ "TeeArrow=21a5,Uparrow=21d1,Updownarrow=21d5,UpperLeftArrow=2196,UpperRightArrow=2197,Upsi=3d2,Upsilo",
1193
+ "n=3a5,Uring=16e,Uscr=1d4b0,Utilde=168,Uuml=dc,VDash=22ab,Vbar=2aeb,Vcy=412,Vdash=22a9,Vdashl=2ae6,Ve",
1194
+ "e=22c1,Verbar=2016,Vert=2016,VerticalBar=2223,VerticalLine=7c,VerticalSeparator=2758,VerticalTilde=2",
1195
+ "240,VeryThinSpace=200a,Vfr=1d519,Vopf=1d54d,Vscr=1d4b1,Vvdash=22aa,Wcirc=174,Wedge=22c0,Wfr=1d51a,Wo",
1196
+ "pf=1d54e,Wscr=1d4b2,Xfr=1d51b,Xi=39e,Xopf=1d54f,Xscr=1d4b3,YAcy=42f,YIcy=407,YUcy=42e,Yacute=dd,Ycir",
1197
+ "c=176,Ycy=42b,Yfr=1d51c,Yopf=1d550,Yscr=1d4b4,Yuml=178,ZHcy=416,Zacute=179,Zcaron=17d,Zcy=417,Zdot=1",
1198
+ "7b,ZeroWidthSpace=200b,Zeta=396,Zfr=2128,Zopf=2124,Zscr=1d4b5,aacute=e1,abreve=103,ac=223e,acE=223e.",
1199
+ "333,acd=223f,acirc=e2,acute=b4,acy=430,aelig=e6,af=2061,afr=1d51e,agrave=e0,alefsym=2135,aleph=2135,",
1200
+ "alpha=3b1,amacr=101,amalg=2a3f,amp=26,and=2227,andand=2a55,andd=2a5c,andslope=2a58,andv=2a5a,ang=222",
1201
+ "0,ange=29a4,angle=2220,angmsd=2221,angmsdaa=29a8,angmsdab=29a9,angmsdac=29aa,angmsdad=29ab,angmsdae=",
1202
+ "29ac,angmsdaf=29ad,angmsdag=29ae,angmsdah=29af,angrt=221f,angrtvb=22be,angrtvbd=299d,angsph=2222,ang",
1203
+ "st=c5,angzarr=237c,aogon=105,aopf=1d552,ap=2248,apE=2a70,apacir=2a6f,ape=224a,apid=224b,apos=27,appr",
1204
+ "ox=2248,approxeq=224a,aring=e5,ascr=1d4b6,ast=2a,asymp=2248,asympeq=224d,atilde=e3,auml=e4,awconint=",
1205
+ "2233,awint=2a11,bNot=2aed,backcong=224c,backepsilon=3f6,backprime=2035,backsim=223d,backsimeq=22cd,b",
1206
+ "arvee=22bd,barwed=2305,barwedge=2305,bbrk=23b5,bbrktbrk=23b6,bcong=224c,bcy=431,bdquo=201e,becaus=22",
1207
+ "35,because=2235,bemptyv=29b0,bepsi=3f6,bernou=212c,beta=3b2,beth=2136,between=226c,bfr=1d51f,bigcap=",
1208
+ "22c2,bigcirc=25ef,bigcup=22c3,bigodot=2a00,bigoplus=2a01,bigotimes=2a02,bigsqcup=2a06,bigstar=2605,b",
1209
+ "igtriangledown=25bd,bigtriangleup=25b3,biguplus=2a04,bigvee=22c1,bigwedge=22c0,bkarow=290d,blackloze",
1210
+ "nge=29eb,blacksquare=25aa,blacktriangle=25b4,blacktriangledown=25be,blacktriangleleft=25c2,blacktria",
1211
+ "ngleright=25b8,blank=2423,blk12=2592,blk14=2591,blk34=2593,block=2588,bne=3d.20e5,bnequiv=2261.20e5,",
1212
+ "bnot=2310,bopf=1d553,bot=22a5,bottom=22a5,bowtie=22c8,boxDL=2557,boxDR=2554,boxDl=2556,boxDr=2553,bo",
1213
+ "xH=2550,boxHD=2566,boxHU=2569,boxHd=2564,boxHu=2567,boxUL=255d,boxUR=255a,boxUl=255c,boxUr=2559,boxV",
1214
+ "=2551,boxVH=256c,boxVL=2563,boxVR=2560,boxVh=256b,boxVl=2562,boxVr=255f,boxbox=29c9,boxdL=2555,boxdR",
1215
+ "=2552,boxdl=2510,boxdr=250c,boxh=2500,boxhD=2565,boxhU=2568,boxhd=252c,boxhu=2534,boxminus=229f,boxp",
1216
+ "lus=229e,boxtimes=22a0,boxuL=255b,boxuR=2558,boxul=2518,boxur=2514,boxv=2502,boxvH=256a,boxvL=2561,b",
1217
+ "oxvR=255e,boxvh=253c,boxvl=2524,boxvr=251c,bprime=2035,breve=2d8,brvbar=a6,bscr=1d4b7,bsemi=204f,bsi",
1218
+ "m=223d,bsime=22cd,bsol=5c,bsolb=29c5,bsolhsub=27c8,bull=2022,bullet=2022,bump=224e,bumpE=2aae,bumpe=",
1219
+ "224f,bumpeq=224f,cacute=107,cap=2229,capand=2a44,capbrcup=2a49,capcap=2a4b,capcup=2a47,capdot=2a40,c",
1220
+ "aps=2229.fe00,caret=2041,caron=2c7,ccaps=2a4d,ccaron=10d,ccedil=e7,ccirc=109,ccups=2a4c,ccupssm=2a50",
1221
+ ",cdot=10b,cedil=b8,cemptyv=29b2,cent=a2,centerdot=b7,cfr=1d520,chcy=447,check=2713,checkmark=2713,ch",
1222
+ "i=3c7,cir=25cb,cirE=29c3,circ=2c6,circeq=2257,circlearrowleft=21ba,circlearrowright=21bb,circledR=ae",
1223
+ ",circledS=24c8,circledast=229b,circledcirc=229a,circleddash=229d,cire=2257,cirfnint=2a10,cirmid=2aef",
1224
+ ",cirscir=29c2,clubs=2663,clubsuit=2663,colon=3a,colone=2254,coloneq=2254,comma=2c,commat=40,comp=220",
1225
+ "1,compfn=2218,complement=2201,complexes=2102,cong=2245,congdot=2a6d,conint=222e,copf=1d554,coprod=22",
1226
+ "10,copy=a9,copysr=2117,crarr=21b5,cross=2717,cscr=1d4b8,csub=2acf,csube=2ad1,csup=2ad0,csupe=2ad2,ct",
1227
+ "dot=22ef,cudarrl=2938,cudarrr=2935,cuepr=22de,cuesc=22df,cularr=21b6,cularrp=293d,cup=222a,cupbrcap=",
1228
+ "2a48,cupcap=2a46,cupcup=2a4a,cupdot=228d,cupor=2a45,cups=222a.fe00,curarr=21b7,curarrm=293c,curlyeqp",
1229
+ "rec=22de,curlyeqsucc=22df,curlyvee=22ce,curlywedge=22cf,curren=a4,curvearrowleft=21b6,curvearrowrigh",
1230
+ "t=21b7,cuvee=22ce,cuwed=22cf,cwconint=2232,cwint=2231,cylcty=232d,dArr=21d3,dHar=2965,dagger=2020,da",
1231
+ "leth=2138,darr=2193,dash=2010,dashv=22a3,dbkarow=290f,dblac=2dd,dcaron=10f,dcy=434,dd=2146,ddagger=2",
1232
+ "021,ddarr=21ca,ddotseq=2a77,deg=b0,delta=3b4,demptyv=29b1,dfisht=297f,dfr=1d521,dharl=21c3,dharr=21c",
1233
+ "2,diam=22c4,diamond=22c4,diamondsuit=2666,diams=2666,die=a8,digamma=3dd,disin=22f2,div=f7,divide=f7,",
1234
+ "divideontimes=22c7,divonx=22c7,djcy=452,dlcorn=231e,dlcrop=230d,dollar=24,dopf=1d555,dot=2d9,doteq=2",
1235
+ "250,doteqdot=2251,dotminus=2238,dotplus=2214,dotsquare=22a1,doublebarwedge=2306,downarrow=2193,downd",
1236
+ "ownarrows=21ca,downharpoonleft=21c3,downharpoonright=21c2,drbkarow=2910,drcorn=231f,drcrop=230c,dscr",
1237
+ "=1d4b9,dscy=455,dsol=29f6,dstrok=111,dtdot=22f1,dtri=25bf,dtrif=25be,duarr=21f5,duhar=296f,dwangle=2",
1238
+ "9a6,dzcy=45f,dzigrarr=27ff,eDDot=2a77,eDot=2251,eacute=e9,easter=2a6e,ecaron=11b,ecir=2256,ecirc=ea,",
1239
+ "ecolon=2255,ecy=44d,edot=117,ee=2147,efDot=2252,efr=1d522,eg=2a9a,egrave=e8,egs=2a96,egsdot=2a98,el=",
1240
+ "2a99,elinters=23e7,ell=2113,els=2a95,elsdot=2a97,emacr=113,empty=2205,emptyset=2205,emptyv=2205,emsp",
1241
+ "=2003,emsp13=2004,emsp14=2005,eng=14b,ensp=2002,eogon=119,eopf=1d556,epar=22d5,eparsl=29e3,eplus=2a7",
1242
+ "1,epsi=3b5,epsilon=3b5,epsiv=3f5,eqcirc=2256,eqcolon=2255,eqsim=2242,eqslantgtr=2a96,eqslantless=2a9",
1243
+ "5,equals=3d,equest=225f,equiv=2261,equivDD=2a78,eqvparsl=29e5,erDot=2253,erarr=2971,escr=212f,esdot=",
1244
+ "2250,esim=2242,eta=3b7,eth=f0,euml=eb,euro=20ac,excl=21,exist=2203,expectation=2130,exponentiale=214",
1245
+ "7,fallingdotseq=2252,fcy=444,female=2640,ffilig=fb03,fflig=fb00,ffllig=fb04,ffr=1d523,filig=fb01,fjl",
1246
+ "ig=66.6a,flat=266d,fllig=fb02,fltns=25b1,fnof=192,fopf=1d557,forall=2200,fork=22d4,forkv=2ad9,fparti",
1247
+ "nt=2a0d,frac12=bd,frac13=2153,frac14=bc,frac15=2155,frac16=2159,frac18=215b,frac23=2154,frac25=2156,",
1248
+ "frac34=be,frac35=2157,frac38=215c,frac45=2158,frac56=215a,frac58=215d,frac78=215e,frasl=2044,frown=2",
1249
+ "322,fscr=1d4bb,gE=2267,gEl=2a8c,gacute=1f5,gamma=3b3,gammad=3dd,gap=2a86,gbreve=11f,gcirc=11d,gcy=43",
1250
+ "3,gdot=121,ge=2265,gel=22db,geq=2265,geqq=2267,geqslant=2a7e,ges=2a7e,gescc=2aa9,gesdot=2a80,gesdoto",
1251
+ "=2a82,gesdotol=2a84,gesl=22db.fe00,gesles=2a94,gfr=1d524,gg=226b,ggg=22d9,gimel=2137,gjcy=453,gl=227",
1252
+ "7,glE=2a92,gla=2aa5,glj=2aa4,gnE=2269,gnap=2a8a,gnapprox=2a8a,gne=2a88,gneq=2a88,gneqq=2269,gnsim=22",
1253
+ "e7,gopf=1d558,grave=60,gscr=210a,gsim=2273,gsime=2a8e,gsiml=2a90,gt=3e,gtcc=2aa7,gtcir=2a7a,gtdot=22",
1254
+ "d7,gtlPar=2995,gtquest=2a7c,gtrapprox=2a86,gtrarr=2978,gtrdot=22d7,gtreqless=22db,gtreqqless=2a8c,gt",
1255
+ "rless=2277,gtrsim=2273,gvertneqq=2269.fe00,gvnE=2269.fe00,hArr=21d4,hairsp=200a,half=bd,hamilt=210b,",
1256
+ "hardcy=44a,harr=2194,harrcir=2948,harrw=21ad,hbar=210f,hcirc=125,hearts=2665,heartsuit=2665,hellip=2",
1257
+ "026,hercon=22b9,hfr=1d525,hksearow=2925,hkswarow=2926,hoarr=21ff,homtht=223b,hookleftarrow=21a9,hook",
1258
+ "rightarrow=21aa,hopf=1d559,horbar=2015,hscr=1d4bd,hslash=210f,hstrok=127,hybull=2043,hyphen=2010,iac",
1259
+ "ute=ed,ic=2063,icirc=ee,icy=438,iecy=435,iexcl=a1,iff=21d4,ifr=1d526,igrave=ec,ii=2148,iiiint=2a0c,i",
1260
+ "iint=222d,iinfin=29dc,iiota=2129,ijlig=133,imacr=12b,image=2111,imagline=2110,imagpart=2111,imath=13",
1261
+ "1,imof=22b7,imped=1b5,in=2208,incare=2105,infin=221e,infintie=29dd,inodot=131,int=222b,intcal=22ba,i",
1262
+ "ntegers=2124,intercal=22ba,intlarhk=2a17,intprod=2a3c,iocy=451,iogon=12f,iopf=1d55a,iota=3b9,iprod=2",
1263
+ "a3c,iquest=bf,iscr=1d4be,isin=2208,isinE=22f9,isindot=22f5,isins=22f4,isinsv=22f3,isinv=2208,it=2062",
1264
+ ",itilde=129,iukcy=456,iuml=ef,jcirc=135,jcy=439,jfr=1d527,jmath=237,jopf=1d55b,jscr=1d4bf,jsercy=458",
1265
+ ",jukcy=454,kappa=3ba,kappav=3f0,kcedil=137,kcy=43a,kfr=1d528,kgreen=138,khcy=445,kjcy=45c,kopf=1d55c",
1266
+ ",kscr=1d4c0,lAarr=21da,lArr=21d0,lAtail=291b,lBarr=290e,lE=2266,lEg=2a8b,lHar=2962,lacute=13a,laempt",
1267
+ "yv=29b4,lagran=2112,lambda=3bb,lang=27e8,langd=2991,langle=27e8,lap=2a85,laquo=ab,larr=2190,larrb=21",
1268
+ "e4,larrbfs=291f,larrfs=291d,larrhk=21a9,larrlp=21ab,larrpl=2939,larrsim=2973,larrtl=21a2,lat=2aab,la",
1269
+ "tail=2919,late=2aad,lates=2aad.fe00,lbarr=290c,lbbrk=2772,lbrace=7b,lbrack=5b,lbrke=298b,lbrksld=298",
1270
+ "f,lbrkslu=298d,lcaron=13e,lcedil=13c,lceil=2308,lcub=7b,lcy=43b,ldca=2936,ldquo=201c,ldquor=201e,ldr",
1271
+ "dhar=2967,ldrushar=294b,ldsh=21b2,le=2264,leftarrow=2190,leftarrowtail=21a2,leftharpoondown=21bd,lef",
1272
+ "tharpoonup=21bc,leftleftarrows=21c7,leftrightarrow=2194,leftrightarrows=21c6,leftrightharpoons=21cb,",
1273
+ "leftrightsquigarrow=21ad,leftthreetimes=22cb,leg=22da,leq=2264,leqq=2266,leqslant=2a7d,les=2a7d,lesc",
1274
+ "c=2aa8,lesdot=2a7f,lesdoto=2a81,lesdotor=2a83,lesg=22da.fe00,lesges=2a93,lessapprox=2a85,lessdot=22d",
1275
+ "6,lesseqgtr=22da,lesseqqgtr=2a8b,lessgtr=2276,lesssim=2272,lfisht=297c,lfloor=230a,lfr=1d529,lg=2276",
1276
+ ",lgE=2a91,lhard=21bd,lharu=21bc,lharul=296a,lhblk=2584,ljcy=459,ll=226a,llarr=21c7,llcorner=231e,llh",
1277
+ "ard=296b,lltri=25fa,lmidot=140,lmoust=23b0,lmoustache=23b0,lnE=2268,lnap=2a89,lnapprox=2a89,lne=2a87",
1278
+ ",lneq=2a87,lneqq=2268,lnsim=22e6,loang=27ec,loarr=21fd,lobrk=27e6,longleftarrow=27f5,longleftrightar",
1279
+ "row=27f7,longmapsto=27fc,longrightarrow=27f6,looparrowleft=21ab,looparrowright=21ac,lopar=2985,lopf=",
1280
+ "1d55d,loplus=2a2d,lotimes=2a34,lowast=2217,lowbar=5f,loz=25ca,lozenge=25ca,lozf=29eb,lpar=28,lparlt=",
1281
+ "2993,lrarr=21c6,lrcorner=231f,lrhar=21cb,lrhard=296d,lrm=200e,lrtri=22bf,lsaquo=2039,lscr=1d4c1,lsh=",
1282
+ "21b0,lsim=2272,lsime=2a8d,lsimg=2a8f,lsqb=5b,lsquo=2018,lsquor=201a,lstrok=142,lt=3c,ltcc=2aa6,ltcir",
1283
+ "=2a79,ltdot=22d6,lthree=22cb,ltimes=22c9,ltlarr=2976,ltquest=2a7b,ltrPar=2996,ltri=25c3,ltrie=22b4,l",
1284
+ "trif=25c2,lurdshar=294a,luruhar=2966,lvertneqq=2268.fe00,lvnE=2268.fe00,mDDot=223a,macr=af,male=2642",
1285
+ ",malt=2720,maltese=2720,map=21a6,mapsto=21a6,mapstodown=21a7,mapstoleft=21a4,mapstoup=21a5,marker=25",
1286
+ "ae,mcomma=2a29,mcy=43c,mdash=2014,measuredangle=2221,mfr=1d52a,mho=2127,micro=b5,mid=2223,midast=2a,",
1287
+ "midcir=2af0,middot=b7,minus=2212,minusb=229f,minusd=2238,minusdu=2a2a,mlcp=2adb,mldr=2026,mnplus=221",
1288
+ "3,models=22a7,mopf=1d55e,mp=2213,mscr=1d4c2,mstpos=223e,mu=3bc,multimap=22b8,mumap=22b8,nGg=22d9.338",
1289
+ ",nGt=226b.20d2,nGtv=226b.338,nLeftarrow=21cd,nLeftrightarrow=21ce,nLl=22d8.338,nLt=226a.20d2,nLtv=22",
1290
+ "6a.338,nRightarrow=21cf,nVDash=22af,nVdash=22ae,nabla=2207,nacute=144,nang=2220.20d2,nap=2249,napE=2",
1291
+ "a70.338,napid=224b.338,napos=149,napprox=2249,natur=266e,natural=266e,naturals=2115,nbsp=a0,nbump=22",
1292
+ "4e.338,nbumpe=224f.338,ncap=2a43,ncaron=148,ncedil=146,ncong=2247,ncongdot=2a6d.338,ncup=2a42,ncy=43",
1293
+ "d,ndash=2013,ne=2260,neArr=21d7,nearhk=2924,nearr=2197,nearrow=2197,nedot=2250.338,nequiv=2262,nesea",
1294
+ "r=2928,nesim=2242.338,nexist=2204,nexists=2204,nfr=1d52b,ngE=2267.338,nge=2271,ngeq=2271,ngeqq=2267.",
1295
+ "338,ngeqslant=2a7e.338,nges=2a7e.338,ngsim=2275,ngt=226f,ngtr=226f,nhArr=21ce,nharr=21ae,nhpar=2af2,",
1296
+ "ni=220b,nis=22fc,nisd=22fa,niv=220b,njcy=45a,nlArr=21cd,nlE=2266.338,nlarr=219a,nldr=2025,nle=2270,n",
1297
+ "leftarrow=219a,nleftrightarrow=21ae,nleq=2270,nleqq=2266.338,nleqslant=2a7d.338,nles=2a7d.338,nless=",
1298
+ "226e,nlsim=2274,nlt=226e,nltri=22ea,nltrie=22ec,nmid=2224,nopf=1d55f,not=ac,notin=2209,notinE=22f9.3",
1299
+ "38,notindot=22f5.338,notinva=2209,notinvb=22f7,notinvc=22f6,notni=220c,notniva=220c,notnivb=22fe,not",
1300
+ "nivc=22fd,npar=2226,nparallel=2226,nparsl=2afd.20e5,npart=2202.338,npolint=2a14,npr=2280,nprcue=22e0",
1301
+ ",npre=2aaf.338,nprec=2280,npreceq=2aaf.338,nrArr=21cf,nrarr=219b,nrarrc=2933.338,nrarrw=219d.338,nri",
1302
+ "ghtarrow=219b,nrtri=22eb,nrtrie=22ed,nsc=2281,nsccue=22e1,nsce=2ab0.338,nscr=1d4c3,nshortmid=2224,ns",
1303
+ "hortparallel=2226,nsim=2241,nsime=2244,nsimeq=2244,nsmid=2224,nspar=2226,nsqsube=22e2,nsqsupe=22e3,n",
1304
+ "sub=2284,nsubE=2ac5.338,nsube=2288,nsubset=2282.20d2,nsubseteq=2288,nsubseteqq=2ac5.338,nsucc=2281,n",
1305
+ "succeq=2ab0.338,nsup=2285,nsupE=2ac6.338,nsupe=2289,nsupset=2283.20d2,nsupseteq=2289,nsupseteqq=2ac6",
1306
+ ".338,ntgl=2279,ntilde=f1,ntlg=2278,ntriangleleft=22ea,ntrianglelefteq=22ec,ntriangleright=22eb,ntria",
1307
+ "nglerighteq=22ed,nu=3bd,num=23,numero=2116,numsp=2007,nvDash=22ad,nvHarr=2904,nvap=224d.20d2,nvdash=",
1308
+ "22ac,nvge=2265.20d2,nvgt=3e.20d2,nvinfin=29de,nvlArr=2902,nvle=2264.20d2,nvlt=3c.20d2,nvltrie=22b4.2",
1309
+ "0d2,nvrArr=2903,nvrtrie=22b5.20d2,nvsim=223c.20d2,nwArr=21d6,nwarhk=2923,nwarr=2196,nwarrow=2196,nwn",
1310
+ "ear=2927,oS=24c8,oacute=f3,oast=229b,ocir=229a,ocirc=f4,ocy=43e,odash=229d,odblac=151,odiv=2a38,odot",
1311
+ "=2299,odsold=29bc,oelig=153,ofcir=29bf,ofr=1d52c,ogon=2db,ograve=f2,ogt=29c1,ohbar=29b5,ohm=3a9,oint",
1312
+ "=222e,olarr=21ba,olcir=29be,olcross=29bb,oline=203e,olt=29c0,omacr=14d,omega=3c9,omicron=3bf,omid=29",
1313
+ "b6,ominus=2296,oopf=1d560,opar=29b7,operp=29b9,oplus=2295,or=2228,orarr=21bb,ord=2a5d,order=2134,ord",
1314
+ "erof=2134,ordf=aa,ordm=ba,origof=22b6,oror=2a56,orslope=2a57,orv=2a5b,oscr=2134,oslash=f8,osol=2298,",
1315
+ "otilde=f5,otimes=2297,otimesas=2a36,ouml=f6,ovbar=233d,par=2225,para=b6,parallel=2225,parsim=2af3,pa",
1316
+ "rsl=2afd,part=2202,pcy=43f,percnt=25,period=2e,permil=2030,perp=22a5,pertenk=2031,pfr=1d52d,phi=3c6,",
1317
+ "phiv=3d5,phmmat=2133,phone=260e,pi=3c0,pitchfork=22d4,piv=3d6,planck=210f,planckh=210e,plankv=210f,p",
1318
+ "lus=2b,plusacir=2a23,plusb=229e,pluscir=2a22,plusdo=2214,plusdu=2a25,pluse=2a72,plusmn=b1,plussim=2a",
1319
+ "26,plustwo=2a27,pm=b1,pointint=2a15,popf=1d561,pound=a3,pr=227a,prE=2ab3,prap=2ab7,prcue=227c,pre=2a",
1320
+ "af,prec=227a,precapprox=2ab7,preccurlyeq=227c,preceq=2aaf,precnapprox=2ab9,precneqq=2ab5,precnsim=22",
1321
+ "e8,precsim=227e,prime=2032,primes=2119,prnE=2ab5,prnap=2ab9,prnsim=22e8,prod=220f,profalar=232e,prof",
1322
+ "line=2312,profsurf=2313,prop=221d,propto=221d,prsim=227e,prurel=22b0,pscr=1d4c5,psi=3c8,puncsp=2008,",
1323
+ "qfr=1d52e,qint=2a0c,qopf=1d562,qprime=2057,qscr=1d4c6,quaternions=210d,quatint=2a16,quest=3f,questeq",
1324
+ "=225f,quot=22,rAarr=21db,rArr=21d2,rAtail=291c,rBarr=290f,rHar=2964,race=223d.331,racute=155,radic=2",
1325
+ "21a,raemptyv=29b3,rang=27e9,rangd=2992,range=29a5,rangle=27e9,raquo=bb,rarr=2192,rarrap=2975,rarrb=2",
1326
+ "1e5,rarrbfs=2920,rarrc=2933,rarrfs=291e,rarrhk=21aa,rarrlp=21ac,rarrpl=2945,rarrsim=2974,rarrtl=21a3",
1327
+ ",rarrw=219d,ratail=291a,ratio=2236,rationals=211a,rbarr=290d,rbbrk=2773,rbrace=7d,rbrack=5d,rbrke=29",
1328
+ "8c,rbrksld=298e,rbrkslu=2990,rcaron=159,rcedil=157,rceil=2309,rcub=7d,rcy=440,rdca=2937,rdldhar=2969",
1329
+ ",rdquo=201d,rdquor=201d,rdsh=21b3,real=211c,realine=211b,realpart=211c,reals=211d,rect=25ad,reg=ae,r",
1330
+ "fisht=297d,rfloor=230b,rfr=1d52f,rhard=21c1,rharu=21c0,rharul=296c,rho=3c1,rhov=3f1,rightarrow=2192,",
1331
+ "rightarrowtail=21a3,rightharpoondown=21c1,rightharpoonup=21c0,rightleftarrows=21c4,rightleftharpoons",
1332
+ "=21cc,rightrightarrows=21c9,rightsquigarrow=219d,rightthreetimes=22cc,ring=2da,risingdotseq=2253,rla",
1333
+ "rr=21c4,rlhar=21cc,rlm=200f,rmoust=23b1,rmoustache=23b1,rnmid=2aee,roang=27ed,roarr=21fe,robrk=27e7,",
1334
+ "ropar=2986,ropf=1d563,roplus=2a2e,rotimes=2a35,rpar=29,rpargt=2994,rppolint=2a12,rrarr=21c9,rsaquo=2",
1335
+ "03a,rscr=1d4c7,rsh=21b1,rsqb=5d,rsquo=2019,rsquor=2019,rthree=22cc,rtimes=22ca,rtri=25b9,rtrie=22b5,",
1336
+ "rtrif=25b8,rtriltri=29ce,ruluhar=2968,rx=211e,sacute=15b,sbquo=201a,sc=227b,scE=2ab4,scap=2ab8,scaro",
1337
+ "n=161,sccue=227d,sce=2ab0,scedil=15f,scirc=15d,scnE=2ab6,scnap=2aba,scnsim=22e9,scpolint=2a13,scsim=",
1338
+ "227f,scy=441,sdot=22c5,sdotb=22a1,sdote=2a66,seArr=21d8,searhk=2925,searr=2198,searrow=2198,sect=a7,",
1339
+ "semi=3b,seswar=2929,setminus=2216,setmn=2216,sext=2736,sfr=1d530,sfrown=2322,sharp=266f,shchcy=449,s",
1340
+ "hcy=448,shortmid=2223,shortparallel=2225,shy=ad,sigma=3c3,sigmaf=3c2,sigmav=3c2,sim=223c,simdot=2a6a",
1341
+ ",sime=2243,simeq=2243,simg=2a9e,simgE=2aa0,siml=2a9d,simlE=2a9f,simne=2246,simplus=2a24,simrarr=2972",
1342
+ ",slarr=2190,smallsetminus=2216,smashp=2a33,smeparsl=29e4,smid=2223,smile=2323,smt=2aaa,smte=2aac,smt",
1343
+ "es=2aac.fe00,softcy=44c,sol=2f,solb=29c4,solbar=233f,sopf=1d564,spades=2660,spadesuit=2660,spar=2225",
1344
+ ",sqcap=2293,sqcaps=2293.fe00,sqcup=2294,sqcups=2294.fe00,sqsub=228f,sqsube=2291,sqsubset=228f,sqsubs",
1345
+ "eteq=2291,sqsup=2290,sqsupe=2292,sqsupset=2290,sqsupseteq=2292,squ=25a1,square=25a1,squarf=25aa,squf",
1346
+ "=25aa,srarr=2192,sscr=1d4c8,ssetmn=2216,ssmile=2323,sstarf=22c6,star=2606,starf=2605,straightepsilon",
1347
+ "=3f5,straightphi=3d5,strns=af,sub=2282,subE=2ac5,subdot=2abd,sube=2286,subedot=2ac3,submult=2ac1,sub",
1348
+ "nE=2acb,subne=228a,subplus=2abf,subrarr=2979,subset=2282,subseteq=2286,subseteqq=2ac5,subsetneq=228a",
1349
+ ",subsetneqq=2acb,subsim=2ac7,subsub=2ad5,subsup=2ad3,succ=227b,succapprox=2ab8,succcurlyeq=227d,succ",
1350
+ "eq=2ab0,succnapprox=2aba,succneqq=2ab6,succnsim=22e9,succsim=227f,sum=2211,sung=266a,sup=2283,sup1=b",
1351
+ "9,sup2=b2,sup3=b3,supE=2ac6,supdot=2abe,supdsub=2ad8,supe=2287,supedot=2ac4,suphsol=27c9,suphsub=2ad",
1352
+ "7,suplarr=297b,supmult=2ac2,supnE=2acc,supne=228b,supplus=2ac0,supset=2283,supseteq=2287,supseteqq=2",
1353
+ "ac6,supsetneq=228b,supsetneqq=2acc,supsim=2ac8,supsub=2ad4,supsup=2ad6,swArr=21d9,swarhk=2926,swarr=",
1354
+ "2199,swarrow=2199,swnwar=292a,szlig=df,target=2316,tau=3c4,tbrk=23b4,tcaron=165,tcedil=163,tcy=442,t",
1355
+ "dot=20db,telrec=2315,tfr=1d531,there4=2234,therefore=2234,theta=3b8,thetasym=3d1,thetav=3d1,thickapp",
1356
+ "rox=2248,thicksim=223c,thinsp=2009,thkap=2248,thksim=223c,thorn=fe,tilde=2dc,times=d7,timesb=22a0,ti",
1357
+ "mesbar=2a31,timesd=2a30,tint=222d,toea=2928,top=22a4,topbot=2336,topcir=2af1,topf=1d565,topfork=2ada",
1358
+ ",tosa=2929,tprime=2034,trade=2122,triangle=25b5,triangledown=25bf,triangleleft=25c3,trianglelefteq=2",
1359
+ "2b4,triangleq=225c,triangleright=25b9,trianglerighteq=22b5,tridot=25ec,trie=225c,triminus=2a3a,tripl",
1360
+ "us=2a39,trisb=29cd,tritime=2a3b,trpezium=23e2,tscr=1d4c9,tscy=446,tshcy=45b,tstrok=167,twixt=226c,tw",
1361
+ "oheadleftarrow=219e,twoheadrightarrow=21a0,uArr=21d1,uHar=2963,uacute=fa,uarr=2191,ubrcy=45e,ubreve=",
1362
+ "16d,ucirc=fb,ucy=443,udarr=21c5,udblac=171,udhar=296e,ufisht=297e,ufr=1d532,ugrave=f9,uharl=21bf,uha",
1363
+ "rr=21be,uhblk=2580,ulcorn=231c,ulcorner=231c,ulcrop=230f,ultri=25f8,umacr=16b,uml=a8,uogon=173,uopf=",
1364
+ "1d566,uparrow=2191,updownarrow=2195,upharpoonleft=21bf,upharpoonright=21be,uplus=228e,upsi=3c5,upsih",
1365
+ "=3d2,upsilon=3c5,upuparrows=21c8,urcorn=231d,urcorner=231d,urcrop=230e,uring=16f,urtri=25f9,uscr=1d4",
1366
+ "ca,utdot=22f0,utilde=169,utri=25b5,utrif=25b4,uuarr=21c8,uuml=fc,uwangle=29a7,vArr=21d5,vBar=2ae8,vB",
1367
+ "arv=2ae9,vDash=22a8,vangrt=299c,varepsilon=3f5,varkappa=3f0,varnothing=2205,varphi=3d5,varpi=3d6,var",
1368
+ "propto=221d,varr=2195,varrho=3f1,varsigma=3c2,varsubsetneq=228a.fe00,varsubsetneqq=2acb.fe00,varsups",
1369
+ "etneq=228b.fe00,varsupsetneqq=2acc.fe00,vartheta=3d1,vartriangleleft=22b2,vartriangleright=22b3,vcy=",
1370
+ "432,vdash=22a2,vee=2228,veebar=22bb,veeeq=225a,vellip=22ee,verbar=7c,vert=7c,vfr=1d533,vltri=22b2,vn",
1371
+ "sub=2282.20d2,vnsup=2283.20d2,vopf=1d567,vprop=221d,vrtri=22b3,vscr=1d4cb,vsubnE=2acb.fe00,vsubne=22",
1372
+ "8a.fe00,vsupnE=2acc.fe00,vsupne=228b.fe00,vzigzag=299a,wcirc=175,wedbar=2a5f,wedge=2227,wedgeq=2259,",
1373
+ "weierp=2118,wfr=1d534,wopf=1d568,wp=2118,wr=2240,wreath=2240,wscr=1d4cc,xcap=22c2,xcirc=25ef,xcup=22",
1374
+ "c3,xdtri=25bd,xfr=1d535,xhArr=27fa,xharr=27f7,xi=3be,xlArr=27f8,xlarr=27f5,xmap=27fc,xnis=22fb,xodot",
1375
+ "=2a00,xopf=1d569,xoplus=2a01,xotime=2a02,xrArr=27f9,xrarr=27f6,xscr=1d4cd,xsqcup=2a06,xuplus=2a04,xu",
1376
+ "tri=25b3,xvee=22c1,xwedge=22c0,yacute=fd,yacy=44f,ycirc=177,ycy=44b,yen=a5,yfr=1d536,yicy=457,yopf=1",
1377
+ "d56a,yscr=1d4ce,yucy=44e,yuml=ff,zacute=17a,zcaron=17e,zcy=437,zdot=17c,zeetrf=2128,zeta=3b6,zfr=1d5",
1378
+ "37,zhcy=436,zigrarr=21dd,zopf=1d56b,zscr=1d4cf,zwj=200d,zwnj=200c"
1379
+ ].join("");
1380
+ var NAMED_REFERENCES = new Map(NAMED.split(",").map((entry) => {
1381
+ const [name, codes] = entry.split("=");
1382
+ return [name, String.fromCodePoint(...codes.split(".").map((code) => Number.parseInt(code, 16)))];
1383
+ }));
1384
+
1110
1385
  // src/html.js
1111
1386
  var LATIN1 = "nbsp iexcl cent pound curren yen brvbar sect uml copy ordf laquo not shy reg macr deg plusmn sup2 sup3 acute micro para middot cedil sup1 ordm raquo frac14 frac12 frac34 iquest Agrave Aacute Acirc Atilde Auml Aring AElig Ccedil Egrave Eacute Ecirc Euml Igrave Iacute Icirc Iuml ETH Ntilde Ograve Oacute Ocirc Otilde Ouml times Oslash Ugrave Uacute Ucirc Uuml Yacute THORN szlig agrave aacute acirc atilde auml aring aelig ccedil egrave eacute ecirc euml igrave iacute icirc iuml eth ntilde ograve oacute ocirc otilde ouml divide oslash ugrave uacute ucirc uuml yacute thorn yuml".split(" ");
1112
- var ENTITIES = new Map([
1113
- ...LATIN1.map((name, index) => [name, String.fromCodePoint(160 + index)]),
1114
- ["amp", "&"],
1115
- ["lt", "<"],
1116
- ["gt", ">"],
1117
- ["quot", '"'],
1118
- ["apos", "'"],
1119
- ["AMP", "&"],
1120
- ["LT", "<"],
1121
- ["GT", ">"],
1122
- ["QUOT", '"'],
1123
- ["COPY", "\xA9"],
1124
- ["REG", "\xAE"],
1125
- ["OElig", "\u0152"],
1126
- ["oelig", "\u0153"],
1127
- ["Scaron", "\u0160"],
1128
- ["scaron", "\u0161"],
1129
- ["Yuml", "\u0178"],
1130
- ["fnof", "\u0192"],
1131
- ["circ", "\u02C6"],
1132
- ["tilde", "\u02DC"],
1133
- ["ensp", "\u2002"],
1134
- ["emsp", "\u2003"],
1135
- ["thinsp", "\u2009"],
1136
- ["zwnj", "\u200C"],
1137
- ["zwj", "\u200D"],
1138
- ["lrm", "\u200E"],
1139
- ["rlm", "\u200F"],
1140
- ["ndash", "\u2013"],
1141
- ["mdash", "\u2014"],
1142
- ["lsquo", "\u2018"],
1143
- ["rsquo", "\u2019"],
1144
- ["sbquo", "\u201A"],
1145
- ["ldquo", "\u201C"],
1146
- ["rdquo", "\u201D"],
1147
- ["bdquo", "\u201E"],
1148
- ["dagger", "\u2020"],
1149
- ["Dagger", "\u2021"],
1150
- ["bull", "\u2022"],
1151
- ["hellip", "\u2026"],
1152
- ["permil", "\u2030"],
1153
- ["prime", "\u2032"],
1154
- ["Prime", "\u2033"],
1155
- ["lsaquo", "\u2039"],
1156
- ["rsaquo", "\u203A"],
1157
- ["euro", "\u20AC"],
1158
- ["trade", "\u2122"],
1159
- ["larr", "\u2190"],
1160
- ["uarr", "\u2191"],
1161
- ["rarr", "\u2192"],
1162
- ["darr", "\u2193"],
1163
- ["hearts", "\u2665"],
1164
- ["check", "\u2713"],
1165
- // The punctuation of a web address, as browsers read these names
1166
- // ("https://bank.example&commat;evil.example/" goes to evil.example)
1167
- ["Tab", " "],
1168
- ["NewLine", "\n"],
1169
- ["excl", "!"],
1170
- ["num", "#"],
1171
- ["dollar", "$"],
1172
- ["percnt", "%"],
1173
- ["lpar", "("],
1174
- ["rpar", ")"],
1175
- ["ast", "*"],
1176
- ["midast", "*"],
1177
- ["plus", "+"],
1178
- ["comma", ","],
1179
- ["period", "."],
1180
- ["sol", "/"],
1181
- ["colon", ":"],
1182
- ["semi", ";"],
1183
- ["equals", "="],
1184
- ["quest", "?"],
1185
- ["commat", "@"],
1186
- ["lsqb", "["],
1187
- ["lbrack", "["],
1188
- ["bsol", "\\"],
1189
- ["rsqb", "]"],
1190
- ["rbrack", "]"],
1191
- ["Hat", "^"],
1192
- ["lowbar", "_"],
1193
- ["UnderBar", "_"],
1194
- ["grave", "`"],
1195
- ["DiacriticalGrave", "`"],
1196
- ["lcub", "{"],
1197
- ["lbrace", "{"],
1198
- ["verbar", "|"],
1199
- ["vert", "|"],
1200
- ["VerticalLine", "|"],
1201
- ["rcub", "}"],
1202
- ["rbrace", "}"]
1203
- ]);
1204
1387
  var LEGACY = /* @__PURE__ */ new Set([...LATIN1, "amp", "lt", "gt", "quot", "AMP", "LT", "GT", "QUOT", "COPY", "REG"]);
1205
1388
  var LEGACY_LONGEST = Math.max(...[...LEGACY].map((name) => name.length));
1206
- var LONGER_NAMES = new Set("centerdot copysr divideontimes gtcc gtcir gtdot gtlPar gtquest gtrapprox gtrarr gtrdot gtreqless gtreqqless gtrless gtrsim ltcc ltcir ltdot lthree ltimes ltlarr ltquest ltrPar ltri ltrie ltrif notin notinE notindot notinva notinvb notinvc notni notniva notnivb notnivc parallel timesb timesbar timesd".split(" "));
1207
1389
  var WINDOWS_1252 = [8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376];
1208
1390
  var REFERENCE = /&(?:#([xX])([\da-fA-F]+)|#(\d+)|([a-zA-Z][a-zA-Z\d]{0,31}))(;?)/g;
1209
1391
  function fromCode(code) {
@@ -1221,8 +1403,8 @@ function decodeEntities(value, { attribute = false } = {}) {
1221
1403
  if (hex !== void 0 || decimal !== void 0) {
1222
1404
  return fromCode(hex === void 0 ? Number.parseInt(decimal, 10) : Number.parseInt(hex, 16));
1223
1405
  }
1224
- if (semicolon && (ENTITIES.has(name) || LONGER_NAMES.has(name))) {
1225
- return ENTITIES.get(name) ?? match;
1406
+ if (semicolon && NAMED_REFERENCES.has(name)) {
1407
+ return NAMED_REFERENCES.get(name);
1226
1408
  }
1227
1409
  let legacy;
1228
1410
  for (let length = Math.min(name.length, LEGACY_LONGEST); length >= 2 && legacy === void 0; length--) {
@@ -1236,7 +1418,7 @@ function decodeEntities(value, { attribute = false } = {}) {
1236
1418
  if (attribute && /[\da-zA-Z=]/.test(next)) {
1237
1419
  return match;
1238
1420
  }
1239
- return ENTITIES.get(legacy) + rest;
1421
+ return NAMED_REFERENCES.get(legacy) + rest;
1240
1422
  });
1241
1423
  }
1242
1424
  var UNSHOWN = /* @__PURE__ */ new Set(["script", "style", "title", "noembed", "noframes", "iframe"]);
@@ -2083,6 +2265,34 @@ function continuesName(text, index) {
2083
2265
  var FILE_TLDS = /* @__PURE__ */ new Set(["ai", "app", "md", "mov", "pm", "ps", "py", "rs", "sh", "so", "zip"]);
2084
2266
  var LINK_FILE_TLDS = /* @__PURE__ */ new Set(["md", "mov", "pm", "ps", "py", "rs", "sh", "so", "zip"]);
2085
2267
  var CLICK_TRACKERS = ["sendgrid.net", "list-manage.com", "mandrillapp.com", "awstrack.me", "pstmrk.it", "hubspotlinks.com", "rs6.net", "klclick.com", "mjt.lu", "exacttarget.com", "convertkit-mail.com", "convertkit-mail2.com", "kit-mail3.com", "sendibt2.com", "sendibt3.com", "createsend1.com", "cmail20.com", "clicks.mlsend.com", "acemlnb.com", "link.mail.beehiiv.com", "bhclick.com", "safelinks.protection.outlook.com", "urldefense.com", "urldefense.proofpoint.com", "mimecast.com", "mimecastprotect.com", "cudasvc.com", "secure-web.cisco.com"];
2268
+ var MAX_URL_HOST = 4096;
2269
+ var AUTHORITY_END = /[/\\?#]/g;
2270
+ var IGNORED_IN_HOST = /[\u00AD\u034F\u180B-\u180D\u180F\u200B\u2060\u2064\uFE00-\uFE0F\uFEFF]|\uD82F[\uDCA0-\uDCA3]|\uDB40[\uDD00-\uDDEF]/g;
2271
+ function boundedUrl(address2) {
2272
+ const text = String(address2).replaceAll(/[\t\n\r]/g, "");
2273
+ const colon = text.indexOf(":");
2274
+ let start = colon === -1 ? 0 : colon + 1;
2275
+ while (start < text.length && (text[start] === "/" || text[start] === "\\")) {
2276
+ start++;
2277
+ }
2278
+ AUTHORITY_END.lastIndex = start;
2279
+ const end = AUTHORITY_END.exec(text)?.index ?? text.length;
2280
+ const file = /^[\0- ]*file:/i.test(text);
2281
+ const at = file ? start : Math.max(start, text.lastIndexOf("@", end - 1) + 1);
2282
+ const bracket = text.lastIndexOf("]", end - 1);
2283
+ const port = text.lastIndexOf(":", end - 1);
2284
+ let host = text.slice(at, !file && port > Math.max(at - 1, bracket) ? port : end);
2285
+ if (host.length > MAX_URL_HOST && host.includes("%")) {
2286
+ try {
2287
+ host = decodeURIComponent(host);
2288
+ } catch {
2289
+ }
2290
+ }
2291
+ if (host.length > MAX_URL_HOST && host.replaceAll(IGNORED_IN_HOST, "").length > MAX_URL_HOST) {
2292
+ throw new TypeError("Invalid URL: host too long");
2293
+ }
2294
+ return new URL(text);
2295
+ }
2086
2296
  var isUnder = (host, domain) => host === domain || host.endsWith(`.${domain}`);
2087
2297
  function isClickTracker(url) {
2088
2298
  const host = url.hostname.toLowerCase();
@@ -2111,7 +2321,7 @@ var MAX_GATEWAYS = 16;
2111
2321
  function unwrapLink(address2) {
2112
2322
  let url;
2113
2323
  try {
2114
- url = new URL(address2);
2324
+ url = boundedUrl(address2);
2115
2325
  } catch {
2116
2326
  return null;
2117
2327
  }
@@ -2122,7 +2332,7 @@ function unwrapLink(address2) {
2122
2332
  }
2123
2333
  let next;
2124
2334
  try {
2125
- next = new URL(target);
2335
+ next = boundedUrl(target);
2126
2336
  } catch {
2127
2337
  break;
2128
2338
  }
@@ -2382,7 +2592,7 @@ function endsInsideTag(html, end, link) {
2382
2592
  return joined !== host && (!isHost(host) || isHost(joined) && registrableDomain(joined) !== registrableDomain(host));
2383
2593
  }
2384
2594
  var MAX_SHOWN_HTML = 2e5;
2385
- function extractLinks(text, html, { maxLength = 5e5, bareHosts = false } = {}) {
2595
+ function extractLinks(text, html, { maxLength = 5e5, bareHosts: bareHosts2 = false } = {}) {
2386
2596
  text = typeof text === "string" ? foldToAscii(text) : text;
2387
2597
  html = typeof html === "string" ? foldToAscii(html) : html;
2388
2598
  const links = [];
@@ -2400,7 +2610,7 @@ function extractLinks(text, html, { maxLength = 5e5, bareHosts = false } = {}) {
2400
2610
  }
2401
2611
  let url;
2402
2612
  try {
2403
- url = new URL(href);
2613
+ url = boundedUrl(href);
2404
2614
  } catch {
2405
2615
  return;
2406
2616
  }
@@ -2449,7 +2659,7 @@ ${shown || ""}`;
2449
2659
  }
2450
2660
  add(link);
2451
2661
  }
2452
- if (bareHosts && index < 2) {
2662
+ if (bareHosts2 && index < 2) {
2453
2663
  bareLinkHosts(read, (host) => {
2454
2664
  bareFound.push(host);
2455
2665
  });
@@ -2725,7 +2935,7 @@ function isDeceptiveLink(link, { from } = {}) {
2725
2935
  }
2726
2936
  let shownUrl;
2727
2937
  try {
2728
- shownUrl = new URL(/^https?:\/\//i.test(link.text) ? link.text : `http://${link.text}`);
2938
+ shownUrl = boundedUrl(/^https?:\/\//i.test(link.text) ? link.text : `http://${link.text}`);
2729
2939
  } catch {
2730
2940
  return false;
2731
2941
  }
@@ -3971,7 +4181,8 @@ async function parseWithin(raw, parse = parseMessage, deadline = Date.now() + PA
3971
4181
  }
3972
4182
  }
3973
4183
  }
3974
- async function expandAttachments(attachments = [], errors = [], parse = parseMessage, unopened = []) {
4184
+ async function expandAttachments(attachments = [], errors = [], parse = parseMessage, gets = {}) {
4185
+ const { unopened = [], mails = [] } = Array.isArray(gets) ? { unopened: gets } : gets ?? {};
3975
4186
  const all = [];
3976
4187
  const deadline = Date.now() + PARSE_TIMEOUT;
3977
4188
  let budget = MAX_NESTED_BYTES;
@@ -4020,6 +4231,7 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
4020
4231
  if (inner.error) {
4021
4232
  errors.push(inner.error);
4022
4233
  }
4234
+ mails.push(inner.mail);
4023
4235
  await visit(inner.mail.attachments, prefix ? `${prefix}/${name}` : name || "attached message", depth + 1);
4024
4236
  }
4025
4237
  }
@@ -4038,6 +4250,11 @@ import { isIP } from "node:net";
4038
4250
  import { domainToASCII } from "node:url";
4039
4251
  import { getPublicSuffix } from "tldts";
4040
4252
  var LOOKUP_LABEL = /^[a-z\d_-]{1,63}$/;
4253
+ var LABEL_SEPARATOR = /[.\u3002\uFF0E\uFF61]/g;
4254
+ function lastLabels(name) {
4255
+ const separators = [...name.matchAll(LABEL_SEPARATOR)];
4256
+ return separators.length < 128 ? 0 : separators.at(-128).index + 1;
4257
+ }
4041
4258
  function lookupName(host) {
4042
4259
  if (typeof host !== "string") {
4043
4260
  return null;
@@ -4048,6 +4265,7 @@ function lookupName(host) {
4048
4265
  }
4049
4266
  let name = host.slice(0, end).toLowerCase();
4050
4267
  if (/[\u0080-\u{10FFFF}]/u.test(name)) {
4268
+ name = name.slice(lastLabels(name));
4051
4269
  name = domainToASCII(name) || name;
4052
4270
  }
4053
4271
  if (isIP(name.replaceAll(/^\[|]$/g, "")) !== 0) {
@@ -4387,7 +4605,7 @@ function asciiHostname(value) {
4387
4605
  return null;
4388
4606
  }
4389
4607
  const lower = value.normalize("NFKC").toLowerCase().replace(/\.$/, "");
4390
- if (lower === "" || /[\0-,/:-^`{-\u007F]/.test(lower)) {
4608
+ if (lower === "" || lower.length > 1024 || /[\0-,/:-^`{-\u007F]/.test(lower)) {
4391
4609
  return null;
4392
4610
  }
4393
4611
  const ascii = domainToASCII2(lower);
@@ -4569,8 +4787,8 @@ function trustReputation(result, trusted = []) {
4569
4787
  for (const value of result.checkedValues.filter((item) => allowed.has(item))) {
4570
4788
  const signers = allowed.get(value);
4571
4789
  const counts = (entry) => Boolean(entry) && (signers === null || signers.some((signer) => vouches(normalizeEntry(entry), signer)));
4572
- const detail = result.details[value];
4573
- const listed = result.allowlisted[value] || (detail?.isAllowlisted ? detail.allowlistValue : null);
4790
+ const detail = Object.hasOwn(result.details, value) ? result.details[value] : void 0;
4791
+ const listed = Object.hasOwn(result.allowlisted, value) && result.allowlisted[value] || (detail?.isAllowlisted ? detail.allowlistValue : null);
4574
4792
  if (counts(listed) && !out.isAllowlisted) {
4575
4793
  out.isAllowlisted = true;
4576
4794
  out.allowlistValue = listed;
@@ -5436,8 +5654,990 @@ var Classifier = class _Classifier {
5436
5654
  }
5437
5655
  };
5438
5656
 
5439
- // src/is-arbitrary.js
5657
+ // src/links.js
5658
+ import { Buffer as Buffer7 } from "node:buffer";
5659
+ import { isIP as isIP3 } from "node:net";
5440
5660
  import { parse as parseDomain3 } from "tldts";
5661
+ var LINK_ATTRIBUTES = /* @__PURE__ */ new Set([
5662
+ "href",
5663
+ "src",
5664
+ "srcset",
5665
+ "background",
5666
+ "action",
5667
+ "formaction",
5668
+ "poster",
5669
+ "cite",
5670
+ "longdesc",
5671
+ "xlink:href",
5672
+ "style",
5673
+ "data",
5674
+ "ping",
5675
+ // (of a <meta> refresh: "0;url=https://example.com/")
5676
+ "content",
5677
+ // (a document of its own, see linksOf)
5678
+ "srcdoc"
5679
+ ]);
5680
+ var TAG_START = /<[a-z][^\s/>]*/gi;
5681
+ var URL_START = /\b(?:https?:\/\/|www\.)[^\s<>"'`]+/gi;
5682
+ var CSS_URL = /url\(\s*["']?([^()"']*)/gi;
5683
+ var CSS_IMPORT = /@import\s+["']([^"'<>]*)/gi;
5684
+ var LENIENT = /[\s"'/<](href|src|srcset|background|action|formaction|poster|cite|longdesc|xlink:href|style|data|ping|content|srcdoc)\s*=\s*/gi;
5685
+ var IN_WORD2 = /[\p{L}\p{N}\p{M}_%]/u;
5686
+ var NAMESPACES = /* @__PURE__ */ new Set(["www.w3.org", "w3.org"]);
5687
+ var MAX_HOST = 760;
5688
+ var MAX_SEEN = 1e5;
5689
+ var TRAILING = /* @__PURE__ */ new Set([")", ".", ",", ";", ":", "!", "?", "'", '"', "]"]);
5690
+ var DECODE_CHUNK = 1024 * 1024;
5691
+ var CHUNK_OVERLAP = 4096;
5692
+ var MAX_EMBED_DEPTH = 3;
5693
+ var MAX_EMBEDDED = 1e3;
5694
+ var MAX_EMBEDDED_LENGTH = 10 * 1024 * 1024;
5695
+ var MAX_ATTACHED = 100;
5696
+ var MAX_ATTACHED_BYTES = 50 * 1024 * 1024;
5697
+ var MAX_READ_TIME = 5e3;
5698
+ var MAX_CARD_BYTES = 4 * 1024 * 1024;
5699
+ var MAX_SHOWN_BYTES = 1024 * 1024;
5700
+ function isHostCode2(code) {
5701
+ return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 45 || code === 46;
5702
+ }
5703
+ function isSpace(code) {
5704
+ return code === 32 || code >= 9 && code <= 13;
5705
+ }
5706
+ function urlOf(value) {
5707
+ let href = value.replaceAll(/[\t\n\r]/g, "");
5708
+ let start = 0;
5709
+ let end = href.length;
5710
+ while (start < end && href.codePointAt(start) <= 32) {
5711
+ start++;
5712
+ }
5713
+ while (end > start && href.codePointAt(end - 1) <= 32) {
5714
+ end--;
5715
+ }
5716
+ while (end > start && TRAILING.has(href[end - 1])) {
5717
+ end--;
5718
+ }
5719
+ href = href.slice(start, end);
5720
+ if (/^[\\/]{2}/.test(href)) {
5721
+ href = `https://${href.slice(2)}`;
5722
+ } else if (/^www\./i.test(href)) {
5723
+ href = `http://${href}`;
5724
+ } else if (!/^https?:/i.test(href)) {
5725
+ return null;
5726
+ }
5727
+ try {
5728
+ return boundedUrl(href);
5729
+ } catch {
5730
+ return null;
5731
+ }
5732
+ }
5733
+ function hostOfUrl(url) {
5734
+ let host = url.hostname.toLowerCase();
5735
+ let dots = host.length;
5736
+ while (dots > 0 && host[dots - 1] === ".") {
5737
+ dots--;
5738
+ }
5739
+ host = host.slice(0, dots);
5740
+ if (host.startsWith("[") && host.endsWith("]")) {
5741
+ host = host.slice(1, -1);
5742
+ }
5743
+ if (!host || NAMESPACES.has(host)) {
5744
+ return null;
5745
+ }
5746
+ return isIP3(host) ? host : lookupName(host);
5747
+ }
5748
+ function hostsOf(value) {
5749
+ const url = urlOf(value);
5750
+ if (!url) {
5751
+ return [];
5752
+ }
5753
+ const host = hostOfUrl(url);
5754
+ const hosts = host ? [host] : [];
5755
+ const target = unwrapLink(url.href);
5756
+ if (target && target.href !== url.href) {
5757
+ const targetHost = hostOfUrl(target);
5758
+ if (targetHost && targetHost !== host) {
5759
+ hosts.push(targetHost);
5760
+ }
5761
+ const further = unwrapLink(target.href);
5762
+ if (further && further.href !== target.href) {
5763
+ hosts.unresolved = true;
5764
+ }
5765
+ }
5766
+ return hosts;
5767
+ }
5768
+ function* attributesOf(html) {
5769
+ TAG_START.lastIndex = 0;
5770
+ let match;
5771
+ while ((match = TAG_START.exec(html)) !== null) {
5772
+ let i = match.index + match[0].length;
5773
+ while (i < html.length && html[i] !== ">") {
5774
+ if (isSpace(html.codePointAt(i)) || html[i] === "/") {
5775
+ i++;
5776
+ continue;
5777
+ }
5778
+ let j = i;
5779
+ while (j < html.length && html[j] !== "=" && html[j] !== ">" && html[j] !== "/" && !isSpace(html.codePointAt(j))) {
5780
+ j++;
5781
+ }
5782
+ const name = html.slice(i, j).toLowerCase();
5783
+ if (j === i) {
5784
+ j++;
5785
+ }
5786
+ while (j < html.length && isSpace(html.codePointAt(j))) {
5787
+ j++;
5788
+ }
5789
+ if (html[j] !== "=") {
5790
+ i = j;
5791
+ continue;
5792
+ }
5793
+ j++;
5794
+ while (j < html.length && isSpace(html.codePointAt(j))) {
5795
+ j++;
5796
+ }
5797
+ let value;
5798
+ if (html[j] === '"' || html[j] === "'") {
5799
+ const close = html.indexOf(html[j], j + 1);
5800
+ const stop = close === -1 ? html.length : close;
5801
+ value = html.slice(j + 1, stop);
5802
+ j = stop + 1;
5803
+ } else {
5804
+ const start = j;
5805
+ while (j < html.length && html[j] !== ">" && !isSpace(html.codePointAt(j))) {
5806
+ j++;
5807
+ }
5808
+ value = html.slice(start, j);
5809
+ }
5810
+ if (name) {
5811
+ yield [name, value];
5812
+ }
5813
+ i = j;
5814
+ }
5815
+ TAG_START.lastIndex = Math.max(i, match.index + 1);
5816
+ }
5817
+ }
5818
+ var MAX_VALUE2 = 1024 * 1024;
5819
+ var LONG_VALUE = 64 * 1024;
5820
+ function linksOf(name, value, { add, embed, cut }) {
5821
+ if (value.length > MAX_VALUE2) {
5822
+ cut();
5823
+ value = value.slice(0, MAX_VALUE2);
5824
+ }
5825
+ const decoded = decodeEntities(value, { attribute: true });
5826
+ switch (name) {
5827
+ case "srcdoc": {
5828
+ embed(decoded);
5829
+ break;
5830
+ }
5831
+ case "style": {
5832
+ for (const [, link] of decoded.matchAll(CSS_URL)) {
5833
+ add(link);
5834
+ }
5835
+ break;
5836
+ }
5837
+ case "srcset": {
5838
+ for (const candidate of decoded.split(",")) {
5839
+ add(candidate.trim().split(/\s/, 1)[0]);
5840
+ }
5841
+ break;
5842
+ }
5843
+ // (several, separated by spaces)
5844
+ case "ping": {
5845
+ for (const link of decoded.split(/\s+/)) {
5846
+ add(link);
5847
+ }
5848
+ break;
5849
+ }
5850
+ // (only the address of a refresh)
5851
+ case "content": {
5852
+ const match = /(?:^|[\s;,])url\s*=\s*["']?([^\s"';]+)/i.exec(decoded.slice(0, 4096));
5853
+ if (match) {
5854
+ add(match[1]);
5855
+ }
5856
+ break;
5857
+ }
5858
+ default: {
5859
+ add(decoded);
5860
+ }
5861
+ }
5862
+ }
5863
+ var MAX_LENIENT_VALUE = 4096;
5864
+ var UNQUOTED_END = /* @__PURE__ */ new Set(["<", ">", '"', "'", "`", "="]);
5865
+ function lenientLinks(html, done, reader) {
5866
+ LENIENT.lastIndex = 0;
5867
+ let match;
5868
+ while (!done() && (match = LENIENT.exec(html)) !== null) {
5869
+ const name = match[1].toLowerCase();
5870
+ let i = LENIENT.lastIndex;
5871
+ let end = i;
5872
+ if (html[i] === '"' || html[i] === "'") {
5873
+ const quote = html[i];
5874
+ i++;
5875
+ end = i;
5876
+ while (end < html.length && html[end] !== quote && html[end] !== "<" && html[end] !== ">") {
5877
+ end++;
5878
+ }
5879
+ if (end < html.length && html[end] !== quote) {
5880
+ const close = html.indexOf(quote, end);
5881
+ if (close !== -1 && close - i <= MAX_LENIENT_VALUE && !reader.spend(close - i)) {
5882
+ linksOf(name, html.slice(i, close), reader);
5883
+ }
5884
+ }
5885
+ } else {
5886
+ while (end < html.length && !isSpace(html.codePointAt(end)) && !UNQUOTED_END.has(html[end])) {
5887
+ end++;
5888
+ }
5889
+ }
5890
+ if (reader.spend(end - i)) {
5891
+ break;
5892
+ }
5893
+ linksOf(name, html.slice(i, end), reader);
5894
+ }
5895
+ }
5896
+ function endsLink2(text, i) {
5897
+ const code = text.codePointAt(i);
5898
+ if (code < 128) {
5899
+ return isSpace(code) || code === 34 || code === 39 || code === 60 || code === 62 || code === 96;
5900
+ }
5901
+ return /\s/u.test(text[i]);
5902
+ }
5903
+ function linkReader(text) {
5904
+ let read = 0;
5905
+ let space = -1;
5906
+ let scheme = -1;
5907
+ return (start) => {
5908
+ for (; read < start; read++) {
5909
+ if (endsLink2(text, read)) {
5910
+ space = read;
5911
+ } else if (text[read] === "/" && text[read - 1] === "/" && text[read - 2] === ":") {
5912
+ scheme = read;
5913
+ }
5914
+ }
5915
+ return scheme > space;
5916
+ };
5917
+ }
5918
+ function isSchemeless(text, start) {
5919
+ if (start < 2 || text[start - 2] !== "/") {
5920
+ return false;
5921
+ }
5922
+ if (start === 2) {
5923
+ return true;
5924
+ }
5925
+ const before = text[start - 3];
5926
+ return !(before === ":" || before === "/" || isHostCode2(text.codePointAt(start - 3)));
5927
+ }
5928
+ function bareName(name) {
5929
+ const labels = name.split(".");
5930
+ const tld = labels.pop();
5931
+ if (!/^(?:[a-z]{2,24}|xn--[a-z\d-]{1,59})$/i.test(tld)) {
5932
+ return null;
5933
+ }
5934
+ const names = [];
5935
+ while (labels.length > 0 && names.length < 11) {
5936
+ const label = labels.pop();
5937
+ if (!/^[a-z\d-]{1,63}$/i.test(label)) {
5938
+ break;
5939
+ }
5940
+ names.unshift(label);
5941
+ }
5942
+ if (names.length === 0) {
5943
+ return null;
5944
+ }
5945
+ names[0] = names[0].replace(/^-+/, "");
5946
+ if (!names[0]) {
5947
+ return null;
5948
+ }
5949
+ return `${names.join(".")}.${tld}`.toLowerCase();
5950
+ }
5951
+ function isRealName(host) {
5952
+ const parsed = parseDomain3(host);
5953
+ return Boolean(parsed.isIcann && parsed.domain);
5954
+ }
5955
+ function bareHosts(text, add, { done, seen, from = 0, cutEnd = false }) {
5956
+ let previous = -1;
5957
+ const inLink = linkReader(text);
5958
+ for (let slash = text.indexOf("/"); slash !== -1; slash = text.indexOf("/", slash + 1)) {
5959
+ if (done()) {
5960
+ return;
5961
+ }
5962
+ const floor = Math.max(previous + 1, slash - MAX_HOST);
5963
+ previous = slash;
5964
+ let end = slash;
5965
+ while (end > floor && text.codePointAt(end - 1) >= 48 && text.codePointAt(end - 1) <= 57) {
5966
+ end--;
5967
+ }
5968
+ end = end > floor && text[end - 1] === ":" ? end - 1 : slash;
5969
+ let start = end;
5970
+ while (start > floor && isHostCode2(text.codePointAt(start - 1))) {
5971
+ start--;
5972
+ }
5973
+ if (start === end || start < from || cutEnd && slash === text.length - 1) {
5974
+ continue;
5975
+ }
5976
+ if (start > 0 && (IN_WORD2.test(text[start - 1]) || text[start - 1] === "/" && !isSchemeless(text, start)) || inLink(start)) {
5977
+ continue;
5978
+ }
5979
+ while (end > start && text[end - 1] === ".") {
5980
+ end--;
5981
+ }
5982
+ const host = bareName(text.slice(start, end));
5983
+ if (!host || seen.has(host)) {
5984
+ continue;
5985
+ }
5986
+ if (seen.size < MAX_SEEN) {
5987
+ seen.add(host);
5988
+ }
5989
+ if (isRealName(host)) {
5990
+ add(`http://${host}/`);
5991
+ }
5992
+ }
5993
+ }
5994
+ function looseHosts(text, add, { from = 0, cutEnd = false, seen, done }) {
5995
+ const inLink = linkReader(text);
5996
+ const { length } = text;
5997
+ let i = 0;
5998
+ while (i < length) {
5999
+ if (!isHostCode2(text.codePointAt(i))) {
6000
+ i++;
6001
+ continue;
6002
+ }
6003
+ const start = i;
6004
+ while (i < length && isHostCode2(text.codePointAt(i))) {
6005
+ i++;
6006
+ }
6007
+ let end = i;
6008
+ if (end - start > MAX_HOST || start < from || cutEnd && end === length) {
6009
+ continue;
6010
+ }
6011
+ while (end > start && text[end - 1] === ".") {
6012
+ end--;
6013
+ }
6014
+ const before = start > 0 ? text[start - 1] : "";
6015
+ const after = end < length ? text[end] : "";
6016
+ if (after === "/" || after === "@" || before === "@") {
6017
+ continue;
6018
+ }
6019
+ if (before && (IN_WORD2.test(before) || before === "/" || before === "\\" || before === "&" || before === "#")) {
6020
+ continue;
6021
+ }
6022
+ if (after && after !== "." && IN_WORD2.test(after)) {
6023
+ continue;
6024
+ }
6025
+ const name = text.slice(start, end);
6026
+ if (!name.includes(".") || inLink(start)) {
6027
+ continue;
6028
+ }
6029
+ const lower = name.toLowerCase();
6030
+ if (seen.has(lower)) {
6031
+ continue;
6032
+ }
6033
+ if (seen.size < MAX_SEEN) {
6034
+ seen.add(lower);
6035
+ }
6036
+ if (done()) {
6037
+ return;
6038
+ }
6039
+ const host = bareName(name);
6040
+ if (host && isRealName(host)) {
6041
+ add(host);
6042
+ }
6043
+ }
6044
+ }
6045
+ function* chunksOf(html) {
6046
+ let start = 0;
6047
+ let cutStart = false;
6048
+ while (start < html.length) {
6049
+ const end = Math.min(html.length, start + DECODE_CHUNK);
6050
+ if (end < html.length) {
6051
+ let cut = end;
6052
+ while (cut > end - 65536 && cut > start) {
6053
+ if (html[cut] === "<" || isSpace(html.codePointAt(cut))) {
6054
+ break;
6055
+ }
6056
+ cut--;
6057
+ }
6058
+ if (cut > start && cut > end - 65536) {
6059
+ yield { text: html.slice(start, cut), cutStart, cutEnd: false };
6060
+ start = cut;
6061
+ cutStart = false;
6062
+ continue;
6063
+ }
6064
+ let hard = end;
6065
+ while (hard > start + CHUNK_OVERLAP + 1 && /[\dA-Za-z#]/.test(html[hard - 1])) {
6066
+ hard--;
6067
+ }
6068
+ if (hard < end && html[hard - 1] === "&" && hard - 1 > start + CHUNK_OVERLAP) {
6069
+ hard--;
6070
+ } else {
6071
+ hard = end;
6072
+ }
6073
+ yield { text: html.slice(start, hard), cutStart, cutEnd: true };
6074
+ start = hard - CHUNK_OVERLAP;
6075
+ cutStart = true;
6076
+ continue;
6077
+ }
6078
+ yield { text: html.slice(start, end), cutStart, cutEnd: false };
6079
+ start = end;
6080
+ }
6081
+ }
6082
+ function withoutNul(text) {
6083
+ return text.includes("\0") ? `${text}
6084
+ ${text.replaceAll("\0", "")}` : text;
6085
+ }
6086
+ function hexValue(code) {
6087
+ if (code >= 48 && code <= 57) {
6088
+ return code - 48;
6089
+ }
6090
+ if (code >= 65 && code <= 70) {
6091
+ return code - 55;
6092
+ }
6093
+ if (code >= 97 && code <= 102) {
6094
+ return code - 87;
6095
+ }
6096
+ return -1;
6097
+ }
6098
+ function percentDecoded(text) {
6099
+ const input = Buffer7.from(text);
6100
+ const output = Buffer7.allocUnsafe(input.length);
6101
+ let length = 0;
6102
+ for (let i = 0; i < input.length; i++) {
6103
+ const high = input[i] === 37 ? hexValue(input[i + 1]) : -1;
6104
+ const low = high === -1 ? -1 : hexValue(input[i + 2]);
6105
+ if (low === -1) {
6106
+ output[length++] = input[i];
6107
+ continue;
6108
+ }
6109
+ output[length++] = high * 16 + low;
6110
+ i += 2;
6111
+ }
6112
+ return output.subarray(0, length);
6113
+ }
6114
+ var UTF16LE = /* @__PURE__ */ new Set(["csunicode", "iso-10646-ucs-2", "ucs-2", "unicode", "unicodefeff", "utf-16", "utf-16le"]);
6115
+ var UTF16BE = /* @__PURE__ */ new Set(["unicodefffe", "utf-16be"]);
6116
+ function swapped(bytes) {
6117
+ return Buffer7.from(bytes.subarray(0, bytes.length - bytes.length % 2)).swap16().toString("utf16le");
6118
+ }
6119
+ function decodeText(content, charset = "") {
6120
+ if (content[0] === 255 && content[1] === 254) {
6121
+ return content.subarray(2).toString("utf16le");
6122
+ }
6123
+ if (content[0] === 254 && content[1] === 255) {
6124
+ return swapped(content.subarray(2));
6125
+ }
6126
+ if (UTF16LE.has(charset)) {
6127
+ return content.toString("utf16le");
6128
+ }
6129
+ return UTF16BE.has(charset) ? swapped(content) : content.toString("utf8");
6130
+ }
6131
+ var DATA_DOCUMENT = /^\s*data:\s*(?:text\/html|application\/xhtml\+xml|image\/svg\+xml|text\/xml|application\/xml)\b([^,]*),/i;
6132
+ function dataDocument(value) {
6133
+ const match = DATA_DOCUMENT.exec(value.slice(0, 1024));
6134
+ if (!match) {
6135
+ return null;
6136
+ }
6137
+ const bytes = percentDecoded(value.slice(match[0].length));
6138
+ const content = /;\s*base64\s*$/i.test(match[1]) ? Buffer7.from(bytes.toString("latin1"), "base64") : bytes;
6139
+ const charset = /;\s*charset\s*=\s*"?([^;"\s]+)/i.exec(match[1]);
6140
+ return decodeText(content, charset ? charset[1].toLowerCase() : "");
6141
+ }
6142
+ var LINE_BREAK = /\r\n?|\n/;
6143
+ var FOLD = /(?:\r\n?|\n)[ \t]/g;
6144
+ var QUOTED_PRINTABLE = /;\s*(?:encoding\s*=\s*)?"?quoted-printable"?\s*(?:;|$)/i;
6145
+ var MEDIA_PROPERTIES = /* @__PURE__ */ new Set(["photo", "logo", "sound", "key", "attach"]);
6146
+ var HTML_PROPERTIES = /^(?:x-alt-desc|styled-description|description|comment)[;:]/i;
6147
+ function readEscapes(text, lenient = false) {
6148
+ let index = text.indexOf("\\");
6149
+ if (index === -1) {
6150
+ return text;
6151
+ }
6152
+ let out = "";
6153
+ let parts = [];
6154
+ let start = 0;
6155
+ while (index !== -1 && index + 1 < text.length) {
6156
+ const next = text[index + 1];
6157
+ if (lenient || "nN,;\\".includes(next)) {
6158
+ parts.push(text.slice(start, index), next === "n" || next === "N" ? "\n" : next);
6159
+ start = index + 2;
6160
+ index = text.indexOf("\\", start);
6161
+ if (parts.length >= 65536) {
6162
+ out += parts.join("");
6163
+ parts = [];
6164
+ }
6165
+ } else {
6166
+ index = text.indexOf("\\", index + 1);
6167
+ }
6168
+ }
6169
+ parts.push(text.slice(start));
6170
+ return out + parts.join("");
6171
+ }
6172
+ function valueColon(line) {
6173
+ let colon = line.indexOf(":");
6174
+ let from = 0;
6175
+ for (; ; ) {
6176
+ const quote = line.indexOf('"', from);
6177
+ if (quote === -1 || colon === -1 || colon < quote) {
6178
+ return colon;
6179
+ }
6180
+ const end = line.indexOf('"', quote + 1);
6181
+ if (end === -1) {
6182
+ return -1;
6183
+ }
6184
+ from = end + 1;
6185
+ if (colon < from) {
6186
+ colon = line.indexOf(":", from);
6187
+ }
6188
+ }
6189
+ }
6190
+ function trimSpaceEnd(text) {
6191
+ let end = text.length;
6192
+ while (end > 0 && (text[end - 1] === " " || text[end - 1] === " ")) {
6193
+ end--;
6194
+ }
6195
+ return end === text.length ? text : text.slice(0, end);
6196
+ }
6197
+ function decodeQuotedPrintable(value, charset) {
6198
+ let decoder;
6199
+ try {
6200
+ decoder = new TextDecoder(charset || "utf8");
6201
+ } catch {
6202
+ decoder = new TextDecoder("utf8");
6203
+ }
6204
+ if (!/[\u0080-\u{10FFFF}]/u.test(value)) {
6205
+ const bytes = Buffer7.from(value, "latin1");
6206
+ let length = 0;
6207
+ for (let index = 0; index < bytes.length; index++) {
6208
+ const hex = bytes[index] === 61 ? value.slice(index + 1, index + 3) : "";
6209
+ if (/^[\da-f]{2}$/i.test(hex)) {
6210
+ bytes[length++] = Number.parseInt(hex, 16);
6211
+ index += 2;
6212
+ } else {
6213
+ bytes[length++] = bytes[index];
6214
+ }
6215
+ }
6216
+ return decoder.decode(bytes.subarray(0, length));
6217
+ }
6218
+ return value.replaceAll(/(?:=[\da-f]{2})+/gi, (run) => decoder.decode(Buffer7.from(run.replaceAll("=", ""), "hex")));
6219
+ }
6220
+ function quotedPrintableValues(text) {
6221
+ if (!/quoted-printable/i.test(text)) {
6222
+ return text;
6223
+ }
6224
+ const lines2 = text.split(LINE_BREAK);
6225
+ const out = [];
6226
+ for (let i = 0; i < lines2.length; i++) {
6227
+ const line = lines2[i];
6228
+ const colon = valueColon(line);
6229
+ const parameters = colon === -1 ? "" : line.slice(0, colon);
6230
+ if (colon === -1 || !QUOTED_PRINTABLE.test(parameters)) {
6231
+ out.push(line);
6232
+ continue;
6233
+ }
6234
+ const charset = /;\s*charset\s*=\s*"?([\w.:-]+)/i.exec(parameters)?.[1];
6235
+ const parts = [trimSpaceEnd(line.slice(colon + 1))];
6236
+ while (parts.at(-1).endsWith("=") && i + 1 < lines2.length) {
6237
+ parts[parts.length - 1] = parts.at(-1).slice(0, -1);
6238
+ parts.push(trimSpaceEnd(lines2[++i]));
6239
+ }
6240
+ out.push(line.slice(0, colon + 1) + decodeQuotedPrintable(parts.join(""), charset));
6241
+ }
6242
+ return out.join("\n");
6243
+ }
6244
+ function shownText(text) {
6245
+ const unfolded = text.replaceAll(FOLD, "");
6246
+ const shown = /* @__PURE__ */ new Set([text, readEscapes(unfolded)]);
6247
+ const decoded = quotedPrintableValues(unfolded);
6248
+ if (decoded !== unfolded) {
6249
+ shown.add(readEscapes(decoded));
6250
+ if (unfolded !== text) {
6251
+ shown.add(quotedPrintableValues(text));
6252
+ }
6253
+ }
6254
+ if (/\\[^nN,;\\]/.test(decoded)) {
6255
+ shown.add(readEscapes(decoded, true));
6256
+ }
6257
+ return [...shown].reverse().join("\n");
6258
+ }
6259
+ function htmlDescriptions(text) {
6260
+ if (!/[<&]|altrep/i.test(text)) {
6261
+ return "";
6262
+ }
6263
+ const html = [];
6264
+ for (const line of text.replaceAll(FOLD, "").split(LINE_BREAK)) {
6265
+ const colon = valueColon(line);
6266
+ if (colon === -1) {
6267
+ continue;
6268
+ }
6269
+ const altrep = /;\s*altrep\s*=\s*"([^"]*)"/i.exec(line.slice(0, colon))?.[1];
6270
+ if (altrep) {
6271
+ html.push(`<iframe src="${altrep.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;")}"></iframe>`);
6272
+ }
6273
+ if (!HTML_PROPERTIES.test(line)) {
6274
+ continue;
6275
+ }
6276
+ const value = readEscapes(line.slice(colon + 1));
6277
+ if (/[<&]/.test(value)) {
6278
+ html.push(value);
6279
+ }
6280
+ }
6281
+ return html.join("\n");
6282
+ }
6283
+ var BINARY = /;\s*(?:(?:encoding\s*=\s*)?"?(?:b|base64)"?|value\s*=\s*"?binary"?)\s*(?:;|$)/i;
6284
+ function withoutBinaryValues(text) {
6285
+ if (!/base64|binary|encoding\s*=\s*"?b|;\s*b\s*[;:]/i.test(text)) {
6286
+ return text;
6287
+ }
6288
+ const lines2 = text.split(LINE_BREAK);
6289
+ const out = [];
6290
+ for (let i = 0; i < lines2.length; i++) {
6291
+ const line = lines2[i];
6292
+ const colon = valueColon(line);
6293
+ if (colon === -1 || !BINARY.test(line.slice(0, colon))) {
6294
+ out.push(line);
6295
+ continue;
6296
+ }
6297
+ const value = [line.slice(colon + 1)];
6298
+ let next = i + 1;
6299
+ while (next < lines2.length && (lines2[next].startsWith(" ") || lines2[next].startsWith(" "))) {
6300
+ value.push(lines2[next++]);
6301
+ }
6302
+ if (!value.every((part) => /^[\w+/=\s]*$/.test(part))) {
6303
+ for (let index = i; index < next; index++) {
6304
+ out.push(lines2[index]);
6305
+ }
6306
+ i = next - 1;
6307
+ continue;
6308
+ }
6309
+ while (next < lines2.length && lines2[next] !== "" && /^[\w+/=]+$/.test(lines2[next])) {
6310
+ value.push(lines2[next++]);
6311
+ }
6312
+ if (next < lines2.length && lines2[next] === "") {
6313
+ next++;
6314
+ }
6315
+ i = next - 1;
6316
+ const name = line.slice(0, colon).split(";")[0].split(".").pop().trim().toLowerCase();
6317
+ out.push(MEDIA_PROPERTIES.has(name) ? line.slice(0, colon + 1) : line.slice(0, colon + 1) + Buffer7.from(value.join("").replaceAll(/\s/g, ""), "base64").toString("utf8"));
6318
+ }
6319
+ return out.join("\n");
6320
+ }
6321
+ var HTML_TYPES = /* @__PURE__ */ new Set(["text/html", "application/xhtml+xml", "image/svg+xml", "text/xml", "application/xml"]);
6322
+ var HTML_EXTENSIONS = /* @__PURE__ */ new Set(["html", "htm", "shtml", "xhtml", "xht", "svg", "xml"]);
6323
+ var CARD_TYPES = /* @__PURE__ */ new Set(["text/calendar", "text/x-vcalendar", "application/ics", "text/vcard", "text/x-vcard", "text/directory"]);
6324
+ var CARD_EXTENSIONS = /* @__PURE__ */ new Set(["ics", "ical", "ifb", "vcs", "vcf", "vcard"]);
6325
+ var MESSAGE_TYPES = /* @__PURE__ */ new Set(["message/rfc822", "message/global"]);
6326
+ function extensionOf2(filename) {
6327
+ let end = filename.length;
6328
+ while (end > 0 && (filename[end - 1] === "." || filename[end - 1] === " ")) {
6329
+ end--;
6330
+ }
6331
+ const name = filename.slice(0, end).trim().toLowerCase();
6332
+ const index = name.lastIndexOf(".");
6333
+ return index !== -1 && index < name.length - 1 ? name.slice(index + 1) : null;
6334
+ }
6335
+ function extensionsOf(attachment) {
6336
+ const names = [];
6337
+ if (typeof attachment.filename === "string") {
6338
+ names.push(attachment.filename);
6339
+ }
6340
+ const { headers } = attachment;
6341
+ if (headers && typeof headers.get === "function") {
6342
+ for (const [field, parameter] of [["content-type", "name"], ["content-disposition", "filename"]]) {
6343
+ const value = headers.get(field)?.params?.[parameter];
6344
+ if (typeof value === "string" && value !== "") {
6345
+ names.push(value);
6346
+ }
6347
+ }
6348
+ }
6349
+ return new Set(names.flatMap((name) => [extensionOf2(name), extensionOf(name)]).filter(Boolean));
6350
+ }
6351
+ function charsetOf(attachment) {
6352
+ const { headers } = attachment;
6353
+ const charset = headers && typeof headers.get === "function" ? headers.get("content-type")?.params?.charset : null;
6354
+ return typeof charset === "string" ? charset.toLowerCase() : "";
6355
+ }
6356
+ function kindOf(attachment) {
6357
+ const { content } = attachment ?? {};
6358
+ if (!Buffer7.isBuffer(content) || content.length === 0) {
6359
+ return null;
6360
+ }
6361
+ const type = typeof attachment.contentType === "string" ? attachment.contentType.toLowerCase() : "";
6362
+ const extensions = extensionsOf(attachment);
6363
+ const has = (set) => [...extensions].some((name) => set.has(name));
6364
+ if (MESSAGE_TYPES.has(type) || extensions.has("eml") || extensions.has("u8msg")) {
6365
+ return null;
6366
+ }
6367
+ const isHtml = HTML_TYPES.has(type) || has(HTML_EXTENSIONS);
6368
+ const isCard = !isHtml && (CARD_TYPES.has(type) || has(CARD_EXTENSIONS));
6369
+ const isText = isCard || !isHtml && type === "text/plain";
6370
+ return isHtml || isText ? { isHtml, isText, isCard } : null;
6371
+ }
6372
+ function* documentsOf(attachments, state, late) {
6373
+ const read = [];
6374
+ let bytes = 0;
6375
+ let opened = 0;
6376
+ for (const attachment of attachments) {
6377
+ const kind = kindOf(attachment);
6378
+ if (!kind) {
6379
+ continue;
6380
+ }
6381
+ if (!kind.isText && ++opened > MAX_ATTACHED || bytes + attachment.content.length > MAX_ATTACHED_BYTES) {
6382
+ state.tooComplex = true;
6383
+ break;
6384
+ }
6385
+ bytes += attachment.content.length;
6386
+ read.push({ attachment, kind });
6387
+ }
6388
+ for (const { attachment, kind: { isHtml, isCard } } of read) {
6389
+ if (late()) {
6390
+ state.incomplete = true;
6391
+ return;
6392
+ }
6393
+ const text = decodeText(attachment.content, charsetOf(attachment));
6394
+ if (isHtml) {
6395
+ yield { html: text };
6396
+ continue;
6397
+ }
6398
+ if (!isCard) {
6399
+ yield { text };
6400
+ continue;
6401
+ }
6402
+ let readable = withoutBinaryValues(text.length > MAX_CARD_BYTES ? text.slice(0, MAX_CARD_BYTES) : text);
6403
+ if (text.length > MAX_CARD_BYTES || readable.length > MAX_SHOWN_BYTES) {
6404
+ state.tooComplex = true;
6405
+ readable = readable.slice(0, MAX_SHOWN_BYTES);
6406
+ }
6407
+ if (late()) {
6408
+ state.incomplete = true;
6409
+ return;
6410
+ }
6411
+ yield { text: shownText(readable) };
6412
+ if (late()) {
6413
+ state.incomplete = true;
6414
+ return;
6415
+ }
6416
+ const html = htmlDescriptions(readable);
6417
+ if (html !== "") {
6418
+ yield { html };
6419
+ }
6420
+ }
6421
+ }
6422
+ var LATE_EVERY = 1024;
6423
+ function carriedLinkHosts(mail, {
6424
+ mails = [],
6425
+ attachments = [],
6426
+ maxHosts = 250,
6427
+ hostsInText = false
6428
+ } = {}) {
6429
+ const hosts = /* @__PURE__ */ new Set();
6430
+ const loose = /* @__PURE__ */ new Set();
6431
+ const seen = /* @__PURE__ */ new Set();
6432
+ const seenBare = /* @__PURE__ */ new Set();
6433
+ const seenLoose = /* @__PURE__ */ new Set();
6434
+ let more = false;
6435
+ let full = false;
6436
+ const domains = /* @__PURE__ */ new Set();
6437
+ const state = { tooComplex: false, incomplete: false };
6438
+ const until = Date.now() + MAX_READ_TIME;
6439
+ const late = () => {
6440
+ if (!state.incomplete && Date.now() > until) {
6441
+ state.incomplete = true;
6442
+ }
6443
+ return state.incomplete;
6444
+ };
6445
+ let calls = 0;
6446
+ const done = () => full || state.incomplete || ++calls % LATE_EVERY === 0 && late();
6447
+ const looseDone = () => done() || loose.size > maxHosts;
6448
+ const addLoose = (host) => {
6449
+ if (!looseDone()) {
6450
+ loose.add(host);
6451
+ }
6452
+ };
6453
+ const queue = [mail, ...mails].map((document) => ({ document, depth: 0 }));
6454
+ const carried = documentsOf(attachments, state, late);
6455
+ let depth = 0;
6456
+ let embedded = 0;
6457
+ let embeddedLength = 0;
6458
+ const embeddedSeen = /* @__PURE__ */ new Set();
6459
+ const embed = (html) => {
6460
+ if (html === "" || embeddedSeen.has(html)) {
6461
+ return;
6462
+ }
6463
+ if (depth >= MAX_EMBED_DEPTH || embedded >= MAX_EMBEDDED || embeddedLength + html.length > MAX_EMBEDDED_LENGTH) {
6464
+ state.tooComplex = true;
6465
+ return;
6466
+ }
6467
+ embeddedSeen.add(html);
6468
+ embedded++;
6469
+ embeddedLength += html.length;
6470
+ queue.push({ document: { html }, depth: depth + 1 });
6471
+ };
6472
+ const add = (value) => {
6473
+ if (full || typeof value !== "string" || value === "" || seen.has(value)) {
6474
+ return;
6475
+ }
6476
+ if (seen.size < MAX_SEEN) {
6477
+ seen.add(value);
6478
+ }
6479
+ if (/^\s*data:/i.test(value)) {
6480
+ const html = dataDocument(value);
6481
+ if (html !== null) {
6482
+ embed(html);
6483
+ }
6484
+ return;
6485
+ }
6486
+ const found = hostsOf(value);
6487
+ if (found.unresolved) {
6488
+ state.tooComplex = true;
6489
+ }
6490
+ for (const host of found) {
6491
+ if (hosts.has(host)) {
6492
+ continue;
6493
+ }
6494
+ const domain = registrableDomain(host);
6495
+ if (hosts.size < maxHosts || !domains.has(domain) && domains.size < maxHosts) {
6496
+ hosts.add(host);
6497
+ domains.add(domain);
6498
+ continue;
6499
+ }
6500
+ more = true;
6501
+ if (domains.size >= maxHosts) {
6502
+ full = true;
6503
+ return;
6504
+ }
6505
+ }
6506
+ };
6507
+ let spent = 0;
6508
+ const spend = (length) => {
6509
+ spent += length;
6510
+ if (spent < LONG_VALUE) {
6511
+ return false;
6512
+ }
6513
+ spent = 0;
6514
+ return late();
6515
+ };
6516
+ const reader = {
6517
+ add,
6518
+ embed,
6519
+ spend,
6520
+ cut() {
6521
+ state.tooComplex = true;
6522
+ }
6523
+ };
6524
+ const readText = (source, { cutStart = false, cutEnd = false, exact = false } = {}) => {
6525
+ for (const match of source.matchAll(URL_START)) {
6526
+ if (done()) {
6527
+ break;
6528
+ }
6529
+ if (cutStart && match.index === 0) {
6530
+ continue;
6531
+ }
6532
+ if (cutEnd && match.index + match[0].length === source.length) {
6533
+ if (exact && source.length - match.index < CHUNK_OVERLAP) {
6534
+ continue;
6535
+ }
6536
+ const value = match[0];
6537
+ const at = /^www\./i.test(value) ? 0 : value.indexOf("//") + 2;
6538
+ if (!/[/?#\\]/.test(value.slice(at))) {
6539
+ state.tooComplex = true;
6540
+ continue;
6541
+ }
6542
+ add(value);
6543
+ const url = urlOf(value);
6544
+ if (url && isClickTracker(url)) {
6545
+ state.tooComplex = true;
6546
+ }
6547
+ continue;
6548
+ }
6549
+ add(match[0]);
6550
+ }
6551
+ const from = cutStart ? 1 : 0;
6552
+ bareHosts(source, add, {
6553
+ done,
6554
+ seen: seenBare,
6555
+ from,
6556
+ cutEnd
6557
+ });
6558
+ if (hostsInText && !looseDone()) {
6559
+ looseHosts(source, addLoose, {
6560
+ from,
6561
+ cutEnd,
6562
+ seen: seenLoose,
6563
+ done: looseDone
6564
+ });
6565
+ }
6566
+ };
6567
+ for (let index = 0; ; index++) {
6568
+ if (full || late()) {
6569
+ break;
6570
+ }
6571
+ if (index >= queue.length) {
6572
+ const next = carried.next();
6573
+ if (next.done) {
6574
+ break;
6575
+ }
6576
+ queue.push({ document: next.value, depth: 0 });
6577
+ }
6578
+ ({ depth } = queue[index]);
6579
+ const { document } = queue[index];
6580
+ const html = withoutNul(typeof document?.html === "string" ? document.html : "");
6581
+ const text = withoutNul(typeof document?.text === "string" ? document.text : "");
6582
+ for (const [name, value] of attributesOf(html)) {
6583
+ if (done()) {
6584
+ break;
6585
+ }
6586
+ if (!LINK_ATTRIBUTES.has(name)) {
6587
+ continue;
6588
+ }
6589
+ if (spend(value.length)) {
6590
+ break;
6591
+ }
6592
+ linksOf(name, value, reader);
6593
+ }
6594
+ lenientLinks(html, done, reader);
6595
+ for (const pattern of [CSS_URL, CSS_IMPORT]) {
6596
+ for (const [, link] of html.matchAll(pattern)) {
6597
+ if (done() || spend(link.length)) {
6598
+ break;
6599
+ }
6600
+ if (link.length > MAX_VALUE2) {
6601
+ state.tooComplex = true;
6602
+ }
6603
+ add(decodeEntities(link.slice(0, MAX_VALUE2), { attribute: true }));
6604
+ }
6605
+ }
6606
+ for (const chunk2 of chunksOf(text)) {
6607
+ if (full || late()) {
6608
+ break;
6609
+ }
6610
+ readText(chunk2.text, { ...chunk2, exact: true });
6611
+ }
6612
+ for (const chunk2 of chunksOf(html)) {
6613
+ if (full || late()) {
6614
+ break;
6615
+ }
6616
+ const decoded = decodeEntities(chunk2.text);
6617
+ readText(decoded, { ...chunk2, exact: decoded.endsWith(chunk2.text.slice(-CHUNK_OVERLAP)) });
6618
+ }
6619
+ }
6620
+ if (!more && !state.incomplete) {
6621
+ for (const name of loose) {
6622
+ if (hosts.size >= maxHosts) {
6623
+ break;
6624
+ }
6625
+ const [host] = hostsOf(`http://${name}/`);
6626
+ if (host) {
6627
+ hosts.add(host);
6628
+ }
6629
+ }
6630
+ }
6631
+ return {
6632
+ hosts: [...hosts],
6633
+ more,
6634
+ tooComplex: state.tooComplex,
6635
+ incomplete: state.incomplete
6636
+ };
6637
+ }
6638
+
6639
+ // src/is-arbitrary.js
6640
+ import { parse as parseDomain4 } from "tldts";
5441
6641
  var GTUBE = "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X";
5442
6642
  var BLOCKED_SUBJECT = /cheecck y0ur acc0untt|\brecorded\s+you\s+(?:masturbating|jerking|touching\s+yourself|watching\s+porn)\b|personal data has leaked|private information has been stolen/i;
5443
6643
  var HACKED_SUBJECT = /\byou['’]ve been hacked\b|\baccount is hacked\b|\b(?:i|we)(?:\s+have|['’]ve)?\s+recorded\s+you\b|\brecorded\s+you\s+(?:doing|while|watching|through|on\s+(?:cam|camera|video|webcam))\b/i;
@@ -5628,13 +6828,13 @@ function joinsDisplay(brand, display, name) {
5628
6828
  return false;
5629
6829
  }
5630
6830
  function isOfficial({ brand, official }, domain, display, shared = true) {
5631
- const parsed = parseDomain3(domain);
6831
+ const parsed = parseDomain4(domain);
5632
6832
  const name = parsed.domainWithoutSuffix ?? "";
5633
6833
  const compact = brand.replaceAll(" ", "");
5634
6834
  if (parsed.publicSuffix === compact || compact.length >= 5 && joinsDisplay(brand, display, name) && !JOINED_WORDS.some((word) => name.slice(compact.length).includes(word))) {
5635
6835
  return true;
5636
6836
  }
5637
- return official.some((entry) => entry instanceof RegExp ? entry.test(registrableDomain(domain)) : entry.includes(".") ? (shared || parseDomain3(entry).domain !== null) && (domain === entry || domain.endsWith(`.${entry}`)) : name === entry);
6837
+ return official.some((entry) => entry instanceof RegExp ? entry.test(registrableDomain(domain)) : entry.includes(".") ? (shared || parseDomain4(entry).domain !== null) && (domain === entry || domain.endsWith(`.${entry}`)) : name === entry);
5638
6838
  }
5639
6839
  function headerString(mail, name) {
5640
6840
  const value = mail.headers?.get?.(name);
@@ -5736,7 +6936,7 @@ ${withoutHelp(markup)}`) || isInjection(readable) || attributeText(markup).some(
5736
6936
  }
5737
6937
  } else if (claimed && isWholeName(name, claimed)) {
5738
6938
  const domain = claimed[1];
5739
- const brand = MAILBOXES.has(parseDomain3(domain).domainWithoutSuffix) ? null : DISPLAY_BRANDS.find((entry) => isOfficial(entry, domain, "", false) && !isOfficial(entry, session.originalFromAddressDomain, ""));
6939
+ const brand = MAILBOXES.has(parseDomain4(domain).domainWithoutSuffix) ? null : DISPLAY_BRANDS.find((entry) => isOfficial(entry, domain, "", false) && !isOfficial(entry, session.originalFromAddressDomain, ""));
5740
6940
  if (brand) {
5741
6941
  hit("FROM_NAME_BRAND", 2, `Display name is an address of "${brand.brand}" but the message is from ${fromRoot}`, "PHISHING");
5742
6942
  }
@@ -5771,7 +6971,7 @@ ${withoutHelp(markup)}`) || isInjection(readable) || attributeText(markup).some(
5771
6971
  }
5772
6972
 
5773
6973
  // src/llm.js
5774
- import { Buffer as Buffer7 } from "node:buffer";
6974
+ import { Buffer as Buffer8 } from "node:buffer";
5775
6975
  import { createHash as createHash2, randomBytes } from "node:crypto";
5776
6976
  import os from "node:os";
5777
6977
  import process from "node:process";
@@ -6132,7 +7332,7 @@ function authHeaders(config) {
6132
7332
  case "basic": {
6133
7333
  const user = config.username ?? "";
6134
7334
  const pass = config.password ?? config.apiKey ?? "";
6135
- headers.authorization = `Basic ${Buffer7.from(`${user}:${pass}`).toString("base64")}`;
7335
+ headers.authorization = `Basic ${Buffer8.from(`${user}:${pass}`).toString("base64")}`;
6136
7336
  break;
6137
7337
  }
6138
7338
  case "x-api-key": {
@@ -7078,7 +8278,7 @@ function scoreResults(results, options) {
7078
8278
  var package_default = {
7079
8279
  name: "spamscanner",
7080
8280
  description: "Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",
7081
- version: "7.0.10",
8281
+ version: "7.0.12",
7082
8282
  author: "Forward Email (https://forwardemail.net)",
7083
8283
  bin: {
7084
8284
  spamscanner: "./dist/esm/cli.js"
@@ -7225,7 +8425,7 @@ var package_default = {
7225
8425
  var VERSION = package_default.version;
7226
8426
 
7227
8427
  // src/headers.js
7228
- import { Buffer as Buffer8 } from "node:buffer";
8428
+ import { Buffer as Buffer9 } from "node:buffer";
7229
8429
  function foldHeader(name, value, newline = "\r\n") {
7230
8430
  const pieces = String(value).split(/(?<=[ ,])/);
7231
8431
  const lines2 = [];
@@ -7260,10 +8460,10 @@ function splitMessage(raw) {
7260
8460
  const match = /^\r?\n|\r?\n\r?\n/.exec(text);
7261
8461
  if (!match) {
7262
8462
  const newline2 = text.includes("\n") && !text.includes("\r\n") ? "\n" : "\r\n";
7263
- return { header: text.replace(/\r?\n$/, ""), body: Buffer8.alloc(0), newline: newline2 };
8463
+ return { header: text.replace(/\r?\n$/, ""), body: Buffer9.alloc(0), newline: newline2 };
7264
8464
  }
7265
8465
  const newline = match[0].startsWith("\r") ? "\r\n" : "\n";
7266
- return { header: text.slice(0, match.index), body: raw.subarray(Buffer8.byteLength(text.slice(0, match.index + match[0].length), "latin1")), newline };
8466
+ return { header: text.slice(0, match.index), body: raw.subarray(Buffer9.byteLength(text.slice(0, match.index + match[0].length), "latin1")), newline };
7267
8467
  }
7268
8468
  var ENCODED_BYTES = 39;
7269
8469
  var PLAIN_WORD = 64;
@@ -7279,14 +8479,14 @@ function subjectTagText(tag, spaceAfter = false) {
7279
8479
  const words2 = [];
7280
8480
  let chunk2 = "";
7281
8481
  for (const character of spaceAfter ? `${line} ` : line) {
7282
- if (Buffer8.byteLength(chunk2 + character, "utf8") > ENCODED_BYTES) {
8482
+ if (Buffer9.byteLength(chunk2 + character, "utf8") > ENCODED_BYTES) {
7283
8483
  words2.push(chunk2);
7284
8484
  chunk2 = "";
7285
8485
  }
7286
8486
  chunk2 += character;
7287
8487
  }
7288
8488
  words2.push(chunk2);
7289
- return words2.map((word) => `=?UTF-8?B?${Buffer8.from(word, "utf8").toString("base64")}?=`).join(" ");
8489
+ return words2.map((word) => `=?UTF-8?B?${Buffer9.from(word, "utf8").toString("base64")}?=`).join(" ");
7290
8490
  }
7291
8491
  function taggedSubject(tag, current, newline = "\r\n") {
7292
8492
  const unfolded = current.replaceAll(/\r?\n/g, "");
@@ -7317,7 +8517,7 @@ function taggedSubject(tag, current, newline = "\r\n") {
7317
8517
  return lines2.join(newline).slice("Subject: ".length);
7318
8518
  }
7319
8519
  function rewriteMessage(source, headers = [], options = {}) {
7320
- const raw = Buffer8.isBuffer(source) ? source : Buffer8.from(String(source), "utf8");
8520
+ const raw = Buffer9.isBuffer(source) ? source : Buffer9.from(String(source), "utf8");
7321
8521
  const { header, body, newline } = splitMessage(raw);
7322
8522
  const remove = options.remove ?? /^x-spam-/i;
7323
8523
  const lines2 = header === "" ? [] : header.split(/\r?\n/);
@@ -7342,10 +8542,13 @@ function rewriteMessage(source, headers = [], options = {}) {
7342
8542
  }
7343
8543
  }
7344
8544
  }
7345
- const added = headers.map(([name, value]) => foldHeader(name, Buffer8.from(String(value), "utf8").toString("latin1"), newline));
7346
- const mbox = kept.length > 0 && kept[0][0].startsWith("From ") ? kept.shift() : [];
8545
+ const added = headers.map(([name, value]) => foldHeader(name, Buffer9.from(String(value), "utf8").toString("latin1"), newline));
8546
+ const mbox = kept.length > 0 && kept[0][0].startsWith("From ") ? kept.shift().slice(0, 1) : [];
8547
+ if (kept.length > 0 && /^[ \t]/.test(kept[0][0])) {
8548
+ kept.shift();
8549
+ }
7347
8550
  const head = [...mbox, ...added, ...kept.map((field) => field.join(newline))].join(newline);
7348
- return Buffer8.concat([Buffer8.from(head === "" && header === "" ? newline : `${head}${newline}${newline}`, "latin1"), body]);
8551
+ return Buffer9.concat([Buffer9.from(head === "" ? newline : `${head}${newline}${newline}`, "latin1"), body]);
7349
8552
  }
7350
8553
 
7351
8554
  // src/train.js
@@ -7353,7 +8556,7 @@ import { createHash as createHash3 } from "node:crypto";
7353
8556
  import path3 from "node:path";
7354
8557
 
7355
8558
  // src/sources.js
7356
- import { Buffer as Buffer9 } from "node:buffer";
8559
+ import { Buffer as Buffer10 } from "node:buffer";
7357
8560
  import { createReadStream } from "node:fs";
7358
8561
  import fs from "node:fs/promises";
7359
8562
  import path2 from "node:path";
@@ -7533,7 +8736,7 @@ async function* readJsonl(file, options = {}) {
7533
8736
  }
7534
8737
  async function* walk(directory) {
7535
8738
  const entries = await fs.readdir(directory, { withFileTypes: true });
7536
- entries.sort((a, b) => Buffer9.compare(Buffer9.from(a.name), Buffer9.from(b.name)));
8739
+ entries.sort((a, b) => Buffer10.compare(Buffer10.from(a.name), Buffer10.from(b.name)));
7537
8740
  for (const entry of entries) {
7538
8741
  if (SKIP.has(entry.name) || entry.name.startsWith(".")) {
7539
8742
  continue;
@@ -7558,7 +8761,7 @@ async function* readMessages(source) {
7558
8761
  const handle = await fs.open(file);
7559
8762
  let head;
7560
8763
  try {
7561
- const { buffer, bytesRead } = await handle.read(Buffer9.alloc(64), 0, 64, 0);
8764
+ const { buffer, bytesRead } = await handle.read(Buffer10.alloc(64), 0, 64, 0);
7562
8765
  head = buffer.subarray(0, bytesRead).toString("latin1");
7563
8766
  } finally {
7564
8767
  await handle.close();
@@ -7682,19 +8885,24 @@ async function evaluate(classifier, examples) {
7682
8885
  }
7683
8886
 
7684
8887
  // src/milter.js
7685
- import { Buffer as Buffer11 } from "node:buffer";
8888
+ import { Buffer as Buffer12 } from "node:buffer";
7686
8889
  import { EventEmitter } from "node:events";
7687
8890
  import net3 from "node:net";
7688
8891
 
7689
8892
  // src/server.js
7690
- import { Buffer as Buffer10 } from "node:buffer";
8893
+ import { Buffer as Buffer11 } from "node:buffer";
7691
8894
  import { timingSafeEqual } from "node:crypto";
7692
8895
  import http2 from "node:http";
7693
8896
  import net2 from "node:net";
7694
8897
  var MAX_MESSAGE_SIZE = 25 * 1024 * 1024;
7695
8898
  var MAX_CONNECTIONS = 200;
7696
8899
  function serializeResult(result, options = {}) {
7697
- const { mail, tokens, ...rest } = result;
8900
+ const {
8901
+ mail,
8902
+ tokens,
8903
+ attached,
8904
+ ...rest
8905
+ } = result;
7698
8906
  const output = {
7699
8907
  ...rest,
7700
8908
  results: { ...result.results }
@@ -7727,8 +8935,8 @@ function sessionFromQuery(query) {
7727
8935
  return session;
7728
8936
  }
7729
8937
  function sameToken(given, expected) {
7730
- const a = Buffer10.from(String(given));
7731
- const b = Buffer10.from(String(expected));
8938
+ const a = Buffer11.from(String(given));
8939
+ const b = Buffer11.from(String(expected));
7732
8940
  return a.length === b.length && timingSafeEqual(a, b);
7733
8941
  }
7734
8942
  function readBody(request, limit) {
@@ -7748,7 +8956,7 @@ function readBody(request, limit) {
7748
8956
  reject(error);
7749
8957
  return;
7750
8958
  }
7751
- resolve(Buffer10.concat(chunks));
8959
+ resolve(Buffer11.concat(chunks));
7752
8960
  });
7753
8961
  request.on("error", reject);
7754
8962
  });
@@ -7763,7 +8971,7 @@ function createHttpServer(scanner, options = {}) {
7763
8971
  let active = 0;
7764
8972
  const server = http2.createServer(async (request, response) => {
7765
8973
  const send = (status, body, headers = {}) => {
7766
- const isBuffer = Buffer10.isBuffer(body);
8974
+ const isBuffer = Buffer11.isBuffer(body);
7767
8975
  response.writeHead(status, { "content-type": isBuffer ? "message/rfc822" : "application/json; charset=utf-8", "cache-control": "no-store", ...headers });
7768
8976
  response.end(isBuffer ? body : `${JSON.stringify(body)}
7769
8977
  `);
@@ -7843,7 +9051,7 @@ function createTcpServer(scanner, options = {}) {
7843
9051
  const onEnd = async () => {
7844
9052
  socket.setTimeout(0);
7845
9053
  try {
7846
- const result = await scanner.scan(Buffer10.concat(chunks));
9054
+ const result = await scanner.scan(Buffer11.concat(chunks));
7847
9055
  socket.end(json ? `${JSON.stringify(serializeResult(result))}
7848
9056
  ` : `${result.isSpam ? "SPAM" : "HAM"} ${result.score.toFixed(1)}/${result.threshold.toFixed(1)} ${result.tests.map((test) => test.name).join(",")}
7849
9057
  `);
@@ -7937,12 +9145,12 @@ var PROTOCOL = {
7937
9145
  HDR_LEADSPC: 1048576
7938
9146
  };
7939
9147
  var MAX_PACKET = 1024 * 1024;
7940
- function packet(command, data = Buffer11.alloc(0)) {
7941
- const payload = Buffer11.isBuffer(data) ? data : Buffer11.from(data, "latin1");
7942
- const header = Buffer11.alloc(5);
9148
+ function packet(command, data = Buffer12.alloc(0)) {
9149
+ const payload = Buffer12.isBuffer(data) ? data : Buffer12.from(data, "latin1");
9150
+ const header = Buffer12.alloc(5);
7943
9151
  header.writeUInt32BE(payload.length + 1);
7944
9152
  header.write(command, 4, "latin1");
7945
- return Buffer11.concat([header, payload]);
9153
+ return Buffer12.concat([header, payload]);
7946
9154
  }
7947
9155
  function cstrings(data) {
7948
9156
  const parts = data.toString("latin1").split("\0");
@@ -7952,7 +9160,7 @@ function cstrings(data) {
7952
9160
  return parts;
7953
9161
  }
7954
9162
  function cstring(value) {
7955
- return Buffer11.from(`${value}\0`, "latin1");
9163
+ return Buffer12.from(`${value}\0`, "latin1");
7956
9164
  }
7957
9165
  function address(value) {
7958
9166
  const text = String(value ?? "").trim();
@@ -8009,7 +9217,7 @@ var MilterSession = class {
8009
9217
  if (this.buffered < this.needed) {
8010
9218
  return;
8011
9219
  }
8012
- let buffer = this.chunks.length === 1 ? this.chunks[0] : Buffer11.concat(this.chunks);
9220
+ let buffer = this.chunks.length === 1 ? this.chunks[0] : Buffer12.concat(this.chunks);
8013
9221
  this.needed = 4;
8014
9222
  while (buffer.length >= 4) {
8015
9223
  const length = buffer.readUInt32BE(0);
@@ -8054,7 +9262,7 @@ var MilterSession = class {
8054
9262
  const protocol = data.readUInt32BE(8);
8055
9263
  const wanted = ACTIONS.ADDHDRS | ACTIONS.CHGHDRS | ACTIONS.QUARANTINE;
8056
9264
  this.leadingSpace = Boolean(protocol & PROTOCOL.HDR_LEADSPC);
8057
- const reply2 = Buffer11.alloc(12);
9265
+ const reply2 = Buffer12.alloc(12);
8058
9266
  reply2.writeUInt32BE(Math.min(version, 6), 0);
8059
9267
  reply2.writeUInt32BE(actions & wanted, 4);
8060
9268
  reply2.writeUInt32BE(protocol & (PROTOCOL.HDR_LEADSPC | PROTOCOL.NOUNKNOWN), 8);
@@ -8119,7 +9327,7 @@ var MilterSession = class {
8119
9327
  if (this.truncated || this.size + data.length > options.maxSize) {
8120
9328
  this.truncated = true;
8121
9329
  } else {
8122
- this.body.push(Buffer11.from(data));
9330
+ this.body.push(Buffer12.from(data));
8123
9331
  this.size += data.length;
8124
9332
  }
8125
9333
  this.send(RESPONSES.CONTINUE);
@@ -8154,7 +9362,7 @@ var MilterSession = class {
8154
9362
  /** @returns {Buffer} */
8155
9363
  rawMessage() {
8156
9364
  const head = this.headers.map(([name, value]) => `${name}:${this.leadingSpace || value.startsWith(" ") || value.startsWith(" ") ? "" : " "}${value}`).join("\r\n");
8157
- return Buffer11.concat([Buffer11.from(this.headers.length > 0 ? `${head}\r
9365
+ return Buffer12.concat([Buffer12.from(this.headers.length > 0 ? `${head}\r
8158
9366
  \r
8159
9367
  ` : "\r\n", "latin1"), ...this.body]);
8160
9368
  }
@@ -8165,9 +9373,9 @@ var MilterSession = class {
8165
9373
  const key = name.toLowerCase();
8166
9374
  counts.set(key, (counts.get(key) || 0) + 1);
8167
9375
  if (key.startsWith("x-spam-")) {
8168
- const index = Buffer11.alloc(4);
9376
+ const index = Buffer12.alloc(4);
8169
9377
  index.writeUInt32BE(counts.get(key));
8170
- this.send(RESPONSES.CHGHEADER, Buffer11.concat([index, cstring(name), cstring("")]));
9378
+ this.send(RESPONSES.CHGHEADER, Buffer12.concat([index, cstring(name), cstring("")]));
8171
9379
  }
8172
9380
  }
8173
9381
  }
@@ -8190,7 +9398,7 @@ var MilterSession = class {
8190
9398
  }
8191
9399
  this.server.report(error);
8192
9400
  this.removeForgedHeaders();
8193
- this.send(RESPONSES.ADDHEADER, Buffer11.concat([cstring("X-Spam-Virus-Scan"), cstring(`${this.leadingSpace ? " " : ""}failed`)]));
9401
+ this.send(RESPONSES.ADDHEADER, Buffer12.concat([cstring("X-Spam-Virus-Scan"), cstring(`${this.leadingSpace ? " " : ""}failed`)]));
8194
9402
  this.send(RESPONSES.CONTINUE);
8195
9403
  this.resetMessage();
8196
9404
  return;
@@ -8211,15 +9419,15 @@ var MilterSession = class {
8211
9419
  headers.unshift(["Authentication-Results", formatAuthResultsHeader(result.results.authentication, options.hostname)]);
8212
9420
  }
8213
9421
  for (const [name, value] of headers) {
8214
- this.send(RESPONSES.ADDHEADER, Buffer11.concat([cstring(name), cstring(`${this.leadingSpace ? " " : ""}${value}`.replaceAll("\r\n", "\n"))]));
9422
+ this.send(RESPONSES.ADDHEADER, Buffer12.concat([cstring(name), cstring(`${this.leadingSpace ? " " : ""}${value}`.replaceAll("\r\n", "\n"))]));
8215
9423
  }
8216
9424
  if (result.isSpam && options.subjectTag) {
8217
9425
  const subject = this.headers.find(([name]) => name.toLowerCase() === "subject");
8218
9426
  const value = taggedSubject(options.subjectTag, subject ? subject[1].trim() : "", "\n");
8219
9427
  if (value !== null) {
8220
- const index = Buffer11.alloc(4);
9428
+ const index = Buffer12.alloc(4);
8221
9429
  index.writeUInt32BE(1);
8222
- this.send(RESPONSES.CHGHEADER, Buffer11.concat([index, cstring("Subject"), cstring(`${this.leadingSpace ? " " : ""}${value}`.replaceAll("\r\n", "\n"))]));
9430
+ this.send(RESPONSES.CHGHEADER, Buffer12.concat([index, cstring("Subject"), cstring(`${this.leadingSpace ? " " : ""}${value}`.replaceAll("\r\n", "\n"))]));
8223
9431
  }
8224
9432
  }
8225
9433
  if (result.action === "reject" && options.reject) {
@@ -8322,7 +9530,7 @@ var MilterServer = class extends EventEmitter {
8322
9530
  };
8323
9531
 
8324
9532
  // src/spamd.js
8325
- import { Buffer as Buffer12 } from "node:buffer";
9533
+ import { Buffer as Buffer13 } from "node:buffer";
8326
9534
  import net4 from "node:net";
8327
9535
  var EX_OK = 0;
8328
9536
  var EX_USAGE = 64;
@@ -8353,8 +9561,9 @@ function reply(code, text, headers = [], body = null) {
8353
9561
  lines2.push(`Content-length: ${body.length}`);
8354
9562
  }
8355
9563
  lines2.push(...headers.map(([name, value]) => `${name}: ${value}`), "", "");
8356
- return body === null ? Buffer12.from(lines2.join("\r\n")) : Buffer12.concat([Buffer12.from(lines2.join("\r\n")), body]);
9564
+ return body === null ? Buffer13.from(lines2.join("\r\n")) : Buffer13.concat([Buffer13.from(lines2.join("\r\n")), body]);
8357
9565
  }
9566
+ var oneLine2 = (text) => String(text ?? "").replaceAll(/[\p{Cc}\u061C\u200E\u200F\u2028\u2029\u202A-\u202E\u2066-\u2069]+/gu, " ");
8358
9567
  function report(result) {
8359
9568
  const lines2 = [
8360
9569
  `Spam detection software, running on the system "spamscanner", has ${result.isSpam ? "identified this incoming email as possible spam" : "scanned this incoming email"}.`,
@@ -8363,7 +9572,7 @@ function report(result) {
8363
9572
  "",
8364
9573
  " pts rule name description",
8365
9574
  "---- ---------------------- --------------------------------------------------",
8366
- ...result.tests.map((test) => `${test.score.toFixed(1).padStart(4)} ${test.name.padEnd(22)} ${test.description}`)
9575
+ ...result.tests.map((test) => `${test.score.toFixed(1).padStart(4)} ${oneLine2(test.name).padEnd(22)} ${oneLine2(test.description)}`)
8367
9576
  ];
8368
9577
  return `${lines2.join("\r\n")}\r
8369
9578
  `;
@@ -8398,11 +9607,11 @@ async function answer(scanner, request, message, options) {
8398
9607
  return reply(EX_OK, "EX_OK", [verdict]);
8399
9608
  }
8400
9609
  case "SYMBOLS": {
8401
- return reply(EX_OK, "EX_OK", [verdict], Buffer12.from(result.tests.map((test) => test.name).join(",")));
9610
+ return reply(EX_OK, "EX_OK", [verdict], Buffer13.from(result.tests.map((test) => test.name).join(",")));
8402
9611
  }
8403
9612
  case "REPORT":
8404
9613
  case "REPORT_IFSPAM": {
8405
- return reply(EX_OK, "EX_OK", [verdict], command === "REPORT" || result.isSpam ? Buffer12.from(report(result)) : Buffer12.alloc(0));
9614
+ return reply(EX_OK, "EX_OK", [verdict], command === "REPORT" || result.isSpam ? Buffer13.from(report(result)) : Buffer13.alloc(0));
8406
9615
  }
8407
9616
  default: {
8408
9617
  const rewritten = rewriteMessage(message, spamHeaders(result, { version: VERSION }), { subjectTag: result.isSpam ? options.subjectTag : null });
@@ -8410,7 +9619,7 @@ async function answer(scanner, request, message, options) {
8410
9619
  return reply(EX_OK, "EX_OK", [verdict], rewritten);
8411
9620
  }
8412
9621
  const { header, newline } = splitMessage(rewritten);
8413
- return reply(EX_OK, "EX_OK", [verdict], Buffer12.from(`${header}${newline}${newline}`, "latin1"));
9622
+ return reply(EX_OK, "EX_OK", [verdict], Buffer13.from(`${header}${newline}${newline}`, "latin1"));
8414
9623
  }
8415
9624
  }
8416
9625
  }
@@ -8425,7 +9634,7 @@ function createSpamdServer(scanner, options = {}) {
8425
9634
  ...options
8426
9635
  };
8427
9636
  const server = net4.createServer({ allowHalfOpen: true }, (socket) => {
8428
- let head = Buffer12.alloc(0);
9637
+ let head = Buffer13.alloc(0);
8429
9638
  const chunks = [];
8430
9639
  let size = 0;
8431
9640
  let request = null;
@@ -8451,7 +9660,7 @@ function createSpamdServer(scanner, options = {}) {
8451
9660
  return;
8452
9661
  }
8453
9662
  if (!request) {
8454
- head = Buffer12.concat([head, chunk2]);
9663
+ head = Buffer13.concat([head, chunk2]);
8455
9664
  const end = head.indexOf("\r\n\r\n");
8456
9665
  if (end === -1) {
8457
9666
  if (head.length > 64 * 1024) {
@@ -8486,7 +9695,7 @@ function createSpamdServer(scanner, options = {}) {
8486
9695
  chunks.push(chunk2);
8487
9696
  size += chunk2.length;
8488
9697
  if (request.length === null ? ["PING", "SKIP"].includes(request.command) : size >= request.length) {
8489
- finish(Buffer12.concat(chunks).subarray(0, request.length ?? 0));
9698
+ finish(Buffer13.concat(chunks).subarray(0, request.length ?? 0));
8490
9699
  } else if (size > settings.maxSize) {
8491
9700
  fail(EX_DATAERR, `Message larger than ${settings.maxSize} bytes`);
8492
9701
  }
@@ -8498,7 +9707,7 @@ function createSpamdServer(scanner, options = {}) {
8498
9707
  if (!request) {
8499
9708
  fail(EX_PROTOCOL, "Incomplete request");
8500
9709
  } else if (request.length === null) {
8501
- finish(Buffer12.concat(chunks));
9710
+ finish(Buffer13.concat(chunks));
8502
9711
  } else {
8503
9712
  fail(EX_PROTOCOL, `Message shorter than its Content-length of ${request.length} bytes`);
8504
9713
  }
@@ -8672,21 +9881,21 @@ async function withTimeout(promise, ms, fallback) {
8672
9881
  }
8673
9882
  }
8674
9883
  async function toBuffer(source) {
8675
- if (Buffer13.isBuffer(source)) {
9884
+ if (Buffer14.isBuffer(source)) {
8676
9885
  return source;
8677
9886
  }
8678
9887
  if (typeof source === "string") {
8679
- return Buffer13.from(source, "utf8");
9888
+ return Buffer14.from(source, "utf8");
8680
9889
  }
8681
9890
  if (source instanceof Uint8Array) {
8682
- return Buffer13.from(source);
9891
+ return Buffer14.from(source);
8683
9892
  }
8684
9893
  if (source && typeof source[Symbol.asyncIterator] === "function") {
8685
9894
  const chunks = [];
8686
9895
  for await (const chunk2 of source) {
8687
- chunks.push(Buffer13.isBuffer(chunk2) ? chunk2 : Buffer13.from(chunk2));
9896
+ chunks.push(Buffer14.isBuffer(chunk2) ? chunk2 : Buffer14.from(chunk2));
8688
9897
  }
8689
- return Buffer13.concat(chunks);
9898
+ return Buffer14.concat(chunks);
8690
9899
  }
8691
9900
  throw new TypeError("A message must be a Buffer, a string, a Uint8Array or a readable stream");
8692
9901
  }
@@ -8708,6 +9917,19 @@ var DEFAULTS2 = {
8708
9917
  cloudflare: true,
8709
9918
  adult: true,
8710
9919
  maxHosts: 25,
9920
+ // Also read the links of the whole message and of what it carries
9921
+ // (attached messages, HTML, SVG and XML files, text files, invitations
9922
+ // and contact cards, documents in iframes and data: URLs) when link
9923
+ // hosts are looked up, for at most five seconds: off by default, as it
9924
+ // reads all of it, up to 50 MB of attachments.
9925
+ attachments: false,
9926
+ // Milliseconds the link lookups may take in all (default: timeout).
9927
+ timeout: void 0,
9928
+ // Also look up host names written in the text without a link or a path
9929
+ // ("evil.example"), which mail clients make links of, as far as there is
9930
+ // room: off by default, as a quoted reply or a signature may name any
9931
+ // site (they are checked for lookalikes either way).
9932
+ hostsInText: false,
8711
9933
  // Characters of text read for links (twice as many of HTML): more than
8712
9934
  // for the classifier (maxLength), as a link anywhere in a message may be
8713
9935
  // followed; a longer message sets results.linksTruncated.
@@ -8820,7 +10042,7 @@ function fromDomain(mail) {
8820
10042
  function withHeader(raw) {
8821
10043
  const first = raw.subarray(0, 1e3).toString("latin1");
8822
10044
  const headerless = raw.length > 0 && !/^(?:[!-9;-~]+[ \t]*:|From |[\r\n])/.test(first);
8823
- return headerless ? Buffer13.concat([Buffer13.from("\r\n"), raw]) : raw;
10045
+ return headerless ? Buffer14.concat([Buffer14.from("\r\n"), raw]) : raw;
8824
10046
  }
8825
10047
  var SpamScanner = class {
8826
10048
  /**
@@ -8982,7 +10204,10 @@ var SpamScanner = class {
8982
10204
  async scanFile(file, options) {
8983
10205
  return this.scan(await readFile(file), options);
8984
10206
  }
8985
- async checkLinks(links, config, hosts = [], from = "") {
10207
+ // (`from`: the sender's domain; `carried`: the link hosts of the whole
10208
+ // message and of what it carries, see carriedLinkHosts)
10209
+ async checkLinks(links, config, hosts = [], options = {}) {
10210
+ const { from = "", carried = null } = typeof options === "string" ? { from: options } : options ?? {};
8986
10211
  const phishing = [];
8987
10212
  const homographs = [];
8988
10213
  const seenHosts = /* @__PURE__ */ new Set();
@@ -9038,18 +10263,23 @@ var SpamScanner = class {
9038
10263
  for (const host of hosts) {
9039
10264
  lookalike(host, { source: "text" });
9040
10265
  }
9041
- const ordered = [...new Set([...found.filter((link) => link.text), ...found.filter((link) => !link.text), ...bare].map((link) => lookupName(destination(link))).filter(Boolean))];
10266
+ const carriedHosts = (carried?.hosts ?? []).map((host) => lookupName(host)).filter(Boolean);
10267
+ for (const host of carriedHosts) {
10268
+ seenHosts.add(host);
10269
+ }
10270
+ const own = [...found.filter((link) => link.text), ...found.filter((link) => !link.text), ...bare].map((link) => lookupName(destination(link))).filter(Boolean);
9042
10271
  const firsts = /* @__PURE__ */ new Map();
9043
- for (const host of ordered) {
10272
+ for (const host of [...own, ...carriedHosts]) {
9044
10273
  const domain = registrableDomain(host);
9045
10274
  if (!firsts.has(domain)) {
9046
10275
  firsts.set(domain, host);
9047
10276
  }
9048
10277
  }
9049
- const checked = [.../* @__PURE__ */ new Set([...firsts.values(), ...ordered])].slice(0, config.phishing.maxHosts);
10278
+ const checked = [.../* @__PURE__ */ new Set([...firsts.values(), ...own, ...carriedHosts])].slice(0, config.phishing.maxHosts);
9050
10279
  const domains = [...new Set(checked.map((host) => registrableDomain(host)))];
9051
10280
  const blocked = [];
9052
10281
  let answered = 0;
10282
+ const linkTimeout = Number(config.phishing.timeout) > 0 ? Number(config.phishing.timeout) : config.timeout;
9053
10283
  const cloudflare = async () => {
9054
10284
  await withTimeout(Promise.all(checked.map(async (host) => {
9055
10285
  const found2 = await this.dns.checkCloudflare([host], { adult: config.phishing.adult });
@@ -9057,12 +10287,12 @@ var SpamScanner = class {
9057
10287
  if (!found2.unanswered?.length) {
9058
10288
  answered++;
9059
10289
  }
9060
- })), config.timeout, null);
10290
+ })), linkTimeout, null);
9061
10291
  return { found: [...blocked], unanswered: checked.length - answered };
9062
10292
  };
9063
10293
  const [cloudflareResult, listed] = await Promise.all([
9064
10294
  config.phishing.cloudflare && checked.length > 0 ? cloudflare() : { found: [], unanswered: 0 },
9065
- config.dnsbl.domain?.length > 0 && domains.length > 0 ? withTimeout(this.dns.checkDomains(domains, config.dnsbl.domain), config.timeout, []) : []
10295
+ config.dnsbl.domain?.length > 0 && domains.length > 0 ? withTimeout(this.dns.checkDomains(domains, config.dnsbl.domain), linkTimeout, []) : []
9066
10296
  ]);
9067
10297
  for (const { host, category } of cloudflareResult.found) {
9068
10298
  phishing.push(finding(category === "malware" ? { type: "malicious_domain", domain: host, message: `Link hostname ${host} is blocked by Cloudflare's malware filtering as phishing or malware` } : { type: "adult_domain", domain: host, message: `Link hostname ${host} was detected by Cloudflare's family filtering to contain adult-related content` }));
@@ -9078,7 +10308,8 @@ var SpamScanner = class {
9078
10308
  return {
9079
10309
  phishing,
9080
10310
  homographs,
9081
- uncheckedHosts: seenHosts.size - checked.length,
10311
+ // (at least one when the message has more hosts than were read)
10312
+ uncheckedHosts: Math.max(seenHosts.size - checked.length, carried?.more ? 1 : 0),
9082
10313
  timedOutHosts: cloudflareResult.unanswered
9083
10314
  };
9084
10315
  }
@@ -9250,10 +10481,17 @@ ${visibleText(html.slice(0, config.maxLength * 2)).text}`, { maxLength: config.m
9250
10481
  styled: plainObfuscation.styled || shownObfuscation.styled
9251
10482
  };
9252
10483
  const unopened = [];
9253
- const attached = await expandAttachments(mail.attachments, parseErrors, void 0, unopened);
10484
+ const opened = [];
10485
+ const attached = await expandAttachments(mail.attachments, parseErrors, void 0, { unopened, mails: opened });
10486
+ const carried = config.phishing.attachments === true && (config.phishing.cloudflare || config.dnsbl.domain?.length > 0) ? carriedLinkHosts(mail, {
10487
+ mails: opened,
10488
+ attachments: attached,
10489
+ maxHosts: config.phishing.maxHosts,
10490
+ hostsInText: config.phishing.hostsInText === true
10491
+ }) : null;
9254
10492
  const attachments = config.attachments === false ? [] : inspectAttachments(attached).filter((item) => config.macros !== false || !["macro", "pdf_active", "rtf_object"].includes(item.type)).map((item) => finding(item));
9255
10493
  const [links, authentication, checkedReputation, dnsbl, virusScan, toxicity, nsfw] = await Promise.all([
9256
- this.checkLinks(checkedLinks, config, extracted.hosts, fromDomain(mail)),
10494
+ this.checkLinks(checkedLinks, config, extracted.hosts, { from: fromDomain(mail), carried }),
9257
10495
  config.authenticationResults && typeof config.authenticationResults === "object" ? normalizeAuthOutput(config.authenticationResults) : config.authentication && session.remoteAddress ? withTimeout(authenticate(raw, {
9258
10496
  timeout: config.timeout,
9259
10497
  ...typeof config.authentication === "object" ? config.authentication : {},
@@ -9381,6 +10619,14 @@ ${visibleText(html.slice(0, config.maxLength * 2)).text}`, { maxLength: config.m
9381
10619
  // Whether the message has more text than phishing.maxLength, whose
9382
10620
  // links were not read.
9383
10621
  linksTruncated,
10622
+ // Whether a link host of the message or of what it carries was
10623
+ // never read whole (it runs past where long text is cut to be
10624
+ // read, it goes through more security gateways than are
10625
+ // unwrapped, or it is in more documents than are read), and whether
10626
+ // an attachment was left unread for time: in either case not every
10627
+ // link was checked (see phishing.attachments).
10628
+ linksTooComplex: Boolean(carried?.tooComplex),
10629
+ linksIncomplete: Boolean(carried?.incomplete),
9384
10630
  idnHomographAttack: {
9385
10631
  detected: links.homographs.length > 0,
9386
10632
  domains: links.homographs,
@@ -9392,6 +10638,9 @@ ${visibleText(html.slice(0, config.maxLength * 2)).text}`, { maxLength: config.m
9392
10638
  language: extracted.language,
9393
10639
  tokens: extracted.words,
9394
10640
  mail,
10641
+ // (the attachments of the message and of its attached messages, as
10642
+ // read, an attached message's named after it)
10643
+ attached,
9395
10644
  version: VERSION,
9396
10645
  metrics: { totalTime: time }
9397
10646
  };