PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i5h-3LL

HOME


5h-3LL 1.0
DIR: /srv/http/vyvoj.adent.cz/egroupware/pfi/felamimail/inc
/srv/http/vyvoj.adent.cz/egroupware/pfi/felamimail/inc/
Upload File:
Current File : /srv/http/vyvoj.adent.cz/egroupware/pfi/felamimail/inc/class.htmlfilter.inc.php
<?php
/**
 * htmlfilter.inc
 * ---------------
 * This set of functions allows you to filter html in order to remove
 * any malicious tags from it. Useful in cases when you need to filter
 * user input for any cross-site-scripting attempts.
 *
 * Copyright (c) 2002 by Duke University
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
 * 02111-1307, USA.
 *
 * @Author  Konstantin Riabitsev <icon@linux.duke.edu>
 * @Version 1.0.5 (Oct-16-2002)
 */

class htmlfilter
{

/**
 * See http://www.mricon.com/html/phpfilter.html
 *
 * This is a debugging function used throughout the code. To enable
 * debugging you have to specify a global variable called "debug" before
 * calling sanitize() and set it to true. 
 *
 * Note: Although insignificantly, debugging does slow you down even
 * when $debug is set to false. If you wish to get rid of all
 * debugging calls, run the following command:
 *
 * fgrep -v 'spew("' htmlfilter.inc > htmlfilter.inc.new
 *
 * htmlfilter.inc.new will contain no debugging calls.
 *
 * @param  $message  A string with the message to output.
 * @return           void.
 */
function spew($message){
	global $debug;
	#$debug = true;
	if ($debug == true){
		echo "$message<br>";
	}
}

/**
 * This function returns the final tag out of the tag name, an array
 * of attributes, and the type of the tag. This function is called by 
 * sanitize internally.
 *
 * @param  $tagname  the name of the tag.
 * @param  $attary   the array of attributes and their values
 * @param  $tagtype  The type of the tag (see in comments).
 * @return           a string with the final tag representation.
 */
function tagprint($tagname, $attary, $tagtype){
	$me = 'tagprint';
	if ($tagtype == 2){
		$fulltag = '</' . $tagname . '>';
	} else {
		$fulltag = '<' . $tagname;
		if (is_array($attary) && sizeof($attary)){
			$atts = Array();
			while (list($attname, $attvalue) = each($attary)){
				array_push($atts, "$attname=$attvalue");
			}
			$fulltag .= ' ' . join(' ', $atts);
		}
		if ($tagtype == 3){
			$fulltag .= ' /';
		}
		$fulltag .= '>';
	}
	$this->spew("$me: $fulltag\n");
	return $fulltag;
}

/**
 * A small helper function to use with array_walk. Modifies a by-ref
 * value and makes it lowercase.
 *
 * @param  $val a value passed by-ref.
 * @return      void since it modifies a by-ref value.
 */
function casenormalize(&$val){
	$val = strtolower($val);
}

/**
 * This function skips any whitespace from the current position within
 * a string and to the next non-whitespace value.
 * 
 * @param  $body   the string
 * @param  $offset the offset within the string where we should start
 *                 looking for the next non-whitespace character.
 * @return         the location within the $body where the next
 *                 non-whitespace char is located.
 */
function skipspace($body, $offset){
	$me = 'skipspace';
	preg_match('/^(\s*)/s', substr($body, $offset), $matches);
	if (sizeof($matches{1})){
		$count = strlen($matches{1});
		$this->spew("$me: skipped $count chars\n");
		$offset += $count;
	}
	return $offset;
}

/**
 * This function looks for the next character within a string.  It's
 * really just a glorified "strpos", except it catches the failures
 * nicely.
 *
 * @param  $body   The string to look for needle in.
 * @param  $offset Start looking from this position.
 * @param  $needle The character/string to look for.
 * @return         location of the next occurance of the needle, or
 *                 strlen($body) if needle wasn't found.
 */
function findnxstr($body, $offset, $needle){
	$me = 'findnxstr';
	$pos = strpos($body, $needle, $offset);
	if ($pos === FALSE){
		$pos = strlen($body);
		$this->spew("$me: end of body reached\n");
	}
	$this->spew("$me: '$needle' found at pos $pos\n");
	return $pos;
}

/**
 * This function takes a PCRE-style regexp and tries to match it
 * within the string.
 *
 * @param  $body   The string to look for needle in.
 * @param  $offset Start looking from here.
 * @param  $reg    A PCRE-style regex to match.
 * @return         Returns a false if no matches found, or an array
 *                 with the following members:
 *                 - integer with the location of the match within $body
 *                 - string with whatever content between offset and the match
 *                 - string with whatever it is we matched
 */
function findnxreg($body, $offset, $reg){
	$me = 'findnxreg';
	$matches = Array();
	$retarr = Array();
	$preg_rule = '%^(.*?)(' . $reg . ')%s';
	preg_match($preg_rule, substr($body, $offset), $matches);
	if (!$matches{0}){
		$this->spew("$me: No matches found.\n");
		$retarr = false;
	} else {
		$retarr{0} = $offset + strlen($matches{1});
		$retarr{1} = $matches{1};
		$retarr{2} = $matches{2};
		$this->spew("$me: '$reg' found at pos $offset matching '".$matches{2}."'\n");
	}
	return $retarr;
}

/**
 * This function looks for the next tag.
 *
 * @param  $body   String where to look for the next tag.
 * @param  $offset Start looking from here.
 * @return         false if no more tags exist in the body, or
 *                 an array with the following members:
 *                 - string with the name of the tag
 *                 - array with attributes and their values
 *                 - integer with tag type (1, 2, or 3)
 *                 - integer where the tag starts (starting "<")
 *                 - integer where the tag ends (ending ">")
 *                 first three members will be false, if the tag is invalid.
 */
function getnxtag($body, $offset){
	$me = 'getnxtag';
	if ($offset > strlen($body)){
		$this->spew("$me: Past the end of body\n");
		return false;
	}
	$lt = $this->findnxstr($body, $offset, '<');
	if ($lt == strlen($body)){
		$this->spew("$me: No more tags found!\n");
		return false;
	}
	/**
	 * We are here:
	 * blah blah <tag attribute="value">
	 * \---------^
	 */
	$this->spew("$me: Found '<' at pos $lt\n");
	$pos = $this->skipspace($body, $lt + 1);
	if ($pos >= strlen($body)){
		$this->spew("$me: End of body reached.\n");
		return Array(false, false, false, $lt, strlen($body));
	}
	/**
	 * There are 3 kinds of tags:
	 * 1. Opening tag, e.g.:
	 *    <a href="blah">
	 * 2. Closing tag, e.g.:
	 *    </a>
	 * 3. XHTML-style content-less tag, e.g.:
	 *    <img src="blah"/>
	 */
	$tagtype = false;
	switch (substr($body, $pos, 1)){
	case '/':
		$this->spew("$me: This is a closing tag (type 2)\n");
		$tagtype = 2;
		$pos++;
		break;
	case '!':
		/**
		 * A comment or an SGML declaration.
		 */
		if (substr($body, $pos+1, 2) == '--'){
			$this->spew("$me: A comment found. Stripping.\n");
			$gt = strpos($body, '-->', $pos);
			if ($gt === false){
				$gt = strlen($body);
			} else {
				$gt += 2;
			}
			return Array(false, false, false, $lt, $gt);
		} else {
			$this->spew("$me: An SGML declaration found. Stripping.\n");
			$gt = $this->findnxstr($body, $pos, '>');
			return Array(false, false, false, $lt, $gt);
		}
		break;
	default:
		/**
		 * Assume tagtype 1 for now. If it's type 3, we'll switch values
		 * later.
		 */
		$tagtype = 1;
		break;
	}
	
	$tag_start = $pos;
	$tagname = '';
	/**
	 * Look for next [\W-_], which will indicate the end of the tag name.
	 */
	$regary = $this->findnxreg($body, $pos, '[^\w\-_]');
	if ($regary == false){
		$this->spew("$me: End of body reached while analyzing tag name\n");
		return Array(false, false, false, $lt, strlen($body));
	}
	list($pos, $tagname, $match) = $regary;
	$tagname = strtolower($tagname);
	
	/**
	 * $match can be either of these:
	 * '>'  indicating the end of the tag entirely.
	 * '\s' indicating the end of the tag name.
	 * '/'  indicating that this is type-3 xhtml tag.
	 * 
	 * Whatever else we find there indicates an invalid tag.
	 */
	switch ($match){
	case '/':
		/**
		 * This is an xhtml-style tag with a closing / at the
		 * end, like so: <img src="blah"/>. Check if it's followed
		 * by the closing bracket. If not, then this tag is invalid
		 */
		if (substr($body, $pos, 2) == '/>'){
			$this->spew("$me: XHTML-style tag found.\n");
			$pos++;
			$this->spew("$me: Setting tagtype to 3\n");
			$tagtype = 3;
		} else {
			$this->spew("$me: Found invalid character '/'.\n");
			$gt = $this->findnxstr($body, $pos, '>');
			$this->spew("$me: Tag is invalid. Returning.\n");
			$retary = Array(false, false, false, $lt, $gt);
			return $retary;
		}
	case '>':
		$this->spew("$me: End of tag found at $pos\n");
		$this->spew("$me: Tagname is '$tagname'\n");
		$this->spew("$me: This tag has no attributes\n");
		return Array($tagname, false, $tagtype, $lt, $pos);
		break;
	default:
		/**
		 * Check if it's whitespace
		 */
		if (preg_match('/\s/', $match)){
			$this->spew("$me: Tagname is '$tagname'\n");
		} else {
			/**
			 * This is an invalid tag! Look for the next closing ">".
			 */
			$this->spew("$me: Invalid characters found in tag name: $match\n");
			$gt = $this->findnxstr($body, $offset, '>');
			return Array(false, false, false, $lt, $gt);
		}
	}
	
	/**
	 * At this point we're here:
	 * <tagname  attribute='blah'>
	 * \-------^
	 *
	 * At this point we loop in order to find all attributes.
	 */
	$attname = '';
	$atttype = false;
	$attary = Array();
	
	while ($pos <= strlen($body)){
		$pos = $this->skipspace($body, $pos);
		if ($pos == strlen($body)){
			/**
			 * Non-closed tag.
			 */
			$this->spew("$me: End of body reached before end of tag. Discarding.\n");
			return Array(false, false, false, $lt, $pos);
		}
		/**
		 * See if we arrived at a ">" or "/>", which means that we reached
		 * the end of the tag.
		 */
		$matches = Array();
		preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches);
		if (isset($matches{0}) && $matches{0}){
			/**
			 * Yep. So we did.
			 */
			$this->spew("$me: Arrived at the end of the tag.\n");
			$pos += strlen($matches{1});
			if ($matches{2} == '/>'){
				$tagtype = 3;
				$pos++;
			}
			return Array($tagname, $attary, $tagtype, $lt, $pos);
		}
		
		/**
		 * There are several types of attributes, with optional
		 * [:space:] between members.
		 * Type 1:
		 *   attrname[:space:]=[:space:]'CDATA'
		 * Type 2:
		 *   attrname[:space:]=[:space:]"CDATA"
		 * Type 3:
		 *   attr[:space:]=[:space:]CDATA
		 * Type 4:
		 *   attrname
		 *
		 * We leave types 1 and 2 the same, type 3 we check for
		 * '"' and convert to "&quot" if needed, then wrap in
		 * double quotes. Type 4 we convert into:
		 * attrname="yes".
		 */
		$regary = $this->findnxreg($body, $pos, '[^\w\-_]');
		if ($regary == false){
			/**
			 * Looks like body ended before the end of tag.
			 */
			$this->spew("$me: End of body found before end of tag.\n");
			$this->spew("$me: Invalid, returning\n");
			return Array(false, false, false, $lt, strlen($body));
		}
		list($pos, $attname, $match) = $regary;
		$attname = strtolower($attname);
		$this->spew("$me: Attribute '$attname' found\n");
		/**
		 * We arrived at the end of attribute name. Several things possible
		 * here:
		 * '>'  means the end of the tag and this is attribute type 4
		 * '/'  if followed by '>' means the same thing as above
		 * '\s' means a lot of things -- look what it's followed by.
		 *      anything else means the attribute is invalid.
		 */
		switch($match){
		case '/':
			/**
			 * This is an xhtml-style tag with a closing / at the
			 * end, like so: <img src="blah"/>. Check if it's followed
			 * by the closing bracket. If not, then this tag is invalid
			 */
			if (substr($body, $pos, 2) == '/>'){
				$this->spew("$me: This is an xhtml-style tag.\n");
				$pos++;
				$this->spew("$me: Setting tagtype to 3\n");
				$tagtype = 3;
			} else {
				$this->spew("$me: Found invalid character '/'.\n");
				$gt = $this->findnxstr($body, $pos, '>');
				$this->spew("$me: Tag is invalid. Returning.\n");
				$retary = Array(false, false, false, $lt, $gt);
				return $retary;
			}
		case '>':
			$this->spew("$me: found type 4 attribute.\n");
			$this->spew("$me: Additionally, end of tag found at $pos\n");
			$this->spew("$me: Attname is '$attname'\n");
			$this->spew("$me: Setting attvalue to 'yes'\n");
			$attary{$attname} = '"yes"';
			return Array($tagname, $attary, $tagtype, $lt, $pos);
			break;
		default:
			/**
			 * Skip whitespace and see what we arrive at.
			 */
			$pos = $this->skipspace($body, $pos);
			$char = substr($body, $pos, 1);
			/**
			 * Two things are valid here:
			 * '=' means this is attribute type 1 2 or 3.
			 * \w means this was attribute type 4.
			 * anything else we ignore and re-loop. End of tag and
			 * invalid stuff will be caught by our checks at the beginning
			 * of the loop.
			 */
			if ($char == '='){
				$this->spew("$me: Attribute type 1, 2, or 3 found.\n");
				$pos++;
				$pos = $this->skipspace($body, $pos);
				/**
				 * Here are 3 possibilities:
				 * "'"  attribute type 1
				 * '"'  attribute type 2
				 * everything else is the content of tag type 3
				 */
				$quot = substr($body, $pos, 1);
				if ($quot == '\''){
					$this->spew("$me: In fact, this is attribute type 1\n");
					$this->spew("$me: looking for closing quote\n");
					$regary = $this->findnxreg($body, $pos+1, '\'');
					if ($regary == false){
						$this->spew("$me: end of body reached before end of val\n");
						$this->spew("$me: Returning\n");
						return Array(false, false, false, $lt, strlen($body));
					}
					list($pos, $attval, $match) = $regary;
					$this->spew("$me: Attvalue is '$attval'\n");
					$pos++;
					$attary{$attname} = '\'' . $attval . '\'';
				} else if ($quot == '"'){
					$this->spew("$me: In fact, this is attribute type 2\n");
					$this->spew("$me: looking for closing quote\n");
					$regary = $this->findnxreg($body, $pos+1, '\"');
					if ($regary == false){
						$this->spew("$me: end of body reached before end of val\n");
						$this->spew("$me: Returning\n");
						return Array(false, false, false, $lt, strlen($body));
					}
					list($pos, $attval, $match) = $regary;
					$this->spew("$me: Attvalue is \"$attval\"\n");
					$pos++;
					$attary{$attname} = '"' . $attval . '"';
				} else {
					$this->spew("$me: This looks like attribute type 3\n");
					/**
					 * These are hateful. Look for \s, or >.
					 */
					$this->spew("$me: Looking for end of attval\n");
					$regary = $this->findnxreg($body, $pos, '[\s>]');
					if ($regary == false){
						$this->spew("$me: end of body reached before end of val\n");
						$this->spew("$me: Returning\n");
						return Array(false, false, false, $lt, strlen($body));
					}
					list($pos, $attval, $match) = $regary;
					/**
					 * If it's ">" it will be caught at the top.
					 */
					$this->spew("$me: translating '\"' into &quot;\n");
					$attval = preg_replace('/\"/s', '&quot;', $attval);
					$this->spew("$me: wrapping in quotes\n");
					$attary{$attname} = '"' . $attval . '"';
				}
			} else if (preg_match('|[\w/>]|', $char)) {
				/**
				 * That was attribute type 4.
				 */
				$this->spew("$me: attribute type 4 found.\n");
				$this->spew("$me: Setting value to 'yes'\n");
				$attary{$attname} = '"yes"';
			} else {
				/**
				 * An illegal character. Find next '>' and return.
				 */
				$this->spew("$me: illegal character '$char' found.\n");
				$this->spew("$me: returning\n");
				$gt = $this->findnxstr($body, $pos, '>');
				return Array(false, false, false, $lt, $gt);
			}
		}
	}
	/**
	 * The fact that we got here indicates that the tag end was never
	 * found. Return invalid tag indication so it gets stripped.
	 */
	$this->spew("$me: No tag end found\n");
	return Array(false, false, false, $lt, strlen($body));
}

