HTMLPurifier.standalone.php 708 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584
  1. <?php
  2. /**
  3. * @file
  4. * This file was auto-generated by generate-includes.php and includes all of
  5. * the core files required by HTML Purifier. Use this if performance is a
  6. * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
  7. * FILE, changes will be overwritten the next time the script is run.
  8. *
  9. * @version 4.12.0
  10. *
  11. * @warning
  12. * You must *not* include any other HTML Purifier files before this file,
  13. * because 'require' not 'require_once' is used.
  14. *
  15. * @warning
  16. * This file requires that the include path contains the HTML Purifier
  17. * library directory; this is not auto-set.
  18. */
  19. /*! @mainpage
  20. *
  21. * HTML Purifier is an HTML filter that will take an arbitrary snippet of
  22. * HTML and rigorously test, validate and filter it into a version that
  23. * is safe for output onto webpages. It achieves this by:
  24. *
  25. * -# Lexing (parsing into tokens) the document,
  26. * -# Executing various strategies on the tokens:
  27. * -# Removing all elements not in the whitelist,
  28. * -# Making the tokens well-formed,
  29. * -# Fixing the nesting of the nodes, and
  30. * -# Validating attributes of the nodes; and
  31. * -# Generating HTML from the purified tokens.
  32. *
  33. * However, most users will only need to interface with the HTMLPurifier
  34. * and HTMLPurifier_Config.
  35. */
  36. /*
  37. HTML Purifier 4.12.0 - Standards Compliant HTML Filtering
  38. Copyright (C) 2006-2008 Edward Z. Yang
  39. This library is free software; you can redistribute it and/or
  40. modify it under the terms of the GNU Lesser General Public
  41. License as published by the Free Software Foundation; either
  42. version 2.1 of the License, or (at your option) any later version.
  43. This library is distributed in the hope that it will be useful,
  44. but WITHOUT ANY WARRANTY; without even the implied warranty of
  45. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  46. Lesser General Public License for more details.
  47. You should have received a copy of the GNU Lesser General Public
  48. License along with this library; if not, write to the Free Software
  49. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  50. */
  51. /**
  52. * Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
  53. *
  54. * @note There are several points in which configuration can be specified
  55. * for HTML Purifier. The precedence of these (from lowest to
  56. * highest) is as follows:
  57. * -# Instance: new HTMLPurifier($config)
  58. * -# Invocation: purify($html, $config)
  59. * These configurations are entirely independent of each other and
  60. * are *not* merged (this behavior may change in the future).
  61. *
  62. * @todo We need an easier way to inject strategies using the configuration
  63. * object.
  64. */
  65. class HTMLPurifier
  66. {
  67. /**
  68. * Version of HTML Purifier.
  69. * @type string
  70. */
  71. public $version = '4.12.0';
  72. /**
  73. * Constant with version of HTML Purifier.
  74. */
  75. const VERSION = '4.12.0';
  76. /**
  77. * Global configuration object.
  78. * @type HTMLPurifier_Config
  79. */
  80. public $config;
  81. /**
  82. * Array of extra filter objects to run on HTML,
  83. * for backwards compatibility.
  84. * @type HTMLPurifier_Filter[]
  85. */
  86. private $filters = array();
  87. /**
  88. * Single instance of HTML Purifier.
  89. * @type HTMLPurifier
  90. */
  91. private static $instance;
  92. /**
  93. * @type HTMLPurifier_Strategy_Core
  94. */
  95. protected $strategy;
  96. /**
  97. * @type HTMLPurifier_Generator
  98. */
  99. protected $generator;
  100. /**
  101. * Resultant context of last run purification.
  102. * Is an array of contexts if the last called method was purifyArray().
  103. * @type HTMLPurifier_Context
  104. */
  105. public $context;
  106. /**
  107. * Initializes the purifier.
  108. *
  109. * @param HTMLPurifier_Config|mixed $config Optional HTMLPurifier_Config object
  110. * for all instances of the purifier, if omitted, a default
  111. * configuration is supplied (which can be overridden on a
  112. * per-use basis).
  113. * The parameter can also be any type that
  114. * HTMLPurifier_Config::create() supports.
  115. */
  116. public function __construct($config = null)
  117. {
  118. $this->config = HTMLPurifier_Config::create($config);
  119. $this->strategy = new HTMLPurifier_Strategy_Core();
  120. }
  121. /**
  122. * Adds a filter to process the output. First come first serve
  123. *
  124. * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
  125. */
  126. public function addFilter($filter)
  127. {
  128. trigger_error(
  129. 'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
  130. ' in the Filter namespace or Filter.Custom',
  131. E_USER_WARNING
  132. );
  133. $this->filters[] = $filter;
  134. }
  135. /**
  136. * Filters an HTML snippet/document to be XSS-free and standards-compliant.
  137. *
  138. * @param string $html String of HTML to purify
  139. * @param HTMLPurifier_Config $config Config object for this operation,
  140. * if omitted, defaults to the config object specified during this
  141. * object's construction. The parameter can also be any type
  142. * that HTMLPurifier_Config::create() supports.
  143. *
  144. * @return string Purified HTML
  145. */
  146. public function purify($html, $config = null)
  147. {
  148. // :TODO: make the config merge in, instead of replace
  149. $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
  150. // implementation is partially environment dependant, partially
  151. // configuration dependant
  152. $lexer = HTMLPurifier_Lexer::create($config);
  153. $context = new HTMLPurifier_Context();
  154. // setup HTML generator
  155. $this->generator = new HTMLPurifier_Generator($config, $context);
  156. $context->register('Generator', $this->generator);
  157. // set up global context variables
  158. if ($config->get('Core.CollectErrors')) {
  159. // may get moved out if other facilities use it
  160. $language_factory = HTMLPurifier_LanguageFactory::instance();
  161. $language = $language_factory->create($config, $context);
  162. $context->register('Locale', $language);
  163. $error_collector = new HTMLPurifier_ErrorCollector($context);
  164. $context->register('ErrorCollector', $error_collector);
  165. }
  166. // setup id_accumulator context, necessary due to the fact that
  167. // AttrValidator can be called from many places
  168. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  169. $context->register('IDAccumulator', $id_accumulator);
  170. $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
  171. // setup filters
  172. $filter_flags = $config->getBatch('Filter');
  173. $custom_filters = $filter_flags['Custom'];
  174. unset($filter_flags['Custom']);
  175. $filters = array();
  176. foreach ($filter_flags as $filter => $flag) {
  177. if (!$flag) {
  178. continue;
  179. }
  180. if (strpos($filter, '.') !== false) {
  181. continue;
  182. }
  183. $class = "HTMLPurifier_Filter_$filter";
  184. $filters[] = new $class;
  185. }
  186. foreach ($custom_filters as $filter) {
  187. // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat
  188. $filters[] = $filter;
  189. }
  190. $filters = array_merge($filters, $this->filters);
  191. // maybe prepare(), but later
  192. for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
  193. $html = $filters[$i]->preFilter($html, $config, $context);
  194. }
  195. // purified HTML
  196. $html =
  197. $this->generator->generateFromTokens(
  198. // list of tokens
  199. $this->strategy->execute(
  200. // list of un-purified tokens
  201. $lexer->tokenizeHTML(
  202. // un-purified HTML
  203. $html,
  204. $config,
  205. $context
  206. ),
  207. $config,
  208. $context
  209. )
  210. );
  211. for ($i = $filter_size - 1; $i >= 0; $i--) {
  212. $html = $filters[$i]->postFilter($html, $config, $context);
  213. }
  214. $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
  215. $this->context =& $context;
  216. return $html;
  217. }
  218. /**
  219. * Filters an array of HTML snippets
  220. *
  221. * @param string[] $array_of_html Array of html snippets
  222. * @param HTMLPurifier_Config $config Optional config object for this operation.
  223. * See HTMLPurifier::purify() for more details.
  224. *
  225. * @return string[] Array of purified HTML
  226. */
  227. public function purifyArray($array_of_html, $config = null)
  228. {
  229. $context_array = array();
  230. foreach($array_of_html as $key=>$value){
  231. if (is_array($value)) {
  232. $array[$key] = $this->purifyArray($value, $config);
  233. } else {
  234. $array[$key] = $this->purify($value, $config);
  235. }
  236. $context_array[$key] = $this->context;
  237. }
  238. $this->context = $context_array;
  239. return $array;
  240. }
  241. /**
  242. * Singleton for enforcing just one HTML Purifier in your system
  243. *
  244. * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
  245. * HTMLPurifier instance to overload singleton with,
  246. * or HTMLPurifier_Config instance to configure the
  247. * generated version with.
  248. *
  249. * @return HTMLPurifier
  250. */
  251. public static function instance($prototype = null)
  252. {
  253. if (!self::$instance || $prototype) {
  254. if ($prototype instanceof HTMLPurifier) {
  255. self::$instance = $prototype;
  256. } elseif ($prototype) {
  257. self::$instance = new HTMLPurifier($prototype);
  258. } else {
  259. self::$instance = new HTMLPurifier();
  260. }
  261. }
  262. return self::$instance;
  263. }
  264. /**
  265. * Singleton for enforcing just one HTML Purifier in your system
  266. *
  267. * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
  268. * HTMLPurifier instance to overload singleton with,
  269. * or HTMLPurifier_Config instance to configure the
  270. * generated version with.
  271. *
  272. * @return HTMLPurifier
  273. * @note Backwards compatibility, see instance()
  274. */
  275. public static function getInstance($prototype = null)
  276. {
  277. return HTMLPurifier::instance($prototype);
  278. }
  279. }
  280. /**
  281. * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
  282. * and back again.
  283. *
  284. * @note This transformation is not an equivalence. We mutate the input
  285. * token stream to make it so; see all [MUT] markers in code.
  286. */
  287. class HTMLPurifier_Arborize
  288. {
  289. public static function arborize($tokens, $config, $context) {
  290. $definition = $config->getHTMLDefinition();
  291. $parent = new HTMLPurifier_Token_Start($definition->info_parent);
  292. $stack = array($parent->toNode());
  293. foreach ($tokens as $token) {
  294. $token->skip = null; // [MUT]
  295. $token->carryover = null; // [MUT]
  296. if ($token instanceof HTMLPurifier_Token_End) {
  297. $token->start = null; // [MUT]
  298. $r = array_pop($stack);
  299. //assert($r->name === $token->name);
  300. //assert(empty($token->attr));
  301. $r->endCol = $token->col;
  302. $r->endLine = $token->line;
  303. $r->endArmor = $token->armor;
  304. continue;
  305. }
  306. $node = $token->toNode();
  307. $stack[count($stack)-1]->children[] = $node;
  308. if ($token instanceof HTMLPurifier_Token_Start) {
  309. $stack[] = $node;
  310. }
  311. }
  312. //assert(count($stack) == 1);
  313. return $stack[0];
  314. }
  315. public static function flatten($node, $config, $context) {
  316. $level = 0;
  317. $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  318. $closingTokens = array();
  319. $tokens = array();
  320. do {
  321. while (!$nodes[$level]->isEmpty()) {
  322. $node = $nodes[$level]->shift(); // FIFO
  323. list($start, $end) = $node->toTokenPair();
  324. if ($level > 0) {
  325. $tokens[] = $start;
  326. }
  327. if ($end !== NULL) {
  328. $closingTokens[$level][] = $end;
  329. }
  330. if ($node instanceof HTMLPurifier_Node_Element) {
  331. $level++;
  332. $nodes[$level] = new HTMLPurifier_Queue();
  333. foreach ($node->children as $childNode) {
  334. $nodes[$level]->push($childNode);
  335. }
  336. }
  337. }
  338. $level--;
  339. if ($level && isset($closingTokens[$level])) {
  340. while ($token = array_pop($closingTokens[$level])) {
  341. $tokens[] = $token;
  342. }
  343. }
  344. } while ($level > 0);
  345. return $tokens;
  346. }
  347. }
  348. /**
  349. * Defines common attribute collections that modules reference
  350. */
  351. class HTMLPurifier_AttrCollections
  352. {
  353. /**
  354. * Associative array of attribute collections, indexed by name.
  355. * @type array
  356. */
  357. public $info = array();
  358. /**
  359. * Performs all expansions on internal data for use by other inclusions
  360. * It also collects all attribute collection extensions from
  361. * modules
  362. * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
  363. * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
  364. */
  365. public function __construct($attr_types, $modules)
  366. {
  367. $this->doConstruct($attr_types, $modules);
  368. }
  369. public function doConstruct($attr_types, $modules)
  370. {
  371. // load extensions from the modules
  372. foreach ($modules as $module) {
  373. foreach ($module->attr_collections as $coll_i => $coll) {
  374. if (!isset($this->info[$coll_i])) {
  375. $this->info[$coll_i] = array();
  376. }
  377. foreach ($coll as $attr_i => $attr) {
  378. if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
  379. // merge in includes
  380. $this->info[$coll_i][$attr_i] = array_merge(
  381. $this->info[$coll_i][$attr_i],
  382. $attr
  383. );
  384. continue;
  385. }
  386. $this->info[$coll_i][$attr_i] = $attr;
  387. }
  388. }
  389. }
  390. // perform internal expansions and inclusions
  391. foreach ($this->info as $name => $attr) {
  392. // merge attribute collections that include others
  393. $this->performInclusions($this->info[$name]);
  394. // replace string identifiers with actual attribute objects
  395. $this->expandIdentifiers($this->info[$name], $attr_types);
  396. }
  397. }
  398. /**
  399. * Takes a reference to an attribute associative array and performs
  400. * all inclusions specified by the zero index.
  401. * @param array &$attr Reference to attribute array
  402. */
  403. public function performInclusions(&$attr)
  404. {
  405. if (!isset($attr[0])) {
  406. return;
  407. }
  408. $merge = $attr[0];
  409. $seen = array(); // recursion guard
  410. // loop through all the inclusions
  411. for ($i = 0; isset($merge[$i]); $i++) {
  412. if (isset($seen[$merge[$i]])) {
  413. continue;
  414. }
  415. $seen[$merge[$i]] = true;
  416. // foreach attribute of the inclusion, copy it over
  417. if (!isset($this->info[$merge[$i]])) {
  418. continue;
  419. }
  420. foreach ($this->info[$merge[$i]] as $key => $value) {
  421. if (isset($attr[$key])) {
  422. continue;
  423. } // also catches more inclusions
  424. $attr[$key] = $value;
  425. }
  426. if (isset($this->info[$merge[$i]][0])) {
  427. // recursion
  428. $merge = array_merge($merge, $this->info[$merge[$i]][0]);
  429. }
  430. }
  431. unset($attr[0]);
  432. }
  433. /**
  434. * Expands all string identifiers in an attribute array by replacing
  435. * them with the appropriate values inside HTMLPurifier_AttrTypes
  436. * @param array &$attr Reference to attribute array
  437. * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
  438. */
  439. public function expandIdentifiers(&$attr, $attr_types)
  440. {
  441. // because foreach will process new elements we add, make sure we
  442. // skip duplicates
  443. $processed = array();
  444. foreach ($attr as $def_i => $def) {
  445. // skip inclusions
  446. if ($def_i === 0) {
  447. continue;
  448. }
  449. if (isset($processed[$def_i])) {
  450. continue;
  451. }
  452. // determine whether or not attribute is required
  453. if ($required = (strpos($def_i, '*') !== false)) {
  454. // rename the definition
  455. unset($attr[$def_i]);
  456. $def_i = trim($def_i, '*');
  457. $attr[$def_i] = $def;
  458. }
  459. $processed[$def_i] = true;
  460. // if we've already got a literal object, move on
  461. if (is_object($def)) {
  462. // preserve previous required
  463. $attr[$def_i]->required = ($required || $attr[$def_i]->required);
  464. continue;
  465. }
  466. if ($def === false) {
  467. unset($attr[$def_i]);
  468. continue;
  469. }
  470. if ($t = $attr_types->get($def)) {
  471. $attr[$def_i] = $t;
  472. $attr[$def_i]->required = $required;
  473. } else {
  474. unset($attr[$def_i]);
  475. }
  476. }
  477. }
  478. }
  479. /**
  480. * Base class for all validating attribute definitions.
  481. *
  482. * This family of classes forms the core for not only HTML attribute validation,
  483. * but also any sort of string that needs to be validated or cleaned (which
  484. * means CSS properties and composite definitions are defined here too).
  485. * Besides defining (through code) what precisely makes the string valid,
  486. * subclasses are also responsible for cleaning the code if possible.
  487. */
  488. abstract class HTMLPurifier_AttrDef
  489. {
  490. /**
  491. * Tells us whether or not an HTML attribute is minimized.
  492. * Has no meaning in other contexts.
  493. * @type bool
  494. */
  495. public $minimized = false;
  496. /**
  497. * Tells us whether or not an HTML attribute is required.
  498. * Has no meaning in other contexts
  499. * @type bool
  500. */
  501. public $required = false;
  502. /**
  503. * Validates and cleans passed string according to a definition.
  504. *
  505. * @param string $string String to be validated and cleaned.
  506. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  507. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
  508. */
  509. abstract public function validate($string, $config, $context);
  510. /**
  511. * Convenience method that parses a string as if it were CDATA.
  512. *
  513. * This method process a string in the manner specified at
  514. * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
  515. * leading and trailing whitespace, ignoring line feeds, and replacing
  516. * carriage returns and tabs with spaces. While most useful for HTML
  517. * attributes specified as CDATA, it can also be applied to most CSS
  518. * values.
  519. *
  520. * @note This method is not entirely standards compliant, as trim() removes
  521. * more types of whitespace than specified in the spec. In practice,
  522. * this is rarely a problem, as those extra characters usually have
  523. * already been removed by HTMLPurifier_Encoder.
  524. *
  525. * @warning This processing is inconsistent with XML's whitespace handling
  526. * as specified by section 3.3.3 and referenced XHTML 1.0 section
  527. * 4.7. However, note that we are NOT necessarily
  528. * parsing XML, thus, this behavior may still be correct. We
  529. * assume that newlines have been normalized.
  530. */
  531. public function parseCDATA($string)
  532. {
  533. $string = trim($string);
  534. $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
  535. return $string;
  536. }
  537. /**
  538. * Factory method for creating this class from a string.
  539. * @param string $string String construction info
  540. * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
  541. */
  542. public function make($string)
  543. {
  544. // default implementation, return a flyweight of this object.
  545. // If $string has an effect on the returned object (i.e. you
  546. // need to overload this method), it is best
  547. // to clone or instantiate new copies. (Instantiation is safer.)
  548. return $this;
  549. }
  550. /**
  551. * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
  552. * properly. THIS IS A HACK!
  553. * @param string $string a CSS colour definition
  554. * @return string
  555. */
  556. protected function mungeRgb($string)
  557. {
  558. $p = '\s*(\d+(\.\d+)?([%]?))\s*';
  559. if (preg_match('/(rgba|hsla)\(/', $string)) {
  560. return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
  561. }
  562. return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
  563. }
  564. /**
  565. * Parses a possibly escaped CSS string and returns the "pure"
  566. * version of it.
  567. */
  568. protected function expandCSSEscape($string)
  569. {
  570. // flexibly parse it
  571. $ret = '';
  572. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  573. if ($string[$i] === '\\') {
  574. $i++;
  575. if ($i >= $c) {
  576. $ret .= '\\';
  577. break;
  578. }
  579. if (ctype_xdigit($string[$i])) {
  580. $code = $string[$i];
  581. for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
  582. if (!ctype_xdigit($string[$i])) {
  583. break;
  584. }
  585. $code .= $string[$i];
  586. }
  587. // We have to be extremely careful when adding
  588. // new characters, to make sure we're not breaking
  589. // the encoding.
  590. $char = HTMLPurifier_Encoder::unichr(hexdec($code));
  591. if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
  592. continue;
  593. }
  594. $ret .= $char;
  595. if ($i < $c && trim($string[$i]) !== '') {
  596. $i--;
  597. }
  598. continue;
  599. }
  600. if ($string[$i] === "\n") {
  601. continue;
  602. }
  603. }
  604. $ret .= $string[$i];
  605. }
  606. return $ret;
  607. }
  608. }
  609. /**
  610. * Processes an entire attribute array for corrections needing multiple values.
  611. *
  612. * Occasionally, a certain attribute will need to be removed and popped onto
  613. * another value. Instead of creating a complex return syntax for
  614. * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
  615. * specialized object and have that do the special work. That is the
  616. * family of HTMLPurifier_AttrTransform.
  617. *
  618. * An attribute transformation can be assigned to run before or after
  619. * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
  620. * more details.
  621. */
  622. abstract class HTMLPurifier_AttrTransform
  623. {
  624. /**
  625. * Abstract: makes changes to the attributes dependent on multiple values.
  626. *
  627. * @param array $attr Assoc array of attributes, usually from
  628. * HTMLPurifier_Token_Tag::$attr
  629. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  630. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  631. * @return array Processed attribute array.
  632. */
  633. abstract public function transform($attr, $config, $context);
  634. /**
  635. * Prepends CSS properties to the style attribute, creating the
  636. * attribute if it doesn't exist.
  637. * @param array &$attr Attribute array to process (passed by reference)
  638. * @param string $css CSS to prepend
  639. */
  640. public function prependCSS(&$attr, $css)
  641. {
  642. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  643. $attr['style'] = $css . $attr['style'];
  644. }
  645. /**
  646. * Retrieves and removes an attribute
  647. * @param array &$attr Attribute array to process (passed by reference)
  648. * @param mixed $key Key of attribute to confiscate
  649. * @return mixed
  650. */
  651. public function confiscateAttr(&$attr, $key)
  652. {
  653. if (!isset($attr[$key])) {
  654. return null;
  655. }
  656. $value = $attr[$key];
  657. unset($attr[$key]);
  658. return $value;
  659. }
  660. }
  661. /**
  662. * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
  663. */
  664. class HTMLPurifier_AttrTypes
  665. {
  666. /**
  667. * Lookup array of attribute string identifiers to concrete implementations.
  668. * @type HTMLPurifier_AttrDef[]
  669. */
  670. protected $info = array();
  671. /**
  672. * Constructs the info array, supplying default implementations for attribute
  673. * types.
  674. */
  675. public function __construct()
  676. {
  677. // XXX This is kind of poor, since we don't actually /clone/
  678. // instances; instead, we use the supplied make() attribute. So,
  679. // the underlying class must know how to deal with arguments.
  680. // With the old implementation of Enum, that ignored its
  681. // arguments when handling a make dispatch, the IAlign
  682. // definition wouldn't work.
  683. // pseudo-types, must be instantiated via shorthand
  684. $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
  685. $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
  686. $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
  687. $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
  688. $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
  689. $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
  690. $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
  691. $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
  692. $this->info['Text'] = new HTMLPurifier_AttrDef_Text();
  693. $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
  694. $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
  695. $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
  696. $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
  697. $this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
  698. $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
  699. // unimplemented aliases
  700. $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
  701. $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
  702. $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
  703. $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
  704. // "proprietary" types
  705. $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
  706. // number is really a positive integer (one or more digits)
  707. // FIXME: ^^ not always, see start and value of list items
  708. $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
  709. }
  710. private static function makeEnum($in)
  711. {
  712. return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
  713. }
  714. /**
  715. * Retrieves a type
  716. * @param string $type String type name
  717. * @return HTMLPurifier_AttrDef Object AttrDef for type
  718. */
  719. public function get($type)
  720. {
  721. // determine if there is any extra info tacked on
  722. if (strpos($type, '#') !== false) {
  723. list($type, $string) = explode('#', $type, 2);
  724. } else {
  725. $string = '';
  726. }
  727. if (!isset($this->info[$type])) {
  728. trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
  729. return;
  730. }
  731. return $this->info[$type]->make($string);
  732. }
  733. /**
  734. * Sets a new implementation for a type
  735. * @param string $type String type name
  736. * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
  737. */
  738. public function set($type, $impl)
  739. {
  740. $this->info[$type] = $impl;
  741. }
  742. }
  743. /**
  744. * Validates the attributes of a token. Doesn't manage required attributes
  745. * very well. The only reason we factored this out was because RemoveForeignElements
  746. * also needed it besides ValidateAttributes.
  747. */
  748. class HTMLPurifier_AttrValidator
  749. {
  750. /**
  751. * Validates the attributes of a token, mutating it as necessary.
  752. * that has valid tokens
  753. * @param HTMLPurifier_Token $token Token to validate.
  754. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  755. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  756. */
  757. public function validateToken($token, $config, $context)
  758. {
  759. $definition = $config->getHTMLDefinition();
  760. $e =& $context->get('ErrorCollector', true);
  761. // initialize IDAccumulator if necessary
  762. $ok =& $context->get('IDAccumulator', true);
  763. if (!$ok) {
  764. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  765. $context->register('IDAccumulator', $id_accumulator);
  766. }
  767. // initialize CurrentToken if necessary
  768. $current_token =& $context->get('CurrentToken', true);
  769. if (!$current_token) {
  770. $context->register('CurrentToken', $token);
  771. }
  772. if (!$token instanceof HTMLPurifier_Token_Start &&
  773. !$token instanceof HTMLPurifier_Token_Empty
  774. ) {
  775. return;
  776. }
  777. // create alias to global definition array, see also $defs
  778. // DEFINITION CALL
  779. $d_defs = $definition->info_global_attr;
  780. // don't update token until the very end, to ensure an atomic update
  781. $attr = $token->attr;
  782. // do global transformations (pre)
  783. // nothing currently utilizes this
  784. foreach ($definition->info_attr_transform_pre as $transform) {
  785. $attr = $transform->transform($o = $attr, $config, $context);
  786. if ($e) {
  787. if ($attr != $o) {
  788. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  789. }
  790. }
  791. }
  792. // do local transformations only applicable to this element (pre)
  793. // ex. <p align="right"> to <p style="text-align:right;">
  794. foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
  795. $attr = $transform->transform($o = $attr, $config, $context);
  796. if ($e) {
  797. if ($attr != $o) {
  798. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  799. }
  800. }
  801. }
  802. // create alias to this element's attribute definition array, see
  803. // also $d_defs (global attribute definition array)
  804. // DEFINITION CALL
  805. $defs = $definition->info[$token->name]->attr;
  806. $attr_key = false;
  807. $context->register('CurrentAttr', $attr_key);
  808. // iterate through all the attribute keypairs
  809. // Watch out for name collisions: $key has previously been used
  810. foreach ($attr as $attr_key => $value) {
  811. // call the definition
  812. if (isset($defs[$attr_key])) {
  813. // there is a local definition defined
  814. if ($defs[$attr_key] === false) {
  815. // We've explicitly been told not to allow this element.
  816. // This is usually when there's a global definition
  817. // that must be overridden.
  818. // Theoretically speaking, we could have a
  819. // AttrDef_DenyAll, but this is faster!
  820. $result = false;
  821. } else {
  822. // validate according to the element's definition
  823. $result = $defs[$attr_key]->validate(
  824. $value,
  825. $config,
  826. $context
  827. );
  828. }
  829. } elseif (isset($d_defs[$attr_key])) {
  830. // there is a global definition defined, validate according
  831. // to the global definition
  832. $result = $d_defs[$attr_key]->validate(
  833. $value,
  834. $config,
  835. $context
  836. );
  837. } else {
  838. // system never heard of the attribute? DELETE!
  839. $result = false;
  840. }
  841. // put the results into effect
  842. if ($result === false || $result === null) {
  843. // this is a generic error message that should replaced
  844. // with more specific ones when possible
  845. if ($e) {
  846. $e->send(E_ERROR, 'AttrValidator: Attribute removed');
  847. }
  848. // remove the attribute
  849. unset($attr[$attr_key]);
  850. } elseif (is_string($result)) {
  851. // generally, if a substitution is happening, there
  852. // was some sort of implicit correction going on. We'll
  853. // delegate it to the attribute classes to say exactly what.
  854. // simple substitution
  855. $attr[$attr_key] = $result;
  856. } else {
  857. // nothing happens
  858. }
  859. // we'd also want slightly more complicated substitution
  860. // involving an array as the return value,
  861. // although we're not sure how colliding attributes would
  862. // resolve (certain ones would be completely overriden,
  863. // others would prepend themselves).
  864. }
  865. $context->destroy('CurrentAttr');
  866. // post transforms
  867. // global (error reporting untested)
  868. foreach ($definition->info_attr_transform_post as $transform) {
  869. $attr = $transform->transform($o = $attr, $config, $context);
  870. if ($e) {
  871. if ($attr != $o) {
  872. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  873. }
  874. }
  875. }
  876. // local (error reporting untested)
  877. foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
  878. $attr = $transform->transform($o = $attr, $config, $context);
  879. if ($e) {
  880. if ($attr != $o) {
  881. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  882. }
  883. }
  884. }
  885. $token->attr = $attr;
  886. // destroy CurrentToken if we made it ourselves
  887. if (!$current_token) {
  888. $context->destroy('CurrentToken');
  889. }
  890. }
  891. }
  892. // constants are slow, so we use as few as possible
  893. if (!defined('HTMLPURIFIER_PREFIX')) {
  894. define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
  895. set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
  896. }
  897. // accomodations for versions earlier than 5.0.2
  898. // borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
  899. if (!defined('PHP_EOL')) {
  900. switch (strtoupper(substr(PHP_OS, 0, 3))) {
  901. case 'WIN':
  902. define('PHP_EOL', "\r\n");
  903. break;
  904. case 'DAR':
  905. define('PHP_EOL', "\r");
  906. break;
  907. default:
  908. define('PHP_EOL', "\n");
  909. }
  910. }
  911. /**
  912. * Bootstrap class that contains meta-functionality for HTML Purifier such as
  913. * the autoload function.
  914. *
  915. * @note
  916. * This class may be used without any other files from HTML Purifier.
  917. */
  918. class HTMLPurifier_Bootstrap
  919. {
  920. /**
  921. * Autoload function for HTML Purifier
  922. * @param string $class Class to load
  923. * @return bool
  924. */
  925. public static function autoload($class)
  926. {
  927. $file = HTMLPurifier_Bootstrap::getPath($class);
  928. if (!$file) {
  929. return false;
  930. }
  931. // Technically speaking, it should be ok and more efficient to
  932. // just do 'require', but Antonio Parraga reports that with
  933. // Zend extensions such as Zend debugger and APC, this invariant
  934. // may be broken. Since we have efficient alternatives, pay
  935. // the cost here and avoid the bug.
  936. require_once HTMLPURIFIER_PREFIX . '/' . $file;
  937. return true;
  938. }
  939. /**
  940. * Returns the path for a specific class.
  941. * @param string $class Class path to get
  942. * @return string
  943. */
  944. public static function getPath($class)
  945. {
  946. if (strncmp('HTMLPurifier', $class, 12) !== 0) {
  947. return false;
  948. }
  949. // Custom implementations
  950. if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
  951. $code = str_replace('_', '-', substr($class, 22));
  952. $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
  953. } else {
  954. $file = str_replace('_', '/', $class) . '.php';
  955. }
  956. if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
  957. return false;
  958. }
  959. return $file;
  960. }
  961. /**
  962. * "Pre-registers" our autoloader on the SPL stack.
  963. */
  964. public static function registerAutoload()
  965. {
  966. $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
  967. if (($funcs = spl_autoload_functions()) === false) {
  968. spl_autoload_register($autoload);
  969. } elseif (function_exists('spl_autoload_unregister')) {
  970. if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
  971. // prepend flag exists, no need for shenanigans
  972. spl_autoload_register($autoload, true, true);
  973. } else {
  974. $buggy = version_compare(PHP_VERSION, '5.2.11', '<');
  975. $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
  976. version_compare(PHP_VERSION, '5.1.0', '>=');
  977. foreach ($funcs as $func) {
  978. if ($buggy && is_array($func)) {
  979. // :TRICKY: There are some compatibility issues and some
  980. // places where we need to error out
  981. $reflector = new ReflectionMethod($func[0], $func[1]);
  982. if (!$reflector->isStatic()) {
  983. throw new Exception(
  984. 'HTML Purifier autoloader registrar is not compatible
  985. with non-static object methods due to PHP Bug #44144;
  986. Please do not use HTMLPurifier.autoload.php (or any
  987. file that includes this file); instead, place the code:
  988. spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
  989. after your own autoloaders.'
  990. );
  991. }
  992. // Suprisingly, spl_autoload_register supports the
  993. // Class::staticMethod callback format, although call_user_func doesn't
  994. if ($compat) {
  995. $func = implode('::', $func);
  996. }
  997. }
  998. spl_autoload_unregister($func);
  999. }
  1000. spl_autoload_register($autoload);
  1001. foreach ($funcs as $func) {
  1002. spl_autoload_register($func);
  1003. }
  1004. }
  1005. }
  1006. }
  1007. }
  1008. /**
  1009. * Super-class for definition datatype objects, implements serialization
  1010. * functions for the class.
  1011. */
  1012. abstract class HTMLPurifier_Definition
  1013. {
  1014. /**
  1015. * Has setup() been called yet?
  1016. * @type bool
  1017. */
  1018. public $setup = false;
  1019. /**
  1020. * If true, write out the final definition object to the cache after
  1021. * setup. This will be true only if all invocations to get a raw
  1022. * definition object are also optimized. This does not cause file
  1023. * system thrashing because on subsequent calls the cached object
  1024. * is used and any writes to the raw definition object are short
  1025. * circuited. See enduser-customize.html for the high-level
  1026. * picture.
  1027. * @type bool
  1028. */
  1029. public $optimized = null;
  1030. /**
  1031. * What type of definition is it?
  1032. * @type string
  1033. */
  1034. public $type;
  1035. /**
  1036. * Sets up the definition object into the final form, something
  1037. * not done by the constructor
  1038. * @param HTMLPurifier_Config $config
  1039. */
  1040. abstract protected function doSetup($config);
  1041. /**
  1042. * Setup function that aborts if already setup
  1043. * @param HTMLPurifier_Config $config
  1044. */
  1045. public function setup($config)
  1046. {
  1047. if ($this->setup) {
  1048. return;
  1049. }
  1050. $this->setup = true;
  1051. $this->doSetup($config);
  1052. }
  1053. }
  1054. /**
  1055. * Defines allowed CSS attributes and what their values are.
  1056. * @see HTMLPurifier_HTMLDefinition
  1057. */
  1058. class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
  1059. {
  1060. public $type = 'CSS';
  1061. /**
  1062. * Assoc array of attribute name to definition object.
  1063. * @type HTMLPurifier_AttrDef[]
  1064. */
  1065. public $info = array();
  1066. /**
  1067. * Constructs the info array. The meat of this class.
  1068. * @param HTMLPurifier_Config $config
  1069. */
  1070. protected function doSetup($config)
  1071. {
  1072. $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
  1073. array('left', 'right', 'center', 'justify'),
  1074. false
  1075. );
  1076. $border_style =
  1077. $this->info['border-bottom-style'] =
  1078. $this->info['border-right-style'] =
  1079. $this->info['border-left-style'] =
  1080. $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
  1081. array(
  1082. 'none',
  1083. 'hidden',
  1084. 'dotted',
  1085. 'dashed',
  1086. 'solid',
  1087. 'double',
  1088. 'groove',
  1089. 'ridge',
  1090. 'inset',
  1091. 'outset'
  1092. ),
  1093. false
  1094. );
  1095. $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
  1096. $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
  1097. array('none', 'left', 'right', 'both'),
  1098. false
  1099. );
  1100. $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
  1101. array('none', 'left', 'right'),
  1102. false
  1103. );
  1104. $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
  1105. array('normal', 'italic', 'oblique'),
  1106. false
  1107. );
  1108. $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
  1109. array('normal', 'small-caps'),
  1110. false
  1111. );
  1112. $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
  1113. array(
  1114. new HTMLPurifier_AttrDef_Enum(array('none')),
  1115. new HTMLPurifier_AttrDef_CSS_URI()
  1116. )
  1117. );
  1118. $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
  1119. array('inside', 'outside'),
  1120. false
  1121. );
  1122. $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
  1123. array(
  1124. 'disc',
  1125. 'circle',
  1126. 'square',
  1127. 'decimal',
  1128. 'lower-roman',
  1129. 'upper-roman',
  1130. 'lower-alpha',
  1131. 'upper-alpha',
  1132. 'none'
  1133. ),
  1134. false
  1135. );
  1136. $this->info['list-style-image'] = $uri_or_none;
  1137. $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
  1138. $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
  1139. array('capitalize', 'uppercase', 'lowercase', 'none'),
  1140. false
  1141. );
  1142. $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1143. $this->info['background-image'] = $uri_or_none;
  1144. $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
  1145. array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
  1146. );
  1147. $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
  1148. array('scroll', 'fixed')
  1149. );
  1150. $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
  1151. $border_color =
  1152. $this->info['border-top-color'] =
  1153. $this->info['border-bottom-color'] =
  1154. $this->info['border-left-color'] =
  1155. $this->info['border-right-color'] =
  1156. $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1157. array(
  1158. new HTMLPurifier_AttrDef_Enum(array('transparent')),
  1159. new HTMLPurifier_AttrDef_CSS_Color()
  1160. )
  1161. );
  1162. $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
  1163. $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
  1164. $border_width =
  1165. $this->info['border-top-width'] =
  1166. $this->info['border-bottom-width'] =
  1167. $this->info['border-left-width'] =
  1168. $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1169. array(
  1170. new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
  1171. new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
  1172. )
  1173. );
  1174. $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
  1175. $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1176. array(
  1177. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1178. new HTMLPurifier_AttrDef_CSS_Length()
  1179. )
  1180. );
  1181. $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1182. array(
  1183. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1184. new HTMLPurifier_AttrDef_CSS_Length()
  1185. )
  1186. );
  1187. $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1188. array(
  1189. new HTMLPurifier_AttrDef_Enum(
  1190. array(
  1191. 'xx-small',
  1192. 'x-small',
  1193. 'small',
  1194. 'medium',
  1195. 'large',
  1196. 'x-large',
  1197. 'xx-large',
  1198. 'larger',
  1199. 'smaller'
  1200. )
  1201. ),
  1202. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1203. new HTMLPurifier_AttrDef_CSS_Length()
  1204. )
  1205. );
  1206. $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1207. array(
  1208. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1209. new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
  1210. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1211. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1212. )
  1213. );
  1214. $margin =
  1215. $this->info['margin-top'] =
  1216. $this->info['margin-bottom'] =
  1217. $this->info['margin-left'] =
  1218. $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1219. array(
  1220. new HTMLPurifier_AttrDef_CSS_Length(),
  1221. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1222. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1223. )
  1224. );
  1225. $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
  1226. // non-negative
  1227. $padding =
  1228. $this->info['padding-top'] =
  1229. $this->info['padding-bottom'] =
  1230. $this->info['padding-left'] =
  1231. $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1232. array(
  1233. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1234. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1235. )
  1236. );
  1237. $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
  1238. $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1239. array(
  1240. new HTMLPurifier_AttrDef_CSS_Length(),
  1241. new HTMLPurifier_AttrDef_CSS_Percentage()
  1242. )
  1243. );
  1244. $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
  1245. array(
  1246. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1247. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  1248. new HTMLPurifier_AttrDef_Enum(array('auto', 'initial', 'inherit'))
  1249. )
  1250. );
  1251. $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
  1252. array(
  1253. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1254. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  1255. new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
  1256. )
  1257. );
  1258. $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
  1259. array(
  1260. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1261. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  1262. new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
  1263. )
  1264. );
  1265. $max = $config->get('CSS.MaxImgLength');
  1266. $this->info['width'] =
  1267. $this->info['height'] =
  1268. $max === null ?
  1269. $trusted_wh :
  1270. new HTMLPurifier_AttrDef_Switch(
  1271. 'img',
  1272. // For img tags:
  1273. new HTMLPurifier_AttrDef_CSS_Composite(
  1274. array(
  1275. new HTMLPurifier_AttrDef_CSS_Length('0', $max),
  1276. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1277. )
  1278. ),
  1279. // For everyone else:
  1280. $trusted_wh
  1281. );
  1282. $this->info['min-width'] =
  1283. $this->info['min-height'] =
  1284. $max === null ?
  1285. $trusted_min_wh :
  1286. new HTMLPurifier_AttrDef_Switch(
  1287. 'img',
  1288. // For img tags:
  1289. new HTMLPurifier_AttrDef_CSS_Composite(
  1290. array(
  1291. new HTMLPurifier_AttrDef_CSS_Length('0', $max),
  1292. new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
  1293. )
  1294. ),
  1295. // For everyone else:
  1296. $trusted_min_wh
  1297. );
  1298. $this->info['max-width'] =
  1299. $this->info['max-height'] =
  1300. $max === null ?
  1301. $trusted_max_wh :
  1302. new HTMLPurifier_AttrDef_Switch(
  1303. 'img',
  1304. // For img tags:
  1305. new HTMLPurifier_AttrDef_CSS_Composite(
  1306. array(
  1307. new HTMLPurifier_AttrDef_CSS_Length('0', $max),
  1308. new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
  1309. )
  1310. ),
  1311. // For everyone else:
  1312. $trusted_max_wh
  1313. );
  1314. $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
  1315. $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
  1316. // this could use specialized code
  1317. $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
  1318. array(
  1319. 'normal',
  1320. 'bold',
  1321. 'bolder',
  1322. 'lighter',
  1323. '100',
  1324. '200',
  1325. '300',
  1326. '400',
  1327. '500',
  1328. '600',
  1329. '700',
  1330. '800',
  1331. '900'
  1332. ),
  1333. false
  1334. );
  1335. // MUST be called after other font properties, as it references
  1336. // a CSSDefinition object
  1337. $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
  1338. // same here
  1339. $this->info['border'] =
  1340. $this->info['border-bottom'] =
  1341. $this->info['border-top'] =
  1342. $this->info['border-left'] =
  1343. $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
  1344. $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
  1345. array('collapse', 'separate')
  1346. );
  1347. $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
  1348. array('top', 'bottom')
  1349. );
  1350. $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
  1351. array('auto', 'fixed')
  1352. );
  1353. $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1354. array(
  1355. new HTMLPurifier_AttrDef_Enum(
  1356. array(
  1357. 'baseline',
  1358. 'sub',
  1359. 'super',
  1360. 'top',
  1361. 'text-top',
  1362. 'middle',
  1363. 'bottom',
  1364. 'text-bottom'
  1365. )
  1366. ),
  1367. new HTMLPurifier_AttrDef_CSS_Length(),
  1368. new HTMLPurifier_AttrDef_CSS_Percentage()
  1369. )
  1370. );
  1371. $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
  1372. // These CSS properties don't work on many browsers, but we live
  1373. // in THE FUTURE!
  1374. $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
  1375. array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
  1376. );
  1377. if ($config->get('CSS.Proprietary')) {
  1378. $this->doSetupProprietary($config);
  1379. }
  1380. if ($config->get('CSS.AllowTricky')) {
  1381. $this->doSetupTricky($config);
  1382. }
  1383. if ($config->get('CSS.Trusted')) {
  1384. $this->doSetupTrusted($config);
  1385. }
  1386. $allow_important = $config->get('CSS.AllowImportant');
  1387. // wrap all attr-defs with decorator that handles !important
  1388. foreach ($this->info as $k => $v) {
  1389. $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
  1390. }
  1391. $this->setupConfigStuff($config);
  1392. }
  1393. /**
  1394. * @param HTMLPurifier_Config $config
  1395. */
  1396. protected function doSetupProprietary($config)
  1397. {
  1398. // Internet Explorer only scrollbar colors
  1399. $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1400. $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1401. $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1402. $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1403. $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1404. $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1405. // vendor specific prefixes of opacity
  1406. $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1407. $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1408. // only opacity, for now
  1409. $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
  1410. // more CSS3
  1411. $this->info['page-break-after'] =
  1412. $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
  1413. array(
  1414. 'auto',
  1415. 'always',
  1416. 'avoid',
  1417. 'left',
  1418. 'right'
  1419. )
  1420. );
  1421. $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
  1422. $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
  1423. array(
  1424. new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
  1425. new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
  1426. ));
  1427. $this->info['border-top-left-radius'] =
  1428. $this->info['border-top-right-radius'] =
  1429. $this->info['border-bottom-right-radius'] =
  1430. $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
  1431. // TODO: support SLASH syntax
  1432. $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
  1433. }
  1434. /**
  1435. * @param HTMLPurifier_Config $config
  1436. */
  1437. protected function doSetupTricky($config)
  1438. {
  1439. $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
  1440. array(
  1441. 'inline',
  1442. 'block',
  1443. 'list-item',
  1444. 'run-in',
  1445. 'compact',
  1446. 'marker',
  1447. 'table',
  1448. 'inline-block',
  1449. 'inline-table',
  1450. 'table-row-group',
  1451. 'table-header-group',
  1452. 'table-footer-group',
  1453. 'table-row',
  1454. 'table-column-group',
  1455. 'table-column',
  1456. 'table-cell',
  1457. 'table-caption',
  1458. 'none'
  1459. )
  1460. );
  1461. $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
  1462. array('visible', 'hidden', 'collapse')
  1463. );
  1464. $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
  1465. $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1466. }
  1467. /**
  1468. * @param HTMLPurifier_Config $config
  1469. */
  1470. protected function doSetupTrusted($config)
  1471. {
  1472. $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
  1473. array('static', 'relative', 'absolute', 'fixed')
  1474. );
  1475. $this->info['top'] =
  1476. $this->info['left'] =
  1477. $this->info['right'] =
  1478. $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1479. array(
  1480. new HTMLPurifier_AttrDef_CSS_Length(),
  1481. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1482. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1483. )
  1484. );
  1485. $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1486. array(
  1487. new HTMLPurifier_AttrDef_Integer(),
  1488. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1489. )
  1490. );
  1491. }
  1492. /**
  1493. * Performs extra config-based processing. Based off of
  1494. * HTMLPurifier_HTMLDefinition.
  1495. * @param HTMLPurifier_Config $config
  1496. * @todo Refactor duplicate elements into common class (probably using
  1497. * composition, not inheritance).
  1498. */
  1499. protected function setupConfigStuff($config)
  1500. {
  1501. // setup allowed elements
  1502. $support = "(for information on implementing this, see the " .
  1503. "support forums) ";
  1504. $allowed_properties = $config->get('CSS.AllowedProperties');
  1505. if ($allowed_properties !== null) {
  1506. foreach ($this->info as $name => $d) {
  1507. if (!isset($allowed_properties[$name])) {
  1508. unset($this->info[$name]);
  1509. }
  1510. unset($allowed_properties[$name]);
  1511. }
  1512. // emit errors
  1513. foreach ($allowed_properties as $name => $d) {
  1514. // :TODO: Is this htmlspecialchars() call really necessary?
  1515. $name = htmlspecialchars($name);
  1516. trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
  1517. }
  1518. }
  1519. $forbidden_properties = $config->get('CSS.ForbiddenProperties');
  1520. if ($forbidden_properties !== null) {
  1521. foreach ($this->info as $name => $d) {
  1522. if (isset($forbidden_properties[$name])) {
  1523. unset($this->info[$name]);
  1524. }
  1525. }
  1526. }
  1527. }
  1528. }
  1529. /**
  1530. * Defines allowed child nodes and validates nodes against it.
  1531. */
  1532. abstract class HTMLPurifier_ChildDef
  1533. {
  1534. /**
  1535. * Type of child definition, usually right-most part of class name lowercase.
  1536. * Used occasionally in terms of context.
  1537. * @type string
  1538. */
  1539. public $type;
  1540. /**
  1541. * Indicates whether or not an empty array of children is okay.
  1542. *
  1543. * This is necessary for redundant checking when changes affecting
  1544. * a child node may cause a parent node to now be disallowed.
  1545. * @type bool
  1546. */
  1547. public $allow_empty;
  1548. /**
  1549. * Lookup array of all elements that this definition could possibly allow.
  1550. * @type array
  1551. */
  1552. public $elements = array();
  1553. /**
  1554. * Get lookup of tag names that should not close this element automatically.
  1555. * All other elements will do so.
  1556. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1557. * @return array
  1558. */
  1559. public function getAllowedElements($config)
  1560. {
  1561. return $this->elements;
  1562. }
  1563. /**
  1564. * Validates nodes according to definition and returns modification.
  1565. *
  1566. * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
  1567. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1568. * @param HTMLPurifier_Context $context HTMLPurifier_Context object
  1569. * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
  1570. */
  1571. abstract public function validateChildren($children, $config, $context);
  1572. }
  1573. /**
  1574. * Configuration object that triggers customizable behavior.
  1575. *
  1576. * @warning This class is strongly defined: that means that the class
  1577. * will fail if an undefined directive is retrieved or set.
  1578. *
  1579. * @note Many classes that could (although many times don't) use the
  1580. * configuration object make it a mandatory parameter. This is
  1581. * because a configuration object should always be forwarded,
  1582. * otherwise, you run the risk of missing a parameter and then
  1583. * being stumped when a configuration directive doesn't work.
  1584. *
  1585. * @todo Reconsider some of the public member variables
  1586. */
  1587. class HTMLPurifier_Config
  1588. {
  1589. /**
  1590. * HTML Purifier's version
  1591. * @type string
  1592. */
  1593. public $version = '4.12.0';
  1594. /**
  1595. * Whether or not to automatically finalize
  1596. * the object if a read operation is done.
  1597. * @type bool
  1598. */
  1599. public $autoFinalize = true;
  1600. // protected member variables
  1601. /**
  1602. * Namespace indexed array of serials for specific namespaces.
  1603. * @see getSerial() for more info.
  1604. * @type string[]
  1605. */
  1606. protected $serials = array();
  1607. /**
  1608. * Serial for entire configuration object.
  1609. * @type string
  1610. */
  1611. protected $serial;
  1612. /**
  1613. * Parser for variables.
  1614. * @type HTMLPurifier_VarParser_Flexible
  1615. */
  1616. protected $parser = null;
  1617. /**
  1618. * Reference HTMLPurifier_ConfigSchema for value checking.
  1619. * @type HTMLPurifier_ConfigSchema
  1620. * @note This is public for introspective purposes. Please don't
  1621. * abuse!
  1622. */
  1623. public $def;
  1624. /**
  1625. * Indexed array of definitions.
  1626. * @type HTMLPurifier_Definition[]
  1627. */
  1628. protected $definitions;
  1629. /**
  1630. * Whether or not config is finalized.
  1631. * @type bool
  1632. */
  1633. protected $finalized = false;
  1634. /**
  1635. * Property list containing configuration directives.
  1636. * @type array
  1637. */
  1638. protected $plist;
  1639. /**
  1640. * Whether or not a set is taking place due to an alias lookup.
  1641. * @type bool
  1642. */
  1643. private $aliasMode;
  1644. /**
  1645. * Set to false if you do not want line and file numbers in errors.
  1646. * (useful when unit testing). This will also compress some errors
  1647. * and exceptions.
  1648. * @type bool
  1649. */
  1650. public $chatty = true;
  1651. /**
  1652. * Current lock; only gets to this namespace are allowed.
  1653. * @type string
  1654. */
  1655. private $lock;
  1656. /**
  1657. * Constructor
  1658. * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
  1659. * what directives are allowed.
  1660. * @param HTMLPurifier_PropertyList $parent
  1661. */
  1662. public function __construct($definition, $parent = null)
  1663. {
  1664. $parent = $parent ? $parent : $definition->defaultPlist;
  1665. $this->plist = new HTMLPurifier_PropertyList($parent);
  1666. $this->def = $definition; // keep a copy around for checking
  1667. $this->parser = new HTMLPurifier_VarParser_Flexible();
  1668. }
  1669. /**
  1670. * Convenience constructor that creates a config object based on a mixed var
  1671. * @param mixed $config Variable that defines the state of the config
  1672. * object. Can be: a HTMLPurifier_Config() object,
  1673. * an array of directives based on loadArray(),
  1674. * or a string filename of an ini file.
  1675. * @param HTMLPurifier_ConfigSchema $schema Schema object
  1676. * @return HTMLPurifier_Config Configured object
  1677. */
  1678. public static function create($config, $schema = null)
  1679. {
  1680. if ($config instanceof HTMLPurifier_Config) {
  1681. // pass-through
  1682. return $config;
  1683. }
  1684. if (!$schema) {
  1685. $ret = HTMLPurifier_Config::createDefault();
  1686. } else {
  1687. $ret = new HTMLPurifier_Config($schema);
  1688. }
  1689. if (is_string($config)) {
  1690. $ret->loadIni($config);
  1691. } elseif (is_array($config)) $ret->loadArray($config);
  1692. return $ret;
  1693. }
  1694. /**
  1695. * Creates a new config object that inherits from a previous one.
  1696. * @param HTMLPurifier_Config $config Configuration object to inherit from.
  1697. * @return HTMLPurifier_Config object with $config as its parent.
  1698. */
  1699. public static function inherit(HTMLPurifier_Config $config)
  1700. {
  1701. return new HTMLPurifier_Config($config->def, $config->plist);
  1702. }
  1703. /**
  1704. * Convenience constructor that creates a default configuration object.
  1705. * @return HTMLPurifier_Config default object.
  1706. */
  1707. public static function createDefault()
  1708. {
  1709. $definition = HTMLPurifier_ConfigSchema::instance();
  1710. $config = new HTMLPurifier_Config($definition);
  1711. return $config;
  1712. }
  1713. /**
  1714. * Retrieves a value from the configuration.
  1715. *
  1716. * @param string $key String key
  1717. * @param mixed $a
  1718. *
  1719. * @return mixed
  1720. */
  1721. public function get($key, $a = null)
  1722. {
  1723. if ($a !== null) {
  1724. $this->triggerError(
  1725. "Using deprecated API: use \$config->get('$key.$a') instead",
  1726. E_USER_WARNING
  1727. );
  1728. $key = "$key.$a";
  1729. }
  1730. if (!$this->finalized) {
  1731. $this->autoFinalize();
  1732. }
  1733. if (!isset($this->def->info[$key])) {
  1734. // can't add % due to SimpleTest bug
  1735. $this->triggerError(
  1736. 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
  1737. E_USER_WARNING
  1738. );
  1739. return;
  1740. }
  1741. if (isset($this->def->info[$key]->isAlias)) {
  1742. $d = $this->def->info[$key];
  1743. $this->triggerError(
  1744. 'Cannot get value from aliased directive, use real name ' . $d->key,
  1745. E_USER_ERROR
  1746. );
  1747. return;
  1748. }
  1749. if ($this->lock) {
  1750. list($ns) = explode('.', $key);
  1751. if ($ns !== $this->lock) {
  1752. $this->triggerError(
  1753. 'Cannot get value of namespace ' . $ns . ' when lock for ' .
  1754. $this->lock .
  1755. ' is active, this probably indicates a Definition setup method ' .
  1756. 'is accessing directives that are not within its namespace',
  1757. E_USER_ERROR
  1758. );
  1759. return;
  1760. }
  1761. }
  1762. return $this->plist->get($key);
  1763. }
  1764. /**
  1765. * Retrieves an array of directives to values from a given namespace
  1766. *
  1767. * @param string $namespace String namespace
  1768. *
  1769. * @return array
  1770. */
  1771. public function getBatch($namespace)
  1772. {
  1773. if (!$this->finalized) {
  1774. $this->autoFinalize();
  1775. }
  1776. $full = $this->getAll();
  1777. if (!isset($full[$namespace])) {
  1778. $this->triggerError(
  1779. 'Cannot retrieve undefined namespace ' .
  1780. htmlspecialchars($namespace),
  1781. E_USER_WARNING
  1782. );
  1783. return;
  1784. }
  1785. return $full[$namespace];
  1786. }
  1787. /**
  1788. * Returns a SHA-1 signature of a segment of the configuration object
  1789. * that uniquely identifies that particular configuration
  1790. *
  1791. * @param string $namespace Namespace to get serial for
  1792. *
  1793. * @return string
  1794. * @note Revision is handled specially and is removed from the batch
  1795. * before processing!
  1796. */
  1797. public function getBatchSerial($namespace)
  1798. {
  1799. if (empty($this->serials[$namespace])) {
  1800. $batch = $this->getBatch($namespace);
  1801. unset($batch['DefinitionRev']);
  1802. $this->serials[$namespace] = sha1(serialize($batch));
  1803. }
  1804. return $this->serials[$namespace];
  1805. }
  1806. /**
  1807. * Returns a SHA-1 signature for the entire configuration object
  1808. * that uniquely identifies that particular configuration
  1809. *
  1810. * @return string
  1811. */
  1812. public function getSerial()
  1813. {
  1814. if (empty($this->serial)) {
  1815. $this->serial = sha1(serialize($this->getAll()));
  1816. }
  1817. return $this->serial;
  1818. }
  1819. /**
  1820. * Retrieves all directives, organized by namespace
  1821. *
  1822. * @warning This is a pretty inefficient function, avoid if you can
  1823. */
  1824. public function getAll()
  1825. {
  1826. if (!$this->finalized) {
  1827. $this->autoFinalize();
  1828. }
  1829. $ret = array();
  1830. foreach ($this->plist->squash() as $name => $value) {
  1831. list($ns, $key) = explode('.', $name, 2);
  1832. $ret[$ns][$key] = $value;
  1833. }
  1834. return $ret;
  1835. }
  1836. /**
  1837. * Sets a value to configuration.
  1838. *
  1839. * @param string $key key
  1840. * @param mixed $value value
  1841. * @param mixed $a
  1842. */
  1843. public function set($key, $value, $a = null)
  1844. {
  1845. if (strpos($key, '.') === false) {
  1846. $namespace = $key;
  1847. $directive = $value;
  1848. $value = $a;
  1849. $key = "$key.$directive";
  1850. $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
  1851. } else {
  1852. list($namespace) = explode('.', $key);
  1853. }
  1854. if ($this->isFinalized('Cannot set directive after finalization')) {
  1855. return;
  1856. }
  1857. if (!isset($this->def->info[$key])) {
  1858. $this->triggerError(
  1859. 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
  1860. E_USER_WARNING
  1861. );
  1862. return;
  1863. }
  1864. $def = $this->def->info[$key];
  1865. if (isset($def->isAlias)) {
  1866. if ($this->aliasMode) {
  1867. $this->triggerError(
  1868. 'Double-aliases not allowed, please fix '.
  1869. 'ConfigSchema bug with' . $key,
  1870. E_USER_ERROR
  1871. );
  1872. return;
  1873. }
  1874. $this->aliasMode = true;
  1875. $this->set($def->key, $value);
  1876. $this->aliasMode = false;
  1877. $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
  1878. return;
  1879. }
  1880. // Raw type might be negative when using the fully optimized form
  1881. // of stdClass, which indicates allow_null == true
  1882. $rtype = is_int($def) ? $def : $def->type;
  1883. if ($rtype < 0) {
  1884. $type = -$rtype;
  1885. $allow_null = true;
  1886. } else {
  1887. $type = $rtype;
  1888. $allow_null = isset($def->allow_null);
  1889. }
  1890. try {
  1891. $value = $this->parser->parse($value, $type, $allow_null);
  1892. } catch (HTMLPurifier_VarParserException $e) {
  1893. $this->triggerError(
  1894. 'Value for ' . $key . ' is of invalid type, should be ' .
  1895. HTMLPurifier_VarParser::getTypeName($type),
  1896. E_USER_WARNING
  1897. );
  1898. return;
  1899. }
  1900. if (is_string($value) && is_object($def)) {
  1901. // resolve value alias if defined
  1902. if (isset($def->aliases[$value])) {
  1903. $value = $def->aliases[$value];
  1904. }
  1905. // check to see if the value is allowed
  1906. if (isset($def->allowed) && !isset($def->allowed[$value])) {
  1907. $this->triggerError(
  1908. 'Value not supported, valid values are: ' .
  1909. $this->_listify($def->allowed),
  1910. E_USER_WARNING
  1911. );
  1912. return;
  1913. }
  1914. }
  1915. $this->plist->set($key, $value);
  1916. // reset definitions if the directives they depend on changed
  1917. // this is a very costly process, so it's discouraged
  1918. // with finalization
  1919. if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
  1920. $this->definitions[$namespace] = null;
  1921. }
  1922. $this->serials[$namespace] = false;
  1923. }
  1924. /**
  1925. * Convenience function for error reporting
  1926. *
  1927. * @param array $lookup
  1928. *
  1929. * @return string
  1930. */
  1931. private function _listify($lookup)
  1932. {
  1933. $list = array();
  1934. foreach ($lookup as $name => $b) {
  1935. $list[] = $name;
  1936. }
  1937. return implode(', ', $list);
  1938. }
  1939. /**
  1940. * Retrieves object reference to the HTML definition.
  1941. *
  1942. * @param bool $raw Return a copy that has not been setup yet. Must be
  1943. * called before it's been setup, otherwise won't work.
  1944. * @param bool $optimized If true, this method may return null, to
  1945. * indicate that a cached version of the modified
  1946. * definition object is available and no further edits
  1947. * are necessary. Consider using
  1948. * maybeGetRawHTMLDefinition, which is more explicitly
  1949. * named, instead.
  1950. *
  1951. * @return HTMLPurifier_HTMLDefinition
  1952. */
  1953. public function getHTMLDefinition($raw = false, $optimized = false)
  1954. {
  1955. return $this->getDefinition('HTML', $raw, $optimized);
  1956. }
  1957. /**
  1958. * Retrieves object reference to the CSS definition
  1959. *
  1960. * @param bool $raw Return a copy that has not been setup yet. Must be
  1961. * called before it's been setup, otherwise won't work.
  1962. * @param bool $optimized If true, this method may return null, to
  1963. * indicate that a cached version of the modified
  1964. * definition object is available and no further edits
  1965. * are necessary. Consider using
  1966. * maybeGetRawCSSDefinition, which is more explicitly
  1967. * named, instead.
  1968. *
  1969. * @return HTMLPurifier_CSSDefinition
  1970. */
  1971. public function getCSSDefinition($raw = false, $optimized = false)
  1972. {
  1973. return $this->getDefinition('CSS', $raw, $optimized);
  1974. }
  1975. /**
  1976. * Retrieves object reference to the URI definition
  1977. *
  1978. * @param bool $raw Return a copy that has not been setup yet. Must be
  1979. * called before it's been setup, otherwise won't work.
  1980. * @param bool $optimized If true, this method may return null, to
  1981. * indicate that a cached version of the modified
  1982. * definition object is available and no further edits
  1983. * are necessary. Consider using
  1984. * maybeGetRawURIDefinition, which is more explicitly
  1985. * named, instead.
  1986. *
  1987. * @return HTMLPurifier_URIDefinition
  1988. */
  1989. public function getURIDefinition($raw = false, $optimized = false)
  1990. {
  1991. return $this->getDefinition('URI', $raw, $optimized);
  1992. }
  1993. /**
  1994. * Retrieves a definition
  1995. *
  1996. * @param string $type Type of definition: HTML, CSS, etc
  1997. * @param bool $raw Whether or not definition should be returned raw
  1998. * @param bool $optimized Only has an effect when $raw is true. Whether
  1999. * or not to return null if the result is already present in
  2000. * the cache. This is off by default for backwards
  2001. * compatibility reasons, but you need to do things this
  2002. * way in order to ensure that caching is done properly.
  2003. * Check out enduser-customize.html for more details.
  2004. * We probably won't ever change this default, as much as the
  2005. * maybe semantics is the "right thing to do."
  2006. *
  2007. * @throws HTMLPurifier_Exception
  2008. * @return HTMLPurifier_Definition
  2009. */
  2010. public function getDefinition($type, $raw = false, $optimized = false)
  2011. {
  2012. if ($optimized && !$raw) {
  2013. throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
  2014. }
  2015. if (!$this->finalized) {
  2016. $this->autoFinalize();
  2017. }
  2018. // temporarily suspend locks, so we can handle recursive definition calls
  2019. $lock = $this->lock;
  2020. $this->lock = null;
  2021. $factory = HTMLPurifier_DefinitionCacheFactory::instance();
  2022. $cache = $factory->create($type, $this);
  2023. $this->lock = $lock;
  2024. if (!$raw) {
  2025. // full definition
  2026. // ---------------
  2027. // check if definition is in memory
  2028. if (!empty($this->definitions[$type])) {
  2029. $def = $this->definitions[$type];
  2030. // check if the definition is setup
  2031. if ($def->setup) {
  2032. return $def;
  2033. } else {
  2034. $def->setup($this);
  2035. if ($def->optimized) {
  2036. $cache->add($def, $this);
  2037. }
  2038. return $def;
  2039. }
  2040. }
  2041. // check if definition is in cache
  2042. $def = $cache->get($this);
  2043. if ($def) {
  2044. // definition in cache, save to memory and return it
  2045. $this->definitions[$type] = $def;
  2046. return $def;
  2047. }
  2048. // initialize it
  2049. $def = $this->initDefinition($type);
  2050. // set it up
  2051. $this->lock = $type;
  2052. $def->setup($this);
  2053. $this->lock = null;
  2054. // save in cache
  2055. $cache->add($def, $this);
  2056. // return it
  2057. return $def;
  2058. } else {
  2059. // raw definition
  2060. // --------------
  2061. // check preconditions
  2062. $def = null;
  2063. if ($optimized) {
  2064. if (is_null($this->get($type . '.DefinitionID'))) {
  2065. // fatally error out if definition ID not set
  2066. throw new HTMLPurifier_Exception(
  2067. "Cannot retrieve raw version without specifying %$type.DefinitionID"
  2068. );
  2069. }
  2070. }
  2071. if (!empty($this->definitions[$type])) {
  2072. $def = $this->definitions[$type];
  2073. if ($def->setup && !$optimized) {
  2074. $extra = $this->chatty ?
  2075. " (try moving this code block earlier in your initialization)" :
  2076. "";
  2077. throw new HTMLPurifier_Exception(
  2078. "Cannot retrieve raw definition after it has already been setup" .
  2079. $extra
  2080. );
  2081. }
  2082. if ($def->optimized === null) {
  2083. $extra = $this->chatty ? " (try flushing your cache)" : "";
  2084. throw new HTMLPurifier_Exception(
  2085. "Optimization status of definition is unknown" . $extra
  2086. );
  2087. }
  2088. if ($def->optimized !== $optimized) {
  2089. $msg = $optimized ? "optimized" : "unoptimized";
  2090. $extra = $this->chatty ?
  2091. " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
  2092. : "";
  2093. throw new HTMLPurifier_Exception(
  2094. "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
  2095. );
  2096. }
  2097. }
  2098. // check if definition was in memory
  2099. if ($def) {
  2100. if ($def->setup) {
  2101. // invariant: $optimized === true (checked above)
  2102. return null;
  2103. } else {
  2104. return $def;
  2105. }
  2106. }
  2107. // if optimized, check if definition was in cache
  2108. // (because we do the memory check first, this formulation
  2109. // is prone to cache slamming, but I think
  2110. // guaranteeing that either /all/ of the raw
  2111. // setup code or /none/ of it is run is more important.)
  2112. if ($optimized) {
  2113. // This code path only gets run once; once we put
  2114. // something in $definitions (which is guaranteed by the
  2115. // trailing code), we always short-circuit above.
  2116. $def = $cache->get($this);
  2117. if ($def) {
  2118. // save the full definition for later, but don't
  2119. // return it yet
  2120. $this->definitions[$type] = $def;
  2121. return null;
  2122. }
  2123. }
  2124. // check invariants for creation
  2125. if (!$optimized) {
  2126. if (!is_null($this->get($type . '.DefinitionID'))) {
  2127. if ($this->chatty) {
  2128. $this->triggerError(
  2129. 'Due to a documentation error in previous version of HTML Purifier, your ' .
  2130. 'definitions are not being cached. If this is OK, you can remove the ' .
  2131. '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
  2132. 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
  2133. 'value is null before making any edits (if it is null, that means that a ' .
  2134. 'cached version is available, and no raw operations are necessary). See ' .
  2135. '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
  2136. 'Customize</a> for more details',
  2137. E_USER_WARNING
  2138. );
  2139. } else {
  2140. $this->triggerError(
  2141. "Useless DefinitionID declaration",
  2142. E_USER_WARNING
  2143. );
  2144. }
  2145. }
  2146. }
  2147. // initialize it
  2148. $def = $this->initDefinition($type);
  2149. $def->optimized = $optimized;
  2150. return $def;
  2151. }
  2152. throw new HTMLPurifier_Exception("The impossible happened!");
  2153. }
  2154. /**
  2155. * Initialise definition
  2156. *
  2157. * @param string $type What type of definition to create
  2158. *
  2159. * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
  2160. * @throws HTMLPurifier_Exception
  2161. */
  2162. private function initDefinition($type)
  2163. {
  2164. // quick checks failed, let's create the object
  2165. if ($type == 'HTML') {
  2166. $def = new HTMLPurifier_HTMLDefinition();
  2167. } elseif ($type == 'CSS') {
  2168. $def = new HTMLPurifier_CSSDefinition();
  2169. } elseif ($type == 'URI') {
  2170. $def = new HTMLPurifier_URIDefinition();
  2171. } else {
  2172. throw new HTMLPurifier_Exception(
  2173. "Definition of $type type not supported"
  2174. );
  2175. }
  2176. $this->definitions[$type] = $def;
  2177. return $def;
  2178. }
  2179. public function maybeGetRawDefinition($name)
  2180. {
  2181. return $this->getDefinition($name, true, true);
  2182. }
  2183. /**
  2184. * @return HTMLPurifier_HTMLDefinition
  2185. */
  2186. public function maybeGetRawHTMLDefinition()
  2187. {
  2188. return $this->getDefinition('HTML', true, true);
  2189. }
  2190. /**
  2191. * @return HTMLPurifier_CSSDefinition
  2192. */
  2193. public function maybeGetRawCSSDefinition()
  2194. {
  2195. return $this->getDefinition('CSS', true, true);
  2196. }
  2197. /**
  2198. * @return HTMLPurifier_URIDefinition
  2199. */
  2200. public function maybeGetRawURIDefinition()
  2201. {
  2202. return $this->getDefinition('URI', true, true);
  2203. }
  2204. /**
  2205. * Loads configuration values from an array with the following structure:
  2206. * Namespace.Directive => Value
  2207. *
  2208. * @param array $config_array Configuration associative array
  2209. */
  2210. public function loadArray($config_array)
  2211. {
  2212. if ($this->isFinalized('Cannot load directives after finalization')) {
  2213. return;
  2214. }
  2215. foreach ($config_array as $key => $value) {
  2216. $key = str_replace('_', '.', $key);
  2217. if (strpos($key, '.') !== false) {
  2218. $this->set($key, $value);
  2219. } else {
  2220. $namespace = $key;
  2221. $namespace_values = $value;
  2222. foreach ($namespace_values as $directive => $value2) {
  2223. $this->set($namespace .'.'. $directive, $value2);
  2224. }
  2225. }
  2226. }
  2227. }
  2228. /**
  2229. * Returns a list of array(namespace, directive) for all directives
  2230. * that are allowed in a web-form context as per an allowed
  2231. * namespaces/directives list.
  2232. *
  2233. * @param array $allowed List of allowed namespaces/directives
  2234. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2235. *
  2236. * @return array
  2237. */
  2238. public static function getAllowedDirectivesForForm($allowed, $schema = null)
  2239. {
  2240. if (!$schema) {
  2241. $schema = HTMLPurifier_ConfigSchema::instance();
  2242. }
  2243. if ($allowed !== true) {
  2244. if (is_string($allowed)) {
  2245. $allowed = array($allowed);
  2246. }
  2247. $allowed_ns = array();
  2248. $allowed_directives = array();
  2249. $blacklisted_directives = array();
  2250. foreach ($allowed as $ns_or_directive) {
  2251. if (strpos($ns_or_directive, '.') !== false) {
  2252. // directive
  2253. if ($ns_or_directive[0] == '-') {
  2254. $blacklisted_directives[substr($ns_or_directive, 1)] = true;
  2255. } else {
  2256. $allowed_directives[$ns_or_directive] = true;
  2257. }
  2258. } else {
  2259. // namespace
  2260. $allowed_ns[$ns_or_directive] = true;
  2261. }
  2262. }
  2263. }
  2264. $ret = array();
  2265. foreach ($schema->info as $key => $def) {
  2266. list($ns, $directive) = explode('.', $key, 2);
  2267. if ($allowed !== true) {
  2268. if (isset($blacklisted_directives["$ns.$directive"])) {
  2269. continue;
  2270. }
  2271. if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
  2272. continue;
  2273. }
  2274. }
  2275. if (isset($def->isAlias)) {
  2276. continue;
  2277. }
  2278. if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
  2279. continue;
  2280. }
  2281. $ret[] = array($ns, $directive);
  2282. }
  2283. return $ret;
  2284. }
  2285. /**
  2286. * Loads configuration values from $_GET/$_POST that were posted
  2287. * via ConfigForm
  2288. *
  2289. * @param array $array $_GET or $_POST array to import
  2290. * @param string|bool $index Index/name that the config variables are in
  2291. * @param array|bool $allowed List of allowed namespaces/directives
  2292. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2293. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2294. *
  2295. * @return mixed
  2296. */
  2297. public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2298. {
  2299. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
  2300. $config = HTMLPurifier_Config::create($ret, $schema);
  2301. return $config;
  2302. }
  2303. /**
  2304. * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
  2305. *
  2306. * @param array $array $_GET or $_POST array to import
  2307. * @param string|bool $index Index/name that the config variables are in
  2308. * @param array|bool $allowed List of allowed namespaces/directives
  2309. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2310. */
  2311. public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
  2312. {
  2313. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
  2314. $this->loadArray($ret);
  2315. }
  2316. /**
  2317. * Prepares an array from a form into something usable for the more
  2318. * strict parts of HTMLPurifier_Config
  2319. *
  2320. * @param array $array $_GET or $_POST array to import
  2321. * @param string|bool $index Index/name that the config variables are in
  2322. * @param array|bool $allowed List of allowed namespaces/directives
  2323. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2324. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2325. *
  2326. * @return array
  2327. */
  2328. public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2329. {
  2330. if ($index !== false) {
  2331. $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
  2332. }
  2333. $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
  2334. $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
  2335. $ret = array();
  2336. foreach ($allowed as $key) {
  2337. list($ns, $directive) = $key;
  2338. $skey = "$ns.$directive";
  2339. if (!empty($array["Null_$skey"])) {
  2340. $ret[$ns][$directive] = null;
  2341. continue;
  2342. }
  2343. if (!isset($array[$skey])) {
  2344. continue;
  2345. }
  2346. $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
  2347. $ret[$ns][$directive] = $value;
  2348. }
  2349. return $ret;
  2350. }
  2351. /**
  2352. * Loads configuration values from an ini file
  2353. *
  2354. * @param string $filename Name of ini file
  2355. */
  2356. public function loadIni($filename)
  2357. {
  2358. if ($this->isFinalized('Cannot load directives after finalization')) {
  2359. return;
  2360. }
  2361. $array = parse_ini_file($filename, true);
  2362. $this->loadArray($array);
  2363. }
  2364. /**
  2365. * Checks whether or not the configuration object is finalized.
  2366. *
  2367. * @param string|bool $error String error message, or false for no error
  2368. *
  2369. * @return bool
  2370. */
  2371. public function isFinalized($error = false)
  2372. {
  2373. if ($this->finalized && $error) {
  2374. $this->triggerError($error, E_USER_ERROR);
  2375. }
  2376. return $this->finalized;
  2377. }
  2378. /**
  2379. * Finalizes configuration only if auto finalize is on and not
  2380. * already finalized
  2381. */
  2382. public function autoFinalize()
  2383. {
  2384. if ($this->autoFinalize) {
  2385. $this->finalize();
  2386. } else {
  2387. $this->plist->squash(true);
  2388. }
  2389. }
  2390. /**
  2391. * Finalizes a configuration object, prohibiting further change
  2392. */
  2393. public function finalize()
  2394. {
  2395. $this->finalized = true;
  2396. $this->parser = null;
  2397. }
  2398. /**
  2399. * Produces a nicely formatted error message by supplying the
  2400. * stack frame information OUTSIDE of HTMLPurifier_Config.
  2401. *
  2402. * @param string $msg An error message
  2403. * @param int $no An error number
  2404. */
  2405. protected function triggerError($msg, $no)
  2406. {
  2407. // determine previous stack frame
  2408. $extra = '';
  2409. if ($this->chatty) {
  2410. $trace = debug_backtrace();
  2411. // zip(tail(trace), trace) -- but PHP is not Haskell har har
  2412. for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
  2413. // XXX this is not correct on some versions of HTML Purifier
  2414. if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
  2415. continue;
  2416. }
  2417. $frame = $trace[$i];
  2418. $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
  2419. break;
  2420. }
  2421. }
  2422. trigger_error($msg . $extra, $no);
  2423. }
  2424. /**
  2425. * Returns a serialized form of the configuration object that can
  2426. * be reconstituted.
  2427. *
  2428. * @return string
  2429. */
  2430. public function serialize()
  2431. {
  2432. $this->getDefinition('HTML');
  2433. $this->getDefinition('CSS');
  2434. $this->getDefinition('URI');
  2435. return serialize($this);
  2436. }
  2437. }
  2438. /**
  2439. * Configuration definition, defines directives and their defaults.
  2440. */
  2441. class HTMLPurifier_ConfigSchema
  2442. {
  2443. /**
  2444. * Defaults of the directives and namespaces.
  2445. * @type array
  2446. * @note This shares the exact same structure as HTMLPurifier_Config::$conf
  2447. */
  2448. public $defaults = array();
  2449. /**
  2450. * The default property list. Do not edit this property list.
  2451. * @type array
  2452. */
  2453. public $defaultPlist;
  2454. /**
  2455. * Definition of the directives.
  2456. * The structure of this is:
  2457. *
  2458. * array(
  2459. * 'Namespace' => array(
  2460. * 'Directive' => new stdClass(),
  2461. * )
  2462. * )
  2463. *
  2464. * The stdClass may have the following properties:
  2465. *
  2466. * - If isAlias isn't set:
  2467. * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
  2468. * - allow_null: If set, this directive allows null values
  2469. * - aliases: If set, an associative array of value aliases to real values
  2470. * - allowed: If set, a lookup array of allowed (string) values
  2471. * - If isAlias is set:
  2472. * - namespace: Namespace this directive aliases to
  2473. * - name: Directive name this directive aliases to
  2474. *
  2475. * In certain degenerate cases, stdClass will actually be an integer. In
  2476. * that case, the value is equivalent to an stdClass with the type
  2477. * property set to the integer. If the integer is negative, type is
  2478. * equal to the absolute value of integer, and allow_null is true.
  2479. *
  2480. * This class is friendly with HTMLPurifier_Config. If you need introspection
  2481. * about the schema, you're better of using the ConfigSchema_Interchange,
  2482. * which uses more memory but has much richer information.
  2483. * @type array
  2484. */
  2485. public $info = array();
  2486. /**
  2487. * Application-wide singleton
  2488. * @type HTMLPurifier_ConfigSchema
  2489. */
  2490. protected static $singleton;
  2491. public function __construct()
  2492. {
  2493. $this->defaultPlist = new HTMLPurifier_PropertyList();
  2494. }
  2495. /**
  2496. * Unserializes the default ConfigSchema.
  2497. * @return HTMLPurifier_ConfigSchema
  2498. */
  2499. public static function makeFromSerial()
  2500. {
  2501. $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
  2502. $r = unserialize($contents);
  2503. if (!$r) {
  2504. $hash = sha1($contents);
  2505. trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
  2506. }
  2507. return $r;
  2508. }
  2509. /**
  2510. * Retrieves an instance of the application-wide configuration definition.
  2511. * @param HTMLPurifier_ConfigSchema $prototype
  2512. * @return HTMLPurifier_ConfigSchema
  2513. */
  2514. public static function instance($prototype = null)
  2515. {
  2516. if ($prototype !== null) {
  2517. HTMLPurifier_ConfigSchema::$singleton = $prototype;
  2518. } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
  2519. HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
  2520. }
  2521. return HTMLPurifier_ConfigSchema::$singleton;
  2522. }
  2523. /**
  2524. * Defines a directive for configuration
  2525. * @warning Will fail of directive's namespace is defined.
  2526. * @warning This method's signature is slightly different from the legacy
  2527. * define() static method! Beware!
  2528. * @param string $key Name of directive
  2529. * @param mixed $default Default value of directive
  2530. * @param string $type Allowed type of the directive. See
  2531. * HTMLPurifier_VarParser::$types for allowed values
  2532. * @param bool $allow_null Whether or not to allow null values
  2533. */
  2534. public function add($key, $default, $type, $allow_null)
  2535. {
  2536. $obj = new stdClass();
  2537. $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
  2538. if ($allow_null) {
  2539. $obj->allow_null = true;
  2540. }
  2541. $this->info[$key] = $obj;
  2542. $this->defaults[$key] = $default;
  2543. $this->defaultPlist->set($key, $default);
  2544. }
  2545. /**
  2546. * Defines a directive value alias.
  2547. *
  2548. * Directive value aliases are convenient for developers because it lets
  2549. * them set a directive to several values and get the same result.
  2550. * @param string $key Name of Directive
  2551. * @param array $aliases Hash of aliased values to the real alias
  2552. */
  2553. public function addValueAliases($key, $aliases)
  2554. {
  2555. if (!isset($this->info[$key]->aliases)) {
  2556. $this->info[$key]->aliases = array();
  2557. }
  2558. foreach ($aliases as $alias => $real) {
  2559. $this->info[$key]->aliases[$alias] = $real;
  2560. }
  2561. }
  2562. /**
  2563. * Defines a set of allowed values for a directive.
  2564. * @warning This is slightly different from the corresponding static
  2565. * method definition.
  2566. * @param string $key Name of directive
  2567. * @param array $allowed Lookup array of allowed values
  2568. */
  2569. public function addAllowedValues($key, $allowed)
  2570. {
  2571. $this->info[$key]->allowed = $allowed;
  2572. }
  2573. /**
  2574. * Defines a directive alias for backwards compatibility
  2575. * @param string $key Directive that will be aliased
  2576. * @param string $new_key Directive that the alias will be to
  2577. */
  2578. public function addAlias($key, $new_key)
  2579. {
  2580. $obj = new stdClass;
  2581. $obj->key = $new_key;
  2582. $obj->isAlias = true;
  2583. $this->info[$key] = $obj;
  2584. }
  2585. /**
  2586. * Replaces any stdClass that only has the type property with type integer.
  2587. */
  2588. public function postProcess()
  2589. {
  2590. foreach ($this->info as $key => $v) {
  2591. if (count((array) $v) == 1) {
  2592. $this->info[$key] = $v->type;
  2593. } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
  2594. $this->info[$key] = -$v->type;
  2595. }
  2596. }
  2597. }
  2598. }
  2599. /**
  2600. * @todo Unit test
  2601. */
  2602. class HTMLPurifier_ContentSets
  2603. {
  2604. /**
  2605. * List of content set strings (pipe separators) indexed by name.
  2606. * @type array
  2607. */
  2608. public $info = array();
  2609. /**
  2610. * List of content set lookups (element => true) indexed by name.
  2611. * @type array
  2612. * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
  2613. */
  2614. public $lookup = array();
  2615. /**
  2616. * Synchronized list of defined content sets (keys of info).
  2617. * @type array
  2618. */
  2619. protected $keys = array();
  2620. /**
  2621. * Synchronized list of defined content values (values of info).
  2622. * @type array
  2623. */
  2624. protected $values = array();
  2625. /**
  2626. * Merges in module's content sets, expands identifiers in the content
  2627. * sets and populates the keys, values and lookup member variables.
  2628. * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
  2629. */
  2630. public function __construct($modules)
  2631. {
  2632. if (!is_array($modules)) {
  2633. $modules = array($modules);
  2634. }
  2635. // populate content_sets based on module hints
  2636. // sorry, no way of overloading
  2637. foreach ($modules as $module) {
  2638. foreach ($module->content_sets as $key => $value) {
  2639. $temp = $this->convertToLookup($value);
  2640. if (isset($this->lookup[$key])) {
  2641. // add it into the existing content set
  2642. $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
  2643. } else {
  2644. $this->lookup[$key] = $temp;
  2645. }
  2646. }
  2647. }
  2648. $old_lookup = false;
  2649. while ($old_lookup !== $this->lookup) {
  2650. $old_lookup = $this->lookup;
  2651. foreach ($this->lookup as $i => $set) {
  2652. $add = array();
  2653. foreach ($set as $element => $x) {
  2654. if (isset($this->lookup[$element])) {
  2655. $add += $this->lookup[$element];
  2656. unset($this->lookup[$i][$element]);
  2657. }
  2658. }
  2659. $this->lookup[$i] += $add;
  2660. }
  2661. }
  2662. foreach ($this->lookup as $key => $lookup) {
  2663. $this->info[$key] = implode(' | ', array_keys($lookup));
  2664. }
  2665. $this->keys = array_keys($this->info);
  2666. $this->values = array_values($this->info);
  2667. }
  2668. /**
  2669. * Accepts a definition; generates and assigns a ChildDef for it
  2670. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
  2671. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2672. */
  2673. public function generateChildDef(&$def, $module)
  2674. {
  2675. if (!empty($def->child)) { // already done!
  2676. return;
  2677. }
  2678. $content_model = $def->content_model;
  2679. if (is_string($content_model)) {
  2680. // Assume that $this->keys is alphanumeric
  2681. $def->content_model = preg_replace_callback(
  2682. '/\b(' . implode('|', $this->keys) . ')\b/',
  2683. array($this, 'generateChildDefCallback'),
  2684. $content_model
  2685. );
  2686. //$def->content_model = str_replace(
  2687. // $this->keys, $this->values, $content_model);
  2688. }
  2689. $def->child = $this->getChildDef($def, $module);
  2690. }
  2691. public function generateChildDefCallback($matches)
  2692. {
  2693. return $this->info[$matches[0]];
  2694. }
  2695. /**
  2696. * Instantiates a ChildDef based on content_model and content_model_type
  2697. * member variables in HTMLPurifier_ElementDef
  2698. * @note This will also defer to modules for custom HTMLPurifier_ChildDef
  2699. * subclasses that need content set expansion
  2700. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
  2701. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2702. * @return HTMLPurifier_ChildDef corresponding to ElementDef
  2703. */
  2704. public function getChildDef($def, $module)
  2705. {
  2706. $value = $def->content_model;
  2707. if (is_object($value)) {
  2708. trigger_error(
  2709. 'Literal object child definitions should be stored in '.
  2710. 'ElementDef->child not ElementDef->content_model',
  2711. E_USER_NOTICE
  2712. );
  2713. return $value;
  2714. }
  2715. switch ($def->content_model_type) {
  2716. case 'required':
  2717. return new HTMLPurifier_ChildDef_Required($value);
  2718. case 'optional':
  2719. return new HTMLPurifier_ChildDef_Optional($value);
  2720. case 'empty':
  2721. return new HTMLPurifier_ChildDef_Empty();
  2722. case 'custom':
  2723. return new HTMLPurifier_ChildDef_Custom($value);
  2724. }
  2725. // defer to its module
  2726. $return = false;
  2727. if ($module->defines_child_def) { // save a func call
  2728. $return = $module->getChildDef($def);
  2729. }
  2730. if ($return !== false) {
  2731. return $return;
  2732. }
  2733. // error-out
  2734. trigger_error(
  2735. 'Could not determine which ChildDef class to instantiate',
  2736. E_USER_ERROR
  2737. );
  2738. return false;
  2739. }
  2740. /**
  2741. * Converts a string list of elements separated by pipes into
  2742. * a lookup array.
  2743. * @param string $string List of elements
  2744. * @return array Lookup array of elements
  2745. */
  2746. protected function convertToLookup($string)
  2747. {
  2748. $array = explode('|', str_replace(' ', '', $string));
  2749. $ret = array();
  2750. foreach ($array as $k) {
  2751. $ret[$k] = true;
  2752. }
  2753. return $ret;
  2754. }
  2755. }
  2756. /**
  2757. * Registry object that contains information about the current context.
  2758. * @warning Is a bit buggy when variables are set to null: it thinks
  2759. * they don't exist! So use false instead, please.
  2760. * @note Since the variables Context deals with may not be objects,
  2761. * references are very important here! Do not remove!
  2762. */
  2763. class HTMLPurifier_Context
  2764. {
  2765. /**
  2766. * Private array that stores the references.
  2767. * @type array
  2768. */
  2769. private $_storage = array();
  2770. /**
  2771. * Registers a variable into the context.
  2772. * @param string $name String name
  2773. * @param mixed $ref Reference to variable to be registered
  2774. */
  2775. public function register($name, &$ref)
  2776. {
  2777. if (array_key_exists($name, $this->_storage)) {
  2778. trigger_error(
  2779. "Name $name produces collision, cannot re-register",
  2780. E_USER_ERROR
  2781. );
  2782. return;
  2783. }
  2784. $this->_storage[$name] =& $ref;
  2785. }
  2786. /**
  2787. * Retrieves a variable reference from the context.
  2788. * @param string $name String name
  2789. * @param bool $ignore_error Boolean whether or not to ignore error
  2790. * @return mixed
  2791. */
  2792. public function &get($name, $ignore_error = false)
  2793. {
  2794. if (!array_key_exists($name, $this->_storage)) {
  2795. if (!$ignore_error) {
  2796. trigger_error(
  2797. "Attempted to retrieve non-existent variable $name",
  2798. E_USER_ERROR
  2799. );
  2800. }
  2801. $var = null; // so we can return by reference
  2802. return $var;
  2803. }
  2804. return $this->_storage[$name];
  2805. }
  2806. /**
  2807. * Destroys a variable in the context.
  2808. * @param string $name String name
  2809. */
  2810. public function destroy($name)
  2811. {
  2812. if (!array_key_exists($name, $this->_storage)) {
  2813. trigger_error(
  2814. "Attempted to destroy non-existent variable $name",
  2815. E_USER_ERROR
  2816. );
  2817. return;
  2818. }
  2819. unset($this->_storage[$name]);
  2820. }
  2821. /**
  2822. * Checks whether or not the variable exists.
  2823. * @param string $name String name
  2824. * @return bool
  2825. */
  2826. public function exists($name)
  2827. {
  2828. return array_key_exists($name, $this->_storage);
  2829. }
  2830. /**
  2831. * Loads a series of variables from an associative array
  2832. * @param array $context_array Assoc array of variables to load
  2833. */
  2834. public function loadArray($context_array)
  2835. {
  2836. foreach ($context_array as $key => $discard) {
  2837. $this->register($key, $context_array[$key]);
  2838. }
  2839. }
  2840. }
  2841. /**
  2842. * Abstract class representing Definition cache managers that implements
  2843. * useful common methods and is a factory.
  2844. * @todo Create a separate maintenance file advanced users can use to
  2845. * cache their custom HTMLDefinition, which can be loaded
  2846. * via a configuration directive
  2847. * @todo Implement memcached
  2848. */
  2849. abstract class HTMLPurifier_DefinitionCache
  2850. {
  2851. /**
  2852. * @type string
  2853. */
  2854. public $type;
  2855. /**
  2856. * @param string $type Type of definition objects this instance of the
  2857. * cache will handle.
  2858. */
  2859. public function __construct($type)
  2860. {
  2861. $this->type = $type;
  2862. }
  2863. /**
  2864. * Generates a unique identifier for a particular configuration
  2865. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  2866. * @return string
  2867. */
  2868. public function generateKey($config)
  2869. {
  2870. return $config->version . ',' . // possibly replace with function calls
  2871. $config->getBatchSerial($this->type) . ',' .
  2872. $config->get($this->type . '.DefinitionRev');
  2873. }
  2874. /**
  2875. * Tests whether or not a key is old with respect to the configuration's
  2876. * version and revision number.
  2877. * @param string $key Key to test
  2878. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
  2879. * @return bool
  2880. */
  2881. public function isOld($key, $config)
  2882. {
  2883. if (substr_count($key, ',') < 2) {
  2884. return true;
  2885. }
  2886. list($version, $hash, $revision) = explode(',', $key, 3);
  2887. $compare = version_compare($version, $config->version);
  2888. // version mismatch, is always old
  2889. if ($compare != 0) {
  2890. return true;
  2891. }
  2892. // versions match, ids match, check revision number
  2893. if ($hash == $config->getBatchSerial($this->type) &&
  2894. $revision < $config->get($this->type . '.DefinitionRev')) {
  2895. return true;
  2896. }
  2897. return false;
  2898. }
  2899. /**
  2900. * Checks if a definition's type jives with the cache's type
  2901. * @note Throws an error on failure
  2902. * @param HTMLPurifier_Definition $def Definition object to check
  2903. * @return bool true if good, false if not
  2904. */
  2905. public function checkDefType($def)
  2906. {
  2907. if ($def->type !== $this->type) {
  2908. trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
  2909. return false;
  2910. }
  2911. return true;
  2912. }
  2913. /**
  2914. * Adds a definition object to the cache
  2915. * @param HTMLPurifier_Definition $def
  2916. * @param HTMLPurifier_Config $config
  2917. */
  2918. abstract public function add($def, $config);
  2919. /**
  2920. * Unconditionally saves a definition object to the cache
  2921. * @param HTMLPurifier_Definition $def
  2922. * @param HTMLPurifier_Config $config
  2923. */
  2924. abstract public function set($def, $config);
  2925. /**
  2926. * Replace an object in the cache
  2927. * @param HTMLPurifier_Definition $def
  2928. * @param HTMLPurifier_Config $config
  2929. */
  2930. abstract public function replace($def, $config);
  2931. /**
  2932. * Retrieves a definition object from the cache
  2933. * @param HTMLPurifier_Config $config
  2934. */
  2935. abstract public function get($config);
  2936. /**
  2937. * Removes a definition object to the cache
  2938. * @param HTMLPurifier_Config $config
  2939. */
  2940. abstract public function remove($config);
  2941. /**
  2942. * Clears all objects from cache
  2943. * @param HTMLPurifier_Config $config
  2944. */
  2945. abstract public function flush($config);
  2946. /**
  2947. * Clears all expired (older version or revision) objects from cache
  2948. * @note Be careful implementing this method as flush. Flush must
  2949. * not interfere with other Definition types, and cleanup()
  2950. * should not be repeatedly called by userland code.
  2951. * @param HTMLPurifier_Config $config
  2952. */
  2953. abstract public function cleanup($config);
  2954. }
  2955. /**
  2956. * Responsible for creating definition caches.
  2957. */
  2958. class HTMLPurifier_DefinitionCacheFactory
  2959. {
  2960. /**
  2961. * @type array
  2962. */
  2963. protected $caches = array('Serializer' => array());
  2964. /**
  2965. * @type array
  2966. */
  2967. protected $implementations = array();
  2968. /**
  2969. * @type HTMLPurifier_DefinitionCache_Decorator[]
  2970. */
  2971. protected $decorators = array();
  2972. /**
  2973. * Initialize default decorators
  2974. */
  2975. public function setup()
  2976. {
  2977. $this->addDecorator('Cleanup');
  2978. }
  2979. /**
  2980. * Retrieves an instance of global definition cache factory.
  2981. * @param HTMLPurifier_DefinitionCacheFactory $prototype
  2982. * @return HTMLPurifier_DefinitionCacheFactory
  2983. */
  2984. public static function instance($prototype = null)
  2985. {
  2986. static $instance;
  2987. if ($prototype !== null) {
  2988. $instance = $prototype;
  2989. } elseif ($instance === null || $prototype === true) {
  2990. $instance = new HTMLPurifier_DefinitionCacheFactory();
  2991. $instance->setup();
  2992. }
  2993. return $instance;
  2994. }
  2995. /**
  2996. * Registers a new definition cache object
  2997. * @param string $short Short name of cache object, for reference
  2998. * @param string $long Full class name of cache object, for construction
  2999. */
  3000. public function register($short, $long)
  3001. {
  3002. $this->implementations[$short] = $long;
  3003. }
  3004. /**
  3005. * Factory method that creates a cache object based on configuration
  3006. * @param string $type Name of definitions handled by cache
  3007. * @param HTMLPurifier_Config $config Config instance
  3008. * @return mixed
  3009. */
  3010. public function create($type, $config)
  3011. {
  3012. $method = $config->get('Cache.DefinitionImpl');
  3013. if ($method === null) {
  3014. return new HTMLPurifier_DefinitionCache_Null($type);
  3015. }
  3016. if (!empty($this->caches[$method][$type])) {
  3017. return $this->caches[$method][$type];
  3018. }
  3019. if (isset($this->implementations[$method]) &&
  3020. class_exists($class = $this->implementations[$method], false)) {
  3021. $cache = new $class($type);
  3022. } else {
  3023. if ($method != 'Serializer') {
  3024. trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
  3025. }
  3026. $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
  3027. }
  3028. foreach ($this->decorators as $decorator) {
  3029. $new_cache = $decorator->decorate($cache);
  3030. // prevent infinite recursion in PHP 4
  3031. unset($cache);
  3032. $cache = $new_cache;
  3033. }
  3034. $this->caches[$method][$type] = $cache;
  3035. return $this->caches[$method][$type];
  3036. }
  3037. /**
  3038. * Registers a decorator to add to all new cache objects
  3039. * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
  3040. */
  3041. public function addDecorator($decorator)
  3042. {
  3043. if (is_string($decorator)) {
  3044. $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
  3045. $decorator = new $class;
  3046. }
  3047. $this->decorators[$decorator->name] = $decorator;
  3048. }
  3049. }
  3050. /**
  3051. * Represents a document type, contains information on which modules
  3052. * need to be loaded.
  3053. * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
  3054. * If structure changes, please update that function.
  3055. */
  3056. class HTMLPurifier_Doctype
  3057. {
  3058. /**
  3059. * Full name of doctype
  3060. * @type string
  3061. */
  3062. public $name;
  3063. /**
  3064. * List of standard modules (string identifiers or literal objects)
  3065. * that this doctype uses
  3066. * @type array
  3067. */
  3068. public $modules = array();
  3069. /**
  3070. * List of modules to use for tidying up code
  3071. * @type array
  3072. */
  3073. public $tidyModules = array();
  3074. /**
  3075. * Is the language derived from XML (i.e. XHTML)?
  3076. * @type bool
  3077. */
  3078. public $xml = true;
  3079. /**
  3080. * List of aliases for this doctype
  3081. * @type array
  3082. */
  3083. public $aliases = array();
  3084. /**
  3085. * Public DTD identifier
  3086. * @type string
  3087. */
  3088. public $dtdPublic;
  3089. /**
  3090. * System DTD identifier
  3091. * @type string
  3092. */
  3093. public $dtdSystem;
  3094. public function __construct(
  3095. $name = null,
  3096. $xml = true,
  3097. $modules = array(),
  3098. $tidyModules = array(),
  3099. $aliases = array(),
  3100. $dtd_public = null,
  3101. $dtd_system = null
  3102. ) {
  3103. $this->name = $name;
  3104. $this->xml = $xml;
  3105. $this->modules = $modules;
  3106. $this->tidyModules = $tidyModules;
  3107. $this->aliases = $aliases;
  3108. $this->dtdPublic = $dtd_public;
  3109. $this->dtdSystem = $dtd_system;
  3110. }
  3111. }
  3112. class HTMLPurifier_DoctypeRegistry
  3113. {
  3114. /**
  3115. * Hash of doctype names to doctype objects.
  3116. * @type array
  3117. */
  3118. protected $doctypes;
  3119. /**
  3120. * Lookup table of aliases to real doctype names.
  3121. * @type array
  3122. */
  3123. protected $aliases;
  3124. /**
  3125. * Registers a doctype to the registry
  3126. * @note Accepts a fully-formed doctype object, or the
  3127. * parameters for constructing a doctype object
  3128. * @param string $doctype Name of doctype or literal doctype object
  3129. * @param bool $xml
  3130. * @param array $modules Modules doctype will load
  3131. * @param array $tidy_modules Modules doctype will load for certain modes
  3132. * @param array $aliases Alias names for doctype
  3133. * @param string $dtd_public
  3134. * @param string $dtd_system
  3135. * @return HTMLPurifier_Doctype Editable registered doctype
  3136. */
  3137. public function register(
  3138. $doctype,
  3139. $xml = true,
  3140. $modules = array(),
  3141. $tidy_modules = array(),
  3142. $aliases = array(),
  3143. $dtd_public = null,
  3144. $dtd_system = null
  3145. ) {
  3146. if (!is_array($modules)) {
  3147. $modules = array($modules);
  3148. }
  3149. if (!is_array($tidy_modules)) {
  3150. $tidy_modules = array($tidy_modules);
  3151. }
  3152. if (!is_array($aliases)) {
  3153. $aliases = array($aliases);
  3154. }
  3155. if (!is_object($doctype)) {
  3156. $doctype = new HTMLPurifier_Doctype(
  3157. $doctype,
  3158. $xml,
  3159. $modules,
  3160. $tidy_modules,
  3161. $aliases,
  3162. $dtd_public,
  3163. $dtd_system
  3164. );
  3165. }
  3166. $this->doctypes[$doctype->name] = $doctype;
  3167. $name = $doctype->name;
  3168. // hookup aliases
  3169. foreach ($doctype->aliases as $alias) {
  3170. if (isset($this->doctypes[$alias])) {
  3171. continue;
  3172. }
  3173. $this->aliases[$alias] = $name;
  3174. }
  3175. // remove old aliases
  3176. if (isset($this->aliases[$name])) {
  3177. unset($this->aliases[$name]);
  3178. }
  3179. return $doctype;
  3180. }
  3181. /**
  3182. * Retrieves reference to a doctype of a certain name
  3183. * @note This function resolves aliases
  3184. * @note When possible, use the more fully-featured make()
  3185. * @param string $doctype Name of doctype
  3186. * @return HTMLPurifier_Doctype Editable doctype object
  3187. */
  3188. public function get($doctype)
  3189. {
  3190. if (isset($this->aliases[$doctype])) {
  3191. $doctype = $this->aliases[$doctype];
  3192. }
  3193. if (!isset($this->doctypes[$doctype])) {
  3194. trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
  3195. $anon = new HTMLPurifier_Doctype($doctype);
  3196. return $anon;
  3197. }
  3198. return $this->doctypes[$doctype];
  3199. }
  3200. /**
  3201. * Creates a doctype based on a configuration object,
  3202. * will perform initialization on the doctype
  3203. * @note Use this function to get a copy of doctype that config
  3204. * can hold on to (this is necessary in order to tell
  3205. * Generator whether or not the current document is XML
  3206. * based or not).
  3207. * @param HTMLPurifier_Config $config
  3208. * @return HTMLPurifier_Doctype
  3209. */
  3210. public function make($config)
  3211. {
  3212. return clone $this->get($this->getDoctypeFromConfig($config));
  3213. }
  3214. /**
  3215. * Retrieves the doctype from the configuration object
  3216. * @param HTMLPurifier_Config $config
  3217. * @return string
  3218. */
  3219. public function getDoctypeFromConfig($config)
  3220. {
  3221. // recommended test
  3222. $doctype = $config->get('HTML.Doctype');
  3223. if (!empty($doctype)) {
  3224. return $doctype;
  3225. }
  3226. $doctype = $config->get('HTML.CustomDoctype');
  3227. if (!empty($doctype)) {
  3228. return $doctype;
  3229. }
  3230. // backwards-compatibility
  3231. if ($config->get('HTML.XHTML')) {
  3232. $doctype = 'XHTML 1.0';
  3233. } else {
  3234. $doctype = 'HTML 4.01';
  3235. }
  3236. if ($config->get('HTML.Strict')) {
  3237. $doctype .= ' Strict';
  3238. } else {
  3239. $doctype .= ' Transitional';
  3240. }
  3241. return $doctype;
  3242. }
  3243. }
  3244. /**
  3245. * Structure that stores an HTML element definition. Used by
  3246. * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
  3247. * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
  3248. * Please update that class too.
  3249. * @warning If you add new properties to this class, you MUST update
  3250. * the mergeIn() method.
  3251. */
  3252. class HTMLPurifier_ElementDef
  3253. {
  3254. /**
  3255. * Does the definition work by itself, or is it created solely
  3256. * for the purpose of merging into another definition?
  3257. * @type bool
  3258. */
  3259. public $standalone = true;
  3260. /**
  3261. * Associative array of attribute name to HTMLPurifier_AttrDef.
  3262. * @type array
  3263. * @note Before being processed by HTMLPurifier_AttrCollections
  3264. * when modules are finalized during
  3265. * HTMLPurifier_HTMLDefinition->setup(), this array may also
  3266. * contain an array at index 0 that indicates which attribute
  3267. * collections to load into the full array. It may also
  3268. * contain string indentifiers in lieu of HTMLPurifier_AttrDef,
  3269. * see HTMLPurifier_AttrTypes on how they are expanded during
  3270. * HTMLPurifier_HTMLDefinition->setup() processing.
  3271. */
  3272. public $attr = array();
  3273. // XXX: Design note: currently, it's not possible to override
  3274. // previously defined AttrTransforms without messing around with
  3275. // the final generated config. This is by design; a previous version
  3276. // used an associated list of attr_transform, but it was extremely
  3277. // easy to accidentally override other attribute transforms by
  3278. // forgetting to specify an index (and just using 0.) While we
  3279. // could check this by checking the index number and complaining,
  3280. // there is a second problem which is that it is not at all easy to
  3281. // tell when something is getting overridden. Combine this with a
  3282. // codebase where this isn't really being used, and it's perfect for
  3283. // nuking.
  3284. /**
  3285. * List of tags HTMLPurifier_AttrTransform to be done before validation.
  3286. * @type array
  3287. */
  3288. public $attr_transform_pre = array();
  3289. /**
  3290. * List of tags HTMLPurifier_AttrTransform to be done after validation.
  3291. * @type array
  3292. */
  3293. public $attr_transform_post = array();
  3294. /**
  3295. * HTMLPurifier_ChildDef of this tag.
  3296. * @type HTMLPurifier_ChildDef
  3297. */
  3298. public $child;
  3299. /**
  3300. * Abstract string representation of internal ChildDef rules.
  3301. * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
  3302. * into an HTMLPurifier_ChildDef.
  3303. * @warning This is a temporary variable that is not available after
  3304. * being processed by HTMLDefinition
  3305. * @type string
  3306. */
  3307. public $content_model;
  3308. /**
  3309. * Value of $child->type, used to determine which ChildDef to use,
  3310. * used in combination with $content_model.
  3311. * @warning This must be lowercase
  3312. * @warning This is a temporary variable that is not available after
  3313. * being processed by HTMLDefinition
  3314. * @type string
  3315. */
  3316. public $content_model_type;
  3317. /**
  3318. * Does the element have a content model (#PCDATA | Inline)*? This
  3319. * is important for chameleon ins and del processing in
  3320. * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
  3321. * have to worry about this one.
  3322. * @type bool
  3323. */
  3324. public $descendants_are_inline = false;
  3325. /**
  3326. * List of the names of required attributes this element has.
  3327. * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
  3328. * @type array
  3329. */
  3330. public $required_attr = array();
  3331. /**
  3332. * Lookup table of tags excluded from all descendants of this tag.
  3333. * @type array
  3334. * @note SGML permits exclusions for all descendants, but this is
  3335. * not possible with DTDs or XML Schemas. W3C has elected to
  3336. * use complicated compositions of content_models to simulate
  3337. * exclusion for children, but we go the simpler, SGML-style
  3338. * route of flat-out exclusions, which correctly apply to
  3339. * all descendants and not just children. Note that the XHTML
  3340. * Modularization Abstract Modules are blithely unaware of such
  3341. * distinctions.
  3342. */
  3343. public $excludes = array();
  3344. /**
  3345. * This tag is explicitly auto-closed by the following tags.
  3346. * @type array
  3347. */
  3348. public $autoclose = array();
  3349. /**
  3350. * If a foreign element is found in this element, test if it is
  3351. * allowed by this sub-element; if it is, instead of closing the
  3352. * current element, place it inside this element.
  3353. * @type string
  3354. */
  3355. public $wrap;
  3356. /**
  3357. * Whether or not this is a formatting element affected by the
  3358. * "Active Formatting Elements" algorithm.
  3359. * @type bool
  3360. */
  3361. public $formatting;
  3362. /**
  3363. * Low-level factory constructor for creating new standalone element defs
  3364. */
  3365. public static function create($content_model, $content_model_type, $attr)
  3366. {
  3367. $def = new HTMLPurifier_ElementDef();
  3368. $def->content_model = $content_model;
  3369. $def->content_model_type = $content_model_type;
  3370. $def->attr = $attr;
  3371. return $def;
  3372. }
  3373. /**
  3374. * Merges the values of another element definition into this one.
  3375. * Values from the new element def take precedence if a value is
  3376. * not mergeable.
  3377. * @param HTMLPurifier_ElementDef $def
  3378. */
  3379. public function mergeIn($def)
  3380. {
  3381. // later keys takes precedence
  3382. foreach ($def->attr as $k => $v) {
  3383. if ($k === 0) {
  3384. // merge in the includes
  3385. // sorry, no way to override an include
  3386. foreach ($v as $v2) {
  3387. $this->attr[0][] = $v2;
  3388. }
  3389. continue;
  3390. }
  3391. if ($v === false) {
  3392. if (isset($this->attr[$k])) {
  3393. unset($this->attr[$k]);
  3394. }
  3395. continue;
  3396. }
  3397. $this->attr[$k] = $v;
  3398. }
  3399. $this->_mergeAssocArray($this->excludes, $def->excludes);
  3400. $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
  3401. $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
  3402. if (!empty($def->content_model)) {
  3403. $this->content_model =
  3404. str_replace("#SUPER", $this->content_model, $def->content_model);
  3405. $this->child = false;
  3406. }
  3407. if (!empty($def->content_model_type)) {
  3408. $this->content_model_type = $def->content_model_type;
  3409. $this->child = false;
  3410. }
  3411. if (!is_null($def->child)) {
  3412. $this->child = $def->child;
  3413. }
  3414. if (!is_null($def->formatting)) {
  3415. $this->formatting = $def->formatting;
  3416. }
  3417. if ($def->descendants_are_inline) {
  3418. $this->descendants_are_inline = $def->descendants_are_inline;
  3419. }
  3420. }
  3421. /**
  3422. * Merges one array into another, removes values which equal false
  3423. * @param $a1 Array by reference that is merged into
  3424. * @param $a2 Array that merges into $a1
  3425. */
  3426. private function _mergeAssocArray(&$a1, $a2)
  3427. {
  3428. foreach ($a2 as $k => $v) {
  3429. if ($v === false) {
  3430. if (isset($a1[$k])) {
  3431. unset($a1[$k]);
  3432. }
  3433. continue;
  3434. }
  3435. $a1[$k] = $v;
  3436. }
  3437. }
  3438. }
  3439. /**
  3440. * A UTF-8 specific character encoder that handles cleaning and transforming.
  3441. * @note All functions in this class should be static.
  3442. */
  3443. class HTMLPurifier_Encoder
  3444. {
  3445. /**
  3446. * Constructor throws fatal error if you attempt to instantiate class
  3447. */
  3448. private function __construct()
  3449. {
  3450. trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
  3451. }
  3452. /**
  3453. * Error-handler that mutes errors, alternative to shut-up operator.
  3454. */
  3455. public static function muteErrorHandler()
  3456. {
  3457. }
  3458. /**
  3459. * iconv wrapper which mutes errors, but doesn't work around bugs.
  3460. * @param string $in Input encoding
  3461. * @param string $out Output encoding
  3462. * @param string $text The text to convert
  3463. * @return string
  3464. */
  3465. public static function unsafeIconv($in, $out, $text)
  3466. {
  3467. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  3468. $r = iconv($in, $out, $text);
  3469. restore_error_handler();
  3470. return $r;
  3471. }
  3472. /**
  3473. * iconv wrapper which mutes errors and works around bugs.
  3474. * @param string $in Input encoding
  3475. * @param string $out Output encoding
  3476. * @param string $text The text to convert
  3477. * @param int $max_chunk_size
  3478. * @return string
  3479. */
  3480. public static function iconv($in, $out, $text, $max_chunk_size = 8000)
  3481. {
  3482. $code = self::testIconvTruncateBug();
  3483. if ($code == self::ICONV_OK) {
  3484. return self::unsafeIconv($in, $out, $text);
  3485. } elseif ($code == self::ICONV_TRUNCATES) {
  3486. // we can only work around this if the input character set
  3487. // is utf-8
  3488. if ($in == 'utf-8') {
  3489. if ($max_chunk_size < 4) {
  3490. trigger_error('max_chunk_size is too small', E_USER_WARNING);
  3491. return false;
  3492. }
  3493. // split into 8000 byte chunks, but be careful to handle
  3494. // multibyte boundaries properly
  3495. if (($c = strlen($text)) <= $max_chunk_size) {
  3496. return self::unsafeIconv($in, $out, $text);
  3497. }
  3498. $r = '';
  3499. $i = 0;
  3500. while (true) {
  3501. if ($i + $max_chunk_size >= $c) {
  3502. $r .= self::unsafeIconv($in, $out, substr($text, $i));
  3503. break;
  3504. }
  3505. // wibble the boundary
  3506. if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
  3507. $chunk_size = $max_chunk_size;
  3508. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
  3509. $chunk_size = $max_chunk_size - 1;
  3510. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
  3511. $chunk_size = $max_chunk_size - 2;
  3512. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
  3513. $chunk_size = $max_chunk_size - 3;
  3514. } else {
  3515. return false; // rather confusing UTF-8...
  3516. }
  3517. $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
  3518. $r .= self::unsafeIconv($in, $out, $chunk);
  3519. $i += $chunk_size;
  3520. }
  3521. return $r;
  3522. } else {
  3523. return false;
  3524. }
  3525. } else {
  3526. return false;
  3527. }
  3528. }
  3529. /**
  3530. * Cleans a UTF-8 string for well-formedness and SGML validity
  3531. *
  3532. * It will parse according to UTF-8 and return a valid UTF8 string, with
  3533. * non-SGML codepoints excluded.
  3534. *
  3535. * Specifically, it will permit:
  3536. * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
  3537. * Source: https://www.w3.org/TR/REC-xml/#NT-Char
  3538. * Arguably this function should be modernized to the HTML5 set
  3539. * of allowed characters:
  3540. * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
  3541. * which simultaneously expand and restrict the set of allowed characters.
  3542. *
  3543. * @param string $str The string to clean
  3544. * @param bool $force_php
  3545. * @return string
  3546. *
  3547. * @note Just for reference, the non-SGML code points are 0 to 31 and
  3548. * 127 to 159, inclusive. However, we allow code points 9, 10
  3549. * and 13, which are the tab, line feed and carriage return
  3550. * respectively. 128 and above the code points map to multibyte
  3551. * UTF-8 representations.
  3552. *
  3553. * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
  3554. * hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
  3555. * LGPL license. Notes on what changed are inside, but in general,
  3556. * the original code transformed UTF-8 text into an array of integer
  3557. * Unicode codepoints. Understandably, transforming that back to
  3558. * a string would be somewhat expensive, so the function was modded to
  3559. * directly operate on the string. However, this discourages code
  3560. * reuse, and the logic enumerated here would be useful for any
  3561. * function that needs to be able to understand UTF-8 characters.
  3562. * As of right now, only smart lossless character encoding converters
  3563. * would need that, and I'm probably not going to implement them.
  3564. */
  3565. public static function cleanUTF8($str, $force_php = false)
  3566. {
  3567. // UTF-8 validity is checked since PHP 4.3.5
  3568. // This is an optimization: if the string is already valid UTF-8, no
  3569. // need to do PHP stuff. 99% of the time, this will be the case.
  3570. if (preg_match(
  3571. '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
  3572. $str
  3573. )) {
  3574. return $str;
  3575. }
  3576. $mState = 0; // cached expected number of octets after the current octet
  3577. // until the beginning of the next UTF8 character sequence
  3578. $mUcs4 = 0; // cached Unicode character
  3579. $mBytes = 1; // cached expected number of octets in the current sequence
  3580. // original code involved an $out that was an array of Unicode
  3581. // codepoints. Instead of having to convert back into UTF-8, we've
  3582. // decided to directly append valid UTF-8 characters onto a string
  3583. // $out once they're done. $char accumulates raw bytes, while $mUcs4
  3584. // turns into the Unicode code point, so there's some redundancy.
  3585. $out = '';
  3586. $char = '';
  3587. $len = strlen($str);
  3588. for ($i = 0; $i < $len; $i++) {
  3589. $in = ord($str[$i]);
  3590. $char .= $str[$i]; // append byte to char
  3591. if (0 == $mState) {
  3592. // When mState is zero we expect either a US-ASCII character
  3593. // or a multi-octet sequence.
  3594. if (0 == (0x80 & ($in))) {
  3595. // US-ASCII, pass straight through.
  3596. if (($in <= 31 || $in == 127) &&
  3597. !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
  3598. ) {
  3599. // control characters, remove
  3600. } else {
  3601. $out .= $char;
  3602. }
  3603. // reset
  3604. $char = '';
  3605. $mBytes = 1;
  3606. } elseif (0xC0 == (0xE0 & ($in))) {
  3607. // First octet of 2 octet sequence
  3608. $mUcs4 = ($in);
  3609. $mUcs4 = ($mUcs4 & 0x1F) << 6;
  3610. $mState = 1;
  3611. $mBytes = 2;
  3612. } elseif (0xE0 == (0xF0 & ($in))) {
  3613. // First octet of 3 octet sequence
  3614. $mUcs4 = ($in);
  3615. $mUcs4 = ($mUcs4 & 0x0F) << 12;
  3616. $mState = 2;
  3617. $mBytes = 3;
  3618. } elseif (0xF0 == (0xF8 & ($in))) {
  3619. // First octet of 4 octet sequence
  3620. $mUcs4 = ($in);
  3621. $mUcs4 = ($mUcs4 & 0x07) << 18;
  3622. $mState = 3;
  3623. $mBytes = 4;
  3624. } elseif (0xF8 == (0xFC & ($in))) {
  3625. // First octet of 5 octet sequence.
  3626. //
  3627. // This is illegal because the encoded codepoint must be
  3628. // either:
  3629. // (a) not the shortest form or
  3630. // (b) outside the Unicode range of 0-0x10FFFF.
  3631. // Rather than trying to resynchronize, we will carry on
  3632. // until the end of the sequence and let the later error
  3633. // handling code catch it.
  3634. $mUcs4 = ($in);
  3635. $mUcs4 = ($mUcs4 & 0x03) << 24;
  3636. $mState = 4;
  3637. $mBytes = 5;
  3638. } elseif (0xFC == (0xFE & ($in))) {
  3639. // First octet of 6 octet sequence, see comments for 5
  3640. // octet sequence.
  3641. $mUcs4 = ($in);
  3642. $mUcs4 = ($mUcs4 & 1) << 30;
  3643. $mState = 5;
  3644. $mBytes = 6;
  3645. } else {
  3646. // Current octet is neither in the US-ASCII range nor a
  3647. // legal first octet of a multi-octet sequence.
  3648. $mState = 0;
  3649. $mUcs4 = 0;
  3650. $mBytes = 1;
  3651. $char = '';
  3652. }
  3653. } else {
  3654. // When mState is non-zero, we expect a continuation of the
  3655. // multi-octet sequence
  3656. if (0x80 == (0xC0 & ($in))) {
  3657. // Legal continuation.
  3658. $shift = ($mState - 1) * 6;
  3659. $tmp = $in;
  3660. $tmp = ($tmp & 0x0000003F) << $shift;
  3661. $mUcs4 |= $tmp;
  3662. if (0 == --$mState) {
  3663. // End of the multi-octet sequence. mUcs4 now contains
  3664. // the final Unicode codepoint to be output
  3665. // Check for illegal sequences and codepoints.
  3666. // From Unicode 3.1, non-shortest form is illegal
  3667. if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
  3668. ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
  3669. ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
  3670. (4 < $mBytes) ||
  3671. // From Unicode 3.2, surrogate characters = illegal
  3672. (($mUcs4 & 0xFFFFF800) == 0xD800) ||
  3673. // Codepoints outside the Unicode range are illegal
  3674. ($mUcs4 > 0x10FFFF)
  3675. ) {
  3676. } elseif (0xFEFF != $mUcs4 && // omit BOM
  3677. // check for valid Char unicode codepoints
  3678. (
  3679. 0x9 == $mUcs4 ||
  3680. 0xA == $mUcs4 ||
  3681. 0xD == $mUcs4 ||
  3682. (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
  3683. // 7F-9F is not strictly prohibited by XML,
  3684. // but it is non-SGML, and thus we don't allow it
  3685. (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
  3686. (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
  3687. (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
  3688. )
  3689. ) {
  3690. $out .= $char;
  3691. }
  3692. // initialize UTF8 cache (reset)
  3693. $mState = 0;
  3694. $mUcs4 = 0;
  3695. $mBytes = 1;
  3696. $char = '';
  3697. }
  3698. } else {
  3699. // ((0xC0 & (*in) != 0x80) && (mState != 0))
  3700. // Incomplete multi-octet sequence.
  3701. // used to result in complete fail, but we'll reset
  3702. $mState = 0;
  3703. $mUcs4 = 0;
  3704. $mBytes = 1;
  3705. $char ='';
  3706. }
  3707. }
  3708. }
  3709. return $out;
  3710. }
  3711. /**
  3712. * Translates a Unicode codepoint into its corresponding UTF-8 character.
  3713. * @note Based on Feyd's function at
  3714. * <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
  3715. * which is in public domain.
  3716. * @note While we're going to do code point parsing anyway, a good
  3717. * optimization would be to refuse to translate code points that
  3718. * are non-SGML characters. However, this could lead to duplication.
  3719. * @note This is very similar to the unichr function in
  3720. * maintenance/generate-entity-file.php (although this is superior,
  3721. * due to its sanity checks).
  3722. */
  3723. // +----------+----------+----------+----------+
  3724. // | 33222222 | 22221111 | 111111 | |
  3725. // | 10987654 | 32109876 | 54321098 | 76543210 | bit
  3726. // +----------+----------+----------+----------+
  3727. // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
  3728. // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
  3729. // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
  3730. // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
  3731. // +----------+----------+----------+----------+
  3732. // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
  3733. // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
  3734. // +----------+----------+----------+----------+
  3735. public static function unichr($code)
  3736. {
  3737. if ($code > 1114111 or $code < 0 or
  3738. ($code >= 55296 and $code <= 57343) ) {
  3739. // bits are set outside the "valid" range as defined
  3740. // by UNICODE 4.1.0
  3741. return '';
  3742. }
  3743. $x = $y = $z = $w = 0;
  3744. if ($code < 128) {
  3745. // regular ASCII character
  3746. $x = $code;
  3747. } else {
  3748. // set up bits for UTF-8
  3749. $x = ($code & 63) | 128;
  3750. if ($code < 2048) {
  3751. $y = (($code & 2047) >> 6) | 192;
  3752. } else {
  3753. $y = (($code & 4032) >> 6) | 128;
  3754. if ($code < 65536) {
  3755. $z = (($code >> 12) & 15) | 224;
  3756. } else {
  3757. $z = (($code >> 12) & 63) | 128;
  3758. $w = (($code >> 18) & 7) | 240;
  3759. }
  3760. }
  3761. }
  3762. // set up the actual character
  3763. $ret = '';
  3764. if ($w) {
  3765. $ret .= chr($w);
  3766. }
  3767. if ($z) {
  3768. $ret .= chr($z);
  3769. }
  3770. if ($y) {
  3771. $ret .= chr($y);
  3772. }
  3773. $ret .= chr($x);
  3774. return $ret;
  3775. }
  3776. /**
  3777. * @return bool
  3778. */
  3779. public static function iconvAvailable()
  3780. {
  3781. static $iconv = null;
  3782. if ($iconv === null) {
  3783. $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
  3784. }
  3785. return $iconv;
  3786. }
  3787. /**
  3788. * Convert a string to UTF-8 based on configuration.
  3789. * @param string $str The string to convert
  3790. * @param HTMLPurifier_Config $config
  3791. * @param HTMLPurifier_Context $context
  3792. * @return string
  3793. */
  3794. public static function convertToUTF8($str, $config, $context)
  3795. {
  3796. $encoding = $config->get('Core.Encoding');
  3797. if ($encoding === 'utf-8') {
  3798. return $str;
  3799. }
  3800. static $iconv = null;
  3801. if ($iconv === null) {
  3802. $iconv = self::iconvAvailable();
  3803. }
  3804. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3805. // unaffected by bugs, since UTF-8 support all characters
  3806. $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
  3807. if ($str === false) {
  3808. // $encoding is not a valid encoding
  3809. trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
  3810. return '';
  3811. }
  3812. // If the string is bjorked by Shift_JIS or a similar encoding
  3813. // that doesn't support all of ASCII, convert the naughty
  3814. // characters to their true byte-wise ASCII/UTF-8 equivalents.
  3815. $str = strtr($str, self::testEncodingSupportsASCII($encoding));
  3816. return $str;
  3817. } elseif ($encoding === 'iso-8859-1') {
  3818. $str = utf8_encode($str);
  3819. return $str;
  3820. }
  3821. $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
  3822. if ($bug == self::ICONV_OK) {
  3823. trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
  3824. } else {
  3825. trigger_error(
  3826. 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
  3827. 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
  3828. E_USER_ERROR
  3829. );
  3830. }
  3831. }
  3832. /**
  3833. * Converts a string from UTF-8 based on configuration.
  3834. * @param string $str The string to convert
  3835. * @param HTMLPurifier_Config $config
  3836. * @param HTMLPurifier_Context $context
  3837. * @return string
  3838. * @note Currently, this is a lossy conversion, with unexpressable
  3839. * characters being omitted.
  3840. */
  3841. public static function convertFromUTF8($str, $config, $context)
  3842. {
  3843. $encoding = $config->get('Core.Encoding');
  3844. if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
  3845. $str = self::convertToASCIIDumbLossless($str);
  3846. }
  3847. if ($encoding === 'utf-8') {
  3848. return $str;
  3849. }
  3850. static $iconv = null;
  3851. if ($iconv === null) {
  3852. $iconv = self::iconvAvailable();
  3853. }
  3854. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3855. // Undo our previous fix in convertToUTF8, otherwise iconv will barf
  3856. $ascii_fix = self::testEncodingSupportsASCII($encoding);
  3857. if (!$escape && !empty($ascii_fix)) {
  3858. $clear_fix = array();
  3859. foreach ($ascii_fix as $utf8 => $native) {
  3860. $clear_fix[$utf8] = '';
  3861. }
  3862. $str = strtr($str, $clear_fix);
  3863. }
  3864. $str = strtr($str, array_flip($ascii_fix));
  3865. // Normal stuff
  3866. $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
  3867. return $str;
  3868. } elseif ($encoding === 'iso-8859-1') {
  3869. $str = utf8_decode($str);
  3870. return $str;
  3871. }
  3872. trigger_error('Encoding not supported', E_USER_ERROR);
  3873. // You might be tempted to assume that the ASCII representation
  3874. // might be OK, however, this is *not* universally true over all
  3875. // encodings. So we take the conservative route here, rather
  3876. // than forcibly turn on %Core.EscapeNonASCIICharacters
  3877. }
  3878. /**
  3879. * Lossless (character-wise) conversion of HTML to ASCII
  3880. * @param string $str UTF-8 string to be converted to ASCII
  3881. * @return string ASCII encoded string with non-ASCII character entity-ized
  3882. * @warning Adapted from MediaWiki, claiming fair use: this is a common
  3883. * algorithm. If you disagree with this license fudgery,
  3884. * implement it yourself.
  3885. * @note Uses decimal numeric entities since they are best supported.
  3886. * @note This is a DUMB function: it has no concept of keeping
  3887. * character entities that the projected character encoding
  3888. * can allow. We could possibly implement a smart version
  3889. * but that would require it to also know which Unicode
  3890. * codepoints the charset supported (not an easy task).
  3891. * @note Sort of with cleanUTF8() but it assumes that $str is
  3892. * well-formed UTF-8
  3893. */
  3894. public static function convertToASCIIDumbLossless($str)
  3895. {
  3896. $bytesleft = 0;
  3897. $result = '';
  3898. $working = 0;
  3899. $len = strlen($str);
  3900. for ($i = 0; $i < $len; $i++) {
  3901. $bytevalue = ord($str[$i]);
  3902. if ($bytevalue <= 0x7F) { //0xxx xxxx
  3903. $result .= chr($bytevalue);
  3904. $bytesleft = 0;
  3905. } elseif ($bytevalue <= 0xBF) { //10xx xxxx
  3906. $working = $working << 6;
  3907. $working += ($bytevalue & 0x3F);
  3908. $bytesleft--;
  3909. if ($bytesleft <= 0) {
  3910. $result .= "&#" . $working . ";";
  3911. }
  3912. } elseif ($bytevalue <= 0xDF) { //110x xxxx
  3913. $working = $bytevalue & 0x1F;
  3914. $bytesleft = 1;
  3915. } elseif ($bytevalue <= 0xEF) { //1110 xxxx
  3916. $working = $bytevalue & 0x0F;
  3917. $bytesleft = 2;
  3918. } else { //1111 0xxx
  3919. $working = $bytevalue & 0x07;
  3920. $bytesleft = 3;
  3921. }
  3922. }
  3923. return $result;
  3924. }
  3925. /** No bugs detected in iconv. */
  3926. const ICONV_OK = 0;
  3927. /** Iconv truncates output if converting from UTF-8 to another
  3928. * character set with //IGNORE, and a non-encodable character is found */
  3929. const ICONV_TRUNCATES = 1;
  3930. /** Iconv does not support //IGNORE, making it unusable for
  3931. * transcoding purposes */
  3932. const ICONV_UNUSABLE = 2;
  3933. /**
  3934. * glibc iconv has a known bug where it doesn't handle the magic
  3935. * //IGNORE stanza correctly. In particular, rather than ignore
  3936. * characters, it will return an EILSEQ after consuming some number
  3937. * of characters, and expect you to restart iconv as if it were
  3938. * an E2BIG. Old versions of PHP did not respect the errno, and
  3939. * returned the fragment, so as a result you would see iconv
  3940. * mysteriously truncating output. We can work around this by
  3941. * manually chopping our input into segments of about 8000
  3942. * characters, as long as PHP ignores the error code. If PHP starts
  3943. * paying attention to the error code, iconv becomes unusable.
  3944. *
  3945. * @return int Error code indicating severity of bug.
  3946. */
  3947. public static function testIconvTruncateBug()
  3948. {
  3949. static $code = null;
  3950. if ($code === null) {
  3951. // better not use iconv, otherwise infinite loop!
  3952. $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
  3953. if ($r === false) {
  3954. $code = self::ICONV_UNUSABLE;
  3955. } elseif (($c = strlen($r)) < 9000) {
  3956. $code = self::ICONV_TRUNCATES;
  3957. } elseif ($c > 9000) {
  3958. trigger_error(
  3959. 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
  3960. 'include your iconv version as per phpversion()',
  3961. E_USER_ERROR
  3962. );
  3963. } else {
  3964. $code = self::ICONV_OK;
  3965. }
  3966. }
  3967. return $code;
  3968. }
  3969. /**
  3970. * This expensive function tests whether or not a given character
  3971. * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
  3972. * fail this test, and require special processing. Variable width
  3973. * encodings shouldn't ever fail.
  3974. *
  3975. * @param string $encoding Encoding name to test, as per iconv format
  3976. * @param bool $bypass Whether or not to bypass the precompiled arrays.
  3977. * @return Array of UTF-8 characters to their corresponding ASCII,
  3978. * which can be used to "undo" any overzealous iconv action.
  3979. */
  3980. public static function testEncodingSupportsASCII($encoding, $bypass = false)
  3981. {
  3982. // All calls to iconv here are unsafe, proof by case analysis:
  3983. // If ICONV_OK, no difference.
  3984. // If ICONV_TRUNCATE, all calls involve one character inputs,
  3985. // so bug is not triggered.
  3986. // If ICONV_UNUSABLE, this call is irrelevant
  3987. static $encodings = array();
  3988. if (!$bypass) {
  3989. if (isset($encodings[$encoding])) {
  3990. return $encodings[$encoding];
  3991. }
  3992. $lenc = strtolower($encoding);
  3993. switch ($lenc) {
  3994. case 'shift_jis':
  3995. return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
  3996. case 'johab':
  3997. return array("\xE2\x82\xA9" => '\\');
  3998. }
  3999. if (strpos($lenc, 'iso-8859-') === 0) {
  4000. return array();
  4001. }
  4002. }
  4003. $ret = array();
  4004. if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
  4005. return false;
  4006. }
  4007. for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
  4008. $c = chr($i); // UTF-8 char
  4009. $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
  4010. if ($r === '' ||
  4011. // This line is needed for iconv implementations that do not
  4012. // omit characters that do not exist in the target character set
  4013. ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
  4014. ) {
  4015. // Reverse engineer: what's the UTF-8 equiv of this byte
  4016. // sequence? This assumes that there's no variable width
  4017. // encoding that doesn't support ASCII.
  4018. $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
  4019. }
  4020. }
  4021. $encodings[$encoding] = $ret;
  4022. return $ret;
  4023. }
  4024. }
  4025. /**
  4026. * Object that provides entity lookup table from entity name to character
  4027. */
  4028. class HTMLPurifier_EntityLookup
  4029. {
  4030. /**
  4031. * Assoc array of entity name to character represented.
  4032. * @type array
  4033. */
  4034. public $table;
  4035. /**
  4036. * Sets up the entity lookup table from the serialized file contents.
  4037. * @param bool $file
  4038. * @note The serialized contents are versioned, but were generated
  4039. * using the maintenance script generate_entity_file.php
  4040. * @warning This is not in constructor to help enforce the Singleton
  4041. */
  4042. public function setup($file = false)
  4043. {
  4044. if (!$file) {
  4045. $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
  4046. }
  4047. $this->table = unserialize(file_get_contents($file));
  4048. }
  4049. /**
  4050. * Retrieves sole instance of the object.
  4051. * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
  4052. * @return HTMLPurifier_EntityLookup
  4053. */
  4054. public static function instance($prototype = false)
  4055. {
  4056. // no references, since PHP doesn't copy unless modified
  4057. static $instance = null;
  4058. if ($prototype) {
  4059. $instance = $prototype;
  4060. } elseif (!$instance) {
  4061. $instance = new HTMLPurifier_EntityLookup();
  4062. $instance->setup();
  4063. }
  4064. return $instance;
  4065. }
  4066. }
  4067. // if want to implement error collecting here, we'll need to use some sort
  4068. // of global data (probably trigger_error) because it's impossible to pass
  4069. // $config or $context to the callback functions.
  4070. /**
  4071. * Handles referencing and derefencing character entities
  4072. */
  4073. class HTMLPurifier_EntityParser
  4074. {
  4075. /**
  4076. * Reference to entity lookup table.
  4077. * @type HTMLPurifier_EntityLookup
  4078. */
  4079. protected $_entity_lookup;
  4080. /**
  4081. * Callback regex string for entities in text.
  4082. * @type string
  4083. */
  4084. protected $_textEntitiesRegex;
  4085. /**
  4086. * Callback regex string for entities in attributes.
  4087. * @type string
  4088. */
  4089. protected $_attrEntitiesRegex;
  4090. /**
  4091. * Tests if the beginning of a string is a semi-optional regex
  4092. */
  4093. protected $_semiOptionalPrefixRegex;
  4094. public function __construct() {
  4095. // From
  4096. // http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
  4097. $semi_optional = "quot|QUOT|lt|LT|gt|GT|amp|AMP|AElig|Aacute|Acirc|Agrave|Aring|Atilde|Auml|COPY|Ccedil|ETH|Eacute|Ecirc|Egrave|Euml|Iacute|Icirc|Igrave|Iuml|Ntilde|Oacute|Ocirc|Ograve|Oslash|Otilde|Ouml|REG|THORN|Uacute|Ucirc|Ugrave|Uuml|Yacute|aacute|acirc|acute|aelig|agrave|aring|atilde|auml|brvbar|ccedil|cedil|cent|copy|curren|deg|divide|eacute|ecirc|egrave|eth|euml|frac12|frac14|frac34|iacute|icirc|iexcl|igrave|iquest|iuml|laquo|macr|micro|middot|nbsp|not|ntilde|oacute|ocirc|ograve|ordf|ordm|oslash|otilde|ouml|para|plusmn|pound|raquo|reg|sect|shy|sup1|sup2|sup3|szlig|thorn|times|uacute|ucirc|ugrave|uml|uuml|yacute|yen|yuml";
  4098. // NB: three empty captures to put the fourth match in the right
  4099. // place
  4100. $this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";
  4101. $this->_textEntitiesRegex =
  4102. '/&(?:'.
  4103. // hex
  4104. '[#]x([a-fA-F0-9]+);?|'.
  4105. // dec
  4106. '[#]0*(\d+);?|'.
  4107. // string (mandatory semicolon)
  4108. // NB: order matters: match semicolon preferentially
  4109. '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
  4110. // string (optional semicolon)
  4111. "($semi_optional)".
  4112. ')/';
  4113. $this->_attrEntitiesRegex =
  4114. '/&(?:'.
  4115. // hex
  4116. '[#]x([a-fA-F0-9]+);?|'.
  4117. // dec
  4118. '[#]0*(\d+);?|'.
  4119. // string (mandatory semicolon)
  4120. // NB: order matters: match semicolon preferentially
  4121. '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
  4122. // string (optional semicolon)
  4123. // don't match if trailing is equals or alphanumeric (URL
  4124. // like)
  4125. "($semi_optional)(?![=;A-Za-z0-9])".
  4126. ')/';
  4127. }
  4128. /**
  4129. * Substitute entities with the parsed equivalents. Use this on
  4130. * textual data in an HTML document (as opposed to attributes.)
  4131. *
  4132. * @param string $string String to have entities parsed.
  4133. * @return string Parsed string.
  4134. */
  4135. public function substituteTextEntities($string)
  4136. {
  4137. return preg_replace_callback(
  4138. $this->_textEntitiesRegex,
  4139. array($this, 'entityCallback'),
  4140. $string
  4141. );
  4142. }
  4143. /**
  4144. * Substitute entities with the parsed equivalents. Use this on
  4145. * attribute contents in documents.
  4146. *
  4147. * @param string $string String to have entities parsed.
  4148. * @return string Parsed string.
  4149. */
  4150. public function substituteAttrEntities($string)
  4151. {
  4152. return preg_replace_callback(
  4153. $this->_attrEntitiesRegex,
  4154. array($this, 'entityCallback'),
  4155. $string
  4156. );
  4157. }
  4158. /**
  4159. * Callback function for substituteNonSpecialEntities() that does the work.
  4160. *
  4161. * @param array $matches PCRE matches array, with 0 the entire match, and
  4162. * either index 1, 2 or 3 set with a hex value, dec value,
  4163. * or string (respectively).
  4164. * @return string Replacement string.
  4165. */
  4166. protected function entityCallback($matches)
  4167. {
  4168. $entity = $matches[0];
  4169. $hex_part = @$matches[1];
  4170. $dec_part = @$matches[2];
  4171. $named_part = empty($matches[3]) ? (empty($matches[4]) ? "" : $matches[4]) : $matches[3];
  4172. if ($hex_part !== NULL && $hex_part !== "") {
  4173. return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
  4174. } elseif ($dec_part !== NULL && $dec_part !== "") {
  4175. return HTMLPurifier_Encoder::unichr((int) $dec_part);
  4176. } else {
  4177. if (!$this->_entity_lookup) {
  4178. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  4179. }
  4180. if (isset($this->_entity_lookup->table[$named_part])) {
  4181. return $this->_entity_lookup->table[$named_part];
  4182. } else {
  4183. // exact match didn't match anything, so test if
  4184. // any of the semicolon optional match the prefix.
  4185. // Test that this is an EXACT match is important to
  4186. // prevent infinite loop
  4187. if (!empty($matches[3])) {
  4188. return preg_replace_callback(
  4189. $this->_semiOptionalPrefixRegex,
  4190. array($this, 'entityCallback'),
  4191. $entity
  4192. );
  4193. }
  4194. return $entity;
  4195. }
  4196. }
  4197. }
  4198. // LEGACY CODE BELOW
  4199. /**
  4200. * Callback regex string for parsing entities.
  4201. * @type string
  4202. */
  4203. protected $_substituteEntitiesRegex =
  4204. '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
  4205. // 1. hex 2. dec 3. string (XML style)
  4206. /**
  4207. * Decimal to parsed string conversion table for special entities.
  4208. * @type array
  4209. */
  4210. protected $_special_dec2str =
  4211. array(
  4212. 34 => '"',
  4213. 38 => '&',
  4214. 39 => "'",
  4215. 60 => '<',
  4216. 62 => '>'
  4217. );
  4218. /**
  4219. * Stripped entity names to decimal conversion table for special entities.
  4220. * @type array
  4221. */
  4222. protected $_special_ent2dec =
  4223. array(
  4224. 'quot' => 34,
  4225. 'amp' => 38,
  4226. 'lt' => 60,
  4227. 'gt' => 62
  4228. );
  4229. /**
  4230. * Substitutes non-special entities with their parsed equivalents. Since
  4231. * running this whenever you have parsed character is t3h 5uck, we run
  4232. * it before everything else.
  4233. *
  4234. * @param string $string String to have non-special entities parsed.
  4235. * @return string Parsed string.
  4236. */
  4237. public function substituteNonSpecialEntities($string)
  4238. {
  4239. // it will try to detect missing semicolons, but don't rely on it
  4240. return preg_replace_callback(
  4241. $this->_substituteEntitiesRegex,
  4242. array($this, 'nonSpecialEntityCallback'),
  4243. $string
  4244. );
  4245. }
  4246. /**
  4247. * Callback function for substituteNonSpecialEntities() that does the work.
  4248. *
  4249. * @param array $matches PCRE matches array, with 0 the entire match, and
  4250. * either index 1, 2 or 3 set with a hex value, dec value,
  4251. * or string (respectively).
  4252. * @return string Replacement string.
  4253. */
  4254. protected function nonSpecialEntityCallback($matches)
  4255. {
  4256. // replaces all but big five
  4257. $entity = $matches[0];
  4258. $is_num = (@$matches[0][1] === '#');
  4259. if ($is_num) {
  4260. $is_hex = (@$entity[2] === 'x');
  4261. $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4262. // abort for special characters
  4263. if (isset($this->_special_dec2str[$code])) {
  4264. return $entity;
  4265. }
  4266. return HTMLPurifier_Encoder::unichr($code);
  4267. } else {
  4268. if (isset($this->_special_ent2dec[$matches[3]])) {
  4269. return $entity;
  4270. }
  4271. if (!$this->_entity_lookup) {
  4272. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  4273. }
  4274. if (isset($this->_entity_lookup->table[$matches[3]])) {
  4275. return $this->_entity_lookup->table[$matches[3]];
  4276. } else {
  4277. return $entity;
  4278. }
  4279. }
  4280. }
  4281. /**
  4282. * Substitutes only special entities with their parsed equivalents.
  4283. *
  4284. * @notice We try to avoid calling this function because otherwise, it
  4285. * would have to be called a lot (for every parsed section).
  4286. *
  4287. * @param string $string String to have non-special entities parsed.
  4288. * @return string Parsed string.
  4289. */
  4290. public function substituteSpecialEntities($string)
  4291. {
  4292. return preg_replace_callback(
  4293. $this->_substituteEntitiesRegex,
  4294. array($this, 'specialEntityCallback'),
  4295. $string
  4296. );
  4297. }
  4298. /**
  4299. * Callback function for substituteSpecialEntities() that does the work.
  4300. *
  4301. * This callback has same syntax as nonSpecialEntityCallback().
  4302. *
  4303. * @param array $matches PCRE-style matches array, with 0 the entire match, and
  4304. * either index 1, 2 or 3 set with a hex value, dec value,
  4305. * or string (respectively).
  4306. * @return string Replacement string.
  4307. */
  4308. protected function specialEntityCallback($matches)
  4309. {
  4310. $entity = $matches[0];
  4311. $is_num = (@$matches[0][1] === '#');
  4312. if ($is_num) {
  4313. $is_hex = (@$entity[2] === 'x');
  4314. $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4315. return isset($this->_special_dec2str[$int]) ?
  4316. $this->_special_dec2str[$int] :
  4317. $entity;
  4318. } else {
  4319. return isset($this->_special_ent2dec[$matches[3]]) ?
  4320. $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
  4321. $entity;
  4322. }
  4323. }
  4324. }
  4325. /**
  4326. * Error collection class that enables HTML Purifier to report HTML
  4327. * problems back to the user
  4328. */
  4329. class HTMLPurifier_ErrorCollector
  4330. {
  4331. /**
  4332. * Identifiers for the returned error array. These are purposely numeric
  4333. * so list() can be used.
  4334. */
  4335. const LINENO = 0;
  4336. const SEVERITY = 1;
  4337. const MESSAGE = 2;
  4338. const CHILDREN = 3;
  4339. /**
  4340. * @type array
  4341. */
  4342. protected $errors;
  4343. /**
  4344. * @type array
  4345. */
  4346. protected $_current;
  4347. /**
  4348. * @type array
  4349. */
  4350. protected $_stacks = array(array());
  4351. /**
  4352. * @type HTMLPurifier_Language
  4353. */
  4354. protected $locale;
  4355. /**
  4356. * @type HTMLPurifier_Generator
  4357. */
  4358. protected $generator;
  4359. /**
  4360. * @type HTMLPurifier_Context
  4361. */
  4362. protected $context;
  4363. /**
  4364. * @type array
  4365. */
  4366. protected $lines = array();
  4367. /**
  4368. * @param HTMLPurifier_Context $context
  4369. */
  4370. public function __construct($context)
  4371. {
  4372. $this->locale =& $context->get('Locale');
  4373. $this->context = $context;
  4374. $this->_current =& $this->_stacks[0];
  4375. $this->errors =& $this->_stacks[0];
  4376. }
  4377. /**
  4378. * Sends an error message to the collector for later use
  4379. * @param int $severity Error severity, PHP error style (don't use E_USER_)
  4380. * @param string $msg Error message text
  4381. */
  4382. public function send($severity, $msg)
  4383. {
  4384. $args = array();
  4385. if (func_num_args() > 2) {
  4386. $args = func_get_args();
  4387. array_shift($args);
  4388. unset($args[0]);
  4389. }
  4390. $token = $this->context->get('CurrentToken', true);
  4391. $line = $token ? $token->line : $this->context->get('CurrentLine', true);
  4392. $col = $token ? $token->col : $this->context->get('CurrentCol', true);
  4393. $attr = $this->context->get('CurrentAttr', true);
  4394. // perform special substitutions, also add custom parameters
  4395. $subst = array();
  4396. if (!is_null($token)) {
  4397. $args['CurrentToken'] = $token;
  4398. }
  4399. if (!is_null($attr)) {
  4400. $subst['$CurrentAttr.Name'] = $attr;
  4401. if (isset($token->attr[$attr])) {
  4402. $subst['$CurrentAttr.Value'] = $token->attr[$attr];
  4403. }
  4404. }
  4405. if (empty($args)) {
  4406. $msg = $this->locale->getMessage($msg);
  4407. } else {
  4408. $msg = $this->locale->formatMessage($msg, $args);
  4409. }
  4410. if (!empty($subst)) {
  4411. $msg = strtr($msg, $subst);
  4412. }
  4413. // (numerically indexed)
  4414. $error = array(
  4415. self::LINENO => $line,
  4416. self::SEVERITY => $severity,
  4417. self::MESSAGE => $msg,
  4418. self::CHILDREN => array()
  4419. );
  4420. $this->_current[] = $error;
  4421. // NEW CODE BELOW ...
  4422. // Top-level errors are either:
  4423. // TOKEN type, if $value is set appropriately, or
  4424. // "syntax" type, if $value is null
  4425. $new_struct = new HTMLPurifier_ErrorStruct();
  4426. $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
  4427. if ($token) {
  4428. $new_struct->value = clone $token;
  4429. }
  4430. if (is_int($line) && is_int($col)) {
  4431. if (isset($this->lines[$line][$col])) {
  4432. $struct = $this->lines[$line][$col];
  4433. } else {
  4434. $struct = $this->lines[$line][$col] = $new_struct;
  4435. }
  4436. // These ksorts may present a performance problem
  4437. ksort($this->lines[$line], SORT_NUMERIC);
  4438. } else {
  4439. if (isset($this->lines[-1])) {
  4440. $struct = $this->lines[-1];
  4441. } else {
  4442. $struct = $this->lines[-1] = $new_struct;
  4443. }
  4444. }
  4445. ksort($this->lines, SORT_NUMERIC);
  4446. // Now, check if we need to operate on a lower structure
  4447. if (!empty($attr)) {
  4448. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
  4449. if (!$struct->value) {
  4450. $struct->value = array($attr, 'PUT VALUE HERE');
  4451. }
  4452. }
  4453. if (!empty($cssprop)) {
  4454. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
  4455. if (!$struct->value) {
  4456. // if we tokenize CSS this might be a little more difficult to do
  4457. $struct->value = array($cssprop, 'PUT VALUE HERE');
  4458. }
  4459. }
  4460. // Ok, structs are all setup, now time to register the error
  4461. $struct->addError($severity, $msg);
  4462. }
  4463. /**
  4464. * Retrieves raw error data for custom formatter to use
  4465. */
  4466. public function getRaw()
  4467. {
  4468. return $this->errors;
  4469. }
  4470. /**
  4471. * Default HTML formatting implementation for error messages
  4472. * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
  4473. * @param array $errors Errors array to display; used for recursion.
  4474. * @return string
  4475. */
  4476. public function getHTMLFormatted($config, $errors = null)
  4477. {
  4478. $ret = array();
  4479. $this->generator = new HTMLPurifier_Generator($config, $this->context);
  4480. if ($errors === null) {
  4481. $errors = $this->errors;
  4482. }
  4483. // 'At line' message needs to be removed
  4484. // generation code for new structure goes here. It needs to be recursive.
  4485. foreach ($this->lines as $line => $col_array) {
  4486. if ($line == -1) {
  4487. continue;
  4488. }
  4489. foreach ($col_array as $col => $struct) {
  4490. $this->_renderStruct($ret, $struct, $line, $col);
  4491. }
  4492. }
  4493. if (isset($this->lines[-1])) {
  4494. $this->_renderStruct($ret, $this->lines[-1]);
  4495. }
  4496. if (empty($errors)) {
  4497. return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
  4498. } else {
  4499. return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
  4500. }
  4501. }
  4502. private function _renderStruct(&$ret, $struct, $line = null, $col = null)
  4503. {
  4504. $stack = array($struct);
  4505. $context_stack = array(array());
  4506. while ($current = array_pop($stack)) {
  4507. $context = array_pop($context_stack);
  4508. foreach ($current->errors as $error) {
  4509. list($severity, $msg) = $error;
  4510. $string = '';
  4511. $string .= '<div>';
  4512. // W3C uses an icon to indicate the severity of the error.
  4513. $error = $this->locale->getErrorName($severity);
  4514. $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
  4515. if (!is_null($line) && !is_null($col)) {
  4516. $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
  4517. } else {
  4518. $string .= '<em class="location">End of Document: </em> ';
  4519. }
  4520. $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
  4521. $string .= '</div>';
  4522. // Here, have a marker for the character on the column appropriate.
  4523. // Be sure to clip extremely long lines.
  4524. //$string .= '<pre>';
  4525. //$string .= '';
  4526. //$string .= '</pre>';
  4527. $ret[] = $string;
  4528. }
  4529. foreach ($current->children as $array) {
  4530. $context[] = $current;
  4531. $stack = array_merge($stack, array_reverse($array, true));
  4532. for ($i = count($array); $i > 0; $i--) {
  4533. $context_stack[] = $context;
  4534. }
  4535. }
  4536. }
  4537. }
  4538. }
  4539. /**
  4540. * Records errors for particular segments of an HTML document such as tokens,
  4541. * attributes or CSS properties. They can contain error structs (which apply
  4542. * to components of what they represent), but their main purpose is to hold
  4543. * errors applying to whatever struct is being used.
  4544. */
  4545. class HTMLPurifier_ErrorStruct
  4546. {
  4547. /**
  4548. * Possible values for $children first-key. Note that top-level structures
  4549. * are automatically token-level.
  4550. */
  4551. const TOKEN = 0;
  4552. const ATTR = 1;
  4553. const CSSPROP = 2;
  4554. /**
  4555. * Type of this struct.
  4556. * @type string
  4557. */
  4558. public $type;
  4559. /**
  4560. * Value of the struct we are recording errors for. There are various
  4561. * values for this:
  4562. * - TOKEN: Instance of HTMLPurifier_Token
  4563. * - ATTR: array('attr-name', 'value')
  4564. * - CSSPROP: array('prop-name', 'value')
  4565. * @type mixed
  4566. */
  4567. public $value;
  4568. /**
  4569. * Errors registered for this structure.
  4570. * @type array
  4571. */
  4572. public $errors = array();
  4573. /**
  4574. * Child ErrorStructs that are from this structure. For example, a TOKEN
  4575. * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
  4576. * array in structure: [TYPE]['identifier']
  4577. * @type array
  4578. */
  4579. public $children = array();
  4580. /**
  4581. * @param string $type
  4582. * @param string $id
  4583. * @return mixed
  4584. */
  4585. public function getChild($type, $id)
  4586. {
  4587. if (!isset($this->children[$type][$id])) {
  4588. $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
  4589. $this->children[$type][$id]->type = $type;
  4590. }
  4591. return $this->children[$type][$id];
  4592. }
  4593. /**
  4594. * @param int $severity
  4595. * @param string $message
  4596. */
  4597. public function addError($severity, $message)
  4598. {
  4599. $this->errors[] = array($severity, $message);
  4600. }
  4601. }
  4602. /**
  4603. * Global exception class for HTML Purifier; any exceptions we throw
  4604. * are from here.
  4605. */
  4606. class HTMLPurifier_Exception extends Exception
  4607. {
  4608. }
  4609. /**
  4610. * Represents a pre or post processing filter on HTML Purifier's output
  4611. *
  4612. * Sometimes, a little ad-hoc fixing of HTML has to be done before
  4613. * it gets sent through HTML Purifier: you can use filters to acheive
  4614. * this effect. For instance, YouTube videos can be preserved using
  4615. * this manner. You could have used a decorator for this task, but
  4616. * PHP's support for them is not terribly robust, so we're going
  4617. * to just loop through the filters.
  4618. *
  4619. * Filters should be exited first in, last out. If there are three filters,
  4620. * named 1, 2 and 3, the order of execution should go 1->preFilter,
  4621. * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
  4622. * 1->postFilter.
  4623. *
  4624. * @note Methods are not declared abstract as it is perfectly legitimate
  4625. * for an implementation not to want anything to happen on a step
  4626. */
  4627. class HTMLPurifier_Filter
  4628. {
  4629. /**
  4630. * Name of the filter for identification purposes.
  4631. * @type string
  4632. */
  4633. public $name;
  4634. /**
  4635. * Pre-processor function, handles HTML before HTML Purifier
  4636. * @param string $html
  4637. * @param HTMLPurifier_Config $config
  4638. * @param HTMLPurifier_Context $context
  4639. * @return string
  4640. */
  4641. public function preFilter($html, $config, $context)
  4642. {
  4643. return $html;
  4644. }
  4645. /**
  4646. * Post-processor function, handles HTML after HTML Purifier
  4647. * @param string $html
  4648. * @param HTMLPurifier_Config $config
  4649. * @param HTMLPurifier_Context $context
  4650. * @return string
  4651. */
  4652. public function postFilter($html, $config, $context)
  4653. {
  4654. return $html;
  4655. }
  4656. }
  4657. /**
  4658. * Generates HTML from tokens.
  4659. * @todo Refactor interface so that configuration/context is determined
  4660. * upon instantiation, no need for messy generateFromTokens() calls
  4661. * @todo Make some of the more internal functions protected, and have
  4662. * unit tests work around that
  4663. */
  4664. class HTMLPurifier_Generator
  4665. {
  4666. /**
  4667. * Whether or not generator should produce XML output.
  4668. * @type bool
  4669. */
  4670. private $_xhtml = true;
  4671. /**
  4672. * :HACK: Whether or not generator should comment the insides of <script> tags.
  4673. * @type bool
  4674. */
  4675. private $_scriptFix = false;
  4676. /**
  4677. * Cache of HTMLDefinition during HTML output to determine whether or
  4678. * not attributes should be minimized.
  4679. * @type HTMLPurifier_HTMLDefinition
  4680. */
  4681. private $_def;
  4682. /**
  4683. * Cache of %Output.SortAttr.
  4684. * @type bool
  4685. */
  4686. private $_sortAttr;
  4687. /**
  4688. * Cache of %Output.FlashCompat.
  4689. * @type bool
  4690. */
  4691. private $_flashCompat;
  4692. /**
  4693. * Cache of %Output.FixInnerHTML.
  4694. * @type bool
  4695. */
  4696. private $_innerHTMLFix;
  4697. /**
  4698. * Stack for keeping track of object information when outputting IE
  4699. * compatibility code.
  4700. * @type array
  4701. */
  4702. private $_flashStack = array();
  4703. /**
  4704. * Configuration for the generator
  4705. * @type HTMLPurifier_Config
  4706. */
  4707. protected $config;
  4708. /**
  4709. * @param HTMLPurifier_Config $config
  4710. * @param HTMLPurifier_Context $context
  4711. */
  4712. public function __construct($config, $context)
  4713. {
  4714. $this->config = $config;
  4715. $this->_scriptFix = $config->get('Output.CommentScriptContents');
  4716. $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
  4717. $this->_sortAttr = $config->get('Output.SortAttr');
  4718. $this->_flashCompat = $config->get('Output.FlashCompat');
  4719. $this->_def = $config->getHTMLDefinition();
  4720. $this->_xhtml = $this->_def->doctype->xml;
  4721. }
  4722. /**
  4723. * Generates HTML from an array of tokens.
  4724. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
  4725. * @return string Generated HTML
  4726. */
  4727. public function generateFromTokens($tokens)
  4728. {
  4729. if (!$tokens) {
  4730. return '';
  4731. }
  4732. // Basic algorithm
  4733. $html = '';
  4734. for ($i = 0, $size = count($tokens); $i < $size; $i++) {
  4735. if ($this->_scriptFix && $tokens[$i]->name === 'script'
  4736. && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
  4737. // script special case
  4738. // the contents of the script block must be ONE token
  4739. // for this to work.
  4740. $html .= $this->generateFromToken($tokens[$i++]);
  4741. $html .= $this->generateScriptFromToken($tokens[$i++]);
  4742. }
  4743. $html .= $this->generateFromToken($tokens[$i]);
  4744. }
  4745. // Tidy cleanup
  4746. if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
  4747. $tidy = new Tidy;
  4748. $tidy->parseString(
  4749. $html,
  4750. array(
  4751. 'indent'=> true,
  4752. 'output-xhtml' => $this->_xhtml,
  4753. 'show-body-only' => true,
  4754. 'indent-spaces' => 2,
  4755. 'wrap' => 68,
  4756. ),
  4757. 'utf8'
  4758. );
  4759. $tidy->cleanRepair();
  4760. $html = (string) $tidy; // explicit cast necessary
  4761. }
  4762. // Normalize newlines to system defined value
  4763. if ($this->config->get('Core.NormalizeNewlines')) {
  4764. $nl = $this->config->get('Output.Newline');
  4765. if ($nl === null) {
  4766. $nl = PHP_EOL;
  4767. }
  4768. if ($nl !== "\n") {
  4769. $html = str_replace("\n", $nl, $html);
  4770. }
  4771. }
  4772. return $html;
  4773. }
  4774. /**
  4775. * Generates HTML from a single token.
  4776. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4777. * @return string Generated HTML
  4778. */
  4779. public function generateFromToken($token)
  4780. {
  4781. if (!$token instanceof HTMLPurifier_Token) {
  4782. trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
  4783. return '';
  4784. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  4785. $attr = $this->generateAttributes($token->attr, $token->name);
  4786. if ($this->_flashCompat) {
  4787. if ($token->name == "object") {
  4788. $flash = new stdClass();
  4789. $flash->attr = $token->attr;
  4790. $flash->param = array();
  4791. $this->_flashStack[] = $flash;
  4792. }
  4793. }
  4794. return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
  4795. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4796. $_extra = '';
  4797. if ($this->_flashCompat) {
  4798. if ($token->name == "object" && !empty($this->_flashStack)) {
  4799. // doesn't do anything for now
  4800. }
  4801. }
  4802. return $_extra . '</' . $token->name . '>';
  4803. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  4804. if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
  4805. $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
  4806. }
  4807. $attr = $this->generateAttributes($token->attr, $token->name);
  4808. return '<' . $token->name . ($attr ? ' ' : '') . $attr .
  4809. ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
  4810. . '>';
  4811. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  4812. return $this->escape($token->data, ENT_NOQUOTES);
  4813. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  4814. return '<!--' . $token->data . '-->';
  4815. } else {
  4816. return '';
  4817. }
  4818. }
  4819. /**
  4820. * Special case processor for the contents of script tags
  4821. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4822. * @return string
  4823. * @warning This runs into problems if there's already a literal
  4824. * --> somewhere inside the script contents.
  4825. */
  4826. public function generateScriptFromToken($token)
  4827. {
  4828. if (!$token instanceof HTMLPurifier_Token_Text) {
  4829. return $this->generateFromToken($token);
  4830. }
  4831. // Thanks <http://lachy.id.au/log/2005/05/script-comments>
  4832. $data = preg_replace('#//\s*$#', '', $token->data);
  4833. return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
  4834. }
  4835. /**
  4836. * Generates attribute declarations from attribute array.
  4837. * @note This does not include the leading or trailing space.
  4838. * @param array $assoc_array_of_attributes Attribute array
  4839. * @param string $element Name of element attributes are for, used to check
  4840. * attribute minimization.
  4841. * @return string Generated HTML fragment for insertion.
  4842. */
  4843. public function generateAttributes($assoc_array_of_attributes, $element = '')
  4844. {
  4845. $html = '';
  4846. if ($this->_sortAttr) {
  4847. ksort($assoc_array_of_attributes);
  4848. }
  4849. foreach ($assoc_array_of_attributes as $key => $value) {
  4850. if (!$this->_xhtml) {
  4851. // Remove namespaced attributes
  4852. if (strpos($key, ':') !== false) {
  4853. continue;
  4854. }
  4855. // Check if we should minimize the attribute: val="val" -> val
  4856. if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
  4857. $html .= $key . ' ';
  4858. continue;
  4859. }
  4860. }
  4861. // Workaround for Internet Explorer innerHTML bug.
  4862. // Essentially, Internet Explorer, when calculating
  4863. // innerHTML, omits quotes if there are no instances of
  4864. // angled brackets, quotes or spaces. However, when parsing
  4865. // HTML (for example, when you assign to innerHTML), it
  4866. // treats backticks as quotes. Thus,
  4867. // <img alt="``" />
  4868. // becomes
  4869. // <img alt=`` />
  4870. // becomes
  4871. // <img alt='' />
  4872. // Fortunately, all we need to do is trigger an appropriate
  4873. // quoting style, which we do by adding an extra space.
  4874. // This also is consistent with the W3C spec, which states
  4875. // that user agents may ignore leading or trailing
  4876. // whitespace (in fact, most don't, at least for attributes
  4877. // like alt, but an extra space at the end is barely
  4878. // noticeable). Still, we have a configuration knob for
  4879. // this, since this transformation is not necesary if you
  4880. // don't process user input with innerHTML or you don't plan
  4881. // on supporting Internet Explorer.
  4882. if ($this->_innerHTMLFix) {
  4883. if (strpos($value, '`') !== false) {
  4884. // check if correct quoting style would not already be
  4885. // triggered
  4886. if (strcspn($value, '"\' <>') === strlen($value)) {
  4887. // protect!
  4888. $value .= ' ';
  4889. }
  4890. }
  4891. }
  4892. $html .= $key.'="'.$this->escape($value).'" ';
  4893. }
  4894. return rtrim($html);
  4895. }
  4896. /**
  4897. * Escapes raw text data.
  4898. * @todo This really ought to be protected, but until we have a facility
  4899. * for properly generating HTML here w/o using tokens, it stays
  4900. * public.
  4901. * @param string $string String data to escape for HTML.
  4902. * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
  4903. * permissible for non-attribute output.
  4904. * @return string escaped data.
  4905. */
  4906. public function escape($string, $quote = null)
  4907. {
  4908. // Workaround for APC bug on Mac Leopard reported by sidepodcast
  4909. // http://htmlpurifier.org/phorum/read.php?3,4823,4846
  4910. if ($quote === null) {
  4911. $quote = ENT_COMPAT;
  4912. }
  4913. return htmlspecialchars($string, $quote, 'UTF-8');
  4914. }
  4915. }
  4916. /**
  4917. * Definition of the purified HTML that describes allowed children,
  4918. * attributes, and many other things.
  4919. *
  4920. * Conventions:
  4921. *
  4922. * All member variables that are prefixed with info
  4923. * (including the main $info array) are used by HTML Purifier internals
  4924. * and should not be directly edited when customizing the HTMLDefinition.
  4925. * They can usually be set via configuration directives or custom
  4926. * modules.
  4927. *
  4928. * On the other hand, member variables without the info prefix are used
  4929. * internally by the HTMLDefinition and MUST NOT be used by other HTML
  4930. * Purifier internals. Many of them, however, are public, and may be
  4931. * edited by userspace code to tweak the behavior of HTMLDefinition.
  4932. *
  4933. * @note This class is inspected by Printer_HTMLDefinition; please
  4934. * update that class if things here change.
  4935. *
  4936. * @warning Directives that change this object's structure must be in
  4937. * the HTML or Attr namespace!
  4938. */
  4939. class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
  4940. {
  4941. // FULLY-PUBLIC VARIABLES ---------------------------------------------
  4942. /**
  4943. * Associative array of element names to HTMLPurifier_ElementDef.
  4944. * @type HTMLPurifier_ElementDef[]
  4945. */
  4946. public $info = array();
  4947. /**
  4948. * Associative array of global attribute name to attribute definition.
  4949. * @type array
  4950. */
  4951. public $info_global_attr = array();
  4952. /**
  4953. * String name of parent element HTML will be going into.
  4954. * @type string
  4955. */
  4956. public $info_parent = 'div';
  4957. /**
  4958. * Definition for parent element, allows parent element to be a
  4959. * tag that's not allowed inside the HTML fragment.
  4960. * @type HTMLPurifier_ElementDef
  4961. */
  4962. public $info_parent_def;
  4963. /**
  4964. * String name of element used to wrap inline elements in block context.
  4965. * @type string
  4966. * @note This is rarely used except for BLOCKQUOTEs in strict mode
  4967. */
  4968. public $info_block_wrapper = 'p';
  4969. /**
  4970. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  4971. * @type array
  4972. */
  4973. public $info_tag_transform = array();
  4974. /**
  4975. * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
  4976. * @type HTMLPurifier_AttrTransform[]
  4977. */
  4978. public $info_attr_transform_pre = array();
  4979. /**
  4980. * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
  4981. * @type HTMLPurifier_AttrTransform[]
  4982. */
  4983. public $info_attr_transform_post = array();
  4984. /**
  4985. * Nested lookup array of content set name (Block, Inline) to
  4986. * element name to whether or not it belongs in that content set.
  4987. * @type array
  4988. */
  4989. public $info_content_sets = array();
  4990. /**
  4991. * Indexed list of HTMLPurifier_Injector to be used.
  4992. * @type HTMLPurifier_Injector[]
  4993. */
  4994. public $info_injector = array();
  4995. /**
  4996. * Doctype object
  4997. * @type HTMLPurifier_Doctype
  4998. */
  4999. public $doctype;
  5000. // RAW CUSTOMIZATION STUFF --------------------------------------------
  5001. /**
  5002. * Adds a custom attribute to a pre-existing element
  5003. * @note This is strictly convenience, and does not have a corresponding
  5004. * method in HTMLPurifier_HTMLModule
  5005. * @param string $element_name Element name to add attribute to
  5006. * @param string $attr_name Name of attribute
  5007. * @param mixed $def Attribute definition, can be string or object, see
  5008. * HTMLPurifier_AttrTypes for details
  5009. */
  5010. public function addAttribute($element_name, $attr_name, $def)
  5011. {
  5012. $module = $this->getAnonymousModule();
  5013. if (!isset($module->info[$element_name])) {
  5014. $element = $module->addBlankElement($element_name);
  5015. } else {
  5016. $element = $module->info[$element_name];
  5017. }
  5018. $element->attr[$attr_name] = $def;
  5019. }
  5020. /**
  5021. * Adds a custom element to your HTML definition
  5022. * @see HTMLPurifier_HTMLModule::addElement() for detailed
  5023. * parameter and return value descriptions.
  5024. */
  5025. public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
  5026. {
  5027. $module = $this->getAnonymousModule();
  5028. // assume that if the user is calling this, the element
  5029. // is safe. This may not be a good idea
  5030. $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
  5031. return $element;
  5032. }
  5033. /**
  5034. * Adds a blank element to your HTML definition, for overriding
  5035. * existing behavior
  5036. * @param string $element_name
  5037. * @return HTMLPurifier_ElementDef
  5038. * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
  5039. * parameter and return value descriptions.
  5040. */
  5041. public function addBlankElement($element_name)
  5042. {
  5043. $module = $this->getAnonymousModule();
  5044. $element = $module->addBlankElement($element_name);
  5045. return $element;
  5046. }
  5047. /**
  5048. * Retrieves a reference to the anonymous module, so you can
  5049. * bust out advanced features without having to make your own
  5050. * module.
  5051. * @return HTMLPurifier_HTMLModule
  5052. */
  5053. public function getAnonymousModule()
  5054. {
  5055. if (!$this->_anonModule) {
  5056. $this->_anonModule = new HTMLPurifier_HTMLModule();
  5057. $this->_anonModule->name = 'Anonymous';
  5058. }
  5059. return $this->_anonModule;
  5060. }
  5061. private $_anonModule = null;
  5062. // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
  5063. /**
  5064. * @type string
  5065. */
  5066. public $type = 'HTML';
  5067. /**
  5068. * @type HTMLPurifier_HTMLModuleManager
  5069. */
  5070. public $manager;
  5071. /**
  5072. * Performs low-cost, preliminary initialization.
  5073. */
  5074. public function __construct()
  5075. {
  5076. $this->manager = new HTMLPurifier_HTMLModuleManager();
  5077. }
  5078. /**
  5079. * @param HTMLPurifier_Config $config
  5080. */
  5081. protected function doSetup($config)
  5082. {
  5083. $this->processModules($config);
  5084. $this->setupConfigStuff($config);
  5085. unset($this->manager);
  5086. // cleanup some of the element definitions
  5087. foreach ($this->info as $k => $v) {
  5088. unset($this->info[$k]->content_model);
  5089. unset($this->info[$k]->content_model_type);
  5090. }
  5091. }
  5092. /**
  5093. * Extract out the information from the manager
  5094. * @param HTMLPurifier_Config $config
  5095. */
  5096. protected function processModules($config)
  5097. {
  5098. if ($this->_anonModule) {
  5099. // for user specific changes
  5100. // this is late-loaded so we don't have to deal with PHP4
  5101. // reference wonky-ness
  5102. $this->manager->addModule($this->_anonModule);
  5103. unset($this->_anonModule);
  5104. }
  5105. $this->manager->setup($config);
  5106. $this->doctype = $this->manager->doctype;
  5107. foreach ($this->manager->modules as $module) {
  5108. foreach ($module->info_tag_transform as $k => $v) {
  5109. if ($v === false) {
  5110. unset($this->info_tag_transform[$k]);
  5111. } else {
  5112. $this->info_tag_transform[$k] = $v;
  5113. }
  5114. }
  5115. foreach ($module->info_attr_transform_pre as $k => $v) {
  5116. if ($v === false) {
  5117. unset($this->info_attr_transform_pre[$k]);
  5118. } else {
  5119. $this->info_attr_transform_pre[$k] = $v;
  5120. }
  5121. }
  5122. foreach ($module->info_attr_transform_post as $k => $v) {
  5123. if ($v === false) {
  5124. unset($this->info_attr_transform_post[$k]);
  5125. } else {
  5126. $this->info_attr_transform_post[$k] = $v;
  5127. }
  5128. }
  5129. foreach ($module->info_injector as $k => $v) {
  5130. if ($v === false) {
  5131. unset($this->info_injector[$k]);
  5132. } else {
  5133. $this->info_injector[$k] = $v;
  5134. }
  5135. }
  5136. }
  5137. $this->info = $this->manager->getElements();
  5138. $this->info_content_sets = $this->manager->contentSets->lookup;
  5139. }
  5140. /**
  5141. * Sets up stuff based on config. We need a better way of doing this.
  5142. * @param HTMLPurifier_Config $config
  5143. */
  5144. protected function setupConfigStuff($config)
  5145. {
  5146. $block_wrapper = $config->get('HTML.BlockWrapper');
  5147. if (isset($this->info_content_sets['Block'][$block_wrapper])) {
  5148. $this->info_block_wrapper = $block_wrapper;
  5149. } else {
  5150. trigger_error(
  5151. 'Cannot use non-block element as block wrapper',
  5152. E_USER_ERROR
  5153. );
  5154. }
  5155. $parent = $config->get('HTML.Parent');
  5156. $def = $this->manager->getElement($parent, true);
  5157. if ($def) {
  5158. $this->info_parent = $parent;
  5159. $this->info_parent_def = $def;
  5160. } else {
  5161. trigger_error(
  5162. 'Cannot use unrecognized element as parent',
  5163. E_USER_ERROR
  5164. );
  5165. $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
  5166. }
  5167. // support template text
  5168. $support = "(for information on implementing this, see the support forums) ";
  5169. // setup allowed elements -----------------------------------------
  5170. $allowed_elements = $config->get('HTML.AllowedElements');
  5171. $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
  5172. if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
  5173. $allowed = $config->get('HTML.Allowed');
  5174. if (is_string($allowed)) {
  5175. list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
  5176. }
  5177. }
  5178. if (is_array($allowed_elements)) {
  5179. foreach ($this->info as $name => $d) {
  5180. if (!isset($allowed_elements[$name])) {
  5181. unset($this->info[$name]);
  5182. }
  5183. unset($allowed_elements[$name]);
  5184. }
  5185. // emit errors
  5186. foreach ($allowed_elements as $element => $d) {
  5187. $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
  5188. trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
  5189. }
  5190. }
  5191. // setup allowed attributes ---------------------------------------
  5192. $allowed_attributes_mutable = $allowed_attributes; // by copy!
  5193. if (is_array($allowed_attributes)) {
  5194. // This actually doesn't do anything, since we went away from
  5195. // global attributes. It's possible that userland code uses
  5196. // it, but HTMLModuleManager doesn't!
  5197. foreach ($this->info_global_attr as $attr => $x) {
  5198. $keys = array($attr, "*@$attr", "*.$attr");
  5199. $delete = true;
  5200. foreach ($keys as $key) {
  5201. if ($delete && isset($allowed_attributes[$key])) {
  5202. $delete = false;
  5203. }
  5204. if (isset($allowed_attributes_mutable[$key])) {
  5205. unset($allowed_attributes_mutable[$key]);
  5206. }
  5207. }
  5208. if ($delete) {
  5209. unset($this->info_global_attr[$attr]);
  5210. }
  5211. }
  5212. foreach ($this->info as $tag => $info) {
  5213. foreach ($info->attr as $attr => $x) {
  5214. $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
  5215. $delete = true;
  5216. foreach ($keys as $key) {
  5217. if ($delete && isset($allowed_attributes[$key])) {
  5218. $delete = false;
  5219. }
  5220. if (isset($allowed_attributes_mutable[$key])) {
  5221. unset($allowed_attributes_mutable[$key]);
  5222. }
  5223. }
  5224. if ($delete) {
  5225. if ($this->info[$tag]->attr[$attr]->required) {
  5226. trigger_error(
  5227. "Required attribute '$attr' in element '$tag' " .
  5228. "was not allowed, which means '$tag' will not be allowed either",
  5229. E_USER_WARNING
  5230. );
  5231. }
  5232. unset($this->info[$tag]->attr[$attr]);
  5233. }
  5234. }
  5235. }
  5236. // emit errors
  5237. foreach ($allowed_attributes_mutable as $elattr => $d) {
  5238. $bits = preg_split('/[.@]/', $elattr, 2);
  5239. $c = count($bits);
  5240. switch ($c) {
  5241. case 2:
  5242. if ($bits[0] !== '*') {
  5243. $element = htmlspecialchars($bits[0]);
  5244. $attribute = htmlspecialchars($bits[1]);
  5245. if (!isset($this->info[$element])) {
  5246. trigger_error(
  5247. "Cannot allow attribute '$attribute' if element " .
  5248. "'$element' is not allowed/supported $support"
  5249. );
  5250. } else {
  5251. trigger_error(
  5252. "Attribute '$attribute' in element '$element' not supported $support",
  5253. E_USER_WARNING
  5254. );
  5255. }
  5256. break;
  5257. }
  5258. // otherwise fall through
  5259. case 1:
  5260. $attribute = htmlspecialchars($bits[0]);
  5261. trigger_error(
  5262. "Global attribute '$attribute' is not ".
  5263. "supported in any elements $support",
  5264. E_USER_WARNING
  5265. );
  5266. break;
  5267. }
  5268. }
  5269. }
  5270. // setup forbidden elements ---------------------------------------
  5271. $forbidden_elements = $config->get('HTML.ForbiddenElements');
  5272. $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
  5273. foreach ($this->info as $tag => $info) {
  5274. if (isset($forbidden_elements[$tag])) {
  5275. unset($this->info[$tag]);
  5276. continue;
  5277. }
  5278. foreach ($info->attr as $attr => $x) {
  5279. if (isset($forbidden_attributes["$tag@$attr"]) ||
  5280. isset($forbidden_attributes["*@$attr"]) ||
  5281. isset($forbidden_attributes[$attr])
  5282. ) {
  5283. unset($this->info[$tag]->attr[$attr]);
  5284. continue;
  5285. } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
  5286. // $tag.$attr are not user supplied, so no worries!
  5287. trigger_error(
  5288. "Error with $tag.$attr: tag.attr syntax not supported for " .
  5289. "HTML.ForbiddenAttributes; use tag@attr instead",
  5290. E_USER_WARNING
  5291. );
  5292. }
  5293. }
  5294. }
  5295. foreach ($forbidden_attributes as $key => $v) {
  5296. if (strlen($key) < 2) {
  5297. continue;
  5298. }
  5299. if ($key[0] != '*') {
  5300. continue;
  5301. }
  5302. if ($key[1] == '.') {
  5303. trigger_error(
  5304. "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
  5305. E_USER_WARNING
  5306. );
  5307. }
  5308. }
  5309. // setup injectors -----------------------------------------------------
  5310. foreach ($this->info_injector as $i => $injector) {
  5311. if ($injector->checkNeeded($config) !== false) {
  5312. // remove injector that does not have it's required
  5313. // elements/attributes present, and is thus not needed.
  5314. unset($this->info_injector[$i]);
  5315. }
  5316. }
  5317. }
  5318. /**
  5319. * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
  5320. * separate lists for processing. Format is element[attr1|attr2],element2...
  5321. * @warning Although it's largely drawn from TinyMCE's implementation,
  5322. * it is different, and you'll probably have to modify your lists
  5323. * @param array $list String list to parse
  5324. * @return array
  5325. * @todo Give this its own class, probably static interface
  5326. */
  5327. public function parseTinyMCEAllowedList($list)
  5328. {
  5329. $list = str_replace(array(' ', "\t"), '', $list);
  5330. $elements = array();
  5331. $attributes = array();
  5332. $chunks = preg_split('/(,|[\n\r]+)/', $list);
  5333. foreach ($chunks as $chunk) {
  5334. if (empty($chunk)) {
  5335. continue;
  5336. }
  5337. // remove TinyMCE element control characters
  5338. if (!strpos($chunk, '[')) {
  5339. $element = $chunk;
  5340. $attr = false;
  5341. } else {
  5342. list($element, $attr) = explode('[', $chunk);
  5343. }
  5344. if ($element !== '*') {
  5345. $elements[$element] = true;
  5346. }
  5347. if (!$attr) {
  5348. continue;
  5349. }
  5350. $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
  5351. $attr = explode('|', $attr);
  5352. foreach ($attr as $key) {
  5353. $attributes["$element.$key"] = true;
  5354. }
  5355. }
  5356. return array($elements, $attributes);
  5357. }
  5358. }
  5359. /**
  5360. * Represents an XHTML 1.1 module, with information on elements, tags
  5361. * and attributes.
  5362. * @note Even though this is technically XHTML 1.1, it is also used for
  5363. * regular HTML parsing. We are using modulization as a convenient
  5364. * way to represent the internals of HTMLDefinition, and our
  5365. * implementation is by no means conforming and does not directly
  5366. * use the normative DTDs or XML schemas.
  5367. * @note The public variables in a module should almost directly
  5368. * correspond to the variables in HTMLPurifier_HTMLDefinition.
  5369. * However, the prefix info carries no special meaning in these
  5370. * objects (include it anyway if that's the correspondence though).
  5371. * @todo Consider making some member functions protected
  5372. */
  5373. class HTMLPurifier_HTMLModule
  5374. {
  5375. // -- Overloadable ----------------------------------------------------
  5376. /**
  5377. * Short unique string identifier of the module.
  5378. * @type string
  5379. */
  5380. public $name;
  5381. /**
  5382. * Informally, a list of elements this module changes.
  5383. * Not used in any significant way.
  5384. * @type array
  5385. */
  5386. public $elements = array();
  5387. /**
  5388. * Associative array of element names to element definitions.
  5389. * Some definitions may be incomplete, to be merged in later
  5390. * with the full definition.
  5391. * @type array
  5392. */
  5393. public $info = array();
  5394. /**
  5395. * Associative array of content set names to content set additions.
  5396. * This is commonly used to, say, add an A element to the Inline
  5397. * content set. This corresponds to an internal variable $content_sets
  5398. * and NOT info_content_sets member variable of HTMLDefinition.
  5399. * @type array
  5400. */
  5401. public $content_sets = array();
  5402. /**
  5403. * Associative array of attribute collection names to attribute
  5404. * collection additions. More rarely used for adding attributes to
  5405. * the global collections. Example is the StyleAttribute module adding
  5406. * the style attribute to the Core. Corresponds to HTMLDefinition's
  5407. * attr_collections->info, since the object's data is only info,
  5408. * with extra behavior associated with it.
  5409. * @type array
  5410. */
  5411. public $attr_collections = array();
  5412. /**
  5413. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  5414. * @type array
  5415. */
  5416. public $info_tag_transform = array();
  5417. /**
  5418. * List of HTMLPurifier_AttrTransform to be performed before validation.
  5419. * @type array
  5420. */
  5421. public $info_attr_transform_pre = array();
  5422. /**
  5423. * List of HTMLPurifier_AttrTransform to be performed after validation.
  5424. * @type array
  5425. */
  5426. public $info_attr_transform_post = array();
  5427. /**
  5428. * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
  5429. * An injector will only be invoked if all of it's pre-requisites are met;
  5430. * if an injector fails setup, there will be no error; it will simply be
  5431. * silently disabled.
  5432. * @type array
  5433. */
  5434. public $info_injector = array();
  5435. /**
  5436. * Boolean flag that indicates whether or not getChildDef is implemented.
  5437. * For optimization reasons: may save a call to a function. Be sure
  5438. * to set it if you do implement getChildDef(), otherwise it will have
  5439. * no effect!
  5440. * @type bool
  5441. */
  5442. public $defines_child_def = false;
  5443. /**
  5444. * Boolean flag whether or not this module is safe. If it is not safe, all
  5445. * of its members are unsafe. Modules are safe by default (this might be
  5446. * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
  5447. * which is based off of safe HTML, to explicitly say, "This is safe," even
  5448. * though there are modules which are "unsafe")
  5449. *
  5450. * @type bool
  5451. * @note Previously, safety could be applied at an element level granularity.
  5452. * We've removed this ability, so in order to add "unsafe" elements
  5453. * or attributes, a dedicated module with this property set to false
  5454. * must be used.
  5455. */
  5456. public $safe = true;
  5457. /**
  5458. * Retrieves a proper HTMLPurifier_ChildDef subclass based on
  5459. * content_model and content_model_type member variables of
  5460. * the HTMLPurifier_ElementDef class. There is a similar function
  5461. * in HTMLPurifier_HTMLDefinition.
  5462. * @param HTMLPurifier_ElementDef $def
  5463. * @return HTMLPurifier_ChildDef subclass
  5464. */
  5465. public function getChildDef($def)
  5466. {
  5467. return false;
  5468. }
  5469. // -- Convenience -----------------------------------------------------
  5470. /**
  5471. * Convenience function that sets up a new element
  5472. * @param string $element Name of element to add
  5473. * @param string|bool $type What content set should element be registered to?
  5474. * Set as false to skip this step.
  5475. * @param string|HTMLPurifier_ChildDef $contents Allowed children in form of:
  5476. * "$content_model_type: $content_model"
  5477. * @param array|string $attr_includes What attribute collections to register to
  5478. * element?
  5479. * @param array $attr What unique attributes does the element define?
  5480. * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
  5481. * @return HTMLPurifier_ElementDef Created element definition object, so you
  5482. * can set advanced parameters
  5483. */
  5484. public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
  5485. {
  5486. $this->elements[] = $element;
  5487. // parse content_model
  5488. list($content_model_type, $content_model) = $this->parseContents($contents);
  5489. // merge in attribute inclusions
  5490. $this->mergeInAttrIncludes($attr, $attr_includes);
  5491. // add element to content sets
  5492. if ($type) {
  5493. $this->addElementToContentSet($element, $type);
  5494. }
  5495. // create element
  5496. $this->info[$element] = HTMLPurifier_ElementDef::create(
  5497. $content_model,
  5498. $content_model_type,
  5499. $attr
  5500. );
  5501. // literal object $contents means direct child manipulation
  5502. if (!is_string($contents)) {
  5503. $this->info[$element]->child = $contents;
  5504. }
  5505. return $this->info[$element];
  5506. }
  5507. /**
  5508. * Convenience function that creates a totally blank, non-standalone
  5509. * element.
  5510. * @param string $element Name of element to create
  5511. * @return HTMLPurifier_ElementDef Created element
  5512. */
  5513. public function addBlankElement($element)
  5514. {
  5515. if (!isset($this->info[$element])) {
  5516. $this->elements[] = $element;
  5517. $this->info[$element] = new HTMLPurifier_ElementDef();
  5518. $this->info[$element]->standalone = false;
  5519. } else {
  5520. trigger_error("Definition for $element already exists in module, cannot redefine");
  5521. }
  5522. return $this->info[$element];
  5523. }
  5524. /**
  5525. * Convenience function that registers an element to a content set
  5526. * @param string $element Element to register
  5527. * @param string $type Name content set (warning: case sensitive, usually upper-case
  5528. * first letter)
  5529. */
  5530. public function addElementToContentSet($element, $type)
  5531. {
  5532. if (!isset($this->content_sets[$type])) {
  5533. $this->content_sets[$type] = '';
  5534. } else {
  5535. $this->content_sets[$type] .= ' | ';
  5536. }
  5537. $this->content_sets[$type] .= $element;
  5538. }
  5539. /**
  5540. * Convenience function that transforms single-string contents
  5541. * into separate content model and content model type
  5542. * @param string $contents Allowed children in form of:
  5543. * "$content_model_type: $content_model"
  5544. * @return array
  5545. * @note If contents is an object, an array of two nulls will be
  5546. * returned, and the callee needs to take the original $contents
  5547. * and use it directly.
  5548. */
  5549. public function parseContents($contents)
  5550. {
  5551. if (!is_string($contents)) {
  5552. return array(null, null);
  5553. } // defer
  5554. switch ($contents) {
  5555. // check for shorthand content model forms
  5556. case 'Empty':
  5557. return array('empty', '');
  5558. case 'Inline':
  5559. return array('optional', 'Inline | #PCDATA');
  5560. case 'Flow':
  5561. return array('optional', 'Flow | #PCDATA');
  5562. }
  5563. list($content_model_type, $content_model) = explode(':', $contents);
  5564. $content_model_type = strtolower(trim($content_model_type));
  5565. $content_model = trim($content_model);
  5566. return array($content_model_type, $content_model);
  5567. }
  5568. /**
  5569. * Convenience function that merges a list of attribute includes into
  5570. * an attribute array.
  5571. * @param array $attr Reference to attr array to modify
  5572. * @param array $attr_includes Array of includes / string include to merge in
  5573. */
  5574. public function mergeInAttrIncludes(&$attr, $attr_includes)
  5575. {
  5576. if (!is_array($attr_includes)) {
  5577. if (empty($attr_includes)) {
  5578. $attr_includes = array();
  5579. } else {
  5580. $attr_includes = array($attr_includes);
  5581. }
  5582. }
  5583. $attr[0] = $attr_includes;
  5584. }
  5585. /**
  5586. * Convenience function that generates a lookup table with boolean
  5587. * true as value.
  5588. * @param string $list List of values to turn into a lookup
  5589. * @note You can also pass an arbitrary number of arguments in
  5590. * place of the regular argument
  5591. * @return array array equivalent of list
  5592. */
  5593. public function makeLookup($list)
  5594. {
  5595. if (is_string($list)) {
  5596. $list = func_get_args();
  5597. }
  5598. $ret = array();
  5599. foreach ($list as $value) {
  5600. if (is_null($value)) {
  5601. continue;
  5602. }
  5603. $ret[$value] = true;
  5604. }
  5605. return $ret;
  5606. }
  5607. /**
  5608. * Lazy load construction of the module after determining whether
  5609. * or not it's needed, and also when a finalized configuration object
  5610. * is available.
  5611. * @param HTMLPurifier_Config $config
  5612. */
  5613. public function setup($config)
  5614. {
  5615. }
  5616. }
  5617. class HTMLPurifier_HTMLModuleManager
  5618. {
  5619. /**
  5620. * @type HTMLPurifier_DoctypeRegistry
  5621. */
  5622. public $doctypes;
  5623. /**
  5624. * Instance of current doctype.
  5625. * @type string
  5626. */
  5627. public $doctype;
  5628. /**
  5629. * @type HTMLPurifier_AttrTypes
  5630. */
  5631. public $attrTypes;
  5632. /**
  5633. * Active instances of modules for the specified doctype are
  5634. * indexed, by name, in this array.
  5635. * @type HTMLPurifier_HTMLModule[]
  5636. */
  5637. public $modules = array();
  5638. /**
  5639. * Array of recognized HTMLPurifier_HTMLModule instances,
  5640. * indexed by module's class name. This array is usually lazy loaded, but a
  5641. * user can overload a module by pre-emptively registering it.
  5642. * @type HTMLPurifier_HTMLModule[]
  5643. */
  5644. public $registeredModules = array();
  5645. /**
  5646. * List of extra modules that were added by the user
  5647. * using addModule(). These get unconditionally merged into the current doctype, whatever
  5648. * it may be.
  5649. * @type HTMLPurifier_HTMLModule[]
  5650. */
  5651. public $userModules = array();
  5652. /**
  5653. * Associative array of element name to list of modules that have
  5654. * definitions for the element; this array is dynamically filled.
  5655. * @type array
  5656. */
  5657. public $elementLookup = array();
  5658. /**
  5659. * List of prefixes we should use for registering small names.
  5660. * @type array
  5661. */
  5662. public $prefixes = array('HTMLPurifier_HTMLModule_');
  5663. /**
  5664. * @type HTMLPurifier_ContentSets
  5665. */
  5666. public $contentSets;
  5667. /**
  5668. * @type HTMLPurifier_AttrCollections
  5669. */
  5670. public $attrCollections;
  5671. /**
  5672. * If set to true, unsafe elements and attributes will be allowed.
  5673. * @type bool
  5674. */
  5675. public $trusted = false;
  5676. public function __construct()
  5677. {
  5678. // editable internal objects
  5679. $this->attrTypes = new HTMLPurifier_AttrTypes();
  5680. $this->doctypes = new HTMLPurifier_DoctypeRegistry();
  5681. // setup basic modules
  5682. $common = array(
  5683. 'CommonAttributes', 'Text', 'Hypertext', 'List',
  5684. 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
  5685. 'StyleAttribute',
  5686. // Unsafe:
  5687. 'Scripting', 'Object', 'Forms',
  5688. // Sorta legacy, but present in strict:
  5689. 'Name',
  5690. );
  5691. $transitional = array('Legacy', 'Target', 'Iframe');
  5692. $xml = array('XMLCommonAttributes');
  5693. $non_xml = array('NonXMLCommonAttributes');
  5694. // setup basic doctypes
  5695. $this->doctypes->register(
  5696. 'HTML 4.01 Transitional',
  5697. false,
  5698. array_merge($common, $transitional, $non_xml),
  5699. array('Tidy_Transitional', 'Tidy_Proprietary'),
  5700. array(),
  5701. '-//W3C//DTD HTML 4.01 Transitional//EN',
  5702. 'http://www.w3.org/TR/html4/loose.dtd'
  5703. );
  5704. $this->doctypes->register(
  5705. 'HTML 4.01 Strict',
  5706. false,
  5707. array_merge($common, $non_xml),
  5708. array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5709. array(),
  5710. '-//W3C//DTD HTML 4.01//EN',
  5711. 'http://www.w3.org/TR/html4/strict.dtd'
  5712. );
  5713. $this->doctypes->register(
  5714. 'XHTML 1.0 Transitional',
  5715. true,
  5716. array_merge($common, $transitional, $xml, $non_xml),
  5717. array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
  5718. array(),
  5719. '-//W3C//DTD XHTML 1.0 Transitional//EN',
  5720. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
  5721. );
  5722. $this->doctypes->register(
  5723. 'XHTML 1.0 Strict',
  5724. true,
  5725. array_merge($common, $xml, $non_xml),
  5726. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5727. array(),
  5728. '-//W3C//DTD XHTML 1.0 Strict//EN',
  5729. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
  5730. );
  5731. $this->doctypes->register(
  5732. 'XHTML 1.1',
  5733. true,
  5734. // Iframe is a real XHTML 1.1 module, despite being
  5735. // "transitional"!
  5736. array_merge($common, $xml, array('Ruby', 'Iframe')),
  5737. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
  5738. array(),
  5739. '-//W3C//DTD XHTML 1.1//EN',
  5740. 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
  5741. );
  5742. }
  5743. /**
  5744. * Registers a module to the recognized module list, useful for
  5745. * overloading pre-existing modules.
  5746. * @param $module Mixed: string module name, with or without
  5747. * HTMLPurifier_HTMLModule prefix, or instance of
  5748. * subclass of HTMLPurifier_HTMLModule.
  5749. * @param $overload Boolean whether or not to overload previous modules.
  5750. * If this is not set, and you do overload a module,
  5751. * HTML Purifier will complain with a warning.
  5752. * @note This function will not call autoload, you must instantiate
  5753. * (and thus invoke) autoload outside the method.
  5754. * @note If a string is passed as a module name, different variants
  5755. * will be tested in this order:
  5756. * - Check for HTMLPurifier_HTMLModule_$name
  5757. * - Check all prefixes with $name in order they were added
  5758. * - Check for literal object name
  5759. * - Throw fatal error
  5760. * If your object name collides with an internal class, specify
  5761. * your module manually. All modules must have been included
  5762. * externally: registerModule will not perform inclusions for you!
  5763. */
  5764. public function registerModule($module, $overload = false)
  5765. {
  5766. if (is_string($module)) {
  5767. // attempt to load the module
  5768. $original_module = $module;
  5769. $ok = false;
  5770. foreach ($this->prefixes as $prefix) {
  5771. $module = $prefix . $original_module;
  5772. if (class_exists($module)) {
  5773. $ok = true;
  5774. break;
  5775. }
  5776. }
  5777. if (!$ok) {
  5778. $module = $original_module;
  5779. if (!class_exists($module)) {
  5780. trigger_error(
  5781. $original_module . ' module does not exist',
  5782. E_USER_ERROR
  5783. );
  5784. return;
  5785. }
  5786. }
  5787. $module = new $module();
  5788. }
  5789. if (empty($module->name)) {
  5790. trigger_error('Module instance of ' . get_class($module) . ' must have name');
  5791. return;
  5792. }
  5793. if (!$overload && isset($this->registeredModules[$module->name])) {
  5794. trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
  5795. }
  5796. $this->registeredModules[$module->name] = $module;
  5797. }
  5798. /**
  5799. * Adds a module to the current doctype by first registering it,
  5800. * and then tacking it on to the active doctype
  5801. */
  5802. public function addModule($module)
  5803. {
  5804. $this->registerModule($module);
  5805. if (is_object($module)) {
  5806. $module = $module->name;
  5807. }
  5808. $this->userModules[] = $module;
  5809. }
  5810. /**
  5811. * Adds a class prefix that registerModule() will use to resolve a
  5812. * string name to a concrete class
  5813. */
  5814. public function addPrefix($prefix)
  5815. {
  5816. $this->prefixes[] = $prefix;
  5817. }
  5818. /**
  5819. * Performs processing on modules, after being called you may
  5820. * use getElement() and getElements()
  5821. * @param HTMLPurifier_Config $config
  5822. */
  5823. public function setup($config)
  5824. {
  5825. $this->trusted = $config->get('HTML.Trusted');
  5826. // generate
  5827. $this->doctype = $this->doctypes->make($config);
  5828. $modules = $this->doctype->modules;
  5829. // take out the default modules that aren't allowed
  5830. $lookup = $config->get('HTML.AllowedModules');
  5831. $special_cases = $config->get('HTML.CoreModules');
  5832. if (is_array($lookup)) {
  5833. foreach ($modules as $k => $m) {
  5834. if (isset($special_cases[$m])) {
  5835. continue;
  5836. }
  5837. if (!isset($lookup[$m])) {
  5838. unset($modules[$k]);
  5839. }
  5840. }
  5841. }
  5842. // custom modules
  5843. if ($config->get('HTML.Proprietary')) {
  5844. $modules[] = 'Proprietary';
  5845. }
  5846. if ($config->get('HTML.SafeObject')) {
  5847. $modules[] = 'SafeObject';
  5848. }
  5849. if ($config->get('HTML.SafeEmbed')) {
  5850. $modules[] = 'SafeEmbed';
  5851. }
  5852. if ($config->get('HTML.SafeScripting') !== array()) {
  5853. $modules[] = 'SafeScripting';
  5854. }
  5855. if ($config->get('HTML.Nofollow')) {
  5856. $modules[] = 'Nofollow';
  5857. }
  5858. if ($config->get('HTML.TargetBlank')) {
  5859. $modules[] = 'TargetBlank';
  5860. }
  5861. // NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank
  5862. // so that its post-attr-transform gets run afterwards.
  5863. if ($config->get('HTML.TargetNoreferrer')) {
  5864. $modules[] = 'TargetNoreferrer';
  5865. }
  5866. if ($config->get('HTML.TargetNoopener')) {
  5867. $modules[] = 'TargetNoopener';
  5868. }
  5869. // merge in custom modules
  5870. $modules = array_merge($modules, $this->userModules);
  5871. foreach ($modules as $module) {
  5872. $this->processModule($module);
  5873. $this->modules[$module]->setup($config);
  5874. }
  5875. foreach ($this->doctype->tidyModules as $module) {
  5876. $this->processModule($module);
  5877. $this->modules[$module]->setup($config);
  5878. }
  5879. // prepare any injectors
  5880. foreach ($this->modules as $module) {
  5881. $n = array();
  5882. foreach ($module->info_injector as $injector) {
  5883. if (!is_object($injector)) {
  5884. $class = "HTMLPurifier_Injector_$injector";
  5885. $injector = new $class;
  5886. }
  5887. $n[$injector->name] = $injector;
  5888. }
  5889. $module->info_injector = $n;
  5890. }
  5891. // setup lookup table based on all valid modules
  5892. foreach ($this->modules as $module) {
  5893. foreach ($module->info as $name => $def) {
  5894. if (!isset($this->elementLookup[$name])) {
  5895. $this->elementLookup[$name] = array();
  5896. }
  5897. $this->elementLookup[$name][] = $module->name;
  5898. }
  5899. }
  5900. // note the different choice
  5901. $this->contentSets = new HTMLPurifier_ContentSets(
  5902. // content set assembly deals with all possible modules,
  5903. // not just ones deemed to be "safe"
  5904. $this->modules
  5905. );
  5906. $this->attrCollections = new HTMLPurifier_AttrCollections(
  5907. $this->attrTypes,
  5908. // there is no way to directly disable a global attribute,
  5909. // but using AllowedAttributes or simply not including
  5910. // the module in your custom doctype should be sufficient
  5911. $this->modules
  5912. );
  5913. }
  5914. /**
  5915. * Takes a module and adds it to the active module collection,
  5916. * registering it if necessary.
  5917. */
  5918. public function processModule($module)
  5919. {
  5920. if (!isset($this->registeredModules[$module]) || is_object($module)) {
  5921. $this->registerModule($module);
  5922. }
  5923. $this->modules[$module] = $this->registeredModules[$module];
  5924. }
  5925. /**
  5926. * Retrieves merged element definitions.
  5927. * @return Array of HTMLPurifier_ElementDef
  5928. */
  5929. public function getElements()
  5930. {
  5931. $elements = array();
  5932. foreach ($this->modules as $module) {
  5933. if (!$this->trusted && !$module->safe) {
  5934. continue;
  5935. }
  5936. foreach ($module->info as $name => $v) {
  5937. if (isset($elements[$name])) {
  5938. continue;
  5939. }
  5940. $elements[$name] = $this->getElement($name);
  5941. }
  5942. }
  5943. // remove dud elements, this happens when an element that
  5944. // appeared to be safe actually wasn't
  5945. foreach ($elements as $n => $v) {
  5946. if ($v === false) {
  5947. unset($elements[$n]);
  5948. }
  5949. }
  5950. return $elements;
  5951. }
  5952. /**
  5953. * Retrieves a single merged element definition
  5954. * @param string $name Name of element
  5955. * @param bool $trusted Boolean trusted overriding parameter: set to true
  5956. * if you want the full version of an element
  5957. * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef
  5958. * @note You may notice that modules are getting iterated over twice (once
  5959. * in getElements() and once here). This
  5960. * is because
  5961. */
  5962. public function getElement($name, $trusted = null)
  5963. {
  5964. if (!isset($this->elementLookup[$name])) {
  5965. return false;
  5966. }
  5967. // setup global state variables
  5968. $def = false;
  5969. if ($trusted === null) {
  5970. $trusted = $this->trusted;
  5971. }
  5972. // iterate through each module that has registered itself to this
  5973. // element
  5974. foreach ($this->elementLookup[$name] as $module_name) {
  5975. $module = $this->modules[$module_name];
  5976. // refuse to create/merge from a module that is deemed unsafe--
  5977. // pretend the module doesn't exist--when trusted mode is not on.
  5978. if (!$trusted && !$module->safe) {
  5979. continue;
  5980. }
  5981. // clone is used because, ideally speaking, the original
  5982. // definition should not be modified. Usually, this will
  5983. // make no difference, but for consistency's sake
  5984. $new_def = clone $module->info[$name];
  5985. if (!$def && $new_def->standalone) {
  5986. $def = $new_def;
  5987. } elseif ($def) {
  5988. // This will occur even if $new_def is standalone. In practice,
  5989. // this will usually result in a full replacement.
  5990. $def->mergeIn($new_def);
  5991. } else {
  5992. // :TODO:
  5993. // non-standalone definitions that don't have a standalone
  5994. // to merge into could be deferred to the end
  5995. // HOWEVER, it is perfectly valid for a non-standalone
  5996. // definition to lack a standalone definition, even
  5997. // after all processing: this allows us to safely
  5998. // specify extra attributes for elements that may not be
  5999. // enabled all in one place. In particular, this might
  6000. // be the case for trusted elements. WARNING: care must
  6001. // be taken that the /extra/ definitions are all safe.
  6002. continue;
  6003. }
  6004. // attribute value expansions
  6005. $this->attrCollections->performInclusions($def->attr);
  6006. $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
  6007. // descendants_are_inline, for ChildDef_Chameleon
  6008. if (is_string($def->content_model) &&
  6009. strpos($def->content_model, 'Inline') !== false) {
  6010. if ($name != 'del' && $name != 'ins') {
  6011. // this is for you, ins/del
  6012. $def->descendants_are_inline = true;
  6013. }
  6014. }
  6015. $this->contentSets->generateChildDef($def, $module);
  6016. }
  6017. // This can occur if there is a blank definition, but no base to
  6018. // mix it in with
  6019. if (!$def) {
  6020. return false;
  6021. }
  6022. // add information on required attributes
  6023. foreach ($def->attr as $attr_name => $attr_def) {
  6024. if ($attr_def->required) {
  6025. $def->required_attr[] = $attr_name;
  6026. }
  6027. }
  6028. return $def;
  6029. }
  6030. }
  6031. /**
  6032. * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
  6033. * @note In Slashdot-speak, dupe means duplicate.
  6034. * @note The default constructor does not accept $config or $context objects:
  6035. * use must use the static build() factory method to perform initialization.
  6036. */
  6037. class HTMLPurifier_IDAccumulator
  6038. {
  6039. /**
  6040. * Lookup table of IDs we've accumulated.
  6041. * @public
  6042. */
  6043. public $ids = array();
  6044. /**
  6045. * Builds an IDAccumulator, also initializing the default blacklist
  6046. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  6047. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  6048. * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator
  6049. */
  6050. public static function build($config, $context)
  6051. {
  6052. $id_accumulator = new HTMLPurifier_IDAccumulator();
  6053. $id_accumulator->load($config->get('Attr.IDBlacklist'));
  6054. return $id_accumulator;
  6055. }
  6056. /**
  6057. * Add an ID to the lookup table.
  6058. * @param string $id ID to be added.
  6059. * @return bool status, true if success, false if there's a dupe
  6060. */
  6061. public function add($id)
  6062. {
  6063. if (isset($this->ids[$id])) {
  6064. return false;
  6065. }
  6066. return $this->ids[$id] = true;
  6067. }
  6068. /**
  6069. * Load a list of IDs into the lookup table
  6070. * @param $array_of_ids Array of IDs to load
  6071. * @note This function doesn't care about duplicates
  6072. */
  6073. public function load($array_of_ids)
  6074. {
  6075. foreach ($array_of_ids as $id) {
  6076. $this->ids[$id] = true;
  6077. }
  6078. }
  6079. }
  6080. /**
  6081. * Injects tokens into the document while parsing for well-formedness.
  6082. * This enables "formatter-like" functionality such as auto-paragraphing,
  6083. * smiley-ification and linkification to take place.
  6084. *
  6085. * A note on how handlers create changes; this is done by assigning a new
  6086. * value to the $token reference. These values can take a variety of forms and
  6087. * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
  6088. * documentation.
  6089. *
  6090. * @todo Allow injectors to request a re-run on their output. This
  6091. * would help if an operation is recursive.
  6092. */
  6093. abstract class HTMLPurifier_Injector
  6094. {
  6095. /**
  6096. * Advisory name of injector, this is for friendly error messages.
  6097. * @type string
  6098. */
  6099. public $name;
  6100. /**
  6101. * @type HTMLPurifier_HTMLDefinition
  6102. */
  6103. protected $htmlDefinition;
  6104. /**
  6105. * Reference to CurrentNesting variable in Context. This is an array
  6106. * list of tokens that we are currently "inside"
  6107. * @type array
  6108. */
  6109. protected $currentNesting;
  6110. /**
  6111. * Reference to current token.
  6112. * @type HTMLPurifier_Token
  6113. */
  6114. protected $currentToken;
  6115. /**
  6116. * Reference to InputZipper variable in Context.
  6117. * @type HTMLPurifier_Zipper
  6118. */
  6119. protected $inputZipper;
  6120. /**
  6121. * Array of elements and attributes this injector creates and therefore
  6122. * need to be allowed by the definition. Takes form of
  6123. * array('element' => array('attr', 'attr2'), 'element2')
  6124. * @type array
  6125. */
  6126. public $needed = array();
  6127. /**
  6128. * Number of elements to rewind backwards (relative).
  6129. * @type bool|int
  6130. */
  6131. protected $rewindOffset = false;
  6132. /**
  6133. * Rewind to a spot to re-perform processing. This is useful if you
  6134. * deleted a node, and now need to see if this change affected any
  6135. * earlier nodes. Rewinding does not affect other injectors, and can
  6136. * result in infinite loops if not used carefully.
  6137. * @param bool|int $offset
  6138. * @warning HTML Purifier will prevent you from fast-forwarding with this
  6139. * function.
  6140. */
  6141. public function rewindOffset($offset)
  6142. {
  6143. $this->rewindOffset = $offset;
  6144. }
  6145. /**
  6146. * Retrieves rewind offset, and then unsets it.
  6147. * @return bool|int
  6148. */
  6149. public function getRewindOffset()
  6150. {
  6151. $r = $this->rewindOffset;
  6152. $this->rewindOffset = false;
  6153. return $r;
  6154. }
  6155. /**
  6156. * Prepares the injector by giving it the config and context objects:
  6157. * this allows references to important variables to be made within
  6158. * the injector. This function also checks if the HTML environment
  6159. * will work with the Injector (see checkNeeded()).
  6160. * @param HTMLPurifier_Config $config
  6161. * @param HTMLPurifier_Context $context
  6162. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  6163. */
  6164. public function prepare($config, $context)
  6165. {
  6166. $this->htmlDefinition = $config->getHTMLDefinition();
  6167. // Even though this might fail, some unit tests ignore this and
  6168. // still test checkNeeded, so be careful. Maybe get rid of that
  6169. // dependency.
  6170. $result = $this->checkNeeded($config);
  6171. if ($result !== false) {
  6172. return $result;
  6173. }
  6174. $this->currentNesting =& $context->get('CurrentNesting');
  6175. $this->currentToken =& $context->get('CurrentToken');
  6176. $this->inputZipper =& $context->get('InputZipper');
  6177. return false;
  6178. }
  6179. /**
  6180. * This function checks if the HTML environment
  6181. * will work with the Injector: if p tags are not allowed, the
  6182. * Auto-Paragraphing injector should not be enabled.
  6183. * @param HTMLPurifier_Config $config
  6184. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  6185. */
  6186. public function checkNeeded($config)
  6187. {
  6188. $def = $config->getHTMLDefinition();
  6189. foreach ($this->needed as $element => $attributes) {
  6190. if (is_int($element)) {
  6191. $element = $attributes;
  6192. }
  6193. if (!isset($def->info[$element])) {
  6194. return $element;
  6195. }
  6196. if (!is_array($attributes)) {
  6197. continue;
  6198. }
  6199. foreach ($attributes as $name) {
  6200. if (!isset($def->info[$element]->attr[$name])) {
  6201. return "$element.$name";
  6202. }
  6203. }
  6204. }
  6205. return false;
  6206. }
  6207. /**
  6208. * Tests if the context node allows a certain element
  6209. * @param string $name Name of element to test for
  6210. * @return bool True if element is allowed, false if it is not
  6211. */
  6212. public function allowsElement($name)
  6213. {
  6214. if (!empty($this->currentNesting)) {
  6215. $parent_token = array_pop($this->currentNesting);
  6216. $this->currentNesting[] = $parent_token;
  6217. $parent = $this->htmlDefinition->info[$parent_token->name];
  6218. } else {
  6219. $parent = $this->htmlDefinition->info_parent_def;
  6220. }
  6221. if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
  6222. return false;
  6223. }
  6224. // check for exclusion
  6225. if (!empty($this->currentNesting)) {
  6226. for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
  6227. $node = $this->currentNesting[$i];
  6228. $def = $this->htmlDefinition->info[$node->name];
  6229. if (isset($def->excludes[$name])) {
  6230. return false;
  6231. }
  6232. }
  6233. }
  6234. return true;
  6235. }
  6236. /**
  6237. * Iterator function, which starts with the next token and continues until
  6238. * you reach the end of the input tokens.
  6239. * @warning Please prevent previous references from interfering with this
  6240. * functions by setting $i = null beforehand!
  6241. * @param int $i Current integer index variable for inputTokens
  6242. * @param HTMLPurifier_Token $current Current token variable.
  6243. * Do NOT use $token, as that variable is also a reference
  6244. * @return bool
  6245. */
  6246. protected function forward(&$i, &$current)
  6247. {
  6248. if ($i === null) {
  6249. $i = count($this->inputZipper->back) - 1;
  6250. } else {
  6251. $i--;
  6252. }
  6253. if ($i < 0) {
  6254. return false;
  6255. }
  6256. $current = $this->inputZipper->back[$i];
  6257. return true;
  6258. }
  6259. /**
  6260. * Similar to _forward, but accepts a third parameter $nesting (which
  6261. * should be initialized at 0) and stops when we hit the end tag
  6262. * for the node $this->inputIndex starts in.
  6263. * @param int $i Current integer index variable for inputTokens
  6264. * @param HTMLPurifier_Token $current Current token variable.
  6265. * Do NOT use $token, as that variable is also a reference
  6266. * @param int $nesting
  6267. * @return bool
  6268. */
  6269. protected function forwardUntilEndToken(&$i, &$current, &$nesting)
  6270. {
  6271. $result = $this->forward($i, $current);
  6272. if (!$result) {
  6273. return false;
  6274. }
  6275. if ($nesting === null) {
  6276. $nesting = 0;
  6277. }
  6278. if ($current instanceof HTMLPurifier_Token_Start) {
  6279. $nesting++;
  6280. } elseif ($current instanceof HTMLPurifier_Token_End) {
  6281. if ($nesting <= 0) {
  6282. return false;
  6283. }
  6284. $nesting--;
  6285. }
  6286. return true;
  6287. }
  6288. /**
  6289. * Iterator function, starts with the previous token and continues until
  6290. * you reach the beginning of input tokens.
  6291. * @warning Please prevent previous references from interfering with this
  6292. * functions by setting $i = null beforehand!
  6293. * @param int $i Current integer index variable for inputTokens
  6294. * @param HTMLPurifier_Token $current Current token variable.
  6295. * Do NOT use $token, as that variable is also a reference
  6296. * @return bool
  6297. */
  6298. protected function backward(&$i, &$current)
  6299. {
  6300. if ($i === null) {
  6301. $i = count($this->inputZipper->front) - 1;
  6302. } else {
  6303. $i--;
  6304. }
  6305. if ($i < 0) {
  6306. return false;
  6307. }
  6308. $current = $this->inputZipper->front[$i];
  6309. return true;
  6310. }
  6311. /**
  6312. * Handler that is called when a text token is processed
  6313. */
  6314. public function handleText(&$token)
  6315. {
  6316. }
  6317. /**
  6318. * Handler that is called when a start or empty token is processed
  6319. */
  6320. public function handleElement(&$token)
  6321. {
  6322. }
  6323. /**
  6324. * Handler that is called when an end token is processed
  6325. */
  6326. public function handleEnd(&$token)
  6327. {
  6328. $this->notifyEnd($token);
  6329. }
  6330. /**
  6331. * Notifier that is called when an end token is processed
  6332. * @param HTMLPurifier_Token $token Current token variable.
  6333. * @note This differs from handlers in that the token is read-only
  6334. * @deprecated
  6335. */
  6336. public function notifyEnd($token)
  6337. {
  6338. }
  6339. }
  6340. /**
  6341. * Represents a language and defines localizable string formatting and
  6342. * other functions, as well as the localized messages for HTML Purifier.
  6343. */
  6344. class HTMLPurifier_Language
  6345. {
  6346. /**
  6347. * ISO 639 language code of language. Prefers shortest possible version.
  6348. * @type string
  6349. */
  6350. public $code = 'en';
  6351. /**
  6352. * Fallback language code.
  6353. * @type bool|string
  6354. */
  6355. public $fallback = false;
  6356. /**
  6357. * Array of localizable messages.
  6358. * @type array
  6359. */
  6360. public $messages = array();
  6361. /**
  6362. * Array of localizable error codes.
  6363. * @type array
  6364. */
  6365. public $errorNames = array();
  6366. /**
  6367. * True if no message file was found for this language, so English
  6368. * is being used instead. Check this if you'd like to notify the
  6369. * user that they've used a non-supported language.
  6370. * @type bool
  6371. */
  6372. public $error = false;
  6373. /**
  6374. * Has the language object been loaded yet?
  6375. * @type bool
  6376. * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
  6377. */
  6378. public $_loaded = false;
  6379. /**
  6380. * @type HTMLPurifier_Config
  6381. */
  6382. protected $config;
  6383. /**
  6384. * @type HTMLPurifier_Context
  6385. */
  6386. protected $context;
  6387. /**
  6388. * @param HTMLPurifier_Config $config
  6389. * @param HTMLPurifier_Context $context
  6390. */
  6391. public function __construct($config, $context)
  6392. {
  6393. $this->config = $config;
  6394. $this->context = $context;
  6395. }
  6396. /**
  6397. * Loads language object with necessary info from factory cache
  6398. * @note This is a lazy loader
  6399. */
  6400. public function load()
  6401. {
  6402. if ($this->_loaded) {
  6403. return;
  6404. }
  6405. $factory = HTMLPurifier_LanguageFactory::instance();
  6406. $factory->loadLanguage($this->code);
  6407. foreach ($factory->keys as $key) {
  6408. $this->$key = $factory->cache[$this->code][$key];
  6409. }
  6410. $this->_loaded = true;
  6411. }
  6412. /**
  6413. * Retrieves a localised message.
  6414. * @param string $key string identifier of message
  6415. * @return string localised message
  6416. */
  6417. public function getMessage($key)
  6418. {
  6419. if (!$this->_loaded) {
  6420. $this->load();
  6421. }
  6422. if (!isset($this->messages[$key])) {
  6423. return "[$key]";
  6424. }
  6425. return $this->messages[$key];
  6426. }
  6427. /**
  6428. * Retrieves a localised error name.
  6429. * @param int $int error number, corresponding to PHP's error reporting
  6430. * @return string localised message
  6431. */
  6432. public function getErrorName($int)
  6433. {
  6434. if (!$this->_loaded) {
  6435. $this->load();
  6436. }
  6437. if (!isset($this->errorNames[$int])) {
  6438. return "[Error: $int]";
  6439. }
  6440. return $this->errorNames[$int];
  6441. }
  6442. /**
  6443. * Converts an array list into a string readable representation
  6444. * @param array $array
  6445. * @return string
  6446. */
  6447. public function listify($array)
  6448. {
  6449. $sep = $this->getMessage('Item separator');
  6450. $sep_last = $this->getMessage('Item separator last');
  6451. $ret = '';
  6452. for ($i = 0, $c = count($array); $i < $c; $i++) {
  6453. if ($i == 0) {
  6454. } elseif ($i + 1 < $c) {
  6455. $ret .= $sep;
  6456. } else {
  6457. $ret .= $sep_last;
  6458. }
  6459. $ret .= $array[$i];
  6460. }
  6461. return $ret;
  6462. }
  6463. /**
  6464. * Formats a localised message with passed parameters
  6465. * @param string $key string identifier of message
  6466. * @param array $args Parameters to substitute in
  6467. * @return string localised message
  6468. * @todo Implement conditionals? Right now, some messages make
  6469. * reference to line numbers, but those aren't always available
  6470. */
  6471. public function formatMessage($key, $args = array())
  6472. {
  6473. if (!$this->_loaded) {
  6474. $this->load();
  6475. }
  6476. if (!isset($this->messages[$key])) {
  6477. return "[$key]";
  6478. }
  6479. $raw = $this->messages[$key];
  6480. $subst = array();
  6481. $generator = false;
  6482. foreach ($args as $i => $value) {
  6483. if (is_object($value)) {
  6484. if ($value instanceof HTMLPurifier_Token) {
  6485. // factor this out some time
  6486. if (!$generator) {
  6487. $generator = $this->context->get('Generator');
  6488. }
  6489. if (isset($value->name)) {
  6490. $subst['$'.$i.'.Name'] = $value->name;
  6491. }
  6492. if (isset($value->data)) {
  6493. $subst['$'.$i.'.Data'] = $value->data;
  6494. }
  6495. $subst['$'.$i.'.Compact'] =
  6496. $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
  6497. // a more complex algorithm for compact representation
  6498. // could be introduced for all types of tokens. This
  6499. // may need to be factored out into a dedicated class
  6500. if (!empty($value->attr)) {
  6501. $stripped_token = clone $value;
  6502. $stripped_token->attr = array();
  6503. $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
  6504. }
  6505. $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
  6506. }
  6507. continue;
  6508. } elseif (is_array($value)) {
  6509. $keys = array_keys($value);
  6510. if (array_keys($keys) === $keys) {
  6511. // list
  6512. $subst['$'.$i] = $this->listify($value);
  6513. } else {
  6514. // associative array
  6515. // no $i implementation yet, sorry
  6516. $subst['$'.$i.'.Keys'] = $this->listify($keys);
  6517. $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
  6518. }
  6519. continue;
  6520. }
  6521. $subst['$' . $i] = $value;
  6522. }
  6523. return strtr($raw, $subst);
  6524. }
  6525. }
  6526. /**
  6527. * Class responsible for generating HTMLPurifier_Language objects, managing
  6528. * caching and fallbacks.
  6529. * @note Thanks to MediaWiki for the general logic, although this version
  6530. * has been entirely rewritten
  6531. * @todo Serialized cache for languages
  6532. */
  6533. class HTMLPurifier_LanguageFactory
  6534. {
  6535. /**
  6536. * Cache of language code information used to load HTMLPurifier_Language objects.
  6537. * Structure is: $factory->cache[$language_code][$key] = $value
  6538. * @type array
  6539. */
  6540. public $cache;
  6541. /**
  6542. * Valid keys in the HTMLPurifier_Language object. Designates which
  6543. * variables to slurp out of a message file.
  6544. * @type array
  6545. */
  6546. public $keys = array('fallback', 'messages', 'errorNames');
  6547. /**
  6548. * Instance to validate language codes.
  6549. * @type HTMLPurifier_AttrDef_Lang
  6550. *
  6551. */
  6552. protected $validator;
  6553. /**
  6554. * Cached copy of dirname(__FILE__), directory of current file without
  6555. * trailing slash.
  6556. * @type string
  6557. */
  6558. protected $dir;
  6559. /**
  6560. * Keys whose contents are a hash map and can be merged.
  6561. * @type array
  6562. */
  6563. protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
  6564. /**
  6565. * Keys whose contents are a list and can be merged.
  6566. * @value array lookup
  6567. */
  6568. protected $mergeable_keys_list = array();
  6569. /**
  6570. * Retrieve sole instance of the factory.
  6571. * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
  6572. * or bool true to reset to default factory.
  6573. * @return HTMLPurifier_LanguageFactory
  6574. */
  6575. public static function instance($prototype = null)
  6576. {
  6577. static $instance = null;
  6578. if ($prototype !== null) {
  6579. $instance = $prototype;
  6580. } elseif ($instance === null || $prototype == true) {
  6581. $instance = new HTMLPurifier_LanguageFactory();
  6582. $instance->setup();
  6583. }
  6584. return $instance;
  6585. }
  6586. /**
  6587. * Sets up the singleton, much like a constructor
  6588. * @note Prevents people from getting this outside of the singleton
  6589. */
  6590. public function setup()
  6591. {
  6592. $this->validator = new HTMLPurifier_AttrDef_Lang();
  6593. $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
  6594. }
  6595. /**
  6596. * Creates a language object, handles class fallbacks
  6597. * @param HTMLPurifier_Config $config
  6598. * @param HTMLPurifier_Context $context
  6599. * @param bool|string $code Code to override configuration with. Private parameter.
  6600. * @return HTMLPurifier_Language
  6601. */
  6602. public function create($config, $context, $code = false)
  6603. {
  6604. // validate language code
  6605. if ($code === false) {
  6606. $code = $this->validator->validate(
  6607. $config->get('Core.Language'),
  6608. $config,
  6609. $context
  6610. );
  6611. } else {
  6612. $code = $this->validator->validate($code, $config, $context);
  6613. }
  6614. if ($code === false) {
  6615. $code = 'en'; // malformed code becomes English
  6616. }
  6617. $pcode = str_replace('-', '_', $code); // make valid PHP classname
  6618. static $depth = 0; // recursion protection
  6619. if ($code == 'en') {
  6620. $lang = new HTMLPurifier_Language($config, $context);
  6621. } else {
  6622. $class = 'HTMLPurifier_Language_' . $pcode;
  6623. $file = $this->dir . '/Language/classes/' . $code . '.php';
  6624. if (file_exists($file) || class_exists($class, false)) {
  6625. $lang = new $class($config, $context);
  6626. } else {
  6627. // Go fallback
  6628. $raw_fallback = $this->getFallbackFor($code);
  6629. $fallback = $raw_fallback ? $raw_fallback : 'en';
  6630. $depth++;
  6631. $lang = $this->create($config, $context, $fallback);
  6632. if (!$raw_fallback) {
  6633. $lang->error = true;
  6634. }
  6635. $depth--;
  6636. }
  6637. }
  6638. $lang->code = $code;
  6639. return $lang;
  6640. }
  6641. /**
  6642. * Returns the fallback language for language
  6643. * @note Loads the original language into cache
  6644. * @param string $code language code
  6645. * @return string|bool
  6646. */
  6647. public function getFallbackFor($code)
  6648. {
  6649. $this->loadLanguage($code);
  6650. return $this->cache[$code]['fallback'];
  6651. }
  6652. /**
  6653. * Loads language into the cache, handles message file and fallbacks
  6654. * @param string $code language code
  6655. */
  6656. public function loadLanguage($code)
  6657. {
  6658. static $languages_seen = array(); // recursion guard
  6659. // abort if we've already loaded it
  6660. if (isset($this->cache[$code])) {
  6661. return;
  6662. }
  6663. // generate filename
  6664. $filename = $this->dir . '/Language/messages/' . $code . '.php';
  6665. // default fallback : may be overwritten by the ensuing include
  6666. $fallback = ($code != 'en') ? 'en' : false;
  6667. // load primary localisation
  6668. if (!file_exists($filename)) {
  6669. // skip the include: will rely solely on fallback
  6670. $filename = $this->dir . '/Language/messages/en.php';
  6671. $cache = array();
  6672. } else {
  6673. include $filename;
  6674. $cache = compact($this->keys);
  6675. }
  6676. // load fallback localisation
  6677. if (!empty($fallback)) {
  6678. // infinite recursion guard
  6679. if (isset($languages_seen[$code])) {
  6680. trigger_error(
  6681. 'Circular fallback reference in language ' .
  6682. $code,
  6683. E_USER_ERROR
  6684. );
  6685. $fallback = 'en';
  6686. }
  6687. $language_seen[$code] = true;
  6688. // load the fallback recursively
  6689. $this->loadLanguage($fallback);
  6690. $fallback_cache = $this->cache[$fallback];
  6691. // merge fallback with current language
  6692. foreach ($this->keys as $key) {
  6693. if (isset($cache[$key]) && isset($fallback_cache[$key])) {
  6694. if (isset($this->mergeable_keys_map[$key])) {
  6695. $cache[$key] = $cache[$key] + $fallback_cache[$key];
  6696. } elseif (isset($this->mergeable_keys_list[$key])) {
  6697. $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
  6698. }
  6699. } else {
  6700. $cache[$key] = $fallback_cache[$key];
  6701. }
  6702. }
  6703. }
  6704. // save to cache for later retrieval
  6705. $this->cache[$code] = $cache;
  6706. return;
  6707. }
  6708. }
  6709. /**
  6710. * Represents a measurable length, with a string numeric magnitude
  6711. * and a unit. This object is immutable.
  6712. */
  6713. class HTMLPurifier_Length
  6714. {
  6715. /**
  6716. * String numeric magnitude.
  6717. * @type string
  6718. */
  6719. protected $n;
  6720. /**
  6721. * String unit. False is permitted if $n = 0.
  6722. * @type string|bool
  6723. */
  6724. protected $unit;
  6725. /**
  6726. * Whether or not this length is valid. Null if not calculated yet.
  6727. * @type bool
  6728. */
  6729. protected $isValid;
  6730. /**
  6731. * Array Lookup array of units recognized by CSS 3
  6732. * @type array
  6733. */
  6734. protected static $allowedUnits = array(
  6735. 'em' => true, 'ex' => true, 'px' => true, 'in' => true,
  6736. 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,
  6737. 'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,
  6738. 'vmin' => true, 'vmax' => true
  6739. );
  6740. /**
  6741. * @param string $n Magnitude
  6742. * @param bool|string $u Unit
  6743. */
  6744. public function __construct($n = '0', $u = false)
  6745. {
  6746. $this->n = (string) $n;
  6747. $this->unit = $u !== false ? (string) $u : false;
  6748. }
  6749. /**
  6750. * @param string $s Unit string, like '2em' or '3.4in'
  6751. * @return HTMLPurifier_Length
  6752. * @warning Does not perform validation.
  6753. */
  6754. public static function make($s)
  6755. {
  6756. if ($s instanceof HTMLPurifier_Length) {
  6757. return $s;
  6758. }
  6759. $n_length = strspn($s, '1234567890.+-');
  6760. $n = substr($s, 0, $n_length);
  6761. $unit = substr($s, $n_length);
  6762. if ($unit === '') {
  6763. $unit = false;
  6764. }
  6765. return new HTMLPurifier_Length($n, $unit);
  6766. }
  6767. /**
  6768. * Validates the number and unit.
  6769. * @return bool
  6770. */
  6771. protected function validate()
  6772. {
  6773. // Special case:
  6774. if ($this->n === '+0' || $this->n === '-0') {
  6775. $this->n = '0';
  6776. }
  6777. if ($this->n === '0' && $this->unit === false) {
  6778. return true;
  6779. }
  6780. if (!ctype_lower($this->unit)) {
  6781. $this->unit = strtolower($this->unit);
  6782. }
  6783. if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
  6784. return false;
  6785. }
  6786. // Hack:
  6787. $def = new HTMLPurifier_AttrDef_CSS_Number();
  6788. $result = $def->validate($this->n, false, false);
  6789. if ($result === false) {
  6790. return false;
  6791. }
  6792. $this->n = $result;
  6793. return true;
  6794. }
  6795. /**
  6796. * Returns string representation of number.
  6797. * @return string
  6798. */
  6799. public function toString()
  6800. {
  6801. if (!$this->isValid()) {
  6802. return false;
  6803. }
  6804. return $this->n . $this->unit;
  6805. }
  6806. /**
  6807. * Retrieves string numeric magnitude.
  6808. * @return string
  6809. */
  6810. public function getN()
  6811. {
  6812. return $this->n;
  6813. }
  6814. /**
  6815. * Retrieves string unit.
  6816. * @return string
  6817. */
  6818. public function getUnit()
  6819. {
  6820. return $this->unit;
  6821. }
  6822. /**
  6823. * Returns true if this length unit is valid.
  6824. * @return bool
  6825. */
  6826. public function isValid()
  6827. {
  6828. if ($this->isValid === null) {
  6829. $this->isValid = $this->validate();
  6830. }
  6831. return $this->isValid;
  6832. }
  6833. /**
  6834. * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
  6835. * @param HTMLPurifier_Length $l
  6836. * @return int
  6837. * @warning If both values are too large or small, this calculation will
  6838. * not work properly
  6839. */
  6840. public function compareTo($l)
  6841. {
  6842. if ($l === false) {
  6843. return false;
  6844. }
  6845. if ($l->unit !== $this->unit) {
  6846. $converter = new HTMLPurifier_UnitConverter();
  6847. $l = $converter->convert($l, $this->unit);
  6848. if ($l === false) {
  6849. return false;
  6850. }
  6851. }
  6852. return $this->n - $l->n;
  6853. }
  6854. }
  6855. /**
  6856. * Forgivingly lexes HTML (SGML-style) markup into tokens.
  6857. *
  6858. * A lexer parses a string of SGML-style markup and converts them into
  6859. * corresponding tokens. It doesn't check for well-formedness, although its
  6860. * internal mechanism may make this automatic (such as the case of
  6861. * HTMLPurifier_Lexer_DOMLex). There are several implementations to choose
  6862. * from.
  6863. *
  6864. * A lexer is HTML-oriented: it might work with XML, but it's not
  6865. * recommended, as we adhere to a subset of the specification for optimization
  6866. * reasons. This might change in the future. Also, most tokenizers are not
  6867. * expected to handle DTDs or PIs.
  6868. *
  6869. * This class should not be directly instantiated, but you may use create() to
  6870. * retrieve a default copy of the lexer. Being a supertype, this class
  6871. * does not actually define any implementation, but offers commonly used
  6872. * convenience functions for subclasses.
  6873. *
  6874. * @note The unit tests will instantiate this class for testing purposes, as
  6875. * many of the utility functions require a class to be instantiated.
  6876. * This means that, even though this class is not runnable, it will
  6877. * not be declared abstract.
  6878. *
  6879. * @par
  6880. *
  6881. * @note
  6882. * We use tokens rather than create a DOM representation because DOM would:
  6883. *
  6884. * @par
  6885. * -# Require more processing and memory to create,
  6886. * -# Is not streamable, and
  6887. * -# Has the entire document structure (html and body not needed).
  6888. *
  6889. * @par
  6890. * However, DOM is helpful in that it makes it easy to move around nodes
  6891. * without a lot of lookaheads to see when a tag is closed. This is a
  6892. * limitation of the token system and some workarounds would be nice.
  6893. */
  6894. class HTMLPurifier_Lexer
  6895. {
  6896. /**
  6897. * Whether or not this lexer implements line-number/column-number tracking.
  6898. * If it does, set to true.
  6899. */
  6900. public $tracksLineNumbers = false;
  6901. // -- STATIC ----------------------------------------------------------
  6902. /**
  6903. * Retrieves or sets the default Lexer as a Prototype Factory.
  6904. *
  6905. * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
  6906. * a few exceptions involving special features that only DirectLex
  6907. * implements.
  6908. *
  6909. * @note The behavior of this class has changed, rather than accepting
  6910. * a prototype object, it now accepts a configuration object.
  6911. * To specify your own prototype, set %Core.LexerImpl to it.
  6912. * This change in behavior de-singletonizes the lexer object.
  6913. *
  6914. * @param HTMLPurifier_Config $config
  6915. * @return HTMLPurifier_Lexer
  6916. * @throws HTMLPurifier_Exception
  6917. */
  6918. public static function create($config)
  6919. {
  6920. if (!($config instanceof HTMLPurifier_Config)) {
  6921. $lexer = $config;
  6922. trigger_error(
  6923. "Passing a prototype to
  6924. HTMLPurifier_Lexer::create() is deprecated, please instead
  6925. use %Core.LexerImpl",
  6926. E_USER_WARNING
  6927. );
  6928. } else {
  6929. $lexer = $config->get('Core.LexerImpl');
  6930. }
  6931. $needs_tracking =
  6932. $config->get('Core.MaintainLineNumbers') ||
  6933. $config->get('Core.CollectErrors');
  6934. $inst = null;
  6935. if (is_object($lexer)) {
  6936. $inst = $lexer;
  6937. } else {
  6938. if (is_null($lexer)) {
  6939. do {
  6940. // auto-detection algorithm
  6941. if ($needs_tracking) {
  6942. $lexer = 'DirectLex';
  6943. break;
  6944. }
  6945. if (class_exists('DOMDocument', false) &&
  6946. method_exists('DOMDocument', 'loadHTML') &&
  6947. !extension_loaded('domxml')
  6948. ) {
  6949. // check for DOM support, because while it's part of the
  6950. // core, it can be disabled compile time. Also, the PECL
  6951. // domxml extension overrides the default DOM, and is evil
  6952. // and nasty and we shan't bother to support it
  6953. $lexer = 'DOMLex';
  6954. } else {
  6955. $lexer = 'DirectLex';
  6956. }
  6957. } while (0);
  6958. } // do..while so we can break
  6959. // instantiate recognized string names
  6960. switch ($lexer) {
  6961. case 'DOMLex':
  6962. $inst = new HTMLPurifier_Lexer_DOMLex();
  6963. break;
  6964. case 'DirectLex':
  6965. $inst = new HTMLPurifier_Lexer_DirectLex();
  6966. break;
  6967. case 'PH5P':
  6968. $inst = new HTMLPurifier_Lexer_PH5P();
  6969. break;
  6970. default:
  6971. throw new HTMLPurifier_Exception(
  6972. "Cannot instantiate unrecognized Lexer type " .
  6973. htmlspecialchars($lexer)
  6974. );
  6975. }
  6976. }
  6977. if (!$inst) {
  6978. throw new HTMLPurifier_Exception('No lexer was instantiated');
  6979. }
  6980. // once PHP DOM implements native line numbers, or we
  6981. // hack out something using XSLT, remove this stipulation
  6982. if ($needs_tracking && !$inst->tracksLineNumbers) {
  6983. throw new HTMLPurifier_Exception(
  6984. 'Cannot use lexer that does not support line numbers with ' .
  6985. 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
  6986. );
  6987. }
  6988. return $inst;
  6989. }
  6990. // -- CONVENIENCE MEMBERS ---------------------------------------------
  6991. public function __construct()
  6992. {
  6993. $this->_entity_parser = new HTMLPurifier_EntityParser();
  6994. }
  6995. /**
  6996. * Most common entity to raw value conversion table for special entities.
  6997. * @type array
  6998. */
  6999. protected $_special_entity2str =
  7000. array(
  7001. '&quot;' => '"',
  7002. '&amp;' => '&',
  7003. '&lt;' => '<',
  7004. '&gt;' => '>',
  7005. '&#39;' => "'",
  7006. '&#039;' => "'",
  7007. '&#x27;' => "'"
  7008. );
  7009. public function parseText($string, $config) {
  7010. return $this->parseData($string, false, $config);
  7011. }
  7012. public function parseAttr($string, $config) {
  7013. return $this->parseData($string, true, $config);
  7014. }
  7015. /**
  7016. * Parses special entities into the proper characters.
  7017. *
  7018. * This string will translate escaped versions of the special characters
  7019. * into the correct ones.
  7020. *
  7021. * @param string $string String character data to be parsed.
  7022. * @return string Parsed character data.
  7023. */
  7024. public function parseData($string, $is_attr, $config)
  7025. {
  7026. // following functions require at least one character
  7027. if ($string === '') {
  7028. return '';
  7029. }
  7030. // subtracts amps that cannot possibly be escaped
  7031. $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
  7032. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  7033. if (!$num_amp) {
  7034. return $string;
  7035. } // abort if no entities
  7036. $num_esc_amp = substr_count($string, '&amp;');
  7037. $string = strtr($string, $this->_special_entity2str);
  7038. // code duplication for sake of optimization, see above
  7039. $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
  7040. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  7041. if ($num_amp_2 <= $num_esc_amp) {
  7042. return $string;
  7043. }
  7044. // hmm... now we have some uncommon entities. Use the callback.
  7045. if ($config->get('Core.LegacyEntityDecoder')) {
  7046. $string = $this->_entity_parser->substituteSpecialEntities($string);
  7047. } else {
  7048. if ($is_attr) {
  7049. $string = $this->_entity_parser->substituteAttrEntities($string);
  7050. } else {
  7051. $string = $this->_entity_parser->substituteTextEntities($string);
  7052. }
  7053. }
  7054. return $string;
  7055. }
  7056. /**
  7057. * Lexes an HTML string into tokens.
  7058. * @param $string String HTML.
  7059. * @param HTMLPurifier_Config $config
  7060. * @param HTMLPurifier_Context $context
  7061. * @return HTMLPurifier_Token[] array representation of HTML.
  7062. */
  7063. public function tokenizeHTML($string, $config, $context)
  7064. {
  7065. trigger_error('Call to abstract class', E_USER_ERROR);
  7066. }
  7067. /**
  7068. * Translates CDATA sections into regular sections (through escaping).
  7069. * @param string $string HTML string to process.
  7070. * @return string HTML with CDATA sections escaped.
  7071. */
  7072. protected static function escapeCDATA($string)
  7073. {
  7074. return preg_replace_callback(
  7075. '/<!\[CDATA\[(.+?)\]\]>/s',
  7076. array('HTMLPurifier_Lexer', 'CDATACallback'),
  7077. $string
  7078. );
  7079. }
  7080. /**
  7081. * Special CDATA case that is especially convoluted for <script>
  7082. * @param string $string HTML string to process.
  7083. * @return string HTML with CDATA sections escaped.
  7084. */
  7085. protected static function escapeCommentedCDATA($string)
  7086. {
  7087. return preg_replace_callback(
  7088. '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
  7089. array('HTMLPurifier_Lexer', 'CDATACallback'),
  7090. $string
  7091. );
  7092. }
  7093. /**
  7094. * Special Internet Explorer conditional comments should be removed.
  7095. * @param string $string HTML string to process.
  7096. * @return string HTML with conditional comments removed.
  7097. */
  7098. protected static function removeIEConditional($string)
  7099. {
  7100. return preg_replace(
  7101. '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
  7102. '',
  7103. $string
  7104. );
  7105. }
  7106. /**
  7107. * Callback function for escapeCDATA() that does the work.
  7108. *
  7109. * @warning Though this is public in order to let the callback happen,
  7110. * calling it directly is not recommended.
  7111. * @param array $matches PCRE matches array, with index 0 the entire match
  7112. * and 1 the inside of the CDATA section.
  7113. * @return string Escaped internals of the CDATA section.
  7114. */
  7115. protected static function CDATACallback($matches)
  7116. {
  7117. // not exactly sure why the character set is needed, but whatever
  7118. return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
  7119. }
  7120. /**
  7121. * Takes a piece of HTML and normalizes it by converting entities, fixing
  7122. * encoding, extracting bits, and other good stuff.
  7123. * @param string $html HTML.
  7124. * @param HTMLPurifier_Config $config
  7125. * @param HTMLPurifier_Context $context
  7126. * @return string
  7127. * @todo Consider making protected
  7128. */
  7129. public function normalize($html, $config, $context)
  7130. {
  7131. // normalize newlines to \n
  7132. if ($config->get('Core.NormalizeNewlines')) {
  7133. $html = str_replace("\r\n", "\n", $html);
  7134. $html = str_replace("\r", "\n", $html);
  7135. }
  7136. if ($config->get('HTML.Trusted')) {
  7137. // escape convoluted CDATA
  7138. $html = $this->escapeCommentedCDATA($html);
  7139. }
  7140. // escape CDATA
  7141. $html = $this->escapeCDATA($html);
  7142. $html = $this->removeIEConditional($html);
  7143. // extract body from document if applicable
  7144. if ($config->get('Core.ConvertDocumentToFragment')) {
  7145. $e = false;
  7146. if ($config->get('Core.CollectErrors')) {
  7147. $e =& $context->get('ErrorCollector');
  7148. }
  7149. $new_html = $this->extractBody($html);
  7150. if ($e && $new_html != $html) {
  7151. $e->send(E_WARNING, 'Lexer: Extracted body');
  7152. }
  7153. $html = $new_html;
  7154. }
  7155. // expand entities that aren't the big five
  7156. if ($config->get('Core.LegacyEntityDecoder')) {
  7157. $html = $this->_entity_parser->substituteNonSpecialEntities($html);
  7158. }
  7159. // clean into wellformed UTF-8 string for an SGML context: this has
  7160. // to be done after entity expansion because the entities sometimes
  7161. // represent non-SGML characters (horror, horror!)
  7162. $html = HTMLPurifier_Encoder::cleanUTF8($html);
  7163. // if processing instructions are to removed, remove them now
  7164. if ($config->get('Core.RemoveProcessingInstructions')) {
  7165. $html = preg_replace('#<\?.+?\?>#s', '', $html);
  7166. }
  7167. $hidden_elements = $config->get('Core.HiddenElements');
  7168. if ($config->get('Core.AggressivelyRemoveScript') &&
  7169. !($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')
  7170. || empty($hidden_elements["script"]))) {
  7171. $html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);
  7172. }
  7173. return $html;
  7174. }
  7175. /**
  7176. * Takes a string of HTML (fragment or document) and returns the content
  7177. * @todo Consider making protected
  7178. */
  7179. public function extractBody($html)
  7180. {
  7181. $matches = array();
  7182. $result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);
  7183. if ($result) {
  7184. // Make sure it's not in a comment
  7185. $comment_start = strrpos($matches[1], '<!--');
  7186. $comment_end = strrpos($matches[1], '-->');
  7187. if ($comment_start === false ||
  7188. ($comment_end !== false && $comment_end > $comment_start)) {
  7189. return $matches[2];
  7190. }
  7191. }
  7192. return $html;
  7193. }
  7194. }
  7195. /**
  7196. * Abstract base node class that all others inherit from.
  7197. *
  7198. * Why do we not use the DOM extension? (1) It is not always available,
  7199. * (2) it has funny constraints on the data it can represent,
  7200. * whereas we want a maximally flexible representation, and (3) its
  7201. * interface is a bit cumbersome.
  7202. */
  7203. abstract class HTMLPurifier_Node
  7204. {
  7205. /**
  7206. * Line number of the start token in the source document
  7207. * @type int
  7208. */
  7209. public $line;
  7210. /**
  7211. * Column number of the start token in the source document. Null if unknown.
  7212. * @type int
  7213. */
  7214. public $col;
  7215. /**
  7216. * Lookup array of processing that this token is exempt from.
  7217. * Currently, valid values are "ValidateAttributes".
  7218. * @type array
  7219. */
  7220. public $armor = array();
  7221. /**
  7222. * When true, this node should be ignored as non-existent.
  7223. *
  7224. * Who is responsible for ignoring dead nodes? FixNesting is
  7225. * responsible for removing them before passing on to child
  7226. * validators.
  7227. */
  7228. public $dead = false;
  7229. /**
  7230. * Returns a pair of start and end tokens, where the end token
  7231. * is null if it is not necessary. Does not include children.
  7232. * @type array
  7233. */
  7234. abstract public function toTokenPair();
  7235. }
  7236. /**
  7237. * Class that handles operations involving percent-encoding in URIs.
  7238. *
  7239. * @warning
  7240. * Be careful when reusing instances of PercentEncoder. The object
  7241. * you use for normalize() SHOULD NOT be used for encode(), or
  7242. * vice-versa.
  7243. */
  7244. class HTMLPurifier_PercentEncoder
  7245. {
  7246. /**
  7247. * Reserved characters to preserve when using encode().
  7248. * @type array
  7249. */
  7250. protected $preserve = array();
  7251. /**
  7252. * String of characters that should be preserved while using encode().
  7253. * @param bool $preserve
  7254. */
  7255. public function __construct($preserve = false)
  7256. {
  7257. // unreserved letters, ought to const-ify
  7258. for ($i = 48; $i <= 57; $i++) { // digits
  7259. $this->preserve[$i] = true;
  7260. }
  7261. for ($i = 65; $i <= 90; $i++) { // upper-case
  7262. $this->preserve[$i] = true;
  7263. }
  7264. for ($i = 97; $i <= 122; $i++) { // lower-case
  7265. $this->preserve[$i] = true;
  7266. }
  7267. $this->preserve[45] = true; // Dash -
  7268. $this->preserve[46] = true; // Period .
  7269. $this->preserve[95] = true; // Underscore _
  7270. $this->preserve[126]= true; // Tilde ~
  7271. // extra letters not to escape
  7272. if ($preserve !== false) {
  7273. for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
  7274. $this->preserve[ord($preserve[$i])] = true;
  7275. }
  7276. }
  7277. }
  7278. /**
  7279. * Our replacement for urlencode, it encodes all non-reserved characters,
  7280. * as well as any extra characters that were instructed to be preserved.
  7281. * @note
  7282. * Assumes that the string has already been normalized, making any
  7283. * and all percent escape sequences valid. Percents will not be
  7284. * re-escaped, regardless of their status in $preserve
  7285. * @param string $string String to be encoded
  7286. * @return string Encoded string.
  7287. */
  7288. public function encode($string)
  7289. {
  7290. $ret = '';
  7291. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  7292. if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
  7293. $ret .= '%' . sprintf('%02X', $int);
  7294. } else {
  7295. $ret .= $string[$i];
  7296. }
  7297. }
  7298. return $ret;
  7299. }
  7300. /**
  7301. * Fix up percent-encoding by decoding unreserved characters and normalizing.
  7302. * @warning This function is affected by $preserve, even though the
  7303. * usual desired behavior is for this not to preserve those
  7304. * characters. Be careful when reusing instances of PercentEncoder!
  7305. * @param string $string String to normalize
  7306. * @return string
  7307. */
  7308. public function normalize($string)
  7309. {
  7310. if ($string == '') {
  7311. return '';
  7312. }
  7313. $parts = explode('%', $string);
  7314. $ret = array_shift($parts);
  7315. foreach ($parts as $part) {
  7316. $length = strlen($part);
  7317. if ($length < 2) {
  7318. $ret .= '%25' . $part;
  7319. continue;
  7320. }
  7321. $encoding = substr($part, 0, 2);
  7322. $text = substr($part, 2);
  7323. if (!ctype_xdigit($encoding)) {
  7324. $ret .= '%25' . $part;
  7325. continue;
  7326. }
  7327. $int = hexdec($encoding);
  7328. if (isset($this->preserve[$int])) {
  7329. $ret .= chr($int) . $text;
  7330. continue;
  7331. }
  7332. $encoding = strtoupper($encoding);
  7333. $ret .= '%' . $encoding . $text;
  7334. }
  7335. return $ret;
  7336. }
  7337. }
  7338. /**
  7339. * Generic property list implementation
  7340. */
  7341. class HTMLPurifier_PropertyList
  7342. {
  7343. /**
  7344. * Internal data-structure for properties.
  7345. * @type array
  7346. */
  7347. protected $data = array();
  7348. /**
  7349. * Parent plist.
  7350. * @type HTMLPurifier_PropertyList
  7351. */
  7352. protected $parent;
  7353. /**
  7354. * Cache.
  7355. * @type array
  7356. */
  7357. protected $cache;
  7358. /**
  7359. * @param HTMLPurifier_PropertyList $parent Parent plist
  7360. */
  7361. public function __construct($parent = null)
  7362. {
  7363. $this->parent = $parent;
  7364. }
  7365. /**
  7366. * Recursively retrieves the value for a key
  7367. * @param string $name
  7368. * @throws HTMLPurifier_Exception
  7369. */
  7370. public function get($name)
  7371. {
  7372. if ($this->has($name)) {
  7373. return $this->data[$name];
  7374. }
  7375. // possible performance bottleneck, convert to iterative if necessary
  7376. if ($this->parent) {
  7377. return $this->parent->get($name);
  7378. }
  7379. throw new HTMLPurifier_Exception("Key '$name' not found");
  7380. }
  7381. /**
  7382. * Sets the value of a key, for this plist
  7383. * @param string $name
  7384. * @param mixed $value
  7385. */
  7386. public function set($name, $value)
  7387. {
  7388. $this->data[$name] = $value;
  7389. }
  7390. /**
  7391. * Returns true if a given key exists
  7392. * @param string $name
  7393. * @return bool
  7394. */
  7395. public function has($name)
  7396. {
  7397. return array_key_exists($name, $this->data);
  7398. }
  7399. /**
  7400. * Resets a value to the value of it's parent, usually the default. If
  7401. * no value is specified, the entire plist is reset.
  7402. * @param string $name
  7403. */
  7404. public function reset($name = null)
  7405. {
  7406. if ($name == null) {
  7407. $this->data = array();
  7408. } else {
  7409. unset($this->data[$name]);
  7410. }
  7411. }
  7412. /**
  7413. * Squashes this property list and all of its property lists into a single
  7414. * array, and returns the array. This value is cached by default.
  7415. * @param bool $force If true, ignores the cache and regenerates the array.
  7416. * @return array
  7417. */
  7418. public function squash($force = false)
  7419. {
  7420. if ($this->cache !== null && !$force) {
  7421. return $this->cache;
  7422. }
  7423. if ($this->parent) {
  7424. return $this->cache = array_merge($this->parent->squash($force), $this->data);
  7425. } else {
  7426. return $this->cache = $this->data;
  7427. }
  7428. }
  7429. /**
  7430. * Returns the parent plist.
  7431. * @return HTMLPurifier_PropertyList
  7432. */
  7433. public function getParent()
  7434. {
  7435. return $this->parent;
  7436. }
  7437. /**
  7438. * Sets the parent plist.
  7439. * @param HTMLPurifier_PropertyList $plist Parent plist
  7440. */
  7441. public function setParent($plist)
  7442. {
  7443. $this->parent = $plist;
  7444. }
  7445. }
  7446. /**
  7447. * Property list iterator. Do not instantiate this class directly.
  7448. */
  7449. class HTMLPurifier_PropertyListIterator extends FilterIterator
  7450. {
  7451. /**
  7452. * @type int
  7453. */
  7454. protected $l;
  7455. /**
  7456. * @type string
  7457. */
  7458. protected $filter;
  7459. /**
  7460. * @param Iterator $iterator Array of data to iterate over
  7461. * @param string $filter Optional prefix to only allow values of
  7462. */
  7463. public function __construct(Iterator $iterator, $filter = null)
  7464. {
  7465. parent::__construct($iterator);
  7466. $this->l = strlen($filter);
  7467. $this->filter = $filter;
  7468. }
  7469. /**
  7470. * @return bool
  7471. */
  7472. public function accept()
  7473. {
  7474. $key = $this->getInnerIterator()->key();
  7475. if (strncmp($key, $this->filter, $this->l) !== 0) {
  7476. return false;
  7477. }
  7478. return true;
  7479. }
  7480. }
  7481. /**
  7482. * A simple array-backed queue, based off of the classic Okasaki
  7483. * persistent amortized queue. The basic idea is to maintain two
  7484. * stacks: an input stack and an output stack. When the output
  7485. * stack runs out, reverse the input stack and use it as the output
  7486. * stack.
  7487. *
  7488. * We don't use the SPL implementation because it's only supported
  7489. * on PHP 5.3 and later.
  7490. *
  7491. * Exercise: Prove that push/pop on this queue take amortized O(1) time.
  7492. *
  7493. * Exercise: Extend this queue to be a deque, while preserving amortized
  7494. * O(1) time. Some care must be taken on rebalancing to avoid quadratic
  7495. * behaviour caused by repeatedly shuffling data from the input stack
  7496. * to the output stack and back.
  7497. */
  7498. class HTMLPurifier_Queue {
  7499. private $input;
  7500. private $output;
  7501. public function __construct($input = array()) {
  7502. $this->input = $input;
  7503. $this->output = array();
  7504. }
  7505. /**
  7506. * Shifts an element off the front of the queue.
  7507. */
  7508. public function shift() {
  7509. if (empty($this->output)) {
  7510. $this->output = array_reverse($this->input);
  7511. $this->input = array();
  7512. }
  7513. if (empty($this->output)) {
  7514. return NULL;
  7515. }
  7516. return array_pop($this->output);
  7517. }
  7518. /**
  7519. * Pushes an element onto the front of the queue.
  7520. */
  7521. public function push($x) {
  7522. array_push($this->input, $x);
  7523. }
  7524. /**
  7525. * Checks if it's empty.
  7526. */
  7527. public function isEmpty() {
  7528. return empty($this->input) && empty($this->output);
  7529. }
  7530. }
  7531. /**
  7532. * Supertype for classes that define a strategy for modifying/purifying tokens.
  7533. *
  7534. * While HTMLPurifier's core purpose is fixing HTML into something proper,
  7535. * strategies provide plug points for extra configuration or even extra
  7536. * features, such as custom tags, custom parsing of text, etc.
  7537. */
  7538. abstract class HTMLPurifier_Strategy
  7539. {
  7540. /**
  7541. * Executes the strategy on the tokens.
  7542. *
  7543. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
  7544. * @param HTMLPurifier_Config $config
  7545. * @param HTMLPurifier_Context $context
  7546. * @return HTMLPurifier_Token[] Processed array of token objects.
  7547. */
  7548. abstract public function execute($tokens, $config, $context);
  7549. }
  7550. /**
  7551. * This is in almost every respect equivalent to an array except
  7552. * that it keeps track of which keys were accessed.
  7553. *
  7554. * @warning For the sake of backwards compatibility with early versions
  7555. * of PHP 5, you must not use the $hash[$key] syntax; if you do
  7556. * our version of offsetGet is never called.
  7557. */
  7558. class HTMLPurifier_StringHash extends ArrayObject
  7559. {
  7560. /**
  7561. * @type array
  7562. */
  7563. protected $accessed = array();
  7564. /**
  7565. * Retrieves a value, and logs the access.
  7566. * @param mixed $index
  7567. * @return mixed
  7568. */
  7569. public function offsetGet($index)
  7570. {
  7571. $this->accessed[$index] = true;
  7572. return parent::offsetGet($index);
  7573. }
  7574. /**
  7575. * Returns a lookup array of all array indexes that have been accessed.
  7576. * @return array in form array($index => true).
  7577. */
  7578. public function getAccessed()
  7579. {
  7580. return $this->accessed;
  7581. }
  7582. /**
  7583. * Resets the access array.
  7584. */
  7585. public function resetAccessed()
  7586. {
  7587. $this->accessed = array();
  7588. }
  7589. }
  7590. /**
  7591. * Parses string hash files. File format is as such:
  7592. *
  7593. * DefaultKeyValue
  7594. * KEY: Value
  7595. * KEY2: Value2
  7596. * --MULTILINE-KEY--
  7597. * Multiline
  7598. * value.
  7599. *
  7600. * Which would output something similar to:
  7601. *
  7602. * array(
  7603. * 'ID' => 'DefaultKeyValue',
  7604. * 'KEY' => 'Value',
  7605. * 'KEY2' => 'Value2',
  7606. * 'MULTILINE-KEY' => "Multiline\nvalue.\n",
  7607. * )
  7608. *
  7609. * We use this as an easy to use file-format for configuration schema
  7610. * files, but the class itself is usage agnostic.
  7611. *
  7612. * You can use ---- to forcibly terminate parsing of a single string-hash;
  7613. * this marker is used in multi string-hashes to delimit boundaries.
  7614. */
  7615. class HTMLPurifier_StringHashParser
  7616. {
  7617. /**
  7618. * @type string
  7619. */
  7620. public $default = 'ID';
  7621. /**
  7622. * Parses a file that contains a single string-hash.
  7623. * @param string $file
  7624. * @return array
  7625. */
  7626. public function parseFile($file)
  7627. {
  7628. if (!file_exists($file)) {
  7629. return false;
  7630. }
  7631. $fh = fopen($file, 'r');
  7632. if (!$fh) {
  7633. return false;
  7634. }
  7635. $ret = $this->parseHandle($fh);
  7636. fclose($fh);
  7637. return $ret;
  7638. }
  7639. /**
  7640. * Parses a file that contains multiple string-hashes delimited by '----'
  7641. * @param string $file
  7642. * @return array
  7643. */
  7644. public function parseMultiFile($file)
  7645. {
  7646. if (!file_exists($file)) {
  7647. return false;
  7648. }
  7649. $ret = array();
  7650. $fh = fopen($file, 'r');
  7651. if (!$fh) {
  7652. return false;
  7653. }
  7654. while (!feof($fh)) {
  7655. $ret[] = $this->parseHandle($fh);
  7656. }
  7657. fclose($fh);
  7658. return $ret;
  7659. }
  7660. /**
  7661. * Internal parser that acepts a file handle.
  7662. * @note While it's possible to simulate in-memory parsing by using
  7663. * custom stream wrappers, if such a use-case arises we should
  7664. * factor out the file handle into its own class.
  7665. * @param resource $fh File handle with pointer at start of valid string-hash
  7666. * block.
  7667. * @return array
  7668. */
  7669. protected function parseHandle($fh)
  7670. {
  7671. $state = false;
  7672. $single = false;
  7673. $ret = array();
  7674. do {
  7675. $line = fgets($fh);
  7676. if ($line === false) {
  7677. break;
  7678. }
  7679. $line = rtrim($line, "\n\r");
  7680. if (!$state && $line === '') {
  7681. continue;
  7682. }
  7683. if ($line === '----') {
  7684. break;
  7685. }
  7686. if (strncmp('--#', $line, 3) === 0) {
  7687. // Comment
  7688. continue;
  7689. } elseif (strncmp('--', $line, 2) === 0) {
  7690. // Multiline declaration
  7691. $state = trim($line, '- ');
  7692. if (!isset($ret[$state])) {
  7693. $ret[$state] = '';
  7694. }
  7695. continue;
  7696. } elseif (!$state) {
  7697. $single = true;
  7698. if (strpos($line, ':') !== false) {
  7699. // Single-line declaration
  7700. list($state, $line) = explode(':', $line, 2);
  7701. $line = trim($line);
  7702. } else {
  7703. // Use default declaration
  7704. $state = $this->default;
  7705. }
  7706. }
  7707. if ($single) {
  7708. $ret[$state] = $line;
  7709. $single = false;
  7710. $state = false;
  7711. } else {
  7712. $ret[$state] .= "$line\n";
  7713. }
  7714. } while (!feof($fh));
  7715. return $ret;
  7716. }
  7717. }
  7718. /**
  7719. * Defines a mutation of an obsolete tag into a valid tag.
  7720. */
  7721. abstract class HTMLPurifier_TagTransform
  7722. {
  7723. /**
  7724. * Tag name to transform the tag to.
  7725. * @type string
  7726. */
  7727. public $transform_to;
  7728. /**
  7729. * Transforms the obsolete tag into the valid tag.
  7730. * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
  7731. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
  7732. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  7733. */
  7734. abstract public function transform($tag, $config, $context);
  7735. /**
  7736. * Prepends CSS properties to the style attribute, creating the
  7737. * attribute if it doesn't exist.
  7738. * @warning Copied over from AttrTransform, be sure to keep in sync
  7739. * @param array $attr Attribute array to process (passed by reference)
  7740. * @param string $css CSS to prepend
  7741. */
  7742. protected function prependCSS(&$attr, $css)
  7743. {
  7744. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  7745. $attr['style'] = $css . $attr['style'];
  7746. }
  7747. }
  7748. /**
  7749. * Abstract base token class that all others inherit from.
  7750. */
  7751. abstract class HTMLPurifier_Token
  7752. {
  7753. /**
  7754. * Line number node was on in source document. Null if unknown.
  7755. * @type int
  7756. */
  7757. public $line;
  7758. /**
  7759. * Column of line node was on in source document. Null if unknown.
  7760. * @type int
  7761. */
  7762. public $col;
  7763. /**
  7764. * Lookup array of processing that this token is exempt from.
  7765. * Currently, valid values are "ValidateAttributes" and
  7766. * "MakeWellFormed_TagClosedError"
  7767. * @type array
  7768. */
  7769. public $armor = array();
  7770. /**
  7771. * Used during MakeWellFormed. See Note [Injector skips]
  7772. * @type
  7773. */
  7774. public $skip;
  7775. /**
  7776. * @type
  7777. */
  7778. public $rewind;
  7779. /**
  7780. * @type
  7781. */
  7782. public $carryover;
  7783. /**
  7784. * @param string $n
  7785. * @return null|string
  7786. */
  7787. public function __get($n)
  7788. {
  7789. if ($n === 'type') {
  7790. trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
  7791. switch (get_class($this)) {
  7792. case 'HTMLPurifier_Token_Start':
  7793. return 'start';
  7794. case 'HTMLPurifier_Token_Empty':
  7795. return 'empty';
  7796. case 'HTMLPurifier_Token_End':
  7797. return 'end';
  7798. case 'HTMLPurifier_Token_Text':
  7799. return 'text';
  7800. case 'HTMLPurifier_Token_Comment':
  7801. return 'comment';
  7802. default:
  7803. return null;
  7804. }
  7805. }
  7806. }
  7807. /**
  7808. * Sets the position of the token in the source document.
  7809. * @param int $l
  7810. * @param int $c
  7811. */
  7812. public function position($l = null, $c = null)
  7813. {
  7814. $this->line = $l;
  7815. $this->col = $c;
  7816. }
  7817. /**
  7818. * Convenience function for DirectLex settings line/col position.
  7819. * @param int $l
  7820. * @param int $c
  7821. */
  7822. public function rawPosition($l, $c)
  7823. {
  7824. if ($c === -1) {
  7825. $l++;
  7826. }
  7827. $this->line = $l;
  7828. $this->col = $c;
  7829. }
  7830. /**
  7831. * Converts a token into its corresponding node.
  7832. */
  7833. abstract public function toNode();
  7834. }
  7835. /**
  7836. * Factory for token generation.
  7837. *
  7838. * @note Doing some benchmarking indicates that the new operator is much
  7839. * slower than the clone operator (even discounting the cost of the
  7840. * constructor). This class is for that optimization.
  7841. * Other then that, there's not much point as we don't
  7842. * maintain parallel HTMLPurifier_Token hierarchies (the main reason why
  7843. * you'd want to use an abstract factory).
  7844. * @todo Port DirectLex to use this
  7845. */
  7846. class HTMLPurifier_TokenFactory
  7847. {
  7848. // p stands for prototype
  7849. /**
  7850. * @type HTMLPurifier_Token_Start
  7851. */
  7852. private $p_start;
  7853. /**
  7854. * @type HTMLPurifier_Token_End
  7855. */
  7856. private $p_end;
  7857. /**
  7858. * @type HTMLPurifier_Token_Empty
  7859. */
  7860. private $p_empty;
  7861. /**
  7862. * @type HTMLPurifier_Token_Text
  7863. */
  7864. private $p_text;
  7865. /**
  7866. * @type HTMLPurifier_Token_Comment
  7867. */
  7868. private $p_comment;
  7869. /**
  7870. * Generates blank prototypes for cloning.
  7871. */
  7872. public function __construct()
  7873. {
  7874. $this->p_start = new HTMLPurifier_Token_Start('', array());
  7875. $this->p_end = new HTMLPurifier_Token_End('');
  7876. $this->p_empty = new HTMLPurifier_Token_Empty('', array());
  7877. $this->p_text = new HTMLPurifier_Token_Text('');
  7878. $this->p_comment = new HTMLPurifier_Token_Comment('');
  7879. }
  7880. /**
  7881. * Creates a HTMLPurifier_Token_Start.
  7882. * @param string $name Tag name
  7883. * @param array $attr Associative array of attributes
  7884. * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
  7885. */
  7886. public function createStart($name, $attr = array())
  7887. {
  7888. $p = clone $this->p_start;
  7889. $p->__construct($name, $attr);
  7890. return $p;
  7891. }
  7892. /**
  7893. * Creates a HTMLPurifier_Token_End.
  7894. * @param string $name Tag name
  7895. * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
  7896. */
  7897. public function createEnd($name)
  7898. {
  7899. $p = clone $this->p_end;
  7900. $p->__construct($name);
  7901. return $p;
  7902. }
  7903. /**
  7904. * Creates a HTMLPurifier_Token_Empty.
  7905. * @param string $name Tag name
  7906. * @param array $attr Associative array of attributes
  7907. * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
  7908. */
  7909. public function createEmpty($name, $attr = array())
  7910. {
  7911. $p = clone $this->p_empty;
  7912. $p->__construct($name, $attr);
  7913. return $p;
  7914. }
  7915. /**
  7916. * Creates a HTMLPurifier_Token_Text.
  7917. * @param string $data Data of text token
  7918. * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
  7919. */
  7920. public function createText($data)
  7921. {
  7922. $p = clone $this->p_text;
  7923. $p->__construct($data);
  7924. return $p;
  7925. }
  7926. /**
  7927. * Creates a HTMLPurifier_Token_Comment.
  7928. * @param string $data Data of comment token
  7929. * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
  7930. */
  7931. public function createComment($data)
  7932. {
  7933. $p = clone $this->p_comment;
  7934. $p->__construct($data);
  7935. return $p;
  7936. }
  7937. }
  7938. /**
  7939. * HTML Purifier's internal representation of a URI.
  7940. * @note
  7941. * Internal data-structures are completely escaped. If the data needs
  7942. * to be used in a non-URI context (which is very unlikely), be sure
  7943. * to decode it first. The URI may not necessarily be well-formed until
  7944. * validate() is called.
  7945. */
  7946. class HTMLPurifier_URI
  7947. {
  7948. /**
  7949. * @type string
  7950. */
  7951. public $scheme;
  7952. /**
  7953. * @type string
  7954. */
  7955. public $userinfo;
  7956. /**
  7957. * @type string
  7958. */
  7959. public $host;
  7960. /**
  7961. * @type int
  7962. */
  7963. public $port;
  7964. /**
  7965. * @type string
  7966. */
  7967. public $path;
  7968. /**
  7969. * @type string
  7970. */
  7971. public $query;
  7972. /**
  7973. * @type string
  7974. */
  7975. public $fragment;
  7976. /**
  7977. * @param string $scheme
  7978. * @param string $userinfo
  7979. * @param string $host
  7980. * @param int $port
  7981. * @param string $path
  7982. * @param string $query
  7983. * @param string $fragment
  7984. * @note Automatically normalizes scheme and port
  7985. */
  7986. public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
  7987. {
  7988. $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
  7989. $this->userinfo = $userinfo;
  7990. $this->host = $host;
  7991. $this->port = is_null($port) ? $port : (int)$port;
  7992. $this->path = $path;
  7993. $this->query = $query;
  7994. $this->fragment = $fragment;
  7995. }
  7996. /**
  7997. * Retrieves a scheme object corresponding to the URI's scheme/default
  7998. * @param HTMLPurifier_Config $config
  7999. * @param HTMLPurifier_Context $context
  8000. * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
  8001. */
  8002. public function getSchemeObj($config, $context)
  8003. {
  8004. $registry = HTMLPurifier_URISchemeRegistry::instance();
  8005. if ($this->scheme !== null) {
  8006. $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
  8007. if (!$scheme_obj) {
  8008. return false;
  8009. } // invalid scheme, clean it out
  8010. } else {
  8011. // no scheme: retrieve the default one
  8012. $def = $config->getDefinition('URI');
  8013. $scheme_obj = $def->getDefaultScheme($config, $context);
  8014. if (!$scheme_obj) {
  8015. if ($def->defaultScheme !== null) {
  8016. // something funky happened to the default scheme object
  8017. trigger_error(
  8018. 'Default scheme object "' . $def->defaultScheme . '" was not readable',
  8019. E_USER_WARNING
  8020. );
  8021. } // suppress error if it's null
  8022. return false;
  8023. }
  8024. }
  8025. return $scheme_obj;
  8026. }
  8027. /**
  8028. * Generic validation method applicable for all schemes. May modify
  8029. * this URI in order to get it into a compliant form.
  8030. * @param HTMLPurifier_Config $config
  8031. * @param HTMLPurifier_Context $context
  8032. * @return bool True if validation/filtering succeeds, false if failure
  8033. */
  8034. public function validate($config, $context)
  8035. {
  8036. // ABNF definitions from RFC 3986
  8037. $chars_sub_delims = '!$&\'()*+,;=';
  8038. $chars_gen_delims = ':/?#[]@';
  8039. $chars_pchar = $chars_sub_delims . ':@';
  8040. // validate host
  8041. if (!is_null($this->host)) {
  8042. $host_def = new HTMLPurifier_AttrDef_URI_Host();
  8043. $this->host = $host_def->validate($this->host, $config, $context);
  8044. if ($this->host === false) {
  8045. $this->host = null;
  8046. }
  8047. }
  8048. // validate scheme
  8049. // NOTE: It's not appropriate to check whether or not this
  8050. // scheme is in our registry, since a URIFilter may convert a
  8051. // URI that we don't allow into one we do. So instead, we just
  8052. // check if the scheme can be dropped because there is no host
  8053. // and it is our default scheme.
  8054. if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
  8055. // support for relative paths is pretty abysmal when the
  8056. // scheme is present, so axe it when possible
  8057. $def = $config->getDefinition('URI');
  8058. if ($def->defaultScheme === $this->scheme) {
  8059. $this->scheme = null;
  8060. }
  8061. }
  8062. // validate username
  8063. if (!is_null($this->userinfo)) {
  8064. $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
  8065. $this->userinfo = $encoder->encode($this->userinfo);
  8066. }
  8067. // validate port
  8068. if (!is_null($this->port)) {
  8069. if ($this->port < 1 || $this->port > 65535) {
  8070. $this->port = null;
  8071. }
  8072. }
  8073. // validate path
  8074. $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
  8075. if (!is_null($this->host)) { // this catches $this->host === ''
  8076. // path-abempty (hier and relative)
  8077. // http://www.example.com/my/path
  8078. // //www.example.com/my/path (looks odd, but works, and
  8079. // recognized by most browsers)
  8080. // (this set is valid or invalid on a scheme by scheme
  8081. // basis, so we'll deal with it later)
  8082. // file:///my/path
  8083. // ///my/path
  8084. $this->path = $segments_encoder->encode($this->path);
  8085. } elseif ($this->path !== '') {
  8086. if ($this->path[0] === '/') {
  8087. // path-absolute (hier and relative)
  8088. // http:/my/path
  8089. // /my/path
  8090. if (strlen($this->path) >= 2 && $this->path[1] === '/') {
  8091. // This could happen if both the host gets stripped
  8092. // out
  8093. // http://my/path
  8094. // //my/path
  8095. $this->path = '';
  8096. } else {
  8097. $this->path = $segments_encoder->encode($this->path);
  8098. }
  8099. } elseif (!is_null($this->scheme)) {
  8100. // path-rootless (hier)
  8101. // http:my/path
  8102. // Short circuit evaluation means we don't need to check nz
  8103. $this->path = $segments_encoder->encode($this->path);
  8104. } else {
  8105. // path-noscheme (relative)
  8106. // my/path
  8107. // (once again, not checking nz)
  8108. $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
  8109. $c = strpos($this->path, '/');
  8110. if ($c !== false) {
  8111. $this->path =
  8112. $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
  8113. $segments_encoder->encode(substr($this->path, $c));
  8114. } else {
  8115. $this->path = $segment_nc_encoder->encode($this->path);
  8116. }
  8117. }
  8118. } else {
  8119. // path-empty (hier and relative)
  8120. $this->path = ''; // just to be safe
  8121. }
  8122. // qf = query and fragment
  8123. $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
  8124. if (!is_null($this->query)) {
  8125. $this->query = $qf_encoder->encode($this->query);
  8126. }
  8127. if (!is_null($this->fragment)) {
  8128. $this->fragment = $qf_encoder->encode($this->fragment);
  8129. }
  8130. return true;
  8131. }
  8132. /**
  8133. * Convert URI back to string
  8134. * @return string URI appropriate for output
  8135. */
  8136. public function toString()
  8137. {
  8138. // reconstruct authority
  8139. $authority = null;
  8140. // there is a rendering difference between a null authority
  8141. // (http:foo-bar) and an empty string authority
  8142. // (http:///foo-bar).
  8143. if (!is_null($this->host)) {
  8144. $authority = '';
  8145. if (!is_null($this->userinfo)) {
  8146. $authority .= $this->userinfo . '@';
  8147. }
  8148. $authority .= $this->host;
  8149. if (!is_null($this->port)) {
  8150. $authority .= ':' . $this->port;
  8151. }
  8152. }
  8153. // Reconstruct the result
  8154. // One might wonder about parsing quirks from browsers after
  8155. // this reconstruction. Unfortunately, parsing behavior depends
  8156. // on what *scheme* was employed (file:///foo is handled *very*
  8157. // differently than http:///foo), so unfortunately we have to
  8158. // defer to the schemes to do the right thing.
  8159. $result = '';
  8160. if (!is_null($this->scheme)) {
  8161. $result .= $this->scheme . ':';
  8162. }
  8163. if (!is_null($authority)) {
  8164. $result .= '//' . $authority;
  8165. }
  8166. $result .= $this->path;
  8167. if (!is_null($this->query)) {
  8168. $result .= '?' . $this->query;
  8169. }
  8170. if (!is_null($this->fragment)) {
  8171. $result .= '#' . $this->fragment;
  8172. }
  8173. return $result;
  8174. }
  8175. /**
  8176. * Returns true if this URL might be considered a 'local' URL given
  8177. * the current context. This is true when the host is null, or
  8178. * when it matches the host supplied to the configuration.
  8179. *
  8180. * Note that this does not do any scheme checking, so it is mostly
  8181. * only appropriate for metadata that doesn't care about protocol
  8182. * security. isBenign is probably what you actually want.
  8183. * @param HTMLPurifier_Config $config
  8184. * @param HTMLPurifier_Context $context
  8185. * @return bool
  8186. */
  8187. public function isLocal($config, $context)
  8188. {
  8189. if ($this->host === null) {
  8190. return true;
  8191. }
  8192. $uri_def = $config->getDefinition('URI');
  8193. if ($uri_def->host === $this->host) {
  8194. return true;
  8195. }
  8196. return false;
  8197. }
  8198. /**
  8199. * Returns true if this URL should be considered a 'benign' URL,
  8200. * that is:
  8201. *
  8202. * - It is a local URL (isLocal), and
  8203. * - It has a equal or better level of security
  8204. * @param HTMLPurifier_Config $config
  8205. * @param HTMLPurifier_Context $context
  8206. * @return bool
  8207. */
  8208. public function isBenign($config, $context)
  8209. {
  8210. if (!$this->isLocal($config, $context)) {
  8211. return false;
  8212. }
  8213. $scheme_obj = $this->getSchemeObj($config, $context);
  8214. if (!$scheme_obj) {
  8215. return false;
  8216. } // conservative approach
  8217. $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
  8218. if ($current_scheme_obj->secure) {
  8219. if (!$scheme_obj->secure) {
  8220. return false;
  8221. }
  8222. }
  8223. return true;
  8224. }
  8225. }
  8226. class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
  8227. {
  8228. public $type = 'URI';
  8229. protected $filters = array();
  8230. protected $postFilters = array();
  8231. protected $registeredFilters = array();
  8232. /**
  8233. * HTMLPurifier_URI object of the base specified at %URI.Base
  8234. */
  8235. public $base;
  8236. /**
  8237. * String host to consider "home" base, derived off of $base
  8238. */
  8239. public $host;
  8240. /**
  8241. * Name of default scheme based on %URI.DefaultScheme and %URI.Base
  8242. */
  8243. public $defaultScheme;
  8244. public function __construct()
  8245. {
  8246. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
  8247. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
  8248. $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
  8249. $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
  8250. $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
  8251. $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
  8252. $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
  8253. }
  8254. public function registerFilter($filter)
  8255. {
  8256. $this->registeredFilters[$filter->name] = $filter;
  8257. }
  8258. public function addFilter($filter, $config)
  8259. {
  8260. $r = $filter->prepare($config);
  8261. if ($r === false) return; // null is ok, for backwards compat
  8262. if ($filter->post) {
  8263. $this->postFilters[$filter->name] = $filter;
  8264. } else {
  8265. $this->filters[$filter->name] = $filter;
  8266. }
  8267. }
  8268. protected function doSetup($config)
  8269. {
  8270. $this->setupMemberVariables($config);
  8271. $this->setupFilters($config);
  8272. }
  8273. protected function setupFilters($config)
  8274. {
  8275. foreach ($this->registeredFilters as $name => $filter) {
  8276. if ($filter->always_load) {
  8277. $this->addFilter($filter, $config);
  8278. } else {
  8279. $conf = $config->get('URI.' . $name);
  8280. if ($conf !== false && $conf !== null) {
  8281. $this->addFilter($filter, $config);
  8282. }
  8283. }
  8284. }
  8285. unset($this->registeredFilters);
  8286. }
  8287. protected function setupMemberVariables($config)
  8288. {
  8289. $this->host = $config->get('URI.Host');
  8290. $base_uri = $config->get('URI.Base');
  8291. if (!is_null($base_uri)) {
  8292. $parser = new HTMLPurifier_URIParser();
  8293. $this->base = $parser->parse($base_uri);
  8294. $this->defaultScheme = $this->base->scheme;
  8295. if (is_null($this->host)) $this->host = $this->base->host;
  8296. }
  8297. if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
  8298. }
  8299. public function getDefaultScheme($config, $context)
  8300. {
  8301. return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
  8302. }
  8303. public function filter(&$uri, $config, $context)
  8304. {
  8305. foreach ($this->filters as $name => $f) {
  8306. $result = $f->filter($uri, $config, $context);
  8307. if (!$result) return false;
  8308. }
  8309. return true;
  8310. }
  8311. public function postFilter(&$uri, $config, $context)
  8312. {
  8313. foreach ($this->postFilters as $name => $f) {
  8314. $result = $f->filter($uri, $config, $context);
  8315. if (!$result) return false;
  8316. }
  8317. return true;
  8318. }
  8319. }
  8320. /**
  8321. * Chainable filters for custom URI processing.
  8322. *
  8323. * These filters can perform custom actions on a URI filter object,
  8324. * including transformation or blacklisting. A filter named Foo
  8325. * must have a corresponding configuration directive %URI.Foo,
  8326. * unless always_load is specified to be true.
  8327. *
  8328. * The following contexts may be available while URIFilters are being
  8329. * processed:
  8330. *
  8331. * - EmbeddedURI: true if URI is an embedded resource that will
  8332. * be loaded automatically on page load
  8333. * - CurrentToken: a reference to the token that is currently
  8334. * being processed
  8335. * - CurrentAttr: the name of the attribute that is currently being
  8336. * processed
  8337. * - CurrentCSSProperty: the name of the CSS property that is
  8338. * currently being processed (if applicable)
  8339. *
  8340. * @warning This filter is called before scheme object validation occurs.
  8341. * Make sure, if you require a specific scheme object, you
  8342. * you check that it exists. This allows filters to convert
  8343. * proprietary URI schemes into regular ones.
  8344. */
  8345. abstract class HTMLPurifier_URIFilter
  8346. {
  8347. /**
  8348. * Unique identifier of filter.
  8349. * @type string
  8350. */
  8351. public $name;
  8352. /**
  8353. * True if this filter should be run after scheme validation.
  8354. * @type bool
  8355. */
  8356. public $post = false;
  8357. /**
  8358. * True if this filter should always be loaded.
  8359. * This permits a filter to be named Foo without the corresponding
  8360. * %URI.Foo directive existing.
  8361. * @type bool
  8362. */
  8363. public $always_load = false;
  8364. /**
  8365. * Performs initialization for the filter. If the filter returns
  8366. * false, this means that it shouldn't be considered active.
  8367. * @param HTMLPurifier_Config $config
  8368. * @return bool
  8369. */
  8370. public function prepare($config)
  8371. {
  8372. return true;
  8373. }
  8374. /**
  8375. * Filter a URI object
  8376. * @param HTMLPurifier_URI $uri Reference to URI object variable
  8377. * @param HTMLPurifier_Config $config
  8378. * @param HTMLPurifier_Context $context
  8379. * @return bool Whether or not to continue processing: false indicates
  8380. * URL is no good, true indicates continue processing. Note that
  8381. * all changes are committed directly on the URI object
  8382. */
  8383. abstract public function filter(&$uri, $config, $context);
  8384. }
  8385. /**
  8386. * Parses a URI into the components and fragment identifier as specified
  8387. * by RFC 3986.
  8388. */
  8389. class HTMLPurifier_URIParser
  8390. {
  8391. /**
  8392. * Instance of HTMLPurifier_PercentEncoder to do normalization with.
  8393. */
  8394. protected $percentEncoder;
  8395. public function __construct()
  8396. {
  8397. $this->percentEncoder = new HTMLPurifier_PercentEncoder();
  8398. }
  8399. /**
  8400. * Parses a URI.
  8401. * @param $uri string URI to parse
  8402. * @return HTMLPurifier_URI representation of URI. This representation has
  8403. * not been validated yet and may not conform to RFC.
  8404. */
  8405. public function parse($uri)
  8406. {
  8407. $uri = $this->percentEncoder->normalize($uri);
  8408. // Regexp is as per Appendix B.
  8409. // Note that ["<>] are an addition to the RFC's recommended
  8410. // characters, because they represent external delimeters.
  8411. $r_URI = '!'.
  8412. '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
  8413. '(//([^/?#"<>]*))?'. // 4. Authority
  8414. '([^?#"<>]*)'. // 5. Path
  8415. '(\?([^#"<>]*))?'. // 7. Query
  8416. '(#([^"<>]*))?'. // 8. Fragment
  8417. '!';
  8418. $matches = array();
  8419. $result = preg_match($r_URI, $uri, $matches);
  8420. if (!$result) return false; // *really* invalid URI
  8421. // seperate out parts
  8422. $scheme = !empty($matches[1]) ? $matches[2] : null;
  8423. $authority = !empty($matches[3]) ? $matches[4] : null;
  8424. $path = $matches[5]; // always present, can be empty
  8425. $query = !empty($matches[6]) ? $matches[7] : null;
  8426. $fragment = !empty($matches[8]) ? $matches[9] : null;
  8427. // further parse authority
  8428. if ($authority !== null) {
  8429. $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
  8430. $matches = array();
  8431. preg_match($r_authority, $authority, $matches);
  8432. $userinfo = !empty($matches[1]) ? $matches[2] : null;
  8433. $host = !empty($matches[3]) ? $matches[3] : '';
  8434. $port = !empty($matches[4]) ? (int) $matches[5] : null;
  8435. } else {
  8436. $port = $host = $userinfo = null;
  8437. }
  8438. return new HTMLPurifier_URI(
  8439. $scheme, $userinfo, $host, $port, $path, $query, $fragment);
  8440. }
  8441. }
  8442. /**
  8443. * Validator for the components of a URI for a specific scheme
  8444. */
  8445. abstract class HTMLPurifier_URIScheme
  8446. {
  8447. /**
  8448. * Scheme's default port (integer). If an explicit port number is
  8449. * specified that coincides with the default port, it will be
  8450. * elided.
  8451. * @type int
  8452. */
  8453. public $default_port = null;
  8454. /**
  8455. * Whether or not URIs of this scheme are locatable by a browser
  8456. * http and ftp are accessible, while mailto and news are not.
  8457. * @type bool
  8458. */
  8459. public $browsable = false;
  8460. /**
  8461. * Whether or not data transmitted over this scheme is encrypted.
  8462. * https is secure, http is not.
  8463. * @type bool
  8464. */
  8465. public $secure = false;
  8466. /**
  8467. * Whether or not the URI always uses <hier_part>, resolves edge cases
  8468. * with making relative URIs absolute
  8469. * @type bool
  8470. */
  8471. public $hierarchical = false;
  8472. /**
  8473. * Whether or not the URI may omit a hostname when the scheme is
  8474. * explicitly specified, ala file:///path/to/file. As of writing,
  8475. * 'file' is the only scheme that browsers support his properly.
  8476. * @type bool
  8477. */
  8478. public $may_omit_host = false;
  8479. /**
  8480. * Validates the components of a URI for a specific scheme.
  8481. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8482. * @param HTMLPurifier_Config $config
  8483. * @param HTMLPurifier_Context $context
  8484. * @return bool success or failure
  8485. */
  8486. abstract public function doValidate(&$uri, $config, $context);
  8487. /**
  8488. * Public interface for validating components of a URI. Performs a
  8489. * bunch of default actions. Don't overload this method.
  8490. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8491. * @param HTMLPurifier_Config $config
  8492. * @param HTMLPurifier_Context $context
  8493. * @return bool success or failure
  8494. */
  8495. public function validate(&$uri, $config, $context)
  8496. {
  8497. if ($this->default_port == $uri->port) {
  8498. $uri->port = null;
  8499. }
  8500. // kludge: browsers do funny things when the scheme but not the
  8501. // authority is set
  8502. if (!$this->may_omit_host &&
  8503. // if the scheme is present, a missing host is always in error
  8504. (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
  8505. // if the scheme is not present, a *blank* host is in error,
  8506. // since this translates into '///path' which most browsers
  8507. // interpret as being 'http://path'.
  8508. (is_null($uri->scheme) && $uri->host === '')
  8509. ) {
  8510. do {
  8511. if (is_null($uri->scheme)) {
  8512. if (substr($uri->path, 0, 2) != '//') {
  8513. $uri->host = null;
  8514. break;
  8515. }
  8516. // URI is '////path', so we cannot nullify the
  8517. // host to preserve semantics. Try expanding the
  8518. // hostname instead (fall through)
  8519. }
  8520. // first see if we can manually insert a hostname
  8521. $host = $config->get('URI.Host');
  8522. if (!is_null($host)) {
  8523. $uri->host = $host;
  8524. } else {
  8525. // we can't do anything sensible, reject the URL.
  8526. return false;
  8527. }
  8528. } while (false);
  8529. }
  8530. return $this->doValidate($uri, $config, $context);
  8531. }
  8532. }
  8533. /**
  8534. * Registry for retrieving specific URI scheme validator objects.
  8535. */
  8536. class HTMLPurifier_URISchemeRegistry
  8537. {
  8538. /**
  8539. * Retrieve sole instance of the registry.
  8540. * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
  8541. * or bool true to reset to default registry.
  8542. * @return HTMLPurifier_URISchemeRegistry
  8543. * @note Pass a registry object $prototype with a compatible interface and
  8544. * the function will copy it and return it all further times.
  8545. */
  8546. public static function instance($prototype = null)
  8547. {
  8548. static $instance = null;
  8549. if ($prototype !== null) {
  8550. $instance = $prototype;
  8551. } elseif ($instance === null || $prototype == true) {
  8552. $instance = new HTMLPurifier_URISchemeRegistry();
  8553. }
  8554. return $instance;
  8555. }
  8556. /**
  8557. * Cache of retrieved schemes.
  8558. * @type HTMLPurifier_URIScheme[]
  8559. */
  8560. protected $schemes = array();
  8561. /**
  8562. * Retrieves a scheme validator object
  8563. * @param string $scheme String scheme name like http or mailto
  8564. * @param HTMLPurifier_Config $config
  8565. * @param HTMLPurifier_Context $context
  8566. * @return HTMLPurifier_URIScheme
  8567. */
  8568. public function getScheme($scheme, $config, $context)
  8569. {
  8570. if (!$config) {
  8571. $config = HTMLPurifier_Config::createDefault();
  8572. }
  8573. // important, otherwise attacker could include arbitrary file
  8574. $allowed_schemes = $config->get('URI.AllowedSchemes');
  8575. if (!$config->get('URI.OverrideAllowedSchemes') &&
  8576. !isset($allowed_schemes[$scheme])
  8577. ) {
  8578. return;
  8579. }
  8580. if (isset($this->schemes[$scheme])) {
  8581. return $this->schemes[$scheme];
  8582. }
  8583. if (!isset($allowed_schemes[$scheme])) {
  8584. return;
  8585. }
  8586. $class = 'HTMLPurifier_URIScheme_' . $scheme;
  8587. if (!class_exists($class)) {
  8588. return;
  8589. }
  8590. $this->schemes[$scheme] = new $class();
  8591. return $this->schemes[$scheme];
  8592. }
  8593. /**
  8594. * Registers a custom scheme to the cache, bypassing reflection.
  8595. * @param string $scheme Scheme name
  8596. * @param HTMLPurifier_URIScheme $scheme_obj
  8597. */
  8598. public function register($scheme, $scheme_obj)
  8599. {
  8600. $this->schemes[$scheme] = $scheme_obj;
  8601. }
  8602. }
  8603. /**
  8604. * Class for converting between different unit-lengths as specified by
  8605. * CSS.
  8606. */
  8607. class HTMLPurifier_UnitConverter
  8608. {
  8609. const ENGLISH = 1;
  8610. const METRIC = 2;
  8611. const DIGITAL = 3;
  8612. /**
  8613. * Units information array. Units are grouped into measuring systems
  8614. * (English, Metric), and are assigned an integer representing
  8615. * the conversion factor between that unit and the smallest unit in
  8616. * the system. Numeric indexes are actually magical constants that
  8617. * encode conversion data from one system to the next, with a O(n^2)
  8618. * constraint on memory (this is generally not a problem, since
  8619. * the number of measuring systems is small.)
  8620. */
  8621. protected static $units = array(
  8622. self::ENGLISH => array(
  8623. 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
  8624. 'pt' => 4,
  8625. 'pc' => 48,
  8626. 'in' => 288,
  8627. self::METRIC => array('pt', '0.352777778', 'mm'),
  8628. ),
  8629. self::METRIC => array(
  8630. 'mm' => 1,
  8631. 'cm' => 10,
  8632. self::ENGLISH => array('mm', '2.83464567', 'pt'),
  8633. ),
  8634. );
  8635. /**
  8636. * Minimum bcmath precision for output.
  8637. * @type int
  8638. */
  8639. protected $outputPrecision;
  8640. /**
  8641. * Bcmath precision for internal calculations.
  8642. * @type int
  8643. */
  8644. protected $internalPrecision;
  8645. /**
  8646. * Whether or not BCMath is available.
  8647. * @type bool
  8648. */
  8649. private $bcmath;
  8650. public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
  8651. {
  8652. $this->outputPrecision = $output_precision;
  8653. $this->internalPrecision = $internal_precision;
  8654. $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
  8655. }
  8656. /**
  8657. * Converts a length object of one unit into another unit.
  8658. * @param HTMLPurifier_Length $length
  8659. * Instance of HTMLPurifier_Length to convert. You must validate()
  8660. * it before passing it here!
  8661. * @param string $to_unit
  8662. * Unit to convert to.
  8663. * @return HTMLPurifier_Length|bool
  8664. * @note
  8665. * About precision: This conversion function pays very special
  8666. * attention to the incoming precision of values and attempts
  8667. * to maintain a number of significant figure. Results are
  8668. * fairly accurate up to nine digits. Some caveats:
  8669. * - If a number is zero-padded as a result of this significant
  8670. * figure tracking, the zeroes will be eliminated.
  8671. * - If a number contains less than four sigfigs ($outputPrecision)
  8672. * and this causes some decimals to be excluded, those
  8673. * decimals will be added on.
  8674. */
  8675. public function convert($length, $to_unit)
  8676. {
  8677. if (!$length->isValid()) {
  8678. return false;
  8679. }
  8680. $n = $length->getN();
  8681. $unit = $length->getUnit();
  8682. if ($n === '0' || $unit === false) {
  8683. return new HTMLPurifier_Length('0', false);
  8684. }
  8685. $state = $dest_state = false;
  8686. foreach (self::$units as $k => $x) {
  8687. if (isset($x[$unit])) {
  8688. $state = $k;
  8689. }
  8690. if (isset($x[$to_unit])) {
  8691. $dest_state = $k;
  8692. }
  8693. }
  8694. if (!$state || !$dest_state) {
  8695. return false;
  8696. }
  8697. // Some calculations about the initial precision of the number;
  8698. // this will be useful when we need to do final rounding.
  8699. $sigfigs = $this->getSigFigs($n);
  8700. if ($sigfigs < $this->outputPrecision) {
  8701. $sigfigs = $this->outputPrecision;
  8702. }
  8703. // BCMath's internal precision deals only with decimals. Use
  8704. // our default if the initial number has no decimals, or increase
  8705. // it by how ever many decimals, thus, the number of guard digits
  8706. // will always be greater than or equal to internalPrecision.
  8707. $log = (int)floor(log(abs($n), 10));
  8708. $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
  8709. for ($i = 0; $i < 2; $i++) {
  8710. // Determine what unit IN THIS SYSTEM we need to convert to
  8711. if ($dest_state === $state) {
  8712. // Simple conversion
  8713. $dest_unit = $to_unit;
  8714. } else {
  8715. // Convert to the smallest unit, pending a system shift
  8716. $dest_unit = self::$units[$state][$dest_state][0];
  8717. }
  8718. // Do the conversion if necessary
  8719. if ($dest_unit !== $unit) {
  8720. $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
  8721. $n = $this->mul($n, $factor, $cp);
  8722. $unit = $dest_unit;
  8723. }
  8724. // Output was zero, so bail out early. Shouldn't ever happen.
  8725. if ($n === '') {
  8726. $n = '0';
  8727. $unit = $to_unit;
  8728. break;
  8729. }
  8730. // It was a simple conversion, so bail out
  8731. if ($dest_state === $state) {
  8732. break;
  8733. }
  8734. if ($i !== 0) {
  8735. // Conversion failed! Apparently, the system we forwarded
  8736. // to didn't have this unit. This should never happen!
  8737. return false;
  8738. }
  8739. // Pre-condition: $i == 0
  8740. // Perform conversion to next system of units
  8741. $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
  8742. $unit = self::$units[$state][$dest_state][2];
  8743. $state = $dest_state;
  8744. // One more loop around to convert the unit in the new system.
  8745. }
  8746. // Post-condition: $unit == $to_unit
  8747. if ($unit !== $to_unit) {
  8748. return false;
  8749. }
  8750. // Useful for debugging:
  8751. //echo "<pre>n";
  8752. //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
  8753. $n = $this->round($n, $sigfigs);
  8754. if (strpos($n, '.') !== false) {
  8755. $n = rtrim($n, '0');
  8756. }
  8757. $n = rtrim($n, '.');
  8758. return new HTMLPurifier_Length($n, $unit);
  8759. }
  8760. /**
  8761. * Returns the number of significant figures in a string number.
  8762. * @param string $n Decimal number
  8763. * @return int number of sigfigs
  8764. */
  8765. public function getSigFigs($n)
  8766. {
  8767. $n = ltrim($n, '0+-');
  8768. $dp = strpos($n, '.'); // decimal position
  8769. if ($dp === false) {
  8770. $sigfigs = strlen(rtrim($n, '0'));
  8771. } else {
  8772. $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
  8773. if ($dp !== 0) {
  8774. $sigfigs--;
  8775. }
  8776. }
  8777. return $sigfigs;
  8778. }
  8779. /**
  8780. * Adds two numbers, using arbitrary precision when available.
  8781. * @param string $s1
  8782. * @param string $s2
  8783. * @param int $scale
  8784. * @return string
  8785. */
  8786. private function add($s1, $s2, $scale)
  8787. {
  8788. if ($this->bcmath) {
  8789. return bcadd($s1, $s2, $scale);
  8790. } else {
  8791. return $this->scale((float)$s1 + (float)$s2, $scale);
  8792. }
  8793. }
  8794. /**
  8795. * Multiples two numbers, using arbitrary precision when available.
  8796. * @param string $s1
  8797. * @param string $s2
  8798. * @param int $scale
  8799. * @return string
  8800. */
  8801. private function mul($s1, $s2, $scale)
  8802. {
  8803. if ($this->bcmath) {
  8804. return bcmul($s1, $s2, $scale);
  8805. } else {
  8806. return $this->scale((float)$s1 * (float)$s2, $scale);
  8807. }
  8808. }
  8809. /**
  8810. * Divides two numbers, using arbitrary precision when available.
  8811. * @param string $s1
  8812. * @param string $s2
  8813. * @param int $scale
  8814. * @return string
  8815. */
  8816. private function div($s1, $s2, $scale)
  8817. {
  8818. if ($this->bcmath) {
  8819. return bcdiv($s1, $s2, $scale);
  8820. } else {
  8821. return $this->scale((float)$s1 / (float)$s2, $scale);
  8822. }
  8823. }
  8824. /**
  8825. * Rounds a number according to the number of sigfigs it should have,
  8826. * using arbitrary precision when available.
  8827. * @param float $n
  8828. * @param int $sigfigs
  8829. * @return string
  8830. */
  8831. private function round($n, $sigfigs)
  8832. {
  8833. $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
  8834. $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
  8835. $neg = $n < 0 ? '-' : ''; // Negative sign
  8836. if ($this->bcmath) {
  8837. if ($rp >= 0) {
  8838. $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
  8839. $n = bcdiv($n, '1', $rp);
  8840. } else {
  8841. // This algorithm partially depends on the standardized
  8842. // form of numbers that comes out of bcmath.
  8843. $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
  8844. $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
  8845. }
  8846. return $n;
  8847. } else {
  8848. return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
  8849. }
  8850. }
  8851. /**
  8852. * Scales a float to $scale digits right of decimal point, like BCMath.
  8853. * @param float $r
  8854. * @param int $scale
  8855. * @return string
  8856. */
  8857. private function scale($r, $scale)
  8858. {
  8859. if ($scale < 0) {
  8860. // The f sprintf type doesn't support negative numbers, so we
  8861. // need to cludge things manually. First get the string.
  8862. $r = sprintf('%.0f', (float)$r);
  8863. // Due to floating point precision loss, $r will more than likely
  8864. // look something like 4652999999999.9234. We grab one more digit
  8865. // than we need to precise from $r and then use that to round
  8866. // appropriately.
  8867. $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
  8868. // Now we return it, truncating the zero that was rounded off.
  8869. return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
  8870. }
  8871. return sprintf('%.' . $scale . 'f', (float)$r);
  8872. }
  8873. }
  8874. /**
  8875. * Parses string representations into their corresponding native PHP
  8876. * variable type. The base implementation does a simple type-check.
  8877. */
  8878. class HTMLPurifier_VarParser
  8879. {
  8880. const C_STRING = 1;
  8881. const ISTRING = 2;
  8882. const TEXT = 3;
  8883. const ITEXT = 4;
  8884. const C_INT = 5;
  8885. const C_FLOAT = 6;
  8886. const C_BOOL = 7;
  8887. const LOOKUP = 8;
  8888. const ALIST = 9;
  8889. const HASH = 10;
  8890. const C_MIXED = 11;
  8891. /**
  8892. * Lookup table of allowed types. Mainly for backwards compatibility, but
  8893. * also convenient for transforming string type names to the integer constants.
  8894. */
  8895. public static $types = array(
  8896. 'string' => self::C_STRING,
  8897. 'istring' => self::ISTRING,
  8898. 'text' => self::TEXT,
  8899. 'itext' => self::ITEXT,
  8900. 'int' => self::C_INT,
  8901. 'float' => self::C_FLOAT,
  8902. 'bool' => self::C_BOOL,
  8903. 'lookup' => self::LOOKUP,
  8904. 'list' => self::ALIST,
  8905. 'hash' => self::HASH,
  8906. 'mixed' => self::C_MIXED
  8907. );
  8908. /**
  8909. * Lookup table of types that are string, and can have aliases or
  8910. * allowed value lists.
  8911. */
  8912. public static $stringTypes = array(
  8913. self::C_STRING => true,
  8914. self::ISTRING => true,
  8915. self::TEXT => true,
  8916. self::ITEXT => true,
  8917. );
  8918. /**
  8919. * Validate a variable according to type.
  8920. * It may return NULL as a valid type if $allow_null is true.
  8921. *
  8922. * @param mixed $var Variable to validate
  8923. * @param int $type Type of variable, see HTMLPurifier_VarParser->types
  8924. * @param bool $allow_null Whether or not to permit null as a value
  8925. * @return string Validated and type-coerced variable
  8926. * @throws HTMLPurifier_VarParserException
  8927. */
  8928. final public function parse($var, $type, $allow_null = false)
  8929. {
  8930. if (is_string($type)) {
  8931. if (!isset(HTMLPurifier_VarParser::$types[$type])) {
  8932. throw new HTMLPurifier_VarParserException("Invalid type '$type'");
  8933. } else {
  8934. $type = HTMLPurifier_VarParser::$types[$type];
  8935. }
  8936. }
  8937. $var = $this->parseImplementation($var, $type, $allow_null);
  8938. if ($allow_null && $var === null) {
  8939. return null;
  8940. }
  8941. // These are basic checks, to make sure nothing horribly wrong
  8942. // happened in our implementations.
  8943. switch ($type) {
  8944. case (self::C_STRING):
  8945. case (self::ISTRING):
  8946. case (self::TEXT):
  8947. case (self::ITEXT):
  8948. if (!is_string($var)) {
  8949. break;
  8950. }
  8951. if ($type == self::ISTRING || $type == self::ITEXT) {
  8952. $var = strtolower($var);
  8953. }
  8954. return $var;
  8955. case (self::C_INT):
  8956. if (!is_int($var)) {
  8957. break;
  8958. }
  8959. return $var;
  8960. case (self::C_FLOAT):
  8961. if (!is_float($var)) {
  8962. break;
  8963. }
  8964. return $var;
  8965. case (self::C_BOOL):
  8966. if (!is_bool($var)) {
  8967. break;
  8968. }
  8969. return $var;
  8970. case (self::LOOKUP):
  8971. case (self::ALIST):
  8972. case (self::HASH):
  8973. if (!is_array($var)) {
  8974. break;
  8975. }
  8976. if ($type === self::LOOKUP) {
  8977. foreach ($var as $k) {
  8978. if ($k !== true) {
  8979. $this->error('Lookup table contains value other than true');
  8980. }
  8981. }
  8982. } elseif ($type === self::ALIST) {
  8983. $keys = array_keys($var);
  8984. if (array_keys($keys) !== $keys) {
  8985. $this->error('Indices for list are not uniform');
  8986. }
  8987. }
  8988. return $var;
  8989. case (self::C_MIXED):
  8990. return $var;
  8991. default:
  8992. $this->errorInconsistent(get_class($this), $type);
  8993. }
  8994. $this->errorGeneric($var, $type);
  8995. }
  8996. /**
  8997. * Actually implements the parsing. Base implementation does not
  8998. * do anything to $var. Subclasses should overload this!
  8999. * @param mixed $var
  9000. * @param int $type
  9001. * @param bool $allow_null
  9002. * @return string
  9003. */
  9004. protected function parseImplementation($var, $type, $allow_null)
  9005. {
  9006. return $var;
  9007. }
  9008. /**
  9009. * Throws an exception.
  9010. * @throws HTMLPurifier_VarParserException
  9011. */
  9012. protected function error($msg)
  9013. {
  9014. throw new HTMLPurifier_VarParserException($msg);
  9015. }
  9016. /**
  9017. * Throws an inconsistency exception.
  9018. * @note This should not ever be called. It would be called if we
  9019. * extend the allowed values of HTMLPurifier_VarParser without
  9020. * updating subclasses.
  9021. * @param string $class
  9022. * @param int $type
  9023. * @throws HTMLPurifier_Exception
  9024. */
  9025. protected function errorInconsistent($class, $type)
  9026. {
  9027. throw new HTMLPurifier_Exception(
  9028. "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
  9029. " not implemented"
  9030. );
  9031. }
  9032. /**
  9033. * Generic error for if a type didn't work.
  9034. * @param mixed $var
  9035. * @param int $type
  9036. */
  9037. protected function errorGeneric($var, $type)
  9038. {
  9039. $vtype = gettype($var);
  9040. $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
  9041. }
  9042. /**
  9043. * @param int $type
  9044. * @return string
  9045. */
  9046. public static function getTypeName($type)
  9047. {
  9048. static $lookup;
  9049. if (!$lookup) {
  9050. // Lazy load the alternative lookup table
  9051. $lookup = array_flip(HTMLPurifier_VarParser::$types);
  9052. }
  9053. if (!isset($lookup[$type])) {
  9054. return 'unknown';
  9055. }
  9056. return $lookup[$type];
  9057. }
  9058. }
  9059. /**
  9060. * Exception type for HTMLPurifier_VarParser
  9061. */
  9062. class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
  9063. {
  9064. }
  9065. /**
  9066. * A zipper is a purely-functional data structure which contains
  9067. * a focus that can be efficiently manipulated. It is known as
  9068. * a "one-hole context". This mutable variant implements a zipper
  9069. * for a list as a pair of two arrays, laid out as follows:
  9070. *
  9071. * Base list: 1 2 3 4 [ ] 6 7 8 9
  9072. * Front list: 1 2 3 4
  9073. * Back list: 9 8 7 6
  9074. *
  9075. * User is expected to keep track of the "current element" and properly
  9076. * fill it back in as necessary. (ToDo: Maybe it's more user friendly
  9077. * to implicitly track the current element?)
  9078. *
  9079. * Nota bene: the current class gets confused if you try to store NULLs
  9080. * in the list.
  9081. */
  9082. class HTMLPurifier_Zipper
  9083. {
  9084. public $front, $back;
  9085. public function __construct($front, $back) {
  9086. $this->front = $front;
  9087. $this->back = $back;
  9088. }
  9089. /**
  9090. * Creates a zipper from an array, with a hole in the
  9091. * 0-index position.
  9092. * @param Array to zipper-ify.
  9093. * @return Tuple of zipper and element of first position.
  9094. */
  9095. static public function fromArray($array) {
  9096. $z = new self(array(), array_reverse($array));
  9097. $t = $z->delete(); // delete the "dummy hole"
  9098. return array($z, $t);
  9099. }
  9100. /**
  9101. * Convert zipper back into a normal array, optionally filling in
  9102. * the hole with a value. (Usually you should supply a $t, unless you
  9103. * are at the end of the array.)
  9104. */
  9105. public function toArray($t = NULL) {
  9106. $a = $this->front;
  9107. if ($t !== NULL) $a[] = $t;
  9108. for ($i = count($this->back)-1; $i >= 0; $i--) {
  9109. $a[] = $this->back[$i];
  9110. }
  9111. return $a;
  9112. }
  9113. /**
  9114. * Move hole to the next element.
  9115. * @param $t Element to fill hole with
  9116. * @return Original contents of new hole.
  9117. */
  9118. public function next($t) {
  9119. if ($t !== NULL) array_push($this->front, $t);
  9120. return empty($this->back) ? NULL : array_pop($this->back);
  9121. }
  9122. /**
  9123. * Iterated hole advancement.
  9124. * @param $t Element to fill hole with
  9125. * @param $i How many forward to advance hole
  9126. * @return Original contents of new hole, i away
  9127. */
  9128. public function advance($t, $n) {
  9129. for ($i = 0; $i < $n; $i++) {
  9130. $t = $this->next($t);
  9131. }
  9132. return $t;
  9133. }
  9134. /**
  9135. * Move hole to the previous element
  9136. * @param $t Element to fill hole with
  9137. * @return Original contents of new hole.
  9138. */
  9139. public function prev($t) {
  9140. if ($t !== NULL) array_push($this->back, $t);
  9141. return empty($this->front) ? NULL : array_pop($this->front);
  9142. }
  9143. /**
  9144. * Delete contents of current hole, shifting hole to
  9145. * next element.
  9146. * @return Original contents of new hole.
  9147. */
  9148. public function delete() {
  9149. return empty($this->back) ? NULL : array_pop($this->back);
  9150. }
  9151. /**
  9152. * Returns true if we are at the end of the list.
  9153. * @return bool
  9154. */
  9155. public function done() {
  9156. return empty($this->back);
  9157. }
  9158. /**
  9159. * Insert element before hole.
  9160. * @param Element to insert
  9161. */
  9162. public function insertBefore($t) {
  9163. if ($t !== NULL) array_push($this->front, $t);
  9164. }
  9165. /**
  9166. * Insert element after hole.
  9167. * @param Element to insert
  9168. */
  9169. public function insertAfter($t) {
  9170. if ($t !== NULL) array_push($this->back, $t);
  9171. }
  9172. /**
  9173. * Splice in multiple elements at hole. Functional specification
  9174. * in terms of array_splice:
  9175. *
  9176. * $arr1 = $arr;
  9177. * $old1 = array_splice($arr1, $i, $delete, $replacement);
  9178. *
  9179. * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
  9180. * $t = $z->advance($t, $i);
  9181. * list($old2, $t) = $z->splice($t, $delete, $replacement);
  9182. * $arr2 = $z->toArray($t);
  9183. *
  9184. * assert($old1 === $old2);
  9185. * assert($arr1 === $arr2);
  9186. *
  9187. * NB: the absolute index location after this operation is
  9188. * *unchanged!*
  9189. *
  9190. * @param Current contents of hole.
  9191. */
  9192. public function splice($t, $delete, $replacement) {
  9193. // delete
  9194. $old = array();
  9195. $r = $t;
  9196. for ($i = $delete; $i > 0; $i--) {
  9197. $old[] = $r;
  9198. $r = $this->delete();
  9199. }
  9200. // insert
  9201. for ($i = count($replacement)-1; $i >= 0; $i--) {
  9202. $this->insertAfter($r);
  9203. $r = $replacement[$i];
  9204. }
  9205. return array($old, $r);
  9206. }
  9207. }
  9208. /**
  9209. * Validates the HTML attribute style, otherwise known as CSS.
  9210. * @note We don't implement the whole CSS specification, so it might be
  9211. * difficult to reuse this component in the context of validating
  9212. * actual stylesheet declarations.
  9213. * @note If we were really serious about validating the CSS, we would
  9214. * tokenize the styles and then parse the tokens. Obviously, we
  9215. * are not doing that. Doing that could seriously harm performance,
  9216. * but would make these components a lot more viable for a CSS
  9217. * filtering solution.
  9218. */
  9219. class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
  9220. {
  9221. /**
  9222. * @param string $css
  9223. * @param HTMLPurifier_Config $config
  9224. * @param HTMLPurifier_Context $context
  9225. * @return bool|string
  9226. */
  9227. public function validate($css, $config, $context)
  9228. {
  9229. $css = $this->parseCDATA($css);
  9230. $definition = $config->getCSSDefinition();
  9231. $allow_duplicates = $config->get("CSS.AllowDuplicates");
  9232. // According to the CSS2.1 spec, the places where a
  9233. // non-delimiting semicolon can appear are in strings
  9234. // escape sequences. So here is some dumb hack to
  9235. // handle quotes.
  9236. $len = strlen($css);
  9237. $accum = "";
  9238. $declarations = array();
  9239. $quoted = false;
  9240. for ($i = 0; $i < $len; $i++) {
  9241. $c = strcspn($css, ";'\"", $i);
  9242. $accum .= substr($css, $i, $c);
  9243. $i += $c;
  9244. if ($i == $len) break;
  9245. $d = $css[$i];
  9246. if ($quoted) {
  9247. $accum .= $d;
  9248. if ($d == $quoted) {
  9249. $quoted = false;
  9250. }
  9251. } else {
  9252. if ($d == ";") {
  9253. $declarations[] = $accum;
  9254. $accum = "";
  9255. } else {
  9256. $accum .= $d;
  9257. $quoted = $d;
  9258. }
  9259. }
  9260. }
  9261. if ($accum != "") $declarations[] = $accum;
  9262. $propvalues = array();
  9263. $new_declarations = '';
  9264. /**
  9265. * Name of the current CSS property being validated.
  9266. */
  9267. $property = false;
  9268. $context->register('CurrentCSSProperty', $property);
  9269. foreach ($declarations as $declaration) {
  9270. if (!$declaration) {
  9271. continue;
  9272. }
  9273. if (!strpos($declaration, ':')) {
  9274. continue;
  9275. }
  9276. list($property, $value) = explode(':', $declaration, 2);
  9277. $property = trim($property);
  9278. $value = trim($value);
  9279. $ok = false;
  9280. do {
  9281. if (isset($definition->info[$property])) {
  9282. $ok = true;
  9283. break;
  9284. }
  9285. if (ctype_lower($property)) {
  9286. break;
  9287. }
  9288. $property = strtolower($property);
  9289. if (isset($definition->info[$property])) {
  9290. $ok = true;
  9291. break;
  9292. }
  9293. } while (0);
  9294. if (!$ok) {
  9295. continue;
  9296. }
  9297. // inefficient call, since the validator will do this again
  9298. if (strtolower(trim($value)) !== 'inherit') {
  9299. // inherit works for everything (but only on the base property)
  9300. $result = $definition->info[$property]->validate(
  9301. $value,
  9302. $config,
  9303. $context
  9304. );
  9305. } else {
  9306. $result = 'inherit';
  9307. }
  9308. if ($result === false) {
  9309. continue;
  9310. }
  9311. if ($allow_duplicates) {
  9312. $new_declarations .= "$property:$result;";
  9313. } else {
  9314. $propvalues[$property] = $result;
  9315. }
  9316. }
  9317. $context->destroy('CurrentCSSProperty');
  9318. // procedure does not write the new CSS simultaneously, so it's
  9319. // slightly inefficient, but it's the only way of getting rid of
  9320. // duplicates. Perhaps config to optimize it, but not now.
  9321. foreach ($propvalues as $prop => $value) {
  9322. $new_declarations .= "$prop:$value;";
  9323. }
  9324. return $new_declarations ? $new_declarations : false;
  9325. }
  9326. }
  9327. /**
  9328. * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
  9329. * with make.
  9330. */
  9331. class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
  9332. {
  9333. /**
  9334. * What we're cloning.
  9335. * @type HTMLPurifier_AttrDef
  9336. */
  9337. protected $clone;
  9338. /**
  9339. * @param HTMLPurifier_AttrDef $clone
  9340. */
  9341. public function __construct($clone)
  9342. {
  9343. $this->clone = $clone;
  9344. }
  9345. /**
  9346. * @param string $v
  9347. * @param HTMLPurifier_Config $config
  9348. * @param HTMLPurifier_Context $context
  9349. * @return bool|string
  9350. */
  9351. public function validate($v, $config, $context)
  9352. {
  9353. return $this->clone->validate($v, $config, $context);
  9354. }
  9355. /**
  9356. * @param string $string
  9357. * @return HTMLPurifier_AttrDef
  9358. */
  9359. public function make($string)
  9360. {
  9361. return clone $this->clone;
  9362. }
  9363. }
  9364. // Enum = Enumerated
  9365. /**
  9366. * Validates a keyword against a list of valid values.
  9367. * @warning The case-insensitive compare of this function uses PHP's
  9368. * built-in strtolower and ctype_lower functions, which may
  9369. * cause problems with international comparisons
  9370. */
  9371. class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
  9372. {
  9373. /**
  9374. * Lookup table of valid values.
  9375. * @type array
  9376. * @todo Make protected
  9377. */
  9378. public $valid_values = array();
  9379. /**
  9380. * Bool indicating whether or not enumeration is case sensitive.
  9381. * @note In general this is always case insensitive.
  9382. */
  9383. protected $case_sensitive = false; // values according to W3C spec
  9384. /**
  9385. * @param array $valid_values List of valid values
  9386. * @param bool $case_sensitive Whether or not case sensitive
  9387. */
  9388. public function __construct($valid_values = array(), $case_sensitive = false)
  9389. {
  9390. $this->valid_values = array_flip($valid_values);
  9391. $this->case_sensitive = $case_sensitive;
  9392. }
  9393. /**
  9394. * @param string $string
  9395. * @param HTMLPurifier_Config $config
  9396. * @param HTMLPurifier_Context $context
  9397. * @return bool|string
  9398. */
  9399. public function validate($string, $config, $context)
  9400. {
  9401. $string = trim($string);
  9402. if (!$this->case_sensitive) {
  9403. // we may want to do full case-insensitive libraries
  9404. $string = ctype_lower($string) ? $string : strtolower($string);
  9405. }
  9406. $result = isset($this->valid_values[$string]);
  9407. return $result ? $string : false;
  9408. }
  9409. /**
  9410. * @param string $string In form of comma-delimited list of case-insensitive
  9411. * valid values. Example: "foo,bar,baz". Prepend "s:" to make
  9412. * case sensitive
  9413. * @return HTMLPurifier_AttrDef_Enum
  9414. */
  9415. public function make($string)
  9416. {
  9417. if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
  9418. $string = substr($string, 2);
  9419. $sensitive = true;
  9420. } else {
  9421. $sensitive = false;
  9422. }
  9423. $values = explode(',', $string);
  9424. return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
  9425. }
  9426. }
  9427. /**
  9428. * Validates an integer.
  9429. * @note While this class was modeled off the CSS definition, no currently
  9430. * allowed CSS uses this type. The properties that do are: widows,
  9431. * orphans, z-index, counter-increment, counter-reset. Some of the
  9432. * HTML attributes, however, find use for a non-negative version of this.
  9433. */
  9434. class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
  9435. {
  9436. /**
  9437. * Whether or not negative values are allowed.
  9438. * @type bool
  9439. */
  9440. protected $negative = true;
  9441. /**
  9442. * Whether or not zero is allowed.
  9443. * @type bool
  9444. */
  9445. protected $zero = true;
  9446. /**
  9447. * Whether or not positive values are allowed.
  9448. * @type bool
  9449. */
  9450. protected $positive = true;
  9451. /**
  9452. * @param $negative Bool indicating whether or not negative values are allowed
  9453. * @param $zero Bool indicating whether or not zero is allowed
  9454. * @param $positive Bool indicating whether or not positive values are allowed
  9455. */
  9456. public function __construct($negative = true, $zero = true, $positive = true)
  9457. {
  9458. $this->negative = $negative;
  9459. $this->zero = $zero;
  9460. $this->positive = $positive;
  9461. }
  9462. /**
  9463. * @param string $integer
  9464. * @param HTMLPurifier_Config $config
  9465. * @param HTMLPurifier_Context $context
  9466. * @return bool|string
  9467. */
  9468. public function validate($integer, $config, $context)
  9469. {
  9470. $integer = $this->parseCDATA($integer);
  9471. if ($integer === '') {
  9472. return false;
  9473. }
  9474. // we could possibly simply typecast it to integer, but there are
  9475. // certain fringe cases that must not return an integer.
  9476. // clip leading sign
  9477. if ($this->negative && $integer[0] === '-') {
  9478. $digits = substr($integer, 1);
  9479. if ($digits === '0') {
  9480. $integer = '0';
  9481. } // rm minus sign for zero
  9482. } elseif ($this->positive && $integer[0] === '+') {
  9483. $digits = $integer = substr($integer, 1); // rm unnecessary plus
  9484. } else {
  9485. $digits = $integer;
  9486. }
  9487. // test if it's numeric
  9488. if (!ctype_digit($digits)) {
  9489. return false;
  9490. }
  9491. // perform scope tests
  9492. if (!$this->zero && $integer == 0) {
  9493. return false;
  9494. }
  9495. if (!$this->positive && $integer > 0) {
  9496. return false;
  9497. }
  9498. if (!$this->negative && $integer < 0) {
  9499. return false;
  9500. }
  9501. return $integer;
  9502. }
  9503. }
  9504. /**
  9505. * Validates the HTML attribute lang, effectively a language code.
  9506. * @note Built according to RFC 3066, which obsoleted RFC 1766
  9507. */
  9508. class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
  9509. {
  9510. /**
  9511. * @param string $string
  9512. * @param HTMLPurifier_Config $config
  9513. * @param HTMLPurifier_Context $context
  9514. * @return bool|string
  9515. */
  9516. public function validate($string, $config, $context)
  9517. {
  9518. $string = trim($string);
  9519. if (!$string) {
  9520. return false;
  9521. }
  9522. $subtags = explode('-', $string);
  9523. $num_subtags = count($subtags);
  9524. if ($num_subtags == 0) { // sanity check
  9525. return false;
  9526. }
  9527. // process primary subtag : $subtags[0]
  9528. $length = strlen($subtags[0]);
  9529. switch ($length) {
  9530. case 0:
  9531. return false;
  9532. case 1:
  9533. if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
  9534. return false;
  9535. }
  9536. break;
  9537. case 2:
  9538. case 3:
  9539. if (!ctype_alpha($subtags[0])) {
  9540. return false;
  9541. } elseif (!ctype_lower($subtags[0])) {
  9542. $subtags[0] = strtolower($subtags[0]);
  9543. }
  9544. break;
  9545. default:
  9546. return false;
  9547. }
  9548. $new_string = $subtags[0];
  9549. if ($num_subtags == 1) {
  9550. return $new_string;
  9551. }
  9552. // process second subtag : $subtags[1]
  9553. $length = strlen($subtags[1]);
  9554. if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
  9555. return $new_string;
  9556. }
  9557. if (!ctype_lower($subtags[1])) {
  9558. $subtags[1] = strtolower($subtags[1]);
  9559. }
  9560. $new_string .= '-' . $subtags[1];
  9561. if ($num_subtags == 2) {
  9562. return $new_string;
  9563. }
  9564. // process all other subtags, index 2 and up
  9565. for ($i = 2; $i < $num_subtags; $i++) {
  9566. $length = strlen($subtags[$i]);
  9567. if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
  9568. return $new_string;
  9569. }
  9570. if (!ctype_lower($subtags[$i])) {
  9571. $subtags[$i] = strtolower($subtags[$i]);
  9572. }
  9573. $new_string .= '-' . $subtags[$i];
  9574. }
  9575. return $new_string;
  9576. }
  9577. }
  9578. /**
  9579. * Decorator that, depending on a token, switches between two definitions.
  9580. */
  9581. class HTMLPurifier_AttrDef_Switch
  9582. {
  9583. /**
  9584. * @type string
  9585. */
  9586. protected $tag;
  9587. /**
  9588. * @type HTMLPurifier_AttrDef
  9589. */
  9590. protected $withTag;
  9591. /**
  9592. * @type HTMLPurifier_AttrDef
  9593. */
  9594. protected $withoutTag;
  9595. /**
  9596. * @param string $tag Tag name to switch upon
  9597. * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
  9598. * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
  9599. */
  9600. public function __construct($tag, $with_tag, $without_tag)
  9601. {
  9602. $this->tag = $tag;
  9603. $this->withTag = $with_tag;
  9604. $this->withoutTag = $without_tag;
  9605. }
  9606. /**
  9607. * @param string $string
  9608. * @param HTMLPurifier_Config $config
  9609. * @param HTMLPurifier_Context $context
  9610. * @return bool|string
  9611. */
  9612. public function validate($string, $config, $context)
  9613. {
  9614. $token = $context->get('CurrentToken', true);
  9615. if (!$token || $token->name !== $this->tag) {
  9616. return $this->withoutTag->validate($string, $config, $context);
  9617. } else {
  9618. return $this->withTag->validate($string, $config, $context);
  9619. }
  9620. }
  9621. }
  9622. /**
  9623. * Validates arbitrary text according to the HTML spec.
  9624. */
  9625. class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
  9626. {
  9627. /**
  9628. * @param string $string
  9629. * @param HTMLPurifier_Config $config
  9630. * @param HTMLPurifier_Context $context
  9631. * @return bool|string
  9632. */
  9633. public function validate($string, $config, $context)
  9634. {
  9635. return $this->parseCDATA($string);
  9636. }
  9637. }
  9638. /**
  9639. * Validates a URI as defined by RFC 3986.
  9640. * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
  9641. */
  9642. class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
  9643. {
  9644. /**
  9645. * @type HTMLPurifier_URIParser
  9646. */
  9647. protected $parser;
  9648. /**
  9649. * @type bool
  9650. */
  9651. protected $embedsResource;
  9652. /**
  9653. * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
  9654. */
  9655. public function __construct($embeds_resource = false)
  9656. {
  9657. $this->parser = new HTMLPurifier_URIParser();
  9658. $this->embedsResource = (bool)$embeds_resource;
  9659. }
  9660. /**
  9661. * @param string $string
  9662. * @return HTMLPurifier_AttrDef_URI
  9663. */
  9664. public function make($string)
  9665. {
  9666. $embeds = ($string === 'embedded');
  9667. return new HTMLPurifier_AttrDef_URI($embeds);
  9668. }
  9669. /**
  9670. * @param string $uri
  9671. * @param HTMLPurifier_Config $config
  9672. * @param HTMLPurifier_Context $context
  9673. * @return bool|string
  9674. */
  9675. public function validate($uri, $config, $context)
  9676. {
  9677. if ($config->get('URI.Disable')) {
  9678. return false;
  9679. }
  9680. $uri = $this->parseCDATA($uri);
  9681. // parse the URI
  9682. $uri = $this->parser->parse($uri);
  9683. if ($uri === false) {
  9684. return false;
  9685. }
  9686. // add embedded flag to context for validators
  9687. $context->register('EmbeddedURI', $this->embedsResource);
  9688. $ok = false;
  9689. do {
  9690. // generic validation
  9691. $result = $uri->validate($config, $context);
  9692. if (!$result) {
  9693. break;
  9694. }
  9695. // chained filtering
  9696. $uri_def = $config->getDefinition('URI');
  9697. $result = $uri_def->filter($uri, $config, $context);
  9698. if (!$result) {
  9699. break;
  9700. }
  9701. // scheme-specific validation
  9702. $scheme_obj = $uri->getSchemeObj($config, $context);
  9703. if (!$scheme_obj) {
  9704. break;
  9705. }
  9706. if ($this->embedsResource && !$scheme_obj->browsable) {
  9707. break;
  9708. }
  9709. $result = $scheme_obj->validate($uri, $config, $context);
  9710. if (!$result) {
  9711. break;
  9712. }
  9713. // Post chained filtering
  9714. $result = $uri_def->postFilter($uri, $config, $context);
  9715. if (!$result) {
  9716. break;
  9717. }
  9718. // survived gauntlet
  9719. $ok = true;
  9720. } while (false);
  9721. $context->destroy('EmbeddedURI');
  9722. if (!$ok) {
  9723. return false;
  9724. }
  9725. // back to string
  9726. return $uri->toString();
  9727. }
  9728. }
  9729. /**
  9730. * Validates a number as defined by the CSS spec.
  9731. */
  9732. class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
  9733. {
  9734. /**
  9735. * Indicates whether or not only positive values are allowed.
  9736. * @type bool
  9737. */
  9738. protected $non_negative = false;
  9739. /**
  9740. * @param bool $non_negative indicates whether negatives are forbidden
  9741. */
  9742. public function __construct($non_negative = false)
  9743. {
  9744. $this->non_negative = $non_negative;
  9745. }
  9746. /**
  9747. * @param string $number
  9748. * @param HTMLPurifier_Config $config
  9749. * @param HTMLPurifier_Context $context
  9750. * @return string|bool
  9751. * @warning Some contexts do not pass $config, $context. These
  9752. * variables should not be used without checking HTMLPurifier_Length
  9753. */
  9754. public function validate($number, $config, $context)
  9755. {
  9756. $number = $this->parseCDATA($number);
  9757. if ($number === '') {
  9758. return false;
  9759. }
  9760. if ($number === '0') {
  9761. return '0';
  9762. }
  9763. $sign = '';
  9764. switch ($number[0]) {
  9765. case '-':
  9766. if ($this->non_negative) {
  9767. return false;
  9768. }
  9769. $sign = '-';
  9770. case '+':
  9771. $number = substr($number, 1);
  9772. }
  9773. if (ctype_digit($number)) {
  9774. $number = ltrim($number, '0');
  9775. return $number ? $sign . $number : '0';
  9776. }
  9777. // Period is the only non-numeric character allowed
  9778. if (strpos($number, '.') === false) {
  9779. return false;
  9780. }
  9781. list($left, $right) = explode('.', $number, 2);
  9782. if ($left === '' && $right === '') {
  9783. return false;
  9784. }
  9785. if ($left !== '' && !ctype_digit($left)) {
  9786. return false;
  9787. }
  9788. $left = ltrim($left, '0');
  9789. $right = rtrim($right, '0');
  9790. if ($right === '') {
  9791. return $left ? $sign . $left : '0';
  9792. } elseif (!ctype_digit($right)) {
  9793. return false;
  9794. }
  9795. return $sign . $left . '.' . $right;
  9796. }
  9797. }
  9798. class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
  9799. {
  9800. public function __construct()
  9801. {
  9802. parent::__construct(false); // opacity is non-negative, but we will clamp it
  9803. }
  9804. /**
  9805. * @param string $number
  9806. * @param HTMLPurifier_Config $config
  9807. * @param HTMLPurifier_Context $context
  9808. * @return string
  9809. */
  9810. public function validate($number, $config, $context)
  9811. {
  9812. $result = parent::validate($number, $config, $context);
  9813. if ($result === false) {
  9814. return $result;
  9815. }
  9816. $float = (float)$result;
  9817. if ($float < 0.0) {
  9818. $result = '0';
  9819. }
  9820. if ($float > 1.0) {
  9821. $result = '1';
  9822. }
  9823. return $result;
  9824. }
  9825. }
  9826. /**
  9827. * Validates shorthand CSS property background.
  9828. * @warning Does not support url tokens that have internal spaces.
  9829. */
  9830. class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
  9831. {
  9832. /**
  9833. * Local copy of component validators.
  9834. * @type HTMLPurifier_AttrDef[]
  9835. * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
  9836. */
  9837. protected $info;
  9838. /**
  9839. * @param HTMLPurifier_Config $config
  9840. */
  9841. public function __construct($config)
  9842. {
  9843. $def = $config->getCSSDefinition();
  9844. $this->info['background-color'] = $def->info['background-color'];
  9845. $this->info['background-image'] = $def->info['background-image'];
  9846. $this->info['background-repeat'] = $def->info['background-repeat'];
  9847. $this->info['background-attachment'] = $def->info['background-attachment'];
  9848. $this->info['background-position'] = $def->info['background-position'];
  9849. }
  9850. /**
  9851. * @param string $string
  9852. * @param HTMLPurifier_Config $config
  9853. * @param HTMLPurifier_Context $context
  9854. * @return bool|string
  9855. */
  9856. public function validate($string, $config, $context)
  9857. {
  9858. // regular pre-processing
  9859. $string = $this->parseCDATA($string);
  9860. if ($string === '') {
  9861. return false;
  9862. }
  9863. // munge rgb() decl if necessary
  9864. $string = $this->mungeRgb($string);
  9865. // assumes URI doesn't have spaces in it
  9866. $bits = explode(' ', $string); // bits to process
  9867. $caught = array();
  9868. $caught['color'] = false;
  9869. $caught['image'] = false;
  9870. $caught['repeat'] = false;
  9871. $caught['attachment'] = false;
  9872. $caught['position'] = false;
  9873. $i = 0; // number of catches
  9874. foreach ($bits as $bit) {
  9875. if ($bit === '') {
  9876. continue;
  9877. }
  9878. foreach ($caught as $key => $status) {
  9879. if ($key != 'position') {
  9880. if ($status !== false) {
  9881. continue;
  9882. }
  9883. $r = $this->info['background-' . $key]->validate($bit, $config, $context);
  9884. } else {
  9885. $r = $bit;
  9886. }
  9887. if ($r === false) {
  9888. continue;
  9889. }
  9890. if ($key == 'position') {
  9891. if ($caught[$key] === false) {
  9892. $caught[$key] = '';
  9893. }
  9894. $caught[$key] .= $r . ' ';
  9895. } else {
  9896. $caught[$key] = $r;
  9897. }
  9898. $i++;
  9899. break;
  9900. }
  9901. }
  9902. if (!$i) {
  9903. return false;
  9904. }
  9905. if ($caught['position'] !== false) {
  9906. $caught['position'] = $this->info['background-position']->
  9907. validate($caught['position'], $config, $context);
  9908. }
  9909. $ret = array();
  9910. foreach ($caught as $value) {
  9911. if ($value === false) {
  9912. continue;
  9913. }
  9914. $ret[] = $value;
  9915. }
  9916. if (empty($ret)) {
  9917. return false;
  9918. }
  9919. return implode(' ', $ret);
  9920. }
  9921. }
  9922. /* W3C says:
  9923. [ // adjective and number must be in correct order, even if
  9924. // you could switch them without introducing ambiguity.
  9925. // some browsers support that syntax
  9926. [
  9927. <percentage> | <length> | left | center | right
  9928. ]
  9929. [
  9930. <percentage> | <length> | top | center | bottom
  9931. ]?
  9932. ] |
  9933. [ // this signifies that the vertical and horizontal adjectives
  9934. // can be arbitrarily ordered, however, there can only be two,
  9935. // one of each, or none at all
  9936. [
  9937. left | center | right
  9938. ] ||
  9939. [
  9940. top | center | bottom
  9941. ]
  9942. ]
  9943. top, left = 0%
  9944. center, (none) = 50%
  9945. bottom, right = 100%
  9946. */
  9947. /* QuirksMode says:
  9948. keyword + length/percentage must be ordered correctly, as per W3C
  9949. Internet Explorer and Opera, however, support arbitrary ordering. We
  9950. should fix it up.
  9951. Minor issue though, not strictly necessary.
  9952. */
  9953. // control freaks may appreciate the ability to convert these to
  9954. // percentages or something, but it's not necessary
  9955. /**
  9956. * Validates the value of background-position.
  9957. */
  9958. class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
  9959. {
  9960. /**
  9961. * @type HTMLPurifier_AttrDef_CSS_Length
  9962. */
  9963. protected $length;
  9964. /**
  9965. * @type HTMLPurifier_AttrDef_CSS_Percentage
  9966. */
  9967. protected $percentage;
  9968. public function __construct()
  9969. {
  9970. $this->length = new HTMLPurifier_AttrDef_CSS_Length();
  9971. $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
  9972. }
  9973. /**
  9974. * @param string $string
  9975. * @param HTMLPurifier_Config $config
  9976. * @param HTMLPurifier_Context $context
  9977. * @return bool|string
  9978. */
  9979. public function validate($string, $config, $context)
  9980. {
  9981. $string = $this->parseCDATA($string);
  9982. $bits = explode(' ', $string);
  9983. $keywords = array();
  9984. $keywords['h'] = false; // left, right
  9985. $keywords['v'] = false; // top, bottom
  9986. $keywords['ch'] = false; // center (first word)
  9987. $keywords['cv'] = false; // center (second word)
  9988. $measures = array();
  9989. $i = 0;
  9990. $lookup = array(
  9991. 'top' => 'v',
  9992. 'bottom' => 'v',
  9993. 'left' => 'h',
  9994. 'right' => 'h',
  9995. 'center' => 'c'
  9996. );
  9997. foreach ($bits as $bit) {
  9998. if ($bit === '') {
  9999. continue;
  10000. }
  10001. // test for keyword
  10002. $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
  10003. if (isset($lookup[$lbit])) {
  10004. $status = $lookup[$lbit];
  10005. if ($status == 'c') {
  10006. if ($i == 0) {
  10007. $status = 'ch';
  10008. } else {
  10009. $status = 'cv';
  10010. }
  10011. }
  10012. $keywords[$status] = $lbit;
  10013. $i++;
  10014. }
  10015. // test for length
  10016. $r = $this->length->validate($bit, $config, $context);
  10017. if ($r !== false) {
  10018. $measures[] = $r;
  10019. $i++;
  10020. }
  10021. // test for percentage
  10022. $r = $this->percentage->validate($bit, $config, $context);
  10023. if ($r !== false) {
  10024. $measures[] = $r;
  10025. $i++;
  10026. }
  10027. }
  10028. if (!$i) {
  10029. return false;
  10030. } // no valid values were caught
  10031. $ret = array();
  10032. // first keyword
  10033. if ($keywords['h']) {
  10034. $ret[] = $keywords['h'];
  10035. } elseif ($keywords['ch']) {
  10036. $ret[] = $keywords['ch'];
  10037. $keywords['cv'] = false; // prevent re-use: center = center center
  10038. } elseif (count($measures)) {
  10039. $ret[] = array_shift($measures);
  10040. }
  10041. if ($keywords['v']) {
  10042. $ret[] = $keywords['v'];
  10043. } elseif ($keywords['cv']) {
  10044. $ret[] = $keywords['cv'];
  10045. } elseif (count($measures)) {
  10046. $ret[] = array_shift($measures);
  10047. }
  10048. if (empty($ret)) {
  10049. return false;
  10050. }
  10051. return implode(' ', $ret);
  10052. }
  10053. }
  10054. /**
  10055. * Validates the border property as defined by CSS.
  10056. */
  10057. class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
  10058. {
  10059. /**
  10060. * Local copy of properties this property is shorthand for.
  10061. * @type HTMLPurifier_AttrDef[]
  10062. */
  10063. protected $info = array();
  10064. /**
  10065. * @param HTMLPurifier_Config $config
  10066. */
  10067. public function __construct($config)
  10068. {
  10069. $def = $config->getCSSDefinition();
  10070. $this->info['border-width'] = $def->info['border-width'];
  10071. $this->info['border-style'] = $def->info['border-style'];
  10072. $this->info['border-top-color'] = $def->info['border-top-color'];
  10073. }
  10074. /**
  10075. * @param string $string
  10076. * @param HTMLPurifier_Config $config
  10077. * @param HTMLPurifier_Context $context
  10078. * @return bool|string
  10079. */
  10080. public function validate($string, $config, $context)
  10081. {
  10082. $string = $this->parseCDATA($string);
  10083. $string = $this->mungeRgb($string);
  10084. $bits = explode(' ', $string);
  10085. $done = array(); // segments we've finished
  10086. $ret = ''; // return value
  10087. foreach ($bits as $bit) {
  10088. foreach ($this->info as $propname => $validator) {
  10089. if (isset($done[$propname])) {
  10090. continue;
  10091. }
  10092. $r = $validator->validate($bit, $config, $context);
  10093. if ($r !== false) {
  10094. $ret .= $r . ' ';
  10095. $done[$propname] = true;
  10096. break;
  10097. }
  10098. }
  10099. }
  10100. return rtrim($ret);
  10101. }
  10102. }
  10103. /**
  10104. * Validates Color as defined by CSS.
  10105. */
  10106. class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
  10107. {
  10108. /**
  10109. * @type HTMLPurifier_AttrDef_CSS_AlphaValue
  10110. */
  10111. protected $alpha;
  10112. public function __construct()
  10113. {
  10114. $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  10115. }
  10116. /**
  10117. * @param string $color
  10118. * @param HTMLPurifier_Config $config
  10119. * @param HTMLPurifier_Context $context
  10120. * @return bool|string
  10121. */
  10122. public function validate($color, $config, $context)
  10123. {
  10124. static $colors = null;
  10125. if ($colors === null) {
  10126. $colors = $config->get('Core.ColorKeywords');
  10127. }
  10128. $color = trim($color);
  10129. if ($color === '') {
  10130. return false;
  10131. }
  10132. $lower = strtolower($color);
  10133. if (isset($colors[$lower])) {
  10134. return $colors[$lower];
  10135. }
  10136. if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
  10137. $length = strlen($color);
  10138. if (strpos($color, ')') !== $length - 1) {
  10139. return false;
  10140. }
  10141. // get used function : rgb, rgba, hsl or hsla
  10142. $function = $matches[1];
  10143. $parameters_size = 3;
  10144. $alpha_channel = false;
  10145. if (substr($function, -1) === 'a') {
  10146. $parameters_size = 4;
  10147. $alpha_channel = true;
  10148. }
  10149. /*
  10150. * Allowed types for values :
  10151. * parameter_position => [type => max_value]
  10152. */
  10153. $allowed_types = array(
  10154. 1 => array('percentage' => 100, 'integer' => 255),
  10155. 2 => array('percentage' => 100, 'integer' => 255),
  10156. 3 => array('percentage' => 100, 'integer' => 255),
  10157. );
  10158. $allow_different_types = false;
  10159. if (strpos($function, 'hsl') !== false) {
  10160. $allowed_types = array(
  10161. 1 => array('integer' => 360),
  10162. 2 => array('percentage' => 100),
  10163. 3 => array('percentage' => 100),
  10164. );
  10165. $allow_different_types = true;
  10166. }
  10167. $values = trim(str_replace($function, '', $color), ' ()');
  10168. $parts = explode(',', $values);
  10169. if (count($parts) !== $parameters_size) {
  10170. return false;
  10171. }
  10172. $type = false;
  10173. $new_parts = array();
  10174. $i = 0;
  10175. foreach ($parts as $part) {
  10176. $i++;
  10177. $part = trim($part);
  10178. if ($part === '') {
  10179. return false;
  10180. }
  10181. // different check for alpha channel
  10182. if ($alpha_channel === true && $i === count($parts)) {
  10183. $result = $this->alpha->validate($part, $config, $context);
  10184. if ($result === false) {
  10185. return false;
  10186. }
  10187. $new_parts[] = (string)$result;
  10188. continue;
  10189. }
  10190. if (substr($part, -1) === '%') {
  10191. $current_type = 'percentage';
  10192. } else {
  10193. $current_type = 'integer';
  10194. }
  10195. if (!array_key_exists($current_type, $allowed_types[$i])) {
  10196. return false;
  10197. }
  10198. if (!$type) {
  10199. $type = $current_type;
  10200. }
  10201. if ($allow_different_types === false && $type != $current_type) {
  10202. return false;
  10203. }
  10204. $max_value = $allowed_types[$i][$current_type];
  10205. if ($current_type == 'integer') {
  10206. // Return value between range 0 -> $max_value
  10207. $new_parts[] = (int)max(min($part, $max_value), 0);
  10208. } elseif ($current_type == 'percentage') {
  10209. $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
  10210. }
  10211. }
  10212. $new_values = implode(',', $new_parts);
  10213. $color = $function . '(' . $new_values . ')';
  10214. } else {
  10215. // hexadecimal handling
  10216. if ($color[0] === '#') {
  10217. $hex = substr($color, 1);
  10218. } else {
  10219. $hex = $color;
  10220. $color = '#' . $color;
  10221. }
  10222. $length = strlen($hex);
  10223. if ($length !== 3 && $length !== 6) {
  10224. return false;
  10225. }
  10226. if (!ctype_xdigit($hex)) {
  10227. return false;
  10228. }
  10229. }
  10230. return $color;
  10231. }
  10232. }
  10233. /**
  10234. * Allows multiple validators to attempt to validate attribute.
  10235. *
  10236. * Composite is just what it sounds like: a composite of many validators.
  10237. * This means that multiple HTMLPurifier_AttrDef objects will have a whack
  10238. * at the string. If one of them passes, that's what is returned. This is
  10239. * especially useful for CSS values, which often are a choice between
  10240. * an enumerated set of predefined values or a flexible data type.
  10241. */
  10242. class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
  10243. {
  10244. /**
  10245. * List of objects that may process strings.
  10246. * @type HTMLPurifier_AttrDef[]
  10247. * @todo Make protected
  10248. */
  10249. public $defs;
  10250. /**
  10251. * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
  10252. */
  10253. public function __construct($defs)
  10254. {
  10255. $this->defs = $defs;
  10256. }
  10257. /**
  10258. * @param string $string
  10259. * @param HTMLPurifier_Config $config
  10260. * @param HTMLPurifier_Context $context
  10261. * @return bool|string
  10262. */
  10263. public function validate($string, $config, $context)
  10264. {
  10265. foreach ($this->defs as $i => $def) {
  10266. $result = $this->defs[$i]->validate($string, $config, $context);
  10267. if ($result !== false) {
  10268. return $result;
  10269. }
  10270. }
  10271. return false;
  10272. }
  10273. }
  10274. /**
  10275. * Decorator which enables CSS properties to be disabled for specific elements.
  10276. */
  10277. class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
  10278. {
  10279. /**
  10280. * @type HTMLPurifier_AttrDef
  10281. */
  10282. public $def;
  10283. /**
  10284. * @type string
  10285. */
  10286. public $element;
  10287. /**
  10288. * @param HTMLPurifier_AttrDef $def Definition to wrap
  10289. * @param string $element Element to deny
  10290. */
  10291. public function __construct($def, $element)
  10292. {
  10293. $this->def = $def;
  10294. $this->element = $element;
  10295. }
  10296. /**
  10297. * Checks if CurrentToken is set and equal to $this->element
  10298. * @param string $string
  10299. * @param HTMLPurifier_Config $config
  10300. * @param HTMLPurifier_Context $context
  10301. * @return bool|string
  10302. */
  10303. public function validate($string, $config, $context)
  10304. {
  10305. $token = $context->get('CurrentToken', true);
  10306. if ($token && $token->name == $this->element) {
  10307. return false;
  10308. }
  10309. return $this->def->validate($string, $config, $context);
  10310. }
  10311. }
  10312. /**
  10313. * Microsoft's proprietary filter: CSS property
  10314. * @note Currently supports the alpha filter. In the future, this will
  10315. * probably need an extensible framework
  10316. */
  10317. class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
  10318. {
  10319. /**
  10320. * @type HTMLPurifier_AttrDef_Integer
  10321. */
  10322. protected $intValidator;
  10323. public function __construct()
  10324. {
  10325. $this->intValidator = new HTMLPurifier_AttrDef_Integer();
  10326. }
  10327. /**
  10328. * @param string $value
  10329. * @param HTMLPurifier_Config $config
  10330. * @param HTMLPurifier_Context $context
  10331. * @return bool|string
  10332. */
  10333. public function validate($value, $config, $context)
  10334. {
  10335. $value = $this->parseCDATA($value);
  10336. if ($value === 'none') {
  10337. return $value;
  10338. }
  10339. // if we looped this we could support multiple filters
  10340. $function_length = strcspn($value, '(');
  10341. $function = trim(substr($value, 0, $function_length));
  10342. if ($function !== 'alpha' &&
  10343. $function !== 'Alpha' &&
  10344. $function !== 'progid:DXImageTransform.Microsoft.Alpha'
  10345. ) {
  10346. return false;
  10347. }
  10348. $cursor = $function_length + 1;
  10349. $parameters_length = strcspn($value, ')', $cursor);
  10350. $parameters = substr($value, $cursor, $parameters_length);
  10351. $params = explode(',', $parameters);
  10352. $ret_params = array();
  10353. $lookup = array();
  10354. foreach ($params as $param) {
  10355. list($key, $value) = explode('=', $param);
  10356. $key = trim($key);
  10357. $value = trim($value);
  10358. if (isset($lookup[$key])) {
  10359. continue;
  10360. }
  10361. if ($key !== 'opacity') {
  10362. continue;
  10363. }
  10364. $value = $this->intValidator->validate($value, $config, $context);
  10365. if ($value === false) {
  10366. continue;
  10367. }
  10368. $int = (int)$value;
  10369. if ($int > 100) {
  10370. $value = '100';
  10371. }
  10372. if ($int < 0) {
  10373. $value = '0';
  10374. }
  10375. $ret_params[] = "$key=$value";
  10376. $lookup[$key] = true;
  10377. }
  10378. $ret_parameters = implode(',', $ret_params);
  10379. $ret_function = "$function($ret_parameters)";
  10380. return $ret_function;
  10381. }
  10382. }
  10383. /**
  10384. * Validates shorthand CSS property font.
  10385. */
  10386. class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
  10387. {
  10388. /**
  10389. * Local copy of validators
  10390. * @type HTMLPurifier_AttrDef[]
  10391. * @note If we moved specific CSS property definitions to their own
  10392. * classes instead of having them be assembled at run time by
  10393. * CSSDefinition, this wouldn't be necessary. We'd instantiate
  10394. * our own copies.
  10395. */
  10396. protected $info = array();
  10397. /**
  10398. * @param HTMLPurifier_Config $config
  10399. */
  10400. public function __construct($config)
  10401. {
  10402. $def = $config->getCSSDefinition();
  10403. $this->info['font-style'] = $def->info['font-style'];
  10404. $this->info['font-variant'] = $def->info['font-variant'];
  10405. $this->info['font-weight'] = $def->info['font-weight'];
  10406. $this->info['font-size'] = $def->info['font-size'];
  10407. $this->info['line-height'] = $def->info['line-height'];
  10408. $this->info['font-family'] = $def->info['font-family'];
  10409. }
  10410. /**
  10411. * @param string $string
  10412. * @param HTMLPurifier_Config $config
  10413. * @param HTMLPurifier_Context $context
  10414. * @return bool|string
  10415. */
  10416. public function validate($string, $config, $context)
  10417. {
  10418. static $system_fonts = array(
  10419. 'caption' => true,
  10420. 'icon' => true,
  10421. 'menu' => true,
  10422. 'message-box' => true,
  10423. 'small-caption' => true,
  10424. 'status-bar' => true
  10425. );
  10426. // regular pre-processing
  10427. $string = $this->parseCDATA($string);
  10428. if ($string === '') {
  10429. return false;
  10430. }
  10431. // check if it's one of the keywords
  10432. $lowercase_string = strtolower($string);
  10433. if (isset($system_fonts[$lowercase_string])) {
  10434. return $lowercase_string;
  10435. }
  10436. $bits = explode(' ', $string); // bits to process
  10437. $stage = 0; // this indicates what we're looking for
  10438. $caught = array(); // which stage 0 properties have we caught?
  10439. $stage_1 = array('font-style', 'font-variant', 'font-weight');
  10440. $final = ''; // output
  10441. for ($i = 0, $size = count($bits); $i < $size; $i++) {
  10442. if ($bits[$i] === '') {
  10443. continue;
  10444. }
  10445. switch ($stage) {
  10446. case 0: // attempting to catch font-style, font-variant or font-weight
  10447. foreach ($stage_1 as $validator_name) {
  10448. if (isset($caught[$validator_name])) {
  10449. continue;
  10450. }
  10451. $r = $this->info[$validator_name]->validate(
  10452. $bits[$i],
  10453. $config,
  10454. $context
  10455. );
  10456. if ($r !== false) {
  10457. $final .= $r . ' ';
  10458. $caught[$validator_name] = true;
  10459. break;
  10460. }
  10461. }
  10462. // all three caught, continue on
  10463. if (count($caught) >= 3) {
  10464. $stage = 1;
  10465. }
  10466. if ($r !== false) {
  10467. break;
  10468. }
  10469. case 1: // attempting to catch font-size and perhaps line-height
  10470. $found_slash = false;
  10471. if (strpos($bits[$i], '/') !== false) {
  10472. list($font_size, $line_height) =
  10473. explode('/', $bits[$i]);
  10474. if ($line_height === '') {
  10475. // ooh, there's a space after the slash!
  10476. $line_height = false;
  10477. $found_slash = true;
  10478. }
  10479. } else {
  10480. $font_size = $bits[$i];
  10481. $line_height = false;
  10482. }
  10483. $r = $this->info['font-size']->validate(
  10484. $font_size,
  10485. $config,
  10486. $context
  10487. );
  10488. if ($r !== false) {
  10489. $final .= $r;
  10490. // attempt to catch line-height
  10491. if ($line_height === false) {
  10492. // we need to scroll forward
  10493. for ($j = $i + 1; $j < $size; $j++) {
  10494. if ($bits[$j] === '') {
  10495. continue;
  10496. }
  10497. if ($bits[$j] === '/') {
  10498. if ($found_slash) {
  10499. return false;
  10500. } else {
  10501. $found_slash = true;
  10502. continue;
  10503. }
  10504. }
  10505. $line_height = $bits[$j];
  10506. break;
  10507. }
  10508. } else {
  10509. // slash already found
  10510. $found_slash = true;
  10511. $j = $i;
  10512. }
  10513. if ($found_slash) {
  10514. $i = $j;
  10515. $r = $this->info['line-height']->validate(
  10516. $line_height,
  10517. $config,
  10518. $context
  10519. );
  10520. if ($r !== false) {
  10521. $final .= '/' . $r;
  10522. }
  10523. }
  10524. $final .= ' ';
  10525. $stage = 2;
  10526. break;
  10527. }
  10528. return false;
  10529. case 2: // attempting to catch font-family
  10530. $font_family =
  10531. implode(' ', array_slice($bits, $i, $size - $i));
  10532. $r = $this->info['font-family']->validate(
  10533. $font_family,
  10534. $config,
  10535. $context
  10536. );
  10537. if ($r !== false) {
  10538. $final .= $r . ' ';
  10539. // processing completed successfully
  10540. return rtrim($final);
  10541. }
  10542. return false;
  10543. }
  10544. }
  10545. return false;
  10546. }
  10547. }
  10548. /**
  10549. * Validates a font family list according to CSS spec
  10550. */
  10551. class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
  10552. {
  10553. protected $mask = null;
  10554. public function __construct()
  10555. {
  10556. $this->mask = '_- ';
  10557. for ($c = 'a'; $c <= 'z'; $c++) {
  10558. $this->mask .= $c;
  10559. }
  10560. for ($c = 'A'; $c <= 'Z'; $c++) {
  10561. $this->mask .= $c;
  10562. }
  10563. for ($c = '0'; $c <= '9'; $c++) {
  10564. $this->mask .= $c;
  10565. } // cast-y, but should be fine
  10566. // special bytes used by UTF-8
  10567. for ($i = 0x80; $i <= 0xFF; $i++) {
  10568. // We don't bother excluding invalid bytes in this range,
  10569. // because the our restriction of well-formed UTF-8 will
  10570. // prevent these from ever occurring.
  10571. $this->mask .= chr($i);
  10572. }
  10573. /*
  10574. PHP's internal strcspn implementation is
  10575. O(length of string * length of mask), making it inefficient
  10576. for large masks. However, it's still faster than
  10577. preg_match 8)
  10578. for (p = s1;;) {
  10579. spanp = s2;
  10580. do {
  10581. if (*spanp == c || p == s1_end) {
  10582. return p - s1;
  10583. }
  10584. } while (spanp++ < (s2_end - 1));
  10585. c = *++p;
  10586. }
  10587. */
  10588. // possible optimization: invert the mask.
  10589. }
  10590. /**
  10591. * @param string $string
  10592. * @param HTMLPurifier_Config $config
  10593. * @param HTMLPurifier_Context $context
  10594. * @return bool|string
  10595. */
  10596. public function validate($string, $config, $context)
  10597. {
  10598. static $generic_names = array(
  10599. 'serif' => true,
  10600. 'sans-serif' => true,
  10601. 'monospace' => true,
  10602. 'fantasy' => true,
  10603. 'cursive' => true
  10604. );
  10605. $allowed_fonts = $config->get('CSS.AllowedFonts');
  10606. // assume that no font names contain commas in them
  10607. $fonts = explode(',', $string);
  10608. $final = '';
  10609. foreach ($fonts as $font) {
  10610. $font = trim($font);
  10611. if ($font === '') {
  10612. continue;
  10613. }
  10614. // match a generic name
  10615. if (isset($generic_names[$font])) {
  10616. if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
  10617. $final .= $font . ', ';
  10618. }
  10619. continue;
  10620. }
  10621. // match a quoted name
  10622. if ($font[0] === '"' || $font[0] === "'") {
  10623. $length = strlen($font);
  10624. if ($length <= 2) {
  10625. continue;
  10626. }
  10627. $quote = $font[0];
  10628. if ($font[$length - 1] !== $quote) {
  10629. continue;
  10630. }
  10631. $font = substr($font, 1, $length - 2);
  10632. }
  10633. $font = $this->expandCSSEscape($font);
  10634. // $font is a pure representation of the font name
  10635. if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
  10636. continue;
  10637. }
  10638. if (ctype_alnum($font) && $font !== '') {
  10639. // very simple font, allow it in unharmed
  10640. $final .= $font . ', ';
  10641. continue;
  10642. }
  10643. // bugger out on whitespace. form feed (0C) really
  10644. // shouldn't show up regardless
  10645. $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
  10646. // Here, there are various classes of characters which need
  10647. // to be treated differently:
  10648. // - Alphanumeric characters are essentially safe. We
  10649. // handled these above.
  10650. // - Spaces require quoting, though most parsers will do
  10651. // the right thing if there aren't any characters that
  10652. // can be misinterpreted
  10653. // - Dashes rarely occur, but they fairly unproblematic
  10654. // for parsing/rendering purposes.
  10655. // The above characters cover the majority of Western font
  10656. // names.
  10657. // - Arbitrary Unicode characters not in ASCII. Because
  10658. // most parsers give little thought to Unicode, treatment
  10659. // of these codepoints is basically uniform, even for
  10660. // punctuation-like codepoints. These characters can
  10661. // show up in non-Western pages and are supported by most
  10662. // major browsers, for example: "MS 明朝" is a
  10663. // legitimate font-name
  10664. // <http://ja.wikipedia.org/wiki/MS_明朝>. See
  10665. // the CSS3 spec for more examples:
  10666. // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
  10667. // You can see live samples of these on the Internet:
  10668. // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
  10669. // However, most of these fonts have ASCII equivalents:
  10670. // for example, 'MS Mincho', and it's considered
  10671. // professional to use ASCII font names instead of
  10672. // Unicode font names. Thanks Takeshi Terada for
  10673. // providing this information.
  10674. // The following characters, to my knowledge, have not been
  10675. // used to name font names.
  10676. // - Single quote. While theoretically you might find a
  10677. // font name that has a single quote in its name (serving
  10678. // as an apostrophe, e.g. Dave's Scribble), I haven't
  10679. // been able to find any actual examples of this.
  10680. // Internet Explorer's cssText translation (which I
  10681. // believe is invoked by innerHTML) normalizes any
  10682. // quoting to single quotes, and fails to escape single
  10683. // quotes. (Note that this is not IE's behavior for all
  10684. // CSS properties, just some sort of special casing for
  10685. // font-family). So a single quote *cannot* be used
  10686. // safely in the font-family context if there will be an
  10687. // innerHTML/cssText translation. Note that Firefox 3.x
  10688. // does this too.
  10689. // - Double quote. In IE, these get normalized to
  10690. // single-quotes, no matter what the encoding. (Fun
  10691. // fact, in IE8, the 'content' CSS property gained
  10692. // support, where they special cased to preserve encoded
  10693. // double quotes, but still translate unadorned double
  10694. // quotes into single quotes.) So, because their
  10695. // fixpoint behavior is identical to single quotes, they
  10696. // cannot be allowed either. Firefox 3.x displays
  10697. // single-quote style behavior.
  10698. // - Backslashes are reduced by one (so \\ -> \) every
  10699. // iteration, so they cannot be used safely. This shows
  10700. // up in IE7, IE8 and FF3
  10701. // - Semicolons, commas and backticks are handled properly.
  10702. // - The rest of the ASCII punctuation is handled properly.
  10703. // We haven't checked what browsers do to unadorned
  10704. // versions, but this is not important as long as the
  10705. // browser doesn't /remove/ surrounding quotes (as IE does
  10706. // for HTML).
  10707. //
  10708. // With these results in hand, we conclude that there are
  10709. // various levels of safety:
  10710. // - Paranoid: alphanumeric, spaces and dashes(?)
  10711. // - International: Paranoid + non-ASCII Unicode
  10712. // - Edgy: Everything except quotes, backslashes
  10713. // - NoJS: Standards compliance, e.g. sod IE. Note that
  10714. // with some judicious character escaping (since certain
  10715. // types of escaping doesn't work) this is theoretically
  10716. // OK as long as innerHTML/cssText is not called.
  10717. // We believe that international is a reasonable default
  10718. // (that we will implement now), and once we do more
  10719. // extensive research, we may feel comfortable with dropping
  10720. // it down to edgy.
  10721. // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
  10722. // str(c)spn assumes that the string was already well formed
  10723. // Unicode (which of course it is).
  10724. if (strspn($font, $this->mask) !== strlen($font)) {
  10725. continue;
  10726. }
  10727. // Historical:
  10728. // In the absence of innerHTML/cssText, these ugly
  10729. // transforms don't pose a security risk (as \\ and \"
  10730. // might--these escapes are not supported by most browsers).
  10731. // We could try to be clever and use single-quote wrapping
  10732. // when there is a double quote present, but I have choosen
  10733. // not to implement that. (NOTE: you can reduce the amount
  10734. // of escapes by one depending on what quoting style you use)
  10735. // $font = str_replace('\\', '\\5C ', $font);
  10736. // $font = str_replace('"', '\\22 ', $font);
  10737. // $font = str_replace("'", '\\27 ', $font);
  10738. // font possibly with spaces, requires quoting
  10739. $final .= "'$font', ";
  10740. }
  10741. $final = rtrim($final, ', ');
  10742. if ($final === '') {
  10743. return false;
  10744. }
  10745. return $final;
  10746. }
  10747. }
  10748. /**
  10749. * Validates based on {ident} CSS grammar production
  10750. */
  10751. class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
  10752. {
  10753. /**
  10754. * @param string $string
  10755. * @param HTMLPurifier_Config $config
  10756. * @param HTMLPurifier_Context $context
  10757. * @return bool|string
  10758. */
  10759. public function validate($string, $config, $context)
  10760. {
  10761. $string = trim($string);
  10762. // early abort: '' and '0' (strings that convert to false) are invalid
  10763. if (!$string) {
  10764. return false;
  10765. }
  10766. $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
  10767. if (!preg_match($pattern, $string)) {
  10768. return false;
  10769. }
  10770. return $string;
  10771. }
  10772. }
  10773. /**
  10774. * Decorator which enables !important to be used in CSS values.
  10775. */
  10776. class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
  10777. {
  10778. /**
  10779. * @type HTMLPurifier_AttrDef
  10780. */
  10781. public $def;
  10782. /**
  10783. * @type bool
  10784. */
  10785. public $allow;
  10786. /**
  10787. * @param HTMLPurifier_AttrDef $def Definition to wrap
  10788. * @param bool $allow Whether or not to allow !important
  10789. */
  10790. public function __construct($def, $allow = false)
  10791. {
  10792. $this->def = $def;
  10793. $this->allow = $allow;
  10794. }
  10795. /**
  10796. * Intercepts and removes !important if necessary
  10797. * @param string $string
  10798. * @param HTMLPurifier_Config $config
  10799. * @param HTMLPurifier_Context $context
  10800. * @return bool|string
  10801. */
  10802. public function validate($string, $config, $context)
  10803. {
  10804. // test for ! and important tokens
  10805. $string = trim($string);
  10806. $is_important = false;
  10807. // :TODO: optimization: test directly for !important and ! important
  10808. if (strlen($string) >= 9 && substr($string, -9) === 'important') {
  10809. $temp = rtrim(substr($string, 0, -9));
  10810. // use a temp, because we might want to restore important
  10811. if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
  10812. $string = rtrim(substr($temp, 0, -1));
  10813. $is_important = true;
  10814. }
  10815. }
  10816. $string = $this->def->validate($string, $config, $context);
  10817. if ($this->allow && $is_important) {
  10818. $string .= ' !important';
  10819. }
  10820. return $string;
  10821. }
  10822. }
  10823. /**
  10824. * Represents a Length as defined by CSS.
  10825. */
  10826. class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
  10827. {
  10828. /**
  10829. * @type HTMLPurifier_Length|string
  10830. */
  10831. protected $min;
  10832. /**
  10833. * @type HTMLPurifier_Length|string
  10834. */
  10835. protected $max;
  10836. /**
  10837. * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
  10838. * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
  10839. */
  10840. public function __construct($min = null, $max = null)
  10841. {
  10842. $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
  10843. $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
  10844. }
  10845. /**
  10846. * @param string $string
  10847. * @param HTMLPurifier_Config $config
  10848. * @param HTMLPurifier_Context $context
  10849. * @return bool|string
  10850. */
  10851. public function validate($string, $config, $context)
  10852. {
  10853. $string = $this->parseCDATA($string);
  10854. // Optimizations
  10855. if ($string === '') {
  10856. return false;
  10857. }
  10858. if ($string === '0') {
  10859. return '0';
  10860. }
  10861. if (strlen($string) === 1) {
  10862. return false;
  10863. }
  10864. $length = HTMLPurifier_Length::make($string);
  10865. if (!$length->isValid()) {
  10866. return false;
  10867. }
  10868. if ($this->min) {
  10869. $c = $length->compareTo($this->min);
  10870. if ($c === false) {
  10871. return false;
  10872. }
  10873. if ($c < 0) {
  10874. return false;
  10875. }
  10876. }
  10877. if ($this->max) {
  10878. $c = $length->compareTo($this->max);
  10879. if ($c === false) {
  10880. return false;
  10881. }
  10882. if ($c > 0) {
  10883. return false;
  10884. }
  10885. }
  10886. return $length->toString();
  10887. }
  10888. }
  10889. /**
  10890. * Validates shorthand CSS property list-style.
  10891. * @warning Does not support url tokens that have internal spaces.
  10892. */
  10893. class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
  10894. {
  10895. /**
  10896. * Local copy of validators.
  10897. * @type HTMLPurifier_AttrDef[]
  10898. * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
  10899. */
  10900. protected $info;
  10901. /**
  10902. * @param HTMLPurifier_Config $config
  10903. */
  10904. public function __construct($config)
  10905. {
  10906. $def = $config->getCSSDefinition();
  10907. $this->info['list-style-type'] = $def->info['list-style-type'];
  10908. $this->info['list-style-position'] = $def->info['list-style-position'];
  10909. $this->info['list-style-image'] = $def->info['list-style-image'];
  10910. }
  10911. /**
  10912. * @param string $string
  10913. * @param HTMLPurifier_Config $config
  10914. * @param HTMLPurifier_Context $context
  10915. * @return bool|string
  10916. */
  10917. public function validate($string, $config, $context)
  10918. {
  10919. // regular pre-processing
  10920. $string = $this->parseCDATA($string);
  10921. if ($string === '') {
  10922. return false;
  10923. }
  10924. // assumes URI doesn't have spaces in it
  10925. $bits = explode(' ', strtolower($string)); // bits to process
  10926. $caught = array();
  10927. $caught['type'] = false;
  10928. $caught['position'] = false;
  10929. $caught['image'] = false;
  10930. $i = 0; // number of catches
  10931. $none = false;
  10932. foreach ($bits as $bit) {
  10933. if ($i >= 3) {
  10934. return;
  10935. } // optimization bit
  10936. if ($bit === '') {
  10937. continue;
  10938. }
  10939. foreach ($caught as $key => $status) {
  10940. if ($status !== false) {
  10941. continue;
  10942. }
  10943. $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
  10944. if ($r === false) {
  10945. continue;
  10946. }
  10947. if ($r === 'none') {
  10948. if ($none) {
  10949. continue;
  10950. } else {
  10951. $none = true;
  10952. }
  10953. if ($key == 'image') {
  10954. continue;
  10955. }
  10956. }
  10957. $caught[$key] = $r;
  10958. $i++;
  10959. break;
  10960. }
  10961. }
  10962. if (!$i) {
  10963. return false;
  10964. }
  10965. $ret = array();
  10966. // construct type
  10967. if ($caught['type']) {
  10968. $ret[] = $caught['type'];
  10969. }
  10970. // construct image
  10971. if ($caught['image']) {
  10972. $ret[] = $caught['image'];
  10973. }
  10974. // construct position
  10975. if ($caught['position']) {
  10976. $ret[] = $caught['position'];
  10977. }
  10978. if (empty($ret)) {
  10979. return false;
  10980. }
  10981. return implode(' ', $ret);
  10982. }
  10983. }
  10984. /**
  10985. * Framework class for strings that involve multiple values.
  10986. *
  10987. * Certain CSS properties such as border-width and margin allow multiple
  10988. * lengths to be specified. This class can take a vanilla border-width
  10989. * definition and multiply it, usually into a max of four.
  10990. *
  10991. * @note Even though the CSS specification isn't clear about it, inherit
  10992. * can only be used alone: it will never manifest as part of a multi
  10993. * shorthand declaration. Thus, this class does not allow inherit.
  10994. */
  10995. class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
  10996. {
  10997. /**
  10998. * Instance of component definition to defer validation to.
  10999. * @type HTMLPurifier_AttrDef
  11000. * @todo Make protected
  11001. */
  11002. public $single;
  11003. /**
  11004. * Max number of values allowed.
  11005. * @todo Make protected
  11006. */
  11007. public $max;
  11008. /**
  11009. * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
  11010. * @param int $max Max number of values allowed (usually four)
  11011. */
  11012. public function __construct($single, $max = 4)
  11013. {
  11014. $this->single = $single;
  11015. $this->max = $max;
  11016. }
  11017. /**
  11018. * @param string $string
  11019. * @param HTMLPurifier_Config $config
  11020. * @param HTMLPurifier_Context $context
  11021. * @return bool|string
  11022. */
  11023. public function validate($string, $config, $context)
  11024. {
  11025. $string = $this->mungeRgb($this->parseCDATA($string));
  11026. if ($string === '') {
  11027. return false;
  11028. }
  11029. $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
  11030. $length = count($parts);
  11031. $final = '';
  11032. for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
  11033. if (ctype_space($parts[$i])) {
  11034. continue;
  11035. }
  11036. $result = $this->single->validate($parts[$i], $config, $context);
  11037. if ($result !== false) {
  11038. $final .= $result . ' ';
  11039. $num++;
  11040. }
  11041. }
  11042. if ($final === '') {
  11043. return false;
  11044. }
  11045. return rtrim($final);
  11046. }
  11047. }
  11048. /**
  11049. * Validates a Percentage as defined by the CSS spec.
  11050. */
  11051. class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
  11052. {
  11053. /**
  11054. * Instance to defer number validation to.
  11055. * @type HTMLPurifier_AttrDef_CSS_Number
  11056. */
  11057. protected $number_def;
  11058. /**
  11059. * @param bool $non_negative Whether to forbid negative values
  11060. */
  11061. public function __construct($non_negative = false)
  11062. {
  11063. $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
  11064. }
  11065. /**
  11066. * @param string $string
  11067. * @param HTMLPurifier_Config $config
  11068. * @param HTMLPurifier_Context $context
  11069. * @return bool|string
  11070. */
  11071. public function validate($string, $config, $context)
  11072. {
  11073. $string = $this->parseCDATA($string);
  11074. if ($string === '') {
  11075. return false;
  11076. }
  11077. $length = strlen($string);
  11078. if ($length === 1) {
  11079. return false;
  11080. }
  11081. if ($string[$length - 1] !== '%') {
  11082. return false;
  11083. }
  11084. $number = substr($string, 0, $length - 1);
  11085. $number = $this->number_def->validate($number, $config, $context);
  11086. if ($number === false) {
  11087. return false;
  11088. }
  11089. return "$number%";
  11090. }
  11091. }
  11092. /**
  11093. * Validates the value for the CSS property text-decoration
  11094. * @note This class could be generalized into a version that acts sort of
  11095. * like Enum except you can compound the allowed values.
  11096. */
  11097. class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
  11098. {
  11099. /**
  11100. * @param string $string
  11101. * @param HTMLPurifier_Config $config
  11102. * @param HTMLPurifier_Context $context
  11103. * @return bool|string
  11104. */
  11105. public function validate($string, $config, $context)
  11106. {
  11107. static $allowed_values = array(
  11108. 'line-through' => true,
  11109. 'overline' => true,
  11110. 'underline' => true,
  11111. );
  11112. $string = strtolower($this->parseCDATA($string));
  11113. if ($string === 'none') {
  11114. return $string;
  11115. }
  11116. $parts = explode(' ', $string);
  11117. $final = '';
  11118. foreach ($parts as $part) {
  11119. if (isset($allowed_values[$part])) {
  11120. $final .= $part . ' ';
  11121. }
  11122. }
  11123. $final = rtrim($final);
  11124. if ($final === '') {
  11125. return false;
  11126. }
  11127. return $final;
  11128. }
  11129. }
  11130. /**
  11131. * Validates a URI in CSS syntax, which uses url('http://example.com')
  11132. * @note While theoretically speaking a URI in a CSS document could
  11133. * be non-embedded, as of CSS2 there is no such usage so we're
  11134. * generalizing it. This may need to be changed in the future.
  11135. * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
  11136. * the separator, you cannot put a literal semicolon in
  11137. * in the URI. Try percent encoding it, in that case.
  11138. */
  11139. class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
  11140. {
  11141. public function __construct()
  11142. {
  11143. parent::__construct(true); // always embedded
  11144. }
  11145. /**
  11146. * @param string $uri_string
  11147. * @param HTMLPurifier_Config $config
  11148. * @param HTMLPurifier_Context $context
  11149. * @return bool|string
  11150. */
  11151. public function validate($uri_string, $config, $context)
  11152. {
  11153. // parse the URI out of the string and then pass it onto
  11154. // the parent object
  11155. $uri_string = $this->parseCDATA($uri_string);
  11156. if (strpos($uri_string, 'url(') !== 0) {
  11157. return false;
  11158. }
  11159. $uri_string = substr($uri_string, 4);
  11160. if (strlen($uri_string) == 0) {
  11161. return false;
  11162. }
  11163. $new_length = strlen($uri_string) - 1;
  11164. if ($uri_string[$new_length] != ')') {
  11165. return false;
  11166. }
  11167. $uri = trim(substr($uri_string, 0, $new_length));
  11168. if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
  11169. $quote = $uri[0];
  11170. $new_length = strlen($uri) - 1;
  11171. if ($uri[$new_length] !== $quote) {
  11172. return false;
  11173. }
  11174. $uri = substr($uri, 1, $new_length - 1);
  11175. }
  11176. $uri = $this->expandCSSEscape($uri);
  11177. $result = parent::validate($uri, $config, $context);
  11178. if ($result === false) {
  11179. return false;
  11180. }
  11181. // extra sanity check; should have been done by URI
  11182. $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
  11183. // suspicious characters are ()'; we're going to percent encode
  11184. // them for safety.
  11185. $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
  11186. // there's an extra bug where ampersands lose their escaping on
  11187. // an innerHTML cycle, so a very unlucky query parameter could
  11188. // then change the meaning of the URL. Unfortunately, there's
  11189. // not much we can do about that...
  11190. return "url(\"$result\")";
  11191. }
  11192. }
  11193. /**
  11194. * Validates a boolean attribute
  11195. */
  11196. class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
  11197. {
  11198. /**
  11199. * @type string
  11200. */
  11201. protected $name;
  11202. /**
  11203. * @type bool
  11204. */
  11205. public $minimized = true;
  11206. /**
  11207. * @param bool|string $name
  11208. */
  11209. public function __construct($name = false)
  11210. {
  11211. $this->name = $name;
  11212. }
  11213. /**
  11214. * @param string $string
  11215. * @param HTMLPurifier_Config $config
  11216. * @param HTMLPurifier_Context $context
  11217. * @return bool|string
  11218. */
  11219. public function validate($string, $config, $context)
  11220. {
  11221. return $this->name;
  11222. }
  11223. /**
  11224. * @param string $string Name of attribute
  11225. * @return HTMLPurifier_AttrDef_HTML_Bool
  11226. */
  11227. public function make($string)
  11228. {
  11229. return new HTMLPurifier_AttrDef_HTML_Bool($string);
  11230. }
  11231. }
  11232. /**
  11233. * Validates contents based on NMTOKENS attribute type.
  11234. */
  11235. class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
  11236. {
  11237. /**
  11238. * @param string $string
  11239. * @param HTMLPurifier_Config $config
  11240. * @param HTMLPurifier_Context $context
  11241. * @return bool|string
  11242. */
  11243. public function validate($string, $config, $context)
  11244. {
  11245. $string = trim($string);
  11246. // early abort: '' and '0' (strings that convert to false) are invalid
  11247. if (!$string) {
  11248. return false;
  11249. }
  11250. $tokens = $this->split($string, $config, $context);
  11251. $tokens = $this->filter($tokens, $config, $context);
  11252. if (empty($tokens)) {
  11253. return false;
  11254. }
  11255. return implode(' ', $tokens);
  11256. }
  11257. /**
  11258. * Splits a space separated list of tokens into its constituent parts.
  11259. * @param string $string
  11260. * @param HTMLPurifier_Config $config
  11261. * @param HTMLPurifier_Context $context
  11262. * @return array
  11263. */
  11264. protected function split($string, $config, $context)
  11265. {
  11266. // OPTIMIZABLE!
  11267. // do the preg_match, capture all subpatterns for reformulation
  11268. // we don't support U+00A1 and up codepoints or
  11269. // escaping because I don't know how to do that with regexps
  11270. // and plus it would complicate optimization efforts (you never
  11271. // see that anyway).
  11272. $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
  11273. '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
  11274. '(?:(?=\s)|\z)/'; // look ahead for space or string end
  11275. preg_match_all($pattern, $string, $matches);
  11276. return $matches[1];
  11277. }
  11278. /**
  11279. * Template method for removing certain tokens based on arbitrary criteria.
  11280. * @note If we wanted to be really functional, we'd do an array_filter
  11281. * with a callback. But... we're not.
  11282. * @param array $tokens
  11283. * @param HTMLPurifier_Config $config
  11284. * @param HTMLPurifier_Context $context
  11285. * @return array
  11286. */
  11287. protected function filter($tokens, $config, $context)
  11288. {
  11289. return $tokens;
  11290. }
  11291. }
  11292. /**
  11293. * Implements special behavior for class attribute (normally NMTOKENS)
  11294. */
  11295. class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
  11296. {
  11297. /**
  11298. * @param string $string
  11299. * @param HTMLPurifier_Config $config
  11300. * @param HTMLPurifier_Context $context
  11301. * @return bool|string
  11302. */
  11303. protected function split($string, $config, $context)
  11304. {
  11305. // really, this twiddle should be lazy loaded
  11306. $name = $config->getDefinition('HTML')->doctype->name;
  11307. if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
  11308. return parent::split($string, $config, $context);
  11309. } else {
  11310. return preg_split('/\s+/', $string);
  11311. }
  11312. }
  11313. /**
  11314. * @param array $tokens
  11315. * @param HTMLPurifier_Config $config
  11316. * @param HTMLPurifier_Context $context
  11317. * @return array
  11318. */
  11319. protected function filter($tokens, $config, $context)
  11320. {
  11321. $allowed = $config->get('Attr.AllowedClasses');
  11322. $forbidden = $config->get('Attr.ForbiddenClasses');
  11323. $ret = array();
  11324. foreach ($tokens as $token) {
  11325. if (($allowed === null || isset($allowed[$token])) &&
  11326. !isset($forbidden[$token]) &&
  11327. // We need this O(n) check because of PHP's array
  11328. // implementation that casts -0 to 0.
  11329. !in_array($token, $ret, true)
  11330. ) {
  11331. $ret[] = $token;
  11332. }
  11333. }
  11334. return $ret;
  11335. }
  11336. }
  11337. /**
  11338. * Validates a color according to the HTML spec.
  11339. */
  11340. class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
  11341. {
  11342. /**
  11343. * @param string $string
  11344. * @param HTMLPurifier_Config $config
  11345. * @param HTMLPurifier_Context $context
  11346. * @return bool|string
  11347. */
  11348. public function validate($string, $config, $context)
  11349. {
  11350. static $colors = null;
  11351. if ($colors === null) {
  11352. $colors = $config->get('Core.ColorKeywords');
  11353. }
  11354. $string = trim($string);
  11355. if (empty($string)) {
  11356. return false;
  11357. }
  11358. $lower = strtolower($string);
  11359. if (isset($colors[$lower])) {
  11360. return $colors[$lower];
  11361. }
  11362. if ($string[0] === '#') {
  11363. $hex = substr($string, 1);
  11364. } else {
  11365. $hex = $string;
  11366. }
  11367. $length = strlen($hex);
  11368. if ($length !== 3 && $length !== 6) {
  11369. return false;
  11370. }
  11371. if (!ctype_xdigit($hex)) {
  11372. return false;
  11373. }
  11374. if ($length === 3) {
  11375. $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
  11376. }
  11377. return "#$hex";
  11378. }
  11379. }
  11380. /**
  11381. * Special-case enum attribute definition that lazy loads allowed frame targets
  11382. */
  11383. class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
  11384. {
  11385. /**
  11386. * @type array
  11387. */
  11388. public $valid_values = false; // uninitialized value
  11389. /**
  11390. * @type bool
  11391. */
  11392. protected $case_sensitive = false;
  11393. public function __construct()
  11394. {
  11395. }
  11396. /**
  11397. * @param string $string
  11398. * @param HTMLPurifier_Config $config
  11399. * @param HTMLPurifier_Context $context
  11400. * @return bool|string
  11401. */
  11402. public function validate($string, $config, $context)
  11403. {
  11404. if ($this->valid_values === false) {
  11405. $this->valid_values = $config->get('Attr.AllowedFrameTargets');
  11406. }
  11407. return parent::validate($string, $config, $context);
  11408. }
  11409. }
  11410. /**
  11411. * Validates the HTML attribute ID.
  11412. * @warning Even though this is the id processor, it
  11413. * will ignore the directive Attr:IDBlacklist, since it will only
  11414. * go according to the ID accumulator. Since the accumulator is
  11415. * automatically generated, it will have already absorbed the
  11416. * blacklist. If you're hacking around, make sure you use load()!
  11417. */
  11418. class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
  11419. {
  11420. // selector is NOT a valid thing to use for IDREFs, because IDREFs
  11421. // *must* target IDs that exist, whereas selector #ids do not.
  11422. /**
  11423. * Determines whether or not we're validating an ID in a CSS
  11424. * selector context.
  11425. * @type bool
  11426. */
  11427. protected $selector;
  11428. /**
  11429. * @param bool $selector
  11430. */
  11431. public function __construct($selector = false)
  11432. {
  11433. $this->selector = $selector;
  11434. }
  11435. /**
  11436. * @param string $id
  11437. * @param HTMLPurifier_Config $config
  11438. * @param HTMLPurifier_Context $context
  11439. * @return bool|string
  11440. */
  11441. public function validate($id, $config, $context)
  11442. {
  11443. if (!$this->selector && !$config->get('Attr.EnableID')) {
  11444. return false;
  11445. }
  11446. $id = trim($id); // trim it first
  11447. if ($id === '') {
  11448. return false;
  11449. }
  11450. $prefix = $config->get('Attr.IDPrefix');
  11451. if ($prefix !== '') {
  11452. $prefix .= $config->get('Attr.IDPrefixLocal');
  11453. // prevent re-appending the prefix
  11454. if (strpos($id, $prefix) !== 0) {
  11455. $id = $prefix . $id;
  11456. }
  11457. } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
  11458. trigger_error(
  11459. '%Attr.IDPrefixLocal cannot be used unless ' .
  11460. '%Attr.IDPrefix is set',
  11461. E_USER_WARNING
  11462. );
  11463. }
  11464. if (!$this->selector) {
  11465. $id_accumulator =& $context->get('IDAccumulator');
  11466. if (isset($id_accumulator->ids[$id])) {
  11467. return false;
  11468. }
  11469. }
  11470. // we purposely avoid using regex, hopefully this is faster
  11471. if ($config->get('Attr.ID.HTML5') === true) {
  11472. if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
  11473. return false;
  11474. }
  11475. } else {
  11476. if (ctype_alpha($id)) {
  11477. // OK
  11478. } else {
  11479. if (!ctype_alpha(@$id[0])) {
  11480. return false;
  11481. }
  11482. // primitive style of regexps, I suppose
  11483. $trim = trim(
  11484. $id,
  11485. 'A..Za..z0..9:-._'
  11486. );
  11487. if ($trim !== '') {
  11488. return false;
  11489. }
  11490. }
  11491. }
  11492. $regexp = $config->get('Attr.IDBlacklistRegexp');
  11493. if ($regexp && preg_match($regexp, $id)) {
  11494. return false;
  11495. }
  11496. if (!$this->selector) {
  11497. $id_accumulator->add($id);
  11498. }
  11499. // if no change was made to the ID, return the result
  11500. // else, return the new id if stripping whitespace made it
  11501. // valid, or return false.
  11502. return $id;
  11503. }
  11504. }
  11505. /**
  11506. * Validates an integer representation of pixels according to the HTML spec.
  11507. */
  11508. class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
  11509. {
  11510. /**
  11511. * @type int
  11512. */
  11513. protected $max;
  11514. /**
  11515. * @param int $max
  11516. */
  11517. public function __construct($max = null)
  11518. {
  11519. $this->max = $max;
  11520. }
  11521. /**
  11522. * @param string $string
  11523. * @param HTMLPurifier_Config $config
  11524. * @param HTMLPurifier_Context $context
  11525. * @return bool|string
  11526. */
  11527. public function validate($string, $config, $context)
  11528. {
  11529. $string = trim($string);
  11530. if ($string === '0') {
  11531. return $string;
  11532. }
  11533. if ($string === '') {
  11534. return false;
  11535. }
  11536. $length = strlen($string);
  11537. if (substr($string, $length - 2) == 'px') {
  11538. $string = substr($string, 0, $length - 2);
  11539. }
  11540. if (!is_numeric($string)) {
  11541. return false;
  11542. }
  11543. $int = (int)$string;
  11544. if ($int < 0) {
  11545. return '0';
  11546. }
  11547. // upper-bound value, extremely high values can
  11548. // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
  11549. // WARNING, above link WILL crash you if you're using Windows
  11550. if ($this->max !== null && $int > $this->max) {
  11551. return (string)$this->max;
  11552. }
  11553. return (string)$int;
  11554. }
  11555. /**
  11556. * @param string $string
  11557. * @return HTMLPurifier_AttrDef
  11558. */
  11559. public function make($string)
  11560. {
  11561. if ($string === '') {
  11562. $max = null;
  11563. } else {
  11564. $max = (int)$string;
  11565. }
  11566. $class = get_class($this);
  11567. return new $class($max);
  11568. }
  11569. }
  11570. /**
  11571. * Validates the HTML type length (not to be confused with CSS's length).
  11572. *
  11573. * This accepts integer pixels or percentages as lengths for certain
  11574. * HTML attributes.
  11575. */
  11576. class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
  11577. {
  11578. /**
  11579. * @param string $string
  11580. * @param HTMLPurifier_Config $config
  11581. * @param HTMLPurifier_Context $context
  11582. * @return bool|string
  11583. */
  11584. public function validate($string, $config, $context)
  11585. {
  11586. $string = trim($string);
  11587. if ($string === '') {
  11588. return false;
  11589. }
  11590. $parent_result = parent::validate($string, $config, $context);
  11591. if ($parent_result !== false) {
  11592. return $parent_result;
  11593. }
  11594. $length = strlen($string);
  11595. $last_char = $string[$length - 1];
  11596. if ($last_char !== '%') {
  11597. return false;
  11598. }
  11599. $points = substr($string, 0, $length - 1);
  11600. if (!is_numeric($points)) {
  11601. return false;
  11602. }
  11603. $points = (int)$points;
  11604. if ($points < 0) {
  11605. return '0%';
  11606. }
  11607. if ($points > 100) {
  11608. return '100%';
  11609. }
  11610. return ((string)$points) . '%';
  11611. }
  11612. }
  11613. /**
  11614. * Validates a rel/rev link attribute against a directive of allowed values
  11615. * @note We cannot use Enum because link types allow multiple
  11616. * values.
  11617. * @note Assumes link types are ASCII text
  11618. */
  11619. class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
  11620. {
  11621. /**
  11622. * Name config attribute to pull.
  11623. * @type string
  11624. */
  11625. protected $name;
  11626. /**
  11627. * @param string $name
  11628. */
  11629. public function __construct($name)
  11630. {
  11631. $configLookup = array(
  11632. 'rel' => 'AllowedRel',
  11633. 'rev' => 'AllowedRev'
  11634. );
  11635. if (!isset($configLookup[$name])) {
  11636. trigger_error(
  11637. 'Unrecognized attribute name for link ' .
  11638. 'relationship.',
  11639. E_USER_ERROR
  11640. );
  11641. return;
  11642. }
  11643. $this->name = $configLookup[$name];
  11644. }
  11645. /**
  11646. * @param string $string
  11647. * @param HTMLPurifier_Config $config
  11648. * @param HTMLPurifier_Context $context
  11649. * @return bool|string
  11650. */
  11651. public function validate($string, $config, $context)
  11652. {
  11653. $allowed = $config->get('Attr.' . $this->name);
  11654. if (empty($allowed)) {
  11655. return false;
  11656. }
  11657. $string = $this->parseCDATA($string);
  11658. $parts = explode(' ', $string);
  11659. // lookup to prevent duplicates
  11660. $ret_lookup = array();
  11661. foreach ($parts as $part) {
  11662. $part = strtolower(trim($part));
  11663. if (!isset($allowed[$part])) {
  11664. continue;
  11665. }
  11666. $ret_lookup[$part] = true;
  11667. }
  11668. if (empty($ret_lookup)) {
  11669. return false;
  11670. }
  11671. $string = implode(' ', array_keys($ret_lookup));
  11672. return $string;
  11673. }
  11674. }
  11675. /**
  11676. * Validates a MultiLength as defined by the HTML spec.
  11677. *
  11678. * A multilength is either a integer (pixel count), a percentage, or
  11679. * a relative number.
  11680. */
  11681. class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
  11682. {
  11683. /**
  11684. * @param string $string
  11685. * @param HTMLPurifier_Config $config
  11686. * @param HTMLPurifier_Context $context
  11687. * @return bool|string
  11688. */
  11689. public function validate($string, $config, $context)
  11690. {
  11691. $string = trim($string);
  11692. if ($string === '') {
  11693. return false;
  11694. }
  11695. $parent_result = parent::validate($string, $config, $context);
  11696. if ($parent_result !== false) {
  11697. return $parent_result;
  11698. }
  11699. $length = strlen($string);
  11700. $last_char = $string[$length - 1];
  11701. if ($last_char !== '*') {
  11702. return false;
  11703. }
  11704. $int = substr($string, 0, $length - 1);
  11705. if ($int == '') {
  11706. return '*';
  11707. }
  11708. if (!is_numeric($int)) {
  11709. return false;
  11710. }
  11711. $int = (int)$int;
  11712. if ($int < 0) {
  11713. return false;
  11714. }
  11715. if ($int == 0) {
  11716. return '0';
  11717. }
  11718. if ($int == 1) {
  11719. return '*';
  11720. }
  11721. return ((string)$int) . '*';
  11722. }
  11723. }
  11724. abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
  11725. {
  11726. /**
  11727. * Unpacks a mailbox into its display-name and address
  11728. * @param string $string
  11729. * @return mixed
  11730. */
  11731. public function unpack($string)
  11732. {
  11733. // needs to be implemented
  11734. }
  11735. }
  11736. // sub-implementations
  11737. /**
  11738. * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
  11739. */
  11740. class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
  11741. {
  11742. /**
  11743. * IPv4 sub-validator.
  11744. * @type HTMLPurifier_AttrDef_URI_IPv4
  11745. */
  11746. protected $ipv4;
  11747. /**
  11748. * IPv6 sub-validator.
  11749. * @type HTMLPurifier_AttrDef_URI_IPv6
  11750. */
  11751. protected $ipv6;
  11752. public function __construct()
  11753. {
  11754. $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
  11755. $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
  11756. }
  11757. /**
  11758. * @param string $string
  11759. * @param HTMLPurifier_Config $config
  11760. * @param HTMLPurifier_Context $context
  11761. * @return bool|string
  11762. */
  11763. public function validate($string, $config, $context)
  11764. {
  11765. $length = strlen($string);
  11766. // empty hostname is OK; it's usually semantically equivalent:
  11767. // the default host as defined by a URI scheme is used:
  11768. //
  11769. // If the URI scheme defines a default for host, then that
  11770. // default applies when the host subcomponent is undefined
  11771. // or when the registered name is empty (zero length).
  11772. if ($string === '') {
  11773. return '';
  11774. }
  11775. if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
  11776. //IPv6
  11777. $ip = substr($string, 1, $length - 2);
  11778. $valid = $this->ipv6->validate($ip, $config, $context);
  11779. if ($valid === false) {
  11780. return false;
  11781. }
  11782. return '[' . $valid . ']';
  11783. }
  11784. // need to do checks on unusual encodings too
  11785. $ipv4 = $this->ipv4->validate($string, $config, $context);
  11786. if ($ipv4 !== false) {
  11787. return $ipv4;
  11788. }
  11789. // A regular domain name.
  11790. // This doesn't match I18N domain names, but we don't have proper IRI support,
  11791. // so force users to insert Punycode.
  11792. // There is not a good sense in which underscores should be
  11793. // allowed, since it's technically not! (And if you go as
  11794. // far to allow everything as specified by the DNS spec...
  11795. // well, that's literally everything, modulo some space limits
  11796. // for the components and the overall name (which, by the way,
  11797. // we are NOT checking!). So we (arbitrarily) decide this:
  11798. // let's allow underscores wherever we would have allowed
  11799. // hyphens, if they are enabled. This is a pretty good match
  11800. // for browser behavior, for example, a large number of browsers
  11801. // cannot handle foo_.example.com, but foo_bar.example.com is
  11802. // fairly well supported.
  11803. $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
  11804. // Based off of RFC 1738, but amended so that
  11805. // as per RFC 3696, the top label need only not be all numeric.
  11806. // The productions describing this are:
  11807. $a = '[a-z]'; // alpha
  11808. $an = '[a-z0-9]'; // alphanum
  11809. $and = "[a-z0-9-$underscore]"; // alphanum | "-"
  11810. // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
  11811. $domainlabel = "$an(?:$and*$an)?";
  11812. // AMENDED as per RFC 3696
  11813. // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum
  11814. // side condition: not all numeric
  11815. $toplabel = "$an(?:$and*$an)?";
  11816. // hostname = *( domainlabel "." ) toplabel [ "." ]
  11817. if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
  11818. if (!ctype_digit($matches[1])) {
  11819. return $string;
  11820. }
  11821. }
  11822. // PHP 5.3 and later support this functionality natively
  11823. if (function_exists('idn_to_ascii')) {
  11824. if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) {
  11825. $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
  11826. } else {
  11827. $string = idn_to_ascii($string);
  11828. }
  11829. // If we have Net_IDNA2 support, we can support IRIs by
  11830. // punycoding them. (This is the most portable thing to do,
  11831. // since otherwise we have to assume browsers support
  11832. } elseif ($config->get('Core.EnableIDNA')) {
  11833. $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
  11834. // we need to encode each period separately
  11835. $parts = explode('.', $string);
  11836. try {
  11837. $new_parts = array();
  11838. foreach ($parts as $part) {
  11839. $encodable = false;
  11840. for ($i = 0, $c = strlen($part); $i < $c; $i++) {
  11841. if (ord($part[$i]) > 0x7a) {
  11842. $encodable = true;
  11843. break;
  11844. }
  11845. }
  11846. if (!$encodable) {
  11847. $new_parts[] = $part;
  11848. } else {
  11849. $new_parts[] = $idna->encode($part);
  11850. }
  11851. }
  11852. $string = implode('.', $new_parts);
  11853. } catch (Exception $e) {
  11854. // XXX error reporting
  11855. }
  11856. }
  11857. // Try again
  11858. if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
  11859. return $string;
  11860. }
  11861. return false;
  11862. }
  11863. }
  11864. /**
  11865. * Validates an IPv4 address
  11866. * @author Feyd @ forums.devnetwork.net (public domain)
  11867. */
  11868. class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
  11869. {
  11870. /**
  11871. * IPv4 regex, protected so that IPv6 can reuse it.
  11872. * @type string
  11873. */
  11874. protected $ip4;
  11875. /**
  11876. * @param string $aIP
  11877. * @param HTMLPurifier_Config $config
  11878. * @param HTMLPurifier_Context $context
  11879. * @return bool|string
  11880. */
  11881. public function validate($aIP, $config, $context)
  11882. {
  11883. if (!$this->ip4) {
  11884. $this->_loadRegex();
  11885. }
  11886. if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
  11887. return $aIP;
  11888. }
  11889. return false;
  11890. }
  11891. /**
  11892. * Lazy load function to prevent regex from being stuffed in
  11893. * cache.
  11894. */
  11895. protected function _loadRegex()
  11896. {
  11897. $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
  11898. $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
  11899. }
  11900. }
  11901. /**
  11902. * Validates an IPv6 address.
  11903. * @author Feyd @ forums.devnetwork.net (public domain)
  11904. * @note This function requires brackets to have been removed from address
  11905. * in URI.
  11906. */
  11907. class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
  11908. {
  11909. /**
  11910. * @param string $aIP
  11911. * @param HTMLPurifier_Config $config
  11912. * @param HTMLPurifier_Context $context
  11913. * @return bool|string
  11914. */
  11915. public function validate($aIP, $config, $context)
  11916. {
  11917. if (!$this->ip4) {
  11918. $this->_loadRegex();
  11919. }
  11920. $original = $aIP;
  11921. $hex = '[0-9a-fA-F]';
  11922. $blk = '(?:' . $hex . '{1,4})';
  11923. $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
  11924. // prefix check
  11925. if (strpos($aIP, '/') !== false) {
  11926. if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
  11927. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11928. unset($find);
  11929. } else {
  11930. return false;
  11931. }
  11932. }
  11933. // IPv4-compatiblity check
  11934. if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
  11935. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11936. $ip = explode('.', $find[0]);
  11937. $ip = array_map('dechex', $ip);
  11938. $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
  11939. unset($find, $ip);
  11940. }
  11941. // compression check
  11942. $aIP = explode('::', $aIP);
  11943. $c = count($aIP);
  11944. if ($c > 2) {
  11945. return false;
  11946. } elseif ($c == 2) {
  11947. list($first, $second) = $aIP;
  11948. $first = explode(':', $first);
  11949. $second = explode(':', $second);
  11950. if (count($first) + count($second) > 8) {
  11951. return false;
  11952. }
  11953. while (count($first) < 8) {
  11954. array_push($first, '0');
  11955. }
  11956. array_splice($first, 8 - count($second), 8, $second);
  11957. $aIP = $first;
  11958. unset($first, $second);
  11959. } else {
  11960. $aIP = explode(':', $aIP[0]);
  11961. }
  11962. $c = count($aIP);
  11963. if ($c != 8) {
  11964. return false;
  11965. }
  11966. // All the pieces should be 16-bit hex strings. Are they?
  11967. foreach ($aIP as $piece) {
  11968. if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
  11969. return false;
  11970. }
  11971. }
  11972. return $original;
  11973. }
  11974. }
  11975. /**
  11976. * Primitive email validation class based on the regexp found at
  11977. * http://www.regular-expressions.info/email.html
  11978. */
  11979. class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
  11980. {
  11981. /**
  11982. * @param string $string
  11983. * @param HTMLPurifier_Config $config
  11984. * @param HTMLPurifier_Context $context
  11985. * @return bool|string
  11986. */
  11987. public function validate($string, $config, $context)
  11988. {
  11989. // no support for named mailboxes i.e. "Bob <bob@example.com>"
  11990. // that needs more percent encoding to be done
  11991. if ($string == '') {
  11992. return false;
  11993. }
  11994. $string = trim($string);
  11995. $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
  11996. return $result ? $string : false;
  11997. }
  11998. }
  11999. /**
  12000. * Pre-transform that changes proprietary background attribute to CSS.
  12001. */
  12002. class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
  12003. {
  12004. /**
  12005. * @param array $attr
  12006. * @param HTMLPurifier_Config $config
  12007. * @param HTMLPurifier_Context $context
  12008. * @return array
  12009. */
  12010. public function transform($attr, $config, $context)
  12011. {
  12012. if (!isset($attr['background'])) {
  12013. return $attr;
  12014. }
  12015. $background = $this->confiscateAttr($attr, 'background');
  12016. // some validation should happen here
  12017. $this->prependCSS($attr, "background-image:url($background);");
  12018. return $attr;
  12019. }
  12020. }
  12021. // this MUST be placed in post, as it assumes that any value in dir is valid
  12022. /**
  12023. * Post-trasnform that ensures that bdo tags have the dir attribute set.
  12024. */
  12025. class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
  12026. {
  12027. /**
  12028. * @param array $attr
  12029. * @param HTMLPurifier_Config $config
  12030. * @param HTMLPurifier_Context $context
  12031. * @return array
  12032. */
  12033. public function transform($attr, $config, $context)
  12034. {
  12035. if (isset($attr['dir'])) {
  12036. return $attr;
  12037. }
  12038. $attr['dir'] = $config->get('Attr.DefaultTextDir');
  12039. return $attr;
  12040. }
  12041. }
  12042. /**
  12043. * Pre-transform that changes deprecated bgcolor attribute to CSS.
  12044. */
  12045. class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
  12046. {
  12047. /**
  12048. * @param array $attr
  12049. * @param HTMLPurifier_Config $config
  12050. * @param HTMLPurifier_Context $context
  12051. * @return array
  12052. */
  12053. public function transform($attr, $config, $context)
  12054. {
  12055. if (!isset($attr['bgcolor'])) {
  12056. return $attr;
  12057. }
  12058. $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
  12059. // some validation should happen here
  12060. $this->prependCSS($attr, "background-color:$bgcolor;");
  12061. return $attr;
  12062. }
  12063. }
  12064. /**
  12065. * Pre-transform that changes converts a boolean attribute to fixed CSS
  12066. */
  12067. class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
  12068. {
  12069. /**
  12070. * Name of boolean attribute that is trigger.
  12071. * @type string
  12072. */
  12073. protected $attr;
  12074. /**
  12075. * CSS declarations to add to style, needs trailing semicolon.
  12076. * @type string
  12077. */
  12078. protected $css;
  12079. /**
  12080. * @param string $attr attribute name to convert from
  12081. * @param string $css CSS declarations to add to style (needs semicolon)
  12082. */
  12083. public function __construct($attr, $css)
  12084. {
  12085. $this->attr = $attr;
  12086. $this->css = $css;
  12087. }
  12088. /**
  12089. * @param array $attr
  12090. * @param HTMLPurifier_Config $config
  12091. * @param HTMLPurifier_Context $context
  12092. * @return array
  12093. */
  12094. public function transform($attr, $config, $context)
  12095. {
  12096. if (!isset($attr[$this->attr])) {
  12097. return $attr;
  12098. }
  12099. unset($attr[$this->attr]);
  12100. $this->prependCSS($attr, $this->css);
  12101. return $attr;
  12102. }
  12103. }
  12104. /**
  12105. * Pre-transform that changes deprecated border attribute to CSS.
  12106. */
  12107. class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
  12108. {
  12109. /**
  12110. * @param array $attr
  12111. * @param HTMLPurifier_Config $config
  12112. * @param HTMLPurifier_Context $context
  12113. * @return array
  12114. */
  12115. public function transform($attr, $config, $context)
  12116. {
  12117. if (!isset($attr['border'])) {
  12118. return $attr;
  12119. }
  12120. $border_width = $this->confiscateAttr($attr, 'border');
  12121. // some validation should happen here
  12122. $this->prependCSS($attr, "border:{$border_width}px solid;");
  12123. return $attr;
  12124. }
  12125. }
  12126. /**
  12127. * Generic pre-transform that converts an attribute with a fixed number of
  12128. * values (enumerated) to CSS.
  12129. */
  12130. class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
  12131. {
  12132. /**
  12133. * Name of attribute to transform from.
  12134. * @type string
  12135. */
  12136. protected $attr;
  12137. /**
  12138. * Lookup array of attribute values to CSS.
  12139. * @type array
  12140. */
  12141. protected $enumToCSS = array();
  12142. /**
  12143. * Case sensitivity of the matching.
  12144. * @type bool
  12145. * @warning Currently can only be guaranteed to work with ASCII
  12146. * values.
  12147. */
  12148. protected $caseSensitive = false;
  12149. /**
  12150. * @param string $attr Attribute name to transform from
  12151. * @param array $enum_to_css Lookup array of attribute values to CSS
  12152. * @param bool $case_sensitive Case sensitivity indicator, default false
  12153. */
  12154. public function __construct($attr, $enum_to_css, $case_sensitive = false)
  12155. {
  12156. $this->attr = $attr;
  12157. $this->enumToCSS = $enum_to_css;
  12158. $this->caseSensitive = (bool)$case_sensitive;
  12159. }
  12160. /**
  12161. * @param array $attr
  12162. * @param HTMLPurifier_Config $config
  12163. * @param HTMLPurifier_Context $context
  12164. * @return array
  12165. */
  12166. public function transform($attr, $config, $context)
  12167. {
  12168. if (!isset($attr[$this->attr])) {
  12169. return $attr;
  12170. }
  12171. $value = trim($attr[$this->attr]);
  12172. unset($attr[$this->attr]);
  12173. if (!$this->caseSensitive) {
  12174. $value = strtolower($value);
  12175. }
  12176. if (!isset($this->enumToCSS[$value])) {
  12177. return $attr;
  12178. }
  12179. $this->prependCSS($attr, $this->enumToCSS[$value]);
  12180. return $attr;
  12181. }
  12182. }
  12183. // must be called POST validation
  12184. /**
  12185. * Transform that supplies default values for the src and alt attributes
  12186. * in img tags, as well as prevents the img tag from being removed
  12187. * because of a missing alt tag. This needs to be registered as both
  12188. * a pre and post attribute transform.
  12189. */
  12190. class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
  12191. {
  12192. /**
  12193. * @param array $attr
  12194. * @param HTMLPurifier_Config $config
  12195. * @param HTMLPurifier_Context $context
  12196. * @return array
  12197. */
  12198. public function transform($attr, $config, $context)
  12199. {
  12200. $src = true;
  12201. if (!isset($attr['src'])) {
  12202. if ($config->get('Core.RemoveInvalidImg')) {
  12203. return $attr;
  12204. }
  12205. $attr['src'] = $config->get('Attr.DefaultInvalidImage');
  12206. $src = false;
  12207. }
  12208. if (!isset($attr['alt'])) {
  12209. if ($src) {
  12210. $alt = $config->get('Attr.DefaultImageAlt');
  12211. if ($alt === null) {
  12212. $attr['alt'] = basename($attr['src']);
  12213. } else {
  12214. $attr['alt'] = $alt;
  12215. }
  12216. } else {
  12217. $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
  12218. }
  12219. }
  12220. return $attr;
  12221. }
  12222. }
  12223. /**
  12224. * Pre-transform that changes deprecated hspace and vspace attributes to CSS
  12225. */
  12226. class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
  12227. {
  12228. /**
  12229. * @type string
  12230. */
  12231. protected $attr;
  12232. /**
  12233. * @type array
  12234. */
  12235. protected $css = array(
  12236. 'hspace' => array('left', 'right'),
  12237. 'vspace' => array('top', 'bottom')
  12238. );
  12239. /**
  12240. * @param string $attr
  12241. */
  12242. public function __construct($attr)
  12243. {
  12244. $this->attr = $attr;
  12245. if (!isset($this->css[$attr])) {
  12246. trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
  12247. }
  12248. }
  12249. /**
  12250. * @param array $attr
  12251. * @param HTMLPurifier_Config $config
  12252. * @param HTMLPurifier_Context $context
  12253. * @return array
  12254. */
  12255. public function transform($attr, $config, $context)
  12256. {
  12257. if (!isset($attr[$this->attr])) {
  12258. return $attr;
  12259. }
  12260. $width = $this->confiscateAttr($attr, $this->attr);
  12261. // some validation could happen here
  12262. if (!isset($this->css[$this->attr])) {
  12263. return $attr;
  12264. }
  12265. $style = '';
  12266. foreach ($this->css[$this->attr] as $suffix) {
  12267. $property = "margin-$suffix";
  12268. $style .= "$property:{$width}px;";
  12269. }
  12270. $this->prependCSS($attr, $style);
  12271. return $attr;
  12272. }
  12273. }
  12274. /**
  12275. * Performs miscellaneous cross attribute validation and filtering for
  12276. * input elements. This is meant to be a post-transform.
  12277. */
  12278. class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
  12279. {
  12280. /**
  12281. * @type HTMLPurifier_AttrDef_HTML_Pixels
  12282. */
  12283. protected $pixels;
  12284. public function __construct()
  12285. {
  12286. $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
  12287. }
  12288. /**
  12289. * @param array $attr
  12290. * @param HTMLPurifier_Config $config
  12291. * @param HTMLPurifier_Context $context
  12292. * @return array
  12293. */
  12294. public function transform($attr, $config, $context)
  12295. {
  12296. if (!isset($attr['type'])) {
  12297. $t = 'text';
  12298. } else {
  12299. $t = strtolower($attr['type']);
  12300. }
  12301. if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
  12302. unset($attr['checked']);
  12303. }
  12304. if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
  12305. unset($attr['maxlength']);
  12306. }
  12307. if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
  12308. $result = $this->pixels->validate($attr['size'], $config, $context);
  12309. if ($result === false) {
  12310. unset($attr['size']);
  12311. } else {
  12312. $attr['size'] = $result;
  12313. }
  12314. }
  12315. if (isset($attr['src']) && $t !== 'image') {
  12316. unset($attr['src']);
  12317. }
  12318. if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
  12319. $attr['value'] = '';
  12320. }
  12321. return $attr;
  12322. }
  12323. }
  12324. /**
  12325. * Post-transform that copies lang's value to xml:lang (and vice-versa)
  12326. * @note Theoretically speaking, this could be a pre-transform, but putting
  12327. * post is more efficient.
  12328. */
  12329. class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
  12330. {
  12331. /**
  12332. * @param array $attr
  12333. * @param HTMLPurifier_Config $config
  12334. * @param HTMLPurifier_Context $context
  12335. * @return array
  12336. */
  12337. public function transform($attr, $config, $context)
  12338. {
  12339. $lang = isset($attr['lang']) ? $attr['lang'] : false;
  12340. $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
  12341. if ($lang !== false && $xml_lang === false) {
  12342. $attr['xml:lang'] = $lang;
  12343. } elseif ($xml_lang !== false) {
  12344. $attr['lang'] = $xml_lang;
  12345. }
  12346. return $attr;
  12347. }
  12348. }
  12349. /**
  12350. * Class for handling width/height length attribute transformations to CSS
  12351. */
  12352. class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
  12353. {
  12354. /**
  12355. * @type string
  12356. */
  12357. protected $name;
  12358. /**
  12359. * @type string
  12360. */
  12361. protected $cssName;
  12362. public function __construct($name, $css_name = null)
  12363. {
  12364. $this->name = $name;
  12365. $this->cssName = $css_name ? $css_name : $name;
  12366. }
  12367. /**
  12368. * @param array $attr
  12369. * @param HTMLPurifier_Config $config
  12370. * @param HTMLPurifier_Context $context
  12371. * @return array
  12372. */
  12373. public function transform($attr, $config, $context)
  12374. {
  12375. if (!isset($attr[$this->name])) {
  12376. return $attr;
  12377. }
  12378. $length = $this->confiscateAttr($attr, $this->name);
  12379. if (ctype_digit($length)) {
  12380. $length .= 'px';
  12381. }
  12382. $this->prependCSS($attr, $this->cssName . ":$length;");
  12383. return $attr;
  12384. }
  12385. }
  12386. /**
  12387. * Pre-transform that changes deprecated name attribute to ID if necessary
  12388. */
  12389. class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
  12390. {
  12391. /**
  12392. * @param array $attr
  12393. * @param HTMLPurifier_Config $config
  12394. * @param HTMLPurifier_Context $context
  12395. * @return array
  12396. */
  12397. public function transform($attr, $config, $context)
  12398. {
  12399. // Abort early if we're using relaxed definition of name
  12400. if ($config->get('HTML.Attr.Name.UseCDATA')) {
  12401. return $attr;
  12402. }
  12403. if (!isset($attr['name'])) {
  12404. return $attr;
  12405. }
  12406. $id = $this->confiscateAttr($attr, 'name');
  12407. if (isset($attr['id'])) {
  12408. return $attr;
  12409. }
  12410. $attr['id'] = $id;
  12411. return $attr;
  12412. }
  12413. }
  12414. /**
  12415. * Post-transform that performs validation to the name attribute; if
  12416. * it is present with an equivalent id attribute, it is passed through;
  12417. * otherwise validation is performed.
  12418. */
  12419. class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
  12420. {
  12421. public function __construct()
  12422. {
  12423. $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
  12424. }
  12425. /**
  12426. * @param array $attr
  12427. * @param HTMLPurifier_Config $config
  12428. * @param HTMLPurifier_Context $context
  12429. * @return array
  12430. */
  12431. public function transform($attr, $config, $context)
  12432. {
  12433. if (!isset($attr['name'])) {
  12434. return $attr;
  12435. }
  12436. $name = $attr['name'];
  12437. if (isset($attr['id']) && $attr['id'] === $name) {
  12438. return $attr;
  12439. }
  12440. $result = $this->idDef->validate($name, $config, $context);
  12441. if ($result === false) {
  12442. unset($attr['name']);
  12443. } else {
  12444. $attr['name'] = $result;
  12445. }
  12446. return $attr;
  12447. }
  12448. }
  12449. // must be called POST validation
  12450. /**
  12451. * Adds rel="nofollow" to all outbound links. This transform is
  12452. * only attached if Attr.Nofollow is TRUE.
  12453. */
  12454. class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
  12455. {
  12456. /**
  12457. * @type HTMLPurifier_URIParser
  12458. */
  12459. private $parser;
  12460. public function __construct()
  12461. {
  12462. $this->parser = new HTMLPurifier_URIParser();
  12463. }
  12464. /**
  12465. * @param array $attr
  12466. * @param HTMLPurifier_Config $config
  12467. * @param HTMLPurifier_Context $context
  12468. * @return array
  12469. */
  12470. public function transform($attr, $config, $context)
  12471. {
  12472. if (!isset($attr['href'])) {
  12473. return $attr;
  12474. }
  12475. // XXX Kind of inefficient
  12476. $url = $this->parser->parse($attr['href']);
  12477. $scheme = $url->getSchemeObj($config, $context);
  12478. if ($scheme->browsable && !$url->isLocal($config, $context)) {
  12479. if (isset($attr['rel'])) {
  12480. $rels = explode(' ', $attr['rel']);
  12481. if (!in_array('nofollow', $rels)) {
  12482. $rels[] = 'nofollow';
  12483. }
  12484. $attr['rel'] = implode(' ', $rels);
  12485. } else {
  12486. $attr['rel'] = 'nofollow';
  12487. }
  12488. }
  12489. return $attr;
  12490. }
  12491. }
  12492. class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
  12493. {
  12494. /**
  12495. * @type string
  12496. */
  12497. public $name = "SafeEmbed";
  12498. /**
  12499. * @param array $attr
  12500. * @param HTMLPurifier_Config $config
  12501. * @param HTMLPurifier_Context $context
  12502. * @return array
  12503. */
  12504. public function transform($attr, $config, $context)
  12505. {
  12506. $attr['allowscriptaccess'] = 'never';
  12507. $attr['allownetworking'] = 'internal';
  12508. $attr['type'] = 'application/x-shockwave-flash';
  12509. return $attr;
  12510. }
  12511. }
  12512. /**
  12513. * Writes default type for all objects. Currently only supports flash.
  12514. */
  12515. class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
  12516. {
  12517. /**
  12518. * @type string
  12519. */
  12520. public $name = "SafeObject";
  12521. /**
  12522. * @param array $attr
  12523. * @param HTMLPurifier_Config $config
  12524. * @param HTMLPurifier_Context $context
  12525. * @return array
  12526. */
  12527. public function transform($attr, $config, $context)
  12528. {
  12529. if (!isset($attr['type'])) {
  12530. $attr['type'] = 'application/x-shockwave-flash';
  12531. }
  12532. return $attr;
  12533. }
  12534. }
  12535. /**
  12536. * Validates name/value pairs in param tags to be used in safe objects. This
  12537. * will only allow name values it recognizes, and pre-fill certain attributes
  12538. * with required values.
  12539. *
  12540. * @note
  12541. * This class only supports Flash. In the future, Quicktime support
  12542. * may be added.
  12543. *
  12544. * @warning
  12545. * This class expects an injector to add the necessary parameters tags.
  12546. */
  12547. class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
  12548. {
  12549. /**
  12550. * @type string
  12551. */
  12552. public $name = "SafeParam";
  12553. /**
  12554. * @type HTMLPurifier_AttrDef_URI
  12555. */
  12556. private $uri;
  12557. public function __construct()
  12558. {
  12559. $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
  12560. $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
  12561. }
  12562. /**
  12563. * @param array $attr
  12564. * @param HTMLPurifier_Config $config
  12565. * @param HTMLPurifier_Context $context
  12566. * @return array
  12567. */
  12568. public function transform($attr, $config, $context)
  12569. {
  12570. // If we add support for other objects, we'll need to alter the
  12571. // transforms.
  12572. switch ($attr['name']) {
  12573. // application/x-shockwave-flash
  12574. // Keep this synchronized with Injector/SafeObject.php
  12575. case 'allowScriptAccess':
  12576. $attr['value'] = 'never';
  12577. break;
  12578. case 'allowNetworking':
  12579. $attr['value'] = 'internal';
  12580. break;
  12581. case 'allowFullScreen':
  12582. if ($config->get('HTML.FlashAllowFullScreen')) {
  12583. $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
  12584. } else {
  12585. $attr['value'] = 'false';
  12586. }
  12587. break;
  12588. case 'wmode':
  12589. $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
  12590. break;
  12591. case 'movie':
  12592. case 'src':
  12593. $attr['name'] = "movie";
  12594. $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
  12595. break;
  12596. case 'flashvars':
  12597. // we're going to allow arbitrary inputs to the SWF, on
  12598. // the reasoning that it could only hack the SWF, not us.
  12599. break;
  12600. // add other cases to support other param name/value pairs
  12601. default:
  12602. $attr['name'] = $attr['value'] = null;
  12603. }
  12604. return $attr;
  12605. }
  12606. }
  12607. /**
  12608. * Implements required attribute stipulation for <script>
  12609. */
  12610. class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
  12611. {
  12612. /**
  12613. * @param array $attr
  12614. * @param HTMLPurifier_Config $config
  12615. * @param HTMLPurifier_Context $context
  12616. * @return array
  12617. */
  12618. public function transform($attr, $config, $context)
  12619. {
  12620. if (!isset($attr['type'])) {
  12621. $attr['type'] = 'text/javascript';
  12622. }
  12623. return $attr;
  12624. }
  12625. }
  12626. // must be called POST validation
  12627. /**
  12628. * Adds target="blank" to all outbound links. This transform is
  12629. * only attached if Attr.TargetBlank is TRUE. This works regardless
  12630. * of whether or not Attr.AllowedFrameTargets
  12631. */
  12632. class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
  12633. {
  12634. /**
  12635. * @type HTMLPurifier_URIParser
  12636. */
  12637. private $parser;
  12638. public function __construct()
  12639. {
  12640. $this->parser = new HTMLPurifier_URIParser();
  12641. }
  12642. /**
  12643. * @param array $attr
  12644. * @param HTMLPurifier_Config $config
  12645. * @param HTMLPurifier_Context $context
  12646. * @return array
  12647. */
  12648. public function transform($attr, $config, $context)
  12649. {
  12650. if (!isset($attr['href'])) {
  12651. return $attr;
  12652. }
  12653. // XXX Kind of inefficient
  12654. $url = $this->parser->parse($attr['href']);
  12655. $scheme = $url->getSchemeObj($config, $context);
  12656. if ($scheme->browsable && !$url->isBenign($config, $context)) {
  12657. $attr['target'] = '_blank';
  12658. }
  12659. return $attr;
  12660. }
  12661. }
  12662. // must be called POST validation
  12663. /**
  12664. * Adds rel="noopener" to any links which target a different window
  12665. * than the current one. This is used to prevent malicious websites
  12666. * from silently replacing the original window, which could be used
  12667. * to do phishing.
  12668. * This transform is controlled by %HTML.TargetNoopener.
  12669. */
  12670. class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform
  12671. {
  12672. /**
  12673. * @param array $attr
  12674. * @param HTMLPurifier_Config $config
  12675. * @param HTMLPurifier_Context $context
  12676. * @return array
  12677. */
  12678. public function transform($attr, $config, $context)
  12679. {
  12680. if (isset($attr['rel'])) {
  12681. $rels = explode(' ', $attr['rel']);
  12682. } else {
  12683. $rels = array();
  12684. }
  12685. if (isset($attr['target']) && !in_array('noopener', $rels)) {
  12686. $rels[] = 'noopener';
  12687. }
  12688. if (!empty($rels) || isset($attr['rel'])) {
  12689. $attr['rel'] = implode(' ', $rels);
  12690. }
  12691. return $attr;
  12692. }
  12693. }
  12694. // must be called POST validation
  12695. /**
  12696. * Adds rel="noreferrer" to any links which target a different window
  12697. * than the current one. This is used to prevent malicious websites
  12698. * from silently replacing the original window, which could be used
  12699. * to do phishing.
  12700. * This transform is controlled by %HTML.TargetNoreferrer.
  12701. */
  12702. class HTMLPurifier_AttrTransform_TargetNoreferrer extends HTMLPurifier_AttrTransform
  12703. {
  12704. /**
  12705. * @param array $attr
  12706. * @param HTMLPurifier_Config $config
  12707. * @param HTMLPurifier_Context $context
  12708. * @return array
  12709. */
  12710. public function transform($attr, $config, $context)
  12711. {
  12712. if (isset($attr['rel'])) {
  12713. $rels = explode(' ', $attr['rel']);
  12714. } else {
  12715. $rels = array();
  12716. }
  12717. if (isset($attr['target']) && !in_array('noreferrer', $rels)) {
  12718. $rels[] = 'noreferrer';
  12719. }
  12720. if (!empty($rels) || isset($attr['rel'])) {
  12721. $attr['rel'] = implode(' ', $rels);
  12722. }
  12723. return $attr;
  12724. }
  12725. }
  12726. /**
  12727. * Sets height/width defaults for <textarea>
  12728. */
  12729. class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
  12730. {
  12731. /**
  12732. * @param array $attr
  12733. * @param HTMLPurifier_Config $config
  12734. * @param HTMLPurifier_Context $context
  12735. * @return array
  12736. */
  12737. public function transform($attr, $config, $context)
  12738. {
  12739. // Calculated from Firefox
  12740. if (!isset($attr['cols'])) {
  12741. $attr['cols'] = '22';
  12742. }
  12743. if (!isset($attr['rows'])) {
  12744. $attr['rows'] = '3';
  12745. }
  12746. return $attr;
  12747. }
  12748. }
  12749. /**
  12750. * Definition that uses different definitions depending on context.
  12751. *
  12752. * The del and ins tags are notable because they allow different types of
  12753. * elements depending on whether or not they're in a block or inline context.
  12754. * Chameleon allows this behavior to happen by using two different
  12755. * definitions depending on context. While this somewhat generalized,
  12756. * it is specifically intended for those two tags.
  12757. */
  12758. class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
  12759. {
  12760. /**
  12761. * Instance of the definition object to use when inline. Usually stricter.
  12762. * @type HTMLPurifier_ChildDef_Optional
  12763. */
  12764. public $inline;
  12765. /**
  12766. * Instance of the definition object to use when block.
  12767. * @type HTMLPurifier_ChildDef_Optional
  12768. */
  12769. public $block;
  12770. /**
  12771. * @type string
  12772. */
  12773. public $type = 'chameleon';
  12774. /**
  12775. * @param array $inline List of elements to allow when inline.
  12776. * @param array $block List of elements to allow when block.
  12777. */
  12778. public function __construct($inline, $block)
  12779. {
  12780. $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
  12781. $this->block = new HTMLPurifier_ChildDef_Optional($block);
  12782. $this->elements = $this->block->elements;
  12783. }
  12784. /**
  12785. * @param HTMLPurifier_Node[] $children
  12786. * @param HTMLPurifier_Config $config
  12787. * @param HTMLPurifier_Context $context
  12788. * @return bool
  12789. */
  12790. public function validateChildren($children, $config, $context)
  12791. {
  12792. if ($context->get('IsInline') === false) {
  12793. return $this->block->validateChildren(
  12794. $children,
  12795. $config,
  12796. $context
  12797. );
  12798. } else {
  12799. return $this->inline->validateChildren(
  12800. $children,
  12801. $config,
  12802. $context
  12803. );
  12804. }
  12805. }
  12806. }
  12807. /**
  12808. * Custom validation class, accepts DTD child definitions
  12809. *
  12810. * @warning Currently this class is an all or nothing proposition, that is,
  12811. * it will only give a bool return value.
  12812. */
  12813. class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
  12814. {
  12815. /**
  12816. * @type string
  12817. */
  12818. public $type = 'custom';
  12819. /**
  12820. * @type bool
  12821. */
  12822. public $allow_empty = false;
  12823. /**
  12824. * Allowed child pattern as defined by the DTD.
  12825. * @type string
  12826. */
  12827. public $dtd_regex;
  12828. /**
  12829. * PCRE regex derived from $dtd_regex.
  12830. * @type string
  12831. */
  12832. private $_pcre_regex;
  12833. /**
  12834. * @param $dtd_regex Allowed child pattern from the DTD
  12835. */
  12836. public function __construct($dtd_regex)
  12837. {
  12838. $this->dtd_regex = $dtd_regex;
  12839. $this->_compileRegex();
  12840. }
  12841. /**
  12842. * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
  12843. */
  12844. protected function _compileRegex()
  12845. {
  12846. $raw = str_replace(' ', '', $this->dtd_regex);
  12847. if ($raw[0] != '(') {
  12848. $raw = "($raw)";
  12849. }
  12850. $el = '[#a-zA-Z0-9_.-]+';
  12851. $reg = $raw;
  12852. // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
  12853. // DOING! Seriously: if there's problems, please report them.
  12854. // collect all elements into the $elements array
  12855. preg_match_all("/$el/", $reg, $matches);
  12856. foreach ($matches[0] as $match) {
  12857. $this->elements[$match] = true;
  12858. }
  12859. // setup all elements as parentheticals with leading commas
  12860. $reg = preg_replace("/$el/", '(,\\0)', $reg);
  12861. // remove commas when they were not solicited
  12862. $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
  12863. // remove all non-paranthetical commas: they are handled by first regex
  12864. $reg = preg_replace("/,\(/", '(', $reg);
  12865. $this->_pcre_regex = $reg;
  12866. }
  12867. /**
  12868. * @param HTMLPurifier_Node[] $children
  12869. * @param HTMLPurifier_Config $config
  12870. * @param HTMLPurifier_Context $context
  12871. * @return bool
  12872. */
  12873. public function validateChildren($children, $config, $context)
  12874. {
  12875. $list_of_children = '';
  12876. $nesting = 0; // depth into the nest
  12877. foreach ($children as $node) {
  12878. if (!empty($node->is_whitespace)) {
  12879. continue;
  12880. }
  12881. $list_of_children .= $node->name . ',';
  12882. }
  12883. // add leading comma to deal with stray comma declarations
  12884. $list_of_children = ',' . rtrim($list_of_children, ',');
  12885. $okay =
  12886. preg_match(
  12887. '/^,?' . $this->_pcre_regex . '$/',
  12888. $list_of_children
  12889. );
  12890. return (bool)$okay;
  12891. }
  12892. }
  12893. /**
  12894. * Definition that disallows all elements.
  12895. * @warning validateChildren() in this class is actually never called, because
  12896. * empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
  12897. * before child definitions are parsed in earnest by
  12898. * HTMLPurifier_Strategy_FixNesting.
  12899. */
  12900. class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
  12901. {
  12902. /**
  12903. * @type bool
  12904. */
  12905. public $allow_empty = true;
  12906. /**
  12907. * @type string
  12908. */
  12909. public $type = 'empty';
  12910. public function __construct()
  12911. {
  12912. }
  12913. /**
  12914. * @param HTMLPurifier_Node[] $children
  12915. * @param HTMLPurifier_Config $config
  12916. * @param HTMLPurifier_Context $context
  12917. * @return array
  12918. */
  12919. public function validateChildren($children, $config, $context)
  12920. {
  12921. return array();
  12922. }
  12923. }
  12924. /**
  12925. * Definition for list containers ul and ol.
  12926. *
  12927. * What does this do? The big thing is to handle ol/ul at the top
  12928. * level of list nodes, which should be handled specially by /folding/
  12929. * them into the previous list node. We generally shouldn't ever
  12930. * see other disallowed elements, because the autoclose behavior
  12931. * in MakeWellFormed handles it.
  12932. */
  12933. class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
  12934. {
  12935. /**
  12936. * @type string
  12937. */
  12938. public $type = 'list';
  12939. /**
  12940. * @type array
  12941. */
  12942. // lying a little bit, so that we can handle ul and ol ourselves
  12943. // XXX: This whole business with 'wrap' is all a bit unsatisfactory
  12944. public $elements = array('li' => true, 'ul' => true, 'ol' => true);
  12945. /**
  12946. * @param array $children
  12947. * @param HTMLPurifier_Config $config
  12948. * @param HTMLPurifier_Context $context
  12949. * @return array
  12950. */
  12951. public function validateChildren($children, $config, $context)
  12952. {
  12953. // Flag for subclasses
  12954. $this->whitespace = false;
  12955. // if there are no tokens, delete parent node
  12956. if (empty($children)) {
  12957. return false;
  12958. }
  12959. // if li is not allowed, delete parent node
  12960. if (!isset($config->getHTMLDefinition()->info['li'])) {
  12961. trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
  12962. return false;
  12963. }
  12964. // the new set of children
  12965. $result = array();
  12966. // a little sanity check to make sure it's not ALL whitespace
  12967. $all_whitespace = true;
  12968. $current_li = null;
  12969. foreach ($children as $node) {
  12970. if (!empty($node->is_whitespace)) {
  12971. $result[] = $node;
  12972. continue;
  12973. }
  12974. $all_whitespace = false; // phew, we're not talking about whitespace
  12975. if ($node->name === 'li') {
  12976. // good
  12977. $current_li = $node;
  12978. $result[] = $node;
  12979. } else {
  12980. // we want to tuck this into the previous li
  12981. // Invariant: we expect the node to be ol/ul
  12982. // ToDo: Make this more robust in the case of not ol/ul
  12983. // by distinguishing between existing li and li created
  12984. // to handle non-list elements; non-list elements should
  12985. // not be appended to an existing li; only li created
  12986. // for non-list. This distinction is not currently made.
  12987. if ($current_li === null) {
  12988. $current_li = new HTMLPurifier_Node_Element('li');
  12989. $result[] = $current_li;
  12990. }
  12991. $current_li->children[] = $node;
  12992. $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
  12993. }
  12994. }
  12995. if (empty($result)) {
  12996. return false;
  12997. }
  12998. if ($all_whitespace) {
  12999. return false;
  13000. }
  13001. return $result;
  13002. }
  13003. }
  13004. /**
  13005. * Definition that allows a set of elements, but disallows empty children.
  13006. */
  13007. class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
  13008. {
  13009. /**
  13010. * Lookup table of allowed elements.
  13011. * @type array
  13012. */
  13013. public $elements = array();
  13014. /**
  13015. * Whether or not the last passed node was all whitespace.
  13016. * @type bool
  13017. */
  13018. protected $whitespace = false;
  13019. /**
  13020. * @param array|string $elements List of allowed element names (lowercase).
  13021. */
  13022. public function __construct($elements)
  13023. {
  13024. if (is_string($elements)) {
  13025. $elements = str_replace(' ', '', $elements);
  13026. $elements = explode('|', $elements);
  13027. }
  13028. $keys = array_keys($elements);
  13029. if ($keys == array_keys($keys)) {
  13030. $elements = array_flip($elements);
  13031. foreach ($elements as $i => $x) {
  13032. $elements[$i] = true;
  13033. if (empty($i)) {
  13034. unset($elements[$i]);
  13035. } // remove blank
  13036. }
  13037. }
  13038. $this->elements = $elements;
  13039. }
  13040. /**
  13041. * @type bool
  13042. */
  13043. public $allow_empty = false;
  13044. /**
  13045. * @type string
  13046. */
  13047. public $type = 'required';
  13048. /**
  13049. * @param array $children
  13050. * @param HTMLPurifier_Config $config
  13051. * @param HTMLPurifier_Context $context
  13052. * @return array
  13053. */
  13054. public function validateChildren($children, $config, $context)
  13055. {
  13056. // Flag for subclasses
  13057. $this->whitespace = false;
  13058. // if there are no tokens, delete parent node
  13059. if (empty($children)) {
  13060. return false;
  13061. }
  13062. // the new set of children
  13063. $result = array();
  13064. // whether or not parsed character data is allowed
  13065. // this controls whether or not we silently drop a tag
  13066. // or generate escaped HTML from it
  13067. $pcdata_allowed = isset($this->elements['#PCDATA']);
  13068. // a little sanity check to make sure it's not ALL whitespace
  13069. $all_whitespace = true;
  13070. $stack = array_reverse($children);
  13071. while (!empty($stack)) {
  13072. $node = array_pop($stack);
  13073. if (!empty($node->is_whitespace)) {
  13074. $result[] = $node;
  13075. continue;
  13076. }
  13077. $all_whitespace = false; // phew, we're not talking about whitespace
  13078. if (!isset($this->elements[$node->name])) {
  13079. // special case text
  13080. // XXX One of these ought to be redundant or something
  13081. if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
  13082. $result[] = $node;
  13083. continue;
  13084. }
  13085. // spill the child contents in
  13086. // ToDo: Make configurable
  13087. if ($node instanceof HTMLPurifier_Node_Element) {
  13088. for ($i = count($node->children) - 1; $i >= 0; $i--) {
  13089. $stack[] = $node->children[$i];
  13090. }
  13091. continue;
  13092. }
  13093. continue;
  13094. }
  13095. $result[] = $node;
  13096. }
  13097. if (empty($result)) {
  13098. return false;
  13099. }
  13100. if ($all_whitespace) {
  13101. $this->whitespace = true;
  13102. return false;
  13103. }
  13104. return $result;
  13105. }
  13106. }
  13107. /**
  13108. * Definition that allows a set of elements, and allows no children.
  13109. * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
  13110. * really, one shouldn't inherit from the other. Only altered behavior
  13111. * is to overload a returned false with an array. Thus, it will never
  13112. * return false.
  13113. */
  13114. class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
  13115. {
  13116. /**
  13117. * @type bool
  13118. */
  13119. public $allow_empty = true;
  13120. /**
  13121. * @type string
  13122. */
  13123. public $type = 'optional';
  13124. /**
  13125. * @param array $children
  13126. * @param HTMLPurifier_Config $config
  13127. * @param HTMLPurifier_Context $context
  13128. * @return array
  13129. */
  13130. public function validateChildren($children, $config, $context)
  13131. {
  13132. $result = parent::validateChildren($children, $config, $context);
  13133. // we assume that $children is not modified
  13134. if ($result === false) {
  13135. if (empty($children)) {
  13136. return true;
  13137. } elseif ($this->whitespace) {
  13138. return $children;
  13139. } else {
  13140. return array();
  13141. }
  13142. }
  13143. return $result;
  13144. }
  13145. }
  13146. /**
  13147. * Takes the contents of blockquote when in strict and reformats for validation.
  13148. */
  13149. class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
  13150. {
  13151. /**
  13152. * @type array
  13153. */
  13154. protected $real_elements;
  13155. /**
  13156. * @type array
  13157. */
  13158. protected $fake_elements;
  13159. /**
  13160. * @type bool
  13161. */
  13162. public $allow_empty = true;
  13163. /**
  13164. * @type string
  13165. */
  13166. public $type = 'strictblockquote';
  13167. /**
  13168. * @type bool
  13169. */
  13170. protected $init = false;
  13171. /**
  13172. * @param HTMLPurifier_Config $config
  13173. * @return array
  13174. * @note We don't want MakeWellFormed to auto-close inline elements since
  13175. * they might be allowed.
  13176. */
  13177. public function getAllowedElements($config)
  13178. {
  13179. $this->init($config);
  13180. return $this->fake_elements;
  13181. }
  13182. /**
  13183. * @param array $children
  13184. * @param HTMLPurifier_Config $config
  13185. * @param HTMLPurifier_Context $context
  13186. * @return array
  13187. */
  13188. public function validateChildren($children, $config, $context)
  13189. {
  13190. $this->init($config);
  13191. // trick the parent class into thinking it allows more
  13192. $this->elements = $this->fake_elements;
  13193. $result = parent::validateChildren($children, $config, $context);
  13194. $this->elements = $this->real_elements;
  13195. if ($result === false) {
  13196. return array();
  13197. }
  13198. if ($result === true) {
  13199. $result = $children;
  13200. }
  13201. $def = $config->getHTMLDefinition();
  13202. $block_wrap_name = $def->info_block_wrapper;
  13203. $block_wrap = false;
  13204. $ret = array();
  13205. foreach ($result as $node) {
  13206. if ($block_wrap === false) {
  13207. if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
  13208. ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
  13209. $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
  13210. $ret[] = $block_wrap;
  13211. }
  13212. } else {
  13213. if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
  13214. $block_wrap = false;
  13215. }
  13216. }
  13217. if ($block_wrap) {
  13218. $block_wrap->children[] = $node;
  13219. } else {
  13220. $ret[] = $node;
  13221. }
  13222. }
  13223. return $ret;
  13224. }
  13225. /**
  13226. * @param HTMLPurifier_Config $config
  13227. */
  13228. private function init($config)
  13229. {
  13230. if (!$this->init) {
  13231. $def = $config->getHTMLDefinition();
  13232. // allow all inline elements
  13233. $this->real_elements = $this->elements;
  13234. $this->fake_elements = $def->info_content_sets['Flow'];
  13235. $this->fake_elements['#PCDATA'] = true;
  13236. $this->init = true;
  13237. }
  13238. }
  13239. }
  13240. /**
  13241. * Definition for tables. The general idea is to extract out all of the
  13242. * essential bits, and then reconstruct it later.
  13243. *
  13244. * This is a bit confusing, because the DTDs and the W3C
  13245. * validators seem to disagree on the appropriate definition. The
  13246. * DTD claims:
  13247. *
  13248. * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
  13249. *
  13250. * But actually, the HTML4 spec then has this to say:
  13251. *
  13252. * The TBODY start tag is always required except when the table
  13253. * contains only one table body and no table head or foot sections.
  13254. * The TBODY end tag may always be safely omitted.
  13255. *
  13256. * So the DTD is kind of wrong. The validator is, unfortunately, kind
  13257. * of on crack.
  13258. *
  13259. * The definition changed again in XHTML1.1; and in my opinion, this
  13260. * formulation makes the most sense.
  13261. *
  13262. * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
  13263. *
  13264. * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
  13265. * If we encounter a thead, tfoot or tbody, we are placed in the former
  13266. * mode, and we *must* wrap any stray tr segments with a tbody. But if
  13267. * we don't run into any of them, just have tr tags is OK.
  13268. */
  13269. class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
  13270. {
  13271. /**
  13272. * @type bool
  13273. */
  13274. public $allow_empty = false;
  13275. /**
  13276. * @type string
  13277. */
  13278. public $type = 'table';
  13279. /**
  13280. * @type array
  13281. */
  13282. public $elements = array(
  13283. 'tr' => true,
  13284. 'tbody' => true,
  13285. 'thead' => true,
  13286. 'tfoot' => true,
  13287. 'caption' => true,
  13288. 'colgroup' => true,
  13289. 'col' => true
  13290. );
  13291. public function __construct()
  13292. {
  13293. }
  13294. /**
  13295. * @param array $children
  13296. * @param HTMLPurifier_Config $config
  13297. * @param HTMLPurifier_Context $context
  13298. * @return array
  13299. */
  13300. public function validateChildren($children, $config, $context)
  13301. {
  13302. if (empty($children)) {
  13303. return false;
  13304. }
  13305. // only one of these elements is allowed in a table
  13306. $caption = false;
  13307. $thead = false;
  13308. $tfoot = false;
  13309. // whitespace
  13310. $initial_ws = array();
  13311. $after_caption_ws = array();
  13312. $after_thead_ws = array();
  13313. $after_tfoot_ws = array();
  13314. // as many of these as you want
  13315. $cols = array();
  13316. $content = array();
  13317. $tbody_mode = false; // if true, then we need to wrap any stray
  13318. // <tr>s with a <tbody>.
  13319. $ws_accum =& $initial_ws;
  13320. foreach ($children as $node) {
  13321. if ($node instanceof HTMLPurifier_Node_Comment) {
  13322. $ws_accum[] = $node;
  13323. continue;
  13324. }
  13325. switch ($node->name) {
  13326. case 'tbody':
  13327. $tbody_mode = true;
  13328. // fall through
  13329. case 'tr':
  13330. $content[] = $node;
  13331. $ws_accum =& $content;
  13332. break;
  13333. case 'caption':
  13334. // there can only be one caption!
  13335. if ($caption !== false) break;
  13336. $caption = $node;
  13337. $ws_accum =& $after_caption_ws;
  13338. break;
  13339. case 'thead':
  13340. $tbody_mode = true;
  13341. // XXX This breaks rendering properties with
  13342. // Firefox, which never floats a <thead> to
  13343. // the top. Ever. (Our scheme will float the
  13344. // first <thead> to the top.) So maybe
  13345. // <thead>s that are not first should be
  13346. // turned into <tbody>? Very tricky, indeed.
  13347. if ($thead === false) {
  13348. $thead = $node;
  13349. $ws_accum =& $after_thead_ws;
  13350. } else {
  13351. // Oops, there's a second one! What
  13352. // should we do? Current behavior is to
  13353. // transmutate the first and last entries into
  13354. // tbody tags, and then put into content.
  13355. // Maybe a better idea is to *attach
  13356. // it* to the existing thead or tfoot?
  13357. // We don't do this, because Firefox
  13358. // doesn't float an extra tfoot to the
  13359. // bottom like it does for the first one.
  13360. $node->name = 'tbody';
  13361. $content[] = $node;
  13362. $ws_accum =& $content;
  13363. }
  13364. break;
  13365. case 'tfoot':
  13366. // see above for some aveats
  13367. $tbody_mode = true;
  13368. if ($tfoot === false) {
  13369. $tfoot = $node;
  13370. $ws_accum =& $after_tfoot_ws;
  13371. } else {
  13372. $node->name = 'tbody';
  13373. $content[] = $node;
  13374. $ws_accum =& $content;
  13375. }
  13376. break;
  13377. case 'colgroup':
  13378. case 'col':
  13379. $cols[] = $node;
  13380. $ws_accum =& $cols;
  13381. break;
  13382. case '#PCDATA':
  13383. // How is whitespace handled? We treat is as sticky to
  13384. // the *end* of the previous element. So all of the
  13385. // nonsense we have worked on is to keep things
  13386. // together.
  13387. if (!empty($node->is_whitespace)) {
  13388. $ws_accum[] = $node;
  13389. }
  13390. break;
  13391. }
  13392. }
  13393. if (empty($content)) {
  13394. return false;
  13395. }
  13396. $ret = $initial_ws;
  13397. if ($caption !== false) {
  13398. $ret[] = $caption;
  13399. $ret = array_merge($ret, $after_caption_ws);
  13400. }
  13401. if ($cols !== false) {
  13402. $ret = array_merge($ret, $cols);
  13403. }
  13404. if ($thead !== false) {
  13405. $ret[] = $thead;
  13406. $ret = array_merge($ret, $after_thead_ws);
  13407. }
  13408. if ($tfoot !== false) {
  13409. $ret[] = $tfoot;
  13410. $ret = array_merge($ret, $after_tfoot_ws);
  13411. }
  13412. if ($tbody_mode) {
  13413. // we have to shuffle tr into tbody
  13414. $current_tr_tbody = null;
  13415. foreach($content as $node) {
  13416. switch ($node->name) {
  13417. case 'tbody':
  13418. $current_tr_tbody = null;
  13419. $ret[] = $node;
  13420. break;
  13421. case 'tr':
  13422. if ($current_tr_tbody === null) {
  13423. $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
  13424. $ret[] = $current_tr_tbody;
  13425. }
  13426. $current_tr_tbody->children[] = $node;
  13427. break;
  13428. case '#PCDATA':
  13429. //assert($node->is_whitespace);
  13430. if ($current_tr_tbody === null) {
  13431. $ret[] = $node;
  13432. } else {
  13433. $current_tr_tbody->children[] = $node;
  13434. }
  13435. break;
  13436. }
  13437. }
  13438. } else {
  13439. $ret = array_merge($ret, $content);
  13440. }
  13441. return $ret;
  13442. }
  13443. }
  13444. class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
  13445. {
  13446. /**
  13447. * Cache object we are decorating
  13448. * @type HTMLPurifier_DefinitionCache
  13449. */
  13450. public $cache;
  13451. /**
  13452. * The name of the decorator
  13453. * @var string
  13454. */
  13455. public $name;
  13456. public function __construct()
  13457. {
  13458. }
  13459. /**
  13460. * Lazy decorator function
  13461. * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
  13462. * @return HTMLPurifier_DefinitionCache_Decorator
  13463. */
  13464. public function decorate(&$cache)
  13465. {
  13466. $decorator = $this->copy();
  13467. // reference is necessary for mocks in PHP 4
  13468. $decorator->cache =& $cache;
  13469. $decorator->type = $cache->type;
  13470. return $decorator;
  13471. }
  13472. /**
  13473. * Cross-compatible clone substitute
  13474. * @return HTMLPurifier_DefinitionCache_Decorator
  13475. */
  13476. public function copy()
  13477. {
  13478. return new HTMLPurifier_DefinitionCache_Decorator();
  13479. }
  13480. /**
  13481. * @param HTMLPurifier_Definition $def
  13482. * @param HTMLPurifier_Config $config
  13483. * @return mixed
  13484. */
  13485. public function add($def, $config)
  13486. {
  13487. return $this->cache->add($def, $config);
  13488. }
  13489. /**
  13490. * @param HTMLPurifier_Definition $def
  13491. * @param HTMLPurifier_Config $config
  13492. * @return mixed
  13493. */
  13494. public function set($def, $config)
  13495. {
  13496. return $this->cache->set($def, $config);
  13497. }
  13498. /**
  13499. * @param HTMLPurifier_Definition $def
  13500. * @param HTMLPurifier_Config $config
  13501. * @return mixed
  13502. */
  13503. public function replace($def, $config)
  13504. {
  13505. return $this->cache->replace($def, $config);
  13506. }
  13507. /**
  13508. * @param HTMLPurifier_Config $config
  13509. * @return mixed
  13510. */
  13511. public function get($config)
  13512. {
  13513. return $this->cache->get($config);
  13514. }
  13515. /**
  13516. * @param HTMLPurifier_Config $config
  13517. * @return mixed
  13518. */
  13519. public function remove($config)
  13520. {
  13521. return $this->cache->remove($config);
  13522. }
  13523. /**
  13524. * @param HTMLPurifier_Config $config
  13525. * @return mixed
  13526. */
  13527. public function flush($config)
  13528. {
  13529. return $this->cache->flush($config);
  13530. }
  13531. /**
  13532. * @param HTMLPurifier_Config $config
  13533. * @return mixed
  13534. */
  13535. public function cleanup($config)
  13536. {
  13537. return $this->cache->cleanup($config);
  13538. }
  13539. }
  13540. /**
  13541. * Null cache object to use when no caching is on.
  13542. */
  13543. class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
  13544. {
  13545. /**
  13546. * @param HTMLPurifier_Definition $def
  13547. * @param HTMLPurifier_Config $config
  13548. * @return bool
  13549. */
  13550. public function add($def, $config)
  13551. {
  13552. return false;
  13553. }
  13554. /**
  13555. * @param HTMLPurifier_Definition $def
  13556. * @param HTMLPurifier_Config $config
  13557. * @return bool
  13558. */
  13559. public function set($def, $config)
  13560. {
  13561. return false;
  13562. }
  13563. /**
  13564. * @param HTMLPurifier_Definition $def
  13565. * @param HTMLPurifier_Config $config
  13566. * @return bool
  13567. */
  13568. public function replace($def, $config)
  13569. {
  13570. return false;
  13571. }
  13572. /**
  13573. * @param HTMLPurifier_Config $config
  13574. * @return bool
  13575. */
  13576. public function remove($config)
  13577. {
  13578. return false;
  13579. }
  13580. /**
  13581. * @param HTMLPurifier_Config $config
  13582. * @return bool
  13583. */
  13584. public function get($config)
  13585. {
  13586. return false;
  13587. }
  13588. /**
  13589. * @param HTMLPurifier_Config $config
  13590. * @return bool
  13591. */
  13592. public function flush($config)
  13593. {
  13594. return false;
  13595. }
  13596. /**
  13597. * @param HTMLPurifier_Config $config
  13598. * @return bool
  13599. */
  13600. public function cleanup($config)
  13601. {
  13602. return false;
  13603. }
  13604. }
  13605. class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
  13606. {
  13607. /**
  13608. * @param HTMLPurifier_Definition $def
  13609. * @param HTMLPurifier_Config $config
  13610. * @return int|bool
  13611. */
  13612. public function add($def, $config)
  13613. {
  13614. if (!$this->checkDefType($def)) {
  13615. return;
  13616. }
  13617. $file = $this->generateFilePath($config);
  13618. if (file_exists($file)) {
  13619. return false;
  13620. }
  13621. if (!$this->_prepareDir($config)) {
  13622. return false;
  13623. }
  13624. return $this->_write($file, serialize($def), $config);
  13625. }
  13626. /**
  13627. * @param HTMLPurifier_Definition $def
  13628. * @param HTMLPurifier_Config $config
  13629. * @return int|bool
  13630. */
  13631. public function set($def, $config)
  13632. {
  13633. if (!$this->checkDefType($def)) {
  13634. return;
  13635. }
  13636. $file = $this->generateFilePath($config);
  13637. if (!$this->_prepareDir($config)) {
  13638. return false;
  13639. }
  13640. return $this->_write($file, serialize($def), $config);
  13641. }
  13642. /**
  13643. * @param HTMLPurifier_Definition $def
  13644. * @param HTMLPurifier_Config $config
  13645. * @return int|bool
  13646. */
  13647. public function replace($def, $config)
  13648. {
  13649. if (!$this->checkDefType($def)) {
  13650. return;
  13651. }
  13652. $file = $this->generateFilePath($config);
  13653. if (!file_exists($file)) {
  13654. return false;
  13655. }
  13656. if (!$this->_prepareDir($config)) {
  13657. return false;
  13658. }
  13659. return $this->_write($file, serialize($def), $config);
  13660. }
  13661. /**
  13662. * @param HTMLPurifier_Config $config
  13663. * @return bool|HTMLPurifier_Config
  13664. */
  13665. public function get($config)
  13666. {
  13667. $file = $this->generateFilePath($config);
  13668. if (!file_exists($file)) {
  13669. return false;
  13670. }
  13671. return unserialize(file_get_contents($file));
  13672. }
  13673. /**
  13674. * @param HTMLPurifier_Config $config
  13675. * @return bool
  13676. */
  13677. public function remove($config)
  13678. {
  13679. $file = $this->generateFilePath($config);
  13680. if (!file_exists($file)) {
  13681. return false;
  13682. }
  13683. return unlink($file);
  13684. }
  13685. /**
  13686. * @param HTMLPurifier_Config $config
  13687. * @return bool
  13688. */
  13689. public function flush($config)
  13690. {
  13691. if (!$this->_prepareDir($config)) {
  13692. return false;
  13693. }
  13694. $dir = $this->generateDirectoryPath($config);
  13695. $dh = opendir($dir);
  13696. // Apparently, on some versions of PHP, readdir will return
  13697. // an empty string if you pass an invalid argument to readdir.
  13698. // So you need this test. See #49.
  13699. if (false === $dh) {
  13700. return false;
  13701. }
  13702. while (false !== ($filename = readdir($dh))) {
  13703. if (empty($filename)) {
  13704. continue;
  13705. }
  13706. if ($filename[0] === '.') {
  13707. continue;
  13708. }
  13709. unlink($dir . '/' . $filename);
  13710. }
  13711. closedir($dh);
  13712. return true;
  13713. }
  13714. /**
  13715. * @param HTMLPurifier_Config $config
  13716. * @return bool
  13717. */
  13718. public function cleanup($config)
  13719. {
  13720. if (!$this->_prepareDir($config)) {
  13721. return false;
  13722. }
  13723. $dir = $this->generateDirectoryPath($config);
  13724. $dh = opendir($dir);
  13725. // See #49 (and above).
  13726. if (false === $dh) {
  13727. return false;
  13728. }
  13729. while (false !== ($filename = readdir($dh))) {
  13730. if (empty($filename)) {
  13731. continue;
  13732. }
  13733. if ($filename[0] === '.') {
  13734. continue;
  13735. }
  13736. $key = substr($filename, 0, strlen($filename) - 4);
  13737. if ($this->isOld($key, $config)) {
  13738. unlink($dir . '/' . $filename);
  13739. }
  13740. }
  13741. closedir($dh);
  13742. return true;
  13743. }
  13744. /**
  13745. * Generates the file path to the serial file corresponding to
  13746. * the configuration and definition name
  13747. * @param HTMLPurifier_Config $config
  13748. * @return string
  13749. * @todo Make protected
  13750. */
  13751. public function generateFilePath($config)
  13752. {
  13753. $key = $this->generateKey($config);
  13754. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  13755. }
  13756. /**
  13757. * Generates the path to the directory contain this cache's serial files
  13758. * @param HTMLPurifier_Config $config
  13759. * @return string
  13760. * @note No trailing slash
  13761. * @todo Make protected
  13762. */
  13763. public function generateDirectoryPath($config)
  13764. {
  13765. $base = $this->generateBaseDirectoryPath($config);
  13766. return $base . '/' . $this->type;
  13767. }
  13768. /**
  13769. * Generates path to base directory that contains all definition type
  13770. * serials
  13771. * @param HTMLPurifier_Config $config
  13772. * @return mixed|string
  13773. * @todo Make protected
  13774. */
  13775. public function generateBaseDirectoryPath($config)
  13776. {
  13777. $base = $config->get('Cache.SerializerPath');
  13778. $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
  13779. return $base;
  13780. }
  13781. /**
  13782. * Convenience wrapper function for file_put_contents
  13783. * @param string $file File name to write to
  13784. * @param string $data Data to write into file
  13785. * @param HTMLPurifier_Config $config
  13786. * @return int|bool Number of bytes written if success, or false if failure.
  13787. */
  13788. private function _write($file, $data, $config)
  13789. {
  13790. $result = file_put_contents($file, $data);
  13791. if ($result !== false) {
  13792. // set permissions of the new file (no execute)
  13793. $chmod = $config->get('Cache.SerializerPermissions');
  13794. if ($chmod !== null) {
  13795. chmod($file, $chmod & 0666);
  13796. }
  13797. }
  13798. return $result;
  13799. }
  13800. /**
  13801. * Prepares the directory that this type stores the serials in
  13802. * @param HTMLPurifier_Config $config
  13803. * @return bool True if successful
  13804. */
  13805. private function _prepareDir($config)
  13806. {
  13807. $directory = $this->generateDirectoryPath($config);
  13808. $chmod = $config->get('Cache.SerializerPermissions');
  13809. if ($chmod === null) {
  13810. if (!@mkdir($directory) && !is_dir($directory)) {
  13811. trigger_error(
  13812. 'Could not create directory ' . $directory . '',
  13813. E_USER_WARNING
  13814. );
  13815. return false;
  13816. }
  13817. return true;
  13818. }
  13819. if (!is_dir($directory)) {
  13820. $base = $this->generateBaseDirectoryPath($config);
  13821. if (!is_dir($base)) {
  13822. trigger_error(
  13823. 'Base directory ' . $base . ' does not exist,
  13824. please create or change using %Cache.SerializerPath',
  13825. E_USER_WARNING
  13826. );
  13827. return false;
  13828. } elseif (!$this->_testPermissions($base, $chmod)) {
  13829. return false;
  13830. }
  13831. if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
  13832. trigger_error(
  13833. 'Could not create directory ' . $directory . '',
  13834. E_USER_WARNING
  13835. );
  13836. return false;
  13837. }
  13838. if (!$this->_testPermissions($directory, $chmod)) {
  13839. return false;
  13840. }
  13841. } elseif (!$this->_testPermissions($directory, $chmod)) {
  13842. return false;
  13843. }
  13844. return true;
  13845. }
  13846. /**
  13847. * Tests permissions on a directory and throws out friendly
  13848. * error messages and attempts to chmod it itself if possible
  13849. * @param string $dir Directory path
  13850. * @param int $chmod Permissions
  13851. * @return bool True if directory is writable
  13852. */
  13853. private function _testPermissions($dir, $chmod)
  13854. {
  13855. // early abort, if it is writable, everything is hunky-dory
  13856. if (is_writable($dir)) {
  13857. return true;
  13858. }
  13859. if (!is_dir($dir)) {
  13860. // generally, you'll want to handle this beforehand
  13861. // so a more specific error message can be given
  13862. trigger_error(
  13863. 'Directory ' . $dir . ' does not exist',
  13864. E_USER_WARNING
  13865. );
  13866. return false;
  13867. }
  13868. if (function_exists('posix_getuid') && $chmod !== null) {
  13869. // POSIX system, we can give more specific advice
  13870. if (fileowner($dir) === posix_getuid()) {
  13871. // we can chmod it ourselves
  13872. $chmod = $chmod | 0700;
  13873. if (chmod($dir, $chmod)) {
  13874. return true;
  13875. }
  13876. } elseif (filegroup($dir) === posix_getgid()) {
  13877. $chmod = $chmod | 0070;
  13878. } else {
  13879. // PHP's probably running as nobody, so we'll
  13880. // need to give global permissions
  13881. $chmod = $chmod | 0777;
  13882. }
  13883. trigger_error(
  13884. 'Directory ' . $dir . ' not writable, ' .
  13885. 'please chmod to ' . decoct($chmod),
  13886. E_USER_WARNING
  13887. );
  13888. } else {
  13889. // generic error message
  13890. trigger_error(
  13891. 'Directory ' . $dir . ' not writable, ' .
  13892. 'please alter file permissions',
  13893. E_USER_WARNING
  13894. );
  13895. }
  13896. return false;
  13897. }
  13898. }
  13899. /**
  13900. * Definition cache decorator class that cleans up the cache
  13901. * whenever there is a cache miss.
  13902. */
  13903. class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
  13904. {
  13905. /**
  13906. * @type string
  13907. */
  13908. public $name = 'Cleanup';
  13909. /**
  13910. * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
  13911. */
  13912. public function copy()
  13913. {
  13914. return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
  13915. }
  13916. /**
  13917. * @param HTMLPurifier_Definition $def
  13918. * @param HTMLPurifier_Config $config
  13919. * @return mixed
  13920. */
  13921. public function add($def, $config)
  13922. {
  13923. $status = parent::add($def, $config);
  13924. if (!$status) {
  13925. parent::cleanup($config);
  13926. }
  13927. return $status;
  13928. }
  13929. /**
  13930. * @param HTMLPurifier_Definition $def
  13931. * @param HTMLPurifier_Config $config
  13932. * @return mixed
  13933. */
  13934. public function set($def, $config)
  13935. {
  13936. $status = parent::set($def, $config);
  13937. if (!$status) {
  13938. parent::cleanup($config);
  13939. }
  13940. return $status;
  13941. }
  13942. /**
  13943. * @param HTMLPurifier_Definition $def
  13944. * @param HTMLPurifier_Config $config
  13945. * @return mixed
  13946. */
  13947. public function replace($def, $config)
  13948. {
  13949. $status = parent::replace($def, $config);
  13950. if (!$status) {
  13951. parent::cleanup($config);
  13952. }
  13953. return $status;
  13954. }
  13955. /**
  13956. * @param HTMLPurifier_Config $config
  13957. * @return mixed
  13958. */
  13959. public function get($config)
  13960. {
  13961. $ret = parent::get($config);
  13962. if (!$ret) {
  13963. parent::cleanup($config);
  13964. }
  13965. return $ret;
  13966. }
  13967. }
  13968. /**
  13969. * Definition cache decorator class that saves all cache retrievals
  13970. * to PHP's memory; good for unit tests or circumstances where
  13971. * there are lots of configuration objects floating around.
  13972. */
  13973. class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
  13974. {
  13975. /**
  13976. * @type array
  13977. */
  13978. protected $definitions;
  13979. /**
  13980. * @type string
  13981. */
  13982. public $name = 'Memory';
  13983. /**
  13984. * @return HTMLPurifier_DefinitionCache_Decorator_Memory
  13985. */
  13986. public function copy()
  13987. {
  13988. return new HTMLPurifier_DefinitionCache_Decorator_Memory();
  13989. }
  13990. /**
  13991. * @param HTMLPurifier_Definition $def
  13992. * @param HTMLPurifier_Config $config
  13993. * @return mixed
  13994. */
  13995. public function add($def, $config)
  13996. {
  13997. $status = parent::add($def, $config);
  13998. if ($status) {
  13999. $this->definitions[$this->generateKey($config)] = $def;
  14000. }
  14001. return $status;
  14002. }
  14003. /**
  14004. * @param HTMLPurifier_Definition $def
  14005. * @param HTMLPurifier_Config $config
  14006. * @return mixed
  14007. */
  14008. public function set($def, $config)
  14009. {
  14010. $status = parent::set($def, $config);
  14011. if ($status) {
  14012. $this->definitions[$this->generateKey($config)] = $def;
  14013. }
  14014. return $status;
  14015. }
  14016. /**
  14017. * @param HTMLPurifier_Definition $def
  14018. * @param HTMLPurifier_Config $config
  14019. * @return mixed
  14020. */
  14021. public function replace($def, $config)
  14022. {
  14023. $status = parent::replace($def, $config);
  14024. if ($status) {
  14025. $this->definitions[$this->generateKey($config)] = $def;
  14026. }
  14027. return $status;
  14028. }
  14029. /**
  14030. * @param HTMLPurifier_Config $config
  14031. * @return mixed
  14032. */
  14033. public function get($config)
  14034. {
  14035. $key = $this->generateKey($config);
  14036. if (isset($this->definitions[$key])) {
  14037. return $this->definitions[$key];
  14038. }
  14039. $this->definitions[$key] = parent::get($config);
  14040. return $this->definitions[$key];
  14041. }
  14042. }
  14043. /**
  14044. * XHTML 1.1 Bi-directional Text Module, defines elements that
  14045. * declare directionality of content. Text Extension Module.
  14046. */
  14047. class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
  14048. {
  14049. /**
  14050. * @type string
  14051. */
  14052. public $name = 'Bdo';
  14053. /**
  14054. * @type array
  14055. */
  14056. public $attr_collections = array(
  14057. 'I18N' => array('dir' => false)
  14058. );
  14059. /**
  14060. * @param HTMLPurifier_Config $config
  14061. */
  14062. public function setup($config)
  14063. {
  14064. $bdo = $this->addElement(
  14065. 'bdo',
  14066. 'Inline',
  14067. 'Inline',
  14068. array('Core', 'Lang'),
  14069. array(
  14070. 'dir' => 'Enum#ltr,rtl', // required
  14071. // The Abstract Module specification has the attribute
  14072. // inclusions wrong for bdo: bdo allows Lang
  14073. )
  14074. );
  14075. $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
  14076. $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
  14077. }
  14078. }
  14079. class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
  14080. {
  14081. /**
  14082. * @type string
  14083. */
  14084. public $name = 'CommonAttributes';
  14085. /**
  14086. * @type array
  14087. */
  14088. public $attr_collections = array(
  14089. 'Core' => array(
  14090. 0 => array('Style'),
  14091. // 'xml:space' => false,
  14092. 'class' => 'Class',
  14093. 'id' => 'ID',
  14094. 'title' => 'CDATA',
  14095. ),
  14096. 'Lang' => array(),
  14097. 'I18N' => array(
  14098. 0 => array('Lang'), // proprietary, for xml:lang/lang
  14099. ),
  14100. 'Common' => array(
  14101. 0 => array('Core', 'I18N')
  14102. )
  14103. );
  14104. }
  14105. /**
  14106. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  14107. * Module.
  14108. */
  14109. class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
  14110. {
  14111. /**
  14112. * @type string
  14113. */
  14114. public $name = 'Edit';
  14115. /**
  14116. * @param HTMLPurifier_Config $config
  14117. */
  14118. public function setup($config)
  14119. {
  14120. $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
  14121. $attr = array(
  14122. 'cite' => 'URI',
  14123. // 'datetime' => 'Datetime', // not implemented
  14124. );
  14125. $this->addElement('del', 'Inline', $contents, 'Common', $attr);
  14126. $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
  14127. }
  14128. // HTML 4.01 specifies that ins/del must not contain block
  14129. // elements when used in an inline context, chameleon is
  14130. // a complicated workaround to acheive this effect
  14131. // Inline context ! Block context (exclamation mark is
  14132. // separator, see getChildDef for parsing)
  14133. /**
  14134. * @type bool
  14135. */
  14136. public $defines_child_def = true;
  14137. /**
  14138. * @param HTMLPurifier_ElementDef $def
  14139. * @return HTMLPurifier_ChildDef_Chameleon
  14140. */
  14141. public function getChildDef($def)
  14142. {
  14143. if ($def->content_model_type != 'chameleon') {
  14144. return false;
  14145. }
  14146. $value = explode('!', $def->content_model);
  14147. return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
  14148. }
  14149. }
  14150. /**
  14151. * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
  14152. */
  14153. class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
  14154. {
  14155. /**
  14156. * @type string
  14157. */
  14158. public $name = 'Forms';
  14159. /**
  14160. * @type bool
  14161. */
  14162. public $safe = false;
  14163. /**
  14164. * @type array
  14165. */
  14166. public $content_sets = array(
  14167. 'Block' => 'Form',
  14168. 'Inline' => 'Formctrl',
  14169. );
  14170. /**
  14171. * @param HTMLPurifier_Config $config
  14172. */
  14173. public function setup($config)
  14174. {
  14175. $form = $this->addElement(
  14176. 'form',
  14177. 'Form',
  14178. 'Required: Heading | List | Block | fieldset',
  14179. 'Common',
  14180. array(
  14181. 'accept' => 'ContentTypes',
  14182. 'accept-charset' => 'Charsets',
  14183. 'action*' => 'URI',
  14184. 'method' => 'Enum#get,post',
  14185. // really ContentType, but these two are the only ones used today
  14186. 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
  14187. )
  14188. );
  14189. $form->excludes = array('form' => true);
  14190. $input = $this->addElement(
  14191. 'input',
  14192. 'Formctrl',
  14193. 'Empty',
  14194. 'Common',
  14195. array(
  14196. 'accept' => 'ContentTypes',
  14197. 'accesskey' => 'Character',
  14198. 'alt' => 'Text',
  14199. 'checked' => 'Bool#checked',
  14200. 'disabled' => 'Bool#disabled',
  14201. 'maxlength' => 'Number',
  14202. 'name' => 'CDATA',
  14203. 'readonly' => 'Bool#readonly',
  14204. 'size' => 'Number',
  14205. 'src' => 'URI#embedded',
  14206. 'tabindex' => 'Number',
  14207. 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
  14208. 'value' => 'CDATA',
  14209. )
  14210. );
  14211. $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
  14212. $this->addElement(
  14213. 'select',
  14214. 'Formctrl',
  14215. 'Required: optgroup | option',
  14216. 'Common',
  14217. array(
  14218. 'disabled' => 'Bool#disabled',
  14219. 'multiple' => 'Bool#multiple',
  14220. 'name' => 'CDATA',
  14221. 'size' => 'Number',
  14222. 'tabindex' => 'Number',
  14223. )
  14224. );
  14225. $this->addElement(
  14226. 'option',
  14227. false,
  14228. 'Optional: #PCDATA',
  14229. 'Common',
  14230. array(
  14231. 'disabled' => 'Bool#disabled',
  14232. 'label' => 'Text',
  14233. 'selected' => 'Bool#selected',
  14234. 'value' => 'CDATA',
  14235. )
  14236. );
  14237. // It's illegal for there to be more than one selected, but not
  14238. // be multiple. Also, no selected means undefined behavior. This might
  14239. // be difficult to implement; perhaps an injector, or a context variable.
  14240. $textarea = $this->addElement(
  14241. 'textarea',
  14242. 'Formctrl',
  14243. 'Optional: #PCDATA',
  14244. 'Common',
  14245. array(
  14246. 'accesskey' => 'Character',
  14247. 'cols*' => 'Number',
  14248. 'disabled' => 'Bool#disabled',
  14249. 'name' => 'CDATA',
  14250. 'readonly' => 'Bool#readonly',
  14251. 'rows*' => 'Number',
  14252. 'tabindex' => 'Number',
  14253. )
  14254. );
  14255. $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
  14256. $button = $this->addElement(
  14257. 'button',
  14258. 'Formctrl',
  14259. 'Optional: #PCDATA | Heading | List | Block | Inline',
  14260. 'Common',
  14261. array(
  14262. 'accesskey' => 'Character',
  14263. 'disabled' => 'Bool#disabled',
  14264. 'name' => 'CDATA',
  14265. 'tabindex' => 'Number',
  14266. 'type' => 'Enum#button,submit,reset',
  14267. 'value' => 'CDATA',
  14268. )
  14269. );
  14270. // For exclusions, ideally we'd specify content sets, not literal elements
  14271. $button->excludes = $this->makeLookup(
  14272. 'form',
  14273. 'fieldset', // Form
  14274. 'input',
  14275. 'select',
  14276. 'textarea',
  14277. 'label',
  14278. 'button', // Formctrl
  14279. 'a', // as per HTML 4.01 spec, this is omitted by modularization
  14280. 'isindex',
  14281. 'iframe' // legacy items
  14282. );
  14283. // Extra exclusion: img usemap="" is not permitted within this element.
  14284. // We'll omit this for now, since we don't have any good way of
  14285. // indicating it yet.
  14286. // This is HIGHLY user-unfriendly; we need a custom child-def for this
  14287. $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
  14288. $label = $this->addElement(
  14289. 'label',
  14290. 'Formctrl',
  14291. 'Optional: #PCDATA | Inline',
  14292. 'Common',
  14293. array(
  14294. 'accesskey' => 'Character',
  14295. // 'for' => 'IDREF', // IDREF not implemented, cannot allow
  14296. )
  14297. );
  14298. $label->excludes = array('label' => true);
  14299. $this->addElement(
  14300. 'legend',
  14301. false,
  14302. 'Optional: #PCDATA | Inline',
  14303. 'Common',
  14304. array(
  14305. 'accesskey' => 'Character',
  14306. )
  14307. );
  14308. $this->addElement(
  14309. 'optgroup',
  14310. false,
  14311. 'Required: option',
  14312. 'Common',
  14313. array(
  14314. 'disabled' => 'Bool#disabled',
  14315. 'label*' => 'Text',
  14316. )
  14317. );
  14318. // Don't forget an injector for <isindex>. This one's a little complex
  14319. // because it maps to multiple elements.
  14320. }
  14321. }
  14322. /**
  14323. * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
  14324. */
  14325. class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
  14326. {
  14327. /**
  14328. * @type string
  14329. */
  14330. public $name = 'Hypertext';
  14331. /**
  14332. * @param HTMLPurifier_Config $config
  14333. */
  14334. public function setup($config)
  14335. {
  14336. $a = $this->addElement(
  14337. 'a',
  14338. 'Inline',
  14339. 'Inline',
  14340. 'Common',
  14341. array(
  14342. // 'accesskey' => 'Character',
  14343. // 'charset' => 'Charset',
  14344. 'href' => 'URI',
  14345. // 'hreflang' => 'LanguageCode',
  14346. 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
  14347. 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
  14348. // 'tabindex' => 'Number',
  14349. // 'type' => 'ContentType',
  14350. )
  14351. );
  14352. $a->formatting = true;
  14353. $a->excludes = array('a' => true);
  14354. }
  14355. }
  14356. /**
  14357. * XHTML 1.1 Iframe Module provides inline frames.
  14358. *
  14359. * @note This module is not considered safe unless an Iframe
  14360. * whitelisting mechanism is specified. Currently, the only
  14361. * such mechanism is %URL.SafeIframeRegexp
  14362. */
  14363. class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
  14364. {
  14365. /**
  14366. * @type string
  14367. */
  14368. public $name = 'Iframe';
  14369. /**
  14370. * @type bool
  14371. */
  14372. public $safe = false;
  14373. /**
  14374. * @param HTMLPurifier_Config $config
  14375. */
  14376. public function setup($config)
  14377. {
  14378. if ($config->get('HTML.SafeIframe')) {
  14379. $this->safe = true;
  14380. }
  14381. $this->addElement(
  14382. 'iframe',
  14383. 'Inline',
  14384. 'Flow',
  14385. 'Common',
  14386. array(
  14387. 'src' => 'URI#embedded',
  14388. 'width' => 'Length',
  14389. 'height' => 'Length',
  14390. 'name' => 'ID',
  14391. 'scrolling' => 'Enum#yes,no,auto',
  14392. 'frameborder' => 'Enum#0,1',
  14393. 'longdesc' => 'URI',
  14394. 'marginheight' => 'Pixels',
  14395. 'marginwidth' => 'Pixels',
  14396. )
  14397. );
  14398. }
  14399. }
  14400. /**
  14401. * XHTML 1.1 Image Module provides basic image embedding.
  14402. * @note There is specialized code for removing empty images in
  14403. * HTMLPurifier_Strategy_RemoveForeignElements
  14404. */
  14405. class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
  14406. {
  14407. /**
  14408. * @type string
  14409. */
  14410. public $name = 'Image';
  14411. /**
  14412. * @param HTMLPurifier_Config $config
  14413. */
  14414. public function setup($config)
  14415. {
  14416. $max = $config->get('HTML.MaxImgLength');
  14417. $img = $this->addElement(
  14418. 'img',
  14419. 'Inline',
  14420. 'Empty',
  14421. 'Common',
  14422. array(
  14423. 'alt*' => 'Text',
  14424. // According to the spec, it's Length, but percents can
  14425. // be abused, so we allow only Pixels.
  14426. 'height' => 'Pixels#' . $max,
  14427. 'width' => 'Pixels#' . $max,
  14428. 'longdesc' => 'URI',
  14429. 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
  14430. )
  14431. );
  14432. if ($max === null || $config->get('HTML.Trusted')) {
  14433. $img->attr['height'] =
  14434. $img->attr['width'] = 'Length';
  14435. }
  14436. // kind of strange, but splitting things up would be inefficient
  14437. $img->attr_transform_pre[] =
  14438. $img->attr_transform_post[] =
  14439. new HTMLPurifier_AttrTransform_ImgRequired();
  14440. }
  14441. }
  14442. /**
  14443. * XHTML 1.1 Legacy module defines elements that were previously
  14444. * deprecated.
  14445. *
  14446. * @note Not all legacy elements have been implemented yet, which
  14447. * is a bit of a reverse problem as compared to browsers! In
  14448. * addition, this legacy module may implement a bit more than
  14449. * mandated by XHTML 1.1.
  14450. *
  14451. * This module can be used in combination with TransformToStrict in order
  14452. * to transform as many deprecated elements as possible, but retain
  14453. * questionably deprecated elements that do not have good alternatives
  14454. * as well as transform elements that don't have an implementation.
  14455. * See docs/ref-strictness.txt for more details.
  14456. */
  14457. class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
  14458. {
  14459. /**
  14460. * @type string
  14461. */
  14462. public $name = 'Legacy';
  14463. /**
  14464. * @param HTMLPurifier_Config $config
  14465. */
  14466. public function setup($config)
  14467. {
  14468. $this->addElement(
  14469. 'basefont',
  14470. 'Inline',
  14471. 'Empty',
  14472. null,
  14473. array(
  14474. 'color' => 'Color',
  14475. 'face' => 'Text', // extremely broad, we should
  14476. 'size' => 'Text', // tighten it
  14477. 'id' => 'ID'
  14478. )
  14479. );
  14480. $this->addElement('center', 'Block', 'Flow', 'Common');
  14481. $this->addElement(
  14482. 'dir',
  14483. 'Block',
  14484. 'Required: li',
  14485. 'Common',
  14486. array(
  14487. 'compact' => 'Bool#compact'
  14488. )
  14489. );
  14490. $this->addElement(
  14491. 'font',
  14492. 'Inline',
  14493. 'Inline',
  14494. array('Core', 'I18N'),
  14495. array(
  14496. 'color' => 'Color',
  14497. 'face' => 'Text', // extremely broad, we should
  14498. 'size' => 'Text', // tighten it
  14499. )
  14500. );
  14501. $this->addElement(
  14502. 'menu',
  14503. 'Block',
  14504. 'Required: li',
  14505. 'Common',
  14506. array(
  14507. 'compact' => 'Bool#compact'
  14508. )
  14509. );
  14510. $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
  14511. $s->formatting = true;
  14512. $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
  14513. $strike->formatting = true;
  14514. $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
  14515. $u->formatting = true;
  14516. // setup modifications to old elements
  14517. $align = 'Enum#left,right,center,justify';
  14518. $address = $this->addBlankElement('address');
  14519. $address->content_model = 'Inline | #PCDATA | p';
  14520. $address->content_model_type = 'optional';
  14521. $address->child = false;
  14522. $blockquote = $this->addBlankElement('blockquote');
  14523. $blockquote->content_model = 'Flow | #PCDATA';
  14524. $blockquote->content_model_type = 'optional';
  14525. $blockquote->child = false;
  14526. $br = $this->addBlankElement('br');
  14527. $br->attr['clear'] = 'Enum#left,all,right,none';
  14528. $caption = $this->addBlankElement('caption');
  14529. $caption->attr['align'] = 'Enum#top,bottom,left,right';
  14530. $div = $this->addBlankElement('div');
  14531. $div->attr['align'] = $align;
  14532. $dl = $this->addBlankElement('dl');
  14533. $dl->attr['compact'] = 'Bool#compact';
  14534. for ($i = 1; $i <= 6; $i++) {
  14535. $h = $this->addBlankElement("h$i");
  14536. $h->attr['align'] = $align;
  14537. }
  14538. $hr = $this->addBlankElement('hr');
  14539. $hr->attr['align'] = $align;
  14540. $hr->attr['noshade'] = 'Bool#noshade';
  14541. $hr->attr['size'] = 'Pixels';
  14542. $hr->attr['width'] = 'Length';
  14543. $img = $this->addBlankElement('img');
  14544. $img->attr['align'] = 'IAlign';
  14545. $img->attr['border'] = 'Pixels';
  14546. $img->attr['hspace'] = 'Pixels';
  14547. $img->attr['vspace'] = 'Pixels';
  14548. // figure out this integer business
  14549. $li = $this->addBlankElement('li');
  14550. $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
  14551. $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
  14552. $ol = $this->addBlankElement('ol');
  14553. $ol->attr['compact'] = 'Bool#compact';
  14554. $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
  14555. $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
  14556. $p = $this->addBlankElement('p');
  14557. $p->attr['align'] = $align;
  14558. $pre = $this->addBlankElement('pre');
  14559. $pre->attr['width'] = 'Number';
  14560. // script omitted
  14561. $table = $this->addBlankElement('table');
  14562. $table->attr['align'] = 'Enum#left,center,right';
  14563. $table->attr['bgcolor'] = 'Color';
  14564. $tr = $this->addBlankElement('tr');
  14565. $tr->attr['bgcolor'] = 'Color';
  14566. $th = $this->addBlankElement('th');
  14567. $th->attr['bgcolor'] = 'Color';
  14568. $th->attr['height'] = 'Length';
  14569. $th->attr['nowrap'] = 'Bool#nowrap';
  14570. $th->attr['width'] = 'Length';
  14571. $td = $this->addBlankElement('td');
  14572. $td->attr['bgcolor'] = 'Color';
  14573. $td->attr['height'] = 'Length';
  14574. $td->attr['nowrap'] = 'Bool#nowrap';
  14575. $td->attr['width'] = 'Length';
  14576. $ul = $this->addBlankElement('ul');
  14577. $ul->attr['compact'] = 'Bool#compact';
  14578. $ul->attr['type'] = 'Enum#square,disc,circle';
  14579. // "safe" modifications to "unsafe" elements
  14580. // WARNING: If you want to add support for an unsafe, legacy
  14581. // attribute, make a new TrustedLegacy module with the trusted
  14582. // bit set appropriately
  14583. $form = $this->addBlankElement('form');
  14584. $form->content_model = 'Flow | #PCDATA';
  14585. $form->content_model_type = 'optional';
  14586. $form->attr['target'] = 'FrameTarget';
  14587. $input = $this->addBlankElement('input');
  14588. $input->attr['align'] = 'IAlign';
  14589. $legend = $this->addBlankElement('legend');
  14590. $legend->attr['align'] = 'LAlign';
  14591. }
  14592. }
  14593. /**
  14594. * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
  14595. */
  14596. class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
  14597. {
  14598. /**
  14599. * @type string
  14600. */
  14601. public $name = 'List';
  14602. // According to the abstract schema, the List content set is a fully formed
  14603. // one or more expr, but it invariably occurs in an optional declaration
  14604. // so we're not going to do that subtlety. It might cause trouble
  14605. // if a user defines "List" and expects that multiple lists are
  14606. // allowed to be specified, but then again, that's not very intuitive.
  14607. // Furthermore, the actual XML Schema may disagree. Regardless,
  14608. // we don't have support for such nested expressions without using
  14609. // the incredibly inefficient and draconic Custom ChildDef.
  14610. /**
  14611. * @type array
  14612. */
  14613. public $content_sets = array('Flow' => 'List');
  14614. /**
  14615. * @param HTMLPurifier_Config $config
  14616. */
  14617. public function setup($config)
  14618. {
  14619. $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14620. $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14621. // XXX The wrap attribute is handled by MakeWellFormed. This is all
  14622. // quite unsatisfactory, because we generated this
  14623. // *specifically* for lists, and now a big chunk of the handling
  14624. // is done properly by the List ChildDef. So actually, we just
  14625. // want enough information to make autoclosing work properly,
  14626. // and then hand off the tricky stuff to the ChildDef.
  14627. $ol->wrap = 'li';
  14628. $ul->wrap = 'li';
  14629. $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
  14630. $this->addElement('li', false, 'Flow', 'Common');
  14631. $this->addElement('dd', false, 'Flow', 'Common');
  14632. $this->addElement('dt', false, 'Inline', 'Common');
  14633. }
  14634. }
  14635. class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
  14636. {
  14637. /**
  14638. * @type string
  14639. */
  14640. public $name = 'Name';
  14641. /**
  14642. * @param HTMLPurifier_Config $config
  14643. */
  14644. public function setup($config)
  14645. {
  14646. $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
  14647. foreach ($elements as $name) {
  14648. $element = $this->addBlankElement($name);
  14649. $element->attr['name'] = 'CDATA';
  14650. if (!$config->get('HTML.Attr.Name.UseCDATA')) {
  14651. $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
  14652. }
  14653. }
  14654. }
  14655. }
  14656. /**
  14657. * Module adds the nofollow attribute transformation to a tags. It
  14658. * is enabled by HTML.Nofollow
  14659. */
  14660. class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
  14661. {
  14662. /**
  14663. * @type string
  14664. */
  14665. public $name = 'Nofollow';
  14666. /**
  14667. * @param HTMLPurifier_Config $config
  14668. */
  14669. public function setup($config)
  14670. {
  14671. $a = $this->addBlankElement('a');
  14672. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
  14673. }
  14674. }
  14675. class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
  14676. {
  14677. /**
  14678. * @type string
  14679. */
  14680. public $name = 'NonXMLCommonAttributes';
  14681. /**
  14682. * @type array
  14683. */
  14684. public $attr_collections = array(
  14685. 'Lang' => array(
  14686. 'lang' => 'LanguageCode',
  14687. )
  14688. );
  14689. }
  14690. /**
  14691. * XHTML 1.1 Object Module, defines elements for generic object inclusion
  14692. * @warning Users will commonly use <embed> to cater to legacy browsers: this
  14693. * module does not allow this sort of behavior
  14694. */
  14695. class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
  14696. {
  14697. /**
  14698. * @type string
  14699. */
  14700. public $name = 'Object';
  14701. /**
  14702. * @type bool
  14703. */
  14704. public $safe = false;
  14705. /**
  14706. * @param HTMLPurifier_Config $config
  14707. */
  14708. public function setup($config)
  14709. {
  14710. $this->addElement(
  14711. 'object',
  14712. 'Inline',
  14713. 'Optional: #PCDATA | Flow | param',
  14714. 'Common',
  14715. array(
  14716. 'archive' => 'URI',
  14717. 'classid' => 'URI',
  14718. 'codebase' => 'URI',
  14719. 'codetype' => 'Text',
  14720. 'data' => 'URI',
  14721. 'declare' => 'Bool#declare',
  14722. 'height' => 'Length',
  14723. 'name' => 'CDATA',
  14724. 'standby' => 'Text',
  14725. 'tabindex' => 'Number',
  14726. 'type' => 'ContentType',
  14727. 'width' => 'Length'
  14728. )
  14729. );
  14730. $this->addElement(
  14731. 'param',
  14732. false,
  14733. 'Empty',
  14734. null,
  14735. array(
  14736. 'id' => 'ID',
  14737. 'name*' => 'Text',
  14738. 'type' => 'Text',
  14739. 'value' => 'Text',
  14740. 'valuetype' => 'Enum#data,ref,object'
  14741. )
  14742. );
  14743. }
  14744. }
  14745. /**
  14746. * XHTML 1.1 Presentation Module, defines simple presentation-related
  14747. * markup. Text Extension Module.
  14748. * @note The official XML Schema and DTD specs further divide this into
  14749. * two modules:
  14750. * - Block Presentation (hr)
  14751. * - Inline Presentation (b, big, i, small, sub, sup, tt)
  14752. * We have chosen not to heed this distinction, as content_sets
  14753. * provides satisfactory disambiguation.
  14754. */
  14755. class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
  14756. {
  14757. /**
  14758. * @type string
  14759. */
  14760. public $name = 'Presentation';
  14761. /**
  14762. * @param HTMLPurifier_Config $config
  14763. */
  14764. public function setup($config)
  14765. {
  14766. $this->addElement('hr', 'Block', 'Empty', 'Common');
  14767. $this->addElement('sub', 'Inline', 'Inline', 'Common');
  14768. $this->addElement('sup', 'Inline', 'Inline', 'Common');
  14769. $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
  14770. $b->formatting = true;
  14771. $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
  14772. $big->formatting = true;
  14773. $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
  14774. $i->formatting = true;
  14775. $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
  14776. $small->formatting = true;
  14777. $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
  14778. $tt->formatting = true;
  14779. }
  14780. }
  14781. /**
  14782. * Module defines proprietary tags and attributes in HTML.
  14783. * @warning If this module is enabled, standards-compliance is off!
  14784. */
  14785. class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
  14786. {
  14787. /**
  14788. * @type string
  14789. */
  14790. public $name = 'Proprietary';
  14791. /**
  14792. * @param HTMLPurifier_Config $config
  14793. */
  14794. public function setup($config)
  14795. {
  14796. $this->addElement(
  14797. 'marquee',
  14798. 'Inline',
  14799. 'Flow',
  14800. 'Common',
  14801. array(
  14802. 'direction' => 'Enum#left,right,up,down',
  14803. 'behavior' => 'Enum#alternate',
  14804. 'width' => 'Length',
  14805. 'height' => 'Length',
  14806. 'scrolldelay' => 'Number',
  14807. 'scrollamount' => 'Number',
  14808. 'loop' => 'Number',
  14809. 'bgcolor' => 'Color',
  14810. 'hspace' => 'Pixels',
  14811. 'vspace' => 'Pixels',
  14812. )
  14813. );
  14814. }
  14815. }
  14816. /**
  14817. * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
  14818. * short runs of text alongside base text for annotation or pronounciation.
  14819. */
  14820. class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
  14821. {
  14822. /**
  14823. * @type string
  14824. */
  14825. public $name = 'Ruby';
  14826. /**
  14827. * @param HTMLPurifier_Config $config
  14828. */
  14829. public function setup($config)
  14830. {
  14831. $this->addElement(
  14832. 'ruby',
  14833. 'Inline',
  14834. 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
  14835. 'Common'
  14836. );
  14837. $this->addElement('rbc', false, 'Required: rb', 'Common');
  14838. $this->addElement('rtc', false, 'Required: rt', 'Common');
  14839. $rb = $this->addElement('rb', false, 'Inline', 'Common');
  14840. $rb->excludes = array('ruby' => true);
  14841. $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
  14842. $rt->excludes = array('ruby' => true);
  14843. $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
  14844. }
  14845. }
  14846. /**
  14847. * A "safe" embed module. See SafeObject. This is a proprietary element.
  14848. */
  14849. class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
  14850. {
  14851. /**
  14852. * @type string
  14853. */
  14854. public $name = 'SafeEmbed';
  14855. /**
  14856. * @param HTMLPurifier_Config $config
  14857. */
  14858. public function setup($config)
  14859. {
  14860. $max = $config->get('HTML.MaxImgLength');
  14861. $embed = $this->addElement(
  14862. 'embed',
  14863. 'Inline',
  14864. 'Empty',
  14865. 'Common',
  14866. array(
  14867. 'src*' => 'URI#embedded',
  14868. 'type' => 'Enum#application/x-shockwave-flash',
  14869. 'width' => 'Pixels#' . $max,
  14870. 'height' => 'Pixels#' . $max,
  14871. 'allowscriptaccess' => 'Enum#never',
  14872. 'allownetworking' => 'Enum#internal',
  14873. 'flashvars' => 'Text',
  14874. 'wmode' => 'Enum#window,transparent,opaque',
  14875. 'name' => 'ID',
  14876. )
  14877. );
  14878. $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
  14879. }
  14880. }
  14881. /**
  14882. * A "safe" object module. In theory, objects permitted by this module will
  14883. * be safe, and untrusted users can be allowed to embed arbitrary flash objects
  14884. * (maybe other types too, but only Flash is supported as of right now).
  14885. * Highly experimental.
  14886. */
  14887. class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
  14888. {
  14889. /**
  14890. * @type string
  14891. */
  14892. public $name = 'SafeObject';
  14893. /**
  14894. * @param HTMLPurifier_Config $config
  14895. */
  14896. public function setup($config)
  14897. {
  14898. // These definitions are not intrinsically safe: the attribute transforms
  14899. // are a vital part of ensuring safety.
  14900. $max = $config->get('HTML.MaxImgLength');
  14901. $object = $this->addElement(
  14902. 'object',
  14903. 'Inline',
  14904. 'Optional: param | Flow | #PCDATA',
  14905. 'Common',
  14906. array(
  14907. // While technically not required by the spec, we're forcing
  14908. // it to this value.
  14909. 'type' => 'Enum#application/x-shockwave-flash',
  14910. 'width' => 'Pixels#' . $max,
  14911. 'height' => 'Pixels#' . $max,
  14912. 'data' => 'URI#embedded',
  14913. 'codebase' => new HTMLPurifier_AttrDef_Enum(
  14914. array(
  14915. 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
  14916. )
  14917. ),
  14918. )
  14919. );
  14920. $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
  14921. $param = $this->addElement(
  14922. 'param',
  14923. false,
  14924. 'Empty',
  14925. false,
  14926. array(
  14927. 'id' => 'ID',
  14928. 'name*' => 'Text',
  14929. 'value' => 'Text'
  14930. )
  14931. );
  14932. $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
  14933. $this->info_injector[] = 'SafeObject';
  14934. }
  14935. }
  14936. /**
  14937. * A "safe" script module. No inline JS is allowed, and pointed to JS
  14938. * files must match whitelist.
  14939. */
  14940. class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
  14941. {
  14942. /**
  14943. * @type string
  14944. */
  14945. public $name = 'SafeScripting';
  14946. /**
  14947. * @param HTMLPurifier_Config $config
  14948. */
  14949. public function setup($config)
  14950. {
  14951. // These definitions are not intrinsically safe: the attribute transforms
  14952. // are a vital part of ensuring safety.
  14953. $allowed = $config->get('HTML.SafeScripting');
  14954. $script = $this->addElement(
  14955. 'script',
  14956. 'Inline',
  14957. 'Optional:', // Not `Empty` to not allow to autoclose the <script /> tag @see https://www.w3.org/TR/html4/interact/scripts.html
  14958. null,
  14959. array(
  14960. // While technically not required by the spec, we're forcing
  14961. // it to this value.
  14962. 'type' => 'Enum#text/javascript',
  14963. 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed), /*case sensitive*/ true)
  14964. )
  14965. );
  14966. $script->attr_transform_pre[] =
  14967. $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
  14968. }
  14969. }
  14970. /*
  14971. WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
  14972. INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
  14973. */
  14974. /**
  14975. * XHTML 1.1 Scripting module, defines elements that are used to contain
  14976. * information pertaining to executable scripts or the lack of support
  14977. * for executable scripts.
  14978. * @note This module does not contain inline scripting elements
  14979. */
  14980. class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
  14981. {
  14982. /**
  14983. * @type string
  14984. */
  14985. public $name = 'Scripting';
  14986. /**
  14987. * @type array
  14988. */
  14989. public $elements = array('script', 'noscript');
  14990. /**
  14991. * @type array
  14992. */
  14993. public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
  14994. /**
  14995. * @type bool
  14996. */
  14997. public $safe = false;
  14998. /**
  14999. * @param HTMLPurifier_Config $config
  15000. */
  15001. public function setup($config)
  15002. {
  15003. // TODO: create custom child-definition for noscript that
  15004. // auto-wraps stray #PCDATA in a similar manner to
  15005. // blockquote's custom definition (we would use it but
  15006. // blockquote's contents are optional while noscript's contents
  15007. // are required)
  15008. // TODO: convert this to new syntax, main problem is getting
  15009. // both content sets working
  15010. // In theory, this could be safe, but I don't see any reason to
  15011. // allow it.
  15012. $this->info['noscript'] = new HTMLPurifier_ElementDef();
  15013. $this->info['noscript']->attr = array(0 => array('Common'));
  15014. $this->info['noscript']->content_model = 'Heading | List | Block';
  15015. $this->info['noscript']->content_model_type = 'required';
  15016. $this->info['script'] = new HTMLPurifier_ElementDef();
  15017. $this->info['script']->attr = array(
  15018. 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
  15019. 'src' => new HTMLPurifier_AttrDef_URI(true),
  15020. 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
  15021. );
  15022. $this->info['script']->content_model = '#PCDATA';
  15023. $this->info['script']->content_model_type = 'optional';
  15024. $this->info['script']->attr_transform_pre[] =
  15025. $this->info['script']->attr_transform_post[] =
  15026. new HTMLPurifier_AttrTransform_ScriptRequired();
  15027. }
  15028. }
  15029. /**
  15030. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  15031. * Module.
  15032. */
  15033. class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
  15034. {
  15035. /**
  15036. * @type string
  15037. */
  15038. public $name = 'StyleAttribute';
  15039. /**
  15040. * @type array
  15041. */
  15042. public $attr_collections = array(
  15043. // The inclusion routine differs from the Abstract Modules but
  15044. // is in line with the DTD and XML Schemas.
  15045. 'Style' => array('style' => false), // see constructor
  15046. 'Core' => array(0 => array('Style'))
  15047. );
  15048. /**
  15049. * @param HTMLPurifier_Config $config
  15050. */
  15051. public function setup($config)
  15052. {
  15053. $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
  15054. }
  15055. }
  15056. /**
  15057. * XHTML 1.1 Tables Module, fully defines accessible table elements.
  15058. */
  15059. class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
  15060. {
  15061. /**
  15062. * @type string
  15063. */
  15064. public $name = 'Tables';
  15065. /**
  15066. * @param HTMLPurifier_Config $config
  15067. */
  15068. public function setup($config)
  15069. {
  15070. $this->addElement('caption', false, 'Inline', 'Common');
  15071. $this->addElement(
  15072. 'table',
  15073. 'Block',
  15074. new HTMLPurifier_ChildDef_Table(),
  15075. 'Common',
  15076. array(
  15077. 'border' => 'Pixels',
  15078. 'cellpadding' => 'Length',
  15079. 'cellspacing' => 'Length',
  15080. 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
  15081. 'rules' => 'Enum#none,groups,rows,cols,all',
  15082. 'summary' => 'Text',
  15083. 'width' => 'Length'
  15084. )
  15085. );
  15086. // common attributes
  15087. $cell_align = array(
  15088. 'align' => 'Enum#left,center,right,justify,char',
  15089. 'charoff' => 'Length',
  15090. 'valign' => 'Enum#top,middle,bottom,baseline',
  15091. );
  15092. $cell_t = array_merge(
  15093. array(
  15094. 'abbr' => 'Text',
  15095. 'colspan' => 'Number',
  15096. 'rowspan' => 'Number',
  15097. // Apparently, as of HTML5 this attribute only applies
  15098. // to 'th' elements.
  15099. 'scope' => 'Enum#row,col,rowgroup,colgroup',
  15100. ),
  15101. $cell_align
  15102. );
  15103. $this->addElement('td', false, 'Flow', 'Common', $cell_t);
  15104. $this->addElement('th', false, 'Flow', 'Common', $cell_t);
  15105. $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
  15106. $cell_col = array_merge(
  15107. array(
  15108. 'span' => 'Number',
  15109. 'width' => 'MultiLength',
  15110. ),
  15111. $cell_align
  15112. );
  15113. $this->addElement('col', false, 'Empty', 'Common', $cell_col);
  15114. $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
  15115. $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
  15116. $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
  15117. $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
  15118. }
  15119. }
  15120. /**
  15121. * XHTML 1.1 Target Module, defines target attribute in link elements.
  15122. */
  15123. class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
  15124. {
  15125. /**
  15126. * @type string
  15127. */
  15128. public $name = 'Target';
  15129. /**
  15130. * @param HTMLPurifier_Config $config
  15131. */
  15132. public function setup($config)
  15133. {
  15134. $elements = array('a');
  15135. foreach ($elements as $name) {
  15136. $e = $this->addBlankElement($name);
  15137. $e->attr = array(
  15138. 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
  15139. );
  15140. }
  15141. }
  15142. }
  15143. /**
  15144. * Module adds the target=blank attribute transformation to a tags. It
  15145. * is enabled by HTML.TargetBlank
  15146. */
  15147. class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
  15148. {
  15149. /**
  15150. * @type string
  15151. */
  15152. public $name = 'TargetBlank';
  15153. /**
  15154. * @param HTMLPurifier_Config $config
  15155. */
  15156. public function setup($config)
  15157. {
  15158. $a = $this->addBlankElement('a');
  15159. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
  15160. }
  15161. }
  15162. /**
  15163. * Module adds the target-based noopener attribute transformation to a tags. It
  15164. * is enabled by HTML.TargetNoopener
  15165. */
  15166. class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule
  15167. {
  15168. /**
  15169. * @type string
  15170. */
  15171. public $name = 'TargetNoopener';
  15172. /**
  15173. * @param HTMLPurifier_Config $config
  15174. */
  15175. public function setup($config) {
  15176. $a = $this->addBlankElement('a');
  15177. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();
  15178. }
  15179. }
  15180. /**
  15181. * Module adds the target-based noreferrer attribute transformation to a tags. It
  15182. * is enabled by HTML.TargetNoreferrer
  15183. */
  15184. class HTMLPurifier_HTMLModule_TargetNoreferrer extends HTMLPurifier_HTMLModule
  15185. {
  15186. /**
  15187. * @type string
  15188. */
  15189. public $name = 'TargetNoreferrer';
  15190. /**
  15191. * @param HTMLPurifier_Config $config
  15192. */
  15193. public function setup($config) {
  15194. $a = $this->addBlankElement('a');
  15195. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer();
  15196. }
  15197. }
  15198. /**
  15199. * XHTML 1.1 Text Module, defines basic text containers. Core Module.
  15200. * @note In the normative XML Schema specification, this module
  15201. * is further abstracted into the following modules:
  15202. * - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
  15203. * - Block Structural (div, p)
  15204. * - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
  15205. * - Inline Structural (br, span)
  15206. * This module, functionally, does not distinguish between these
  15207. * sub-modules, but the code is internally structured to reflect
  15208. * these distinctions.
  15209. */
  15210. class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
  15211. {
  15212. /**
  15213. * @type string
  15214. */
  15215. public $name = 'Text';
  15216. /**
  15217. * @type array
  15218. */
  15219. public $content_sets = array(
  15220. 'Flow' => 'Heading | Block | Inline'
  15221. );
  15222. /**
  15223. * @param HTMLPurifier_Config $config
  15224. */
  15225. public function setup($config)
  15226. {
  15227. // Inline Phrasal -------------------------------------------------
  15228. $this->addElement('abbr', 'Inline', 'Inline', 'Common');
  15229. $this->addElement('acronym', 'Inline', 'Inline', 'Common');
  15230. $this->addElement('cite', 'Inline', 'Inline', 'Common');
  15231. $this->addElement('dfn', 'Inline', 'Inline', 'Common');
  15232. $this->addElement('kbd', 'Inline', 'Inline', 'Common');
  15233. $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
  15234. $this->addElement('samp', 'Inline', 'Inline', 'Common');
  15235. $this->addElement('var', 'Inline', 'Inline', 'Common');
  15236. $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
  15237. $em->formatting = true;
  15238. $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
  15239. $strong->formatting = true;
  15240. $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
  15241. $code->formatting = true;
  15242. // Inline Structural ----------------------------------------------
  15243. $this->addElement('span', 'Inline', 'Inline', 'Common');
  15244. $this->addElement('br', 'Inline', 'Empty', 'Core');
  15245. // Block Phrasal --------------------------------------------------
  15246. $this->addElement('address', 'Block', 'Inline', 'Common');
  15247. $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
  15248. $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
  15249. $pre->excludes = $this->makeLookup(
  15250. 'img',
  15251. 'big',
  15252. 'small',
  15253. 'object',
  15254. 'applet',
  15255. 'font',
  15256. 'basefont'
  15257. );
  15258. $this->addElement('h1', 'Heading', 'Inline', 'Common');
  15259. $this->addElement('h2', 'Heading', 'Inline', 'Common');
  15260. $this->addElement('h3', 'Heading', 'Inline', 'Common');
  15261. $this->addElement('h4', 'Heading', 'Inline', 'Common');
  15262. $this->addElement('h5', 'Heading', 'Inline', 'Common');
  15263. $this->addElement('h6', 'Heading', 'Inline', 'Common');
  15264. // Block Structural -----------------------------------------------
  15265. $p = $this->addElement('p', 'Block', 'Inline', 'Common');
  15266. $p->autoclose = array_flip(
  15267. array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
  15268. );
  15269. $this->addElement('div', 'Block', 'Flow', 'Common');
  15270. }
  15271. }
  15272. /**
  15273. * Abstract class for a set of proprietary modules that clean up (tidy)
  15274. * poorly written HTML.
  15275. * @todo Figure out how to protect some of these methods/properties
  15276. */
  15277. class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
  15278. {
  15279. /**
  15280. * List of supported levels.
  15281. * Index zero is a special case "no fixes" level.
  15282. * @type array
  15283. */
  15284. public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
  15285. /**
  15286. * Default level to place all fixes in.
  15287. * Disabled by default.
  15288. * @type string
  15289. */
  15290. public $defaultLevel = null;
  15291. /**
  15292. * Lists of fixes used by getFixesForLevel().
  15293. * Format is:
  15294. * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
  15295. * @type array
  15296. */
  15297. public $fixesForLevel = array(
  15298. 'light' => array(),
  15299. 'medium' => array(),
  15300. 'heavy' => array()
  15301. );
  15302. /**
  15303. * Lazy load constructs the module by determining the necessary
  15304. * fixes to create and then delegating to the populate() function.
  15305. * @param HTMLPurifier_Config $config
  15306. * @todo Wildcard matching and error reporting when an added or
  15307. * subtracted fix has no effect.
  15308. */
  15309. public function setup($config)
  15310. {
  15311. // create fixes, initialize fixesForLevel
  15312. $fixes = $this->makeFixes();
  15313. $this->makeFixesForLevel($fixes);
  15314. // figure out which fixes to use
  15315. $level = $config->get('HTML.TidyLevel');
  15316. $fixes_lookup = $this->getFixesForLevel($level);
  15317. // get custom fix declarations: these need namespace processing
  15318. $add_fixes = $config->get('HTML.TidyAdd');
  15319. $remove_fixes = $config->get('HTML.TidyRemove');
  15320. foreach ($fixes as $name => $fix) {
  15321. // needs to be refactored a little to implement globbing
  15322. if (isset($remove_fixes[$name]) ||
  15323. (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
  15324. unset($fixes[$name]);
  15325. }
  15326. }
  15327. // populate this module with necessary fixes
  15328. $this->populate($fixes);
  15329. }
  15330. /**
  15331. * Retrieves all fixes per a level, returning fixes for that specific
  15332. * level as well as all levels below it.
  15333. * @param string $level level identifier, see $levels for valid values
  15334. * @return array Lookup up table of fixes
  15335. */
  15336. public function getFixesForLevel($level)
  15337. {
  15338. if ($level == $this->levels[0]) {
  15339. return array();
  15340. }
  15341. $activated_levels = array();
  15342. for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
  15343. $activated_levels[] = $this->levels[$i];
  15344. if ($this->levels[$i] == $level) {
  15345. break;
  15346. }
  15347. }
  15348. if ($i == $c) {
  15349. trigger_error(
  15350. 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
  15351. E_USER_WARNING
  15352. );
  15353. return array();
  15354. }
  15355. $ret = array();
  15356. foreach ($activated_levels as $level) {
  15357. foreach ($this->fixesForLevel[$level] as $fix) {
  15358. $ret[$fix] = true;
  15359. }
  15360. }
  15361. return $ret;
  15362. }
  15363. /**
  15364. * Dynamically populates the $fixesForLevel member variable using
  15365. * the fixes array. It may be custom overloaded, used in conjunction
  15366. * with $defaultLevel, or not used at all.
  15367. * @param array $fixes
  15368. */
  15369. public function makeFixesForLevel($fixes)
  15370. {
  15371. if (!isset($this->defaultLevel)) {
  15372. return;
  15373. }
  15374. if (!isset($this->fixesForLevel[$this->defaultLevel])) {
  15375. trigger_error(
  15376. 'Default level ' . $this->defaultLevel . ' does not exist',
  15377. E_USER_ERROR
  15378. );
  15379. return;
  15380. }
  15381. $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
  15382. }
  15383. /**
  15384. * Populates the module with transforms and other special-case code
  15385. * based on a list of fixes passed to it
  15386. * @param array $fixes Lookup table of fixes to activate
  15387. */
  15388. public function populate($fixes)
  15389. {
  15390. foreach ($fixes as $name => $fix) {
  15391. // determine what the fix is for
  15392. list($type, $params) = $this->getFixType($name);
  15393. switch ($type) {
  15394. case 'attr_transform_pre':
  15395. case 'attr_transform_post':
  15396. $attr = $params['attr'];
  15397. if (isset($params['element'])) {
  15398. $element = $params['element'];
  15399. if (empty($this->info[$element])) {
  15400. $e = $this->addBlankElement($element);
  15401. } else {
  15402. $e = $this->info[$element];
  15403. }
  15404. } else {
  15405. $type = "info_$type";
  15406. $e = $this;
  15407. }
  15408. // PHP does some weird parsing when I do
  15409. // $e->$type[$attr], so I have to assign a ref.
  15410. $f =& $e->$type;
  15411. $f[$attr] = $fix;
  15412. break;
  15413. case 'tag_transform':
  15414. $this->info_tag_transform[$params['element']] = $fix;
  15415. break;
  15416. case 'child':
  15417. case 'content_model_type':
  15418. $element = $params['element'];
  15419. if (empty($this->info[$element])) {
  15420. $e = $this->addBlankElement($element);
  15421. } else {
  15422. $e = $this->info[$element];
  15423. }
  15424. $e->$type = $fix;
  15425. break;
  15426. default:
  15427. trigger_error("Fix type $type not supported", E_USER_ERROR);
  15428. break;
  15429. }
  15430. }
  15431. }
  15432. /**
  15433. * Parses a fix name and determines what kind of fix it is, as well
  15434. * as other information defined by the fix
  15435. * @param $name String name of fix
  15436. * @return array(string $fix_type, array $fix_parameters)
  15437. * @note $fix_parameters is type dependant, see populate() for usage
  15438. * of these parameters
  15439. */
  15440. public function getFixType($name)
  15441. {
  15442. // parse it
  15443. $property = $attr = null;
  15444. if (strpos($name, '#') !== false) {
  15445. list($name, $property) = explode('#', $name);
  15446. }
  15447. if (strpos($name, '@') !== false) {
  15448. list($name, $attr) = explode('@', $name);
  15449. }
  15450. // figure out the parameters
  15451. $params = array();
  15452. if ($name !== '') {
  15453. $params['element'] = $name;
  15454. }
  15455. if (!is_null($attr)) {
  15456. $params['attr'] = $attr;
  15457. }
  15458. // special case: attribute transform
  15459. if (!is_null($attr)) {
  15460. if (is_null($property)) {
  15461. $property = 'pre';
  15462. }
  15463. $type = 'attr_transform_' . $property;
  15464. return array($type, $params);
  15465. }
  15466. // special case: tag transform
  15467. if (is_null($property)) {
  15468. return array('tag_transform', $params);
  15469. }
  15470. return array($property, $params);
  15471. }
  15472. /**
  15473. * Defines all fixes the module will perform in a compact
  15474. * associative array of fix name to fix implementation.
  15475. * @return array
  15476. */
  15477. public function makeFixes()
  15478. {
  15479. }
  15480. }
  15481. class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
  15482. {
  15483. /**
  15484. * @type string
  15485. */
  15486. public $name = 'XMLCommonAttributes';
  15487. /**
  15488. * @type array
  15489. */
  15490. public $attr_collections = array(
  15491. 'Lang' => array(
  15492. 'xml:lang' => 'LanguageCode',
  15493. )
  15494. );
  15495. }
  15496. /**
  15497. * Name is deprecated, but allowed in strict doctypes, so onl
  15498. */
  15499. class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
  15500. {
  15501. /**
  15502. * @type string
  15503. */
  15504. public $name = 'Tidy_Name';
  15505. /**
  15506. * @type string
  15507. */
  15508. public $defaultLevel = 'heavy';
  15509. /**
  15510. * @return array
  15511. */
  15512. public function makeFixes()
  15513. {
  15514. $r = array();
  15515. // @name for img, a -----------------------------------------------
  15516. // Technically, it's allowed even on strict, so we allow authors to use
  15517. // it. However, it's deprecated in future versions of XHTML.
  15518. $r['img@name'] =
  15519. $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
  15520. return $r;
  15521. }
  15522. }
  15523. class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
  15524. {
  15525. /**
  15526. * @type string
  15527. */
  15528. public $name = 'Tidy_Proprietary';
  15529. /**
  15530. * @type string
  15531. */
  15532. public $defaultLevel = 'light';
  15533. /**
  15534. * @return array
  15535. */
  15536. public function makeFixes()
  15537. {
  15538. $r = array();
  15539. $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
  15540. $r['td@background'] = new HTMLPurifier_AttrTransform_Background();
  15541. $r['th@background'] = new HTMLPurifier_AttrTransform_Background();
  15542. $r['tr@background'] = new HTMLPurifier_AttrTransform_Background();
  15543. $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
  15544. $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
  15545. $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
  15546. $r['table@height'] = new HTMLPurifier_AttrTransform_Length('height');
  15547. return $r;
  15548. }
  15549. }
  15550. class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
  15551. {
  15552. /**
  15553. * @return array
  15554. */
  15555. public function makeFixes()
  15556. {
  15557. $r = array();
  15558. // == deprecated tag transforms ===================================
  15559. $r['font'] = new HTMLPurifier_TagTransform_Font();
  15560. $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
  15561. $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
  15562. $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
  15563. $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
  15564. $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15565. $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15566. // == deprecated attribute transforms =============================
  15567. $r['caption@align'] =
  15568. new HTMLPurifier_AttrTransform_EnumToCSS(
  15569. 'align',
  15570. array(
  15571. // we're following IE's behavior, not Firefox's, due
  15572. // to the fact that no one supports caption-side:right,
  15573. // W3C included (with CSS 2.1). This is a slightly
  15574. // unreasonable attribute!
  15575. 'left' => 'text-align:left;',
  15576. 'right' => 'text-align:right;',
  15577. 'top' => 'caption-side:top;',
  15578. 'bottom' => 'caption-side:bottom;' // not supported by IE
  15579. )
  15580. );
  15581. // @align for img -------------------------------------------------
  15582. $r['img@align'] =
  15583. new HTMLPurifier_AttrTransform_EnumToCSS(
  15584. 'align',
  15585. array(
  15586. 'left' => 'float:left;',
  15587. 'right' => 'float:right;',
  15588. 'top' => 'vertical-align:top;',
  15589. 'middle' => 'vertical-align:middle;',
  15590. 'bottom' => 'vertical-align:baseline;',
  15591. )
  15592. );
  15593. // @align for table -----------------------------------------------
  15594. $r['table@align'] =
  15595. new HTMLPurifier_AttrTransform_EnumToCSS(
  15596. 'align',
  15597. array(
  15598. 'left' => 'float:left;',
  15599. 'center' => 'margin-left:auto;margin-right:auto;',
  15600. 'right' => 'float:right;'
  15601. )
  15602. );
  15603. // @align for hr -----------------------------------------------
  15604. $r['hr@align'] =
  15605. new HTMLPurifier_AttrTransform_EnumToCSS(
  15606. 'align',
  15607. array(
  15608. // we use both text-align and margin because these work
  15609. // for different browsers (IE and Firefox, respectively)
  15610. // and the melange makes for a pretty cross-compatible
  15611. // solution
  15612. 'left' => 'margin-left:0;margin-right:auto;text-align:left;',
  15613. 'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
  15614. 'right' => 'margin-left:auto;margin-right:0;text-align:right;'
  15615. )
  15616. );
  15617. // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
  15618. // {{{
  15619. $align_lookup = array();
  15620. $align_values = array('left', 'right', 'center', 'justify');
  15621. foreach ($align_values as $v) {
  15622. $align_lookup[$v] = "text-align:$v;";
  15623. }
  15624. // }}}
  15625. $r['h1@align'] =
  15626. $r['h2@align'] =
  15627. $r['h3@align'] =
  15628. $r['h4@align'] =
  15629. $r['h5@align'] =
  15630. $r['h6@align'] =
  15631. $r['p@align'] =
  15632. $r['div@align'] =
  15633. new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
  15634. // @bgcolor for table, tr, td, th ---------------------------------
  15635. $r['table@bgcolor'] =
  15636. $r['td@bgcolor'] =
  15637. $r['th@bgcolor'] =
  15638. new HTMLPurifier_AttrTransform_BgColor();
  15639. // @border for img ------------------------------------------------
  15640. $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
  15641. // @clear for br --------------------------------------------------
  15642. $r['br@clear'] =
  15643. new HTMLPurifier_AttrTransform_EnumToCSS(
  15644. 'clear',
  15645. array(
  15646. 'left' => 'clear:left;',
  15647. 'right' => 'clear:right;',
  15648. 'all' => 'clear:both;',
  15649. 'none' => 'clear:none;',
  15650. )
  15651. );
  15652. // @height for td, th ---------------------------------------------
  15653. $r['td@height'] =
  15654. $r['th@height'] =
  15655. new HTMLPurifier_AttrTransform_Length('height');
  15656. // @hspace for img ------------------------------------------------
  15657. $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
  15658. // @noshade for hr ------------------------------------------------
  15659. // this transformation is not precise but often good enough.
  15660. // different browsers use different styles to designate noshade
  15661. $r['hr@noshade'] =
  15662. new HTMLPurifier_AttrTransform_BoolToCSS(
  15663. 'noshade',
  15664. 'color:#808080;background-color:#808080;border:0;'
  15665. );
  15666. // @nowrap for td, th ---------------------------------------------
  15667. $r['td@nowrap'] =
  15668. $r['th@nowrap'] =
  15669. new HTMLPurifier_AttrTransform_BoolToCSS(
  15670. 'nowrap',
  15671. 'white-space:nowrap;'
  15672. );
  15673. // @size for hr --------------------------------------------------
  15674. $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
  15675. // @type for li, ol, ul -------------------------------------------
  15676. // {{{
  15677. $ul_types = array(
  15678. 'disc' => 'list-style-type:disc;',
  15679. 'square' => 'list-style-type:square;',
  15680. 'circle' => 'list-style-type:circle;'
  15681. );
  15682. $ol_types = array(
  15683. '1' => 'list-style-type:decimal;',
  15684. 'i' => 'list-style-type:lower-roman;',
  15685. 'I' => 'list-style-type:upper-roman;',
  15686. 'a' => 'list-style-type:lower-alpha;',
  15687. 'A' => 'list-style-type:upper-alpha;'
  15688. );
  15689. $li_types = $ul_types + $ol_types;
  15690. // }}}
  15691. $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
  15692. $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
  15693. $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
  15694. // @vspace for img ------------------------------------------------
  15695. $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
  15696. // @width for hr, td, th ------------------------------------------
  15697. $r['td@width'] =
  15698. $r['th@width'] =
  15699. $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
  15700. return $r;
  15701. }
  15702. }
  15703. class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15704. {
  15705. /**
  15706. * @type string
  15707. */
  15708. public $name = 'Tidy_Strict';
  15709. /**
  15710. * @type string
  15711. */
  15712. public $defaultLevel = 'light';
  15713. /**
  15714. * @return array
  15715. */
  15716. public function makeFixes()
  15717. {
  15718. $r = parent::makeFixes();
  15719. $r['blockquote#content_model_type'] = 'strictblockquote';
  15720. return $r;
  15721. }
  15722. /**
  15723. * @type bool
  15724. */
  15725. public $defines_child_def = true;
  15726. /**
  15727. * @param HTMLPurifier_ElementDef $def
  15728. * @return HTMLPurifier_ChildDef_StrictBlockquote
  15729. */
  15730. public function getChildDef($def)
  15731. {
  15732. if ($def->content_model_type != 'strictblockquote') {
  15733. return parent::getChildDef($def);
  15734. }
  15735. return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
  15736. }
  15737. }
  15738. class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15739. {
  15740. /**
  15741. * @type string
  15742. */
  15743. public $name = 'Tidy_Transitional';
  15744. /**
  15745. * @type string
  15746. */
  15747. public $defaultLevel = 'heavy';
  15748. }
  15749. class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
  15750. {
  15751. /**
  15752. * @type string
  15753. */
  15754. public $name = 'Tidy_XHTML';
  15755. /**
  15756. * @type string
  15757. */
  15758. public $defaultLevel = 'medium';
  15759. /**
  15760. * @return array
  15761. */
  15762. public function makeFixes()
  15763. {
  15764. $r = array();
  15765. $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
  15766. return $r;
  15767. }
  15768. }
  15769. /**
  15770. * Injector that auto paragraphs text in the root node based on
  15771. * double-spacing.
  15772. * @todo Ensure all states are unit tested, including variations as well.
  15773. * @todo Make a graph of the flow control for this Injector.
  15774. */
  15775. class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
  15776. {
  15777. /**
  15778. * @type string
  15779. */
  15780. public $name = 'AutoParagraph';
  15781. /**
  15782. * @type array
  15783. */
  15784. public $needed = array('p');
  15785. /**
  15786. * @return HTMLPurifier_Token_Start
  15787. */
  15788. private function _pStart()
  15789. {
  15790. $par = new HTMLPurifier_Token_Start('p');
  15791. $par->armor['MakeWellFormed_TagClosedError'] = true;
  15792. return $par;
  15793. }
  15794. /**
  15795. * @param HTMLPurifier_Token_Text $token
  15796. */
  15797. public function handleText(&$token)
  15798. {
  15799. $text = $token->data;
  15800. // Does the current parent allow <p> tags?
  15801. if ($this->allowsElement('p')) {
  15802. if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
  15803. // Note that we have differing behavior when dealing with text
  15804. // in the anonymous root node, or a node inside the document.
  15805. // If the text as a double-newline, the treatment is the same;
  15806. // if it doesn't, see the next if-block if you're in the document.
  15807. $i = $nesting = null;
  15808. if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
  15809. // State 1.1: ... ^ (whitespace, then document end)
  15810. // ----
  15811. // This is a degenerate case
  15812. } else {
  15813. if (!$token->is_whitespace || $this->_isInline($current)) {
  15814. // State 1.2: PAR1
  15815. // ----
  15816. // State 1.3: PAR1\n\nPAR2
  15817. // ------------
  15818. // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
  15819. // ------------
  15820. $token = array($this->_pStart());
  15821. $this->_splitText($text, $token);
  15822. } else {
  15823. // State 1.5: \n<hr />
  15824. // --
  15825. }
  15826. }
  15827. } else {
  15828. // State 2: <div>PAR1... (similar to 1.4)
  15829. // ----
  15830. // We're in an element that allows paragraph tags, but we're not
  15831. // sure if we're going to need them.
  15832. if ($this->_pLookAhead()) {
  15833. // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
  15834. // ----
  15835. // Note: This will always be the first child, since any
  15836. // previous inline element would have triggered this very
  15837. // same routine, and found the double newline. One possible
  15838. // exception would be a comment.
  15839. $token = array($this->_pStart(), $token);
  15840. } else {
  15841. // State 2.2.1: <div>PAR1<div>
  15842. // ----
  15843. // State 2.2.2: <div>PAR1<b>PAR1</b></div>
  15844. // ----
  15845. }
  15846. }
  15847. // Is the current parent a <p> tag?
  15848. } elseif (!empty($this->currentNesting) &&
  15849. $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
  15850. // State 3.1: ...<p>PAR1
  15851. // ----
  15852. // State 3.2: ...<p>PAR1\n\nPAR2
  15853. // ------------
  15854. $token = array();
  15855. $this->_splitText($text, $token);
  15856. // Abort!
  15857. } else {
  15858. // State 4.1: ...<b>PAR1
  15859. // ----
  15860. // State 4.2: ...<b>PAR1\n\nPAR2
  15861. // ------------
  15862. }
  15863. }
  15864. /**
  15865. * @param HTMLPurifier_Token $token
  15866. */
  15867. public function handleElement(&$token)
  15868. {
  15869. // We don't have to check if we're already in a <p> tag for block
  15870. // tokens, because the tag would have been autoclosed by MakeWellFormed.
  15871. if ($this->allowsElement('p')) {
  15872. if (!empty($this->currentNesting)) {
  15873. if ($this->_isInline($token)) {
  15874. // State 1: <div>...<b>
  15875. // ---
  15876. // Check if this token is adjacent to the parent token
  15877. // (seek backwards until token isn't whitespace)
  15878. $i = null;
  15879. $this->backward($i, $prev);
  15880. if (!$prev instanceof HTMLPurifier_Token_Start) {
  15881. // Token wasn't adjacent
  15882. if ($prev instanceof HTMLPurifier_Token_Text &&
  15883. substr($prev->data, -2) === "\n\n"
  15884. ) {
  15885. // State 1.1.4: <div><p>PAR1</p>\n\n<b>
  15886. // ---
  15887. // Quite frankly, this should be handled by splitText
  15888. $token = array($this->_pStart(), $token);
  15889. } else {
  15890. // State 1.1.1: <div><p>PAR1</p><b>
  15891. // ---
  15892. // State 1.1.2: <div><br /><b>
  15893. // ---
  15894. // State 1.1.3: <div>PAR<b>
  15895. // ---
  15896. }
  15897. } else {
  15898. // State 1.2.1: <div><b>
  15899. // ---
  15900. // Lookahead to see if <p> is needed.
  15901. if ($this->_pLookAhead()) {
  15902. // State 1.3.1: <div><b>PAR1\n\nPAR2
  15903. // ---
  15904. $token = array($this->_pStart(), $token);
  15905. } else {
  15906. // State 1.3.2: <div><b>PAR1</b></div>
  15907. // ---
  15908. // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
  15909. // ---
  15910. }
  15911. }
  15912. } else {
  15913. // State 2.3: ...<div>
  15914. // -----
  15915. }
  15916. } else {
  15917. if ($this->_isInline($token)) {
  15918. // State 3.1: <b>
  15919. // ---
  15920. // This is where the {p} tag is inserted, not reflected in
  15921. // inputTokens yet, however.
  15922. $token = array($this->_pStart(), $token);
  15923. } else {
  15924. // State 3.2: <div>
  15925. // -----
  15926. }
  15927. $i = null;
  15928. if ($this->backward($i, $prev)) {
  15929. if (!$prev instanceof HTMLPurifier_Token_Text) {
  15930. // State 3.1.1: ...</p>{p}<b>
  15931. // ---
  15932. // State 3.2.1: ...</p><div>
  15933. // -----
  15934. if (!is_array($token)) {
  15935. $token = array($token);
  15936. }
  15937. array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
  15938. } else {
  15939. // State 3.1.2: ...</p>\n\n{p}<b>
  15940. // ---
  15941. // State 3.2.2: ...</p>\n\n<div>
  15942. // -----
  15943. // Note: PAR<ELEM> cannot occur because PAR would have been
  15944. // wrapped in <p> tags.
  15945. }
  15946. }
  15947. }
  15948. } else {
  15949. // State 2.2: <ul><li>
  15950. // ----
  15951. // State 2.4: <p><b>
  15952. // ---
  15953. }
  15954. }
  15955. /**
  15956. * Splits up a text in paragraph tokens and appends them
  15957. * to the result stream that will replace the original
  15958. * @param string $data String text data that will be processed
  15959. * into paragraphs
  15960. * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
  15961. * tags will be appended onto
  15962. */
  15963. private function _splitText($data, &$result)
  15964. {
  15965. $raw_paragraphs = explode("\n\n", $data);
  15966. $paragraphs = array(); // without empty paragraphs
  15967. $needs_start = false;
  15968. $needs_end = false;
  15969. $c = count($raw_paragraphs);
  15970. if ($c == 1) {
  15971. // There were no double-newlines, abort quickly. In theory this
  15972. // should never happen.
  15973. $result[] = new HTMLPurifier_Token_Text($data);
  15974. return;
  15975. }
  15976. for ($i = 0; $i < $c; $i++) {
  15977. $par = $raw_paragraphs[$i];
  15978. if (trim($par) !== '') {
  15979. $paragraphs[] = $par;
  15980. } else {
  15981. if ($i == 0) {
  15982. // Double newline at the front
  15983. if (empty($result)) {
  15984. // The empty result indicates that the AutoParagraph
  15985. // injector did not add any start paragraph tokens.
  15986. // This means that we have been in a paragraph for
  15987. // a while, and the newline means we should start a new one.
  15988. $result[] = new HTMLPurifier_Token_End('p');
  15989. $result[] = new HTMLPurifier_Token_Text("\n\n");
  15990. // However, the start token should only be added if
  15991. // there is more processing to be done (i.e. there are
  15992. // real paragraphs in here). If there are none, the
  15993. // next start paragraph tag will be handled by the
  15994. // next call to the injector
  15995. $needs_start = true;
  15996. } else {
  15997. // We just started a new paragraph!
  15998. // Reinstate a double-newline for presentation's sake, since
  15999. // it was in the source code.
  16000. array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
  16001. }
  16002. } elseif ($i + 1 == $c) {
  16003. // Double newline at the end
  16004. // There should be a trailing </p> when we're finally done.
  16005. $needs_end = true;
  16006. }
  16007. }
  16008. }
  16009. // Check if this was just a giant blob of whitespace. Move this earlier,
  16010. // perhaps?
  16011. if (empty($paragraphs)) {
  16012. return;
  16013. }
  16014. // Add the start tag indicated by \n\n at the beginning of $data
  16015. if ($needs_start) {
  16016. $result[] = $this->_pStart();
  16017. }
  16018. // Append the paragraphs onto the result
  16019. foreach ($paragraphs as $par) {
  16020. $result[] = new HTMLPurifier_Token_Text($par);
  16021. $result[] = new HTMLPurifier_Token_End('p');
  16022. $result[] = new HTMLPurifier_Token_Text("\n\n");
  16023. $result[] = $this->_pStart();
  16024. }
  16025. // Remove trailing start token; Injector will handle this later if
  16026. // it was indeed needed. This prevents from needing to do a lookahead,
  16027. // at the cost of a lookbehind later.
  16028. array_pop($result);
  16029. // If there is no need for an end tag, remove all of it and let
  16030. // MakeWellFormed close it later.
  16031. if (!$needs_end) {
  16032. array_pop($result); // removes \n\n
  16033. array_pop($result); // removes </p>
  16034. }
  16035. }
  16036. /**
  16037. * Returns true if passed token is inline (and, ergo, allowed in
  16038. * paragraph tags)
  16039. * @param HTMLPurifier_Token $token
  16040. * @return bool
  16041. */
  16042. private function _isInline($token)
  16043. {
  16044. return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
  16045. }
  16046. /**
  16047. * Looks ahead in the token list and determines whether or not we need
  16048. * to insert a <p> tag.
  16049. * @return bool
  16050. */
  16051. private function _pLookAhead()
  16052. {
  16053. if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
  16054. $nesting = 1;
  16055. } else {
  16056. $nesting = 0;
  16057. }
  16058. $ok = false;
  16059. $i = null;
  16060. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  16061. $result = $this->_checkNeedsP($current);
  16062. if ($result !== null) {
  16063. $ok = $result;
  16064. break;
  16065. }
  16066. }
  16067. return $ok;
  16068. }
  16069. /**
  16070. * Determines if a particular token requires an earlier inline token
  16071. * to get a paragraph. This should be used with _forwardUntilEndToken
  16072. * @param HTMLPurifier_Token $current
  16073. * @return bool
  16074. */
  16075. private function _checkNeedsP($current)
  16076. {
  16077. if ($current instanceof HTMLPurifier_Token_Start) {
  16078. if (!$this->_isInline($current)) {
  16079. // <div>PAR1<div>
  16080. // ----
  16081. // Terminate early, since we hit a block element
  16082. return false;
  16083. }
  16084. } elseif ($current instanceof HTMLPurifier_Token_Text) {
  16085. if (strpos($current->data, "\n\n") !== false) {
  16086. // <div>PAR1<b>PAR1\n\nPAR2
  16087. // ----
  16088. return true;
  16089. } else {
  16090. // <div>PAR1<b>PAR1...
  16091. // ----
  16092. }
  16093. }
  16094. return null;
  16095. }
  16096. }
  16097. /**
  16098. * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
  16099. */
  16100. class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
  16101. {
  16102. /**
  16103. * @type string
  16104. */
  16105. public $name = 'DisplayLinkURI';
  16106. /**
  16107. * @type array
  16108. */
  16109. public $needed = array('a');
  16110. /**
  16111. * @param $token
  16112. */
  16113. public function handleElement(&$token)
  16114. {
  16115. }
  16116. /**
  16117. * @param HTMLPurifier_Token $token
  16118. */
  16119. public function handleEnd(&$token)
  16120. {
  16121. if (isset($token->start->attr['href'])) {
  16122. $url = $token->start->attr['href'];
  16123. unset($token->start->attr['href']);
  16124. $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
  16125. } else {
  16126. // nothing to display
  16127. }
  16128. }
  16129. }
  16130. /**
  16131. * Injector that converts http, https and ftp text URLs to actual links.
  16132. */
  16133. class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
  16134. {
  16135. /**
  16136. * @type string
  16137. */
  16138. public $name = 'Linkify';
  16139. /**
  16140. * @type array
  16141. */
  16142. public $needed = array('a' => array('href'));
  16143. /**
  16144. * @param HTMLPurifier_Token $token
  16145. */
  16146. public function handleText(&$token)
  16147. {
  16148. if (!$this->allowsElement('a')) {
  16149. return;
  16150. }
  16151. if (strpos($token->data, '://') === false) {
  16152. // our really quick heuristic failed, abort
  16153. // this may not work so well if we want to match things like
  16154. // "google.com", but then again, most people don't
  16155. return;
  16156. }
  16157. // there is/are URL(s). Let's split the string.
  16158. // We use this regex:
  16159. // https://gist.github.com/gruber/249502
  16160. // but with @cscott's backtracking fix and also
  16161. // the Unicode characters un-Unicodified.
  16162. $bits = preg_split(
  16163. '/\\b((?:[a-z][\\w\\-]+:(?:\\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\\/)(?:[^\\s()<>]|\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\))+(?:\\((?:[^\\s()<>]|(?:\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?\x{00ab}\x{00bb}\x{201c}\x{201d}\x{2018}\x{2019}]))/iu',
  16164. $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  16165. $token = array();
  16166. // $i = index
  16167. // $c = count
  16168. // $l = is link
  16169. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  16170. if (!$l) {
  16171. if ($bits[$i] === '') {
  16172. continue;
  16173. }
  16174. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16175. } else {
  16176. $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
  16177. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16178. $token[] = new HTMLPurifier_Token_End('a');
  16179. }
  16180. }
  16181. }
  16182. }
  16183. /**
  16184. * Injector that converts configuration directive syntax %Namespace.Directive
  16185. * to links
  16186. */
  16187. class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
  16188. {
  16189. /**
  16190. * @type string
  16191. */
  16192. public $name = 'PurifierLinkify';
  16193. /**
  16194. * @type string
  16195. */
  16196. public $docURL;
  16197. /**
  16198. * @type array
  16199. */
  16200. public $needed = array('a' => array('href'));
  16201. /**
  16202. * @param HTMLPurifier_Config $config
  16203. * @param HTMLPurifier_Context $context
  16204. * @return string
  16205. */
  16206. public function prepare($config, $context)
  16207. {
  16208. $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
  16209. return parent::prepare($config, $context);
  16210. }
  16211. /**
  16212. * @param HTMLPurifier_Token $token
  16213. */
  16214. public function handleText(&$token)
  16215. {
  16216. if (!$this->allowsElement('a')) {
  16217. return;
  16218. }
  16219. if (strpos($token->data, '%') === false) {
  16220. return;
  16221. }
  16222. $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  16223. $token = array();
  16224. // $i = index
  16225. // $c = count
  16226. // $l = is link
  16227. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  16228. if (!$l) {
  16229. if ($bits[$i] === '') {
  16230. continue;
  16231. }
  16232. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16233. } else {
  16234. $token[] = new HTMLPurifier_Token_Start(
  16235. 'a',
  16236. array('href' => str_replace('%s', $bits[$i], $this->docURL))
  16237. );
  16238. $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
  16239. $token[] = new HTMLPurifier_Token_End('a');
  16240. }
  16241. }
  16242. }
  16243. }
  16244. class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
  16245. {
  16246. /**
  16247. * @type HTMLPurifier_Context
  16248. */
  16249. private $context;
  16250. /**
  16251. * @type HTMLPurifier_Config
  16252. */
  16253. private $config;
  16254. /**
  16255. * @type HTMLPurifier_AttrValidator
  16256. */
  16257. private $attrValidator;
  16258. /**
  16259. * @type bool
  16260. */
  16261. private $removeNbsp;
  16262. /**
  16263. * @type bool
  16264. */
  16265. private $removeNbspExceptions;
  16266. /**
  16267. * Cached contents of %AutoFormat.RemoveEmpty.Predicate
  16268. * @type array
  16269. */
  16270. private $exclude;
  16271. /**
  16272. * @param HTMLPurifier_Config $config
  16273. * @param HTMLPurifier_Context $context
  16274. * @return void
  16275. */
  16276. public function prepare($config, $context)
  16277. {
  16278. parent::prepare($config, $context);
  16279. $this->config = $config;
  16280. $this->context = $context;
  16281. $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
  16282. $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
  16283. $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
  16284. foreach ($this->exclude as $key => $attrs) {
  16285. if (!is_array($attrs)) {
  16286. // HACK, see HTMLPurifier/Printer/ConfigForm.php
  16287. $this->exclude[$key] = explode(';', $attrs);
  16288. }
  16289. }
  16290. $this->attrValidator = new HTMLPurifier_AttrValidator();
  16291. }
  16292. /**
  16293. * @param HTMLPurifier_Token $token
  16294. */
  16295. public function handleElement(&$token)
  16296. {
  16297. if (!$token instanceof HTMLPurifier_Token_Start) {
  16298. return;
  16299. }
  16300. $next = false;
  16301. $deleted = 1; // the current tag
  16302. for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
  16303. $next = $this->inputZipper->back[$i];
  16304. if ($next instanceof HTMLPurifier_Token_Text) {
  16305. if ($next->is_whitespace) {
  16306. continue;
  16307. }
  16308. if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
  16309. $plain = str_replace("\xC2\xA0", "", $next->data);
  16310. $isWsOrNbsp = $plain === '' || ctype_space($plain);
  16311. if ($isWsOrNbsp) {
  16312. continue;
  16313. }
  16314. }
  16315. }
  16316. break;
  16317. }
  16318. if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
  16319. $this->attrValidator->validateToken($token, $this->config, $this->context);
  16320. $token->armor['ValidateAttributes'] = true;
  16321. if (isset($this->exclude[$token->name])) {
  16322. $r = true;
  16323. foreach ($this->exclude[$token->name] as $elem) {
  16324. if (!isset($token->attr[$elem])) $r = false;
  16325. }
  16326. if ($r) return;
  16327. }
  16328. if (isset($token->attr['id']) || isset($token->attr['name'])) {
  16329. return;
  16330. }
  16331. $token = $deleted + 1;
  16332. for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
  16333. $prev = $this->inputZipper->front[$b];
  16334. if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
  16335. continue;
  16336. }
  16337. break;
  16338. }
  16339. // This is safe because we removed the token that triggered this.
  16340. $this->rewindOffset($b+$deleted);
  16341. return;
  16342. }
  16343. }
  16344. }
  16345. /**
  16346. * Injector that removes spans with no attributes
  16347. */
  16348. class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
  16349. {
  16350. /**
  16351. * @type string
  16352. */
  16353. public $name = 'RemoveSpansWithoutAttributes';
  16354. /**
  16355. * @type array
  16356. */
  16357. public $needed = array('span');
  16358. /**
  16359. * @type HTMLPurifier_AttrValidator
  16360. */
  16361. private $attrValidator;
  16362. /**
  16363. * Used by AttrValidator.
  16364. * @type HTMLPurifier_Config
  16365. */
  16366. private $config;
  16367. /**
  16368. * @type HTMLPurifier_Context
  16369. */
  16370. private $context;
  16371. public function prepare($config, $context)
  16372. {
  16373. $this->attrValidator = new HTMLPurifier_AttrValidator();
  16374. $this->config = $config;
  16375. $this->context = $context;
  16376. return parent::prepare($config, $context);
  16377. }
  16378. /**
  16379. * @param HTMLPurifier_Token $token
  16380. */
  16381. public function handleElement(&$token)
  16382. {
  16383. if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
  16384. return;
  16385. }
  16386. // We need to validate the attributes now since this doesn't normally
  16387. // happen until after MakeWellFormed. If all the attributes are removed
  16388. // the span needs to be removed too.
  16389. $this->attrValidator->validateToken($token, $this->config, $this->context);
  16390. $token->armor['ValidateAttributes'] = true;
  16391. if (!empty($token->attr)) {
  16392. return;
  16393. }
  16394. $nesting = 0;
  16395. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  16396. }
  16397. if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
  16398. // Mark closing span tag for deletion
  16399. $current->markForDeletion = true;
  16400. // Delete open span tag
  16401. $token = false;
  16402. }
  16403. }
  16404. /**
  16405. * @param HTMLPurifier_Token $token
  16406. */
  16407. public function handleEnd(&$token)
  16408. {
  16409. if ($token->markForDeletion) {
  16410. $token = false;
  16411. }
  16412. }
  16413. }
  16414. /**
  16415. * Adds important param elements to inside of object in order to make
  16416. * things safe.
  16417. */
  16418. class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
  16419. {
  16420. /**
  16421. * @type string
  16422. */
  16423. public $name = 'SafeObject';
  16424. /**
  16425. * @type array
  16426. */
  16427. public $needed = array('object', 'param');
  16428. /**
  16429. * @type array
  16430. */
  16431. protected $objectStack = array();
  16432. /**
  16433. * @type array
  16434. */
  16435. protected $paramStack = array();
  16436. /**
  16437. * Keep this synchronized with AttrTransform/SafeParam.php.
  16438. * @type array
  16439. */
  16440. protected $addParam = array(
  16441. 'allowScriptAccess' => 'never',
  16442. 'allowNetworking' => 'internal',
  16443. );
  16444. /**
  16445. * These are all lower-case keys.
  16446. * @type array
  16447. */
  16448. protected $allowedParam = array(
  16449. 'wmode' => true,
  16450. 'movie' => true,
  16451. 'flashvars' => true,
  16452. 'src' => true,
  16453. 'allowfullscreen' => true, // if omitted, assume to be 'false'
  16454. );
  16455. /**
  16456. * @param HTMLPurifier_Config $config
  16457. * @param HTMLPurifier_Context $context
  16458. * @return void
  16459. */
  16460. public function prepare($config, $context)
  16461. {
  16462. parent::prepare($config, $context);
  16463. }
  16464. /**
  16465. * @param HTMLPurifier_Token $token
  16466. */
  16467. public function handleElement(&$token)
  16468. {
  16469. if ($token->name == 'object') {
  16470. $this->objectStack[] = $token;
  16471. $this->paramStack[] = array();
  16472. $new = array($token);
  16473. foreach ($this->addParam as $name => $value) {
  16474. $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
  16475. }
  16476. $token = $new;
  16477. } elseif ($token->name == 'param') {
  16478. $nest = count($this->currentNesting) - 1;
  16479. if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
  16480. $i = count($this->objectStack) - 1;
  16481. if (!isset($token->attr['name'])) {
  16482. $token = false;
  16483. return;
  16484. }
  16485. $n = $token->attr['name'];
  16486. // We need this fix because YouTube doesn't supply a data
  16487. // attribute, which we need if a type is specified. This is
  16488. // *very* Flash specific.
  16489. if (!isset($this->objectStack[$i]->attr['data']) &&
  16490. ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
  16491. ) {
  16492. $this->objectStack[$i]->attr['data'] = $token->attr['value'];
  16493. }
  16494. // Check if the parameter is the correct value but has not
  16495. // already been added
  16496. if (!isset($this->paramStack[$i][$n]) &&
  16497. isset($this->addParam[$n]) &&
  16498. $token->attr['name'] === $this->addParam[$n]) {
  16499. // keep token, and add to param stack
  16500. $this->paramStack[$i][$n] = true;
  16501. } elseif (isset($this->allowedParam[strtolower($n)])) {
  16502. // keep token, don't do anything to it
  16503. // (could possibly check for duplicates here)
  16504. // Note: In principle, parameters should be case sensitive.
  16505. // But it seems they are not really; so accept any case.
  16506. } else {
  16507. $token = false;
  16508. }
  16509. } else {
  16510. // not directly inside an object, DENY!
  16511. $token = false;
  16512. }
  16513. }
  16514. }
  16515. public function handleEnd(&$token)
  16516. {
  16517. // This is the WRONG way of handling the object and param stacks;
  16518. // we should be inserting them directly on the relevant object tokens
  16519. // so that the global stack handling handles it.
  16520. if ($token->name == 'object') {
  16521. array_pop($this->objectStack);
  16522. array_pop($this->paramStack);
  16523. }
  16524. }
  16525. }
  16526. /**
  16527. * Parser that uses PHP 5's DOM extension (part of the core).
  16528. *
  16529. * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
  16530. * It gives us a forgiving HTML parser, which we use to transform the HTML
  16531. * into a DOM, and then into the tokens. It is blazingly fast (for large
  16532. * documents, it performs twenty times faster than
  16533. * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
  16534. *
  16535. * @note Any empty elements will have empty tokens associated with them, even if
  16536. * this is prohibited by the spec. This is cannot be fixed until the spec
  16537. * comes into play.
  16538. *
  16539. * @note PHP's DOM extension does not actually parse any entities, we use
  16540. * our own function to do that.
  16541. *
  16542. * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
  16543. * If this is a huge problem, due to the fact that HTML is hand
  16544. * edited and you are unable to get a parser cache that caches the
  16545. * the output of HTML Purifier while keeping the original HTML lying
  16546. * around, you may want to run Tidy on the resulting output or use
  16547. * HTMLPurifier_DirectLex
  16548. */
  16549. class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
  16550. {
  16551. /**
  16552. * @type HTMLPurifier_TokenFactory
  16553. */
  16554. private $factory;
  16555. public function __construct()
  16556. {
  16557. // setup the factory
  16558. parent::__construct();
  16559. $this->factory = new HTMLPurifier_TokenFactory();
  16560. }
  16561. /**
  16562. * @param string $html
  16563. * @param HTMLPurifier_Config $config
  16564. * @param HTMLPurifier_Context $context
  16565. * @return HTMLPurifier_Token[]
  16566. */
  16567. public function tokenizeHTML($html, $config, $context)
  16568. {
  16569. $html = $this->normalize($html, $config, $context);
  16570. // attempt to armor stray angled brackets that cannot possibly
  16571. // form tags and thus are probably being used as emoticons
  16572. if ($config->get('Core.AggressivelyFixLt')) {
  16573. $char = '[^a-z!\/]';
  16574. $comment = "/<!--(.*?)(-->|\z)/is";
  16575. $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
  16576. do {
  16577. $old = $html;
  16578. $html = preg_replace("/<($char)/i", '&lt;\\1', $html);
  16579. } while ($html !== $old);
  16580. $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
  16581. }
  16582. // preprocess html, essential for UTF-8
  16583. $html = $this->wrapHTML($html, $config, $context);
  16584. $doc = new DOMDocument();
  16585. $doc->encoding = 'UTF-8'; // theoretically, the above has this covered
  16586. $options = 0;
  16587. if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {
  16588. $options |= LIBXML_PARSEHUGE;
  16589. }
  16590. set_error_handler(array($this, 'muteErrorHandler'));
  16591. // loadHTML() fails on PHP 5.3 when second parameter is given
  16592. if ($options) {
  16593. $doc->loadHTML($html, $options);
  16594. } else {
  16595. $doc->loadHTML($html);
  16596. }
  16597. restore_error_handler();
  16598. $body = $doc->getElementsByTagName('html')->item(0)-> // <html>
  16599. getElementsByTagName('body')->item(0); // <body>
  16600. $div = $body->getElementsByTagName('div')->item(0); // <div>
  16601. $tokens = array();
  16602. $this->tokenizeDOM($div, $tokens, $config);
  16603. // If the div has a sibling, that means we tripped across
  16604. // a premature </div> tag. So remove the div we parsed,
  16605. // and then tokenize the rest of body. We can't tokenize
  16606. // the sibling directly as we'll lose the tags in that case.
  16607. if ($div->nextSibling) {
  16608. $body->removeChild($div);
  16609. $this->tokenizeDOM($body, $tokens, $config);
  16610. }
  16611. return $tokens;
  16612. }
  16613. /**
  16614. * Iterative function that tokenizes a node, putting it into an accumulator.
  16615. * To iterate is human, to recurse divine - L. Peter Deutsch
  16616. * @param DOMNode $node DOMNode to be tokenized.
  16617. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16618. * @return HTMLPurifier_Token of node appended to previously passed tokens.
  16619. */
  16620. protected function tokenizeDOM($node, &$tokens, $config)
  16621. {
  16622. $level = 0;
  16623. $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  16624. $closingNodes = array();
  16625. do {
  16626. while (!$nodes[$level]->isEmpty()) {
  16627. $node = $nodes[$level]->shift(); // FIFO
  16628. $collect = $level > 0 ? true : false;
  16629. $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
  16630. if ($needEndingTag) {
  16631. $closingNodes[$level][] = $node;
  16632. }
  16633. if ($node->childNodes && $node->childNodes->length) {
  16634. $level++;
  16635. $nodes[$level] = new HTMLPurifier_Queue();
  16636. foreach ($node->childNodes as $childNode) {
  16637. $nodes[$level]->push($childNode);
  16638. }
  16639. }
  16640. }
  16641. $level--;
  16642. if ($level && isset($closingNodes[$level])) {
  16643. while ($node = array_pop($closingNodes[$level])) {
  16644. $this->createEndNode($node, $tokens);
  16645. }
  16646. }
  16647. } while ($level > 0);
  16648. }
  16649. /**
  16650. * Portably retrieve the tag name of a node; deals with older versions
  16651. * of libxml like 2.7.6
  16652. * @param DOMNode $node
  16653. */
  16654. protected function getTagName($node)
  16655. {
  16656. if (isset($node->tagName)) {
  16657. return $node->tagName;
  16658. } else if (isset($node->nodeName)) {
  16659. return $node->nodeName;
  16660. } else if (isset($node->localName)) {
  16661. return $node->localName;
  16662. }
  16663. return null;
  16664. }
  16665. /**
  16666. * Portably retrieve the data of a node; deals with older versions
  16667. * of libxml like 2.7.6
  16668. * @param DOMNode $node
  16669. */
  16670. protected function getData($node)
  16671. {
  16672. if (isset($node->data)) {
  16673. return $node->data;
  16674. } else if (isset($node->nodeValue)) {
  16675. return $node->nodeValue;
  16676. } else if (isset($node->textContent)) {
  16677. return $node->textContent;
  16678. }
  16679. return null;
  16680. }
  16681. /**
  16682. * @param DOMNode $node DOMNode to be tokenized.
  16683. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16684. * @param bool $collect Says whether or start and close are collected, set to
  16685. * false at first recursion because it's the implicit DIV
  16686. * tag you're dealing with.
  16687. * @return bool if the token needs an endtoken
  16688. * @todo data and tagName properties don't seem to exist in DOMNode?
  16689. */
  16690. protected function createStartNode($node, &$tokens, $collect, $config)
  16691. {
  16692. // intercept non element nodes. WE MUST catch all of them,
  16693. // but we're not getting the character reference nodes because
  16694. // those should have been preprocessed
  16695. if ($node->nodeType === XML_TEXT_NODE) {
  16696. $data = $this->getData($node); // Handle variable data property
  16697. if ($data !== null) {
  16698. $tokens[] = $this->factory->createText($data);
  16699. }
  16700. return false;
  16701. } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
  16702. // undo libxml's special treatment of <script> and <style> tags
  16703. $last = end($tokens);
  16704. $data = $node->data;
  16705. // (note $node->tagname is already normalized)
  16706. if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
  16707. $new_data = trim($data);
  16708. if (substr($new_data, 0, 4) === '<!--') {
  16709. $data = substr($new_data, 4);
  16710. if (substr($data, -3) === '-->') {
  16711. $data = substr($data, 0, -3);
  16712. } else {
  16713. // Highly suspicious! Not sure what to do...
  16714. }
  16715. }
  16716. }
  16717. $tokens[] = $this->factory->createText($this->parseText($data, $config));
  16718. return false;
  16719. } elseif ($node->nodeType === XML_COMMENT_NODE) {
  16720. // this is code is only invoked for comments in script/style in versions
  16721. // of libxml pre-2.6.28 (regular comments, of course, are still
  16722. // handled regularly)
  16723. $tokens[] = $this->factory->createComment($node->data);
  16724. return false;
  16725. } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
  16726. // not-well tested: there may be other nodes we have to grab
  16727. return false;
  16728. }
  16729. $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
  16730. $tag_name = $this->getTagName($node); // Handle variable tagName property
  16731. if (empty($tag_name)) {
  16732. return (bool) $node->childNodes->length;
  16733. }
  16734. // We still have to make sure that the element actually IS empty
  16735. if (!$node->childNodes->length) {
  16736. if ($collect) {
  16737. $tokens[] = $this->factory->createEmpty($tag_name, $attr);
  16738. }
  16739. return false;
  16740. } else {
  16741. if ($collect) {
  16742. $tokens[] = $this->factory->createStart($tag_name, $attr);
  16743. }
  16744. return true;
  16745. }
  16746. }
  16747. /**
  16748. * @param DOMNode $node
  16749. * @param HTMLPurifier_Token[] $tokens
  16750. */
  16751. protected function createEndNode($node, &$tokens)
  16752. {
  16753. $tag_name = $this->getTagName($node); // Handle variable tagName property
  16754. $tokens[] = $this->factory->createEnd($tag_name);
  16755. }
  16756. /**
  16757. * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
  16758. *
  16759. * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
  16760. * @return array Associative array of attributes.
  16761. */
  16762. protected function transformAttrToAssoc($node_map)
  16763. {
  16764. // NamedNodeMap is documented very well, so we're using undocumented
  16765. // features, namely, the fact that it implements Iterator and
  16766. // has a ->length attribute
  16767. if ($node_map->length === 0) {
  16768. return array();
  16769. }
  16770. $array = array();
  16771. foreach ($node_map as $attr) {
  16772. $array[$attr->name] = $attr->value;
  16773. }
  16774. return $array;
  16775. }
  16776. /**
  16777. * An error handler that mutes all errors
  16778. * @param int $errno
  16779. * @param string $errstr
  16780. */
  16781. public function muteErrorHandler($errno, $errstr)
  16782. {
  16783. }
  16784. /**
  16785. * Callback function for undoing escaping of stray angled brackets
  16786. * in comments
  16787. * @param array $matches
  16788. * @return string
  16789. */
  16790. public function callbackUndoCommentSubst($matches)
  16791. {
  16792. return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
  16793. }
  16794. /**
  16795. * Callback function that entity-izes ampersands in comments so that
  16796. * callbackUndoCommentSubst doesn't clobber them
  16797. * @param array $matches
  16798. * @return string
  16799. */
  16800. public function callbackArmorCommentEntities($matches)
  16801. {
  16802. return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
  16803. }
  16804. /**
  16805. * Wraps an HTML fragment in the necessary HTML
  16806. * @param string $html
  16807. * @param HTMLPurifier_Config $config
  16808. * @param HTMLPurifier_Context $context
  16809. * @return string
  16810. */
  16811. protected function wrapHTML($html, $config, $context, $use_div = true)
  16812. {
  16813. $def = $config->getDefinition('HTML');
  16814. $ret = '';
  16815. if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
  16816. $ret .= '<!DOCTYPE html ';
  16817. if (!empty($def->doctype->dtdPublic)) {
  16818. $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
  16819. }
  16820. if (!empty($def->doctype->dtdSystem)) {
  16821. $ret .= '"' . $def->doctype->dtdSystem . '" ';
  16822. }
  16823. $ret .= '>';
  16824. }
  16825. $ret .= '<html><head>';
  16826. $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  16827. // No protection if $html contains a stray </div>!
  16828. $ret .= '</head><body>';
  16829. if ($use_div) $ret .= '<div>';
  16830. $ret .= $html;
  16831. if ($use_div) $ret .= '</div>';
  16832. $ret .= '</body></html>';
  16833. return $ret;
  16834. }
  16835. }
  16836. /**
  16837. * Our in-house implementation of a parser.
  16838. *
  16839. * A pure PHP parser, DirectLex has absolutely no dependencies, making
  16840. * it a reasonably good default for PHP4. Written with efficiency in mind,
  16841. * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
  16842. * pales in comparison to HTMLPurifier_Lexer_DOMLex.
  16843. *
  16844. * @todo Reread XML spec and document differences.
  16845. */
  16846. class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
  16847. {
  16848. /**
  16849. * @type bool
  16850. */
  16851. public $tracksLineNumbers = true;
  16852. /**
  16853. * Whitespace characters for str(c)spn.
  16854. * @type string
  16855. */
  16856. protected $_whitespace = "\x20\x09\x0D\x0A";
  16857. /**
  16858. * Callback function for script CDATA fudge
  16859. * @param array $matches, in form of array(opening tag, contents, closing tag)
  16860. * @return string
  16861. */
  16862. protected function scriptCallback($matches)
  16863. {
  16864. return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
  16865. }
  16866. /**
  16867. * @param String $html
  16868. * @param HTMLPurifier_Config $config
  16869. * @param HTMLPurifier_Context $context
  16870. * @return array|HTMLPurifier_Token[]
  16871. */
  16872. public function tokenizeHTML($html, $config, $context)
  16873. {
  16874. // special normalization for script tags without any armor
  16875. // our "armor" heurstic is a < sign any number of whitespaces after
  16876. // the first script tag
  16877. if ($config->get('HTML.Trusted')) {
  16878. $html = preg_replace_callback(
  16879. '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
  16880. array($this, 'scriptCallback'),
  16881. $html
  16882. );
  16883. }
  16884. $html = $this->normalize($html, $config, $context);
  16885. $cursor = 0; // our location in the text
  16886. $inside_tag = false; // whether or not we're parsing the inside of a tag
  16887. $array = array(); // result array
  16888. // This is also treated to mean maintain *column* numbers too
  16889. $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
  16890. if ($maintain_line_numbers === null) {
  16891. // automatically determine line numbering by checking
  16892. // if error collection is on
  16893. $maintain_line_numbers = $config->get('Core.CollectErrors');
  16894. }
  16895. if ($maintain_line_numbers) {
  16896. $current_line = 1;
  16897. $current_col = 0;
  16898. $length = strlen($html);
  16899. } else {
  16900. $current_line = false;
  16901. $current_col = false;
  16902. $length = false;
  16903. }
  16904. $context->register('CurrentLine', $current_line);
  16905. $context->register('CurrentCol', $current_col);
  16906. $nl = "\n";
  16907. // how often to manually recalculate. This will ALWAYS be right,
  16908. // but it's pretty wasteful. Set to 0 to turn off
  16909. $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
  16910. $e = false;
  16911. if ($config->get('Core.CollectErrors')) {
  16912. $e =& $context->get('ErrorCollector');
  16913. }
  16914. // for testing synchronization
  16915. $loops = 0;
  16916. while (++$loops) {
  16917. // $cursor is either at the start of a token, or inside of
  16918. // a tag (i.e. there was a < immediately before it), as indicated
  16919. // by $inside_tag
  16920. if ($maintain_line_numbers) {
  16921. // $rcursor, however, is always at the start of a token.
  16922. $rcursor = $cursor - (int)$inside_tag;
  16923. // Column number is cheap, so we calculate it every round.
  16924. // We're interested at the *end* of the newline string, so
  16925. // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
  16926. // from our "rcursor" position.
  16927. $nl_pos = strrpos($html, $nl, $rcursor - $length);
  16928. $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
  16929. // recalculate lines
  16930. if ($synchronize_interval && // synchronization is on
  16931. $cursor > 0 && // cursor is further than zero
  16932. $loops % $synchronize_interval === 0) { // time to synchronize!
  16933. $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
  16934. }
  16935. }
  16936. $position_next_lt = strpos($html, '<', $cursor);
  16937. $position_next_gt = strpos($html, '>', $cursor);
  16938. // triggers on "<b>asdf</b>" but not "asdf <b></b>"
  16939. // special case to set up context
  16940. if ($position_next_lt === $cursor) {
  16941. $inside_tag = true;
  16942. $cursor++;
  16943. }
  16944. if (!$inside_tag && $position_next_lt !== false) {
  16945. // We are not inside tag and there still is another tag to parse
  16946. $token = new
  16947. HTMLPurifier_Token_Text(
  16948. $this->parseText(
  16949. substr(
  16950. $html,
  16951. $cursor,
  16952. $position_next_lt - $cursor
  16953. ), $config
  16954. )
  16955. );
  16956. if ($maintain_line_numbers) {
  16957. $token->rawPosition($current_line, $current_col);
  16958. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
  16959. }
  16960. $array[] = $token;
  16961. $cursor = $position_next_lt + 1;
  16962. $inside_tag = true;
  16963. continue;
  16964. } elseif (!$inside_tag) {
  16965. // We are not inside tag but there are no more tags
  16966. // If we're already at the end, break
  16967. if ($cursor === strlen($html)) {
  16968. break;
  16969. }
  16970. // Create Text of rest of string
  16971. $token = new
  16972. HTMLPurifier_Token_Text(
  16973. $this->parseText(
  16974. substr(
  16975. $html,
  16976. $cursor
  16977. ), $config
  16978. )
  16979. );
  16980. if ($maintain_line_numbers) {
  16981. $token->rawPosition($current_line, $current_col);
  16982. }
  16983. $array[] = $token;
  16984. break;
  16985. } elseif ($inside_tag && $position_next_gt !== false) {
  16986. // We are in tag and it is well formed
  16987. // Grab the internals of the tag
  16988. $strlen_segment = $position_next_gt - $cursor;
  16989. if ($strlen_segment < 1) {
  16990. // there's nothing to process!
  16991. $token = new HTMLPurifier_Token_Text('<');
  16992. $cursor++;
  16993. continue;
  16994. }
  16995. $segment = substr($html, $cursor, $strlen_segment);
  16996. if ($segment === false) {
  16997. // somehow, we attempted to access beyond the end of
  16998. // the string, defense-in-depth, reported by Nate Abele
  16999. break;
  17000. }
  17001. // Check if it's a comment
  17002. if (substr($segment, 0, 3) === '!--') {
  17003. // re-determine segment length, looking for -->
  17004. $position_comment_end = strpos($html, '-->', $cursor);
  17005. if ($position_comment_end === false) {
  17006. // uh oh, we have a comment that extends to
  17007. // infinity. Can't be helped: set comment
  17008. // end position to end of string
  17009. if ($e) {
  17010. $e->send(E_WARNING, 'Lexer: Unclosed comment');
  17011. }
  17012. $position_comment_end = strlen($html);
  17013. $end = true;
  17014. } else {
  17015. $end = false;
  17016. }
  17017. $strlen_segment = $position_comment_end - $cursor;
  17018. $segment = substr($html, $cursor, $strlen_segment);
  17019. $token = new
  17020. HTMLPurifier_Token_Comment(
  17021. substr(
  17022. $segment,
  17023. 3,
  17024. $strlen_segment - 3
  17025. )
  17026. );
  17027. if ($maintain_line_numbers) {
  17028. $token->rawPosition($current_line, $current_col);
  17029. $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
  17030. }
  17031. $array[] = $token;
  17032. $cursor = $end ? $position_comment_end : $position_comment_end + 3;
  17033. $inside_tag = false;
  17034. continue;
  17035. }
  17036. // Check if it's an end tag
  17037. $is_end_tag = (strpos($segment, '/') === 0);
  17038. if ($is_end_tag) {
  17039. $type = substr($segment, 1);
  17040. $token = new HTMLPurifier_Token_End($type);
  17041. if ($maintain_line_numbers) {
  17042. $token->rawPosition($current_line, $current_col);
  17043. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17044. }
  17045. $array[] = $token;
  17046. $inside_tag = false;
  17047. $cursor = $position_next_gt + 1;
  17048. continue;
  17049. }
  17050. // Check leading character is alnum, if not, we may
  17051. // have accidently grabbed an emoticon. Translate into
  17052. // text and go our merry way
  17053. if (!ctype_alpha($segment[0])) {
  17054. // XML: $segment[0] !== '_' && $segment[0] !== ':'
  17055. if ($e) {
  17056. $e->send(E_NOTICE, 'Lexer: Unescaped lt');
  17057. }
  17058. $token = new HTMLPurifier_Token_Text('<');
  17059. if ($maintain_line_numbers) {
  17060. $token->rawPosition($current_line, $current_col);
  17061. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17062. }
  17063. $array[] = $token;
  17064. $inside_tag = false;
  17065. continue;
  17066. }
  17067. // Check if it is explicitly self closing, if so, remove
  17068. // trailing slash. Remember, we could have a tag like <br>, so
  17069. // any later token processing scripts must convert improperly
  17070. // classified EmptyTags from StartTags.
  17071. $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
  17072. if ($is_self_closing) {
  17073. $strlen_segment--;
  17074. $segment = substr($segment, 0, $strlen_segment);
  17075. }
  17076. // Check if there are any attributes
  17077. $position_first_space = strcspn($segment, $this->_whitespace);
  17078. if ($position_first_space >= $strlen_segment) {
  17079. if ($is_self_closing) {
  17080. $token = new HTMLPurifier_Token_Empty($segment);
  17081. } else {
  17082. $token = new HTMLPurifier_Token_Start($segment);
  17083. }
  17084. if ($maintain_line_numbers) {
  17085. $token->rawPosition($current_line, $current_col);
  17086. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17087. }
  17088. $array[] = $token;
  17089. $inside_tag = false;
  17090. $cursor = $position_next_gt + 1;
  17091. continue;
  17092. }
  17093. // Grab out all the data
  17094. $type = substr($segment, 0, $position_first_space);
  17095. $attribute_string =
  17096. trim(
  17097. substr(
  17098. $segment,
  17099. $position_first_space
  17100. )
  17101. );
  17102. if ($attribute_string) {
  17103. $attr = $this->parseAttributeString(
  17104. $attribute_string,
  17105. $config,
  17106. $context
  17107. );
  17108. } else {
  17109. $attr = array();
  17110. }
  17111. if ($is_self_closing) {
  17112. $token = new HTMLPurifier_Token_Empty($type, $attr);
  17113. } else {
  17114. $token = new HTMLPurifier_Token_Start($type, $attr);
  17115. }
  17116. if ($maintain_line_numbers) {
  17117. $token->rawPosition($current_line, $current_col);
  17118. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17119. }
  17120. $array[] = $token;
  17121. $cursor = $position_next_gt + 1;
  17122. $inside_tag = false;
  17123. continue;
  17124. } else {
  17125. // inside tag, but there's no ending > sign
  17126. if ($e) {
  17127. $e->send(E_WARNING, 'Lexer: Missing gt');
  17128. }
  17129. $token = new
  17130. HTMLPurifier_Token_Text(
  17131. '<' .
  17132. $this->parseText(
  17133. substr($html, $cursor), $config
  17134. )
  17135. );
  17136. if ($maintain_line_numbers) {
  17137. $token->rawPosition($current_line, $current_col);
  17138. }
  17139. // no cursor scroll? Hmm...
  17140. $array[] = $token;
  17141. break;
  17142. }
  17143. break;
  17144. }
  17145. $context->destroy('CurrentLine');
  17146. $context->destroy('CurrentCol');
  17147. return $array;
  17148. }
  17149. /**
  17150. * PHP 5.0.x compatible substr_count that implements offset and length
  17151. * @param string $haystack
  17152. * @param string $needle
  17153. * @param int $offset
  17154. * @param int $length
  17155. * @return int
  17156. */
  17157. protected function substrCount($haystack, $needle, $offset, $length)
  17158. {
  17159. static $oldVersion;
  17160. if ($oldVersion === null) {
  17161. $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
  17162. }
  17163. if ($oldVersion) {
  17164. $haystack = substr($haystack, $offset, $length);
  17165. return substr_count($haystack, $needle);
  17166. } else {
  17167. return substr_count($haystack, $needle, $offset, $length);
  17168. }
  17169. }
  17170. /**
  17171. * Takes the inside of an HTML tag and makes an assoc array of attributes.
  17172. *
  17173. * @param string $string Inside of tag excluding name.
  17174. * @param HTMLPurifier_Config $config
  17175. * @param HTMLPurifier_Context $context
  17176. * @return array Assoc array of attributes.
  17177. */
  17178. public function parseAttributeString($string, $config, $context)
  17179. {
  17180. $string = (string)$string; // quick typecast
  17181. if ($string == '') {
  17182. return array();
  17183. } // no attributes
  17184. $e = false;
  17185. if ($config->get('Core.CollectErrors')) {
  17186. $e =& $context->get('ErrorCollector');
  17187. }
  17188. // let's see if we can abort as quickly as possible
  17189. // one equal sign, no spaces => one attribute
  17190. $num_equal = substr_count($string, '=');
  17191. $has_space = strpos($string, ' ');
  17192. if ($num_equal === 0 && !$has_space) {
  17193. // bool attribute
  17194. return array($string => $string);
  17195. } elseif ($num_equal === 1 && !$has_space) {
  17196. // only one attribute
  17197. list($key, $quoted_value) = explode('=', $string);
  17198. $quoted_value = trim($quoted_value);
  17199. if (!$key) {
  17200. if ($e) {
  17201. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17202. }
  17203. return array();
  17204. }
  17205. if (!$quoted_value) {
  17206. return array($key => '');
  17207. }
  17208. $first_char = @$quoted_value[0];
  17209. $last_char = @$quoted_value[strlen($quoted_value) - 1];
  17210. $same_quote = ($first_char == $last_char);
  17211. $open_quote = ($first_char == '"' || $first_char == "'");
  17212. if ($same_quote && $open_quote) {
  17213. // well behaved
  17214. $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
  17215. } else {
  17216. // not well behaved
  17217. if ($open_quote) {
  17218. if ($e) {
  17219. $e->send(E_ERROR, 'Lexer: Missing end quote');
  17220. }
  17221. $value = substr($quoted_value, 1);
  17222. } else {
  17223. $value = $quoted_value;
  17224. }
  17225. }
  17226. if ($value === false) {
  17227. $value = '';
  17228. }
  17229. return array($key => $this->parseAttr($value, $config));
  17230. }
  17231. // setup loop environment
  17232. $array = array(); // return assoc array of attributes
  17233. $cursor = 0; // current position in string (moves forward)
  17234. $size = strlen($string); // size of the string (stays the same)
  17235. // if we have unquoted attributes, the parser expects a terminating
  17236. // space, so let's guarantee that there's always a terminating space.
  17237. $string .= ' ';
  17238. $old_cursor = -1;
  17239. while ($cursor < $size) {
  17240. if ($old_cursor >= $cursor) {
  17241. throw new Exception("Infinite loop detected");
  17242. }
  17243. $old_cursor = $cursor;
  17244. $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
  17245. // grab the key
  17246. $key_begin = $cursor; //we're currently at the start of the key
  17247. // scroll past all characters that are the key (not whitespace or =)
  17248. $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
  17249. $key_end = $cursor; // now at the end of the key
  17250. $key = substr($string, $key_begin, $key_end - $key_begin);
  17251. if (!$key) {
  17252. if ($e) {
  17253. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17254. }
  17255. $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
  17256. continue; // empty key
  17257. }
  17258. // scroll past all whitespace
  17259. $cursor += strspn($string, $this->_whitespace, $cursor);
  17260. if ($cursor >= $size) {
  17261. $array[$key] = $key;
  17262. break;
  17263. }
  17264. // if the next character is an equal sign, we've got a regular
  17265. // pair, otherwise, it's a bool attribute
  17266. $first_char = @$string[$cursor];
  17267. if ($first_char == '=') {
  17268. // key="value"
  17269. $cursor++;
  17270. $cursor += strspn($string, $this->_whitespace, $cursor);
  17271. if ($cursor === false) {
  17272. $array[$key] = '';
  17273. break;
  17274. }
  17275. // we might be in front of a quote right now
  17276. $char = @$string[$cursor];
  17277. if ($char == '"' || $char == "'") {
  17278. // it's quoted, end bound is $char
  17279. $cursor++;
  17280. $value_begin = $cursor;
  17281. $cursor = strpos($string, $char, $cursor);
  17282. $value_end = $cursor;
  17283. } else {
  17284. // it's not quoted, end bound is whitespace
  17285. $value_begin = $cursor;
  17286. $cursor += strcspn($string, $this->_whitespace, $cursor);
  17287. $value_end = $cursor;
  17288. }
  17289. // we reached a premature end
  17290. if ($cursor === false) {
  17291. $cursor = $size;
  17292. $value_end = $cursor;
  17293. }
  17294. $value = substr($string, $value_begin, $value_end - $value_begin);
  17295. if ($value === false) {
  17296. $value = '';
  17297. }
  17298. $array[$key] = $this->parseAttr($value, $config);
  17299. $cursor++;
  17300. } else {
  17301. // boolattr
  17302. if ($key !== '') {
  17303. $array[$key] = $key;
  17304. } else {
  17305. // purely theoretical
  17306. if ($e) {
  17307. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17308. }
  17309. }
  17310. }
  17311. }
  17312. return $array;
  17313. }
  17314. }
  17315. /**
  17316. * Concrete comment node class.
  17317. */
  17318. class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
  17319. {
  17320. /**
  17321. * Character data within comment.
  17322. * @type string
  17323. */
  17324. public $data;
  17325. /**
  17326. * @type bool
  17327. */
  17328. public $is_whitespace = true;
  17329. /**
  17330. * Transparent constructor.
  17331. *
  17332. * @param string $data String comment data.
  17333. * @param int $line
  17334. * @param int $col
  17335. */
  17336. public function __construct($data, $line = null, $col = null)
  17337. {
  17338. $this->data = $data;
  17339. $this->line = $line;
  17340. $this->col = $col;
  17341. }
  17342. public function toTokenPair() {
  17343. return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
  17344. }
  17345. }
  17346. /**
  17347. * Concrete element node class.
  17348. */
  17349. class HTMLPurifier_Node_Element extends HTMLPurifier_Node
  17350. {
  17351. /**
  17352. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  17353. *
  17354. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  17355. * be lower-casing them, but these tokens cater to HTML tags, which are
  17356. * insensitive.
  17357. * @type string
  17358. */
  17359. public $name;
  17360. /**
  17361. * Associative array of the node's attributes.
  17362. * @type array
  17363. */
  17364. public $attr = array();
  17365. /**
  17366. * List of child elements.
  17367. * @type array
  17368. */
  17369. public $children = array();
  17370. /**
  17371. * Does this use the <a></a> form or the </a> form, i.e.
  17372. * is it a pair of start/end tokens or an empty token.
  17373. * @bool
  17374. */
  17375. public $empty = false;
  17376. public $endCol = null, $endLine = null, $endArmor = array();
  17377. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
  17378. $this->name = $name;
  17379. $this->attr = $attr;
  17380. $this->line = $line;
  17381. $this->col = $col;
  17382. $this->armor = $armor;
  17383. }
  17384. public function toTokenPair() {
  17385. // XXX inefficiency here, normalization is not necessary
  17386. if ($this->empty) {
  17387. return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
  17388. } else {
  17389. $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
  17390. $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
  17391. //$end->start = $start;
  17392. return array($start, $end);
  17393. }
  17394. }
  17395. }
  17396. /**
  17397. * Concrete text token class.
  17398. *
  17399. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  17400. * character data (from the CDATA sections). Internally, their
  17401. * data is parsed with all entities expanded. Surprisingly, the text token
  17402. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  17403. * in permissible child nodes.
  17404. */
  17405. class HTMLPurifier_Node_Text extends HTMLPurifier_Node
  17406. {
  17407. /**
  17408. * PCDATA tag name compatible with DTD, see
  17409. * HTMLPurifier_ChildDef_Custom for details.
  17410. * @type string
  17411. */
  17412. public $name = '#PCDATA';
  17413. /**
  17414. * @type string
  17415. */
  17416. public $data;
  17417. /**< Parsed character data of text. */
  17418. /**
  17419. * @type bool
  17420. */
  17421. public $is_whitespace;
  17422. /**< Bool indicating if node is whitespace. */
  17423. /**
  17424. * Constructor, accepts data and determines if it is whitespace.
  17425. * @param string $data String parsed character data.
  17426. * @param int $line
  17427. * @param int $col
  17428. */
  17429. public function __construct($data, $is_whitespace, $line = null, $col = null)
  17430. {
  17431. $this->data = $data;
  17432. $this->is_whitespace = $is_whitespace;
  17433. $this->line = $line;
  17434. $this->col = $col;
  17435. }
  17436. public function toTokenPair() {
  17437. return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
  17438. }
  17439. }
  17440. /**
  17441. * Composite strategy that runs multiple strategies on tokens.
  17442. */
  17443. abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
  17444. {
  17445. /**
  17446. * List of strategies to run tokens through.
  17447. * @type HTMLPurifier_Strategy[]
  17448. */
  17449. protected $strategies = array();
  17450. /**
  17451. * @param HTMLPurifier_Token[] $tokens
  17452. * @param HTMLPurifier_Config $config
  17453. * @param HTMLPurifier_Context $context
  17454. * @return HTMLPurifier_Token[]
  17455. */
  17456. public function execute($tokens, $config, $context)
  17457. {
  17458. foreach ($this->strategies as $strategy) {
  17459. $tokens = $strategy->execute($tokens, $config, $context);
  17460. }
  17461. return $tokens;
  17462. }
  17463. }
  17464. /**
  17465. * Core strategy composed of the big four strategies.
  17466. */
  17467. class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
  17468. {
  17469. public function __construct()
  17470. {
  17471. $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
  17472. $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
  17473. $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
  17474. $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
  17475. }
  17476. }
  17477. /**
  17478. * Takes a well formed list of tokens and fixes their nesting.
  17479. *
  17480. * HTML elements dictate which elements are allowed to be their children,
  17481. * for example, you can't have a p tag in a span tag. Other elements have
  17482. * much more rigorous definitions: tables, for instance, require a specific
  17483. * order for their elements. There are also constraints not expressible by
  17484. * document type definitions, such as the chameleon nature of ins/del
  17485. * tags and global child exclusions.
  17486. *
  17487. * The first major objective of this strategy is to iterate through all
  17488. * the nodes and determine whether or not their children conform to the
  17489. * element's definition. If they do not, the child definition may
  17490. * optionally supply an amended list of elements that is valid or
  17491. * require that the entire node be deleted (and the previous node
  17492. * rescanned).
  17493. *
  17494. * The second objective is to ensure that explicitly excluded elements of
  17495. * an element do not appear in its children. Code that accomplishes this
  17496. * task is pervasive through the strategy, though the two are distinct tasks
  17497. * and could, theoretically, be seperated (although it's not recommended).
  17498. *
  17499. * @note Whether or not unrecognized children are silently dropped or
  17500. * translated into text depends on the child definitions.
  17501. *
  17502. * @todo Enable nodes to be bubbled out of the structure. This is
  17503. * easier with our new algorithm.
  17504. */
  17505. class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
  17506. {
  17507. /**
  17508. * @param HTMLPurifier_Token[] $tokens
  17509. * @param HTMLPurifier_Config $config
  17510. * @param HTMLPurifier_Context $context
  17511. * @return array|HTMLPurifier_Token[]
  17512. */
  17513. public function execute($tokens, $config, $context)
  17514. {
  17515. //####################################################################//
  17516. // Pre-processing
  17517. // O(n) pass to convert to a tree, so that we can efficiently
  17518. // refer to substrings
  17519. $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
  17520. // get a copy of the HTML definition
  17521. $definition = $config->getHTMLDefinition();
  17522. $excludes_enabled = !$config->get('Core.DisableExcludes');
  17523. // setup the context variable 'IsInline', for chameleon processing
  17524. // is 'false' when we are not inline, 'true' when it must always
  17525. // be inline, and an integer when it is inline for a certain
  17526. // branch of the document tree
  17527. $is_inline = $definition->info_parent_def->descendants_are_inline;
  17528. $context->register('IsInline', $is_inline);
  17529. // setup error collector
  17530. $e =& $context->get('ErrorCollector', true);
  17531. //####################################################################//
  17532. // Loop initialization
  17533. // stack that contains all elements that are excluded
  17534. // it is organized by parent elements, similar to $stack,
  17535. // but it is only populated when an element with exclusions is
  17536. // processed, i.e. there won't be empty exclusions.
  17537. $exclude_stack = array($definition->info_parent_def->excludes);
  17538. // variable that contains the start token while we are processing
  17539. // nodes. This enables error reporting to do its job
  17540. $node = $top_node;
  17541. // dummy token
  17542. list($token, $d) = $node->toTokenPair();
  17543. $context->register('CurrentNode', $node);
  17544. $context->register('CurrentToken', $token);
  17545. //####################################################################//
  17546. // Loop
  17547. // We need to implement a post-order traversal iteratively, to
  17548. // avoid running into stack space limits. This is pretty tricky
  17549. // to reason about, so we just manually stack-ify the recursive
  17550. // variant:
  17551. //
  17552. // function f($node) {
  17553. // foreach ($node->children as $child) {
  17554. // f($child);
  17555. // }
  17556. // validate($node);
  17557. // }
  17558. //
  17559. // Thus, we will represent a stack frame as array($node,
  17560. // $is_inline, stack of children)
  17561. // e.g. array_reverse($node->children) - already processed
  17562. // children.
  17563. $parent_def = $definition->info_parent_def;
  17564. $stack = array(
  17565. array($top_node,
  17566. $parent_def->descendants_are_inline,
  17567. $parent_def->excludes, // exclusions
  17568. 0)
  17569. );
  17570. while (!empty($stack)) {
  17571. list($node, $is_inline, $excludes, $ix) = array_pop($stack);
  17572. // recursive call
  17573. $go = false;
  17574. $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
  17575. while (isset($node->children[$ix])) {
  17576. $child = $node->children[$ix++];
  17577. if ($child instanceof HTMLPurifier_Node_Element) {
  17578. $go = true;
  17579. $stack[] = array($node, $is_inline, $excludes, $ix);
  17580. $stack[] = array($child,
  17581. // ToDo: I don't think it matters if it's def or
  17582. // child_def, but double check this...
  17583. $is_inline || $def->descendants_are_inline,
  17584. empty($def->excludes) ? $excludes
  17585. : array_merge($excludes, $def->excludes),
  17586. 0);
  17587. break;
  17588. }
  17589. };
  17590. if ($go) continue;
  17591. list($token, $d) = $node->toTokenPair();
  17592. // base case
  17593. if ($excludes_enabled && isset($excludes[$node->name])) {
  17594. $node->dead = true;
  17595. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
  17596. } else {
  17597. // XXX I suppose it would be slightly more efficient to
  17598. // avoid the allocation here and have children
  17599. // strategies handle it
  17600. $children = array();
  17601. foreach ($node->children as $child) {
  17602. if (!$child->dead) $children[] = $child;
  17603. }
  17604. $result = $def->child->validateChildren($children, $config, $context);
  17605. if ($result === true) {
  17606. // nop
  17607. $node->children = $children;
  17608. } elseif ($result === false) {
  17609. $node->dead = true;
  17610. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
  17611. } else {
  17612. $node->children = $result;
  17613. if ($e) {
  17614. // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
  17615. if (empty($result) && !empty($children)) {
  17616. $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
  17617. } else if ($result != $children) {
  17618. $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
  17619. }
  17620. }
  17621. }
  17622. }
  17623. }
  17624. //####################################################################//
  17625. // Post-processing
  17626. // remove context variables
  17627. $context->destroy('IsInline');
  17628. $context->destroy('CurrentNode');
  17629. $context->destroy('CurrentToken');
  17630. //####################################################################//
  17631. // Return
  17632. return HTMLPurifier_Arborize::flatten($node, $config, $context);
  17633. }
  17634. }
  17635. /**
  17636. * Takes tokens makes them well-formed (balance end tags, etc.)
  17637. *
  17638. * Specification of the armor attributes this strategy uses:
  17639. *
  17640. * - MakeWellFormed_TagClosedError: This armor field is used to
  17641. * suppress tag closed errors for certain tokens [TagClosedSuppress],
  17642. * in particular, if a tag was generated automatically by HTML
  17643. * Purifier, we may rely on our infrastructure to close it for us
  17644. * and shouldn't report an error to the user [TagClosedAuto].
  17645. */
  17646. class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
  17647. {
  17648. /**
  17649. * Array stream of tokens being processed.
  17650. * @type HTMLPurifier_Token[]
  17651. */
  17652. protected $tokens;
  17653. /**
  17654. * Current token.
  17655. * @type HTMLPurifier_Token
  17656. */
  17657. protected $token;
  17658. /**
  17659. * Zipper managing the true state.
  17660. * @type HTMLPurifier_Zipper
  17661. */
  17662. protected $zipper;
  17663. /**
  17664. * Current nesting of elements.
  17665. * @type array
  17666. */
  17667. protected $stack;
  17668. /**
  17669. * Injectors active in this stream processing.
  17670. * @type HTMLPurifier_Injector[]
  17671. */
  17672. protected $injectors;
  17673. /**
  17674. * Current instance of HTMLPurifier_Config.
  17675. * @type HTMLPurifier_Config
  17676. */
  17677. protected $config;
  17678. /**
  17679. * Current instance of HTMLPurifier_Context.
  17680. * @type HTMLPurifier_Context
  17681. */
  17682. protected $context;
  17683. /**
  17684. * @param HTMLPurifier_Token[] $tokens
  17685. * @param HTMLPurifier_Config $config
  17686. * @param HTMLPurifier_Context $context
  17687. * @return HTMLPurifier_Token[]
  17688. * @throws HTMLPurifier_Exception
  17689. */
  17690. public function execute($tokens, $config, $context)
  17691. {
  17692. $definition = $config->getHTMLDefinition();
  17693. // local variables
  17694. $generator = new HTMLPurifier_Generator($config, $context);
  17695. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  17696. // used for autoclose early abortion
  17697. $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
  17698. $e = $context->get('ErrorCollector', true);
  17699. $i = false; // injector index
  17700. list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
  17701. if ($token === NULL) {
  17702. return array();
  17703. }
  17704. $reprocess = false; // whether or not to reprocess the same token
  17705. $stack = array();
  17706. // member variables
  17707. $this->stack =& $stack;
  17708. $this->tokens =& $tokens;
  17709. $this->token =& $token;
  17710. $this->zipper =& $zipper;
  17711. $this->config = $config;
  17712. $this->context = $context;
  17713. // context variables
  17714. $context->register('CurrentNesting', $stack);
  17715. $context->register('InputZipper', $zipper);
  17716. $context->register('CurrentToken', $token);
  17717. // -- begin INJECTOR --
  17718. $this->injectors = array();
  17719. $injectors = $config->getBatch('AutoFormat');
  17720. $def_injectors = $definition->info_injector;
  17721. $custom_injectors = $injectors['Custom'];
  17722. unset($injectors['Custom']); // special case
  17723. foreach ($injectors as $injector => $b) {
  17724. // XXX: Fix with a legitimate lookup table of enabled filters
  17725. if (strpos($injector, '.') !== false) {
  17726. continue;
  17727. }
  17728. $injector = "HTMLPurifier_Injector_$injector";
  17729. if (!$b) {
  17730. continue;
  17731. }
  17732. $this->injectors[] = new $injector;
  17733. }
  17734. foreach ($def_injectors as $injector) {
  17735. // assumed to be objects
  17736. $this->injectors[] = $injector;
  17737. }
  17738. foreach ($custom_injectors as $injector) {
  17739. if (!$injector) {
  17740. continue;
  17741. }
  17742. if (is_string($injector)) {
  17743. $injector = "HTMLPurifier_Injector_$injector";
  17744. $injector = new $injector;
  17745. }
  17746. $this->injectors[] = $injector;
  17747. }
  17748. // give the injectors references to the definition and context
  17749. // variables for performance reasons
  17750. foreach ($this->injectors as $ix => $injector) {
  17751. $error = $injector->prepare($config, $context);
  17752. if (!$error) {
  17753. continue;
  17754. }
  17755. array_splice($this->injectors, $ix, 1); // rm the injector
  17756. trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
  17757. }
  17758. // -- end INJECTOR --
  17759. // a note on reprocessing:
  17760. // In order to reduce code duplication, whenever some code needs
  17761. // to make HTML changes in order to make things "correct", the
  17762. // new HTML gets sent through the purifier, regardless of its
  17763. // status. This means that if we add a start token, because it
  17764. // was totally necessary, we don't have to update nesting; we just
  17765. // punt ($reprocess = true; continue;) and it does that for us.
  17766. // isset is in loop because $tokens size changes during loop exec
  17767. for (;;
  17768. // only increment if we don't need to reprocess
  17769. $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
  17770. // check for a rewind
  17771. if (is_int($i)) {
  17772. // possibility: disable rewinding if the current token has a
  17773. // rewind set on it already. This would offer protection from
  17774. // infinite loop, but might hinder some advanced rewinding.
  17775. $rewind_offset = $this->injectors[$i]->getRewindOffset();
  17776. if (is_int($rewind_offset)) {
  17777. for ($j = 0; $j < $rewind_offset; $j++) {
  17778. if (empty($zipper->front)) break;
  17779. $token = $zipper->prev($token);
  17780. // indicate that other injectors should not process this token,
  17781. // but we need to reprocess it. See Note [Injector skips]
  17782. unset($token->skip[$i]);
  17783. $token->rewind = $i;
  17784. if ($token instanceof HTMLPurifier_Token_Start) {
  17785. array_pop($this->stack);
  17786. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17787. $this->stack[] = $token->start;
  17788. }
  17789. }
  17790. }
  17791. $i = false;
  17792. }
  17793. // handle case of document end
  17794. if ($token === NULL) {
  17795. // kill processing if stack is empty
  17796. if (empty($this->stack)) {
  17797. break;
  17798. }
  17799. // peek
  17800. $top_nesting = array_pop($this->stack);
  17801. $this->stack[] = $top_nesting;
  17802. // send error [TagClosedSuppress]
  17803. if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
  17804. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
  17805. }
  17806. // append, don't splice, since this is the end
  17807. $token = new HTMLPurifier_Token_End($top_nesting->name);
  17808. // punt!
  17809. $reprocess = true;
  17810. continue;
  17811. }
  17812. //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);
  17813. //flush();
  17814. // quick-check: if it's not a tag, no need to process
  17815. if (empty($token->is_tag)) {
  17816. if ($token instanceof HTMLPurifier_Token_Text) {
  17817. foreach ($this->injectors as $i => $injector) {
  17818. if (isset($token->skip[$i])) {
  17819. // See Note [Injector skips]
  17820. continue;
  17821. }
  17822. if ($token->rewind !== null && $token->rewind !== $i) {
  17823. continue;
  17824. }
  17825. // XXX fuckup
  17826. $r = $token;
  17827. $injector->handleText($r);
  17828. $token = $this->processToken($r, $i);
  17829. $reprocess = true;
  17830. break;
  17831. }
  17832. }
  17833. // another possibility is a comment
  17834. continue;
  17835. }
  17836. if (isset($definition->info[$token->name])) {
  17837. $type = $definition->info[$token->name]->child->type;
  17838. } else {
  17839. $type = false; // Type is unknown, treat accordingly
  17840. }
  17841. // quick tag checks: anything that's *not* an end tag
  17842. $ok = false;
  17843. if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
  17844. // claims to be a start tag but is empty
  17845. $token = new HTMLPurifier_Token_Empty(
  17846. $token->name,
  17847. $token->attr,
  17848. $token->line,
  17849. $token->col,
  17850. $token->armor
  17851. );
  17852. $ok = true;
  17853. } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
  17854. // claims to be empty but really is a start tag
  17855. // NB: this assignment is required
  17856. $old_token = $token;
  17857. $token = new HTMLPurifier_Token_End($token->name);
  17858. $token = $this->insertBefore(
  17859. new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
  17860. );
  17861. // punt (since we had to modify the input stream in a non-trivial way)
  17862. $reprocess = true;
  17863. continue;
  17864. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  17865. // real empty token
  17866. $ok = true;
  17867. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  17868. // start tag
  17869. // ...unless they also have to close their parent
  17870. if (!empty($this->stack)) {
  17871. // Performance note: you might think that it's rather
  17872. // inefficient, recalculating the autoclose information
  17873. // for every tag that a token closes (since when we
  17874. // do an autoclose, we push a new token into the
  17875. // stream and then /process/ that, before
  17876. // re-processing this token.) But this is
  17877. // necessary, because an injector can make an
  17878. // arbitrary transformations to the autoclosing
  17879. // tokens we introduce, so things may have changed
  17880. // in the meantime. Also, doing the inefficient thing is
  17881. // "easy" to reason about (for certain perverse definitions
  17882. // of "easy")
  17883. $parent = array_pop($this->stack);
  17884. $this->stack[] = $parent;
  17885. $parent_def = null;
  17886. $parent_elements = null;
  17887. $autoclose = false;
  17888. if (isset($definition->info[$parent->name])) {
  17889. $parent_def = $definition->info[$parent->name];
  17890. $parent_elements = $parent_def->child->getAllowedElements($config);
  17891. $autoclose = !isset($parent_elements[$token->name]);
  17892. }
  17893. if ($autoclose && $definition->info[$token->name]->wrap) {
  17894. // Check if an element can be wrapped by another
  17895. // element to make it valid in a context (for
  17896. // example, <ul><ul> needs a <li> in between)
  17897. $wrapname = $definition->info[$token->name]->wrap;
  17898. $wrapdef = $definition->info[$wrapname];
  17899. $elements = $wrapdef->child->getAllowedElements($config);
  17900. if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
  17901. $newtoken = new HTMLPurifier_Token_Start($wrapname);
  17902. $token = $this->insertBefore($newtoken);
  17903. $reprocess = true;
  17904. continue;
  17905. }
  17906. }
  17907. $carryover = false;
  17908. if ($autoclose && $parent_def->formatting) {
  17909. $carryover = true;
  17910. }
  17911. if ($autoclose) {
  17912. // check if this autoclose is doomed to fail
  17913. // (this rechecks $parent, which his harmless)
  17914. $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
  17915. if (!$autoclose_ok) {
  17916. foreach ($this->stack as $ancestor) {
  17917. $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
  17918. if (isset($elements[$token->name])) {
  17919. $autoclose_ok = true;
  17920. break;
  17921. }
  17922. if ($definition->info[$token->name]->wrap) {
  17923. $wrapname = $definition->info[$token->name]->wrap;
  17924. $wrapdef = $definition->info[$wrapname];
  17925. $wrap_elements = $wrapdef->child->getAllowedElements($config);
  17926. if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
  17927. $autoclose_ok = true;
  17928. break;
  17929. }
  17930. }
  17931. }
  17932. }
  17933. if ($autoclose_ok) {
  17934. // errors need to be updated
  17935. $new_token = new HTMLPurifier_Token_End($parent->name);
  17936. $new_token->start = $parent;
  17937. // [TagClosedSuppress]
  17938. if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
  17939. if (!$carryover) {
  17940. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
  17941. } else {
  17942. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
  17943. }
  17944. }
  17945. if ($carryover) {
  17946. $element = clone $parent;
  17947. // [TagClosedAuto]
  17948. $element->armor['MakeWellFormed_TagClosedError'] = true;
  17949. $element->carryover = true;
  17950. $token = $this->processToken(array($new_token, $token, $element));
  17951. } else {
  17952. $token = $this->insertBefore($new_token);
  17953. }
  17954. } else {
  17955. $token = $this->remove();
  17956. }
  17957. $reprocess = true;
  17958. continue;
  17959. }
  17960. }
  17961. $ok = true;
  17962. }
  17963. if ($ok) {
  17964. foreach ($this->injectors as $i => $injector) {
  17965. if (isset($token->skip[$i])) {
  17966. // See Note [Injector skips]
  17967. continue;
  17968. }
  17969. if ($token->rewind !== null && $token->rewind !== $i) {
  17970. continue;
  17971. }
  17972. $r = $token;
  17973. $injector->handleElement($r);
  17974. $token = $this->processToken($r, $i);
  17975. $reprocess = true;
  17976. break;
  17977. }
  17978. if (!$reprocess) {
  17979. // ah, nothing interesting happened; do normal processing
  17980. if ($token instanceof HTMLPurifier_Token_Start) {
  17981. $this->stack[] = $token;
  17982. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17983. throw new HTMLPurifier_Exception(
  17984. 'Improper handling of end tag in start code; possible error in MakeWellFormed'
  17985. );
  17986. }
  17987. }
  17988. continue;
  17989. }
  17990. // sanity check: we should be dealing with a closing tag
  17991. if (!$token instanceof HTMLPurifier_Token_End) {
  17992. throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
  17993. }
  17994. // make sure that we have something open
  17995. if (empty($this->stack)) {
  17996. if ($escape_invalid_tags) {
  17997. if ($e) {
  17998. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
  17999. }
  18000. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  18001. } else {
  18002. if ($e) {
  18003. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
  18004. }
  18005. $token = $this->remove();
  18006. }
  18007. $reprocess = true;
  18008. continue;
  18009. }
  18010. // first, check for the simplest case: everything closes neatly.
  18011. // Eventually, everything passes through here; if there are problems
  18012. // we modify the input stream accordingly and then punt, so that
  18013. // the tokens get processed again.
  18014. $current_parent = array_pop($this->stack);
  18015. if ($current_parent->name == $token->name) {
  18016. $token->start = $current_parent;
  18017. foreach ($this->injectors as $i => $injector) {
  18018. if (isset($token->skip[$i])) {
  18019. // See Note [Injector skips]
  18020. continue;
  18021. }
  18022. if ($token->rewind !== null && $token->rewind !== $i) {
  18023. continue;
  18024. }
  18025. $r = $token;
  18026. $injector->handleEnd($r);
  18027. $token = $this->processToken($r, $i);
  18028. $this->stack[] = $current_parent;
  18029. $reprocess = true;
  18030. break;
  18031. }
  18032. continue;
  18033. }
  18034. // okay, so we're trying to close the wrong tag
  18035. // undo the pop previous pop
  18036. $this->stack[] = $current_parent;
  18037. // scroll back the entire nest, trying to find our tag.
  18038. // (feature could be to specify how far you'd like to go)
  18039. $size = count($this->stack);
  18040. // -2 because -1 is the last element, but we already checked that
  18041. $skipped_tags = false;
  18042. for ($j = $size - 2; $j >= 0; $j--) {
  18043. if ($this->stack[$j]->name == $token->name) {
  18044. $skipped_tags = array_slice($this->stack, $j);
  18045. break;
  18046. }
  18047. }
  18048. // we didn't find the tag, so remove
  18049. if ($skipped_tags === false) {
  18050. if ($escape_invalid_tags) {
  18051. if ($e) {
  18052. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
  18053. }
  18054. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  18055. } else {
  18056. if ($e) {
  18057. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
  18058. }
  18059. $token = $this->remove();
  18060. }
  18061. $reprocess = true;
  18062. continue;
  18063. }
  18064. // do errors, in REVERSE $j order: a,b,c with </a></b></c>
  18065. $c = count($skipped_tags);
  18066. if ($e) {
  18067. for ($j = $c - 1; $j > 0; $j--) {
  18068. // notice we exclude $j == 0, i.e. the current ending tag, from
  18069. // the errors... [TagClosedSuppress]
  18070. if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
  18071. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
  18072. }
  18073. }
  18074. }
  18075. // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
  18076. $replace = array($token);
  18077. for ($j = 1; $j < $c; $j++) {
  18078. // ...as well as from the insertions
  18079. $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
  18080. $new_token->start = $skipped_tags[$j];
  18081. array_unshift($replace, $new_token);
  18082. if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
  18083. // [TagClosedAuto]
  18084. $element = clone $skipped_tags[$j];
  18085. $element->carryover = true;
  18086. $element->armor['MakeWellFormed_TagClosedError'] = true;
  18087. $replace[] = $element;
  18088. }
  18089. }
  18090. $token = $this->processToken($replace);
  18091. $reprocess = true;
  18092. continue;
  18093. }
  18094. $context->destroy('CurrentToken');
  18095. $context->destroy('CurrentNesting');
  18096. $context->destroy('InputZipper');
  18097. unset($this->injectors, $this->stack, $this->tokens);
  18098. return $zipper->toArray($token);
  18099. }
  18100. /**
  18101. * Processes arbitrary token values for complicated substitution patterns.
  18102. * In general:
  18103. *
  18104. * If $token is an array, it is a list of tokens to substitute for the
  18105. * current token. These tokens then get individually processed. If there
  18106. * is a leading integer in the list, that integer determines how many
  18107. * tokens from the stream should be removed.
  18108. *
  18109. * If $token is a regular token, it is swapped with the current token.
  18110. *
  18111. * If $token is false, the current token is deleted.
  18112. *
  18113. * If $token is an integer, that number of tokens (with the first token
  18114. * being the current one) will be deleted.
  18115. *
  18116. * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
  18117. * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
  18118. * this is not an injector related operation.
  18119. * @throws HTMLPurifier_Exception
  18120. */
  18121. protected function processToken($token, $injector = -1)
  18122. {
  18123. // Zend OpCache miscompiles $token = array($token), so
  18124. // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108
  18125. // normalize forms of token
  18126. if (is_object($token)) {
  18127. $tmp = $token;
  18128. $token = array(1, $tmp);
  18129. }
  18130. if (is_int($token)) {
  18131. $tmp = $token;
  18132. $token = array($tmp);
  18133. }
  18134. if ($token === false) {
  18135. $token = array(1);
  18136. }
  18137. if (!is_array($token)) {
  18138. throw new HTMLPurifier_Exception('Invalid token type from injector');
  18139. }
  18140. if (!is_int($token[0])) {
  18141. array_unshift($token, 1);
  18142. }
  18143. if ($token[0] === 0) {
  18144. throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
  18145. }
  18146. // $token is now an array with the following form:
  18147. // array(number nodes to delete, new node 1, new node 2, ...)
  18148. $delete = array_shift($token);
  18149. list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
  18150. if ($injector > -1) {
  18151. // See Note [Injector skips]
  18152. // Determine appropriate skips. Here's what the code does:
  18153. // *If* we deleted one or more tokens, copy the skips
  18154. // of those tokens into the skips of the new tokens (in $token).
  18155. // Also, mark the newly inserted tokens as having come from
  18156. // $injector.
  18157. $oldskip = isset($old[0]) ? $old[0]->skip : array();
  18158. foreach ($token as $object) {
  18159. $object->skip = $oldskip;
  18160. $object->skip[$injector] = true;
  18161. }
  18162. }
  18163. return $r;
  18164. }
  18165. /**
  18166. * Inserts a token before the current token. Cursor now points to
  18167. * this token. You must reprocess after this.
  18168. * @param HTMLPurifier_Token $token
  18169. */
  18170. private function insertBefore($token)
  18171. {
  18172. // NB not $this->zipper->insertBefore(), due to positioning
  18173. // differences
  18174. $splice = $this->zipper->splice($this->token, 0, array($token));
  18175. return $splice[1];
  18176. }
  18177. /**
  18178. * Removes current token. Cursor now points to new token occupying previously
  18179. * occupied space. You must reprocess after this.
  18180. */
  18181. private function remove()
  18182. {
  18183. return $this->zipper->delete();
  18184. }
  18185. }
  18186. // Note [Injector skips]
  18187. // ~~~~~~~~~~~~~~~~~~~~~
  18188. // When I originally designed this class, the idea behind the 'skip'
  18189. // property of HTMLPurifier_Token was to help avoid infinite loops
  18190. // in injector processing. For example, suppose you wrote an injector
  18191. // that bolded swear words. Naively, you might write it so that
  18192. // whenever you saw ****, you replaced it with <strong>****</strong>.
  18193. //
  18194. // When this happens, we will reprocess all of the tokens with the
  18195. // other injectors. Now there is an opportunity for infinite loop:
  18196. // if we rerun the swear-word injector on these tokens, we might
  18197. // see **** and then reprocess again to get
  18198. // <strong><strong>****</strong></strong> ad infinitum.
  18199. //
  18200. // Thus, the idea of a skip is that once we process a token with
  18201. // an injector, we mark all of those tokens as having "come from"
  18202. // the injector, and we never run the injector again on these
  18203. // tokens.
  18204. //
  18205. // There were two more complications, however:
  18206. //
  18207. // - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
  18208. // you had <b><i></i></b>, after you removed the <i></i>, you
  18209. // really would like this injector to go back and reprocess
  18210. // the <b> tag, discovering that it is now empty and can be
  18211. // removed. So we reintroduced the possibility of infinite looping
  18212. // by adding a "rewind" function, which let you go back to an
  18213. // earlier point in the token stream and reprocess it with injectors.
  18214. // Needless to say, we need to UN-skip the token so it gets
  18215. // reprocessed.
  18216. //
  18217. // - Suppose that you successfuly process a token, replace it with
  18218. // one with your skip mark, but now another injector wants to
  18219. // process the skipped token with another token. Should you continue
  18220. // to skip that new token, or reprocess it? If you reprocess,
  18221. // you can end up with an infinite loop where one injector converts
  18222. // <a> to <b>, and then another injector converts it back. So
  18223. // we inherit the skips, but for some reason, I thought that we
  18224. // should inherit the skip from the first token of the token
  18225. // that we deleted. Why? Well, it seems to work OK.
  18226. //
  18227. // If I were to redesign this functionality, I would absolutely not
  18228. // go about doing it this way: the semantics are just not very well
  18229. // defined, and in any case you probably wanted to operate on trees,
  18230. // not token streams.
  18231. /**
  18232. * Removes all unrecognized tags from the list of tokens.
  18233. *
  18234. * This strategy iterates through all the tokens and removes unrecognized
  18235. * tokens. If a token is not recognized but a TagTransform is defined for
  18236. * that element, the element will be transformed accordingly.
  18237. */
  18238. class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
  18239. {
  18240. /**
  18241. * @param HTMLPurifier_Token[] $tokens
  18242. * @param HTMLPurifier_Config $config
  18243. * @param HTMLPurifier_Context $context
  18244. * @return array|HTMLPurifier_Token[]
  18245. */
  18246. public function execute($tokens, $config, $context)
  18247. {
  18248. $definition = $config->getHTMLDefinition();
  18249. $generator = new HTMLPurifier_Generator($config, $context);
  18250. $result = array();
  18251. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  18252. $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
  18253. // currently only used to determine if comments should be kept
  18254. $trusted = $config->get('HTML.Trusted');
  18255. $comment_lookup = $config->get('HTML.AllowedComments');
  18256. $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
  18257. $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
  18258. $remove_script_contents = $config->get('Core.RemoveScriptContents');
  18259. $hidden_elements = $config->get('Core.HiddenElements');
  18260. // remove script contents compatibility
  18261. if ($remove_script_contents === true) {
  18262. $hidden_elements['script'] = true;
  18263. } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
  18264. unset($hidden_elements['script']);
  18265. }
  18266. $attr_validator = new HTMLPurifier_AttrValidator();
  18267. // removes tokens until it reaches a closing tag with its value
  18268. $remove_until = false;
  18269. // converts comments into text tokens when this is equal to a tag name
  18270. $textify_comments = false;
  18271. $token = false;
  18272. $context->register('CurrentToken', $token);
  18273. $e = false;
  18274. if ($config->get('Core.CollectErrors')) {
  18275. $e =& $context->get('ErrorCollector');
  18276. }
  18277. foreach ($tokens as $token) {
  18278. if ($remove_until) {
  18279. if (empty($token->is_tag) || $token->name !== $remove_until) {
  18280. continue;
  18281. }
  18282. }
  18283. if (!empty($token->is_tag)) {
  18284. // DEFINITION CALL
  18285. // before any processing, try to transform the element
  18286. if (isset($definition->info_tag_transform[$token->name])) {
  18287. $original_name = $token->name;
  18288. // there is a transformation for this tag
  18289. // DEFINITION CALL
  18290. $token = $definition->
  18291. info_tag_transform[$token->name]->transform($token, $config, $context);
  18292. if ($e) {
  18293. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
  18294. }
  18295. }
  18296. if (isset($definition->info[$token->name])) {
  18297. // mostly everything's good, but
  18298. // we need to make sure required attributes are in order
  18299. if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
  18300. $definition->info[$token->name]->required_attr &&
  18301. ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
  18302. ) {
  18303. $attr_validator->validateToken($token, $config, $context);
  18304. $ok = true;
  18305. foreach ($definition->info[$token->name]->required_attr as $name) {
  18306. if (!isset($token->attr[$name])) {
  18307. $ok = false;
  18308. break;
  18309. }
  18310. }
  18311. if (!$ok) {
  18312. if ($e) {
  18313. $e->send(
  18314. E_ERROR,
  18315. 'Strategy_RemoveForeignElements: Missing required attribute',
  18316. $name
  18317. );
  18318. }
  18319. continue;
  18320. }
  18321. $token->armor['ValidateAttributes'] = true;
  18322. }
  18323. if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
  18324. $textify_comments = $token->name;
  18325. } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
  18326. $textify_comments = false;
  18327. }
  18328. } elseif ($escape_invalid_tags) {
  18329. // invalid tag, generate HTML representation and insert in
  18330. if ($e) {
  18331. $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
  18332. }
  18333. $token = new HTMLPurifier_Token_Text(
  18334. $generator->generateFromToken($token)
  18335. );
  18336. } else {
  18337. // check if we need to destroy all of the tag's children
  18338. // CAN BE GENERICIZED
  18339. if (isset($hidden_elements[$token->name])) {
  18340. if ($token instanceof HTMLPurifier_Token_Start) {
  18341. $remove_until = $token->name;
  18342. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  18343. // do nothing: we're still looking
  18344. } else {
  18345. $remove_until = false;
  18346. }
  18347. if ($e) {
  18348. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
  18349. }
  18350. } else {
  18351. if ($e) {
  18352. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
  18353. }
  18354. }
  18355. continue;
  18356. }
  18357. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  18358. // textify comments in script tags when they are allowed
  18359. if ($textify_comments !== false) {
  18360. $data = $token->data;
  18361. $token = new HTMLPurifier_Token_Text($data);
  18362. } elseif ($trusted || $check_comments) {
  18363. // always cleanup comments
  18364. $trailing_hyphen = false;
  18365. if ($e) {
  18366. // perform check whether or not there's a trailing hyphen
  18367. if (substr($token->data, -1) == '-') {
  18368. $trailing_hyphen = true;
  18369. }
  18370. }
  18371. $token->data = rtrim($token->data, '-');
  18372. $found_double_hyphen = false;
  18373. while (strpos($token->data, '--') !== false) {
  18374. $found_double_hyphen = true;
  18375. $token->data = str_replace('--', '-', $token->data);
  18376. }
  18377. if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
  18378. ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
  18379. // OK good
  18380. if ($e) {
  18381. if ($trailing_hyphen) {
  18382. $e->send(
  18383. E_NOTICE,
  18384. 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
  18385. );
  18386. }
  18387. if ($found_double_hyphen) {
  18388. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
  18389. }
  18390. }
  18391. } else {
  18392. if ($e) {
  18393. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  18394. }
  18395. continue;
  18396. }
  18397. } else {
  18398. // strip comments
  18399. if ($e) {
  18400. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  18401. }
  18402. continue;
  18403. }
  18404. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  18405. } else {
  18406. continue;
  18407. }
  18408. $result[] = $token;
  18409. }
  18410. if ($remove_until && $e) {
  18411. // we removed tokens until the end, throw error
  18412. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
  18413. }
  18414. $context->destroy('CurrentToken');
  18415. return $result;
  18416. }
  18417. }
  18418. /**
  18419. * Validate all attributes in the tokens.
  18420. */
  18421. class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
  18422. {
  18423. /**
  18424. * @param HTMLPurifier_Token[] $tokens
  18425. * @param HTMLPurifier_Config $config
  18426. * @param HTMLPurifier_Context $context
  18427. * @return HTMLPurifier_Token[]
  18428. */
  18429. public function execute($tokens, $config, $context)
  18430. {
  18431. // setup validator
  18432. $validator = new HTMLPurifier_AttrValidator();
  18433. $token = false;
  18434. $context->register('CurrentToken', $token);
  18435. foreach ($tokens as $key => $token) {
  18436. // only process tokens that have attributes,
  18437. // namely start and empty tags
  18438. if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
  18439. continue;
  18440. }
  18441. // skip tokens that are armored
  18442. if (!empty($token->armor['ValidateAttributes'])) {
  18443. continue;
  18444. }
  18445. // note that we have no facilities here for removing tokens
  18446. $validator->validateToken($token, $config, $context);
  18447. }
  18448. $context->destroy('CurrentToken');
  18449. return $tokens;
  18450. }
  18451. }
  18452. /**
  18453. * Transforms FONT tags to the proper form (SPAN with CSS styling)
  18454. *
  18455. * This transformation takes the three proprietary attributes of FONT and
  18456. * transforms them into their corresponding CSS attributes. These are color,
  18457. * face, and size.
  18458. *
  18459. * @note Size is an interesting case because it doesn't map cleanly to CSS.
  18460. * Thanks to
  18461. * http://style.cleverchimp.com/font_size_intervals/altintervals.html
  18462. * for reasonable mappings.
  18463. * @warning This doesn't work completely correctly; specifically, this
  18464. * TagTransform operates before well-formedness is enforced, so
  18465. * the "active formatting elements" algorithm doesn't get applied.
  18466. */
  18467. class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
  18468. {
  18469. /**
  18470. * @type string
  18471. */
  18472. public $transform_to = 'span';
  18473. /**
  18474. * @type array
  18475. */
  18476. protected $_size_lookup = array(
  18477. '0' => 'xx-small',
  18478. '1' => 'xx-small',
  18479. '2' => 'small',
  18480. '3' => 'medium',
  18481. '4' => 'large',
  18482. '5' => 'x-large',
  18483. '6' => 'xx-large',
  18484. '7' => '300%',
  18485. '-1' => 'smaller',
  18486. '-2' => '60%',
  18487. '+1' => 'larger',
  18488. '+2' => '150%',
  18489. '+3' => '200%',
  18490. '+4' => '300%'
  18491. );
  18492. /**
  18493. * @param HTMLPurifier_Token_Tag $tag
  18494. * @param HTMLPurifier_Config $config
  18495. * @param HTMLPurifier_Context $context
  18496. * @return HTMLPurifier_Token_End|string
  18497. */
  18498. public function transform($tag, $config, $context)
  18499. {
  18500. if ($tag instanceof HTMLPurifier_Token_End) {
  18501. $new_tag = clone $tag;
  18502. $new_tag->name = $this->transform_to;
  18503. return $new_tag;
  18504. }
  18505. $attr = $tag->attr;
  18506. $prepend_style = '';
  18507. // handle color transform
  18508. if (isset($attr['color'])) {
  18509. $prepend_style .= 'color:' . $attr['color'] . ';';
  18510. unset($attr['color']);
  18511. }
  18512. // handle face transform
  18513. if (isset($attr['face'])) {
  18514. $prepend_style .= 'font-family:' . $attr['face'] . ';';
  18515. unset($attr['face']);
  18516. }
  18517. // handle size transform
  18518. if (isset($attr['size'])) {
  18519. // normalize large numbers
  18520. if ($attr['size'] !== '') {
  18521. if ($attr['size'][0] == '+' || $attr['size'][0] == '-') {
  18522. $size = (int)$attr['size'];
  18523. if ($size < -2) {
  18524. $attr['size'] = '-2';
  18525. }
  18526. if ($size > 4) {
  18527. $attr['size'] = '+4';
  18528. }
  18529. } else {
  18530. $size = (int)$attr['size'];
  18531. if ($size > 7) {
  18532. $attr['size'] = '7';
  18533. }
  18534. }
  18535. }
  18536. if (isset($this->_size_lookup[$attr['size']])) {
  18537. $prepend_style .= 'font-size:' .
  18538. $this->_size_lookup[$attr['size']] . ';';
  18539. }
  18540. unset($attr['size']);
  18541. }
  18542. if ($prepend_style) {
  18543. $attr['style'] = isset($attr['style']) ?
  18544. $prepend_style . $attr['style'] :
  18545. $prepend_style;
  18546. }
  18547. $new_tag = clone $tag;
  18548. $new_tag->name = $this->transform_to;
  18549. $new_tag->attr = $attr;
  18550. return $new_tag;
  18551. }
  18552. }
  18553. /**
  18554. * Simple transformation, just change tag name to something else,
  18555. * and possibly add some styling. This will cover most of the deprecated
  18556. * tag cases.
  18557. */
  18558. class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
  18559. {
  18560. /**
  18561. * @type string
  18562. */
  18563. protected $style;
  18564. /**
  18565. * @param string $transform_to Tag name to transform to.
  18566. * @param string $style CSS style to add to the tag
  18567. */
  18568. public function __construct($transform_to, $style = null)
  18569. {
  18570. $this->transform_to = $transform_to;
  18571. $this->style = $style;
  18572. }
  18573. /**
  18574. * @param HTMLPurifier_Token_Tag $tag
  18575. * @param HTMLPurifier_Config $config
  18576. * @param HTMLPurifier_Context $context
  18577. * @return string
  18578. */
  18579. public function transform($tag, $config, $context)
  18580. {
  18581. $new_tag = clone $tag;
  18582. $new_tag->name = $this->transform_to;
  18583. if (!is_null($this->style) &&
  18584. ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
  18585. ) {
  18586. $this->prependCSS($new_tag->attr, $this->style);
  18587. }
  18588. return $new_tag;
  18589. }
  18590. }
  18591. /**
  18592. * Concrete comment token class. Generally will be ignored.
  18593. */
  18594. class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
  18595. {
  18596. /**
  18597. * Character data within comment.
  18598. * @type string
  18599. */
  18600. public $data;
  18601. /**
  18602. * @type bool
  18603. */
  18604. public $is_whitespace = true;
  18605. /**
  18606. * Transparent constructor.
  18607. *
  18608. * @param string $data String comment data.
  18609. * @param int $line
  18610. * @param int $col
  18611. */
  18612. public function __construct($data, $line = null, $col = null)
  18613. {
  18614. $this->data = $data;
  18615. $this->line = $line;
  18616. $this->col = $col;
  18617. }
  18618. public function toNode() {
  18619. return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
  18620. }
  18621. }
  18622. /**
  18623. * Abstract class of a tag token (start, end or empty), and its behavior.
  18624. */
  18625. abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
  18626. {
  18627. /**
  18628. * Static bool marker that indicates the class is a tag.
  18629. *
  18630. * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
  18631. * without having to use a function call <tt>is_a()</tt>.
  18632. * @type bool
  18633. */
  18634. public $is_tag = true;
  18635. /**
  18636. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  18637. *
  18638. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  18639. * be lower-casing them, but these tokens cater to HTML tags, which are
  18640. * insensitive.
  18641. * @type string
  18642. */
  18643. public $name;
  18644. /**
  18645. * Associative array of the tag's attributes.
  18646. * @type array
  18647. */
  18648. public $attr = array();
  18649. /**
  18650. * Non-overloaded constructor, which lower-cases passed tag name.
  18651. *
  18652. * @param string $name String name.
  18653. * @param array $attr Associative array of attributes.
  18654. * @param int $line
  18655. * @param int $col
  18656. * @param array $armor
  18657. */
  18658. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
  18659. {
  18660. $this->name = ctype_lower($name) ? $name : strtolower($name);
  18661. foreach ($attr as $key => $value) {
  18662. // normalization only necessary when key is not lowercase
  18663. if (!ctype_lower($key)) {
  18664. $new_key = strtolower($key);
  18665. if (!isset($attr[$new_key])) {
  18666. $attr[$new_key] = $attr[$key];
  18667. }
  18668. if ($new_key !== $key) {
  18669. unset($attr[$key]);
  18670. }
  18671. }
  18672. }
  18673. $this->attr = $attr;
  18674. $this->line = $line;
  18675. $this->col = $col;
  18676. $this->armor = $armor;
  18677. }
  18678. public function toNode() {
  18679. return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
  18680. }
  18681. }
  18682. /**
  18683. * Concrete empty token class.
  18684. */
  18685. class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
  18686. {
  18687. public function toNode() {
  18688. $n = parent::toNode();
  18689. $n->empty = true;
  18690. return $n;
  18691. }
  18692. }
  18693. /**
  18694. * Concrete end token class.
  18695. *
  18696. * @warning This class accepts attributes even though end tags cannot. This
  18697. * is for optimization reasons, as under normal circumstances, the Lexers
  18698. * do not pass attributes.
  18699. */
  18700. class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
  18701. {
  18702. /**
  18703. * Token that started this node.
  18704. * Added by MakeWellFormed. Please do not edit this!
  18705. * @type HTMLPurifier_Token
  18706. */
  18707. public $start;
  18708. public function toNode() {
  18709. throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
  18710. }
  18711. }
  18712. /**
  18713. * Concrete start token class.
  18714. */
  18715. class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
  18716. {
  18717. }
  18718. /**
  18719. * Concrete text token class.
  18720. *
  18721. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  18722. * character data (from the CDATA sections). Internally, their
  18723. * data is parsed with all entities expanded. Surprisingly, the text token
  18724. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  18725. * in permissible child nodes.
  18726. */
  18727. class HTMLPurifier_Token_Text extends HTMLPurifier_Token
  18728. {
  18729. /**
  18730. * @type string
  18731. */
  18732. public $name = '#PCDATA';
  18733. /**< PCDATA tag name compatible with DTD. */
  18734. /**
  18735. * @type string
  18736. */
  18737. public $data;
  18738. /**< Parsed character data of text. */
  18739. /**
  18740. * @type bool
  18741. */
  18742. public $is_whitespace;
  18743. /**< Bool indicating if node is whitespace. */
  18744. /**
  18745. * Constructor, accepts data and determines if it is whitespace.
  18746. * @param string $data String parsed character data.
  18747. * @param int $line
  18748. * @param int $col
  18749. */
  18750. public function __construct($data, $line = null, $col = null)
  18751. {
  18752. $this->data = $data;
  18753. $this->is_whitespace = ctype_space($data);
  18754. $this->line = $line;
  18755. $this->col = $col;
  18756. }
  18757. public function toNode() {
  18758. return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
  18759. }
  18760. }
  18761. class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
  18762. {
  18763. /**
  18764. * @type string
  18765. */
  18766. public $name = 'DisableExternal';
  18767. /**
  18768. * @type array
  18769. */
  18770. protected $ourHostParts = false;
  18771. /**
  18772. * @param HTMLPurifier_Config $config
  18773. * @return void
  18774. */
  18775. public function prepare($config)
  18776. {
  18777. $our_host = $config->getDefinition('URI')->host;
  18778. if ($our_host !== null) {
  18779. $this->ourHostParts = array_reverse(explode('.', $our_host));
  18780. }
  18781. }
  18782. /**
  18783. * @param HTMLPurifier_URI $uri Reference
  18784. * @param HTMLPurifier_Config $config
  18785. * @param HTMLPurifier_Context $context
  18786. * @return bool
  18787. */
  18788. public function filter(&$uri, $config, $context)
  18789. {
  18790. if (is_null($uri->host)) {
  18791. return true;
  18792. }
  18793. if ($this->ourHostParts === false) {
  18794. return false;
  18795. }
  18796. $host_parts = array_reverse(explode('.', $uri->host));
  18797. foreach ($this->ourHostParts as $i => $x) {
  18798. if (!isset($host_parts[$i])) {
  18799. return false;
  18800. }
  18801. if ($host_parts[$i] != $this->ourHostParts[$i]) {
  18802. return false;
  18803. }
  18804. }
  18805. return true;
  18806. }
  18807. }
  18808. class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
  18809. {
  18810. /**
  18811. * @type string
  18812. */
  18813. public $name = 'DisableExternalResources';
  18814. /**
  18815. * @param HTMLPurifier_URI $uri
  18816. * @param HTMLPurifier_Config $config
  18817. * @param HTMLPurifier_Context $context
  18818. * @return bool
  18819. */
  18820. public function filter(&$uri, $config, $context)
  18821. {
  18822. if (!$context->get('EmbeddedURI', true)) {
  18823. return true;
  18824. }
  18825. return parent::filter($uri, $config, $context);
  18826. }
  18827. }
  18828. class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
  18829. {
  18830. /**
  18831. * @type string
  18832. */
  18833. public $name = 'DisableResources';
  18834. /**
  18835. * @param HTMLPurifier_URI $uri
  18836. * @param HTMLPurifier_Config $config
  18837. * @param HTMLPurifier_Context $context
  18838. * @return bool
  18839. */
  18840. public function filter(&$uri, $config, $context)
  18841. {
  18842. return !$context->get('EmbeddedURI', true);
  18843. }
  18844. }
  18845. // It's not clear to me whether or not Punycode means that hostnames
  18846. // do not have canonical forms anymore. As far as I can tell, it's
  18847. // not a problem (punycoding should be identity when no Unicode
  18848. // points are involved), but I'm not 100% sure
  18849. class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
  18850. {
  18851. /**
  18852. * @type string
  18853. */
  18854. public $name = 'HostBlacklist';
  18855. /**
  18856. * @type array
  18857. */
  18858. protected $blacklist = array();
  18859. /**
  18860. * @param HTMLPurifier_Config $config
  18861. * @return bool
  18862. */
  18863. public function prepare($config)
  18864. {
  18865. $this->blacklist = $config->get('URI.HostBlacklist');
  18866. return true;
  18867. }
  18868. /**
  18869. * @param HTMLPurifier_URI $uri
  18870. * @param HTMLPurifier_Config $config
  18871. * @param HTMLPurifier_Context $context
  18872. * @return bool
  18873. */
  18874. public function filter(&$uri, $config, $context)
  18875. {
  18876. foreach ($this->blacklist as $blacklisted_host_fragment) {
  18877. if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
  18878. return false;
  18879. }
  18880. }
  18881. return true;
  18882. }
  18883. }
  18884. // does not support network paths
  18885. class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
  18886. {
  18887. /**
  18888. * @type string
  18889. */
  18890. public $name = 'MakeAbsolute';
  18891. /**
  18892. * @type
  18893. */
  18894. protected $base;
  18895. /**
  18896. * @type array
  18897. */
  18898. protected $basePathStack = array();
  18899. /**
  18900. * @param HTMLPurifier_Config $config
  18901. * @return bool
  18902. */
  18903. public function prepare($config)
  18904. {
  18905. $def = $config->getDefinition('URI');
  18906. $this->base = $def->base;
  18907. if (is_null($this->base)) {
  18908. trigger_error(
  18909. 'URI.MakeAbsolute is being ignored due to lack of ' .
  18910. 'value for URI.Base configuration',
  18911. E_USER_WARNING
  18912. );
  18913. return false;
  18914. }
  18915. $this->base->fragment = null; // fragment is invalid for base URI
  18916. $stack = explode('/', $this->base->path);
  18917. array_pop($stack); // discard last segment
  18918. $stack = $this->_collapseStack($stack); // do pre-parsing
  18919. $this->basePathStack = $stack;
  18920. return true;
  18921. }
  18922. /**
  18923. * @param HTMLPurifier_URI $uri
  18924. * @param HTMLPurifier_Config $config
  18925. * @param HTMLPurifier_Context $context
  18926. * @return bool
  18927. */
  18928. public function filter(&$uri, $config, $context)
  18929. {
  18930. if (is_null($this->base)) {
  18931. return true;
  18932. } // abort early
  18933. if ($uri->path === '' && is_null($uri->scheme) &&
  18934. is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
  18935. // reference to current document
  18936. $uri = clone $this->base;
  18937. return true;
  18938. }
  18939. if (!is_null($uri->scheme)) {
  18940. // absolute URI already: don't change
  18941. if (!is_null($uri->host)) {
  18942. return true;
  18943. }
  18944. $scheme_obj = $uri->getSchemeObj($config, $context);
  18945. if (!$scheme_obj) {
  18946. // scheme not recognized
  18947. return false;
  18948. }
  18949. if (!$scheme_obj->hierarchical) {
  18950. // non-hierarchal URI with explicit scheme, don't change
  18951. return true;
  18952. }
  18953. // special case: had a scheme but always is hierarchical and had no authority
  18954. }
  18955. if (!is_null($uri->host)) {
  18956. // network path, don't bother
  18957. return true;
  18958. }
  18959. if ($uri->path === '') {
  18960. $uri->path = $this->base->path;
  18961. } elseif ($uri->path[0] !== '/') {
  18962. // relative path, needs more complicated processing
  18963. $stack = explode('/', $uri->path);
  18964. $new_stack = array_merge($this->basePathStack, $stack);
  18965. if ($new_stack[0] !== '' && !is_null($this->base->host)) {
  18966. array_unshift($new_stack, '');
  18967. }
  18968. $new_stack = $this->_collapseStack($new_stack);
  18969. $uri->path = implode('/', $new_stack);
  18970. } else {
  18971. // absolute path, but still we should collapse
  18972. $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
  18973. }
  18974. // re-combine
  18975. $uri->scheme = $this->base->scheme;
  18976. if (is_null($uri->userinfo)) {
  18977. $uri->userinfo = $this->base->userinfo;
  18978. }
  18979. if (is_null($uri->host)) {
  18980. $uri->host = $this->base->host;
  18981. }
  18982. if (is_null($uri->port)) {
  18983. $uri->port = $this->base->port;
  18984. }
  18985. return true;
  18986. }
  18987. /**
  18988. * Resolve dots and double-dots in a path stack
  18989. * @param array $stack
  18990. * @return array
  18991. */
  18992. private function _collapseStack($stack)
  18993. {
  18994. $result = array();
  18995. $is_folder = false;
  18996. for ($i = 0; isset($stack[$i]); $i++) {
  18997. $is_folder = false;
  18998. // absorb an internally duplicated slash
  18999. if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
  19000. continue;
  19001. }
  19002. if ($stack[$i] == '..') {
  19003. if (!empty($result)) {
  19004. $segment = array_pop($result);
  19005. if ($segment === '' && empty($result)) {
  19006. // error case: attempted to back out too far:
  19007. // restore the leading slash
  19008. $result[] = '';
  19009. } elseif ($segment === '..') {
  19010. $result[] = '..'; // cannot remove .. with ..
  19011. }
  19012. } else {
  19013. // relative path, preserve the double-dots
  19014. $result[] = '..';
  19015. }
  19016. $is_folder = true;
  19017. continue;
  19018. }
  19019. if ($stack[$i] == '.') {
  19020. // silently absorb
  19021. $is_folder = true;
  19022. continue;
  19023. }
  19024. $result[] = $stack[$i];
  19025. }
  19026. if ($is_folder) {
  19027. $result[] = '';
  19028. }
  19029. return $result;
  19030. }
  19031. }
  19032. class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
  19033. {
  19034. /**
  19035. * @type string
  19036. */
  19037. public $name = 'Munge';
  19038. /**
  19039. * @type bool
  19040. */
  19041. public $post = true;
  19042. /**
  19043. * @type string
  19044. */
  19045. private $target;
  19046. /**
  19047. * @type HTMLPurifier_URIParser
  19048. */
  19049. private $parser;
  19050. /**
  19051. * @type bool
  19052. */
  19053. private $doEmbed;
  19054. /**
  19055. * @type string
  19056. */
  19057. private $secretKey;
  19058. /**
  19059. * @type array
  19060. */
  19061. protected $replace = array();
  19062. /**
  19063. * @param HTMLPurifier_Config $config
  19064. * @return bool
  19065. */
  19066. public function prepare($config)
  19067. {
  19068. $this->target = $config->get('URI.' . $this->name);
  19069. $this->parser = new HTMLPurifier_URIParser();
  19070. $this->doEmbed = $config->get('URI.MungeResources');
  19071. $this->secretKey = $config->get('URI.MungeSecretKey');
  19072. if ($this->secretKey && !function_exists('hash_hmac')) {
  19073. throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
  19074. }
  19075. return true;
  19076. }
  19077. /**
  19078. * @param HTMLPurifier_URI $uri
  19079. * @param HTMLPurifier_Config $config
  19080. * @param HTMLPurifier_Context $context
  19081. * @return bool
  19082. */
  19083. public function filter(&$uri, $config, $context)
  19084. {
  19085. if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
  19086. return true;
  19087. }
  19088. $scheme_obj = $uri->getSchemeObj($config, $context);
  19089. if (!$scheme_obj) {
  19090. return true;
  19091. } // ignore unknown schemes, maybe another postfilter did it
  19092. if (!$scheme_obj->browsable) {
  19093. return true;
  19094. } // ignore non-browseable schemes, since we can't munge those in a reasonable way
  19095. if ($uri->isBenign($config, $context)) {
  19096. return true;
  19097. } // don't redirect if a benign URL
  19098. $this->makeReplace($uri, $config, $context);
  19099. $this->replace = array_map('rawurlencode', $this->replace);
  19100. $new_uri = strtr($this->target, $this->replace);
  19101. $new_uri = $this->parser->parse($new_uri);
  19102. // don't redirect if the target host is the same as the
  19103. // starting host
  19104. if ($uri->host === $new_uri->host) {
  19105. return true;
  19106. }
  19107. $uri = $new_uri; // overwrite
  19108. return true;
  19109. }
  19110. /**
  19111. * @param HTMLPurifier_URI $uri
  19112. * @param HTMLPurifier_Config $config
  19113. * @param HTMLPurifier_Context $context
  19114. */
  19115. protected function makeReplace($uri, $config, $context)
  19116. {
  19117. $string = $uri->toString();
  19118. // always available
  19119. $this->replace['%s'] = $string;
  19120. $this->replace['%r'] = $context->get('EmbeddedURI', true);
  19121. $token = $context->get('CurrentToken', true);
  19122. $this->replace['%n'] = $token ? $token->name : null;
  19123. $this->replace['%m'] = $context->get('CurrentAttr', true);
  19124. $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
  19125. // not always available
  19126. if ($this->secretKey) {
  19127. $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
  19128. }
  19129. }
  19130. }
  19131. /**
  19132. * Implements safety checks for safe iframes.
  19133. *
  19134. * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
  19135. * works safely.
  19136. */
  19137. class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
  19138. {
  19139. /**
  19140. * @type string
  19141. */
  19142. public $name = 'SafeIframe';
  19143. /**
  19144. * @type bool
  19145. */
  19146. public $always_load = true;
  19147. /**
  19148. * @type string
  19149. */
  19150. protected $regexp = null;
  19151. // XXX: The not so good bit about how this is all set up now is we
  19152. // can't check HTML.SafeIframe in the 'prepare' step: we have to
  19153. // defer till the actual filtering.
  19154. /**
  19155. * @param HTMLPurifier_Config $config
  19156. * @return bool
  19157. */
  19158. public function prepare($config)
  19159. {
  19160. $this->regexp = $config->get('URI.SafeIframeRegexp');
  19161. return true;
  19162. }
  19163. /**
  19164. * @param HTMLPurifier_URI $uri
  19165. * @param HTMLPurifier_Config $config
  19166. * @param HTMLPurifier_Context $context
  19167. * @return bool
  19168. */
  19169. public function filter(&$uri, $config, $context)
  19170. {
  19171. // check if filter not applicable
  19172. if (!$config->get('HTML.SafeIframe')) {
  19173. return true;
  19174. }
  19175. // check if the filter should actually trigger
  19176. if (!$context->get('EmbeddedURI', true)) {
  19177. return true;
  19178. }
  19179. $token = $context->get('CurrentToken', true);
  19180. if (!($token && $token->name == 'iframe')) {
  19181. return true;
  19182. }
  19183. // check if we actually have some whitelists enabled
  19184. if ($this->regexp === null) {
  19185. return false;
  19186. }
  19187. // actually check the whitelists
  19188. return preg_match($this->regexp, $uri->toString());
  19189. }
  19190. }
  19191. /**
  19192. * Implements data: URI for base64 encoded images supported by GD.
  19193. */
  19194. class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
  19195. {
  19196. /**
  19197. * @type bool
  19198. */
  19199. public $browsable = true;
  19200. /**
  19201. * @type array
  19202. */
  19203. public $allowed_types = array(
  19204. // you better write validation code for other types if you
  19205. // decide to allow them
  19206. 'image/jpeg' => true,
  19207. 'image/gif' => true,
  19208. 'image/png' => true,
  19209. );
  19210. // this is actually irrelevant since we only write out the path
  19211. // component
  19212. /**
  19213. * @type bool
  19214. */
  19215. public $may_omit_host = true;
  19216. /**
  19217. * @param HTMLPurifier_URI $uri
  19218. * @param HTMLPurifier_Config $config
  19219. * @param HTMLPurifier_Context $context
  19220. * @return bool
  19221. */
  19222. public function doValidate(&$uri, $config, $context)
  19223. {
  19224. $result = explode(',', $uri->path, 2);
  19225. $is_base64 = false;
  19226. $charset = null;
  19227. $content_type = null;
  19228. if (count($result) == 2) {
  19229. list($metadata, $data) = $result;
  19230. // do some legwork on the metadata
  19231. $metas = explode(';', $metadata);
  19232. while (!empty($metas)) {
  19233. $cur = array_shift($metas);
  19234. if ($cur == 'base64') {
  19235. $is_base64 = true;
  19236. break;
  19237. }
  19238. if (substr($cur, 0, 8) == 'charset=') {
  19239. // doesn't match if there are arbitrary spaces, but
  19240. // whatever dude
  19241. if ($charset !== null) {
  19242. continue;
  19243. } // garbage
  19244. $charset = substr($cur, 8); // not used
  19245. } else {
  19246. if ($content_type !== null) {
  19247. continue;
  19248. } // garbage
  19249. $content_type = $cur;
  19250. }
  19251. }
  19252. } else {
  19253. $data = $result[0];
  19254. }
  19255. if ($content_type !== null && empty($this->allowed_types[$content_type])) {
  19256. return false;
  19257. }
  19258. if ($charset !== null) {
  19259. // error; we don't allow plaintext stuff
  19260. $charset = null;
  19261. }
  19262. $data = rawurldecode($data);
  19263. if ($is_base64) {
  19264. $raw_data = base64_decode($data);
  19265. } else {
  19266. $raw_data = $data;
  19267. }
  19268. if ( strlen($raw_data) < 12 ) {
  19269. // error; exif_imagetype throws exception with small files,
  19270. // and this likely indicates a corrupt URI/failed parse anyway
  19271. return false;
  19272. }
  19273. // XXX probably want to refactor this into a general mechanism
  19274. // for filtering arbitrary content types
  19275. if (function_exists('sys_get_temp_dir')) {
  19276. $file = tempnam(sys_get_temp_dir(), "");
  19277. } else {
  19278. $file = tempnam("/tmp", "");
  19279. }
  19280. file_put_contents($file, $raw_data);
  19281. if (function_exists('exif_imagetype')) {
  19282. $image_code = exif_imagetype($file);
  19283. unlink($file);
  19284. } elseif (function_exists('getimagesize')) {
  19285. set_error_handler(array($this, 'muteErrorHandler'));
  19286. $info = getimagesize($file);
  19287. restore_error_handler();
  19288. unlink($file);
  19289. if ($info == false) {
  19290. return false;
  19291. }
  19292. $image_code = $info[2];
  19293. } else {
  19294. trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
  19295. }
  19296. $real_content_type = image_type_to_mime_type($image_code);
  19297. if ($real_content_type != $content_type) {
  19298. // we're nice guys; if the content type is something else we
  19299. // support, change it over
  19300. if (empty($this->allowed_types[$real_content_type])) {
  19301. return false;
  19302. }
  19303. $content_type = $real_content_type;
  19304. }
  19305. // ok, it's kosher, rewrite what we need
  19306. $uri->userinfo = null;
  19307. $uri->host = null;
  19308. $uri->port = null;
  19309. $uri->fragment = null;
  19310. $uri->query = null;
  19311. $uri->path = "$content_type;base64," . base64_encode($raw_data);
  19312. return true;
  19313. }
  19314. /**
  19315. * @param int $errno
  19316. * @param string $errstr
  19317. */
  19318. public function muteErrorHandler($errno, $errstr)
  19319. {
  19320. }
  19321. }
  19322. /**
  19323. * Validates file as defined by RFC 1630 and RFC 1738.
  19324. */
  19325. class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
  19326. {
  19327. /**
  19328. * Generally file:// URLs are not accessible from most
  19329. * machines, so placing them as an img src is incorrect.
  19330. * @type bool
  19331. */
  19332. public $browsable = false;
  19333. /**
  19334. * Basically the *only* URI scheme for which this is true, since
  19335. * accessing files on the local machine is very common. In fact,
  19336. * browsers on some operating systems don't understand the
  19337. * authority, though I hear it is used on Windows to refer to
  19338. * network shares.
  19339. * @type bool
  19340. */
  19341. public $may_omit_host = true;
  19342. /**
  19343. * @param HTMLPurifier_URI $uri
  19344. * @param HTMLPurifier_Config $config
  19345. * @param HTMLPurifier_Context $context
  19346. * @return bool
  19347. */
  19348. public function doValidate(&$uri, $config, $context)
  19349. {
  19350. // Authentication method is not supported
  19351. $uri->userinfo = null;
  19352. // file:// makes no provisions for accessing the resource
  19353. $uri->port = null;
  19354. // While it seems to work on Firefox, the querystring has
  19355. // no possible effect and is thus stripped.
  19356. $uri->query = null;
  19357. return true;
  19358. }
  19359. }
  19360. /**
  19361. * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
  19362. */
  19363. class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
  19364. {
  19365. /**
  19366. * @type int
  19367. */
  19368. public $default_port = 21;
  19369. /**
  19370. * @type bool
  19371. */
  19372. public $browsable = true; // usually
  19373. /**
  19374. * @type bool
  19375. */
  19376. public $hierarchical = true;
  19377. /**
  19378. * @param HTMLPurifier_URI $uri
  19379. * @param HTMLPurifier_Config $config
  19380. * @param HTMLPurifier_Context $context
  19381. * @return bool
  19382. */
  19383. public function doValidate(&$uri, $config, $context)
  19384. {
  19385. $uri->query = null;
  19386. // typecode check
  19387. $semicolon_pos = strrpos($uri->path, ';'); // reverse
  19388. if ($semicolon_pos !== false) {
  19389. $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
  19390. $uri->path = substr($uri->path, 0, $semicolon_pos);
  19391. $type_ret = '';
  19392. if (strpos($type, '=') !== false) {
  19393. // figure out whether or not the declaration is correct
  19394. list($key, $typecode) = explode('=', $type, 2);
  19395. if ($key !== 'type') {
  19396. // invalid key, tack it back on encoded
  19397. $uri->path .= '%3B' . $type;
  19398. } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
  19399. $type_ret = ";type=$typecode";
  19400. }
  19401. } else {
  19402. $uri->path .= '%3B' . $type;
  19403. }
  19404. $uri->path = str_replace(';', '%3B', $uri->path);
  19405. $uri->path .= $type_ret;
  19406. }
  19407. return true;
  19408. }
  19409. }
  19410. /**
  19411. * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
  19412. */
  19413. class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
  19414. {
  19415. /**
  19416. * @type int
  19417. */
  19418. public $default_port = 80;
  19419. /**
  19420. * @type bool
  19421. */
  19422. public $browsable = true;
  19423. /**
  19424. * @type bool
  19425. */
  19426. public $hierarchical = true;
  19427. /**
  19428. * @param HTMLPurifier_URI $uri
  19429. * @param HTMLPurifier_Config $config
  19430. * @param HTMLPurifier_Context $context
  19431. * @return bool
  19432. */
  19433. public function doValidate(&$uri, $config, $context)
  19434. {
  19435. $uri->userinfo = null;
  19436. return true;
  19437. }
  19438. }
  19439. /**
  19440. * Validates https (Secure HTTP) according to http scheme.
  19441. */
  19442. class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
  19443. {
  19444. /**
  19445. * @type int
  19446. */
  19447. public $default_port = 443;
  19448. /**
  19449. * @type bool
  19450. */
  19451. public $secure = true;
  19452. }
  19453. // VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
  19454. // email is valid, but be careful!
  19455. /**
  19456. * Validates mailto (for E-mail) according to RFC 2368
  19457. * @todo Validate the email address
  19458. * @todo Filter allowed query parameters
  19459. */
  19460. class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
  19461. {
  19462. /**
  19463. * @type bool
  19464. */
  19465. public $browsable = false;
  19466. /**
  19467. * @type bool
  19468. */
  19469. public $may_omit_host = true;
  19470. /**
  19471. * @param HTMLPurifier_URI $uri
  19472. * @param HTMLPurifier_Config $config
  19473. * @param HTMLPurifier_Context $context
  19474. * @return bool
  19475. */
  19476. public function doValidate(&$uri, $config, $context)
  19477. {
  19478. $uri->userinfo = null;
  19479. $uri->host = null;
  19480. $uri->port = null;
  19481. // we need to validate path against RFC 2368's addr-spec
  19482. return true;
  19483. }
  19484. }
  19485. /**
  19486. * Validates news (Usenet) as defined by generic RFC 1738
  19487. */
  19488. class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
  19489. {
  19490. /**
  19491. * @type bool
  19492. */
  19493. public $browsable = false;
  19494. /**
  19495. * @type bool
  19496. */
  19497. public $may_omit_host = true;
  19498. /**
  19499. * @param HTMLPurifier_URI $uri
  19500. * @param HTMLPurifier_Config $config
  19501. * @param HTMLPurifier_Context $context
  19502. * @return bool
  19503. */
  19504. public function doValidate(&$uri, $config, $context)
  19505. {
  19506. $uri->userinfo = null;
  19507. $uri->host = null;
  19508. $uri->port = null;
  19509. $uri->query = null;
  19510. // typecode check needed on path
  19511. return true;
  19512. }
  19513. }
  19514. /**
  19515. * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
  19516. */
  19517. class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
  19518. {
  19519. /**
  19520. * @type int
  19521. */
  19522. public $default_port = 119;
  19523. /**
  19524. * @type bool
  19525. */
  19526. public $browsable = false;
  19527. /**
  19528. * @param HTMLPurifier_URI $uri
  19529. * @param HTMLPurifier_Config $config
  19530. * @param HTMLPurifier_Context $context
  19531. * @return bool
  19532. */
  19533. public function doValidate(&$uri, $config, $context)
  19534. {
  19535. $uri->userinfo = null;
  19536. $uri->query = null;
  19537. return true;
  19538. }
  19539. }
  19540. /**
  19541. * Validates tel (for phone numbers).
  19542. *
  19543. * The relevant specifications for this protocol are RFC 3966 and RFC 5341,
  19544. * but this class takes a much simpler approach: we normalize phone
  19545. * numbers so that they only include (possibly) a leading plus,
  19546. * and then any number of digits and x'es.
  19547. */
  19548. class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme
  19549. {
  19550. /**
  19551. * @type bool
  19552. */
  19553. public $browsable = false;
  19554. /**
  19555. * @type bool
  19556. */
  19557. public $may_omit_host = true;
  19558. /**
  19559. * @param HTMLPurifier_URI $uri
  19560. * @param HTMLPurifier_Config $config
  19561. * @param HTMLPurifier_Context $context
  19562. * @return bool
  19563. */
  19564. public function doValidate(&$uri, $config, $context)
  19565. {
  19566. $uri->userinfo = null;
  19567. $uri->host = null;
  19568. $uri->port = null;
  19569. // Delete all non-numeric characters, non-x characters
  19570. // from phone number, EXCEPT for a leading plus sign.
  19571. $uri->path = preg_replace('/(?!^\+)[^\dx]/', '',
  19572. // Normalize e(x)tension to lower-case
  19573. str_replace('X', 'x', $uri->path));
  19574. return true;
  19575. }
  19576. }
  19577. /**
  19578. * Performs safe variable parsing based on types which can be used by
  19579. * users. This may not be able to represent all possible data inputs,
  19580. * however.
  19581. */
  19582. class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
  19583. {
  19584. /**
  19585. * @param mixed $var
  19586. * @param int $type
  19587. * @param bool $allow_null
  19588. * @return array|bool|float|int|mixed|null|string
  19589. * @throws HTMLPurifier_VarParserException
  19590. */
  19591. protected function parseImplementation($var, $type, $allow_null)
  19592. {
  19593. if ($allow_null && $var === null) {
  19594. return null;
  19595. }
  19596. switch ($type) {
  19597. // Note: if code "breaks" from the switch, it triggers a generic
  19598. // exception to be thrown. Specific errors can be specifically
  19599. // done here.
  19600. case self::C_MIXED:
  19601. case self::ISTRING:
  19602. case self::C_STRING:
  19603. case self::TEXT:
  19604. case self::ITEXT:
  19605. return $var;
  19606. case self::C_INT:
  19607. if (is_string($var) && ctype_digit($var)) {
  19608. $var = (int)$var;
  19609. }
  19610. return $var;
  19611. case self::C_FLOAT:
  19612. if ((is_string($var) && is_numeric($var)) || is_int($var)) {
  19613. $var = (float)$var;
  19614. }
  19615. return $var;
  19616. case self::C_BOOL:
  19617. if (is_int($var) && ($var === 0 || $var === 1)) {
  19618. $var = (bool)$var;
  19619. } elseif (is_string($var)) {
  19620. if ($var == 'on' || $var == 'true' || $var == '1') {
  19621. $var = true;
  19622. } elseif ($var == 'off' || $var == 'false' || $var == '0') {
  19623. $var = false;
  19624. } else {
  19625. throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
  19626. }
  19627. }
  19628. return $var;
  19629. case self::ALIST:
  19630. case self::HASH:
  19631. case self::LOOKUP:
  19632. if (is_string($var)) {
  19633. // special case: technically, this is an array with
  19634. // a single empty string item, but having an empty
  19635. // array is more intuitive
  19636. if ($var == '') {
  19637. return array();
  19638. }
  19639. if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
  19640. // simplistic string to array method that only works
  19641. // for simple lists of tag names or alphanumeric characters
  19642. $var = explode(',', $var);
  19643. } else {
  19644. $var = preg_split('/(,|[\n\r]+)/', $var);
  19645. }
  19646. // remove spaces
  19647. foreach ($var as $i => $j) {
  19648. $var[$i] = trim($j);
  19649. }
  19650. if ($type === self::HASH) {
  19651. // key:value,key2:value2
  19652. $nvar = array();
  19653. foreach ($var as $keypair) {
  19654. $c = explode(':', $keypair, 2);
  19655. if (!isset($c[1])) {
  19656. continue;
  19657. }
  19658. $nvar[trim($c[0])] = trim($c[1]);
  19659. }
  19660. $var = $nvar;
  19661. }
  19662. }
  19663. if (!is_array($var)) {
  19664. break;
  19665. }
  19666. $keys = array_keys($var);
  19667. if ($keys === array_keys($keys)) {
  19668. if ($type == self::ALIST) {
  19669. return $var;
  19670. } elseif ($type == self::LOOKUP) {
  19671. $new = array();
  19672. foreach ($var as $key) {
  19673. $new[$key] = true;
  19674. }
  19675. return $new;
  19676. } else {
  19677. break;
  19678. }
  19679. }
  19680. if ($type === self::ALIST) {
  19681. trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
  19682. return array_values($var);
  19683. }
  19684. if ($type === self::LOOKUP) {
  19685. foreach ($var as $key => $value) {
  19686. if ($value !== true) {
  19687. trigger_error(
  19688. "Lookup array has non-true value at key '$key'; " .
  19689. "maybe your input array was not indexed numerically",
  19690. E_USER_WARNING
  19691. );
  19692. }
  19693. $var[$key] = true;
  19694. }
  19695. }
  19696. return $var;
  19697. default:
  19698. $this->errorInconsistent(__CLASS__, $type);
  19699. }
  19700. $this->errorGeneric($var, $type);
  19701. }
  19702. }
  19703. /**
  19704. * This variable parser uses PHP's internal code engine. Because it does
  19705. * this, it can represent all inputs; however, it is dangerous and cannot
  19706. * be used by users.
  19707. */
  19708. class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
  19709. {
  19710. /**
  19711. * @param mixed $var
  19712. * @param int $type
  19713. * @param bool $allow_null
  19714. * @return null|string
  19715. */
  19716. protected function parseImplementation($var, $type, $allow_null)
  19717. {
  19718. return $this->evalExpression($var);
  19719. }
  19720. /**
  19721. * @param string $expr
  19722. * @return mixed
  19723. * @throws HTMLPurifier_VarParserException
  19724. */
  19725. protected function evalExpression($expr)
  19726. {
  19727. $var = null;
  19728. $result = eval("\$var = $expr;");
  19729. if ($result === false) {
  19730. throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
  19731. }
  19732. return $var;
  19733. }
  19734. }