/**
 * This function checks attribute values for entity-encoded values
 * and returns them translated into 8-bit strings so we can run
 * checks on them.
 *
 * @param  $attvalue A string to run entity check against.
 * @return           Translated value.
 */
function deent($attvalue){
	$me = 'deent';
	/**
	 * See if we have to run the checks first. All entities must start
	 * with "&".
	 */
	if (strpos($attvalue, '&') === false){
		return $attvalue;
	}
	/**
	 * Check named entities first.
	 */
	$this->spew("$me: translating named entities\n");
	$trans = get_html_translation_table(HTML_ENTITIES);
	/**
	 * Leave &quot; in, as it can mess us up.
	 */
	$trans = array_flip($trans);
	unset($trans{'&quot;'});
	while (list($ent, $val) = each($trans)){
		$attvalue = preg_replace('/' . $ent . '*/si', $val, $attvalue);
	}
	/**
	 * Now translate numbered entities from 1 to 255 if needed.
	 */
	if (strpos($attvalue, '#') !== false){
		$this->spew("$me: translating numbered entities\n");
		$omit = Array(34, 39);
		for ($asc = 256; $asc >= 0; $asc--){
			if (!in_array($asc, $omit)){
				$chr = chr($asc);
				$octrule = '/\&#0*' . $asc . ';*/si';
				$hexrule = '/\&#x0*' . dechex($asc) . ';*/si';
				$attvalue = preg_replace($octrule, $chr, $attvalue);
				$attvalue = preg_replace($hexrule, $chr, $attvalue);
			}
		}
	}
	$this->spew("$me: translated into: $attvalue\n");
	return $attvalue;
}

/**
 * This function runs various checks against the attributes.
 *
 * @param  $tagname         String with the name of the tag.
 * @param  $attary          Array with all tag attributes.
 * @param  $rm_attnames     See description for sanitize
 * @param  $bad_attvals     See description for sanitize
 * @param  $add_attr_to_tag See description for sanitize
 * @return                  Array with modified attributes.
 */
function fixatts($tagname, 
								 $attary, 
								 $rm_attnames,
								 $bad_attvals,
								 $add_attr_to_tag
								 ){
	$me = 'fixatts';
	$this->spew("$me: Fixing attributes\n");
	while (list($attname, $attvalue) = each($attary)){
		/**
		 * See if this attribute should be removed.
		 */
		foreach ($rm_attnames as $matchtag=>$matchattrs){
			if (preg_match($matchtag, $tagname)){
				foreach ($matchattrs as $matchattr){
					if (preg_match($matchattr, $attname)){
						$this->spew("$me: Attribute '$attname' defined as bad.\n");
						$this->spew("$me: Removing.\n");
						unset($attary{$attname});
						continue;
					}
				}
			}
		}
		/**
		 * Remove any entities.
		 */
		$attvalue = $this->deent($attvalue);
		
		/**
		 * Now let's run checks on the attvalues.
		 * I don't expect anyone to comprehend this. If you do,
		 * get in touch with me so I can drive to where you live and
		 * shake your hand personally. :)
		 */
		foreach ($bad_attvals as $matchtag=>$matchattrs){
			if (preg_match($matchtag, $tagname)){
				foreach ($matchattrs as $matchattr=>$valary){
					if (preg_match($matchattr, $attname)){
						/**
						 * There are two arrays in valary.
						 * First is matches.
						 * Second one is replacements
						 */
						list($valmatch, $valrepl) = $valary;
						$newvalue = preg_replace($valmatch, $valrepl, $attvalue);
						if ($newvalue != $attvalue){
							$this->spew("$me: attvalue is now $newvalue\n");
							$attary{$attname} = $newvalue;
						}
					}
				}
			}
		}
	}
	/**
	 * See if we need to append any attributes to this tag.
	 */
	foreach ($add_attr_to_tag as $matchtag=>$addattary){
		if (preg_match($matchtag, $tagname)){
			$attary = array_merge($attary, $addattary);
			$this->spew("$me: Added attributes to this tag\n");
		}
	}
	return $attary;
}

/**
 * This is the main function and the one you should actually be calling.
 * There are several variables you should be aware of an which need
 * special description.
 *
 * $tag_list
 * ----------
 * This is a simple one-dimentional array of strings, except for the
 * very first one. The first member should be einter false or true.
 * In case it's FALSE, the following list will be considered a list of
 * tags that should be explicitly REMOVED from the body, and all
 * others that did not match the list will be allowed.  If the first
 * member is TRUE, then the list is the list of tags that should be
 * explicitly ALLOWED -- any tag not matching this list will be
 * discarded.
 *
 * Examples:
 * $tag_list = Array(
 *                   false,   
 *                   "blink", 
 *                   "link",
 *		     "object",
 *		     "meta",
 *                   "marquee",
 *                   "html"
 *		            );
 *
 * This will allow all tags except for blink, link, object, meta, marquee, 
 * and html.
 *
 * $tag_list = Array(
 *                   true, 
 *                   "b", 
 *                   "a", 
 *                   "i", 
 *                   "img", 
 *                   "strong", 
 *                   "em", 
 *                   "p"
 *                  );
 *
 * This will remove all tags from the body except b, a, i, img, strong, em and
 * p.
 *
 * $rm_tags_with_content
 * ---------------------
 * This is a simple one-dimentional array of strings, which specifies the
 * tags to be removed with any and all content between the beginning and
 * the end of the tag.
 * Example:
 * $rm_tags_with_content = Array(
 *                               "script",
 *                               "style", 
 *                               "applet",
 *                               "embed"
 *                              );
 *
 * This will remove the following structure:
 * <script>
 *  window.alert("Isn't cross-site-scripting fun?!");
 * </script>
 * 
 * $self_closing_tags
 * ------------------
 * This is a simple one-dimentional array of strings, which specifies which
 * tags contain no content and should not be forcefully closed if this option
 * is turned on (see further).
 * Example:
 * $self_closing_tags =  Array(
 *                             "img",
 *                             "br", 
 *                             "hr",
 *                             "input"
 *                            );    
 *
 * $force_tag_closing
 * ------------------
 * Set it to true to forcefully close any tags opened within the document.
 * This is good if you want to take care of people who like to screw up
 * the pages by leaving unclosed tags like <a>, <b>, <i>, etc.
 *
 * $rm_attnames
 * -------------
 * Now we come to parameters that are more obscure. This parameter is
 * a nested array which is used to specify which attributes should be
 * removed. It goes like so:
 * 
 * $rm_attnames = Array(
 *   "PCRE regex to match tag name" =>
 *     Array(
 *           "PCRE regex to match attribute name"
 *           )
 *   );
 *
 * Example:
 * $rm_attnames = Array(
 *   "|.*|" =>
 *     Array(
 *           "|target|i",
 *           "|^on.*|i"  
 *          )
 *   );
 *
 * This will match all attributes (.*), and specify that all attributes
 * named "target" and starting with "on" should be removed. This will take
 * care of the following problem:
 * <em onmouseover="window.alert('muahahahaha')">
 * The "onmouseover" will be removed.
 *
 * $bad_attvals
 * ------------
 * This is where it gets ugly. This is a nested array with many levels.
 * It goes like so:
 *
 * $bad_attvals = Array(
 *   "pcre regex to match tag name" =>
 *     Array(
 *           "pcre regex to match attribute name" =>
 *             Array(
 *                   "pcre regex to match attribute value"
 *                  )
 *             Array(
 *                   "pcre regex replace a match from above with"
 *                  )
 *          )
 *   );
 *
 * An extensive example:
 *
 * $bad_attvals = Array(
 *   "|.*|" =>
 *      Array(
 *            "/^src|background|href|action/i" =>
 *                Array(
 *                      Array(
 *                            "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si"
 *                            ),
 *                      Array(
 *                            "\\1http://veryfunny.com/\\2"
 *                            )
 *                      ),
 *            "/^style/i" =>
 *                Array(
 *                      Array(
 *                            "/expression/si",
 *                            "/url\(([\'\"])\s*https*:.*([\'\"])\)/si",
 *                            "/url\(([\'\"])\s*\S+script:.*([\'\"])\)/si"
 *                           ),
 *                      Array(
 *                            "idiocy",
 *                            "url(\\1http://veryfunny.com/\\2)",
 *                            "url(\\1http://veryfynny.com/\\2)"
 *                           )
 *                      )
 *            )
 *  );
 *
 * This will take care of nearly all known cross-site scripting exploits,
 * plus some (see my filter sample at 
 * http://www.mricon.com/html/phpfilter.html for a working version).
 *
 * $add_attr_to_tag
 * ----------------
 * This is a useful little feature which lets you add attributes to 
 * certain tags. It is a nested array as well, but not at all like
 * the previous one. It goes like so:
 * 
 * $add_attr_to_tag = Array(
 *   "PCRE regex to match tag name" =>
 *     Array(
 *           "attribute name"=>'"attribute value"'
 *          )
 *   );
 * 
 * Note: don't forget quotes around attribute value.
 * 
 * Example:
 * 
 * $add_attr_to_tag = Array(
 *   "/^a$/si" => 
 *     Array(
 *           'target'=>'"_new"'
 *          )
 *   );
 * 
 * This will change all <a> tags and add target="_new" to them so all links
 * open in a new window.
 *
 *
 *
 * @param $body                 the string with HTML you wish to filter
 * @param $tag_list             see description above
 * @param $rm_tags_with_content see description above
 * @param $self_closing_tags    see description above
 * @param $force_tag_closing    see description above
 * @param $rm_attnames          see description above
 * @param $bad_attvals          see description above
 * @param $add_attr_to_tag      see description above
 * @return                      sanitized html safe to show on your pages.
 */
function sanitize($body, 
									$tag_list, 
									$rm_tags_with_content,
									$self_closing_tags,
									$force_tag_closing,
									$rm_attnames,
									$bad_attvals,
									$add_attr_to_tag
									){
	$me = 'sanitize';
	/**
	 * Normalize rm_tags and rm_tags_with_content.
	 */
	@array_walk($rm_tags, 'casenormalize');
	@array_walk($rm_tags_with_content, 'casenormalize');
	@array_walk($self_closing_tags, 'casenormalize');
	/**
	 * See if tag_list is of tags to remove or tags to allow.
	 * false  means remove these tags
	 * true   means allow these tags
	 */
	$rm_tags = array_shift($tag_list);
	$curpos = 0;
	$open_tags = Array();
	#$trusted = "<!-- begin sanitized html -->\n";
	$trusted = "";
	$skip_content = false;
	/**
	 * Take care of netscape's stupid javascript entities like
	 * &{alert('boo')};
	 */
	$body = preg_replace('/&(\{.*?\};)/si', '&amp;\\1', $body);
	$this->spew("$me: invoking the loop\n");
	while (($curtag = $this->getnxtag($body, $curpos)) != FALSE){
		list($tagname, $attary, $tagtype, $lt, $gt) = $curtag;
		$this->spew("$me: grabbing free-standing content\n");
		$free_content = substr($body, $curpos, $lt - $curpos);
		$this->spew("$me: " . strlen($free_content) . " chars grabbed\n");
		if ($skip_content == false){
			$this->spew("$me: appending free content to trusted.\n");
			$trusted .= $free_content;
		} else {
			$this->spew("$me: Skipping free content.\n");
		}
		if ($tagname != FALSE){
			$this->spew("$me: tagname is '$tagname'\n");
			if ($tagtype == 2){
				$this->spew("$me: This is a closing tag\n");
				if ($skip_content == $tagname){
					/**
					 * Got to the end of tag we needed to remove.
					 */
					$this->spew("$me: Finished removing tag with content\n");
					$tagname = false;
					$skip_content = false;
				} else {
					if ($skip_content == false){
						if (isset($open_tags{$tagname}) && 
								$open_tags{$tagname} > 0){
							$this->spew("$me: popping '$tagname' from open_tags\n");
							$open_tags{$tagname}--;
						} else {
							$this->spew("$me: '$tagname' was never opened\n");
							$this->spew("$me: removing\n");
							$tagname = false;
						}
					} else {
						$this->spew("$me: Skipping this tag\n");
					}
				}
			} else {
				/**
				 * $rm_tags_with_content
				 */
				if ($skip_content == false){
					/**
					 * See if this is a self-closing type and change
					 * tagtype appropriately.
					 */
					if ($tagtype == 1
							&& in_array($tagname, $self_closing_tags)){
						$this->spew("$me: Self-closing tag. Changing tagtype.\n");
						$tagtype = 3;
					}
					/**
					 * See if we should skip this tag and any content
					 * inside it.
					 */
					if ($tagtype == 1 && in_array($tagname, $rm_tags_with_content)){
						$this->spew("$me: removing this tag with content\n");
						$skip_content = $tagname;
					} else {
						if (($rm_tags == false && in_array($tagname, $tag_list)) ||
								($rm_tags == true && !in_array($tagname, $tag_list))){
							$this->spew("$me: Removing this tag.\n");
							$tagname = false;
						} else {
							if ($tagtype == 1){
								$this->spew("$me: adding '$tagname' to open_tags\n");
								if (isset($open_tags{$tagname})){
									$open_tags{$tagname}++;
								} else {
									$open_tags{$tagname} = 1;
								}
							}
							/**
							 * This is where we run other checks.
							 */
							if (is_array($attary) && sizeof($attary) > 0){
								$attary = $this->fixatts($tagname,
																	$attary,
																	$rm_attnames,
																	$bad_attvals,
																	$add_attr_to_tag);
							}
						}
					}
				} else {
					$this->spew("$me: Skipping this tag\n");
				}
			}
			if ($tagname != false && $skip_content == false){
				$this->spew("$me: Appending tag to trusted.\n");
				$trusted .= $this->tagprint($tagname, $attary, $tagtype);
			}
		} else {
			$this->spew("$me: Removing invalid tag\n");
		}
		$curpos = $gt + 1;
	}
	$this->spew("$me: Appending any leftover content\n");
	$trusted .= substr($body, $curpos, strlen($body) - $curpos);
	if ($force_tag_closing == true){
		foreach ($open_tags as $tagname=>$opentimes){
			while ($opentimes > 0){
				$this->spew("$me: '$tagname' left open. Closing by force.\n");
				$trusted .= '</' . $tagname . '>';
				$opentimes--;
			}
		}
		$trusted .= "\n";
	}
#  $trusted .= "<!-- end sanitized html -->\n";
	$trusted .= "";
	return $trusted;
}
// class end
}
?>