HTMLPurifier.standalone.php 706 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524
  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.10.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.10.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.10.0';
  72. /**
  73. * Constant with version of HTML Purifier.
  74. */
  75. const VERSION = '4.10.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 => $html) {
  231. $array_of_html[$key] = $this->purify($html, $config);
  232. $context_array[$key] = $this->context;
  233. }
  234. $this->context = $context_array;
  235. return $array_of_html;
  236. }
  237. /**
  238. * Singleton for enforcing just one HTML Purifier in your system
  239. *
  240. * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
  241. * HTMLPurifier instance to overload singleton with,
  242. * or HTMLPurifier_Config instance to configure the
  243. * generated version with.
  244. *
  245. * @return HTMLPurifier
  246. */
  247. public static function instance($prototype = null)
  248. {
  249. if (!self::$instance || $prototype) {
  250. if ($prototype instanceof HTMLPurifier) {
  251. self::$instance = $prototype;
  252. } elseif ($prototype) {
  253. self::$instance = new HTMLPurifier($prototype);
  254. } else {
  255. self::$instance = new HTMLPurifier();
  256. }
  257. }
  258. return self::$instance;
  259. }
  260. /**
  261. * Singleton for enforcing just one HTML Purifier in your system
  262. *
  263. * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
  264. * HTMLPurifier instance to overload singleton with,
  265. * or HTMLPurifier_Config instance to configure the
  266. * generated version with.
  267. *
  268. * @return HTMLPurifier
  269. * @note Backwards compatibility, see instance()
  270. */
  271. public static function getInstance($prototype = null)
  272. {
  273. return HTMLPurifier::instance($prototype);
  274. }
  275. }
  276. /**
  277. * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
  278. * and back again.
  279. *
  280. * @note This transformation is not an equivalence. We mutate the input
  281. * token stream to make it so; see all [MUT] markers in code.
  282. */
  283. class HTMLPurifier_Arborize
  284. {
  285. public static function arborize($tokens, $config, $context) {
  286. $definition = $config->getHTMLDefinition();
  287. $parent = new HTMLPurifier_Token_Start($definition->info_parent);
  288. $stack = array($parent->toNode());
  289. foreach ($tokens as $token) {
  290. $token->skip = null; // [MUT]
  291. $token->carryover = null; // [MUT]
  292. if ($token instanceof HTMLPurifier_Token_End) {
  293. $token->start = null; // [MUT]
  294. $r = array_pop($stack);
  295. //assert($r->name === $token->name);
  296. //assert(empty($token->attr));
  297. $r->endCol = $token->col;
  298. $r->endLine = $token->line;
  299. $r->endArmor = $token->armor;
  300. continue;
  301. }
  302. $node = $token->toNode();
  303. $stack[count($stack)-1]->children[] = $node;
  304. if ($token instanceof HTMLPurifier_Token_Start) {
  305. $stack[] = $node;
  306. }
  307. }
  308. //assert(count($stack) == 1);
  309. return $stack[0];
  310. }
  311. public static function flatten($node, $config, $context) {
  312. $level = 0;
  313. $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  314. $closingTokens = array();
  315. $tokens = array();
  316. do {
  317. while (!$nodes[$level]->isEmpty()) {
  318. $node = $nodes[$level]->shift(); // FIFO
  319. list($start, $end) = $node->toTokenPair();
  320. if ($level > 0) {
  321. $tokens[] = $start;
  322. }
  323. if ($end !== NULL) {
  324. $closingTokens[$level][] = $end;
  325. }
  326. if ($node instanceof HTMLPurifier_Node_Element) {
  327. $level++;
  328. $nodes[$level] = new HTMLPurifier_Queue();
  329. foreach ($node->children as $childNode) {
  330. $nodes[$level]->push($childNode);
  331. }
  332. }
  333. }
  334. $level--;
  335. if ($level && isset($closingTokens[$level])) {
  336. while ($token = array_pop($closingTokens[$level])) {
  337. $tokens[] = $token;
  338. }
  339. }
  340. } while ($level > 0);
  341. return $tokens;
  342. }
  343. }
  344. /**
  345. * Defines common attribute collections that modules reference
  346. */
  347. class HTMLPurifier_AttrCollections
  348. {
  349. /**
  350. * Associative array of attribute collections, indexed by name.
  351. * @type array
  352. */
  353. public $info = array();
  354. /**
  355. * Performs all expansions on internal data for use by other inclusions
  356. * It also collects all attribute collection extensions from
  357. * modules
  358. * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
  359. * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
  360. */
  361. public function __construct($attr_types, $modules)
  362. {
  363. $this->doConstruct($attr_types, $modules);
  364. }
  365. public function doConstruct($attr_types, $modules)
  366. {
  367. // load extensions from the modules
  368. foreach ($modules as $module) {
  369. foreach ($module->attr_collections as $coll_i => $coll) {
  370. if (!isset($this->info[$coll_i])) {
  371. $this->info[$coll_i] = array();
  372. }
  373. foreach ($coll as $attr_i => $attr) {
  374. if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
  375. // merge in includes
  376. $this->info[$coll_i][$attr_i] = array_merge(
  377. $this->info[$coll_i][$attr_i],
  378. $attr
  379. );
  380. continue;
  381. }
  382. $this->info[$coll_i][$attr_i] = $attr;
  383. }
  384. }
  385. }
  386. // perform internal expansions and inclusions
  387. foreach ($this->info as $name => $attr) {
  388. // merge attribute collections that include others
  389. $this->performInclusions($this->info[$name]);
  390. // replace string identifiers with actual attribute objects
  391. $this->expandIdentifiers($this->info[$name], $attr_types);
  392. }
  393. }
  394. /**
  395. * Takes a reference to an attribute associative array and performs
  396. * all inclusions specified by the zero index.
  397. * @param array &$attr Reference to attribute array
  398. */
  399. public function performInclusions(&$attr)
  400. {
  401. if (!isset($attr[0])) {
  402. return;
  403. }
  404. $merge = $attr[0];
  405. $seen = array(); // recursion guard
  406. // loop through all the inclusions
  407. for ($i = 0; isset($merge[$i]); $i++) {
  408. if (isset($seen[$merge[$i]])) {
  409. continue;
  410. }
  411. $seen[$merge[$i]] = true;
  412. // foreach attribute of the inclusion, copy it over
  413. if (!isset($this->info[$merge[$i]])) {
  414. continue;
  415. }
  416. foreach ($this->info[$merge[$i]] as $key => $value) {
  417. if (isset($attr[$key])) {
  418. continue;
  419. } // also catches more inclusions
  420. $attr[$key] = $value;
  421. }
  422. if (isset($this->info[$merge[$i]][0])) {
  423. // recursion
  424. $merge = array_merge($merge, $this->info[$merge[$i]][0]);
  425. }
  426. }
  427. unset($attr[0]);
  428. }
  429. /**
  430. * Expands all string identifiers in an attribute array by replacing
  431. * them with the appropriate values inside HTMLPurifier_AttrTypes
  432. * @param array &$attr Reference to attribute array
  433. * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
  434. */
  435. public function expandIdentifiers(&$attr, $attr_types)
  436. {
  437. // because foreach will process new elements we add, make sure we
  438. // skip duplicates
  439. $processed = array();
  440. foreach ($attr as $def_i => $def) {
  441. // skip inclusions
  442. if ($def_i === 0) {
  443. continue;
  444. }
  445. if (isset($processed[$def_i])) {
  446. continue;
  447. }
  448. // determine whether or not attribute is required
  449. if ($required = (strpos($def_i, '*') !== false)) {
  450. // rename the definition
  451. unset($attr[$def_i]);
  452. $def_i = trim($def_i, '*');
  453. $attr[$def_i] = $def;
  454. }
  455. $processed[$def_i] = true;
  456. // if we've already got a literal object, move on
  457. if (is_object($def)) {
  458. // preserve previous required
  459. $attr[$def_i]->required = ($required || $attr[$def_i]->required);
  460. continue;
  461. }
  462. if ($def === false) {
  463. unset($attr[$def_i]);
  464. continue;
  465. }
  466. if ($t = $attr_types->get($def)) {
  467. $attr[$def_i] = $t;
  468. $attr[$def_i]->required = $required;
  469. } else {
  470. unset($attr[$def_i]);
  471. }
  472. }
  473. }
  474. }
  475. /**
  476. * Base class for all validating attribute definitions.
  477. *
  478. * This family of classes forms the core for not only HTML attribute validation,
  479. * but also any sort of string that needs to be validated or cleaned (which
  480. * means CSS properties and composite definitions are defined here too).
  481. * Besides defining (through code) what precisely makes the string valid,
  482. * subclasses are also responsible for cleaning the code if possible.
  483. */
  484. abstract class HTMLPurifier_AttrDef
  485. {
  486. /**
  487. * Tells us whether or not an HTML attribute is minimized.
  488. * Has no meaning in other contexts.
  489. * @type bool
  490. */
  491. public $minimized = false;
  492. /**
  493. * Tells us whether or not an HTML attribute is required.
  494. * Has no meaning in other contexts
  495. * @type bool
  496. */
  497. public $required = false;
  498. /**
  499. * Validates and cleans passed string according to a definition.
  500. *
  501. * @param string $string String to be validated and cleaned.
  502. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  503. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
  504. */
  505. abstract public function validate($string, $config, $context);
  506. /**
  507. * Convenience method that parses a string as if it were CDATA.
  508. *
  509. * This method process a string in the manner specified at
  510. * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
  511. * leading and trailing whitespace, ignoring line feeds, and replacing
  512. * carriage returns and tabs with spaces. While most useful for HTML
  513. * attributes specified as CDATA, it can also be applied to most CSS
  514. * values.
  515. *
  516. * @note This method is not entirely standards compliant, as trim() removes
  517. * more types of whitespace than specified in the spec. In practice,
  518. * this is rarely a problem, as those extra characters usually have
  519. * already been removed by HTMLPurifier_Encoder.
  520. *
  521. * @warning This processing is inconsistent with XML's whitespace handling
  522. * as specified by section 3.3.3 and referenced XHTML 1.0 section
  523. * 4.7. However, note that we are NOT necessarily
  524. * parsing XML, thus, this behavior may still be correct. We
  525. * assume that newlines have been normalized.
  526. */
  527. public function parseCDATA($string)
  528. {
  529. $string = trim($string);
  530. $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
  531. return $string;
  532. }
  533. /**
  534. * Factory method for creating this class from a string.
  535. * @param string $string String construction info
  536. * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
  537. */
  538. public function make($string)
  539. {
  540. // default implementation, return a flyweight of this object.
  541. // If $string has an effect on the returned object (i.e. you
  542. // need to overload this method), it is best
  543. // to clone or instantiate new copies. (Instantiation is safer.)
  544. return $this;
  545. }
  546. /**
  547. * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
  548. * properly. THIS IS A HACK!
  549. * @param string $string a CSS colour definition
  550. * @return string
  551. */
  552. protected function mungeRgb($string)
  553. {
  554. $p = '\s*(\d+(\.\d+)?([%]?))\s*';
  555. if (preg_match('/(rgba|hsla)\(/', $string)) {
  556. return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string);
  557. }
  558. return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string);
  559. }
  560. /**
  561. * Parses a possibly escaped CSS string and returns the "pure"
  562. * version of it.
  563. */
  564. protected function expandCSSEscape($string)
  565. {
  566. // flexibly parse it
  567. $ret = '';
  568. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  569. if ($string[$i] === '\\') {
  570. $i++;
  571. if ($i >= $c) {
  572. $ret .= '\\';
  573. break;
  574. }
  575. if (ctype_xdigit($string[$i])) {
  576. $code = $string[$i];
  577. for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
  578. if (!ctype_xdigit($string[$i])) {
  579. break;
  580. }
  581. $code .= $string[$i];
  582. }
  583. // We have to be extremely careful when adding
  584. // new characters, to make sure we're not breaking
  585. // the encoding.
  586. $char = HTMLPurifier_Encoder::unichr(hexdec($code));
  587. if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
  588. continue;
  589. }
  590. $ret .= $char;
  591. if ($i < $c && trim($string[$i]) !== '') {
  592. $i--;
  593. }
  594. continue;
  595. }
  596. if ($string[$i] === "\n") {
  597. continue;
  598. }
  599. }
  600. $ret .= $string[$i];
  601. }
  602. return $ret;
  603. }
  604. }
  605. /**
  606. * Processes an entire attribute array for corrections needing multiple values.
  607. *
  608. * Occasionally, a certain attribute will need to be removed and popped onto
  609. * another value. Instead of creating a complex return syntax for
  610. * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
  611. * specialized object and have that do the special work. That is the
  612. * family of HTMLPurifier_AttrTransform.
  613. *
  614. * An attribute transformation can be assigned to run before or after
  615. * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
  616. * more details.
  617. */
  618. abstract class HTMLPurifier_AttrTransform
  619. {
  620. /**
  621. * Abstract: makes changes to the attributes dependent on multiple values.
  622. *
  623. * @param array $attr Assoc array of attributes, usually from
  624. * HTMLPurifier_Token_Tag::$attr
  625. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  626. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  627. * @return array Processed attribute array.
  628. */
  629. abstract public function transform($attr, $config, $context);
  630. /**
  631. * Prepends CSS properties to the style attribute, creating the
  632. * attribute if it doesn't exist.
  633. * @param array &$attr Attribute array to process (passed by reference)
  634. * @param string $css CSS to prepend
  635. */
  636. public function prependCSS(&$attr, $css)
  637. {
  638. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  639. $attr['style'] = $css . $attr['style'];
  640. }
  641. /**
  642. * Retrieves and removes an attribute
  643. * @param array &$attr Attribute array to process (passed by reference)
  644. * @param mixed $key Key of attribute to confiscate
  645. * @return mixed
  646. */
  647. public function confiscateAttr(&$attr, $key)
  648. {
  649. if (!isset($attr[$key])) {
  650. return null;
  651. }
  652. $value = $attr[$key];
  653. unset($attr[$key]);
  654. return $value;
  655. }
  656. }
  657. /**
  658. * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
  659. */
  660. class HTMLPurifier_AttrTypes
  661. {
  662. /**
  663. * Lookup array of attribute string identifiers to concrete implementations.
  664. * @type HTMLPurifier_AttrDef[]
  665. */
  666. protected $info = array();
  667. /**
  668. * Constructs the info array, supplying default implementations for attribute
  669. * types.
  670. */
  671. public function __construct()
  672. {
  673. // XXX This is kind of poor, since we don't actually /clone/
  674. // instances; instead, we use the supplied make() attribute. So,
  675. // the underlying class must know how to deal with arguments.
  676. // With the old implementation of Enum, that ignored its
  677. // arguments when handling a make dispatch, the IAlign
  678. // definition wouldn't work.
  679. // pseudo-types, must be instantiated via shorthand
  680. $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
  681. $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
  682. $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
  683. $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
  684. $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
  685. $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
  686. $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
  687. $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
  688. $this->info['Text'] = new HTMLPurifier_AttrDef_Text();
  689. $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
  690. $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
  691. $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
  692. $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
  693. $this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
  694. $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
  695. // unimplemented aliases
  696. $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
  697. $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
  698. $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
  699. $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
  700. // "proprietary" types
  701. $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
  702. // number is really a positive integer (one or more digits)
  703. // FIXME: ^^ not always, see start and value of list items
  704. $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
  705. }
  706. private static function makeEnum($in)
  707. {
  708. return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
  709. }
  710. /**
  711. * Retrieves a type
  712. * @param string $type String type name
  713. * @return HTMLPurifier_AttrDef Object AttrDef for type
  714. */
  715. public function get($type)
  716. {
  717. // determine if there is any extra info tacked on
  718. if (strpos($type, '#') !== false) {
  719. list($type, $string) = explode('#', $type, 2);
  720. } else {
  721. $string = '';
  722. }
  723. if (!isset($this->info[$type])) {
  724. trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
  725. return;
  726. }
  727. return $this->info[$type]->make($string);
  728. }
  729. /**
  730. * Sets a new implementation for a type
  731. * @param string $type String type name
  732. * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
  733. */
  734. public function set($type, $impl)
  735. {
  736. $this->info[$type] = $impl;
  737. }
  738. }
  739. /**
  740. * Validates the attributes of a token. Doesn't manage required attributes
  741. * very well. The only reason we factored this out was because RemoveForeignElements
  742. * also needed it besides ValidateAttributes.
  743. */
  744. class HTMLPurifier_AttrValidator
  745. {
  746. /**
  747. * Validates the attributes of a token, mutating it as necessary.
  748. * that has valid tokens
  749. * @param HTMLPurifier_Token $token Token to validate.
  750. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  751. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  752. */
  753. public function validateToken($token, $config, $context)
  754. {
  755. $definition = $config->getHTMLDefinition();
  756. $e =& $context->get('ErrorCollector', true);
  757. // initialize IDAccumulator if necessary
  758. $ok =& $context->get('IDAccumulator', true);
  759. if (!$ok) {
  760. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  761. $context->register('IDAccumulator', $id_accumulator);
  762. }
  763. // initialize CurrentToken if necessary
  764. $current_token =& $context->get('CurrentToken', true);
  765. if (!$current_token) {
  766. $context->register('CurrentToken', $token);
  767. }
  768. if (!$token instanceof HTMLPurifier_Token_Start &&
  769. !$token instanceof HTMLPurifier_Token_Empty
  770. ) {
  771. return;
  772. }
  773. // create alias to global definition array, see also $defs
  774. // DEFINITION CALL
  775. $d_defs = $definition->info_global_attr;
  776. // don't update token until the very end, to ensure an atomic update
  777. $attr = $token->attr;
  778. // do global transformations (pre)
  779. // nothing currently utilizes this
  780. foreach ($definition->info_attr_transform_pre as $transform) {
  781. $attr = $transform->transform($o = $attr, $config, $context);
  782. if ($e) {
  783. if ($attr != $o) {
  784. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  785. }
  786. }
  787. }
  788. // do local transformations only applicable to this element (pre)
  789. // ex. <p align="right"> to <p style="text-align:right;">
  790. foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
  791. $attr = $transform->transform($o = $attr, $config, $context);
  792. if ($e) {
  793. if ($attr != $o) {
  794. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  795. }
  796. }
  797. }
  798. // create alias to this element's attribute definition array, see
  799. // also $d_defs (global attribute definition array)
  800. // DEFINITION CALL
  801. $defs = $definition->info[$token->name]->attr;
  802. $attr_key = false;
  803. $context->register('CurrentAttr', $attr_key);
  804. // iterate through all the attribute keypairs
  805. // Watch out for name collisions: $key has previously been used
  806. foreach ($attr as $attr_key => $value) {
  807. // call the definition
  808. if (isset($defs[$attr_key])) {
  809. // there is a local definition defined
  810. if ($defs[$attr_key] === false) {
  811. // We've explicitly been told not to allow this element.
  812. // This is usually when there's a global definition
  813. // that must be overridden.
  814. // Theoretically speaking, we could have a
  815. // AttrDef_DenyAll, but this is faster!
  816. $result = false;
  817. } else {
  818. // validate according to the element's definition
  819. $result = $defs[$attr_key]->validate(
  820. $value,
  821. $config,
  822. $context
  823. );
  824. }
  825. } elseif (isset($d_defs[$attr_key])) {
  826. // there is a global definition defined, validate according
  827. // to the global definition
  828. $result = $d_defs[$attr_key]->validate(
  829. $value,
  830. $config,
  831. $context
  832. );
  833. } else {
  834. // system never heard of the attribute? DELETE!
  835. $result = false;
  836. }
  837. // put the results into effect
  838. if ($result === false || $result === null) {
  839. // this is a generic error message that should replaced
  840. // with more specific ones when possible
  841. if ($e) {
  842. $e->send(E_ERROR, 'AttrValidator: Attribute removed');
  843. }
  844. // remove the attribute
  845. unset($attr[$attr_key]);
  846. } elseif (is_string($result)) {
  847. // generally, if a substitution is happening, there
  848. // was some sort of implicit correction going on. We'll
  849. // delegate it to the attribute classes to say exactly what.
  850. // simple substitution
  851. $attr[$attr_key] = $result;
  852. } else {
  853. // nothing happens
  854. }
  855. // we'd also want slightly more complicated substitution
  856. // involving an array as the return value,
  857. // although we're not sure how colliding attributes would
  858. // resolve (certain ones would be completely overriden,
  859. // others would prepend themselves).
  860. }
  861. $context->destroy('CurrentAttr');
  862. // post transforms
  863. // global (error reporting untested)
  864. foreach ($definition->info_attr_transform_post as $transform) {
  865. $attr = $transform->transform($o = $attr, $config, $context);
  866. if ($e) {
  867. if ($attr != $o) {
  868. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  869. }
  870. }
  871. }
  872. // local (error reporting untested)
  873. foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
  874. $attr = $transform->transform($o = $attr, $config, $context);
  875. if ($e) {
  876. if ($attr != $o) {
  877. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  878. }
  879. }
  880. }
  881. $token->attr = $attr;
  882. // destroy CurrentToken if we made it ourselves
  883. if (!$current_token) {
  884. $context->destroy('CurrentToken');
  885. }
  886. }
  887. }
  888. // constants are slow, so we use as few as possible
  889. if (!defined('HTMLPURIFIER_PREFIX')) {
  890. define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
  891. set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
  892. }
  893. // accomodations for versions earlier than 5.0.2
  894. // borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
  895. if (!defined('PHP_EOL')) {
  896. switch (strtoupper(substr(PHP_OS, 0, 3))) {
  897. case 'WIN':
  898. define('PHP_EOL', "\r\n");
  899. break;
  900. case 'DAR':
  901. define('PHP_EOL', "\r");
  902. break;
  903. default:
  904. define('PHP_EOL', "\n");
  905. }
  906. }
  907. /**
  908. * Bootstrap class that contains meta-functionality for HTML Purifier such as
  909. * the autoload function.
  910. *
  911. * @note
  912. * This class may be used without any other files from HTML Purifier.
  913. */
  914. class HTMLPurifier_Bootstrap
  915. {
  916. /**
  917. * Autoload function for HTML Purifier
  918. * @param string $class Class to load
  919. * @return bool
  920. */
  921. public static function autoload($class)
  922. {
  923. $file = HTMLPurifier_Bootstrap::getPath($class);
  924. if (!$file) {
  925. return false;
  926. }
  927. // Technically speaking, it should be ok and more efficient to
  928. // just do 'require', but Antonio Parraga reports that with
  929. // Zend extensions such as Zend debugger and APC, this invariant
  930. // may be broken. Since we have efficient alternatives, pay
  931. // the cost here and avoid the bug.
  932. require_once HTMLPURIFIER_PREFIX . '/' . $file;
  933. return true;
  934. }
  935. /**
  936. * Returns the path for a specific class.
  937. * @param string $class Class path to get
  938. * @return string
  939. */
  940. public static function getPath($class)
  941. {
  942. if (strncmp('HTMLPurifier', $class, 12) !== 0) {
  943. return false;
  944. }
  945. // Custom implementations
  946. if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
  947. $code = str_replace('_', '-', substr($class, 22));
  948. $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
  949. } else {
  950. $file = str_replace('_', '/', $class) . '.php';
  951. }
  952. if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
  953. return false;
  954. }
  955. return $file;
  956. }
  957. /**
  958. * "Pre-registers" our autoloader on the SPL stack.
  959. */
  960. public static function registerAutoload()
  961. {
  962. $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
  963. if (($funcs = spl_autoload_functions()) === false) {
  964. spl_autoload_register($autoload);
  965. } elseif (function_exists('spl_autoload_unregister')) {
  966. if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
  967. // prepend flag exists, no need for shenanigans
  968. spl_autoload_register($autoload, true, true);
  969. } else {
  970. $buggy = version_compare(PHP_VERSION, '5.2.11', '<');
  971. $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
  972. version_compare(PHP_VERSION, '5.1.0', '>=');
  973. foreach ($funcs as $func) {
  974. if ($buggy && is_array($func)) {
  975. // :TRICKY: There are some compatibility issues and some
  976. // places where we need to error out
  977. $reflector = new ReflectionMethod($func[0], $func[1]);
  978. if (!$reflector->isStatic()) {
  979. throw new Exception(
  980. 'HTML Purifier autoloader registrar is not compatible
  981. with non-static object methods due to PHP Bug #44144;
  982. Please do not use HTMLPurifier.autoload.php (or any
  983. file that includes this file); instead, place the code:
  984. spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
  985. after your own autoloaders.'
  986. );
  987. }
  988. // Suprisingly, spl_autoload_register supports the
  989. // Class::staticMethod callback format, although call_user_func doesn't
  990. if ($compat) {
  991. $func = implode('::', $func);
  992. }
  993. }
  994. spl_autoload_unregister($func);
  995. }
  996. spl_autoload_register($autoload);
  997. foreach ($funcs as $func) {
  998. spl_autoload_register($func);
  999. }
  1000. }
  1001. }
  1002. }
  1003. }
  1004. /**
  1005. * Super-class for definition datatype objects, implements serialization
  1006. * functions for the class.
  1007. */
  1008. abstract class HTMLPurifier_Definition
  1009. {
  1010. /**
  1011. * Has setup() been called yet?
  1012. * @type bool
  1013. */
  1014. public $setup = false;
  1015. /**
  1016. * If true, write out the final definition object to the cache after
  1017. * setup. This will be true only if all invocations to get a raw
  1018. * definition object are also optimized. This does not cause file
  1019. * system thrashing because on subsequent calls the cached object
  1020. * is used and any writes to the raw definition object are short
  1021. * circuited. See enduser-customize.html for the high-level
  1022. * picture.
  1023. * @type bool
  1024. */
  1025. public $optimized = null;
  1026. /**
  1027. * What type of definition is it?
  1028. * @type string
  1029. */
  1030. public $type;
  1031. /**
  1032. * Sets up the definition object into the final form, something
  1033. * not done by the constructor
  1034. * @param HTMLPurifier_Config $config
  1035. */
  1036. abstract protected function doSetup($config);
  1037. /**
  1038. * Setup function that aborts if already setup
  1039. * @param HTMLPurifier_Config $config
  1040. */
  1041. public function setup($config)
  1042. {
  1043. if ($this->setup) {
  1044. return;
  1045. }
  1046. $this->setup = true;
  1047. $this->doSetup($config);
  1048. }
  1049. }
  1050. /**
  1051. * Defines allowed CSS attributes and what their values are.
  1052. * @see HTMLPurifier_HTMLDefinition
  1053. */
  1054. class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
  1055. {
  1056. public $type = 'CSS';
  1057. /**
  1058. * Assoc array of attribute name to definition object.
  1059. * @type HTMLPurifier_AttrDef[]
  1060. */
  1061. public $info = array();
  1062. /**
  1063. * Constructs the info array. The meat of this class.
  1064. * @param HTMLPurifier_Config $config
  1065. */
  1066. protected function doSetup($config)
  1067. {
  1068. $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
  1069. array('left', 'right', 'center', 'justify'),
  1070. false
  1071. );
  1072. $border_style =
  1073. $this->info['border-bottom-style'] =
  1074. $this->info['border-right-style'] =
  1075. $this->info['border-left-style'] =
  1076. $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
  1077. array(
  1078. 'none',
  1079. 'hidden',
  1080. 'dotted',
  1081. 'dashed',
  1082. 'solid',
  1083. 'double',
  1084. 'groove',
  1085. 'ridge',
  1086. 'inset',
  1087. 'outset'
  1088. ),
  1089. false
  1090. );
  1091. $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
  1092. $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
  1093. array('none', 'left', 'right', 'both'),
  1094. false
  1095. );
  1096. $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
  1097. array('none', 'left', 'right'),
  1098. false
  1099. );
  1100. $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
  1101. array('normal', 'italic', 'oblique'),
  1102. false
  1103. );
  1104. $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
  1105. array('normal', 'small-caps'),
  1106. false
  1107. );
  1108. $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
  1109. array(
  1110. new HTMLPurifier_AttrDef_Enum(array('none')),
  1111. new HTMLPurifier_AttrDef_CSS_URI()
  1112. )
  1113. );
  1114. $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
  1115. array('inside', 'outside'),
  1116. false
  1117. );
  1118. $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
  1119. array(
  1120. 'disc',
  1121. 'circle',
  1122. 'square',
  1123. 'decimal',
  1124. 'lower-roman',
  1125. 'upper-roman',
  1126. 'lower-alpha',
  1127. 'upper-alpha',
  1128. 'none'
  1129. ),
  1130. false
  1131. );
  1132. $this->info['list-style-image'] = $uri_or_none;
  1133. $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
  1134. $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
  1135. array('capitalize', 'uppercase', 'lowercase', 'none'),
  1136. false
  1137. );
  1138. $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1139. $this->info['background-image'] = $uri_or_none;
  1140. $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
  1141. array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
  1142. );
  1143. $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
  1144. array('scroll', 'fixed')
  1145. );
  1146. $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
  1147. $border_color =
  1148. $this->info['border-top-color'] =
  1149. $this->info['border-bottom-color'] =
  1150. $this->info['border-left-color'] =
  1151. $this->info['border-right-color'] =
  1152. $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1153. array(
  1154. new HTMLPurifier_AttrDef_Enum(array('transparent')),
  1155. new HTMLPurifier_AttrDef_CSS_Color()
  1156. )
  1157. );
  1158. $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
  1159. $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
  1160. $border_width =
  1161. $this->info['border-top-width'] =
  1162. $this->info['border-bottom-width'] =
  1163. $this->info['border-left-width'] =
  1164. $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1165. array(
  1166. new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
  1167. new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
  1168. )
  1169. );
  1170. $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
  1171. $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1172. array(
  1173. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1174. new HTMLPurifier_AttrDef_CSS_Length()
  1175. )
  1176. );
  1177. $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1178. array(
  1179. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1180. new HTMLPurifier_AttrDef_CSS_Length()
  1181. )
  1182. );
  1183. $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1184. array(
  1185. new HTMLPurifier_AttrDef_Enum(
  1186. array(
  1187. 'xx-small',
  1188. 'x-small',
  1189. 'small',
  1190. 'medium',
  1191. 'large',
  1192. 'x-large',
  1193. 'xx-large',
  1194. 'larger',
  1195. 'smaller'
  1196. )
  1197. ),
  1198. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1199. new HTMLPurifier_AttrDef_CSS_Length()
  1200. )
  1201. );
  1202. $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1203. array(
  1204. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1205. new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
  1206. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1207. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1208. )
  1209. );
  1210. $margin =
  1211. $this->info['margin-top'] =
  1212. $this->info['margin-bottom'] =
  1213. $this->info['margin-left'] =
  1214. $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1215. array(
  1216. new HTMLPurifier_AttrDef_CSS_Length(),
  1217. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1218. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1219. )
  1220. );
  1221. $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
  1222. // non-negative
  1223. $padding =
  1224. $this->info['padding-top'] =
  1225. $this->info['padding-bottom'] =
  1226. $this->info['padding-left'] =
  1227. $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1228. array(
  1229. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1230. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1231. )
  1232. );
  1233. $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
  1234. $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1235. array(
  1236. new HTMLPurifier_AttrDef_CSS_Length(),
  1237. new HTMLPurifier_AttrDef_CSS_Percentage()
  1238. )
  1239. );
  1240. $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
  1241. array(
  1242. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1243. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  1244. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1245. )
  1246. );
  1247. $max = $config->get('CSS.MaxImgLength');
  1248. $this->info['min-width'] =
  1249. $this->info['max-width'] =
  1250. $this->info['min-height'] =
  1251. $this->info['max-height'] =
  1252. $this->info['width'] =
  1253. $this->info['height'] =
  1254. $max === null ?
  1255. $trusted_wh :
  1256. new HTMLPurifier_AttrDef_Switch(
  1257. 'img',
  1258. // For img tags:
  1259. new HTMLPurifier_AttrDef_CSS_Composite(
  1260. array(
  1261. new HTMLPurifier_AttrDef_CSS_Length('0', $max),
  1262. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1263. )
  1264. ),
  1265. // For everyone else:
  1266. $trusted_wh
  1267. );
  1268. $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
  1269. $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
  1270. // this could use specialized code
  1271. $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
  1272. array(
  1273. 'normal',
  1274. 'bold',
  1275. 'bolder',
  1276. 'lighter',
  1277. '100',
  1278. '200',
  1279. '300',
  1280. '400',
  1281. '500',
  1282. '600',
  1283. '700',
  1284. '800',
  1285. '900'
  1286. ),
  1287. false
  1288. );
  1289. // MUST be called after other font properties, as it references
  1290. // a CSSDefinition object
  1291. $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
  1292. // same here
  1293. $this->info['border'] =
  1294. $this->info['border-bottom'] =
  1295. $this->info['border-top'] =
  1296. $this->info['border-left'] =
  1297. $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
  1298. $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
  1299. array('collapse', 'separate')
  1300. );
  1301. $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
  1302. array('top', 'bottom')
  1303. );
  1304. $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
  1305. array('auto', 'fixed')
  1306. );
  1307. $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1308. array(
  1309. new HTMLPurifier_AttrDef_Enum(
  1310. array(
  1311. 'baseline',
  1312. 'sub',
  1313. 'super',
  1314. 'top',
  1315. 'text-top',
  1316. 'middle',
  1317. 'bottom',
  1318. 'text-bottom'
  1319. )
  1320. ),
  1321. new HTMLPurifier_AttrDef_CSS_Length(),
  1322. new HTMLPurifier_AttrDef_CSS_Percentage()
  1323. )
  1324. );
  1325. $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
  1326. // These CSS properties don't work on many browsers, but we live
  1327. // in THE FUTURE!
  1328. $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
  1329. array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
  1330. );
  1331. if ($config->get('CSS.Proprietary')) {
  1332. $this->doSetupProprietary($config);
  1333. }
  1334. if ($config->get('CSS.AllowTricky')) {
  1335. $this->doSetupTricky($config);
  1336. }
  1337. if ($config->get('CSS.Trusted')) {
  1338. $this->doSetupTrusted($config);
  1339. }
  1340. $allow_important = $config->get('CSS.AllowImportant');
  1341. // wrap all attr-defs with decorator that handles !important
  1342. foreach ($this->info as $k => $v) {
  1343. $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
  1344. }
  1345. $this->setupConfigStuff($config);
  1346. }
  1347. /**
  1348. * @param HTMLPurifier_Config $config
  1349. */
  1350. protected function doSetupProprietary($config)
  1351. {
  1352. // Internet Explorer only scrollbar colors
  1353. $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1354. $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1355. $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1356. $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1357. $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1358. $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1359. // vendor specific prefixes of opacity
  1360. $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1361. $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1362. // only opacity, for now
  1363. $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
  1364. // more CSS3
  1365. $this->info['page-break-after'] =
  1366. $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
  1367. array(
  1368. 'auto',
  1369. 'always',
  1370. 'avoid',
  1371. 'left',
  1372. 'right'
  1373. )
  1374. );
  1375. $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
  1376. $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
  1377. array(
  1378. new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
  1379. new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
  1380. ));
  1381. $this->info['border-top-left-radius'] =
  1382. $this->info['border-top-right-radius'] =
  1383. $this->info['border-bottom-right-radius'] =
  1384. $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
  1385. // TODO: support SLASH syntax
  1386. $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
  1387. }
  1388. /**
  1389. * @param HTMLPurifier_Config $config
  1390. */
  1391. protected function doSetupTricky($config)
  1392. {
  1393. $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
  1394. array(
  1395. 'inline',
  1396. 'block',
  1397. 'list-item',
  1398. 'run-in',
  1399. 'compact',
  1400. 'marker',
  1401. 'table',
  1402. 'inline-block',
  1403. 'inline-table',
  1404. 'table-row-group',
  1405. 'table-header-group',
  1406. 'table-footer-group',
  1407. 'table-row',
  1408. 'table-column-group',
  1409. 'table-column',
  1410. 'table-cell',
  1411. 'table-caption',
  1412. 'none'
  1413. )
  1414. );
  1415. $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
  1416. array('visible', 'hidden', 'collapse')
  1417. );
  1418. $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
  1419. $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1420. }
  1421. /**
  1422. * @param HTMLPurifier_Config $config
  1423. */
  1424. protected function doSetupTrusted($config)
  1425. {
  1426. $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
  1427. array('static', 'relative', 'absolute', 'fixed')
  1428. );
  1429. $this->info['top'] =
  1430. $this->info['left'] =
  1431. $this->info['right'] =
  1432. $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1433. array(
  1434. new HTMLPurifier_AttrDef_CSS_Length(),
  1435. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1436. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1437. )
  1438. );
  1439. $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1440. array(
  1441. new HTMLPurifier_AttrDef_Integer(),
  1442. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1443. )
  1444. );
  1445. }
  1446. /**
  1447. * Performs extra config-based processing. Based off of
  1448. * HTMLPurifier_HTMLDefinition.
  1449. * @param HTMLPurifier_Config $config
  1450. * @todo Refactor duplicate elements into common class (probably using
  1451. * composition, not inheritance).
  1452. */
  1453. protected function setupConfigStuff($config)
  1454. {
  1455. // setup allowed elements
  1456. $support = "(for information on implementing this, see the " .
  1457. "support forums) ";
  1458. $allowed_properties = $config->get('CSS.AllowedProperties');
  1459. if ($allowed_properties !== null) {
  1460. foreach ($this->info as $name => $d) {
  1461. if (!isset($allowed_properties[$name])) {
  1462. unset($this->info[$name]);
  1463. }
  1464. unset($allowed_properties[$name]);
  1465. }
  1466. // emit errors
  1467. foreach ($allowed_properties as $name => $d) {
  1468. // :TODO: Is this htmlspecialchars() call really necessary?
  1469. $name = htmlspecialchars($name);
  1470. trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
  1471. }
  1472. }
  1473. $forbidden_properties = $config->get('CSS.ForbiddenProperties');
  1474. if ($forbidden_properties !== null) {
  1475. foreach ($this->info as $name => $d) {
  1476. if (isset($forbidden_properties[$name])) {
  1477. unset($this->info[$name]);
  1478. }
  1479. }
  1480. }
  1481. }
  1482. }
  1483. /**
  1484. * Defines allowed child nodes and validates nodes against it.
  1485. */
  1486. abstract class HTMLPurifier_ChildDef
  1487. {
  1488. /**
  1489. * Type of child definition, usually right-most part of class name lowercase.
  1490. * Used occasionally in terms of context.
  1491. * @type string
  1492. */
  1493. public $type;
  1494. /**
  1495. * Indicates whether or not an empty array of children is okay.
  1496. *
  1497. * This is necessary for redundant checking when changes affecting
  1498. * a child node may cause a parent node to now be disallowed.
  1499. * @type bool
  1500. */
  1501. public $allow_empty;
  1502. /**
  1503. * Lookup array of all elements that this definition could possibly allow.
  1504. * @type array
  1505. */
  1506. public $elements = array();
  1507. /**
  1508. * Get lookup of tag names that should not close this element automatically.
  1509. * All other elements will do so.
  1510. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1511. * @return array
  1512. */
  1513. public function getAllowedElements($config)
  1514. {
  1515. return $this->elements;
  1516. }
  1517. /**
  1518. * Validates nodes according to definition and returns modification.
  1519. *
  1520. * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
  1521. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1522. * @param HTMLPurifier_Context $context HTMLPurifier_Context object
  1523. * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
  1524. */
  1525. abstract public function validateChildren($children, $config, $context);
  1526. }
  1527. /**
  1528. * Configuration object that triggers customizable behavior.
  1529. *
  1530. * @warning This class is strongly defined: that means that the class
  1531. * will fail if an undefined directive is retrieved or set.
  1532. *
  1533. * @note Many classes that could (although many times don't) use the
  1534. * configuration object make it a mandatory parameter. This is
  1535. * because a configuration object should always be forwarded,
  1536. * otherwise, you run the risk of missing a parameter and then
  1537. * being stumped when a configuration directive doesn't work.
  1538. *
  1539. * @todo Reconsider some of the public member variables
  1540. */
  1541. class HTMLPurifier_Config
  1542. {
  1543. /**
  1544. * HTML Purifier's version
  1545. * @type string
  1546. */
  1547. public $version = '4.10.0';
  1548. /**
  1549. * Whether or not to automatically finalize
  1550. * the object if a read operation is done.
  1551. * @type bool
  1552. */
  1553. public $autoFinalize = true;
  1554. // protected member variables
  1555. /**
  1556. * Namespace indexed array of serials for specific namespaces.
  1557. * @see getSerial() for more info.
  1558. * @type string[]
  1559. */
  1560. protected $serials = array();
  1561. /**
  1562. * Serial for entire configuration object.
  1563. * @type string
  1564. */
  1565. protected $serial;
  1566. /**
  1567. * Parser for variables.
  1568. * @type HTMLPurifier_VarParser_Flexible
  1569. */
  1570. protected $parser = null;
  1571. /**
  1572. * Reference HTMLPurifier_ConfigSchema for value checking.
  1573. * @type HTMLPurifier_ConfigSchema
  1574. * @note This is public for introspective purposes. Please don't
  1575. * abuse!
  1576. */
  1577. public $def;
  1578. /**
  1579. * Indexed array of definitions.
  1580. * @type HTMLPurifier_Definition[]
  1581. */
  1582. protected $definitions;
  1583. /**
  1584. * Whether or not config is finalized.
  1585. * @type bool
  1586. */
  1587. protected $finalized = false;
  1588. /**
  1589. * Property list containing configuration directives.
  1590. * @type array
  1591. */
  1592. protected $plist;
  1593. /**
  1594. * Whether or not a set is taking place due to an alias lookup.
  1595. * @type bool
  1596. */
  1597. private $aliasMode;
  1598. /**
  1599. * Set to false if you do not want line and file numbers in errors.
  1600. * (useful when unit testing). This will also compress some errors
  1601. * and exceptions.
  1602. * @type bool
  1603. */
  1604. public $chatty = true;
  1605. /**
  1606. * Current lock; only gets to this namespace are allowed.
  1607. * @type string
  1608. */
  1609. private $lock;
  1610. /**
  1611. * Constructor
  1612. * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
  1613. * what directives are allowed.
  1614. * @param HTMLPurifier_PropertyList $parent
  1615. */
  1616. public function __construct($definition, $parent = null)
  1617. {
  1618. $parent = $parent ? $parent : $definition->defaultPlist;
  1619. $this->plist = new HTMLPurifier_PropertyList($parent);
  1620. $this->def = $definition; // keep a copy around for checking
  1621. $this->parser = new HTMLPurifier_VarParser_Flexible();
  1622. }
  1623. /**
  1624. * Convenience constructor that creates a config object based on a mixed var
  1625. * @param mixed $config Variable that defines the state of the config
  1626. * object. Can be: a HTMLPurifier_Config() object,
  1627. * an array of directives based on loadArray(),
  1628. * or a string filename of an ini file.
  1629. * @param HTMLPurifier_ConfigSchema $schema Schema object
  1630. * @return HTMLPurifier_Config Configured object
  1631. */
  1632. public static function create($config, $schema = null)
  1633. {
  1634. if ($config instanceof HTMLPurifier_Config) {
  1635. // pass-through
  1636. return $config;
  1637. }
  1638. if (!$schema) {
  1639. $ret = HTMLPurifier_Config::createDefault();
  1640. } else {
  1641. $ret = new HTMLPurifier_Config($schema);
  1642. }
  1643. if (is_string($config)) {
  1644. $ret->loadIni($config);
  1645. } elseif (is_array($config)) $ret->loadArray($config);
  1646. return $ret;
  1647. }
  1648. /**
  1649. * Creates a new config object that inherits from a previous one.
  1650. * @param HTMLPurifier_Config $config Configuration object to inherit from.
  1651. * @return HTMLPurifier_Config object with $config as its parent.
  1652. */
  1653. public static function inherit(HTMLPurifier_Config $config)
  1654. {
  1655. return new HTMLPurifier_Config($config->def, $config->plist);
  1656. }
  1657. /**
  1658. * Convenience constructor that creates a default configuration object.
  1659. * @return HTMLPurifier_Config default object.
  1660. */
  1661. public static function createDefault()
  1662. {
  1663. $definition = HTMLPurifier_ConfigSchema::instance();
  1664. $config = new HTMLPurifier_Config($definition);
  1665. return $config;
  1666. }
  1667. /**
  1668. * Retrieves a value from the configuration.
  1669. *
  1670. * @param string $key String key
  1671. * @param mixed $a
  1672. *
  1673. * @return mixed
  1674. */
  1675. public function get($key, $a = null)
  1676. {
  1677. if ($a !== null) {
  1678. $this->triggerError(
  1679. "Using deprecated API: use \$config->get('$key.$a') instead",
  1680. E_USER_WARNING
  1681. );
  1682. $key = "$key.$a";
  1683. }
  1684. if (!$this->finalized) {
  1685. $this->autoFinalize();
  1686. }
  1687. if (!isset($this->def->info[$key])) {
  1688. // can't add % due to SimpleTest bug
  1689. $this->triggerError(
  1690. 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
  1691. E_USER_WARNING
  1692. );
  1693. return;
  1694. }
  1695. if (isset($this->def->info[$key]->isAlias)) {
  1696. $d = $this->def->info[$key];
  1697. $this->triggerError(
  1698. 'Cannot get value from aliased directive, use real name ' . $d->key,
  1699. E_USER_ERROR
  1700. );
  1701. return;
  1702. }
  1703. if ($this->lock) {
  1704. list($ns) = explode('.', $key);
  1705. if ($ns !== $this->lock) {
  1706. $this->triggerError(
  1707. 'Cannot get value of namespace ' . $ns . ' when lock for ' .
  1708. $this->lock .
  1709. ' is active, this probably indicates a Definition setup method ' .
  1710. 'is accessing directives that are not within its namespace',
  1711. E_USER_ERROR
  1712. );
  1713. return;
  1714. }
  1715. }
  1716. return $this->plist->get($key);
  1717. }
  1718. /**
  1719. * Retrieves an array of directives to values from a given namespace
  1720. *
  1721. * @param string $namespace String namespace
  1722. *
  1723. * @return array
  1724. */
  1725. public function getBatch($namespace)
  1726. {
  1727. if (!$this->finalized) {
  1728. $this->autoFinalize();
  1729. }
  1730. $full = $this->getAll();
  1731. if (!isset($full[$namespace])) {
  1732. $this->triggerError(
  1733. 'Cannot retrieve undefined namespace ' .
  1734. htmlspecialchars($namespace),
  1735. E_USER_WARNING
  1736. );
  1737. return;
  1738. }
  1739. return $full[$namespace];
  1740. }
  1741. /**
  1742. * Returns a SHA-1 signature of a segment of the configuration object
  1743. * that uniquely identifies that particular configuration
  1744. *
  1745. * @param string $namespace Namespace to get serial for
  1746. *
  1747. * @return string
  1748. * @note Revision is handled specially and is removed from the batch
  1749. * before processing!
  1750. */
  1751. public function getBatchSerial($namespace)
  1752. {
  1753. if (empty($this->serials[$namespace])) {
  1754. $batch = $this->getBatch($namespace);
  1755. unset($batch['DefinitionRev']);
  1756. $this->serials[$namespace] = sha1(serialize($batch));
  1757. }
  1758. return $this->serials[$namespace];
  1759. }
  1760. /**
  1761. * Returns a SHA-1 signature for the entire configuration object
  1762. * that uniquely identifies that particular configuration
  1763. *
  1764. * @return string
  1765. */
  1766. public function getSerial()
  1767. {
  1768. if (empty($this->serial)) {
  1769. $this->serial = sha1(serialize($this->getAll()));
  1770. }
  1771. return $this->serial;
  1772. }
  1773. /**
  1774. * Retrieves all directives, organized by namespace
  1775. *
  1776. * @warning This is a pretty inefficient function, avoid if you can
  1777. */
  1778. public function getAll()
  1779. {
  1780. if (!$this->finalized) {
  1781. $this->autoFinalize();
  1782. }
  1783. $ret = array();
  1784. foreach ($this->plist->squash() as $name => $value) {
  1785. list($ns, $key) = explode('.', $name, 2);
  1786. $ret[$ns][$key] = $value;
  1787. }
  1788. return $ret;
  1789. }
  1790. /**
  1791. * Sets a value to configuration.
  1792. *
  1793. * @param string $key key
  1794. * @param mixed $value value
  1795. * @param mixed $a
  1796. */
  1797. public function set($key, $value, $a = null)
  1798. {
  1799. if (strpos($key, '.') === false) {
  1800. $namespace = $key;
  1801. $directive = $value;
  1802. $value = $a;
  1803. $key = "$key.$directive";
  1804. $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
  1805. } else {
  1806. list($namespace) = explode('.', $key);
  1807. }
  1808. if ($this->isFinalized('Cannot set directive after finalization')) {
  1809. return;
  1810. }
  1811. if (!isset($this->def->info[$key])) {
  1812. $this->triggerError(
  1813. 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
  1814. E_USER_WARNING
  1815. );
  1816. return;
  1817. }
  1818. $def = $this->def->info[$key];
  1819. if (isset($def->isAlias)) {
  1820. if ($this->aliasMode) {
  1821. $this->triggerError(
  1822. 'Double-aliases not allowed, please fix '.
  1823. 'ConfigSchema bug with' . $key,
  1824. E_USER_ERROR
  1825. );
  1826. return;
  1827. }
  1828. $this->aliasMode = true;
  1829. $this->set($def->key, $value);
  1830. $this->aliasMode = false;
  1831. $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
  1832. return;
  1833. }
  1834. // Raw type might be negative when using the fully optimized form
  1835. // of stdClass, which indicates allow_null == true
  1836. $rtype = is_int($def) ? $def : $def->type;
  1837. if ($rtype < 0) {
  1838. $type = -$rtype;
  1839. $allow_null = true;
  1840. } else {
  1841. $type = $rtype;
  1842. $allow_null = isset($def->allow_null);
  1843. }
  1844. try {
  1845. $value = $this->parser->parse($value, $type, $allow_null);
  1846. } catch (HTMLPurifier_VarParserException $e) {
  1847. $this->triggerError(
  1848. 'Value for ' . $key . ' is of invalid type, should be ' .
  1849. HTMLPurifier_VarParser::getTypeName($type),
  1850. E_USER_WARNING
  1851. );
  1852. return;
  1853. }
  1854. if (is_string($value) && is_object($def)) {
  1855. // resolve value alias if defined
  1856. if (isset($def->aliases[$value])) {
  1857. $value = $def->aliases[$value];
  1858. }
  1859. // check to see if the value is allowed
  1860. if (isset($def->allowed) && !isset($def->allowed[$value])) {
  1861. $this->triggerError(
  1862. 'Value not supported, valid values are: ' .
  1863. $this->_listify($def->allowed),
  1864. E_USER_WARNING
  1865. );
  1866. return;
  1867. }
  1868. }
  1869. $this->plist->set($key, $value);
  1870. // reset definitions if the directives they depend on changed
  1871. // this is a very costly process, so it's discouraged
  1872. // with finalization
  1873. if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
  1874. $this->definitions[$namespace] = null;
  1875. }
  1876. $this->serials[$namespace] = false;
  1877. }
  1878. /**
  1879. * Convenience function for error reporting
  1880. *
  1881. * @param array $lookup
  1882. *
  1883. * @return string
  1884. */
  1885. private function _listify($lookup)
  1886. {
  1887. $list = array();
  1888. foreach ($lookup as $name => $b) {
  1889. $list[] = $name;
  1890. }
  1891. return implode(', ', $list);
  1892. }
  1893. /**
  1894. * Retrieves object reference to the HTML definition.
  1895. *
  1896. * @param bool $raw Return a copy that has not been setup yet. Must be
  1897. * called before it's been setup, otherwise won't work.
  1898. * @param bool $optimized If true, this method may return null, to
  1899. * indicate that a cached version of the modified
  1900. * definition object is available and no further edits
  1901. * are necessary. Consider using
  1902. * maybeGetRawHTMLDefinition, which is more explicitly
  1903. * named, instead.
  1904. *
  1905. * @return HTMLPurifier_HTMLDefinition
  1906. */
  1907. public function getHTMLDefinition($raw = false, $optimized = false)
  1908. {
  1909. return $this->getDefinition('HTML', $raw, $optimized);
  1910. }
  1911. /**
  1912. * Retrieves object reference to the CSS definition
  1913. *
  1914. * @param bool $raw Return a copy that has not been setup yet. Must be
  1915. * called before it's been setup, otherwise won't work.
  1916. * @param bool $optimized If true, this method may return null, to
  1917. * indicate that a cached version of the modified
  1918. * definition object is available and no further edits
  1919. * are necessary. Consider using
  1920. * maybeGetRawCSSDefinition, which is more explicitly
  1921. * named, instead.
  1922. *
  1923. * @return HTMLPurifier_CSSDefinition
  1924. */
  1925. public function getCSSDefinition($raw = false, $optimized = false)
  1926. {
  1927. return $this->getDefinition('CSS', $raw, $optimized);
  1928. }
  1929. /**
  1930. * Retrieves object reference to the URI definition
  1931. *
  1932. * @param bool $raw Return a copy that has not been setup yet. Must be
  1933. * called before it's been setup, otherwise won't work.
  1934. * @param bool $optimized If true, this method may return null, to
  1935. * indicate that a cached version of the modified
  1936. * definition object is available and no further edits
  1937. * are necessary. Consider using
  1938. * maybeGetRawURIDefinition, which is more explicitly
  1939. * named, instead.
  1940. *
  1941. * @return HTMLPurifier_URIDefinition
  1942. */
  1943. public function getURIDefinition($raw = false, $optimized = false)
  1944. {
  1945. return $this->getDefinition('URI', $raw, $optimized);
  1946. }
  1947. /**
  1948. * Retrieves a definition
  1949. *
  1950. * @param string $type Type of definition: HTML, CSS, etc
  1951. * @param bool $raw Whether or not definition should be returned raw
  1952. * @param bool $optimized Only has an effect when $raw is true. Whether
  1953. * or not to return null if the result is already present in
  1954. * the cache. This is off by default for backwards
  1955. * compatibility reasons, but you need to do things this
  1956. * way in order to ensure that caching is done properly.
  1957. * Check out enduser-customize.html for more details.
  1958. * We probably won't ever change this default, as much as the
  1959. * maybe semantics is the "right thing to do."
  1960. *
  1961. * @throws HTMLPurifier_Exception
  1962. * @return HTMLPurifier_Definition
  1963. */
  1964. public function getDefinition($type, $raw = false, $optimized = false)
  1965. {
  1966. if ($optimized && !$raw) {
  1967. throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
  1968. }
  1969. if (!$this->finalized) {
  1970. $this->autoFinalize();
  1971. }
  1972. // temporarily suspend locks, so we can handle recursive definition calls
  1973. $lock = $this->lock;
  1974. $this->lock = null;
  1975. $factory = HTMLPurifier_DefinitionCacheFactory::instance();
  1976. $cache = $factory->create($type, $this);
  1977. $this->lock = $lock;
  1978. if (!$raw) {
  1979. // full definition
  1980. // ---------------
  1981. // check if definition is in memory
  1982. if (!empty($this->definitions[$type])) {
  1983. $def = $this->definitions[$type];
  1984. // check if the definition is setup
  1985. if ($def->setup) {
  1986. return $def;
  1987. } else {
  1988. $def->setup($this);
  1989. if ($def->optimized) {
  1990. $cache->add($def, $this);
  1991. }
  1992. return $def;
  1993. }
  1994. }
  1995. // check if definition is in cache
  1996. $def = $cache->get($this);
  1997. if ($def) {
  1998. // definition in cache, save to memory and return it
  1999. $this->definitions[$type] = $def;
  2000. return $def;
  2001. }
  2002. // initialize it
  2003. $def = $this->initDefinition($type);
  2004. // set it up
  2005. $this->lock = $type;
  2006. $def->setup($this);
  2007. $this->lock = null;
  2008. // save in cache
  2009. $cache->add($def, $this);
  2010. // return it
  2011. return $def;
  2012. } else {
  2013. // raw definition
  2014. // --------------
  2015. // check preconditions
  2016. $def = null;
  2017. if ($optimized) {
  2018. if (is_null($this->get($type . '.DefinitionID'))) {
  2019. // fatally error out if definition ID not set
  2020. throw new HTMLPurifier_Exception(
  2021. "Cannot retrieve raw version without specifying %$type.DefinitionID"
  2022. );
  2023. }
  2024. }
  2025. if (!empty($this->definitions[$type])) {
  2026. $def = $this->definitions[$type];
  2027. if ($def->setup && !$optimized) {
  2028. $extra = $this->chatty ?
  2029. " (try moving this code block earlier in your initialization)" :
  2030. "";
  2031. throw new HTMLPurifier_Exception(
  2032. "Cannot retrieve raw definition after it has already been setup" .
  2033. $extra
  2034. );
  2035. }
  2036. if ($def->optimized === null) {
  2037. $extra = $this->chatty ? " (try flushing your cache)" : "";
  2038. throw new HTMLPurifier_Exception(
  2039. "Optimization status of definition is unknown" . $extra
  2040. );
  2041. }
  2042. if ($def->optimized !== $optimized) {
  2043. $msg = $optimized ? "optimized" : "unoptimized";
  2044. $extra = $this->chatty ?
  2045. " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
  2046. : "";
  2047. throw new HTMLPurifier_Exception(
  2048. "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
  2049. );
  2050. }
  2051. }
  2052. // check if definition was in memory
  2053. if ($def) {
  2054. if ($def->setup) {
  2055. // invariant: $optimized === true (checked above)
  2056. return null;
  2057. } else {
  2058. return $def;
  2059. }
  2060. }
  2061. // if optimized, check if definition was in cache
  2062. // (because we do the memory check first, this formulation
  2063. // is prone to cache slamming, but I think
  2064. // guaranteeing that either /all/ of the raw
  2065. // setup code or /none/ of it is run is more important.)
  2066. if ($optimized) {
  2067. // This code path only gets run once; once we put
  2068. // something in $definitions (which is guaranteed by the
  2069. // trailing code), we always short-circuit above.
  2070. $def = $cache->get($this);
  2071. if ($def) {
  2072. // save the full definition for later, but don't
  2073. // return it yet
  2074. $this->definitions[$type] = $def;
  2075. return null;
  2076. }
  2077. }
  2078. // check invariants for creation
  2079. if (!$optimized) {
  2080. if (!is_null($this->get($type . '.DefinitionID'))) {
  2081. if ($this->chatty) {
  2082. $this->triggerError(
  2083. 'Due to a documentation error in previous version of HTML Purifier, your ' .
  2084. 'definitions are not being cached. If this is OK, you can remove the ' .
  2085. '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
  2086. 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
  2087. 'value is null before making any edits (if it is null, that means that a ' .
  2088. 'cached version is available, and no raw operations are necessary). See ' .
  2089. '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
  2090. 'Customize</a> for more details',
  2091. E_USER_WARNING
  2092. );
  2093. } else {
  2094. $this->triggerError(
  2095. "Useless DefinitionID declaration",
  2096. E_USER_WARNING
  2097. );
  2098. }
  2099. }
  2100. }
  2101. // initialize it
  2102. $def = $this->initDefinition($type);
  2103. $def->optimized = $optimized;
  2104. return $def;
  2105. }
  2106. throw new HTMLPurifier_Exception("The impossible happened!");
  2107. }
  2108. /**
  2109. * Initialise definition
  2110. *
  2111. * @param string $type What type of definition to create
  2112. *
  2113. * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
  2114. * @throws HTMLPurifier_Exception
  2115. */
  2116. private function initDefinition($type)
  2117. {
  2118. // quick checks failed, let's create the object
  2119. if ($type == 'HTML') {
  2120. $def = new HTMLPurifier_HTMLDefinition();
  2121. } elseif ($type == 'CSS') {
  2122. $def = new HTMLPurifier_CSSDefinition();
  2123. } elseif ($type == 'URI') {
  2124. $def = new HTMLPurifier_URIDefinition();
  2125. } else {
  2126. throw new HTMLPurifier_Exception(
  2127. "Definition of $type type not supported"
  2128. );
  2129. }
  2130. $this->definitions[$type] = $def;
  2131. return $def;
  2132. }
  2133. public function maybeGetRawDefinition($name)
  2134. {
  2135. return $this->getDefinition($name, true, true);
  2136. }
  2137. /**
  2138. * @return HTMLPurifier_HTMLDefinition
  2139. */
  2140. public function maybeGetRawHTMLDefinition()
  2141. {
  2142. return $this->getDefinition('HTML', true, true);
  2143. }
  2144. /**
  2145. * @return HTMLPurifier_CSSDefinition
  2146. */
  2147. public function maybeGetRawCSSDefinition()
  2148. {
  2149. return $this->getDefinition('CSS', true, true);
  2150. }
  2151. /**
  2152. * @return HTMLPurifier_URIDefinition
  2153. */
  2154. public function maybeGetRawURIDefinition()
  2155. {
  2156. return $this->getDefinition('URI', true, true);
  2157. }
  2158. /**
  2159. * Loads configuration values from an array with the following structure:
  2160. * Namespace.Directive => Value
  2161. *
  2162. * @param array $config_array Configuration associative array
  2163. */
  2164. public function loadArray($config_array)
  2165. {
  2166. if ($this->isFinalized('Cannot load directives after finalization')) {
  2167. return;
  2168. }
  2169. foreach ($config_array as $key => $value) {
  2170. $key = str_replace('_', '.', $key);
  2171. if (strpos($key, '.') !== false) {
  2172. $this->set($key, $value);
  2173. } else {
  2174. $namespace = $key;
  2175. $namespace_values = $value;
  2176. foreach ($namespace_values as $directive => $value2) {
  2177. $this->set($namespace .'.'. $directive, $value2);
  2178. }
  2179. }
  2180. }
  2181. }
  2182. /**
  2183. * Returns a list of array(namespace, directive) for all directives
  2184. * that are allowed in a web-form context as per an allowed
  2185. * namespaces/directives list.
  2186. *
  2187. * @param array $allowed List of allowed namespaces/directives
  2188. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2189. *
  2190. * @return array
  2191. */
  2192. public static function getAllowedDirectivesForForm($allowed, $schema = null)
  2193. {
  2194. if (!$schema) {
  2195. $schema = HTMLPurifier_ConfigSchema::instance();
  2196. }
  2197. if ($allowed !== true) {
  2198. if (is_string($allowed)) {
  2199. $allowed = array($allowed);
  2200. }
  2201. $allowed_ns = array();
  2202. $allowed_directives = array();
  2203. $blacklisted_directives = array();
  2204. foreach ($allowed as $ns_or_directive) {
  2205. if (strpos($ns_or_directive, '.') !== false) {
  2206. // directive
  2207. if ($ns_or_directive[0] == '-') {
  2208. $blacklisted_directives[substr($ns_or_directive, 1)] = true;
  2209. } else {
  2210. $allowed_directives[$ns_or_directive] = true;
  2211. }
  2212. } else {
  2213. // namespace
  2214. $allowed_ns[$ns_or_directive] = true;
  2215. }
  2216. }
  2217. }
  2218. $ret = array();
  2219. foreach ($schema->info as $key => $def) {
  2220. list($ns, $directive) = explode('.', $key, 2);
  2221. if ($allowed !== true) {
  2222. if (isset($blacklisted_directives["$ns.$directive"])) {
  2223. continue;
  2224. }
  2225. if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
  2226. continue;
  2227. }
  2228. }
  2229. if (isset($def->isAlias)) {
  2230. continue;
  2231. }
  2232. if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
  2233. continue;
  2234. }
  2235. $ret[] = array($ns, $directive);
  2236. }
  2237. return $ret;
  2238. }
  2239. /**
  2240. * Loads configuration values from $_GET/$_POST that were posted
  2241. * via ConfigForm
  2242. *
  2243. * @param array $array $_GET or $_POST array to import
  2244. * @param string|bool $index Index/name that the config variables are in
  2245. * @param array|bool $allowed List of allowed namespaces/directives
  2246. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2247. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2248. *
  2249. * @return mixed
  2250. */
  2251. public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2252. {
  2253. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
  2254. $config = HTMLPurifier_Config::create($ret, $schema);
  2255. return $config;
  2256. }
  2257. /**
  2258. * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
  2259. *
  2260. * @param array $array $_GET or $_POST array to import
  2261. * @param string|bool $index Index/name that the config variables are in
  2262. * @param array|bool $allowed List of allowed namespaces/directives
  2263. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2264. */
  2265. public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
  2266. {
  2267. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
  2268. $this->loadArray($ret);
  2269. }
  2270. /**
  2271. * Prepares an array from a form into something usable for the more
  2272. * strict parts of HTMLPurifier_Config
  2273. *
  2274. * @param array $array $_GET or $_POST array to import
  2275. * @param string|bool $index Index/name that the config variables are in
  2276. * @param array|bool $allowed List of allowed namespaces/directives
  2277. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2278. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2279. *
  2280. * @return array
  2281. */
  2282. public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2283. {
  2284. if ($index !== false) {
  2285. $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
  2286. }
  2287. $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
  2288. $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
  2289. $ret = array();
  2290. foreach ($allowed as $key) {
  2291. list($ns, $directive) = $key;
  2292. $skey = "$ns.$directive";
  2293. if (!empty($array["Null_$skey"])) {
  2294. $ret[$ns][$directive] = null;
  2295. continue;
  2296. }
  2297. if (!isset($array[$skey])) {
  2298. continue;
  2299. }
  2300. $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
  2301. $ret[$ns][$directive] = $value;
  2302. }
  2303. return $ret;
  2304. }
  2305. /**
  2306. * Loads configuration values from an ini file
  2307. *
  2308. * @param string $filename Name of ini file
  2309. */
  2310. public function loadIni($filename)
  2311. {
  2312. if ($this->isFinalized('Cannot load directives after finalization')) {
  2313. return;
  2314. }
  2315. $array = parse_ini_file($filename, true);
  2316. $this->loadArray($array);
  2317. }
  2318. /**
  2319. * Checks whether or not the configuration object is finalized.
  2320. *
  2321. * @param string|bool $error String error message, or false for no error
  2322. *
  2323. * @return bool
  2324. */
  2325. public function isFinalized($error = false)
  2326. {
  2327. if ($this->finalized && $error) {
  2328. $this->triggerError($error, E_USER_ERROR);
  2329. }
  2330. return $this->finalized;
  2331. }
  2332. /**
  2333. * Finalizes configuration only if auto finalize is on and not
  2334. * already finalized
  2335. */
  2336. public function autoFinalize()
  2337. {
  2338. if ($this->autoFinalize) {
  2339. $this->finalize();
  2340. } else {
  2341. $this->plist->squash(true);
  2342. }
  2343. }
  2344. /**
  2345. * Finalizes a configuration object, prohibiting further change
  2346. */
  2347. public function finalize()
  2348. {
  2349. $this->finalized = true;
  2350. $this->parser = null;
  2351. }
  2352. /**
  2353. * Produces a nicely formatted error message by supplying the
  2354. * stack frame information OUTSIDE of HTMLPurifier_Config.
  2355. *
  2356. * @param string $msg An error message
  2357. * @param int $no An error number
  2358. */
  2359. protected function triggerError($msg, $no)
  2360. {
  2361. // determine previous stack frame
  2362. $extra = '';
  2363. if ($this->chatty) {
  2364. $trace = debug_backtrace();
  2365. // zip(tail(trace), trace) -- but PHP is not Haskell har har
  2366. for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
  2367. // XXX this is not correct on some versions of HTML Purifier
  2368. if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
  2369. continue;
  2370. }
  2371. $frame = $trace[$i];
  2372. $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
  2373. break;
  2374. }
  2375. }
  2376. trigger_error($msg . $extra, $no);
  2377. }
  2378. /**
  2379. * Returns a serialized form of the configuration object that can
  2380. * be reconstituted.
  2381. *
  2382. * @return string
  2383. */
  2384. public function serialize()
  2385. {
  2386. $this->getDefinition('HTML');
  2387. $this->getDefinition('CSS');
  2388. $this->getDefinition('URI');
  2389. return serialize($this);
  2390. }
  2391. }
  2392. /**
  2393. * Configuration definition, defines directives and their defaults.
  2394. */
  2395. class HTMLPurifier_ConfigSchema
  2396. {
  2397. /**
  2398. * Defaults of the directives and namespaces.
  2399. * @type array
  2400. * @note This shares the exact same structure as HTMLPurifier_Config::$conf
  2401. */
  2402. public $defaults = array();
  2403. /**
  2404. * The default property list. Do not edit this property list.
  2405. * @type array
  2406. */
  2407. public $defaultPlist;
  2408. /**
  2409. * Definition of the directives.
  2410. * The structure of this is:
  2411. *
  2412. * array(
  2413. * 'Namespace' => array(
  2414. * 'Directive' => new stdClass(),
  2415. * )
  2416. * )
  2417. *
  2418. * The stdClass may have the following properties:
  2419. *
  2420. * - If isAlias isn't set:
  2421. * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
  2422. * - allow_null: If set, this directive allows null values
  2423. * - aliases: If set, an associative array of value aliases to real values
  2424. * - allowed: If set, a lookup array of allowed (string) values
  2425. * - If isAlias is set:
  2426. * - namespace: Namespace this directive aliases to
  2427. * - name: Directive name this directive aliases to
  2428. *
  2429. * In certain degenerate cases, stdClass will actually be an integer. In
  2430. * that case, the value is equivalent to an stdClass with the type
  2431. * property set to the integer. If the integer is negative, type is
  2432. * equal to the absolute value of integer, and allow_null is true.
  2433. *
  2434. * This class is friendly with HTMLPurifier_Config. If you need introspection
  2435. * about the schema, you're better of using the ConfigSchema_Interchange,
  2436. * which uses more memory but has much richer information.
  2437. * @type array
  2438. */
  2439. public $info = array();
  2440. /**
  2441. * Application-wide singleton
  2442. * @type HTMLPurifier_ConfigSchema
  2443. */
  2444. protected static $singleton;
  2445. public function __construct()
  2446. {
  2447. $this->defaultPlist = new HTMLPurifier_PropertyList();
  2448. }
  2449. /**
  2450. * Unserializes the default ConfigSchema.
  2451. * @return HTMLPurifier_ConfigSchema
  2452. */
  2453. public static function makeFromSerial()
  2454. {
  2455. $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
  2456. $r = unserialize($contents);
  2457. if (!$r) {
  2458. $hash = sha1($contents);
  2459. trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
  2460. }
  2461. return $r;
  2462. }
  2463. /**
  2464. * Retrieves an instance of the application-wide configuration definition.
  2465. * @param HTMLPurifier_ConfigSchema $prototype
  2466. * @return HTMLPurifier_ConfigSchema
  2467. */
  2468. public static function instance($prototype = null)
  2469. {
  2470. if ($prototype !== null) {
  2471. HTMLPurifier_ConfigSchema::$singleton = $prototype;
  2472. } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
  2473. HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
  2474. }
  2475. return HTMLPurifier_ConfigSchema::$singleton;
  2476. }
  2477. /**
  2478. * Defines a directive for configuration
  2479. * @warning Will fail of directive's namespace is defined.
  2480. * @warning This method's signature is slightly different from the legacy
  2481. * define() static method! Beware!
  2482. * @param string $key Name of directive
  2483. * @param mixed $default Default value of directive
  2484. * @param string $type Allowed type of the directive. See
  2485. * HTMLPurifier_DirectiveDef::$type for allowed values
  2486. * @param bool $allow_null Whether or not to allow null values
  2487. */
  2488. public function add($key, $default, $type, $allow_null)
  2489. {
  2490. $obj = new stdClass();
  2491. $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
  2492. if ($allow_null) {
  2493. $obj->allow_null = true;
  2494. }
  2495. $this->info[$key] = $obj;
  2496. $this->defaults[$key] = $default;
  2497. $this->defaultPlist->set($key, $default);
  2498. }
  2499. /**
  2500. * Defines a directive value alias.
  2501. *
  2502. * Directive value aliases are convenient for developers because it lets
  2503. * them set a directive to several values and get the same result.
  2504. * @param string $key Name of Directive
  2505. * @param array $aliases Hash of aliased values to the real alias
  2506. */
  2507. public function addValueAliases($key, $aliases)
  2508. {
  2509. if (!isset($this->info[$key]->aliases)) {
  2510. $this->info[$key]->aliases = array();
  2511. }
  2512. foreach ($aliases as $alias => $real) {
  2513. $this->info[$key]->aliases[$alias] = $real;
  2514. }
  2515. }
  2516. /**
  2517. * Defines a set of allowed values for a directive.
  2518. * @warning This is slightly different from the corresponding static
  2519. * method definition.
  2520. * @param string $key Name of directive
  2521. * @param array $allowed Lookup array of allowed values
  2522. */
  2523. public function addAllowedValues($key, $allowed)
  2524. {
  2525. $this->info[$key]->allowed = $allowed;
  2526. }
  2527. /**
  2528. * Defines a directive alias for backwards compatibility
  2529. * @param string $key Directive that will be aliased
  2530. * @param string $new_key Directive that the alias will be to
  2531. */
  2532. public function addAlias($key, $new_key)
  2533. {
  2534. $obj = new stdClass;
  2535. $obj->key = $new_key;
  2536. $obj->isAlias = true;
  2537. $this->info[$key] = $obj;
  2538. }
  2539. /**
  2540. * Replaces any stdClass that only has the type property with type integer.
  2541. */
  2542. public function postProcess()
  2543. {
  2544. foreach ($this->info as $key => $v) {
  2545. if (count((array) $v) == 1) {
  2546. $this->info[$key] = $v->type;
  2547. } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
  2548. $this->info[$key] = -$v->type;
  2549. }
  2550. }
  2551. }
  2552. }
  2553. /**
  2554. * @todo Unit test
  2555. */
  2556. class HTMLPurifier_ContentSets
  2557. {
  2558. /**
  2559. * List of content set strings (pipe separators) indexed by name.
  2560. * @type array
  2561. */
  2562. public $info = array();
  2563. /**
  2564. * List of content set lookups (element => true) indexed by name.
  2565. * @type array
  2566. * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
  2567. */
  2568. public $lookup = array();
  2569. /**
  2570. * Synchronized list of defined content sets (keys of info).
  2571. * @type array
  2572. */
  2573. protected $keys = array();
  2574. /**
  2575. * Synchronized list of defined content values (values of info).
  2576. * @type array
  2577. */
  2578. protected $values = array();
  2579. /**
  2580. * Merges in module's content sets, expands identifiers in the content
  2581. * sets and populates the keys, values and lookup member variables.
  2582. * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
  2583. */
  2584. public function __construct($modules)
  2585. {
  2586. if (!is_array($modules)) {
  2587. $modules = array($modules);
  2588. }
  2589. // populate content_sets based on module hints
  2590. // sorry, no way of overloading
  2591. foreach ($modules as $module) {
  2592. foreach ($module->content_sets as $key => $value) {
  2593. $temp = $this->convertToLookup($value);
  2594. if (isset($this->lookup[$key])) {
  2595. // add it into the existing content set
  2596. $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
  2597. } else {
  2598. $this->lookup[$key] = $temp;
  2599. }
  2600. }
  2601. }
  2602. $old_lookup = false;
  2603. while ($old_lookup !== $this->lookup) {
  2604. $old_lookup = $this->lookup;
  2605. foreach ($this->lookup as $i => $set) {
  2606. $add = array();
  2607. foreach ($set as $element => $x) {
  2608. if (isset($this->lookup[$element])) {
  2609. $add += $this->lookup[$element];
  2610. unset($this->lookup[$i][$element]);
  2611. }
  2612. }
  2613. $this->lookup[$i] += $add;
  2614. }
  2615. }
  2616. foreach ($this->lookup as $key => $lookup) {
  2617. $this->info[$key] = implode(' | ', array_keys($lookup));
  2618. }
  2619. $this->keys = array_keys($this->info);
  2620. $this->values = array_values($this->info);
  2621. }
  2622. /**
  2623. * Accepts a definition; generates and assigns a ChildDef for it
  2624. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
  2625. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2626. */
  2627. public function generateChildDef(&$def, $module)
  2628. {
  2629. if (!empty($def->child)) { // already done!
  2630. return;
  2631. }
  2632. $content_model = $def->content_model;
  2633. if (is_string($content_model)) {
  2634. // Assume that $this->keys is alphanumeric
  2635. $def->content_model = preg_replace_callback(
  2636. '/\b(' . implode('|', $this->keys) . ')\b/',
  2637. array($this, 'generateChildDefCallback'),
  2638. $content_model
  2639. );
  2640. //$def->content_model = str_replace(
  2641. // $this->keys, $this->values, $content_model);
  2642. }
  2643. $def->child = $this->getChildDef($def, $module);
  2644. }
  2645. public function generateChildDefCallback($matches)
  2646. {
  2647. return $this->info[$matches[0]];
  2648. }
  2649. /**
  2650. * Instantiates a ChildDef based on content_model and content_model_type
  2651. * member variables in HTMLPurifier_ElementDef
  2652. * @note This will also defer to modules for custom HTMLPurifier_ChildDef
  2653. * subclasses that need content set expansion
  2654. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
  2655. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2656. * @return HTMLPurifier_ChildDef corresponding to ElementDef
  2657. */
  2658. public function getChildDef($def, $module)
  2659. {
  2660. $value = $def->content_model;
  2661. if (is_object($value)) {
  2662. trigger_error(
  2663. 'Literal object child definitions should be stored in '.
  2664. 'ElementDef->child not ElementDef->content_model',
  2665. E_USER_NOTICE
  2666. );
  2667. return $value;
  2668. }
  2669. switch ($def->content_model_type) {
  2670. case 'required':
  2671. return new HTMLPurifier_ChildDef_Required($value);
  2672. case 'optional':
  2673. return new HTMLPurifier_ChildDef_Optional($value);
  2674. case 'empty':
  2675. return new HTMLPurifier_ChildDef_Empty();
  2676. case 'custom':
  2677. return new HTMLPurifier_ChildDef_Custom($value);
  2678. }
  2679. // defer to its module
  2680. $return = false;
  2681. if ($module->defines_child_def) { // save a func call
  2682. $return = $module->getChildDef($def);
  2683. }
  2684. if ($return !== false) {
  2685. return $return;
  2686. }
  2687. // error-out
  2688. trigger_error(
  2689. 'Could not determine which ChildDef class to instantiate',
  2690. E_USER_ERROR
  2691. );
  2692. return false;
  2693. }
  2694. /**
  2695. * Converts a string list of elements separated by pipes into
  2696. * a lookup array.
  2697. * @param string $string List of elements
  2698. * @return array Lookup array of elements
  2699. */
  2700. protected function convertToLookup($string)
  2701. {
  2702. $array = explode('|', str_replace(' ', '', $string));
  2703. $ret = array();
  2704. foreach ($array as $k) {
  2705. $ret[$k] = true;
  2706. }
  2707. return $ret;
  2708. }
  2709. }
  2710. /**
  2711. * Registry object that contains information about the current context.
  2712. * @warning Is a bit buggy when variables are set to null: it thinks
  2713. * they don't exist! So use false instead, please.
  2714. * @note Since the variables Context deals with may not be objects,
  2715. * references are very important here! Do not remove!
  2716. */
  2717. class HTMLPurifier_Context
  2718. {
  2719. /**
  2720. * Private array that stores the references.
  2721. * @type array
  2722. */
  2723. private $_storage = array();
  2724. /**
  2725. * Registers a variable into the context.
  2726. * @param string $name String name
  2727. * @param mixed $ref Reference to variable to be registered
  2728. */
  2729. public function register($name, &$ref)
  2730. {
  2731. if (array_key_exists($name, $this->_storage)) {
  2732. trigger_error(
  2733. "Name $name produces collision, cannot re-register",
  2734. E_USER_ERROR
  2735. );
  2736. return;
  2737. }
  2738. $this->_storage[$name] =& $ref;
  2739. }
  2740. /**
  2741. * Retrieves a variable reference from the context.
  2742. * @param string $name String name
  2743. * @param bool $ignore_error Boolean whether or not to ignore error
  2744. * @return mixed
  2745. */
  2746. public function &get($name, $ignore_error = false)
  2747. {
  2748. if (!array_key_exists($name, $this->_storage)) {
  2749. if (!$ignore_error) {
  2750. trigger_error(
  2751. "Attempted to retrieve non-existent variable $name",
  2752. E_USER_ERROR
  2753. );
  2754. }
  2755. $var = null; // so we can return by reference
  2756. return $var;
  2757. }
  2758. return $this->_storage[$name];
  2759. }
  2760. /**
  2761. * Destroys a variable in the context.
  2762. * @param string $name String name
  2763. */
  2764. public function destroy($name)
  2765. {
  2766. if (!array_key_exists($name, $this->_storage)) {
  2767. trigger_error(
  2768. "Attempted to destroy non-existent variable $name",
  2769. E_USER_ERROR
  2770. );
  2771. return;
  2772. }
  2773. unset($this->_storage[$name]);
  2774. }
  2775. /**
  2776. * Checks whether or not the variable exists.
  2777. * @param string $name String name
  2778. * @return bool
  2779. */
  2780. public function exists($name)
  2781. {
  2782. return array_key_exists($name, $this->_storage);
  2783. }
  2784. /**
  2785. * Loads a series of variables from an associative array
  2786. * @param array $context_array Assoc array of variables to load
  2787. */
  2788. public function loadArray($context_array)
  2789. {
  2790. foreach ($context_array as $key => $discard) {
  2791. $this->register($key, $context_array[$key]);
  2792. }
  2793. }
  2794. }
  2795. /**
  2796. * Abstract class representing Definition cache managers that implements
  2797. * useful common methods and is a factory.
  2798. * @todo Create a separate maintenance file advanced users can use to
  2799. * cache their custom HTMLDefinition, which can be loaded
  2800. * via a configuration directive
  2801. * @todo Implement memcached
  2802. */
  2803. abstract class HTMLPurifier_DefinitionCache
  2804. {
  2805. /**
  2806. * @type string
  2807. */
  2808. public $type;
  2809. /**
  2810. * @param string $type Type of definition objects this instance of the
  2811. * cache will handle.
  2812. */
  2813. public function __construct($type)
  2814. {
  2815. $this->type = $type;
  2816. }
  2817. /**
  2818. * Generates a unique identifier for a particular configuration
  2819. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  2820. * @return string
  2821. */
  2822. public function generateKey($config)
  2823. {
  2824. return $config->version . ',' . // possibly replace with function calls
  2825. $config->getBatchSerial($this->type) . ',' .
  2826. $config->get($this->type . '.DefinitionRev');
  2827. }
  2828. /**
  2829. * Tests whether or not a key is old with respect to the configuration's
  2830. * version and revision number.
  2831. * @param string $key Key to test
  2832. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
  2833. * @return bool
  2834. */
  2835. public function isOld($key, $config)
  2836. {
  2837. if (substr_count($key, ',') < 2) {
  2838. return true;
  2839. }
  2840. list($version, $hash, $revision) = explode(',', $key, 3);
  2841. $compare = version_compare($version, $config->version);
  2842. // version mismatch, is always old
  2843. if ($compare != 0) {
  2844. return true;
  2845. }
  2846. // versions match, ids match, check revision number
  2847. if ($hash == $config->getBatchSerial($this->type) &&
  2848. $revision < $config->get($this->type . '.DefinitionRev')) {
  2849. return true;
  2850. }
  2851. return false;
  2852. }
  2853. /**
  2854. * Checks if a definition's type jives with the cache's type
  2855. * @note Throws an error on failure
  2856. * @param HTMLPurifier_Definition $def Definition object to check
  2857. * @return bool true if good, false if not
  2858. */
  2859. public function checkDefType($def)
  2860. {
  2861. if ($def->type !== $this->type) {
  2862. trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
  2863. return false;
  2864. }
  2865. return true;
  2866. }
  2867. /**
  2868. * Adds a definition object to the cache
  2869. * @param HTMLPurifier_Definition $def
  2870. * @param HTMLPurifier_Config $config
  2871. */
  2872. abstract public function add($def, $config);
  2873. /**
  2874. * Unconditionally saves a definition object to the cache
  2875. * @param HTMLPurifier_Definition $def
  2876. * @param HTMLPurifier_Config $config
  2877. */
  2878. abstract public function set($def, $config);
  2879. /**
  2880. * Replace an object in the cache
  2881. * @param HTMLPurifier_Definition $def
  2882. * @param HTMLPurifier_Config $config
  2883. */
  2884. abstract public function replace($def, $config);
  2885. /**
  2886. * Retrieves a definition object from the cache
  2887. * @param HTMLPurifier_Config $config
  2888. */
  2889. abstract public function get($config);
  2890. /**
  2891. * Removes a definition object to the cache
  2892. * @param HTMLPurifier_Config $config
  2893. */
  2894. abstract public function remove($config);
  2895. /**
  2896. * Clears all objects from cache
  2897. * @param HTMLPurifier_Config $config
  2898. */
  2899. abstract public function flush($config);
  2900. /**
  2901. * Clears all expired (older version or revision) objects from cache
  2902. * @note Be careful implementing this method as flush. Flush must
  2903. * not interfere with other Definition types, and cleanup()
  2904. * should not be repeatedly called by userland code.
  2905. * @param HTMLPurifier_Config $config
  2906. */
  2907. abstract public function cleanup($config);
  2908. }
  2909. /**
  2910. * Responsible for creating definition caches.
  2911. */
  2912. class HTMLPurifier_DefinitionCacheFactory
  2913. {
  2914. /**
  2915. * @type array
  2916. */
  2917. protected $caches = array('Serializer' => array());
  2918. /**
  2919. * @type array
  2920. */
  2921. protected $implementations = array();
  2922. /**
  2923. * @type HTMLPurifier_DefinitionCache_Decorator[]
  2924. */
  2925. protected $decorators = array();
  2926. /**
  2927. * Initialize default decorators
  2928. */
  2929. public function setup()
  2930. {
  2931. $this->addDecorator('Cleanup');
  2932. }
  2933. /**
  2934. * Retrieves an instance of global definition cache factory.
  2935. * @param HTMLPurifier_DefinitionCacheFactory $prototype
  2936. * @return HTMLPurifier_DefinitionCacheFactory
  2937. */
  2938. public static function instance($prototype = null)
  2939. {
  2940. static $instance;
  2941. if ($prototype !== null) {
  2942. $instance = $prototype;
  2943. } elseif ($instance === null || $prototype === true) {
  2944. $instance = new HTMLPurifier_DefinitionCacheFactory();
  2945. $instance->setup();
  2946. }
  2947. return $instance;
  2948. }
  2949. /**
  2950. * Registers a new definition cache object
  2951. * @param string $short Short name of cache object, for reference
  2952. * @param string $long Full class name of cache object, for construction
  2953. */
  2954. public function register($short, $long)
  2955. {
  2956. $this->implementations[$short] = $long;
  2957. }
  2958. /**
  2959. * Factory method that creates a cache object based on configuration
  2960. * @param string $type Name of definitions handled by cache
  2961. * @param HTMLPurifier_Config $config Config instance
  2962. * @return mixed
  2963. */
  2964. public function create($type, $config)
  2965. {
  2966. $method = $config->get('Cache.DefinitionImpl');
  2967. if ($method === null) {
  2968. return new HTMLPurifier_DefinitionCache_Null($type);
  2969. }
  2970. if (!empty($this->caches[$method][$type])) {
  2971. return $this->caches[$method][$type];
  2972. }
  2973. if (isset($this->implementations[$method]) &&
  2974. class_exists($class = $this->implementations[$method], false)) {
  2975. $cache = new $class($type);
  2976. } else {
  2977. if ($method != 'Serializer') {
  2978. trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
  2979. }
  2980. $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
  2981. }
  2982. foreach ($this->decorators as $decorator) {
  2983. $new_cache = $decorator->decorate($cache);
  2984. // prevent infinite recursion in PHP 4
  2985. unset($cache);
  2986. $cache = $new_cache;
  2987. }
  2988. $this->caches[$method][$type] = $cache;
  2989. return $this->caches[$method][$type];
  2990. }
  2991. /**
  2992. * Registers a decorator to add to all new cache objects
  2993. * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
  2994. */
  2995. public function addDecorator($decorator)
  2996. {
  2997. if (is_string($decorator)) {
  2998. $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
  2999. $decorator = new $class;
  3000. }
  3001. $this->decorators[$decorator->name] = $decorator;
  3002. }
  3003. }
  3004. /**
  3005. * Represents a document type, contains information on which modules
  3006. * need to be loaded.
  3007. * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
  3008. * If structure changes, please update that function.
  3009. */
  3010. class HTMLPurifier_Doctype
  3011. {
  3012. /**
  3013. * Full name of doctype
  3014. * @type string
  3015. */
  3016. public $name;
  3017. /**
  3018. * List of standard modules (string identifiers or literal objects)
  3019. * that this doctype uses
  3020. * @type array
  3021. */
  3022. public $modules = array();
  3023. /**
  3024. * List of modules to use for tidying up code
  3025. * @type array
  3026. */
  3027. public $tidyModules = array();
  3028. /**
  3029. * Is the language derived from XML (i.e. XHTML)?
  3030. * @type bool
  3031. */
  3032. public $xml = true;
  3033. /**
  3034. * List of aliases for this doctype
  3035. * @type array
  3036. */
  3037. public $aliases = array();
  3038. /**
  3039. * Public DTD identifier
  3040. * @type string
  3041. */
  3042. public $dtdPublic;
  3043. /**
  3044. * System DTD identifier
  3045. * @type string
  3046. */
  3047. public $dtdSystem;
  3048. public function __construct(
  3049. $name = null,
  3050. $xml = true,
  3051. $modules = array(),
  3052. $tidyModules = array(),
  3053. $aliases = array(),
  3054. $dtd_public = null,
  3055. $dtd_system = null
  3056. ) {
  3057. $this->name = $name;
  3058. $this->xml = $xml;
  3059. $this->modules = $modules;
  3060. $this->tidyModules = $tidyModules;
  3061. $this->aliases = $aliases;
  3062. $this->dtdPublic = $dtd_public;
  3063. $this->dtdSystem = $dtd_system;
  3064. }
  3065. }
  3066. class HTMLPurifier_DoctypeRegistry
  3067. {
  3068. /**
  3069. * Hash of doctype names to doctype objects.
  3070. * @type array
  3071. */
  3072. protected $doctypes;
  3073. /**
  3074. * Lookup table of aliases to real doctype names.
  3075. * @type array
  3076. */
  3077. protected $aliases;
  3078. /**
  3079. * Registers a doctype to the registry
  3080. * @note Accepts a fully-formed doctype object, or the
  3081. * parameters for constructing a doctype object
  3082. * @param string $doctype Name of doctype or literal doctype object
  3083. * @param bool $xml
  3084. * @param array $modules Modules doctype will load
  3085. * @param array $tidy_modules Modules doctype will load for certain modes
  3086. * @param array $aliases Alias names for doctype
  3087. * @param string $dtd_public
  3088. * @param string $dtd_system
  3089. * @return HTMLPurifier_Doctype Editable registered doctype
  3090. */
  3091. public function register(
  3092. $doctype,
  3093. $xml = true,
  3094. $modules = array(),
  3095. $tidy_modules = array(),
  3096. $aliases = array(),
  3097. $dtd_public = null,
  3098. $dtd_system = null
  3099. ) {
  3100. if (!is_array($modules)) {
  3101. $modules = array($modules);
  3102. }
  3103. if (!is_array($tidy_modules)) {
  3104. $tidy_modules = array($tidy_modules);
  3105. }
  3106. if (!is_array($aliases)) {
  3107. $aliases = array($aliases);
  3108. }
  3109. if (!is_object($doctype)) {
  3110. $doctype = new HTMLPurifier_Doctype(
  3111. $doctype,
  3112. $xml,
  3113. $modules,
  3114. $tidy_modules,
  3115. $aliases,
  3116. $dtd_public,
  3117. $dtd_system
  3118. );
  3119. }
  3120. $this->doctypes[$doctype->name] = $doctype;
  3121. $name = $doctype->name;
  3122. // hookup aliases
  3123. foreach ($doctype->aliases as $alias) {
  3124. if (isset($this->doctypes[$alias])) {
  3125. continue;
  3126. }
  3127. $this->aliases[$alias] = $name;
  3128. }
  3129. // remove old aliases
  3130. if (isset($this->aliases[$name])) {
  3131. unset($this->aliases[$name]);
  3132. }
  3133. return $doctype;
  3134. }
  3135. /**
  3136. * Retrieves reference to a doctype of a certain name
  3137. * @note This function resolves aliases
  3138. * @note When possible, use the more fully-featured make()
  3139. * @param string $doctype Name of doctype
  3140. * @return HTMLPurifier_Doctype Editable doctype object
  3141. */
  3142. public function get($doctype)
  3143. {
  3144. if (isset($this->aliases[$doctype])) {
  3145. $doctype = $this->aliases[$doctype];
  3146. }
  3147. if (!isset($this->doctypes[$doctype])) {
  3148. trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
  3149. $anon = new HTMLPurifier_Doctype($doctype);
  3150. return $anon;
  3151. }
  3152. return $this->doctypes[$doctype];
  3153. }
  3154. /**
  3155. * Creates a doctype based on a configuration object,
  3156. * will perform initialization on the doctype
  3157. * @note Use this function to get a copy of doctype that config
  3158. * can hold on to (this is necessary in order to tell
  3159. * Generator whether or not the current document is XML
  3160. * based or not).
  3161. * @param HTMLPurifier_Config $config
  3162. * @return HTMLPurifier_Doctype
  3163. */
  3164. public function make($config)
  3165. {
  3166. return clone $this->get($this->getDoctypeFromConfig($config));
  3167. }
  3168. /**
  3169. * Retrieves the doctype from the configuration object
  3170. * @param HTMLPurifier_Config $config
  3171. * @return string
  3172. */
  3173. public function getDoctypeFromConfig($config)
  3174. {
  3175. // recommended test
  3176. $doctype = $config->get('HTML.Doctype');
  3177. if (!empty($doctype)) {
  3178. return $doctype;
  3179. }
  3180. $doctype = $config->get('HTML.CustomDoctype');
  3181. if (!empty($doctype)) {
  3182. return $doctype;
  3183. }
  3184. // backwards-compatibility
  3185. if ($config->get('HTML.XHTML')) {
  3186. $doctype = 'XHTML 1.0';
  3187. } else {
  3188. $doctype = 'HTML 4.01';
  3189. }
  3190. if ($config->get('HTML.Strict')) {
  3191. $doctype .= ' Strict';
  3192. } else {
  3193. $doctype .= ' Transitional';
  3194. }
  3195. return $doctype;
  3196. }
  3197. }
  3198. /**
  3199. * Structure that stores an HTML element definition. Used by
  3200. * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
  3201. * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
  3202. * Please update that class too.
  3203. * @warning If you add new properties to this class, you MUST update
  3204. * the mergeIn() method.
  3205. */
  3206. class HTMLPurifier_ElementDef
  3207. {
  3208. /**
  3209. * Does the definition work by itself, or is it created solely
  3210. * for the purpose of merging into another definition?
  3211. * @type bool
  3212. */
  3213. public $standalone = true;
  3214. /**
  3215. * Associative array of attribute name to HTMLPurifier_AttrDef.
  3216. * @type array
  3217. * @note Before being processed by HTMLPurifier_AttrCollections
  3218. * when modules are finalized during
  3219. * HTMLPurifier_HTMLDefinition->setup(), this array may also
  3220. * contain an array at index 0 that indicates which attribute
  3221. * collections to load into the full array. It may also
  3222. * contain string indentifiers in lieu of HTMLPurifier_AttrDef,
  3223. * see HTMLPurifier_AttrTypes on how they are expanded during
  3224. * HTMLPurifier_HTMLDefinition->setup() processing.
  3225. */
  3226. public $attr = array();
  3227. // XXX: Design note: currently, it's not possible to override
  3228. // previously defined AttrTransforms without messing around with
  3229. // the final generated config. This is by design; a previous version
  3230. // used an associated list of attr_transform, but it was extremely
  3231. // easy to accidentally override other attribute transforms by
  3232. // forgetting to specify an index (and just using 0.) While we
  3233. // could check this by checking the index number and complaining,
  3234. // there is a second problem which is that it is not at all easy to
  3235. // tell when something is getting overridden. Combine this with a
  3236. // codebase where this isn't really being used, and it's perfect for
  3237. // nuking.
  3238. /**
  3239. * List of tags HTMLPurifier_AttrTransform to be done before validation.
  3240. * @type array
  3241. */
  3242. public $attr_transform_pre = array();
  3243. /**
  3244. * List of tags HTMLPurifier_AttrTransform to be done after validation.
  3245. * @type array
  3246. */
  3247. public $attr_transform_post = array();
  3248. /**
  3249. * HTMLPurifier_ChildDef of this tag.
  3250. * @type HTMLPurifier_ChildDef
  3251. */
  3252. public $child;
  3253. /**
  3254. * Abstract string representation of internal ChildDef rules.
  3255. * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
  3256. * into an HTMLPurifier_ChildDef.
  3257. * @warning This is a temporary variable that is not available after
  3258. * being processed by HTMLDefinition
  3259. * @type string
  3260. */
  3261. public $content_model;
  3262. /**
  3263. * Value of $child->type, used to determine which ChildDef to use,
  3264. * used in combination with $content_model.
  3265. * @warning This must be lowercase
  3266. * @warning This is a temporary variable that is not available after
  3267. * being processed by HTMLDefinition
  3268. * @type string
  3269. */
  3270. public $content_model_type;
  3271. /**
  3272. * Does the element have a content model (#PCDATA | Inline)*? This
  3273. * is important for chameleon ins and del processing in
  3274. * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
  3275. * have to worry about this one.
  3276. * @type bool
  3277. */
  3278. public $descendants_are_inline = false;
  3279. /**
  3280. * List of the names of required attributes this element has.
  3281. * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
  3282. * @type array
  3283. */
  3284. public $required_attr = array();
  3285. /**
  3286. * Lookup table of tags excluded from all descendants of this tag.
  3287. * @type array
  3288. * @note SGML permits exclusions for all descendants, but this is
  3289. * not possible with DTDs or XML Schemas. W3C has elected to
  3290. * use complicated compositions of content_models to simulate
  3291. * exclusion for children, but we go the simpler, SGML-style
  3292. * route of flat-out exclusions, which correctly apply to
  3293. * all descendants and not just children. Note that the XHTML
  3294. * Modularization Abstract Modules are blithely unaware of such
  3295. * distinctions.
  3296. */
  3297. public $excludes = array();
  3298. /**
  3299. * This tag is explicitly auto-closed by the following tags.
  3300. * @type array
  3301. */
  3302. public $autoclose = array();
  3303. /**
  3304. * If a foreign element is found in this element, test if it is
  3305. * allowed by this sub-element; if it is, instead of closing the
  3306. * current element, place it inside this element.
  3307. * @type string
  3308. */
  3309. public $wrap;
  3310. /**
  3311. * Whether or not this is a formatting element affected by the
  3312. * "Active Formatting Elements" algorithm.
  3313. * @type bool
  3314. */
  3315. public $formatting;
  3316. /**
  3317. * Low-level factory constructor for creating new standalone element defs
  3318. */
  3319. public static function create($content_model, $content_model_type, $attr)
  3320. {
  3321. $def = new HTMLPurifier_ElementDef();
  3322. $def->content_model = $content_model;
  3323. $def->content_model_type = $content_model_type;
  3324. $def->attr = $attr;
  3325. return $def;
  3326. }
  3327. /**
  3328. * Merges the values of another element definition into this one.
  3329. * Values from the new element def take precedence if a value is
  3330. * not mergeable.
  3331. * @param HTMLPurifier_ElementDef $def
  3332. */
  3333. public function mergeIn($def)
  3334. {
  3335. // later keys takes precedence
  3336. foreach ($def->attr as $k => $v) {
  3337. if ($k === 0) {
  3338. // merge in the includes
  3339. // sorry, no way to override an include
  3340. foreach ($v as $v2) {
  3341. $this->attr[0][] = $v2;
  3342. }
  3343. continue;
  3344. }
  3345. if ($v === false) {
  3346. if (isset($this->attr[$k])) {
  3347. unset($this->attr[$k]);
  3348. }
  3349. continue;
  3350. }
  3351. $this->attr[$k] = $v;
  3352. }
  3353. $this->_mergeAssocArray($this->excludes, $def->excludes);
  3354. $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
  3355. $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
  3356. if (!empty($def->content_model)) {
  3357. $this->content_model =
  3358. str_replace("#SUPER", $this->content_model, $def->content_model);
  3359. $this->child = false;
  3360. }
  3361. if (!empty($def->content_model_type)) {
  3362. $this->content_model_type = $def->content_model_type;
  3363. $this->child = false;
  3364. }
  3365. if (!is_null($def->child)) {
  3366. $this->child = $def->child;
  3367. }
  3368. if (!is_null($def->formatting)) {
  3369. $this->formatting = $def->formatting;
  3370. }
  3371. if ($def->descendants_are_inline) {
  3372. $this->descendants_are_inline = $def->descendants_are_inline;
  3373. }
  3374. }
  3375. /**
  3376. * Merges one array into another, removes values which equal false
  3377. * @param $a1 Array by reference that is merged into
  3378. * @param $a2 Array that merges into $a1
  3379. */
  3380. private function _mergeAssocArray(&$a1, $a2)
  3381. {
  3382. foreach ($a2 as $k => $v) {
  3383. if ($v === false) {
  3384. if (isset($a1[$k])) {
  3385. unset($a1[$k]);
  3386. }
  3387. continue;
  3388. }
  3389. $a1[$k] = $v;
  3390. }
  3391. }
  3392. }
  3393. /**
  3394. * A UTF-8 specific character encoder that handles cleaning and transforming.
  3395. * @note All functions in this class should be static.
  3396. */
  3397. class HTMLPurifier_Encoder
  3398. {
  3399. /**
  3400. * Constructor throws fatal error if you attempt to instantiate class
  3401. */
  3402. private function __construct()
  3403. {
  3404. trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
  3405. }
  3406. /**
  3407. * Error-handler that mutes errors, alternative to shut-up operator.
  3408. */
  3409. public static function muteErrorHandler()
  3410. {
  3411. }
  3412. /**
  3413. * iconv wrapper which mutes errors, but doesn't work around bugs.
  3414. * @param string $in Input encoding
  3415. * @param string $out Output encoding
  3416. * @param string $text The text to convert
  3417. * @return string
  3418. */
  3419. public static function unsafeIconv($in, $out, $text)
  3420. {
  3421. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  3422. $r = iconv($in, $out, $text);
  3423. restore_error_handler();
  3424. return $r;
  3425. }
  3426. /**
  3427. * iconv wrapper which mutes errors and works around bugs.
  3428. * @param string $in Input encoding
  3429. * @param string $out Output encoding
  3430. * @param string $text The text to convert
  3431. * @param int $max_chunk_size
  3432. * @return string
  3433. */
  3434. public static function iconv($in, $out, $text, $max_chunk_size = 8000)
  3435. {
  3436. $code = self::testIconvTruncateBug();
  3437. if ($code == self::ICONV_OK) {
  3438. return self::unsafeIconv($in, $out, $text);
  3439. } elseif ($code == self::ICONV_TRUNCATES) {
  3440. // we can only work around this if the input character set
  3441. // is utf-8
  3442. if ($in == 'utf-8') {
  3443. if ($max_chunk_size < 4) {
  3444. trigger_error('max_chunk_size is too small', E_USER_WARNING);
  3445. return false;
  3446. }
  3447. // split into 8000 byte chunks, but be careful to handle
  3448. // multibyte boundaries properly
  3449. if (($c = strlen($text)) <= $max_chunk_size) {
  3450. return self::unsafeIconv($in, $out, $text);
  3451. }
  3452. $r = '';
  3453. $i = 0;
  3454. while (true) {
  3455. if ($i + $max_chunk_size >= $c) {
  3456. $r .= self::unsafeIconv($in, $out, substr($text, $i));
  3457. break;
  3458. }
  3459. // wibble the boundary
  3460. if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
  3461. $chunk_size = $max_chunk_size;
  3462. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
  3463. $chunk_size = $max_chunk_size - 1;
  3464. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
  3465. $chunk_size = $max_chunk_size - 2;
  3466. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
  3467. $chunk_size = $max_chunk_size - 3;
  3468. } else {
  3469. return false; // rather confusing UTF-8...
  3470. }
  3471. $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
  3472. $r .= self::unsafeIconv($in, $out, $chunk);
  3473. $i += $chunk_size;
  3474. }
  3475. return $r;
  3476. } else {
  3477. return false;
  3478. }
  3479. } else {
  3480. return false;
  3481. }
  3482. }
  3483. /**
  3484. * Cleans a UTF-8 string for well-formedness and SGML validity
  3485. *
  3486. * It will parse according to UTF-8 and return a valid UTF8 string, with
  3487. * non-SGML codepoints excluded.
  3488. *
  3489. * Specifically, it will permit:
  3490. * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}
  3491. * Source: https://www.w3.org/TR/REC-xml/#NT-Char
  3492. * Arguably this function should be modernized to the HTML5 set
  3493. * of allowed characters:
  3494. * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream
  3495. * which simultaneously expand and restrict the set of allowed characters.
  3496. *
  3497. * @param string $str The string to clean
  3498. * @param bool $force_php
  3499. * @return string
  3500. *
  3501. * @note Just for reference, the non-SGML code points are 0 to 31 and
  3502. * 127 to 159, inclusive. However, we allow code points 9, 10
  3503. * and 13, which are the tab, line feed and carriage return
  3504. * respectively. 128 and above the code points map to multibyte
  3505. * UTF-8 representations.
  3506. *
  3507. * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
  3508. * hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
  3509. * LGPL license. Notes on what changed are inside, but in general,
  3510. * the original code transformed UTF-8 text into an array of integer
  3511. * Unicode codepoints. Understandably, transforming that back to
  3512. * a string would be somewhat expensive, so the function was modded to
  3513. * directly operate on the string. However, this discourages code
  3514. * reuse, and the logic enumerated here would be useful for any
  3515. * function that needs to be able to understand UTF-8 characters.
  3516. * As of right now, only smart lossless character encoding converters
  3517. * would need that, and I'm probably not going to implement them.
  3518. */
  3519. public static function cleanUTF8($str, $force_php = false)
  3520. {
  3521. // UTF-8 validity is checked since PHP 4.3.5
  3522. // This is an optimization: if the string is already valid UTF-8, no
  3523. // need to do PHP stuff. 99% of the time, this will be the case.
  3524. if (preg_match(
  3525. '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
  3526. $str
  3527. )) {
  3528. return $str;
  3529. }
  3530. $mState = 0; // cached expected number of octets after the current octet
  3531. // until the beginning of the next UTF8 character sequence
  3532. $mUcs4 = 0; // cached Unicode character
  3533. $mBytes = 1; // cached expected number of octets in the current sequence
  3534. // original code involved an $out that was an array of Unicode
  3535. // codepoints. Instead of having to convert back into UTF-8, we've
  3536. // decided to directly append valid UTF-8 characters onto a string
  3537. // $out once they're done. $char accumulates raw bytes, while $mUcs4
  3538. // turns into the Unicode code point, so there's some redundancy.
  3539. $out = '';
  3540. $char = '';
  3541. $len = strlen($str);
  3542. for ($i = 0; $i < $len; $i++) {
  3543. $in = ord($str{$i});
  3544. $char .= $str[$i]; // append byte to char
  3545. if (0 == $mState) {
  3546. // When mState is zero we expect either a US-ASCII character
  3547. // or a multi-octet sequence.
  3548. if (0 == (0x80 & ($in))) {
  3549. // US-ASCII, pass straight through.
  3550. if (($in <= 31 || $in == 127) &&
  3551. !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
  3552. ) {
  3553. // control characters, remove
  3554. } else {
  3555. $out .= $char;
  3556. }
  3557. // reset
  3558. $char = '';
  3559. $mBytes = 1;
  3560. } elseif (0xC0 == (0xE0 & ($in))) {
  3561. // First octet of 2 octet sequence
  3562. $mUcs4 = ($in);
  3563. $mUcs4 = ($mUcs4 & 0x1F) << 6;
  3564. $mState = 1;
  3565. $mBytes = 2;
  3566. } elseif (0xE0 == (0xF0 & ($in))) {
  3567. // First octet of 3 octet sequence
  3568. $mUcs4 = ($in);
  3569. $mUcs4 = ($mUcs4 & 0x0F) << 12;
  3570. $mState = 2;
  3571. $mBytes = 3;
  3572. } elseif (0xF0 == (0xF8 & ($in))) {
  3573. // First octet of 4 octet sequence
  3574. $mUcs4 = ($in);
  3575. $mUcs4 = ($mUcs4 & 0x07) << 18;
  3576. $mState = 3;
  3577. $mBytes = 4;
  3578. } elseif (0xF8 == (0xFC & ($in))) {
  3579. // First octet of 5 octet sequence.
  3580. //
  3581. // This is illegal because the encoded codepoint must be
  3582. // either:
  3583. // (a) not the shortest form or
  3584. // (b) outside the Unicode range of 0-0x10FFFF.
  3585. // Rather than trying to resynchronize, we will carry on
  3586. // until the end of the sequence and let the later error
  3587. // handling code catch it.
  3588. $mUcs4 = ($in);
  3589. $mUcs4 = ($mUcs4 & 0x03) << 24;
  3590. $mState = 4;
  3591. $mBytes = 5;
  3592. } elseif (0xFC == (0xFE & ($in))) {
  3593. // First octet of 6 octet sequence, see comments for 5
  3594. // octet sequence.
  3595. $mUcs4 = ($in);
  3596. $mUcs4 = ($mUcs4 & 1) << 30;
  3597. $mState = 5;
  3598. $mBytes = 6;
  3599. } else {
  3600. // Current octet is neither in the US-ASCII range nor a
  3601. // legal first octet of a multi-octet sequence.
  3602. $mState = 0;
  3603. $mUcs4 = 0;
  3604. $mBytes = 1;
  3605. $char = '';
  3606. }
  3607. } else {
  3608. // When mState is non-zero, we expect a continuation of the
  3609. // multi-octet sequence
  3610. if (0x80 == (0xC0 & ($in))) {
  3611. // Legal continuation.
  3612. $shift = ($mState - 1) * 6;
  3613. $tmp = $in;
  3614. $tmp = ($tmp & 0x0000003F) << $shift;
  3615. $mUcs4 |= $tmp;
  3616. if (0 == --$mState) {
  3617. // End of the multi-octet sequence. mUcs4 now contains
  3618. // the final Unicode codepoint to be output
  3619. // Check for illegal sequences and codepoints.
  3620. // From Unicode 3.1, non-shortest form is illegal
  3621. if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
  3622. ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
  3623. ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
  3624. (4 < $mBytes) ||
  3625. // From Unicode 3.2, surrogate characters = illegal
  3626. (($mUcs4 & 0xFFFFF800) == 0xD800) ||
  3627. // Codepoints outside the Unicode range are illegal
  3628. ($mUcs4 > 0x10FFFF)
  3629. ) {
  3630. } elseif (0xFEFF != $mUcs4 && // omit BOM
  3631. // check for valid Char unicode codepoints
  3632. (
  3633. 0x9 == $mUcs4 ||
  3634. 0xA == $mUcs4 ||
  3635. 0xD == $mUcs4 ||
  3636. (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
  3637. // 7F-9F is not strictly prohibited by XML,
  3638. // but it is non-SGML, and thus we don't allow it
  3639. (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
  3640. (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) ||
  3641. (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
  3642. )
  3643. ) {
  3644. $out .= $char;
  3645. }
  3646. // initialize UTF8 cache (reset)
  3647. $mState = 0;
  3648. $mUcs4 = 0;
  3649. $mBytes = 1;
  3650. $char = '';
  3651. }
  3652. } else {
  3653. // ((0xC0 & (*in) != 0x80) && (mState != 0))
  3654. // Incomplete multi-octet sequence.
  3655. // used to result in complete fail, but we'll reset
  3656. $mState = 0;
  3657. $mUcs4 = 0;
  3658. $mBytes = 1;
  3659. $char ='';
  3660. }
  3661. }
  3662. }
  3663. return $out;
  3664. }
  3665. /**
  3666. * Translates a Unicode codepoint into its corresponding UTF-8 character.
  3667. * @note Based on Feyd's function at
  3668. * <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
  3669. * which is in public domain.
  3670. * @note While we're going to do code point parsing anyway, a good
  3671. * optimization would be to refuse to translate code points that
  3672. * are non-SGML characters. However, this could lead to duplication.
  3673. * @note This is very similar to the unichr function in
  3674. * maintenance/generate-entity-file.php (although this is superior,
  3675. * due to its sanity checks).
  3676. */
  3677. // +----------+----------+----------+----------+
  3678. // | 33222222 | 22221111 | 111111 | |
  3679. // | 10987654 | 32109876 | 54321098 | 76543210 | bit
  3680. // +----------+----------+----------+----------+
  3681. // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
  3682. // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
  3683. // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
  3684. // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
  3685. // +----------+----------+----------+----------+
  3686. // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
  3687. // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
  3688. // +----------+----------+----------+----------+
  3689. public static function unichr($code)
  3690. {
  3691. if ($code > 1114111 or $code < 0 or
  3692. ($code >= 55296 and $code <= 57343) ) {
  3693. // bits are set outside the "valid" range as defined
  3694. // by UNICODE 4.1.0
  3695. return '';
  3696. }
  3697. $x = $y = $z = $w = 0;
  3698. if ($code < 128) {
  3699. // regular ASCII character
  3700. $x = $code;
  3701. } else {
  3702. // set up bits for UTF-8
  3703. $x = ($code & 63) | 128;
  3704. if ($code < 2048) {
  3705. $y = (($code & 2047) >> 6) | 192;
  3706. } else {
  3707. $y = (($code & 4032) >> 6) | 128;
  3708. if ($code < 65536) {
  3709. $z = (($code >> 12) & 15) | 224;
  3710. } else {
  3711. $z = (($code >> 12) & 63) | 128;
  3712. $w = (($code >> 18) & 7) | 240;
  3713. }
  3714. }
  3715. }
  3716. // set up the actual character
  3717. $ret = '';
  3718. if ($w) {
  3719. $ret .= chr($w);
  3720. }
  3721. if ($z) {
  3722. $ret .= chr($z);
  3723. }
  3724. if ($y) {
  3725. $ret .= chr($y);
  3726. }
  3727. $ret .= chr($x);
  3728. return $ret;
  3729. }
  3730. /**
  3731. * @return bool
  3732. */
  3733. public static function iconvAvailable()
  3734. {
  3735. static $iconv = null;
  3736. if ($iconv === null) {
  3737. $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
  3738. }
  3739. return $iconv;
  3740. }
  3741. /**
  3742. * Convert a string to UTF-8 based on configuration.
  3743. * @param string $str The string to convert
  3744. * @param HTMLPurifier_Config $config
  3745. * @param HTMLPurifier_Context $context
  3746. * @return string
  3747. */
  3748. public static function convertToUTF8($str, $config, $context)
  3749. {
  3750. $encoding = $config->get('Core.Encoding');
  3751. if ($encoding === 'utf-8') {
  3752. return $str;
  3753. }
  3754. static $iconv = null;
  3755. if ($iconv === null) {
  3756. $iconv = self::iconvAvailable();
  3757. }
  3758. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3759. // unaffected by bugs, since UTF-8 support all characters
  3760. $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
  3761. if ($str === false) {
  3762. // $encoding is not a valid encoding
  3763. trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
  3764. return '';
  3765. }
  3766. // If the string is bjorked by Shift_JIS or a similar encoding
  3767. // that doesn't support all of ASCII, convert the naughty
  3768. // characters to their true byte-wise ASCII/UTF-8 equivalents.
  3769. $str = strtr($str, self::testEncodingSupportsASCII($encoding));
  3770. return $str;
  3771. } elseif ($encoding === 'iso-8859-1') {
  3772. $str = utf8_encode($str);
  3773. return $str;
  3774. }
  3775. $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
  3776. if ($bug == self::ICONV_OK) {
  3777. trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
  3778. } else {
  3779. trigger_error(
  3780. 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
  3781. 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
  3782. E_USER_ERROR
  3783. );
  3784. }
  3785. }
  3786. /**
  3787. * Converts a string from UTF-8 based on configuration.
  3788. * @param string $str The string to convert
  3789. * @param HTMLPurifier_Config $config
  3790. * @param HTMLPurifier_Context $context
  3791. * @return string
  3792. * @note Currently, this is a lossy conversion, with unexpressable
  3793. * characters being omitted.
  3794. */
  3795. public static function convertFromUTF8($str, $config, $context)
  3796. {
  3797. $encoding = $config->get('Core.Encoding');
  3798. if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
  3799. $str = self::convertToASCIIDumbLossless($str);
  3800. }
  3801. if ($encoding === 'utf-8') {
  3802. return $str;
  3803. }
  3804. static $iconv = null;
  3805. if ($iconv === null) {
  3806. $iconv = self::iconvAvailable();
  3807. }
  3808. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3809. // Undo our previous fix in convertToUTF8, otherwise iconv will barf
  3810. $ascii_fix = self::testEncodingSupportsASCII($encoding);
  3811. if (!$escape && !empty($ascii_fix)) {
  3812. $clear_fix = array();
  3813. foreach ($ascii_fix as $utf8 => $native) {
  3814. $clear_fix[$utf8] = '';
  3815. }
  3816. $str = strtr($str, $clear_fix);
  3817. }
  3818. $str = strtr($str, array_flip($ascii_fix));
  3819. // Normal stuff
  3820. $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
  3821. return $str;
  3822. } elseif ($encoding === 'iso-8859-1') {
  3823. $str = utf8_decode($str);
  3824. return $str;
  3825. }
  3826. trigger_error('Encoding not supported', E_USER_ERROR);
  3827. // You might be tempted to assume that the ASCII representation
  3828. // might be OK, however, this is *not* universally true over all
  3829. // encodings. So we take the conservative route here, rather
  3830. // than forcibly turn on %Core.EscapeNonASCIICharacters
  3831. }
  3832. /**
  3833. * Lossless (character-wise) conversion of HTML to ASCII
  3834. * @param string $str UTF-8 string to be converted to ASCII
  3835. * @return string ASCII encoded string with non-ASCII character entity-ized
  3836. * @warning Adapted from MediaWiki, claiming fair use: this is a common
  3837. * algorithm. If you disagree with this license fudgery,
  3838. * implement it yourself.
  3839. * @note Uses decimal numeric entities since they are best supported.
  3840. * @note This is a DUMB function: it has no concept of keeping
  3841. * character entities that the projected character encoding
  3842. * can allow. We could possibly implement a smart version
  3843. * but that would require it to also know which Unicode
  3844. * codepoints the charset supported (not an easy task).
  3845. * @note Sort of with cleanUTF8() but it assumes that $str is
  3846. * well-formed UTF-8
  3847. */
  3848. public static function convertToASCIIDumbLossless($str)
  3849. {
  3850. $bytesleft = 0;
  3851. $result = '';
  3852. $working = 0;
  3853. $len = strlen($str);
  3854. for ($i = 0; $i < $len; $i++) {
  3855. $bytevalue = ord($str[$i]);
  3856. if ($bytevalue <= 0x7F) { //0xxx xxxx
  3857. $result .= chr($bytevalue);
  3858. $bytesleft = 0;
  3859. } elseif ($bytevalue <= 0xBF) { //10xx xxxx
  3860. $working = $working << 6;
  3861. $working += ($bytevalue & 0x3F);
  3862. $bytesleft--;
  3863. if ($bytesleft <= 0) {
  3864. $result .= "&#" . $working . ";";
  3865. }
  3866. } elseif ($bytevalue <= 0xDF) { //110x xxxx
  3867. $working = $bytevalue & 0x1F;
  3868. $bytesleft = 1;
  3869. } elseif ($bytevalue <= 0xEF) { //1110 xxxx
  3870. $working = $bytevalue & 0x0F;
  3871. $bytesleft = 2;
  3872. } else { //1111 0xxx
  3873. $working = $bytevalue & 0x07;
  3874. $bytesleft = 3;
  3875. }
  3876. }
  3877. return $result;
  3878. }
  3879. /** No bugs detected in iconv. */
  3880. const ICONV_OK = 0;
  3881. /** Iconv truncates output if converting from UTF-8 to another
  3882. * character set with //IGNORE, and a non-encodable character is found */
  3883. const ICONV_TRUNCATES = 1;
  3884. /** Iconv does not support //IGNORE, making it unusable for
  3885. * transcoding purposes */
  3886. const ICONV_UNUSABLE = 2;
  3887. /**
  3888. * glibc iconv has a known bug where it doesn't handle the magic
  3889. * //IGNORE stanza correctly. In particular, rather than ignore
  3890. * characters, it will return an EILSEQ after consuming some number
  3891. * of characters, and expect you to restart iconv as if it were
  3892. * an E2BIG. Old versions of PHP did not respect the errno, and
  3893. * returned the fragment, so as a result you would see iconv
  3894. * mysteriously truncating output. We can work around this by
  3895. * manually chopping our input into segments of about 8000
  3896. * characters, as long as PHP ignores the error code. If PHP starts
  3897. * paying attention to the error code, iconv becomes unusable.
  3898. *
  3899. * @return int Error code indicating severity of bug.
  3900. */
  3901. public static function testIconvTruncateBug()
  3902. {
  3903. static $code = null;
  3904. if ($code === null) {
  3905. // better not use iconv, otherwise infinite loop!
  3906. $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
  3907. if ($r === false) {
  3908. $code = self::ICONV_UNUSABLE;
  3909. } elseif (($c = strlen($r)) < 9000) {
  3910. $code = self::ICONV_TRUNCATES;
  3911. } elseif ($c > 9000) {
  3912. trigger_error(
  3913. 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
  3914. 'include your iconv version as per phpversion()',
  3915. E_USER_ERROR
  3916. );
  3917. } else {
  3918. $code = self::ICONV_OK;
  3919. }
  3920. }
  3921. return $code;
  3922. }
  3923. /**
  3924. * This expensive function tests whether or not a given character
  3925. * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
  3926. * fail this test, and require special processing. Variable width
  3927. * encodings shouldn't ever fail.
  3928. *
  3929. * @param string $encoding Encoding name to test, as per iconv format
  3930. * @param bool $bypass Whether or not to bypass the precompiled arrays.
  3931. * @return Array of UTF-8 characters to their corresponding ASCII,
  3932. * which can be used to "undo" any overzealous iconv action.
  3933. */
  3934. public static function testEncodingSupportsASCII($encoding, $bypass = false)
  3935. {
  3936. // All calls to iconv here are unsafe, proof by case analysis:
  3937. // If ICONV_OK, no difference.
  3938. // If ICONV_TRUNCATE, all calls involve one character inputs,
  3939. // so bug is not triggered.
  3940. // If ICONV_UNUSABLE, this call is irrelevant
  3941. static $encodings = array();
  3942. if (!$bypass) {
  3943. if (isset($encodings[$encoding])) {
  3944. return $encodings[$encoding];
  3945. }
  3946. $lenc = strtolower($encoding);
  3947. switch ($lenc) {
  3948. case 'shift_jis':
  3949. return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
  3950. case 'johab':
  3951. return array("\xE2\x82\xA9" => '\\');
  3952. }
  3953. if (strpos($lenc, 'iso-8859-') === 0) {
  3954. return array();
  3955. }
  3956. }
  3957. $ret = array();
  3958. if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
  3959. return false;
  3960. }
  3961. for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
  3962. $c = chr($i); // UTF-8 char
  3963. $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
  3964. if ($r === '' ||
  3965. // This line is needed for iconv implementations that do not
  3966. // omit characters that do not exist in the target character set
  3967. ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
  3968. ) {
  3969. // Reverse engineer: what's the UTF-8 equiv of this byte
  3970. // sequence? This assumes that there's no variable width
  3971. // encoding that doesn't support ASCII.
  3972. $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
  3973. }
  3974. }
  3975. $encodings[$encoding] = $ret;
  3976. return $ret;
  3977. }
  3978. }
  3979. /**
  3980. * Object that provides entity lookup table from entity name to character
  3981. */
  3982. class HTMLPurifier_EntityLookup
  3983. {
  3984. /**
  3985. * Assoc array of entity name to character represented.
  3986. * @type array
  3987. */
  3988. public $table;
  3989. /**
  3990. * Sets up the entity lookup table from the serialized file contents.
  3991. * @param bool $file
  3992. * @note The serialized contents are versioned, but were generated
  3993. * using the maintenance script generate_entity_file.php
  3994. * @warning This is not in constructor to help enforce the Singleton
  3995. */
  3996. public function setup($file = false)
  3997. {
  3998. if (!$file) {
  3999. $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
  4000. }
  4001. $this->table = unserialize(file_get_contents($file));
  4002. }
  4003. /**
  4004. * Retrieves sole instance of the object.
  4005. * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
  4006. * @return HTMLPurifier_EntityLookup
  4007. */
  4008. public static function instance($prototype = false)
  4009. {
  4010. // no references, since PHP doesn't copy unless modified
  4011. static $instance = null;
  4012. if ($prototype) {
  4013. $instance = $prototype;
  4014. } elseif (!$instance) {
  4015. $instance = new HTMLPurifier_EntityLookup();
  4016. $instance->setup();
  4017. }
  4018. return $instance;
  4019. }
  4020. }
  4021. // if want to implement error collecting here, we'll need to use some sort
  4022. // of global data (probably trigger_error) because it's impossible to pass
  4023. // $config or $context to the callback functions.
  4024. /**
  4025. * Handles referencing and derefencing character entities
  4026. */
  4027. class HTMLPurifier_EntityParser
  4028. {
  4029. /**
  4030. * Reference to entity lookup table.
  4031. * @type HTMLPurifier_EntityLookup
  4032. */
  4033. protected $_entity_lookup;
  4034. /**
  4035. * Callback regex string for entities in text.
  4036. * @type string
  4037. */
  4038. protected $_textEntitiesRegex;
  4039. /**
  4040. * Callback regex string for entities in attributes.
  4041. * @type string
  4042. */
  4043. protected $_attrEntitiesRegex;
  4044. /**
  4045. * Tests if the beginning of a string is a semi-optional regex
  4046. */
  4047. protected $_semiOptionalPrefixRegex;
  4048. public function __construct() {
  4049. // From
  4050. // http://stackoverflow.com/questions/15532252/why-is-reg-being-rendered-as-without-the-bounding-semicolon
  4051. $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";
  4052. // NB: three empty captures to put the fourth match in the right
  4053. // place
  4054. $this->_semiOptionalPrefixRegex = "/&()()()($semi_optional)/";
  4055. $this->_textEntitiesRegex =
  4056. '/&(?:'.
  4057. // hex
  4058. '[#]x([a-fA-F0-9]+);?|'.
  4059. // dec
  4060. '[#]0*(\d+);?|'.
  4061. // string (mandatory semicolon)
  4062. // NB: order matters: match semicolon preferentially
  4063. '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
  4064. // string (optional semicolon)
  4065. "($semi_optional)".
  4066. ')/';
  4067. $this->_attrEntitiesRegex =
  4068. '/&(?:'.
  4069. // hex
  4070. '[#]x([a-fA-F0-9]+);?|'.
  4071. // dec
  4072. '[#]0*(\d+);?|'.
  4073. // string (mandatory semicolon)
  4074. // NB: order matters: match semicolon preferentially
  4075. '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'.
  4076. // string (optional semicolon)
  4077. // don't match if trailing is equals or alphanumeric (URL
  4078. // like)
  4079. "($semi_optional)(?![=;A-Za-z0-9])".
  4080. ')/';
  4081. }
  4082. /**
  4083. * Substitute entities with the parsed equivalents. Use this on
  4084. * textual data in an HTML document (as opposed to attributes.)
  4085. *
  4086. * @param string $string String to have entities parsed.
  4087. * @return string Parsed string.
  4088. */
  4089. public function substituteTextEntities($string)
  4090. {
  4091. return preg_replace_callback(
  4092. $this->_textEntitiesRegex,
  4093. array($this, 'entityCallback'),
  4094. $string
  4095. );
  4096. }
  4097. /**
  4098. * Substitute entities with the parsed equivalents. Use this on
  4099. * attribute contents in documents.
  4100. *
  4101. * @param string $string String to have entities parsed.
  4102. * @return string Parsed string.
  4103. */
  4104. public function substituteAttrEntities($string)
  4105. {
  4106. return preg_replace_callback(
  4107. $this->_attrEntitiesRegex,
  4108. array($this, 'entityCallback'),
  4109. $string
  4110. );
  4111. }
  4112. /**
  4113. * Callback function for substituteNonSpecialEntities() that does the work.
  4114. *
  4115. * @param array $matches PCRE matches array, with 0 the entire match, and
  4116. * either index 1, 2 or 3 set with a hex value, dec value,
  4117. * or string (respectively).
  4118. * @return string Replacement string.
  4119. */
  4120. protected function entityCallback($matches)
  4121. {
  4122. $entity = $matches[0];
  4123. $hex_part = @$matches[1];
  4124. $dec_part = @$matches[2];
  4125. $named_part = empty($matches[3]) ? @$matches[4] : $matches[3];
  4126. if ($hex_part !== NULL && $hex_part !== "") {
  4127. return HTMLPurifier_Encoder::unichr(hexdec($hex_part));
  4128. } elseif ($dec_part !== NULL && $dec_part !== "") {
  4129. return HTMLPurifier_Encoder::unichr((int) $dec_part);
  4130. } else {
  4131. if (!$this->_entity_lookup) {
  4132. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  4133. }
  4134. if (isset($this->_entity_lookup->table[$named_part])) {
  4135. return $this->_entity_lookup->table[$named_part];
  4136. } else {
  4137. // exact match didn't match anything, so test if
  4138. // any of the semicolon optional match the prefix.
  4139. // Test that this is an EXACT match is important to
  4140. // prevent infinite loop
  4141. if (!empty($matches[3])) {
  4142. return preg_replace_callback(
  4143. $this->_semiOptionalPrefixRegex,
  4144. array($this, 'entityCallback'),
  4145. $entity
  4146. );
  4147. }
  4148. return $entity;
  4149. }
  4150. }
  4151. }
  4152. // LEGACY CODE BELOW
  4153. /**
  4154. * Callback regex string for parsing entities.
  4155. * @type string
  4156. */
  4157. protected $_substituteEntitiesRegex =
  4158. '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
  4159. // 1. hex 2. dec 3. string (XML style)
  4160. /**
  4161. * Decimal to parsed string conversion table for special entities.
  4162. * @type array
  4163. */
  4164. protected $_special_dec2str =
  4165. array(
  4166. 34 => '"',
  4167. 38 => '&',
  4168. 39 => "'",
  4169. 60 => '<',
  4170. 62 => '>'
  4171. );
  4172. /**
  4173. * Stripped entity names to decimal conversion table for special entities.
  4174. * @type array
  4175. */
  4176. protected $_special_ent2dec =
  4177. array(
  4178. 'quot' => 34,
  4179. 'amp' => 38,
  4180. 'lt' => 60,
  4181. 'gt' => 62
  4182. );
  4183. /**
  4184. * Substitutes non-special entities with their parsed equivalents. Since
  4185. * running this whenever you have parsed character is t3h 5uck, we run
  4186. * it before everything else.
  4187. *
  4188. * @param string $string String to have non-special entities parsed.
  4189. * @return string Parsed string.
  4190. */
  4191. public function substituteNonSpecialEntities($string)
  4192. {
  4193. // it will try to detect missing semicolons, but don't rely on it
  4194. return preg_replace_callback(
  4195. $this->_substituteEntitiesRegex,
  4196. array($this, 'nonSpecialEntityCallback'),
  4197. $string
  4198. );
  4199. }
  4200. /**
  4201. * Callback function for substituteNonSpecialEntities() that does the work.
  4202. *
  4203. * @param array $matches PCRE matches array, with 0 the entire match, and
  4204. * either index 1, 2 or 3 set with a hex value, dec value,
  4205. * or string (respectively).
  4206. * @return string Replacement string.
  4207. */
  4208. protected function nonSpecialEntityCallback($matches)
  4209. {
  4210. // replaces all but big five
  4211. $entity = $matches[0];
  4212. $is_num = (@$matches[0][1] === '#');
  4213. if ($is_num) {
  4214. $is_hex = (@$entity[2] === 'x');
  4215. $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4216. // abort for special characters
  4217. if (isset($this->_special_dec2str[$code])) {
  4218. return $entity;
  4219. }
  4220. return HTMLPurifier_Encoder::unichr($code);
  4221. } else {
  4222. if (isset($this->_special_ent2dec[$matches[3]])) {
  4223. return $entity;
  4224. }
  4225. if (!$this->_entity_lookup) {
  4226. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  4227. }
  4228. if (isset($this->_entity_lookup->table[$matches[3]])) {
  4229. return $this->_entity_lookup->table[$matches[3]];
  4230. } else {
  4231. return $entity;
  4232. }
  4233. }
  4234. }
  4235. /**
  4236. * Substitutes only special entities with their parsed equivalents.
  4237. *
  4238. * @notice We try to avoid calling this function because otherwise, it
  4239. * would have to be called a lot (for every parsed section).
  4240. *
  4241. * @param string $string String to have non-special entities parsed.
  4242. * @return string Parsed string.
  4243. */
  4244. public function substituteSpecialEntities($string)
  4245. {
  4246. return preg_replace_callback(
  4247. $this->_substituteEntitiesRegex,
  4248. array($this, 'specialEntityCallback'),
  4249. $string
  4250. );
  4251. }
  4252. /**
  4253. * Callback function for substituteSpecialEntities() that does the work.
  4254. *
  4255. * This callback has same syntax as nonSpecialEntityCallback().
  4256. *
  4257. * @param array $matches PCRE-style matches array, with 0 the entire match, and
  4258. * either index 1, 2 or 3 set with a hex value, dec value,
  4259. * or string (respectively).
  4260. * @return string Replacement string.
  4261. */
  4262. protected function specialEntityCallback($matches)
  4263. {
  4264. $entity = $matches[0];
  4265. $is_num = (@$matches[0][1] === '#');
  4266. if ($is_num) {
  4267. $is_hex = (@$entity[2] === 'x');
  4268. $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4269. return isset($this->_special_dec2str[$int]) ?
  4270. $this->_special_dec2str[$int] :
  4271. $entity;
  4272. } else {
  4273. return isset($this->_special_ent2dec[$matches[3]]) ?
  4274. $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] :
  4275. $entity;
  4276. }
  4277. }
  4278. }
  4279. /**
  4280. * Error collection class that enables HTML Purifier to report HTML
  4281. * problems back to the user
  4282. */
  4283. class HTMLPurifier_ErrorCollector
  4284. {
  4285. /**
  4286. * Identifiers for the returned error array. These are purposely numeric
  4287. * so list() can be used.
  4288. */
  4289. const LINENO = 0;
  4290. const SEVERITY = 1;
  4291. const MESSAGE = 2;
  4292. const CHILDREN = 3;
  4293. /**
  4294. * @type array
  4295. */
  4296. protected $errors;
  4297. /**
  4298. * @type array
  4299. */
  4300. protected $_current;
  4301. /**
  4302. * @type array
  4303. */
  4304. protected $_stacks = array(array());
  4305. /**
  4306. * @type HTMLPurifier_Language
  4307. */
  4308. protected $locale;
  4309. /**
  4310. * @type HTMLPurifier_Generator
  4311. */
  4312. protected $generator;
  4313. /**
  4314. * @type HTMLPurifier_Context
  4315. */
  4316. protected $context;
  4317. /**
  4318. * @type array
  4319. */
  4320. protected $lines = array();
  4321. /**
  4322. * @param HTMLPurifier_Context $context
  4323. */
  4324. public function __construct($context)
  4325. {
  4326. $this->locale =& $context->get('Locale');
  4327. $this->context = $context;
  4328. $this->_current =& $this->_stacks[0];
  4329. $this->errors =& $this->_stacks[0];
  4330. }
  4331. /**
  4332. * Sends an error message to the collector for later use
  4333. * @param int $severity Error severity, PHP error style (don't use E_USER_)
  4334. * @param string $msg Error message text
  4335. */
  4336. public function send($severity, $msg)
  4337. {
  4338. $args = array();
  4339. if (func_num_args() > 2) {
  4340. $args = func_get_args();
  4341. array_shift($args);
  4342. unset($args[0]);
  4343. }
  4344. $token = $this->context->get('CurrentToken', true);
  4345. $line = $token ? $token->line : $this->context->get('CurrentLine', true);
  4346. $col = $token ? $token->col : $this->context->get('CurrentCol', true);
  4347. $attr = $this->context->get('CurrentAttr', true);
  4348. // perform special substitutions, also add custom parameters
  4349. $subst = array();
  4350. if (!is_null($token)) {
  4351. $args['CurrentToken'] = $token;
  4352. }
  4353. if (!is_null($attr)) {
  4354. $subst['$CurrentAttr.Name'] = $attr;
  4355. if (isset($token->attr[$attr])) {
  4356. $subst['$CurrentAttr.Value'] = $token->attr[$attr];
  4357. }
  4358. }
  4359. if (empty($args)) {
  4360. $msg = $this->locale->getMessage($msg);
  4361. } else {
  4362. $msg = $this->locale->formatMessage($msg, $args);
  4363. }
  4364. if (!empty($subst)) {
  4365. $msg = strtr($msg, $subst);
  4366. }
  4367. // (numerically indexed)
  4368. $error = array(
  4369. self::LINENO => $line,
  4370. self::SEVERITY => $severity,
  4371. self::MESSAGE => $msg,
  4372. self::CHILDREN => array()
  4373. );
  4374. $this->_current[] = $error;
  4375. // NEW CODE BELOW ...
  4376. // Top-level errors are either:
  4377. // TOKEN type, if $value is set appropriately, or
  4378. // "syntax" type, if $value is null
  4379. $new_struct = new HTMLPurifier_ErrorStruct();
  4380. $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
  4381. if ($token) {
  4382. $new_struct->value = clone $token;
  4383. }
  4384. if (is_int($line) && is_int($col)) {
  4385. if (isset($this->lines[$line][$col])) {
  4386. $struct = $this->lines[$line][$col];
  4387. } else {
  4388. $struct = $this->lines[$line][$col] = $new_struct;
  4389. }
  4390. // These ksorts may present a performance problem
  4391. ksort($this->lines[$line], SORT_NUMERIC);
  4392. } else {
  4393. if (isset($this->lines[-1])) {
  4394. $struct = $this->lines[-1];
  4395. } else {
  4396. $struct = $this->lines[-1] = $new_struct;
  4397. }
  4398. }
  4399. ksort($this->lines, SORT_NUMERIC);
  4400. // Now, check if we need to operate on a lower structure
  4401. if (!empty($attr)) {
  4402. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
  4403. if (!$struct->value) {
  4404. $struct->value = array($attr, 'PUT VALUE HERE');
  4405. }
  4406. }
  4407. if (!empty($cssprop)) {
  4408. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
  4409. if (!$struct->value) {
  4410. // if we tokenize CSS this might be a little more difficult to do
  4411. $struct->value = array($cssprop, 'PUT VALUE HERE');
  4412. }
  4413. }
  4414. // Ok, structs are all setup, now time to register the error
  4415. $struct->addError($severity, $msg);
  4416. }
  4417. /**
  4418. * Retrieves raw error data for custom formatter to use
  4419. */
  4420. public function getRaw()
  4421. {
  4422. return $this->errors;
  4423. }
  4424. /**
  4425. * Default HTML formatting implementation for error messages
  4426. * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
  4427. * @param array $errors Errors array to display; used for recursion.
  4428. * @return string
  4429. */
  4430. public function getHTMLFormatted($config, $errors = null)
  4431. {
  4432. $ret = array();
  4433. $this->generator = new HTMLPurifier_Generator($config, $this->context);
  4434. if ($errors === null) {
  4435. $errors = $this->errors;
  4436. }
  4437. // 'At line' message needs to be removed
  4438. // generation code for new structure goes here. It needs to be recursive.
  4439. foreach ($this->lines as $line => $col_array) {
  4440. if ($line == -1) {
  4441. continue;
  4442. }
  4443. foreach ($col_array as $col => $struct) {
  4444. $this->_renderStruct($ret, $struct, $line, $col);
  4445. }
  4446. }
  4447. if (isset($this->lines[-1])) {
  4448. $this->_renderStruct($ret, $this->lines[-1]);
  4449. }
  4450. if (empty($errors)) {
  4451. return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
  4452. } else {
  4453. return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
  4454. }
  4455. }
  4456. private function _renderStruct(&$ret, $struct, $line = null, $col = null)
  4457. {
  4458. $stack = array($struct);
  4459. $context_stack = array(array());
  4460. while ($current = array_pop($stack)) {
  4461. $context = array_pop($context_stack);
  4462. foreach ($current->errors as $error) {
  4463. list($severity, $msg) = $error;
  4464. $string = '';
  4465. $string .= '<div>';
  4466. // W3C uses an icon to indicate the severity of the error.
  4467. $error = $this->locale->getErrorName($severity);
  4468. $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
  4469. if (!is_null($line) && !is_null($col)) {
  4470. $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
  4471. } else {
  4472. $string .= '<em class="location">End of Document: </em> ';
  4473. }
  4474. $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
  4475. $string .= '</div>';
  4476. // Here, have a marker for the character on the column appropriate.
  4477. // Be sure to clip extremely long lines.
  4478. //$string .= '<pre>';
  4479. //$string .= '';
  4480. //$string .= '</pre>';
  4481. $ret[] = $string;
  4482. }
  4483. foreach ($current->children as $array) {
  4484. $context[] = $current;
  4485. $stack = array_merge($stack, array_reverse($array, true));
  4486. for ($i = count($array); $i > 0; $i--) {
  4487. $context_stack[] = $context;
  4488. }
  4489. }
  4490. }
  4491. }
  4492. }
  4493. /**
  4494. * Records errors for particular segments of an HTML document such as tokens,
  4495. * attributes or CSS properties. They can contain error structs (which apply
  4496. * to components of what they represent), but their main purpose is to hold
  4497. * errors applying to whatever struct is being used.
  4498. */
  4499. class HTMLPurifier_ErrorStruct
  4500. {
  4501. /**
  4502. * Possible values for $children first-key. Note that top-level structures
  4503. * are automatically token-level.
  4504. */
  4505. const TOKEN = 0;
  4506. const ATTR = 1;
  4507. const CSSPROP = 2;
  4508. /**
  4509. * Type of this struct.
  4510. * @type string
  4511. */
  4512. public $type;
  4513. /**
  4514. * Value of the struct we are recording errors for. There are various
  4515. * values for this:
  4516. * - TOKEN: Instance of HTMLPurifier_Token
  4517. * - ATTR: array('attr-name', 'value')
  4518. * - CSSPROP: array('prop-name', 'value')
  4519. * @type mixed
  4520. */
  4521. public $value;
  4522. /**
  4523. * Errors registered for this structure.
  4524. * @type array
  4525. */
  4526. public $errors = array();
  4527. /**
  4528. * Child ErrorStructs that are from this structure. For example, a TOKEN
  4529. * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
  4530. * array in structure: [TYPE]['identifier']
  4531. * @type array
  4532. */
  4533. public $children = array();
  4534. /**
  4535. * @param string $type
  4536. * @param string $id
  4537. * @return mixed
  4538. */
  4539. public function getChild($type, $id)
  4540. {
  4541. if (!isset($this->children[$type][$id])) {
  4542. $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
  4543. $this->children[$type][$id]->type = $type;
  4544. }
  4545. return $this->children[$type][$id];
  4546. }
  4547. /**
  4548. * @param int $severity
  4549. * @param string $message
  4550. */
  4551. public function addError($severity, $message)
  4552. {
  4553. $this->errors[] = array($severity, $message);
  4554. }
  4555. }
  4556. /**
  4557. * Global exception class for HTML Purifier; any exceptions we throw
  4558. * are from here.
  4559. */
  4560. class HTMLPurifier_Exception extends Exception
  4561. {
  4562. }
  4563. /**
  4564. * Represents a pre or post processing filter on HTML Purifier's output
  4565. *
  4566. * Sometimes, a little ad-hoc fixing of HTML has to be done before
  4567. * it gets sent through HTML Purifier: you can use filters to acheive
  4568. * this effect. For instance, YouTube videos can be preserved using
  4569. * this manner. You could have used a decorator for this task, but
  4570. * PHP's support for them is not terribly robust, so we're going
  4571. * to just loop through the filters.
  4572. *
  4573. * Filters should be exited first in, last out. If there are three filters,
  4574. * named 1, 2 and 3, the order of execution should go 1->preFilter,
  4575. * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
  4576. * 1->postFilter.
  4577. *
  4578. * @note Methods are not declared abstract as it is perfectly legitimate
  4579. * for an implementation not to want anything to happen on a step
  4580. */
  4581. class HTMLPurifier_Filter
  4582. {
  4583. /**
  4584. * Name of the filter for identification purposes.
  4585. * @type string
  4586. */
  4587. public $name;
  4588. /**
  4589. * Pre-processor function, handles HTML before HTML Purifier
  4590. * @param string $html
  4591. * @param HTMLPurifier_Config $config
  4592. * @param HTMLPurifier_Context $context
  4593. * @return string
  4594. */
  4595. public function preFilter($html, $config, $context)
  4596. {
  4597. return $html;
  4598. }
  4599. /**
  4600. * Post-processor function, handles HTML after HTML Purifier
  4601. * @param string $html
  4602. * @param HTMLPurifier_Config $config
  4603. * @param HTMLPurifier_Context $context
  4604. * @return string
  4605. */
  4606. public function postFilter($html, $config, $context)
  4607. {
  4608. return $html;
  4609. }
  4610. }
  4611. /**
  4612. * Generates HTML from tokens.
  4613. * @todo Refactor interface so that configuration/context is determined
  4614. * upon instantiation, no need for messy generateFromTokens() calls
  4615. * @todo Make some of the more internal functions protected, and have
  4616. * unit tests work around that
  4617. */
  4618. class HTMLPurifier_Generator
  4619. {
  4620. /**
  4621. * Whether or not generator should produce XML output.
  4622. * @type bool
  4623. */
  4624. private $_xhtml = true;
  4625. /**
  4626. * :HACK: Whether or not generator should comment the insides of <script> tags.
  4627. * @type bool
  4628. */
  4629. private $_scriptFix = false;
  4630. /**
  4631. * Cache of HTMLDefinition during HTML output to determine whether or
  4632. * not attributes should be minimized.
  4633. * @type HTMLPurifier_HTMLDefinition
  4634. */
  4635. private $_def;
  4636. /**
  4637. * Cache of %Output.SortAttr.
  4638. * @type bool
  4639. */
  4640. private $_sortAttr;
  4641. /**
  4642. * Cache of %Output.FlashCompat.
  4643. * @type bool
  4644. */
  4645. private $_flashCompat;
  4646. /**
  4647. * Cache of %Output.FixInnerHTML.
  4648. * @type bool
  4649. */
  4650. private $_innerHTMLFix;
  4651. /**
  4652. * Stack for keeping track of object information when outputting IE
  4653. * compatibility code.
  4654. * @type array
  4655. */
  4656. private $_flashStack = array();
  4657. /**
  4658. * Configuration for the generator
  4659. * @type HTMLPurifier_Config
  4660. */
  4661. protected $config;
  4662. /**
  4663. * @param HTMLPurifier_Config $config
  4664. * @param HTMLPurifier_Context $context
  4665. */
  4666. public function __construct($config, $context)
  4667. {
  4668. $this->config = $config;
  4669. $this->_scriptFix = $config->get('Output.CommentScriptContents');
  4670. $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
  4671. $this->_sortAttr = $config->get('Output.SortAttr');
  4672. $this->_flashCompat = $config->get('Output.FlashCompat');
  4673. $this->_def = $config->getHTMLDefinition();
  4674. $this->_xhtml = $this->_def->doctype->xml;
  4675. }
  4676. /**
  4677. * Generates HTML from an array of tokens.
  4678. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
  4679. * @return string Generated HTML
  4680. */
  4681. public function generateFromTokens($tokens)
  4682. {
  4683. if (!$tokens) {
  4684. return '';
  4685. }
  4686. // Basic algorithm
  4687. $html = '';
  4688. for ($i = 0, $size = count($tokens); $i < $size; $i++) {
  4689. if ($this->_scriptFix && $tokens[$i]->name === 'script'
  4690. && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
  4691. // script special case
  4692. // the contents of the script block must be ONE token
  4693. // for this to work.
  4694. $html .= $this->generateFromToken($tokens[$i++]);
  4695. $html .= $this->generateScriptFromToken($tokens[$i++]);
  4696. }
  4697. $html .= $this->generateFromToken($tokens[$i]);
  4698. }
  4699. // Tidy cleanup
  4700. if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
  4701. $tidy = new Tidy;
  4702. $tidy->parseString(
  4703. $html,
  4704. array(
  4705. 'indent'=> true,
  4706. 'output-xhtml' => $this->_xhtml,
  4707. 'show-body-only' => true,
  4708. 'indent-spaces' => 2,
  4709. 'wrap' => 68,
  4710. ),
  4711. 'utf8'
  4712. );
  4713. $tidy->cleanRepair();
  4714. $html = (string) $tidy; // explicit cast necessary
  4715. }
  4716. // Normalize newlines to system defined value
  4717. if ($this->config->get('Core.NormalizeNewlines')) {
  4718. $nl = $this->config->get('Output.Newline');
  4719. if ($nl === null) {
  4720. $nl = PHP_EOL;
  4721. }
  4722. if ($nl !== "\n") {
  4723. $html = str_replace("\n", $nl, $html);
  4724. }
  4725. }
  4726. return $html;
  4727. }
  4728. /**
  4729. * Generates HTML from a single token.
  4730. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4731. * @return string Generated HTML
  4732. */
  4733. public function generateFromToken($token)
  4734. {
  4735. if (!$token instanceof HTMLPurifier_Token) {
  4736. trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
  4737. return '';
  4738. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  4739. $attr = $this->generateAttributes($token->attr, $token->name);
  4740. if ($this->_flashCompat) {
  4741. if ($token->name == "object") {
  4742. $flash = new stdClass();
  4743. $flash->attr = $token->attr;
  4744. $flash->param = array();
  4745. $this->_flashStack[] = $flash;
  4746. }
  4747. }
  4748. return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
  4749. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4750. $_extra = '';
  4751. if ($this->_flashCompat) {
  4752. if ($token->name == "object" && !empty($this->_flashStack)) {
  4753. // doesn't do anything for now
  4754. }
  4755. }
  4756. return $_extra . '</' . $token->name . '>';
  4757. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  4758. if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
  4759. $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
  4760. }
  4761. $attr = $this->generateAttributes($token->attr, $token->name);
  4762. return '<' . $token->name . ($attr ? ' ' : '') . $attr .
  4763. ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
  4764. . '>';
  4765. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  4766. return $this->escape($token->data, ENT_NOQUOTES);
  4767. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  4768. return '<!--' . $token->data . '-->';
  4769. } else {
  4770. return '';
  4771. }
  4772. }
  4773. /**
  4774. * Special case processor for the contents of script tags
  4775. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4776. * @return string
  4777. * @warning This runs into problems if there's already a literal
  4778. * --> somewhere inside the script contents.
  4779. */
  4780. public function generateScriptFromToken($token)
  4781. {
  4782. if (!$token instanceof HTMLPurifier_Token_Text) {
  4783. return $this->generateFromToken($token);
  4784. }
  4785. // Thanks <http://lachy.id.au/log/2005/05/script-comments>
  4786. $data = preg_replace('#//\s*$#', '', $token->data);
  4787. return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
  4788. }
  4789. /**
  4790. * Generates attribute declarations from attribute array.
  4791. * @note This does not include the leading or trailing space.
  4792. * @param array $assoc_array_of_attributes Attribute array
  4793. * @param string $element Name of element attributes are for, used to check
  4794. * attribute minimization.
  4795. * @return string Generated HTML fragment for insertion.
  4796. */
  4797. public function generateAttributes($assoc_array_of_attributes, $element = '')
  4798. {
  4799. $html = '';
  4800. if ($this->_sortAttr) {
  4801. ksort($assoc_array_of_attributes);
  4802. }
  4803. foreach ($assoc_array_of_attributes as $key => $value) {
  4804. if (!$this->_xhtml) {
  4805. // Remove namespaced attributes
  4806. if (strpos($key, ':') !== false) {
  4807. continue;
  4808. }
  4809. // Check if we should minimize the attribute: val="val" -> val
  4810. if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
  4811. $html .= $key . ' ';
  4812. continue;
  4813. }
  4814. }
  4815. // Workaround for Internet Explorer innerHTML bug.
  4816. // Essentially, Internet Explorer, when calculating
  4817. // innerHTML, omits quotes if there are no instances of
  4818. // angled brackets, quotes or spaces. However, when parsing
  4819. // HTML (for example, when you assign to innerHTML), it
  4820. // treats backticks as quotes. Thus,
  4821. // <img alt="``" />
  4822. // becomes
  4823. // <img alt=`` />
  4824. // becomes
  4825. // <img alt='' />
  4826. // Fortunately, all we need to do is trigger an appropriate
  4827. // quoting style, which we do by adding an extra space.
  4828. // This also is consistent with the W3C spec, which states
  4829. // that user agents may ignore leading or trailing
  4830. // whitespace (in fact, most don't, at least for attributes
  4831. // like alt, but an extra space at the end is barely
  4832. // noticeable). Still, we have a configuration knob for
  4833. // this, since this transformation is not necesary if you
  4834. // don't process user input with innerHTML or you don't plan
  4835. // on supporting Internet Explorer.
  4836. if ($this->_innerHTMLFix) {
  4837. if (strpos($value, '`') !== false) {
  4838. // check if correct quoting style would not already be
  4839. // triggered
  4840. if (strcspn($value, '"\' <>') === strlen($value)) {
  4841. // protect!
  4842. $value .= ' ';
  4843. }
  4844. }
  4845. }
  4846. $html .= $key.'="'.$this->escape($value).'" ';
  4847. }
  4848. return rtrim($html);
  4849. }
  4850. /**
  4851. * Escapes raw text data.
  4852. * @todo This really ought to be protected, but until we have a facility
  4853. * for properly generating HTML here w/o using tokens, it stays
  4854. * public.
  4855. * @param string $string String data to escape for HTML.
  4856. * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
  4857. * permissible for non-attribute output.
  4858. * @return string escaped data.
  4859. */
  4860. public function escape($string, $quote = null)
  4861. {
  4862. // Workaround for APC bug on Mac Leopard reported by sidepodcast
  4863. // http://htmlpurifier.org/phorum/read.php?3,4823,4846
  4864. if ($quote === null) {
  4865. $quote = ENT_COMPAT;
  4866. }
  4867. return htmlspecialchars($string, $quote, 'UTF-8');
  4868. }
  4869. }
  4870. /**
  4871. * Definition of the purified HTML that describes allowed children,
  4872. * attributes, and many other things.
  4873. *
  4874. * Conventions:
  4875. *
  4876. * All member variables that are prefixed with info
  4877. * (including the main $info array) are used by HTML Purifier internals
  4878. * and should not be directly edited when customizing the HTMLDefinition.
  4879. * They can usually be set via configuration directives or custom
  4880. * modules.
  4881. *
  4882. * On the other hand, member variables without the info prefix are used
  4883. * internally by the HTMLDefinition and MUST NOT be used by other HTML
  4884. * Purifier internals. Many of them, however, are public, and may be
  4885. * edited by userspace code to tweak the behavior of HTMLDefinition.
  4886. *
  4887. * @note This class is inspected by Printer_HTMLDefinition; please
  4888. * update that class if things here change.
  4889. *
  4890. * @warning Directives that change this object's structure must be in
  4891. * the HTML or Attr namespace!
  4892. */
  4893. class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
  4894. {
  4895. // FULLY-PUBLIC VARIABLES ---------------------------------------------
  4896. /**
  4897. * Associative array of element names to HTMLPurifier_ElementDef.
  4898. * @type HTMLPurifier_ElementDef[]
  4899. */
  4900. public $info = array();
  4901. /**
  4902. * Associative array of global attribute name to attribute definition.
  4903. * @type array
  4904. */
  4905. public $info_global_attr = array();
  4906. /**
  4907. * String name of parent element HTML will be going into.
  4908. * @type string
  4909. */
  4910. public $info_parent = 'div';
  4911. /**
  4912. * Definition for parent element, allows parent element to be a
  4913. * tag that's not allowed inside the HTML fragment.
  4914. * @type HTMLPurifier_ElementDef
  4915. */
  4916. public $info_parent_def;
  4917. /**
  4918. * String name of element used to wrap inline elements in block context.
  4919. * @type string
  4920. * @note This is rarely used except for BLOCKQUOTEs in strict mode
  4921. */
  4922. public $info_block_wrapper = 'p';
  4923. /**
  4924. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  4925. * @type array
  4926. */
  4927. public $info_tag_transform = array();
  4928. /**
  4929. * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
  4930. * @type HTMLPurifier_AttrTransform[]
  4931. */
  4932. public $info_attr_transform_pre = array();
  4933. /**
  4934. * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
  4935. * @type HTMLPurifier_AttrTransform[]
  4936. */
  4937. public $info_attr_transform_post = array();
  4938. /**
  4939. * Nested lookup array of content set name (Block, Inline) to
  4940. * element name to whether or not it belongs in that content set.
  4941. * @type array
  4942. */
  4943. public $info_content_sets = array();
  4944. /**
  4945. * Indexed list of HTMLPurifier_Injector to be used.
  4946. * @type HTMLPurifier_Injector[]
  4947. */
  4948. public $info_injector = array();
  4949. /**
  4950. * Doctype object
  4951. * @type HTMLPurifier_Doctype
  4952. */
  4953. public $doctype;
  4954. // RAW CUSTOMIZATION STUFF --------------------------------------------
  4955. /**
  4956. * Adds a custom attribute to a pre-existing element
  4957. * @note This is strictly convenience, and does not have a corresponding
  4958. * method in HTMLPurifier_HTMLModule
  4959. * @param string $element_name Element name to add attribute to
  4960. * @param string $attr_name Name of attribute
  4961. * @param mixed $def Attribute definition, can be string or object, see
  4962. * HTMLPurifier_AttrTypes for details
  4963. */
  4964. public function addAttribute($element_name, $attr_name, $def)
  4965. {
  4966. $module = $this->getAnonymousModule();
  4967. if (!isset($module->info[$element_name])) {
  4968. $element = $module->addBlankElement($element_name);
  4969. } else {
  4970. $element = $module->info[$element_name];
  4971. }
  4972. $element->attr[$attr_name] = $def;
  4973. }
  4974. /**
  4975. * Adds a custom element to your HTML definition
  4976. * @see HTMLPurifier_HTMLModule::addElement() for detailed
  4977. * parameter and return value descriptions.
  4978. */
  4979. public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
  4980. {
  4981. $module = $this->getAnonymousModule();
  4982. // assume that if the user is calling this, the element
  4983. // is safe. This may not be a good idea
  4984. $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
  4985. return $element;
  4986. }
  4987. /**
  4988. * Adds a blank element to your HTML definition, for overriding
  4989. * existing behavior
  4990. * @param string $element_name
  4991. * @return HTMLPurifier_ElementDef
  4992. * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
  4993. * parameter and return value descriptions.
  4994. */
  4995. public function addBlankElement($element_name)
  4996. {
  4997. $module = $this->getAnonymousModule();
  4998. $element = $module->addBlankElement($element_name);
  4999. return $element;
  5000. }
  5001. /**
  5002. * Retrieves a reference to the anonymous module, so you can
  5003. * bust out advanced features without having to make your own
  5004. * module.
  5005. * @return HTMLPurifier_HTMLModule
  5006. */
  5007. public function getAnonymousModule()
  5008. {
  5009. if (!$this->_anonModule) {
  5010. $this->_anonModule = new HTMLPurifier_HTMLModule();
  5011. $this->_anonModule->name = 'Anonymous';
  5012. }
  5013. return $this->_anonModule;
  5014. }
  5015. private $_anonModule = null;
  5016. // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
  5017. /**
  5018. * @type string
  5019. */
  5020. public $type = 'HTML';
  5021. /**
  5022. * @type HTMLPurifier_HTMLModuleManager
  5023. */
  5024. public $manager;
  5025. /**
  5026. * Performs low-cost, preliminary initialization.
  5027. */
  5028. public function __construct()
  5029. {
  5030. $this->manager = new HTMLPurifier_HTMLModuleManager();
  5031. }
  5032. /**
  5033. * @param HTMLPurifier_Config $config
  5034. */
  5035. protected function doSetup($config)
  5036. {
  5037. $this->processModules($config);
  5038. $this->setupConfigStuff($config);
  5039. unset($this->manager);
  5040. // cleanup some of the element definitions
  5041. foreach ($this->info as $k => $v) {
  5042. unset($this->info[$k]->content_model);
  5043. unset($this->info[$k]->content_model_type);
  5044. }
  5045. }
  5046. /**
  5047. * Extract out the information from the manager
  5048. * @param HTMLPurifier_Config $config
  5049. */
  5050. protected function processModules($config)
  5051. {
  5052. if ($this->_anonModule) {
  5053. // for user specific changes
  5054. // this is late-loaded so we don't have to deal with PHP4
  5055. // reference wonky-ness
  5056. $this->manager->addModule($this->_anonModule);
  5057. unset($this->_anonModule);
  5058. }
  5059. $this->manager->setup($config);
  5060. $this->doctype = $this->manager->doctype;
  5061. foreach ($this->manager->modules as $module) {
  5062. foreach ($module->info_tag_transform as $k => $v) {
  5063. if ($v === false) {
  5064. unset($this->info_tag_transform[$k]);
  5065. } else {
  5066. $this->info_tag_transform[$k] = $v;
  5067. }
  5068. }
  5069. foreach ($module->info_attr_transform_pre as $k => $v) {
  5070. if ($v === false) {
  5071. unset($this->info_attr_transform_pre[$k]);
  5072. } else {
  5073. $this->info_attr_transform_pre[$k] = $v;
  5074. }
  5075. }
  5076. foreach ($module->info_attr_transform_post as $k => $v) {
  5077. if ($v === false) {
  5078. unset($this->info_attr_transform_post[$k]);
  5079. } else {
  5080. $this->info_attr_transform_post[$k] = $v;
  5081. }
  5082. }
  5083. foreach ($module->info_injector as $k => $v) {
  5084. if ($v === false) {
  5085. unset($this->info_injector[$k]);
  5086. } else {
  5087. $this->info_injector[$k] = $v;
  5088. }
  5089. }
  5090. }
  5091. $this->info = $this->manager->getElements();
  5092. $this->info_content_sets = $this->manager->contentSets->lookup;
  5093. }
  5094. /**
  5095. * Sets up stuff based on config. We need a better way of doing this.
  5096. * @param HTMLPurifier_Config $config
  5097. */
  5098. protected function setupConfigStuff($config)
  5099. {
  5100. $block_wrapper = $config->get('HTML.BlockWrapper');
  5101. if (isset($this->info_content_sets['Block'][$block_wrapper])) {
  5102. $this->info_block_wrapper = $block_wrapper;
  5103. } else {
  5104. trigger_error(
  5105. 'Cannot use non-block element as block wrapper',
  5106. E_USER_ERROR
  5107. );
  5108. }
  5109. $parent = $config->get('HTML.Parent');
  5110. $def = $this->manager->getElement($parent, true);
  5111. if ($def) {
  5112. $this->info_parent = $parent;
  5113. $this->info_parent_def = $def;
  5114. } else {
  5115. trigger_error(
  5116. 'Cannot use unrecognized element as parent',
  5117. E_USER_ERROR
  5118. );
  5119. $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
  5120. }
  5121. // support template text
  5122. $support = "(for information on implementing this, see the support forums) ";
  5123. // setup allowed elements -----------------------------------------
  5124. $allowed_elements = $config->get('HTML.AllowedElements');
  5125. $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
  5126. if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
  5127. $allowed = $config->get('HTML.Allowed');
  5128. if (is_string($allowed)) {
  5129. list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
  5130. }
  5131. }
  5132. if (is_array($allowed_elements)) {
  5133. foreach ($this->info as $name => $d) {
  5134. if (!isset($allowed_elements[$name])) {
  5135. unset($this->info[$name]);
  5136. }
  5137. unset($allowed_elements[$name]);
  5138. }
  5139. // emit errors
  5140. foreach ($allowed_elements as $element => $d) {
  5141. $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
  5142. trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
  5143. }
  5144. }
  5145. // setup allowed attributes ---------------------------------------
  5146. $allowed_attributes_mutable = $allowed_attributes; // by copy!
  5147. if (is_array($allowed_attributes)) {
  5148. // This actually doesn't do anything, since we went away from
  5149. // global attributes. It's possible that userland code uses
  5150. // it, but HTMLModuleManager doesn't!
  5151. foreach ($this->info_global_attr as $attr => $x) {
  5152. $keys = array($attr, "*@$attr", "*.$attr");
  5153. $delete = true;
  5154. foreach ($keys as $key) {
  5155. if ($delete && isset($allowed_attributes[$key])) {
  5156. $delete = false;
  5157. }
  5158. if (isset($allowed_attributes_mutable[$key])) {
  5159. unset($allowed_attributes_mutable[$key]);
  5160. }
  5161. }
  5162. if ($delete) {
  5163. unset($this->info_global_attr[$attr]);
  5164. }
  5165. }
  5166. foreach ($this->info as $tag => $info) {
  5167. foreach ($info->attr as $attr => $x) {
  5168. $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
  5169. $delete = true;
  5170. foreach ($keys as $key) {
  5171. if ($delete && isset($allowed_attributes[$key])) {
  5172. $delete = false;
  5173. }
  5174. if (isset($allowed_attributes_mutable[$key])) {
  5175. unset($allowed_attributes_mutable[$key]);
  5176. }
  5177. }
  5178. if ($delete) {
  5179. if ($this->info[$tag]->attr[$attr]->required) {
  5180. trigger_error(
  5181. "Required attribute '$attr' in element '$tag' " .
  5182. "was not allowed, which means '$tag' will not be allowed either",
  5183. E_USER_WARNING
  5184. );
  5185. }
  5186. unset($this->info[$tag]->attr[$attr]);
  5187. }
  5188. }
  5189. }
  5190. // emit errors
  5191. foreach ($allowed_attributes_mutable as $elattr => $d) {
  5192. $bits = preg_split('/[.@]/', $elattr, 2);
  5193. $c = count($bits);
  5194. switch ($c) {
  5195. case 2:
  5196. if ($bits[0] !== '*') {
  5197. $element = htmlspecialchars($bits[0]);
  5198. $attribute = htmlspecialchars($bits[1]);
  5199. if (!isset($this->info[$element])) {
  5200. trigger_error(
  5201. "Cannot allow attribute '$attribute' if element " .
  5202. "'$element' is not allowed/supported $support"
  5203. );
  5204. } else {
  5205. trigger_error(
  5206. "Attribute '$attribute' in element '$element' not supported $support",
  5207. E_USER_WARNING
  5208. );
  5209. }
  5210. break;
  5211. }
  5212. // otherwise fall through
  5213. case 1:
  5214. $attribute = htmlspecialchars($bits[0]);
  5215. trigger_error(
  5216. "Global attribute '$attribute' is not ".
  5217. "supported in any elements $support",
  5218. E_USER_WARNING
  5219. );
  5220. break;
  5221. }
  5222. }
  5223. }
  5224. // setup forbidden elements ---------------------------------------
  5225. $forbidden_elements = $config->get('HTML.ForbiddenElements');
  5226. $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
  5227. foreach ($this->info as $tag => $info) {
  5228. if (isset($forbidden_elements[$tag])) {
  5229. unset($this->info[$tag]);
  5230. continue;
  5231. }
  5232. foreach ($info->attr as $attr => $x) {
  5233. if (isset($forbidden_attributes["$tag@$attr"]) ||
  5234. isset($forbidden_attributes["*@$attr"]) ||
  5235. isset($forbidden_attributes[$attr])
  5236. ) {
  5237. unset($this->info[$tag]->attr[$attr]);
  5238. continue;
  5239. } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
  5240. // $tag.$attr are not user supplied, so no worries!
  5241. trigger_error(
  5242. "Error with $tag.$attr: tag.attr syntax not supported for " .
  5243. "HTML.ForbiddenAttributes; use tag@attr instead",
  5244. E_USER_WARNING
  5245. );
  5246. }
  5247. }
  5248. }
  5249. foreach ($forbidden_attributes as $key => $v) {
  5250. if (strlen($key) < 2) {
  5251. continue;
  5252. }
  5253. if ($key[0] != '*') {
  5254. continue;
  5255. }
  5256. if ($key[1] == '.') {
  5257. trigger_error(
  5258. "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
  5259. E_USER_WARNING
  5260. );
  5261. }
  5262. }
  5263. // setup injectors -----------------------------------------------------
  5264. foreach ($this->info_injector as $i => $injector) {
  5265. if ($injector->checkNeeded($config) !== false) {
  5266. // remove injector that does not have it's required
  5267. // elements/attributes present, and is thus not needed.
  5268. unset($this->info_injector[$i]);
  5269. }
  5270. }
  5271. }
  5272. /**
  5273. * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
  5274. * separate lists for processing. Format is element[attr1|attr2],element2...
  5275. * @warning Although it's largely drawn from TinyMCE's implementation,
  5276. * it is different, and you'll probably have to modify your lists
  5277. * @param array $list String list to parse
  5278. * @return array
  5279. * @todo Give this its own class, probably static interface
  5280. */
  5281. public function parseTinyMCEAllowedList($list)
  5282. {
  5283. $list = str_replace(array(' ', "\t"), '', $list);
  5284. $elements = array();
  5285. $attributes = array();
  5286. $chunks = preg_split('/(,|[\n\r]+)/', $list);
  5287. foreach ($chunks as $chunk) {
  5288. if (empty($chunk)) {
  5289. continue;
  5290. }
  5291. // remove TinyMCE element control characters
  5292. if (!strpos($chunk, '[')) {
  5293. $element = $chunk;
  5294. $attr = false;
  5295. } else {
  5296. list($element, $attr) = explode('[', $chunk);
  5297. }
  5298. if ($element !== '*') {
  5299. $elements[$element] = true;
  5300. }
  5301. if (!$attr) {
  5302. continue;
  5303. }
  5304. $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
  5305. $attr = explode('|', $attr);
  5306. foreach ($attr as $key) {
  5307. $attributes["$element.$key"] = true;
  5308. }
  5309. }
  5310. return array($elements, $attributes);
  5311. }
  5312. }
  5313. /**
  5314. * Represents an XHTML 1.1 module, with information on elements, tags
  5315. * and attributes.
  5316. * @note Even though this is technically XHTML 1.1, it is also used for
  5317. * regular HTML parsing. We are using modulization as a convenient
  5318. * way to represent the internals of HTMLDefinition, and our
  5319. * implementation is by no means conforming and does not directly
  5320. * use the normative DTDs or XML schemas.
  5321. * @note The public variables in a module should almost directly
  5322. * correspond to the variables in HTMLPurifier_HTMLDefinition.
  5323. * However, the prefix info carries no special meaning in these
  5324. * objects (include it anyway if that's the correspondence though).
  5325. * @todo Consider making some member functions protected
  5326. */
  5327. class HTMLPurifier_HTMLModule
  5328. {
  5329. // -- Overloadable ----------------------------------------------------
  5330. /**
  5331. * Short unique string identifier of the module.
  5332. * @type string
  5333. */
  5334. public $name;
  5335. /**
  5336. * Informally, a list of elements this module changes.
  5337. * Not used in any significant way.
  5338. * @type array
  5339. */
  5340. public $elements = array();
  5341. /**
  5342. * Associative array of element names to element definitions.
  5343. * Some definitions may be incomplete, to be merged in later
  5344. * with the full definition.
  5345. * @type array
  5346. */
  5347. public $info = array();
  5348. /**
  5349. * Associative array of content set names to content set additions.
  5350. * This is commonly used to, say, add an A element to the Inline
  5351. * content set. This corresponds to an internal variable $content_sets
  5352. * and NOT info_content_sets member variable of HTMLDefinition.
  5353. * @type array
  5354. */
  5355. public $content_sets = array();
  5356. /**
  5357. * Associative array of attribute collection names to attribute
  5358. * collection additions. More rarely used for adding attributes to
  5359. * the global collections. Example is the StyleAttribute module adding
  5360. * the style attribute to the Core. Corresponds to HTMLDefinition's
  5361. * attr_collections->info, since the object's data is only info,
  5362. * with extra behavior associated with it.
  5363. * @type array
  5364. */
  5365. public $attr_collections = array();
  5366. /**
  5367. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  5368. * @type array
  5369. */
  5370. public $info_tag_transform = array();
  5371. /**
  5372. * List of HTMLPurifier_AttrTransform to be performed before validation.
  5373. * @type array
  5374. */
  5375. public $info_attr_transform_pre = array();
  5376. /**
  5377. * List of HTMLPurifier_AttrTransform to be performed after validation.
  5378. * @type array
  5379. */
  5380. public $info_attr_transform_post = array();
  5381. /**
  5382. * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
  5383. * An injector will only be invoked if all of it's pre-requisites are met;
  5384. * if an injector fails setup, there will be no error; it will simply be
  5385. * silently disabled.
  5386. * @type array
  5387. */
  5388. public $info_injector = array();
  5389. /**
  5390. * Boolean flag that indicates whether or not getChildDef is implemented.
  5391. * For optimization reasons: may save a call to a function. Be sure
  5392. * to set it if you do implement getChildDef(), otherwise it will have
  5393. * no effect!
  5394. * @type bool
  5395. */
  5396. public $defines_child_def = false;
  5397. /**
  5398. * Boolean flag whether or not this module is safe. If it is not safe, all
  5399. * of its members are unsafe. Modules are safe by default (this might be
  5400. * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
  5401. * which is based off of safe HTML, to explicitly say, "This is safe," even
  5402. * though there are modules which are "unsafe")
  5403. *
  5404. * @type bool
  5405. * @note Previously, safety could be applied at an element level granularity.
  5406. * We've removed this ability, so in order to add "unsafe" elements
  5407. * or attributes, a dedicated module with this property set to false
  5408. * must be used.
  5409. */
  5410. public $safe = true;
  5411. /**
  5412. * Retrieves a proper HTMLPurifier_ChildDef subclass based on
  5413. * content_model and content_model_type member variables of
  5414. * the HTMLPurifier_ElementDef class. There is a similar function
  5415. * in HTMLPurifier_HTMLDefinition.
  5416. * @param HTMLPurifier_ElementDef $def
  5417. * @return HTMLPurifier_ChildDef subclass
  5418. */
  5419. public function getChildDef($def)
  5420. {
  5421. return false;
  5422. }
  5423. // -- Convenience -----------------------------------------------------
  5424. /**
  5425. * Convenience function that sets up a new element
  5426. * @param string $element Name of element to add
  5427. * @param string|bool $type What content set should element be registered to?
  5428. * Set as false to skip this step.
  5429. * @param string $contents Allowed children in form of:
  5430. * "$content_model_type: $content_model"
  5431. * @param array $attr_includes What attribute collections to register to
  5432. * element?
  5433. * @param array $attr What unique attributes does the element define?
  5434. * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
  5435. * @return HTMLPurifier_ElementDef Created element definition object, so you
  5436. * can set advanced parameters
  5437. */
  5438. public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
  5439. {
  5440. $this->elements[] = $element;
  5441. // parse content_model
  5442. list($content_model_type, $content_model) = $this->parseContents($contents);
  5443. // merge in attribute inclusions
  5444. $this->mergeInAttrIncludes($attr, $attr_includes);
  5445. // add element to content sets
  5446. if ($type) {
  5447. $this->addElementToContentSet($element, $type);
  5448. }
  5449. // create element
  5450. $this->info[$element] = HTMLPurifier_ElementDef::create(
  5451. $content_model,
  5452. $content_model_type,
  5453. $attr
  5454. );
  5455. // literal object $contents means direct child manipulation
  5456. if (!is_string($contents)) {
  5457. $this->info[$element]->child = $contents;
  5458. }
  5459. return $this->info[$element];
  5460. }
  5461. /**
  5462. * Convenience function that creates a totally blank, non-standalone
  5463. * element.
  5464. * @param string $element Name of element to create
  5465. * @return HTMLPurifier_ElementDef Created element
  5466. */
  5467. public function addBlankElement($element)
  5468. {
  5469. if (!isset($this->info[$element])) {
  5470. $this->elements[] = $element;
  5471. $this->info[$element] = new HTMLPurifier_ElementDef();
  5472. $this->info[$element]->standalone = false;
  5473. } else {
  5474. trigger_error("Definition for $element already exists in module, cannot redefine");
  5475. }
  5476. return $this->info[$element];
  5477. }
  5478. /**
  5479. * Convenience function that registers an element to a content set
  5480. * @param string $element Element to register
  5481. * @param string $type Name content set (warning: case sensitive, usually upper-case
  5482. * first letter)
  5483. */
  5484. public function addElementToContentSet($element, $type)
  5485. {
  5486. if (!isset($this->content_sets[$type])) {
  5487. $this->content_sets[$type] = '';
  5488. } else {
  5489. $this->content_sets[$type] .= ' | ';
  5490. }
  5491. $this->content_sets[$type] .= $element;
  5492. }
  5493. /**
  5494. * Convenience function that transforms single-string contents
  5495. * into separate content model and content model type
  5496. * @param string $contents Allowed children in form of:
  5497. * "$content_model_type: $content_model"
  5498. * @return array
  5499. * @note If contents is an object, an array of two nulls will be
  5500. * returned, and the callee needs to take the original $contents
  5501. * and use it directly.
  5502. */
  5503. public function parseContents($contents)
  5504. {
  5505. if (!is_string($contents)) {
  5506. return array(null, null);
  5507. } // defer
  5508. switch ($contents) {
  5509. // check for shorthand content model forms
  5510. case 'Empty':
  5511. return array('empty', '');
  5512. case 'Inline':
  5513. return array('optional', 'Inline | #PCDATA');
  5514. case 'Flow':
  5515. return array('optional', 'Flow | #PCDATA');
  5516. }
  5517. list($content_model_type, $content_model) = explode(':', $contents);
  5518. $content_model_type = strtolower(trim($content_model_type));
  5519. $content_model = trim($content_model);
  5520. return array($content_model_type, $content_model);
  5521. }
  5522. /**
  5523. * Convenience function that merges a list of attribute includes into
  5524. * an attribute array.
  5525. * @param array $attr Reference to attr array to modify
  5526. * @param array $attr_includes Array of includes / string include to merge in
  5527. */
  5528. public function mergeInAttrIncludes(&$attr, $attr_includes)
  5529. {
  5530. if (!is_array($attr_includes)) {
  5531. if (empty($attr_includes)) {
  5532. $attr_includes = array();
  5533. } else {
  5534. $attr_includes = array($attr_includes);
  5535. }
  5536. }
  5537. $attr[0] = $attr_includes;
  5538. }
  5539. /**
  5540. * Convenience function that generates a lookup table with boolean
  5541. * true as value.
  5542. * @param string $list List of values to turn into a lookup
  5543. * @note You can also pass an arbitrary number of arguments in
  5544. * place of the regular argument
  5545. * @return array array equivalent of list
  5546. */
  5547. public function makeLookup($list)
  5548. {
  5549. if (is_string($list)) {
  5550. $list = func_get_args();
  5551. }
  5552. $ret = array();
  5553. foreach ($list as $value) {
  5554. if (is_null($value)) {
  5555. continue;
  5556. }
  5557. $ret[$value] = true;
  5558. }
  5559. return $ret;
  5560. }
  5561. /**
  5562. * Lazy load construction of the module after determining whether
  5563. * or not it's needed, and also when a finalized configuration object
  5564. * is available.
  5565. * @param HTMLPurifier_Config $config
  5566. */
  5567. public function setup($config)
  5568. {
  5569. }
  5570. }
  5571. class HTMLPurifier_HTMLModuleManager
  5572. {
  5573. /**
  5574. * @type HTMLPurifier_DoctypeRegistry
  5575. */
  5576. public $doctypes;
  5577. /**
  5578. * Instance of current doctype.
  5579. * @type string
  5580. */
  5581. public $doctype;
  5582. /**
  5583. * @type HTMLPurifier_AttrTypes
  5584. */
  5585. public $attrTypes;
  5586. /**
  5587. * Active instances of modules for the specified doctype are
  5588. * indexed, by name, in this array.
  5589. * @type HTMLPurifier_HTMLModule[]
  5590. */
  5591. public $modules = array();
  5592. /**
  5593. * Array of recognized HTMLPurifier_HTMLModule instances,
  5594. * indexed by module's class name. This array is usually lazy loaded, but a
  5595. * user can overload a module by pre-emptively registering it.
  5596. * @type HTMLPurifier_HTMLModule[]
  5597. */
  5598. public $registeredModules = array();
  5599. /**
  5600. * List of extra modules that were added by the user
  5601. * using addModule(). These get unconditionally merged into the current doctype, whatever
  5602. * it may be.
  5603. * @type HTMLPurifier_HTMLModule[]
  5604. */
  5605. public $userModules = array();
  5606. /**
  5607. * Associative array of element name to list of modules that have
  5608. * definitions for the element; this array is dynamically filled.
  5609. * @type array
  5610. */
  5611. public $elementLookup = array();
  5612. /**
  5613. * List of prefixes we should use for registering small names.
  5614. * @type array
  5615. */
  5616. public $prefixes = array('HTMLPurifier_HTMLModule_');
  5617. /**
  5618. * @type HTMLPurifier_ContentSets
  5619. */
  5620. public $contentSets;
  5621. /**
  5622. * @type HTMLPurifier_AttrCollections
  5623. */
  5624. public $attrCollections;
  5625. /**
  5626. * If set to true, unsafe elements and attributes will be allowed.
  5627. * @type bool
  5628. */
  5629. public $trusted = false;
  5630. public function __construct()
  5631. {
  5632. // editable internal objects
  5633. $this->attrTypes = new HTMLPurifier_AttrTypes();
  5634. $this->doctypes = new HTMLPurifier_DoctypeRegistry();
  5635. // setup basic modules
  5636. $common = array(
  5637. 'CommonAttributes', 'Text', 'Hypertext', 'List',
  5638. 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
  5639. 'StyleAttribute',
  5640. // Unsafe:
  5641. 'Scripting', 'Object', 'Forms',
  5642. // Sorta legacy, but present in strict:
  5643. 'Name',
  5644. );
  5645. $transitional = array('Legacy', 'Target', 'Iframe');
  5646. $xml = array('XMLCommonAttributes');
  5647. $non_xml = array('NonXMLCommonAttributes');
  5648. // setup basic doctypes
  5649. $this->doctypes->register(
  5650. 'HTML 4.01 Transitional',
  5651. false,
  5652. array_merge($common, $transitional, $non_xml),
  5653. array('Tidy_Transitional', 'Tidy_Proprietary'),
  5654. array(),
  5655. '-//W3C//DTD HTML 4.01 Transitional//EN',
  5656. 'http://www.w3.org/TR/html4/loose.dtd'
  5657. );
  5658. $this->doctypes->register(
  5659. 'HTML 4.01 Strict',
  5660. false,
  5661. array_merge($common, $non_xml),
  5662. array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5663. array(),
  5664. '-//W3C//DTD HTML 4.01//EN',
  5665. 'http://www.w3.org/TR/html4/strict.dtd'
  5666. );
  5667. $this->doctypes->register(
  5668. 'XHTML 1.0 Transitional',
  5669. true,
  5670. array_merge($common, $transitional, $xml, $non_xml),
  5671. array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
  5672. array(),
  5673. '-//W3C//DTD XHTML 1.0 Transitional//EN',
  5674. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
  5675. );
  5676. $this->doctypes->register(
  5677. 'XHTML 1.0 Strict',
  5678. true,
  5679. array_merge($common, $xml, $non_xml),
  5680. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5681. array(),
  5682. '-//W3C//DTD XHTML 1.0 Strict//EN',
  5683. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
  5684. );
  5685. $this->doctypes->register(
  5686. 'XHTML 1.1',
  5687. true,
  5688. // Iframe is a real XHTML 1.1 module, despite being
  5689. // "transitional"!
  5690. array_merge($common, $xml, array('Ruby', 'Iframe')),
  5691. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
  5692. array(),
  5693. '-//W3C//DTD XHTML 1.1//EN',
  5694. 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
  5695. );
  5696. }
  5697. /**
  5698. * Registers a module to the recognized module list, useful for
  5699. * overloading pre-existing modules.
  5700. * @param $module Mixed: string module name, with or without
  5701. * HTMLPurifier_HTMLModule prefix, or instance of
  5702. * subclass of HTMLPurifier_HTMLModule.
  5703. * @param $overload Boolean whether or not to overload previous modules.
  5704. * If this is not set, and you do overload a module,
  5705. * HTML Purifier will complain with a warning.
  5706. * @note This function will not call autoload, you must instantiate
  5707. * (and thus invoke) autoload outside the method.
  5708. * @note If a string is passed as a module name, different variants
  5709. * will be tested in this order:
  5710. * - Check for HTMLPurifier_HTMLModule_$name
  5711. * - Check all prefixes with $name in order they were added
  5712. * - Check for literal object name
  5713. * - Throw fatal error
  5714. * If your object name collides with an internal class, specify
  5715. * your module manually. All modules must have been included
  5716. * externally: registerModule will not perform inclusions for you!
  5717. */
  5718. public function registerModule($module, $overload = false)
  5719. {
  5720. if (is_string($module)) {
  5721. // attempt to load the module
  5722. $original_module = $module;
  5723. $ok = false;
  5724. foreach ($this->prefixes as $prefix) {
  5725. $module = $prefix . $original_module;
  5726. if (class_exists($module)) {
  5727. $ok = true;
  5728. break;
  5729. }
  5730. }
  5731. if (!$ok) {
  5732. $module = $original_module;
  5733. if (!class_exists($module)) {
  5734. trigger_error(
  5735. $original_module . ' module does not exist',
  5736. E_USER_ERROR
  5737. );
  5738. return;
  5739. }
  5740. }
  5741. $module = new $module();
  5742. }
  5743. if (empty($module->name)) {
  5744. trigger_error('Module instance of ' . get_class($module) . ' must have name');
  5745. return;
  5746. }
  5747. if (!$overload && isset($this->registeredModules[$module->name])) {
  5748. trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
  5749. }
  5750. $this->registeredModules[$module->name] = $module;
  5751. }
  5752. /**
  5753. * Adds a module to the current doctype by first registering it,
  5754. * and then tacking it on to the active doctype
  5755. */
  5756. public function addModule($module)
  5757. {
  5758. $this->registerModule($module);
  5759. if (is_object($module)) {
  5760. $module = $module->name;
  5761. }
  5762. $this->userModules[] = $module;
  5763. }
  5764. /**
  5765. * Adds a class prefix that registerModule() will use to resolve a
  5766. * string name to a concrete class
  5767. */
  5768. public function addPrefix($prefix)
  5769. {
  5770. $this->prefixes[] = $prefix;
  5771. }
  5772. /**
  5773. * Performs processing on modules, after being called you may
  5774. * use getElement() and getElements()
  5775. * @param HTMLPurifier_Config $config
  5776. */
  5777. public function setup($config)
  5778. {
  5779. $this->trusted = $config->get('HTML.Trusted');
  5780. // generate
  5781. $this->doctype = $this->doctypes->make($config);
  5782. $modules = $this->doctype->modules;
  5783. // take out the default modules that aren't allowed
  5784. $lookup = $config->get('HTML.AllowedModules');
  5785. $special_cases = $config->get('HTML.CoreModules');
  5786. if (is_array($lookup)) {
  5787. foreach ($modules as $k => $m) {
  5788. if (isset($special_cases[$m])) {
  5789. continue;
  5790. }
  5791. if (!isset($lookup[$m])) {
  5792. unset($modules[$k]);
  5793. }
  5794. }
  5795. }
  5796. // custom modules
  5797. if ($config->get('HTML.Proprietary')) {
  5798. $modules[] = 'Proprietary';
  5799. }
  5800. if ($config->get('HTML.SafeObject')) {
  5801. $modules[] = 'SafeObject';
  5802. }
  5803. if ($config->get('HTML.SafeEmbed')) {
  5804. $modules[] = 'SafeEmbed';
  5805. }
  5806. if ($config->get('HTML.SafeScripting') !== array()) {
  5807. $modules[] = 'SafeScripting';
  5808. }
  5809. if ($config->get('HTML.Nofollow')) {
  5810. $modules[] = 'Nofollow';
  5811. }
  5812. if ($config->get('HTML.TargetBlank')) {
  5813. $modules[] = 'TargetBlank';
  5814. }
  5815. // NB: HTML.TargetNoreferrer and HTML.TargetNoopener must be AFTER HTML.TargetBlank
  5816. // so that its post-attr-transform gets run afterwards.
  5817. if ($config->get('HTML.TargetNoreferrer')) {
  5818. $modules[] = 'TargetNoreferrer';
  5819. }
  5820. if ($config->get('HTML.TargetNoopener')) {
  5821. $modules[] = 'TargetNoopener';
  5822. }
  5823. // merge in custom modules
  5824. $modules = array_merge($modules, $this->userModules);
  5825. foreach ($modules as $module) {
  5826. $this->processModule($module);
  5827. $this->modules[$module]->setup($config);
  5828. }
  5829. foreach ($this->doctype->tidyModules as $module) {
  5830. $this->processModule($module);
  5831. $this->modules[$module]->setup($config);
  5832. }
  5833. // prepare any injectors
  5834. foreach ($this->modules as $module) {
  5835. $n = array();
  5836. foreach ($module->info_injector as $injector) {
  5837. if (!is_object($injector)) {
  5838. $class = "HTMLPurifier_Injector_$injector";
  5839. $injector = new $class;
  5840. }
  5841. $n[$injector->name] = $injector;
  5842. }
  5843. $module->info_injector = $n;
  5844. }
  5845. // setup lookup table based on all valid modules
  5846. foreach ($this->modules as $module) {
  5847. foreach ($module->info as $name => $def) {
  5848. if (!isset($this->elementLookup[$name])) {
  5849. $this->elementLookup[$name] = array();
  5850. }
  5851. $this->elementLookup[$name][] = $module->name;
  5852. }
  5853. }
  5854. // note the different choice
  5855. $this->contentSets = new HTMLPurifier_ContentSets(
  5856. // content set assembly deals with all possible modules,
  5857. // not just ones deemed to be "safe"
  5858. $this->modules
  5859. );
  5860. $this->attrCollections = new HTMLPurifier_AttrCollections(
  5861. $this->attrTypes,
  5862. // there is no way to directly disable a global attribute,
  5863. // but using AllowedAttributes or simply not including
  5864. // the module in your custom doctype should be sufficient
  5865. $this->modules
  5866. );
  5867. }
  5868. /**
  5869. * Takes a module and adds it to the active module collection,
  5870. * registering it if necessary.
  5871. */
  5872. public function processModule($module)
  5873. {
  5874. if (!isset($this->registeredModules[$module]) || is_object($module)) {
  5875. $this->registerModule($module);
  5876. }
  5877. $this->modules[$module] = $this->registeredModules[$module];
  5878. }
  5879. /**
  5880. * Retrieves merged element definitions.
  5881. * @return Array of HTMLPurifier_ElementDef
  5882. */
  5883. public function getElements()
  5884. {
  5885. $elements = array();
  5886. foreach ($this->modules as $module) {
  5887. if (!$this->trusted && !$module->safe) {
  5888. continue;
  5889. }
  5890. foreach ($module->info as $name => $v) {
  5891. if (isset($elements[$name])) {
  5892. continue;
  5893. }
  5894. $elements[$name] = $this->getElement($name);
  5895. }
  5896. }
  5897. // remove dud elements, this happens when an element that
  5898. // appeared to be safe actually wasn't
  5899. foreach ($elements as $n => $v) {
  5900. if ($v === false) {
  5901. unset($elements[$n]);
  5902. }
  5903. }
  5904. return $elements;
  5905. }
  5906. /**
  5907. * Retrieves a single merged element definition
  5908. * @param string $name Name of element
  5909. * @param bool $trusted Boolean trusted overriding parameter: set to true
  5910. * if you want the full version of an element
  5911. * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef
  5912. * @note You may notice that modules are getting iterated over twice (once
  5913. * in getElements() and once here). This
  5914. * is because
  5915. */
  5916. public function getElement($name, $trusted = null)
  5917. {
  5918. if (!isset($this->elementLookup[$name])) {
  5919. return false;
  5920. }
  5921. // setup global state variables
  5922. $def = false;
  5923. if ($trusted === null) {
  5924. $trusted = $this->trusted;
  5925. }
  5926. // iterate through each module that has registered itself to this
  5927. // element
  5928. foreach ($this->elementLookup[$name] as $module_name) {
  5929. $module = $this->modules[$module_name];
  5930. // refuse to create/merge from a module that is deemed unsafe--
  5931. // pretend the module doesn't exist--when trusted mode is not on.
  5932. if (!$trusted && !$module->safe) {
  5933. continue;
  5934. }
  5935. // clone is used because, ideally speaking, the original
  5936. // definition should not be modified. Usually, this will
  5937. // make no difference, but for consistency's sake
  5938. $new_def = clone $module->info[$name];
  5939. if (!$def && $new_def->standalone) {
  5940. $def = $new_def;
  5941. } elseif ($def) {
  5942. // This will occur even if $new_def is standalone. In practice,
  5943. // this will usually result in a full replacement.
  5944. $def->mergeIn($new_def);
  5945. } else {
  5946. // :TODO:
  5947. // non-standalone definitions that don't have a standalone
  5948. // to merge into could be deferred to the end
  5949. // HOWEVER, it is perfectly valid for a non-standalone
  5950. // definition to lack a standalone definition, even
  5951. // after all processing: this allows us to safely
  5952. // specify extra attributes for elements that may not be
  5953. // enabled all in one place. In particular, this might
  5954. // be the case for trusted elements. WARNING: care must
  5955. // be taken that the /extra/ definitions are all safe.
  5956. continue;
  5957. }
  5958. // attribute value expansions
  5959. $this->attrCollections->performInclusions($def->attr);
  5960. $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
  5961. // descendants_are_inline, for ChildDef_Chameleon
  5962. if (is_string($def->content_model) &&
  5963. strpos($def->content_model, 'Inline') !== false) {
  5964. if ($name != 'del' && $name != 'ins') {
  5965. // this is for you, ins/del
  5966. $def->descendants_are_inline = true;
  5967. }
  5968. }
  5969. $this->contentSets->generateChildDef($def, $module);
  5970. }
  5971. // This can occur if there is a blank definition, but no base to
  5972. // mix it in with
  5973. if (!$def) {
  5974. return false;
  5975. }
  5976. // add information on required attributes
  5977. foreach ($def->attr as $attr_name => $attr_def) {
  5978. if ($attr_def->required) {
  5979. $def->required_attr[] = $attr_name;
  5980. }
  5981. }
  5982. return $def;
  5983. }
  5984. }
  5985. /**
  5986. * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
  5987. * @note In Slashdot-speak, dupe means duplicate.
  5988. * @note The default constructor does not accept $config or $context objects:
  5989. * use must use the static build() factory method to perform initialization.
  5990. */
  5991. class HTMLPurifier_IDAccumulator
  5992. {
  5993. /**
  5994. * Lookup table of IDs we've accumulated.
  5995. * @public
  5996. */
  5997. public $ids = array();
  5998. /**
  5999. * Builds an IDAccumulator, also initializing the default blacklist
  6000. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  6001. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  6002. * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator
  6003. */
  6004. public static function build($config, $context)
  6005. {
  6006. $id_accumulator = new HTMLPurifier_IDAccumulator();
  6007. $id_accumulator->load($config->get('Attr.IDBlacklist'));
  6008. return $id_accumulator;
  6009. }
  6010. /**
  6011. * Add an ID to the lookup table.
  6012. * @param string $id ID to be added.
  6013. * @return bool status, true if success, false if there's a dupe
  6014. */
  6015. public function add($id)
  6016. {
  6017. if (isset($this->ids[$id])) {
  6018. return false;
  6019. }
  6020. return $this->ids[$id] = true;
  6021. }
  6022. /**
  6023. * Load a list of IDs into the lookup table
  6024. * @param $array_of_ids Array of IDs to load
  6025. * @note This function doesn't care about duplicates
  6026. */
  6027. public function load($array_of_ids)
  6028. {
  6029. foreach ($array_of_ids as $id) {
  6030. $this->ids[$id] = true;
  6031. }
  6032. }
  6033. }
  6034. /**
  6035. * Injects tokens into the document while parsing for well-formedness.
  6036. * This enables "formatter-like" functionality such as auto-paragraphing,
  6037. * smiley-ification and linkification to take place.
  6038. *
  6039. * A note on how handlers create changes; this is done by assigning a new
  6040. * value to the $token reference. These values can take a variety of forms and
  6041. * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
  6042. * documentation.
  6043. *
  6044. * @todo Allow injectors to request a re-run on their output. This
  6045. * would help if an operation is recursive.
  6046. */
  6047. abstract class HTMLPurifier_Injector
  6048. {
  6049. /**
  6050. * Advisory name of injector, this is for friendly error messages.
  6051. * @type string
  6052. */
  6053. public $name;
  6054. /**
  6055. * @type HTMLPurifier_HTMLDefinition
  6056. */
  6057. protected $htmlDefinition;
  6058. /**
  6059. * Reference to CurrentNesting variable in Context. This is an array
  6060. * list of tokens that we are currently "inside"
  6061. * @type array
  6062. */
  6063. protected $currentNesting;
  6064. /**
  6065. * Reference to current token.
  6066. * @type HTMLPurifier_Token
  6067. */
  6068. protected $currentToken;
  6069. /**
  6070. * Reference to InputZipper variable in Context.
  6071. * @type HTMLPurifier_Zipper
  6072. */
  6073. protected $inputZipper;
  6074. /**
  6075. * Array of elements and attributes this injector creates and therefore
  6076. * need to be allowed by the definition. Takes form of
  6077. * array('element' => array('attr', 'attr2'), 'element2')
  6078. * @type array
  6079. */
  6080. public $needed = array();
  6081. /**
  6082. * Number of elements to rewind backwards (relative).
  6083. * @type bool|int
  6084. */
  6085. protected $rewindOffset = false;
  6086. /**
  6087. * Rewind to a spot to re-perform processing. This is useful if you
  6088. * deleted a node, and now need to see if this change affected any
  6089. * earlier nodes. Rewinding does not affect other injectors, and can
  6090. * result in infinite loops if not used carefully.
  6091. * @param bool|int $offset
  6092. * @warning HTML Purifier will prevent you from fast-forwarding with this
  6093. * function.
  6094. */
  6095. public function rewindOffset($offset)
  6096. {
  6097. $this->rewindOffset = $offset;
  6098. }
  6099. /**
  6100. * Retrieves rewind offset, and then unsets it.
  6101. * @return bool|int
  6102. */
  6103. public function getRewindOffset()
  6104. {
  6105. $r = $this->rewindOffset;
  6106. $this->rewindOffset = false;
  6107. return $r;
  6108. }
  6109. /**
  6110. * Prepares the injector by giving it the config and context objects:
  6111. * this allows references to important variables to be made within
  6112. * the injector. This function also checks if the HTML environment
  6113. * will work with the Injector (see checkNeeded()).
  6114. * @param HTMLPurifier_Config $config
  6115. * @param HTMLPurifier_Context $context
  6116. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  6117. */
  6118. public function prepare($config, $context)
  6119. {
  6120. $this->htmlDefinition = $config->getHTMLDefinition();
  6121. // Even though this might fail, some unit tests ignore this and
  6122. // still test checkNeeded, so be careful. Maybe get rid of that
  6123. // dependency.
  6124. $result = $this->checkNeeded($config);
  6125. if ($result !== false) {
  6126. return $result;
  6127. }
  6128. $this->currentNesting =& $context->get('CurrentNesting');
  6129. $this->currentToken =& $context->get('CurrentToken');
  6130. $this->inputZipper =& $context->get('InputZipper');
  6131. return false;
  6132. }
  6133. /**
  6134. * This function checks if the HTML environment
  6135. * will work with the Injector: if p tags are not allowed, the
  6136. * Auto-Paragraphing injector should not be enabled.
  6137. * @param HTMLPurifier_Config $config
  6138. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  6139. */
  6140. public function checkNeeded($config)
  6141. {
  6142. $def = $config->getHTMLDefinition();
  6143. foreach ($this->needed as $element => $attributes) {
  6144. if (is_int($element)) {
  6145. $element = $attributes;
  6146. }
  6147. if (!isset($def->info[$element])) {
  6148. return $element;
  6149. }
  6150. if (!is_array($attributes)) {
  6151. continue;
  6152. }
  6153. foreach ($attributes as $name) {
  6154. if (!isset($def->info[$element]->attr[$name])) {
  6155. return "$element.$name";
  6156. }
  6157. }
  6158. }
  6159. return false;
  6160. }
  6161. /**
  6162. * Tests if the context node allows a certain element
  6163. * @param string $name Name of element to test for
  6164. * @return bool True if element is allowed, false if it is not
  6165. */
  6166. public function allowsElement($name)
  6167. {
  6168. if (!empty($this->currentNesting)) {
  6169. $parent_token = array_pop($this->currentNesting);
  6170. $this->currentNesting[] = $parent_token;
  6171. $parent = $this->htmlDefinition->info[$parent_token->name];
  6172. } else {
  6173. $parent = $this->htmlDefinition->info_parent_def;
  6174. }
  6175. if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
  6176. return false;
  6177. }
  6178. // check for exclusion
  6179. if (!empty($this->currentNesting)) {
  6180. for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
  6181. $node = $this->currentNesting[$i];
  6182. $def = $this->htmlDefinition->info[$node->name];
  6183. if (isset($def->excludes[$name])) {
  6184. return false;
  6185. }
  6186. }
  6187. }
  6188. return true;
  6189. }
  6190. /**
  6191. * Iterator function, which starts with the next token and continues until
  6192. * you reach the end of the input tokens.
  6193. * @warning Please prevent previous references from interfering with this
  6194. * functions by setting $i = null beforehand!
  6195. * @param int $i Current integer index variable for inputTokens
  6196. * @param HTMLPurifier_Token $current Current token variable.
  6197. * Do NOT use $token, as that variable is also a reference
  6198. * @return bool
  6199. */
  6200. protected function forward(&$i, &$current)
  6201. {
  6202. if ($i === null) {
  6203. $i = count($this->inputZipper->back) - 1;
  6204. } else {
  6205. $i--;
  6206. }
  6207. if ($i < 0) {
  6208. return false;
  6209. }
  6210. $current = $this->inputZipper->back[$i];
  6211. return true;
  6212. }
  6213. /**
  6214. * Similar to _forward, but accepts a third parameter $nesting (which
  6215. * should be initialized at 0) and stops when we hit the end tag
  6216. * for the node $this->inputIndex starts in.
  6217. * @param int $i Current integer index variable for inputTokens
  6218. * @param HTMLPurifier_Token $current Current token variable.
  6219. * Do NOT use $token, as that variable is also a reference
  6220. * @param int $nesting
  6221. * @return bool
  6222. */
  6223. protected function forwardUntilEndToken(&$i, &$current, &$nesting)
  6224. {
  6225. $result = $this->forward($i, $current);
  6226. if (!$result) {
  6227. return false;
  6228. }
  6229. if ($nesting === null) {
  6230. $nesting = 0;
  6231. }
  6232. if ($current instanceof HTMLPurifier_Token_Start) {
  6233. $nesting++;
  6234. } elseif ($current instanceof HTMLPurifier_Token_End) {
  6235. if ($nesting <= 0) {
  6236. return false;
  6237. }
  6238. $nesting--;
  6239. }
  6240. return true;
  6241. }
  6242. /**
  6243. * Iterator function, starts with the previous token and continues until
  6244. * you reach the beginning of input tokens.
  6245. * @warning Please prevent previous references from interfering with this
  6246. * functions by setting $i = null beforehand!
  6247. * @param int $i Current integer index variable for inputTokens
  6248. * @param HTMLPurifier_Token $current Current token variable.
  6249. * Do NOT use $token, as that variable is also a reference
  6250. * @return bool
  6251. */
  6252. protected function backward(&$i, &$current)
  6253. {
  6254. if ($i === null) {
  6255. $i = count($this->inputZipper->front) - 1;
  6256. } else {
  6257. $i--;
  6258. }
  6259. if ($i < 0) {
  6260. return false;
  6261. }
  6262. $current = $this->inputZipper->front[$i];
  6263. return true;
  6264. }
  6265. /**
  6266. * Handler that is called when a text token is processed
  6267. */
  6268. public function handleText(&$token)
  6269. {
  6270. }
  6271. /**
  6272. * Handler that is called when a start or empty token is processed
  6273. */
  6274. public function handleElement(&$token)
  6275. {
  6276. }
  6277. /**
  6278. * Handler that is called when an end token is processed
  6279. */
  6280. public function handleEnd(&$token)
  6281. {
  6282. $this->notifyEnd($token);
  6283. }
  6284. /**
  6285. * Notifier that is called when an end token is processed
  6286. * @param HTMLPurifier_Token $token Current token variable.
  6287. * @note This differs from handlers in that the token is read-only
  6288. * @deprecated
  6289. */
  6290. public function notifyEnd($token)
  6291. {
  6292. }
  6293. }
  6294. /**
  6295. * Represents a language and defines localizable string formatting and
  6296. * other functions, as well as the localized messages for HTML Purifier.
  6297. */
  6298. class HTMLPurifier_Language
  6299. {
  6300. /**
  6301. * ISO 639 language code of language. Prefers shortest possible version.
  6302. * @type string
  6303. */
  6304. public $code = 'en';
  6305. /**
  6306. * Fallback language code.
  6307. * @type bool|string
  6308. */
  6309. public $fallback = false;
  6310. /**
  6311. * Array of localizable messages.
  6312. * @type array
  6313. */
  6314. public $messages = array();
  6315. /**
  6316. * Array of localizable error codes.
  6317. * @type array
  6318. */
  6319. public $errorNames = array();
  6320. /**
  6321. * True if no message file was found for this language, so English
  6322. * is being used instead. Check this if you'd like to notify the
  6323. * user that they've used a non-supported language.
  6324. * @type bool
  6325. */
  6326. public $error = false;
  6327. /**
  6328. * Has the language object been loaded yet?
  6329. * @type bool
  6330. * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
  6331. */
  6332. public $_loaded = false;
  6333. /**
  6334. * @type HTMLPurifier_Config
  6335. */
  6336. protected $config;
  6337. /**
  6338. * @type HTMLPurifier_Context
  6339. */
  6340. protected $context;
  6341. /**
  6342. * @param HTMLPurifier_Config $config
  6343. * @param HTMLPurifier_Context $context
  6344. */
  6345. public function __construct($config, $context)
  6346. {
  6347. $this->config = $config;
  6348. $this->context = $context;
  6349. }
  6350. /**
  6351. * Loads language object with necessary info from factory cache
  6352. * @note This is a lazy loader
  6353. */
  6354. public function load()
  6355. {
  6356. if ($this->_loaded) {
  6357. return;
  6358. }
  6359. $factory = HTMLPurifier_LanguageFactory::instance();
  6360. $factory->loadLanguage($this->code);
  6361. foreach ($factory->keys as $key) {
  6362. $this->$key = $factory->cache[$this->code][$key];
  6363. }
  6364. $this->_loaded = true;
  6365. }
  6366. /**
  6367. * Retrieves a localised message.
  6368. * @param string $key string identifier of message
  6369. * @return string localised message
  6370. */
  6371. public function getMessage($key)
  6372. {
  6373. if (!$this->_loaded) {
  6374. $this->load();
  6375. }
  6376. if (!isset($this->messages[$key])) {
  6377. return "[$key]";
  6378. }
  6379. return $this->messages[$key];
  6380. }
  6381. /**
  6382. * Retrieves a localised error name.
  6383. * @param int $int error number, corresponding to PHP's error reporting
  6384. * @return string localised message
  6385. */
  6386. public function getErrorName($int)
  6387. {
  6388. if (!$this->_loaded) {
  6389. $this->load();
  6390. }
  6391. if (!isset($this->errorNames[$int])) {
  6392. return "[Error: $int]";
  6393. }
  6394. return $this->errorNames[$int];
  6395. }
  6396. /**
  6397. * Converts an array list into a string readable representation
  6398. * @param array $array
  6399. * @return string
  6400. */
  6401. public function listify($array)
  6402. {
  6403. $sep = $this->getMessage('Item separator');
  6404. $sep_last = $this->getMessage('Item separator last');
  6405. $ret = '';
  6406. for ($i = 0, $c = count($array); $i < $c; $i++) {
  6407. if ($i == 0) {
  6408. } elseif ($i + 1 < $c) {
  6409. $ret .= $sep;
  6410. } else {
  6411. $ret .= $sep_last;
  6412. }
  6413. $ret .= $array[$i];
  6414. }
  6415. return $ret;
  6416. }
  6417. /**
  6418. * Formats a localised message with passed parameters
  6419. * @param string $key string identifier of message
  6420. * @param array $args Parameters to substitute in
  6421. * @return string localised message
  6422. * @todo Implement conditionals? Right now, some messages make
  6423. * reference to line numbers, but those aren't always available
  6424. */
  6425. public function formatMessage($key, $args = array())
  6426. {
  6427. if (!$this->_loaded) {
  6428. $this->load();
  6429. }
  6430. if (!isset($this->messages[$key])) {
  6431. return "[$key]";
  6432. }
  6433. $raw = $this->messages[$key];
  6434. $subst = array();
  6435. $generator = false;
  6436. foreach ($args as $i => $value) {
  6437. if (is_object($value)) {
  6438. if ($value instanceof HTMLPurifier_Token) {
  6439. // factor this out some time
  6440. if (!$generator) {
  6441. $generator = $this->context->get('Generator');
  6442. }
  6443. if (isset($value->name)) {
  6444. $subst['$'.$i.'.Name'] = $value->name;
  6445. }
  6446. if (isset($value->data)) {
  6447. $subst['$'.$i.'.Data'] = $value->data;
  6448. }
  6449. $subst['$'.$i.'.Compact'] =
  6450. $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
  6451. // a more complex algorithm for compact representation
  6452. // could be introduced for all types of tokens. This
  6453. // may need to be factored out into a dedicated class
  6454. if (!empty($value->attr)) {
  6455. $stripped_token = clone $value;
  6456. $stripped_token->attr = array();
  6457. $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
  6458. }
  6459. $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
  6460. }
  6461. continue;
  6462. } elseif (is_array($value)) {
  6463. $keys = array_keys($value);
  6464. if (array_keys($keys) === $keys) {
  6465. // list
  6466. $subst['$'.$i] = $this->listify($value);
  6467. } else {
  6468. // associative array
  6469. // no $i implementation yet, sorry
  6470. $subst['$'.$i.'.Keys'] = $this->listify($keys);
  6471. $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
  6472. }
  6473. continue;
  6474. }
  6475. $subst['$' . $i] = $value;
  6476. }
  6477. return strtr($raw, $subst);
  6478. }
  6479. }
  6480. /**
  6481. * Class responsible for generating HTMLPurifier_Language objects, managing
  6482. * caching and fallbacks.
  6483. * @note Thanks to MediaWiki for the general logic, although this version
  6484. * has been entirely rewritten
  6485. * @todo Serialized cache for languages
  6486. */
  6487. class HTMLPurifier_LanguageFactory
  6488. {
  6489. /**
  6490. * Cache of language code information used to load HTMLPurifier_Language objects.
  6491. * Structure is: $factory->cache[$language_code][$key] = $value
  6492. * @type array
  6493. */
  6494. public $cache;
  6495. /**
  6496. * Valid keys in the HTMLPurifier_Language object. Designates which
  6497. * variables to slurp out of a message file.
  6498. * @type array
  6499. */
  6500. public $keys = array('fallback', 'messages', 'errorNames');
  6501. /**
  6502. * Instance to validate language codes.
  6503. * @type HTMLPurifier_AttrDef_Lang
  6504. *
  6505. */
  6506. protected $validator;
  6507. /**
  6508. * Cached copy of dirname(__FILE__), directory of current file without
  6509. * trailing slash.
  6510. * @type string
  6511. */
  6512. protected $dir;
  6513. /**
  6514. * Keys whose contents are a hash map and can be merged.
  6515. * @type array
  6516. */
  6517. protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
  6518. /**
  6519. * Keys whose contents are a list and can be merged.
  6520. * @value array lookup
  6521. */
  6522. protected $mergeable_keys_list = array();
  6523. /**
  6524. * Retrieve sole instance of the factory.
  6525. * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
  6526. * or bool true to reset to default factory.
  6527. * @return HTMLPurifier_LanguageFactory
  6528. */
  6529. public static function instance($prototype = null)
  6530. {
  6531. static $instance = null;
  6532. if ($prototype !== null) {
  6533. $instance = $prototype;
  6534. } elseif ($instance === null || $prototype == true) {
  6535. $instance = new HTMLPurifier_LanguageFactory();
  6536. $instance->setup();
  6537. }
  6538. return $instance;
  6539. }
  6540. /**
  6541. * Sets up the singleton, much like a constructor
  6542. * @note Prevents people from getting this outside of the singleton
  6543. */
  6544. public function setup()
  6545. {
  6546. $this->validator = new HTMLPurifier_AttrDef_Lang();
  6547. $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
  6548. }
  6549. /**
  6550. * Creates a language object, handles class fallbacks
  6551. * @param HTMLPurifier_Config $config
  6552. * @param HTMLPurifier_Context $context
  6553. * @param bool|string $code Code to override configuration with. Private parameter.
  6554. * @return HTMLPurifier_Language
  6555. */
  6556. public function create($config, $context, $code = false)
  6557. {
  6558. // validate language code
  6559. if ($code === false) {
  6560. $code = $this->validator->validate(
  6561. $config->get('Core.Language'),
  6562. $config,
  6563. $context
  6564. );
  6565. } else {
  6566. $code = $this->validator->validate($code, $config, $context);
  6567. }
  6568. if ($code === false) {
  6569. $code = 'en'; // malformed code becomes English
  6570. }
  6571. $pcode = str_replace('-', '_', $code); // make valid PHP classname
  6572. static $depth = 0; // recursion protection
  6573. if ($code == 'en') {
  6574. $lang = new HTMLPurifier_Language($config, $context);
  6575. } else {
  6576. $class = 'HTMLPurifier_Language_' . $pcode;
  6577. $file = $this->dir . '/Language/classes/' . $code . '.php';
  6578. if (file_exists($file) || class_exists($class, false)) {
  6579. $lang = new $class($config, $context);
  6580. } else {
  6581. // Go fallback
  6582. $raw_fallback = $this->getFallbackFor($code);
  6583. $fallback = $raw_fallback ? $raw_fallback : 'en';
  6584. $depth++;
  6585. $lang = $this->create($config, $context, $fallback);
  6586. if (!$raw_fallback) {
  6587. $lang->error = true;
  6588. }
  6589. $depth--;
  6590. }
  6591. }
  6592. $lang->code = $code;
  6593. return $lang;
  6594. }
  6595. /**
  6596. * Returns the fallback language for language
  6597. * @note Loads the original language into cache
  6598. * @param string $code language code
  6599. * @return string|bool
  6600. */
  6601. public function getFallbackFor($code)
  6602. {
  6603. $this->loadLanguage($code);
  6604. return $this->cache[$code]['fallback'];
  6605. }
  6606. /**
  6607. * Loads language into the cache, handles message file and fallbacks
  6608. * @param string $code language code
  6609. */
  6610. public function loadLanguage($code)
  6611. {
  6612. static $languages_seen = array(); // recursion guard
  6613. // abort if we've already loaded it
  6614. if (isset($this->cache[$code])) {
  6615. return;
  6616. }
  6617. // generate filename
  6618. $filename = $this->dir . '/Language/messages/' . $code . '.php';
  6619. // default fallback : may be overwritten by the ensuing include
  6620. $fallback = ($code != 'en') ? 'en' : false;
  6621. // load primary localisation
  6622. if (!file_exists($filename)) {
  6623. // skip the include: will rely solely on fallback
  6624. $filename = $this->dir . '/Language/messages/en.php';
  6625. $cache = array();
  6626. } else {
  6627. include $filename;
  6628. $cache = compact($this->keys);
  6629. }
  6630. // load fallback localisation
  6631. if (!empty($fallback)) {
  6632. // infinite recursion guard
  6633. if (isset($languages_seen[$code])) {
  6634. trigger_error(
  6635. 'Circular fallback reference in language ' .
  6636. $code,
  6637. E_USER_ERROR
  6638. );
  6639. $fallback = 'en';
  6640. }
  6641. $language_seen[$code] = true;
  6642. // load the fallback recursively
  6643. $this->loadLanguage($fallback);
  6644. $fallback_cache = $this->cache[$fallback];
  6645. // merge fallback with current language
  6646. foreach ($this->keys as $key) {
  6647. if (isset($cache[$key]) && isset($fallback_cache[$key])) {
  6648. if (isset($this->mergeable_keys_map[$key])) {
  6649. $cache[$key] = $cache[$key] + $fallback_cache[$key];
  6650. } elseif (isset($this->mergeable_keys_list[$key])) {
  6651. $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
  6652. }
  6653. } else {
  6654. $cache[$key] = $fallback_cache[$key];
  6655. }
  6656. }
  6657. }
  6658. // save to cache for later retrieval
  6659. $this->cache[$code] = $cache;
  6660. return;
  6661. }
  6662. }
  6663. /**
  6664. * Represents a measurable length, with a string numeric magnitude
  6665. * and a unit. This object is immutable.
  6666. */
  6667. class HTMLPurifier_Length
  6668. {
  6669. /**
  6670. * String numeric magnitude.
  6671. * @type string
  6672. */
  6673. protected $n;
  6674. /**
  6675. * String unit. False is permitted if $n = 0.
  6676. * @type string|bool
  6677. */
  6678. protected $unit;
  6679. /**
  6680. * Whether or not this length is valid. Null if not calculated yet.
  6681. * @type bool
  6682. */
  6683. protected $isValid;
  6684. /**
  6685. * Array Lookup array of units recognized by CSS 3
  6686. * @type array
  6687. */
  6688. protected static $allowedUnits = array(
  6689. 'em' => true, 'ex' => true, 'px' => true, 'in' => true,
  6690. 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,
  6691. 'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,
  6692. 'vmin' => true, 'vmax' => true
  6693. );
  6694. /**
  6695. * @param string $n Magnitude
  6696. * @param bool|string $u Unit
  6697. */
  6698. public function __construct($n = '0', $u = false)
  6699. {
  6700. $this->n = (string) $n;
  6701. $this->unit = $u !== false ? (string) $u : false;
  6702. }
  6703. /**
  6704. * @param string $s Unit string, like '2em' or '3.4in'
  6705. * @return HTMLPurifier_Length
  6706. * @warning Does not perform validation.
  6707. */
  6708. public static function make($s)
  6709. {
  6710. if ($s instanceof HTMLPurifier_Length) {
  6711. return $s;
  6712. }
  6713. $n_length = strspn($s, '1234567890.+-');
  6714. $n = substr($s, 0, $n_length);
  6715. $unit = substr($s, $n_length);
  6716. if ($unit === '') {
  6717. $unit = false;
  6718. }
  6719. return new HTMLPurifier_Length($n, $unit);
  6720. }
  6721. /**
  6722. * Validates the number and unit.
  6723. * @return bool
  6724. */
  6725. protected function validate()
  6726. {
  6727. // Special case:
  6728. if ($this->n === '+0' || $this->n === '-0') {
  6729. $this->n = '0';
  6730. }
  6731. if ($this->n === '0' && $this->unit === false) {
  6732. return true;
  6733. }
  6734. if (!ctype_lower($this->unit)) {
  6735. $this->unit = strtolower($this->unit);
  6736. }
  6737. if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
  6738. return false;
  6739. }
  6740. // Hack:
  6741. $def = new HTMLPurifier_AttrDef_CSS_Number();
  6742. $result = $def->validate($this->n, false, false);
  6743. if ($result === false) {
  6744. return false;
  6745. }
  6746. $this->n = $result;
  6747. return true;
  6748. }
  6749. /**
  6750. * Returns string representation of number.
  6751. * @return string
  6752. */
  6753. public function toString()
  6754. {
  6755. if (!$this->isValid()) {
  6756. return false;
  6757. }
  6758. return $this->n . $this->unit;
  6759. }
  6760. /**
  6761. * Retrieves string numeric magnitude.
  6762. * @return string
  6763. */
  6764. public function getN()
  6765. {
  6766. return $this->n;
  6767. }
  6768. /**
  6769. * Retrieves string unit.
  6770. * @return string
  6771. */
  6772. public function getUnit()
  6773. {
  6774. return $this->unit;
  6775. }
  6776. /**
  6777. * Returns true if this length unit is valid.
  6778. * @return bool
  6779. */
  6780. public function isValid()
  6781. {
  6782. if ($this->isValid === null) {
  6783. $this->isValid = $this->validate();
  6784. }
  6785. return $this->isValid;
  6786. }
  6787. /**
  6788. * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
  6789. * @param HTMLPurifier_Length $l
  6790. * @return int
  6791. * @warning If both values are too large or small, this calculation will
  6792. * not work properly
  6793. */
  6794. public function compareTo($l)
  6795. {
  6796. if ($l === false) {
  6797. return false;
  6798. }
  6799. if ($l->unit !== $this->unit) {
  6800. $converter = new HTMLPurifier_UnitConverter();
  6801. $l = $converter->convert($l, $this->unit);
  6802. if ($l === false) {
  6803. return false;
  6804. }
  6805. }
  6806. return $this->n - $l->n;
  6807. }
  6808. }
  6809. /**
  6810. * Forgivingly lexes HTML (SGML-style) markup into tokens.
  6811. *
  6812. * A lexer parses a string of SGML-style markup and converts them into
  6813. * corresponding tokens. It doesn't check for well-formedness, although its
  6814. * internal mechanism may make this automatic (such as the case of
  6815. * HTMLPurifier_Lexer_DOMLex). There are several implementations to choose
  6816. * from.
  6817. *
  6818. * A lexer is HTML-oriented: it might work with XML, but it's not
  6819. * recommended, as we adhere to a subset of the specification for optimization
  6820. * reasons. This might change in the future. Also, most tokenizers are not
  6821. * expected to handle DTDs or PIs.
  6822. *
  6823. * This class should not be directly instantiated, but you may use create() to
  6824. * retrieve a default copy of the lexer. Being a supertype, this class
  6825. * does not actually define any implementation, but offers commonly used
  6826. * convenience functions for subclasses.
  6827. *
  6828. * @note The unit tests will instantiate this class for testing purposes, as
  6829. * many of the utility functions require a class to be instantiated.
  6830. * This means that, even though this class is not runnable, it will
  6831. * not be declared abstract.
  6832. *
  6833. * @par
  6834. *
  6835. * @note
  6836. * We use tokens rather than create a DOM representation because DOM would:
  6837. *
  6838. * @par
  6839. * -# Require more processing and memory to create,
  6840. * -# Is not streamable, and
  6841. * -# Has the entire document structure (html and body not needed).
  6842. *
  6843. * @par
  6844. * However, DOM is helpful in that it makes it easy to move around nodes
  6845. * without a lot of lookaheads to see when a tag is closed. This is a
  6846. * limitation of the token system and some workarounds would be nice.
  6847. */
  6848. class HTMLPurifier_Lexer
  6849. {
  6850. /**
  6851. * Whether or not this lexer implements line-number/column-number tracking.
  6852. * If it does, set to true.
  6853. */
  6854. public $tracksLineNumbers = false;
  6855. // -- STATIC ----------------------------------------------------------
  6856. /**
  6857. * Retrieves or sets the default Lexer as a Prototype Factory.
  6858. *
  6859. * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
  6860. * a few exceptions involving special features that only DirectLex
  6861. * implements.
  6862. *
  6863. * @note The behavior of this class has changed, rather than accepting
  6864. * a prototype object, it now accepts a configuration object.
  6865. * To specify your own prototype, set %Core.LexerImpl to it.
  6866. * This change in behavior de-singletonizes the lexer object.
  6867. *
  6868. * @param HTMLPurifier_Config $config
  6869. * @return HTMLPurifier_Lexer
  6870. * @throws HTMLPurifier_Exception
  6871. */
  6872. public static function create($config)
  6873. {
  6874. if (!($config instanceof HTMLPurifier_Config)) {
  6875. $lexer = $config;
  6876. trigger_error(
  6877. "Passing a prototype to
  6878. HTMLPurifier_Lexer::create() is deprecated, please instead
  6879. use %Core.LexerImpl",
  6880. E_USER_WARNING
  6881. );
  6882. } else {
  6883. $lexer = $config->get('Core.LexerImpl');
  6884. }
  6885. $needs_tracking =
  6886. $config->get('Core.MaintainLineNumbers') ||
  6887. $config->get('Core.CollectErrors');
  6888. $inst = null;
  6889. if (is_object($lexer)) {
  6890. $inst = $lexer;
  6891. } else {
  6892. if (is_null($lexer)) {
  6893. do {
  6894. // auto-detection algorithm
  6895. if ($needs_tracking) {
  6896. $lexer = 'DirectLex';
  6897. break;
  6898. }
  6899. if (class_exists('DOMDocument', false) &&
  6900. method_exists('DOMDocument', 'loadHTML') &&
  6901. !extension_loaded('domxml')
  6902. ) {
  6903. // check for DOM support, because while it's part of the
  6904. // core, it can be disabled compile time. Also, the PECL
  6905. // domxml extension overrides the default DOM, and is evil
  6906. // and nasty and we shan't bother to support it
  6907. $lexer = 'DOMLex';
  6908. } else {
  6909. $lexer = 'DirectLex';
  6910. }
  6911. } while (0);
  6912. } // do..while so we can break
  6913. // instantiate recognized string names
  6914. switch ($lexer) {
  6915. case 'DOMLex':
  6916. $inst = new HTMLPurifier_Lexer_DOMLex();
  6917. break;
  6918. case 'DirectLex':
  6919. $inst = new HTMLPurifier_Lexer_DirectLex();
  6920. break;
  6921. case 'PH5P':
  6922. $inst = new HTMLPurifier_Lexer_PH5P();
  6923. break;
  6924. default:
  6925. throw new HTMLPurifier_Exception(
  6926. "Cannot instantiate unrecognized Lexer type " .
  6927. htmlspecialchars($lexer)
  6928. );
  6929. }
  6930. }
  6931. if (!$inst) {
  6932. throw new HTMLPurifier_Exception('No lexer was instantiated');
  6933. }
  6934. // once PHP DOM implements native line numbers, or we
  6935. // hack out something using XSLT, remove this stipulation
  6936. if ($needs_tracking && !$inst->tracksLineNumbers) {
  6937. throw new HTMLPurifier_Exception(
  6938. 'Cannot use lexer that does not support line numbers with ' .
  6939. 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
  6940. );
  6941. }
  6942. return $inst;
  6943. }
  6944. // -- CONVENIENCE MEMBERS ---------------------------------------------
  6945. public function __construct()
  6946. {
  6947. $this->_entity_parser = new HTMLPurifier_EntityParser();
  6948. }
  6949. /**
  6950. * Most common entity to raw value conversion table for special entities.
  6951. * @type array
  6952. */
  6953. protected $_special_entity2str =
  6954. array(
  6955. '&quot;' => '"',
  6956. '&amp;' => '&',
  6957. '&lt;' => '<',
  6958. '&gt;' => '>',
  6959. '&#39;' => "'",
  6960. '&#039;' => "'",
  6961. '&#x27;' => "'"
  6962. );
  6963. public function parseText($string, $config) {
  6964. return $this->parseData($string, false, $config);
  6965. }
  6966. public function parseAttr($string, $config) {
  6967. return $this->parseData($string, true, $config);
  6968. }
  6969. /**
  6970. * Parses special entities into the proper characters.
  6971. *
  6972. * This string will translate escaped versions of the special characters
  6973. * into the correct ones.
  6974. *
  6975. * @param string $string String character data to be parsed.
  6976. * @return string Parsed character data.
  6977. */
  6978. public function parseData($string, $is_attr, $config)
  6979. {
  6980. // following functions require at least one character
  6981. if ($string === '') {
  6982. return '';
  6983. }
  6984. // subtracts amps that cannot possibly be escaped
  6985. $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
  6986. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  6987. if (!$num_amp) {
  6988. return $string;
  6989. } // abort if no entities
  6990. $num_esc_amp = substr_count($string, '&amp;');
  6991. $string = strtr($string, $this->_special_entity2str);
  6992. // code duplication for sake of optimization, see above
  6993. $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
  6994. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  6995. if ($num_amp_2 <= $num_esc_amp) {
  6996. return $string;
  6997. }
  6998. // hmm... now we have some uncommon entities. Use the callback.
  6999. if ($config->get('Core.LegacyEntityDecoder')) {
  7000. $string = $this->_entity_parser->substituteSpecialEntities($string);
  7001. } else {
  7002. if ($is_attr) {
  7003. $string = $this->_entity_parser->substituteAttrEntities($string);
  7004. } else {
  7005. $string = $this->_entity_parser->substituteTextEntities($string);
  7006. }
  7007. }
  7008. return $string;
  7009. }
  7010. /**
  7011. * Lexes an HTML string into tokens.
  7012. * @param $string String HTML.
  7013. * @param HTMLPurifier_Config $config
  7014. * @param HTMLPurifier_Context $context
  7015. * @return HTMLPurifier_Token[] array representation of HTML.
  7016. */
  7017. public function tokenizeHTML($string, $config, $context)
  7018. {
  7019. trigger_error('Call to abstract class', E_USER_ERROR);
  7020. }
  7021. /**
  7022. * Translates CDATA sections into regular sections (through escaping).
  7023. * @param string $string HTML string to process.
  7024. * @return string HTML with CDATA sections escaped.
  7025. */
  7026. protected static function escapeCDATA($string)
  7027. {
  7028. return preg_replace_callback(
  7029. '/<!\[CDATA\[(.+?)\]\]>/s',
  7030. array('HTMLPurifier_Lexer', 'CDATACallback'),
  7031. $string
  7032. );
  7033. }
  7034. /**
  7035. * Special CDATA case that is especially convoluted for <script>
  7036. * @param string $string HTML string to process.
  7037. * @return string HTML with CDATA sections escaped.
  7038. */
  7039. protected static function escapeCommentedCDATA($string)
  7040. {
  7041. return preg_replace_callback(
  7042. '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
  7043. array('HTMLPurifier_Lexer', 'CDATACallback'),
  7044. $string
  7045. );
  7046. }
  7047. /**
  7048. * Special Internet Explorer conditional comments should be removed.
  7049. * @param string $string HTML string to process.
  7050. * @return string HTML with conditional comments removed.
  7051. */
  7052. protected static function removeIEConditional($string)
  7053. {
  7054. return preg_replace(
  7055. '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
  7056. '',
  7057. $string
  7058. );
  7059. }
  7060. /**
  7061. * Callback function for escapeCDATA() that does the work.
  7062. *
  7063. * @warning Though this is public in order to let the callback happen,
  7064. * calling it directly is not recommended.
  7065. * @param array $matches PCRE matches array, with index 0 the entire match
  7066. * and 1 the inside of the CDATA section.
  7067. * @return string Escaped internals of the CDATA section.
  7068. */
  7069. protected static function CDATACallback($matches)
  7070. {
  7071. // not exactly sure why the character set is needed, but whatever
  7072. return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
  7073. }
  7074. /**
  7075. * Takes a piece of HTML and normalizes it by converting entities, fixing
  7076. * encoding, extracting bits, and other good stuff.
  7077. * @param string $html HTML.
  7078. * @param HTMLPurifier_Config $config
  7079. * @param HTMLPurifier_Context $context
  7080. * @return string
  7081. * @todo Consider making protected
  7082. */
  7083. public function normalize($html, $config, $context)
  7084. {
  7085. // normalize newlines to \n
  7086. if ($config->get('Core.NormalizeNewlines')) {
  7087. $html = str_replace("\r\n", "\n", $html);
  7088. $html = str_replace("\r", "\n", $html);
  7089. }
  7090. if ($config->get('HTML.Trusted')) {
  7091. // escape convoluted CDATA
  7092. $html = $this->escapeCommentedCDATA($html);
  7093. }
  7094. // escape CDATA
  7095. $html = $this->escapeCDATA($html);
  7096. $html = $this->removeIEConditional($html);
  7097. // extract body from document if applicable
  7098. if ($config->get('Core.ConvertDocumentToFragment')) {
  7099. $e = false;
  7100. if ($config->get('Core.CollectErrors')) {
  7101. $e =& $context->get('ErrorCollector');
  7102. }
  7103. $new_html = $this->extractBody($html);
  7104. if ($e && $new_html != $html) {
  7105. $e->send(E_WARNING, 'Lexer: Extracted body');
  7106. }
  7107. $html = $new_html;
  7108. }
  7109. // expand entities that aren't the big five
  7110. if ($config->get('Core.LegacyEntityDecoder')) {
  7111. $html = $this->_entity_parser->substituteNonSpecialEntities($html);
  7112. }
  7113. // clean into wellformed UTF-8 string for an SGML context: this has
  7114. // to be done after entity expansion because the entities sometimes
  7115. // represent non-SGML characters (horror, horror!)
  7116. $html = HTMLPurifier_Encoder::cleanUTF8($html);
  7117. // if processing instructions are to removed, remove them now
  7118. if ($config->get('Core.RemoveProcessingInstructions')) {
  7119. $html = preg_replace('#<\?.+?\?>#s', '', $html);
  7120. }
  7121. $hidden_elements = $config->get('Core.HiddenElements');
  7122. if ($config->get('Core.AggressivelyRemoveScript') &&
  7123. !($config->get('HTML.Trusted') || !$config->get('Core.RemoveScriptContents')
  7124. || empty($hidden_elements["script"]))) {
  7125. $html = preg_replace('#<script[^>]*>.*?</script>#i', '', $html);
  7126. }
  7127. return $html;
  7128. }
  7129. /**
  7130. * Takes a string of HTML (fragment or document) and returns the content
  7131. * @todo Consider making protected
  7132. */
  7133. public function extractBody($html)
  7134. {
  7135. $matches = array();
  7136. $result = preg_match('|(.*?)<body[^>]*>(.*)</body>|is', $html, $matches);
  7137. if ($result) {
  7138. // Make sure it's not in a comment
  7139. $comment_start = strrpos($matches[1], '<!--');
  7140. $comment_end = strrpos($matches[1], '-->');
  7141. if ($comment_start === false ||
  7142. ($comment_end !== false && $comment_end > $comment_start)) {
  7143. return $matches[2];
  7144. }
  7145. }
  7146. return $html;
  7147. }
  7148. }
  7149. /**
  7150. * Abstract base node class that all others inherit from.
  7151. *
  7152. * Why do we not use the DOM extension? (1) It is not always available,
  7153. * (2) it has funny constraints on the data it can represent,
  7154. * whereas we want a maximally flexible representation, and (3) its
  7155. * interface is a bit cumbersome.
  7156. */
  7157. abstract class HTMLPurifier_Node
  7158. {
  7159. /**
  7160. * Line number of the start token in the source document
  7161. * @type int
  7162. */
  7163. public $line;
  7164. /**
  7165. * Column number of the start token in the source document. Null if unknown.
  7166. * @type int
  7167. */
  7168. public $col;
  7169. /**
  7170. * Lookup array of processing that this token is exempt from.
  7171. * Currently, valid values are "ValidateAttributes".
  7172. * @type array
  7173. */
  7174. public $armor = array();
  7175. /**
  7176. * When true, this node should be ignored as non-existent.
  7177. *
  7178. * Who is responsible for ignoring dead nodes? FixNesting is
  7179. * responsible for removing them before passing on to child
  7180. * validators.
  7181. */
  7182. public $dead = false;
  7183. /**
  7184. * Returns a pair of start and end tokens, where the end token
  7185. * is null if it is not necessary. Does not include children.
  7186. * @type array
  7187. */
  7188. abstract public function toTokenPair();
  7189. }
  7190. /**
  7191. * Class that handles operations involving percent-encoding in URIs.
  7192. *
  7193. * @warning
  7194. * Be careful when reusing instances of PercentEncoder. The object
  7195. * you use for normalize() SHOULD NOT be used for encode(), or
  7196. * vice-versa.
  7197. */
  7198. class HTMLPurifier_PercentEncoder
  7199. {
  7200. /**
  7201. * Reserved characters to preserve when using encode().
  7202. * @type array
  7203. */
  7204. protected $preserve = array();
  7205. /**
  7206. * String of characters that should be preserved while using encode().
  7207. * @param bool $preserve
  7208. */
  7209. public function __construct($preserve = false)
  7210. {
  7211. // unreserved letters, ought to const-ify
  7212. for ($i = 48; $i <= 57; $i++) { // digits
  7213. $this->preserve[$i] = true;
  7214. }
  7215. for ($i = 65; $i <= 90; $i++) { // upper-case
  7216. $this->preserve[$i] = true;
  7217. }
  7218. for ($i = 97; $i <= 122; $i++) { // lower-case
  7219. $this->preserve[$i] = true;
  7220. }
  7221. $this->preserve[45] = true; // Dash -
  7222. $this->preserve[46] = true; // Period .
  7223. $this->preserve[95] = true; // Underscore _
  7224. $this->preserve[126]= true; // Tilde ~
  7225. // extra letters not to escape
  7226. if ($preserve !== false) {
  7227. for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
  7228. $this->preserve[ord($preserve[$i])] = true;
  7229. }
  7230. }
  7231. }
  7232. /**
  7233. * Our replacement for urlencode, it encodes all non-reserved characters,
  7234. * as well as any extra characters that were instructed to be preserved.
  7235. * @note
  7236. * Assumes that the string has already been normalized, making any
  7237. * and all percent escape sequences valid. Percents will not be
  7238. * re-escaped, regardless of their status in $preserve
  7239. * @param string $string String to be encoded
  7240. * @return string Encoded string.
  7241. */
  7242. public function encode($string)
  7243. {
  7244. $ret = '';
  7245. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  7246. if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
  7247. $ret .= '%' . sprintf('%02X', $int);
  7248. } else {
  7249. $ret .= $string[$i];
  7250. }
  7251. }
  7252. return $ret;
  7253. }
  7254. /**
  7255. * Fix up percent-encoding by decoding unreserved characters and normalizing.
  7256. * @warning This function is affected by $preserve, even though the
  7257. * usual desired behavior is for this not to preserve those
  7258. * characters. Be careful when reusing instances of PercentEncoder!
  7259. * @param string $string String to normalize
  7260. * @return string
  7261. */
  7262. public function normalize($string)
  7263. {
  7264. if ($string == '') {
  7265. return '';
  7266. }
  7267. $parts = explode('%', $string);
  7268. $ret = array_shift($parts);
  7269. foreach ($parts as $part) {
  7270. $length = strlen($part);
  7271. if ($length < 2) {
  7272. $ret .= '%25' . $part;
  7273. continue;
  7274. }
  7275. $encoding = substr($part, 0, 2);
  7276. $text = substr($part, 2);
  7277. if (!ctype_xdigit($encoding)) {
  7278. $ret .= '%25' . $part;
  7279. continue;
  7280. }
  7281. $int = hexdec($encoding);
  7282. if (isset($this->preserve[$int])) {
  7283. $ret .= chr($int) . $text;
  7284. continue;
  7285. }
  7286. $encoding = strtoupper($encoding);
  7287. $ret .= '%' . $encoding . $text;
  7288. }
  7289. return $ret;
  7290. }
  7291. }
  7292. /**
  7293. * Generic property list implementation
  7294. */
  7295. class HTMLPurifier_PropertyList
  7296. {
  7297. /**
  7298. * Internal data-structure for properties.
  7299. * @type array
  7300. */
  7301. protected $data = array();
  7302. /**
  7303. * Parent plist.
  7304. * @type HTMLPurifier_PropertyList
  7305. */
  7306. protected $parent;
  7307. /**
  7308. * Cache.
  7309. * @type array
  7310. */
  7311. protected $cache;
  7312. /**
  7313. * @param HTMLPurifier_PropertyList $parent Parent plist
  7314. */
  7315. public function __construct($parent = null)
  7316. {
  7317. $this->parent = $parent;
  7318. }
  7319. /**
  7320. * Recursively retrieves the value for a key
  7321. * @param string $name
  7322. * @throws HTMLPurifier_Exception
  7323. */
  7324. public function get($name)
  7325. {
  7326. if ($this->has($name)) {
  7327. return $this->data[$name];
  7328. }
  7329. // possible performance bottleneck, convert to iterative if necessary
  7330. if ($this->parent) {
  7331. return $this->parent->get($name);
  7332. }
  7333. throw new HTMLPurifier_Exception("Key '$name' not found");
  7334. }
  7335. /**
  7336. * Sets the value of a key, for this plist
  7337. * @param string $name
  7338. * @param mixed $value
  7339. */
  7340. public function set($name, $value)
  7341. {
  7342. $this->data[$name] = $value;
  7343. }
  7344. /**
  7345. * Returns true if a given key exists
  7346. * @param string $name
  7347. * @return bool
  7348. */
  7349. public function has($name)
  7350. {
  7351. return array_key_exists($name, $this->data);
  7352. }
  7353. /**
  7354. * Resets a value to the value of it's parent, usually the default. If
  7355. * no value is specified, the entire plist is reset.
  7356. * @param string $name
  7357. */
  7358. public function reset($name = null)
  7359. {
  7360. if ($name == null) {
  7361. $this->data = array();
  7362. } else {
  7363. unset($this->data[$name]);
  7364. }
  7365. }
  7366. /**
  7367. * Squashes this property list and all of its property lists into a single
  7368. * array, and returns the array. This value is cached by default.
  7369. * @param bool $force If true, ignores the cache and regenerates the array.
  7370. * @return array
  7371. */
  7372. public function squash($force = false)
  7373. {
  7374. if ($this->cache !== null && !$force) {
  7375. return $this->cache;
  7376. }
  7377. if ($this->parent) {
  7378. return $this->cache = array_merge($this->parent->squash($force), $this->data);
  7379. } else {
  7380. return $this->cache = $this->data;
  7381. }
  7382. }
  7383. /**
  7384. * Returns the parent plist.
  7385. * @return HTMLPurifier_PropertyList
  7386. */
  7387. public function getParent()
  7388. {
  7389. return $this->parent;
  7390. }
  7391. /**
  7392. * Sets the parent plist.
  7393. * @param HTMLPurifier_PropertyList $plist Parent plist
  7394. */
  7395. public function setParent($plist)
  7396. {
  7397. $this->parent = $plist;
  7398. }
  7399. }
  7400. /**
  7401. * Property list iterator. Do not instantiate this class directly.
  7402. */
  7403. class HTMLPurifier_PropertyListIterator extends FilterIterator
  7404. {
  7405. /**
  7406. * @type int
  7407. */
  7408. protected $l;
  7409. /**
  7410. * @type string
  7411. */
  7412. protected $filter;
  7413. /**
  7414. * @param Iterator $iterator Array of data to iterate over
  7415. * @param string $filter Optional prefix to only allow values of
  7416. */
  7417. public function __construct(Iterator $iterator, $filter = null)
  7418. {
  7419. parent::__construct($iterator);
  7420. $this->l = strlen($filter);
  7421. $this->filter = $filter;
  7422. }
  7423. /**
  7424. * @return bool
  7425. */
  7426. public function accept()
  7427. {
  7428. $key = $this->getInnerIterator()->key();
  7429. if (strncmp($key, $this->filter, $this->l) !== 0) {
  7430. return false;
  7431. }
  7432. return true;
  7433. }
  7434. }
  7435. /**
  7436. * A simple array-backed queue, based off of the classic Okasaki
  7437. * persistent amortized queue. The basic idea is to maintain two
  7438. * stacks: an input stack and an output stack. When the output
  7439. * stack runs out, reverse the input stack and use it as the output
  7440. * stack.
  7441. *
  7442. * We don't use the SPL implementation because it's only supported
  7443. * on PHP 5.3 and later.
  7444. *
  7445. * Exercise: Prove that push/pop on this queue take amortized O(1) time.
  7446. *
  7447. * Exercise: Extend this queue to be a deque, while preserving amortized
  7448. * O(1) time. Some care must be taken on rebalancing to avoid quadratic
  7449. * behaviour caused by repeatedly shuffling data from the input stack
  7450. * to the output stack and back.
  7451. */
  7452. class HTMLPurifier_Queue {
  7453. private $input;
  7454. private $output;
  7455. public function __construct($input = array()) {
  7456. $this->input = $input;
  7457. $this->output = array();
  7458. }
  7459. /**
  7460. * Shifts an element off the front of the queue.
  7461. */
  7462. public function shift() {
  7463. if (empty($this->output)) {
  7464. $this->output = array_reverse($this->input);
  7465. $this->input = array();
  7466. }
  7467. if (empty($this->output)) {
  7468. return NULL;
  7469. }
  7470. return array_pop($this->output);
  7471. }
  7472. /**
  7473. * Pushes an element onto the front of the queue.
  7474. */
  7475. public function push($x) {
  7476. array_push($this->input, $x);
  7477. }
  7478. /**
  7479. * Checks if it's empty.
  7480. */
  7481. public function isEmpty() {
  7482. return empty($this->input) && empty($this->output);
  7483. }
  7484. }
  7485. /**
  7486. * Supertype for classes that define a strategy for modifying/purifying tokens.
  7487. *
  7488. * While HTMLPurifier's core purpose is fixing HTML into something proper,
  7489. * strategies provide plug points for extra configuration or even extra
  7490. * features, such as custom tags, custom parsing of text, etc.
  7491. */
  7492. abstract class HTMLPurifier_Strategy
  7493. {
  7494. /**
  7495. * Executes the strategy on the tokens.
  7496. *
  7497. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
  7498. * @param HTMLPurifier_Config $config
  7499. * @param HTMLPurifier_Context $context
  7500. * @return HTMLPurifier_Token[] Processed array of token objects.
  7501. */
  7502. abstract public function execute($tokens, $config, $context);
  7503. }
  7504. /**
  7505. * This is in almost every respect equivalent to an array except
  7506. * that it keeps track of which keys were accessed.
  7507. *
  7508. * @warning For the sake of backwards compatibility with early versions
  7509. * of PHP 5, you must not use the $hash[$key] syntax; if you do
  7510. * our version of offsetGet is never called.
  7511. */
  7512. class HTMLPurifier_StringHash extends ArrayObject
  7513. {
  7514. /**
  7515. * @type array
  7516. */
  7517. protected $accessed = array();
  7518. /**
  7519. * Retrieves a value, and logs the access.
  7520. * @param mixed $index
  7521. * @return mixed
  7522. */
  7523. public function offsetGet($index)
  7524. {
  7525. $this->accessed[$index] = true;
  7526. return parent::offsetGet($index);
  7527. }
  7528. /**
  7529. * Returns a lookup array of all array indexes that have been accessed.
  7530. * @return array in form array($index => true).
  7531. */
  7532. public function getAccessed()
  7533. {
  7534. return $this->accessed;
  7535. }
  7536. /**
  7537. * Resets the access array.
  7538. */
  7539. public function resetAccessed()
  7540. {
  7541. $this->accessed = array();
  7542. }
  7543. }
  7544. /**
  7545. * Parses string hash files. File format is as such:
  7546. *
  7547. * DefaultKeyValue
  7548. * KEY: Value
  7549. * KEY2: Value2
  7550. * --MULTILINE-KEY--
  7551. * Multiline
  7552. * value.
  7553. *
  7554. * Which would output something similar to:
  7555. *
  7556. * array(
  7557. * 'ID' => 'DefaultKeyValue',
  7558. * 'KEY' => 'Value',
  7559. * 'KEY2' => 'Value2',
  7560. * 'MULTILINE-KEY' => "Multiline\nvalue.\n",
  7561. * )
  7562. *
  7563. * We use this as an easy to use file-format for configuration schema
  7564. * files, but the class itself is usage agnostic.
  7565. *
  7566. * You can use ---- to forcibly terminate parsing of a single string-hash;
  7567. * this marker is used in multi string-hashes to delimit boundaries.
  7568. */
  7569. class HTMLPurifier_StringHashParser
  7570. {
  7571. /**
  7572. * @type string
  7573. */
  7574. public $default = 'ID';
  7575. /**
  7576. * Parses a file that contains a single string-hash.
  7577. * @param string $file
  7578. * @return array
  7579. */
  7580. public function parseFile($file)
  7581. {
  7582. if (!file_exists($file)) {
  7583. return false;
  7584. }
  7585. $fh = fopen($file, 'r');
  7586. if (!$fh) {
  7587. return false;
  7588. }
  7589. $ret = $this->parseHandle($fh);
  7590. fclose($fh);
  7591. return $ret;
  7592. }
  7593. /**
  7594. * Parses a file that contains multiple string-hashes delimited by '----'
  7595. * @param string $file
  7596. * @return array
  7597. */
  7598. public function parseMultiFile($file)
  7599. {
  7600. if (!file_exists($file)) {
  7601. return false;
  7602. }
  7603. $ret = array();
  7604. $fh = fopen($file, 'r');
  7605. if (!$fh) {
  7606. return false;
  7607. }
  7608. while (!feof($fh)) {
  7609. $ret[] = $this->parseHandle($fh);
  7610. }
  7611. fclose($fh);
  7612. return $ret;
  7613. }
  7614. /**
  7615. * Internal parser that acepts a file handle.
  7616. * @note While it's possible to simulate in-memory parsing by using
  7617. * custom stream wrappers, if such a use-case arises we should
  7618. * factor out the file handle into its own class.
  7619. * @param resource $fh File handle with pointer at start of valid string-hash
  7620. * block.
  7621. * @return array
  7622. */
  7623. protected function parseHandle($fh)
  7624. {
  7625. $state = false;
  7626. $single = false;
  7627. $ret = array();
  7628. do {
  7629. $line = fgets($fh);
  7630. if ($line === false) {
  7631. break;
  7632. }
  7633. $line = rtrim($line, "\n\r");
  7634. if (!$state && $line === '') {
  7635. continue;
  7636. }
  7637. if ($line === '----') {
  7638. break;
  7639. }
  7640. if (strncmp('--#', $line, 3) === 0) {
  7641. // Comment
  7642. continue;
  7643. } elseif (strncmp('--', $line, 2) === 0) {
  7644. // Multiline declaration
  7645. $state = trim($line, '- ');
  7646. if (!isset($ret[$state])) {
  7647. $ret[$state] = '';
  7648. }
  7649. continue;
  7650. } elseif (!$state) {
  7651. $single = true;
  7652. if (strpos($line, ':') !== false) {
  7653. // Single-line declaration
  7654. list($state, $line) = explode(':', $line, 2);
  7655. $line = trim($line);
  7656. } else {
  7657. // Use default declaration
  7658. $state = $this->default;
  7659. }
  7660. }
  7661. if ($single) {
  7662. $ret[$state] = $line;
  7663. $single = false;
  7664. $state = false;
  7665. } else {
  7666. $ret[$state] .= "$line\n";
  7667. }
  7668. } while (!feof($fh));
  7669. return $ret;
  7670. }
  7671. }
  7672. /**
  7673. * Defines a mutation of an obsolete tag into a valid tag.
  7674. */
  7675. abstract class HTMLPurifier_TagTransform
  7676. {
  7677. /**
  7678. * Tag name to transform the tag to.
  7679. * @type string
  7680. */
  7681. public $transform_to;
  7682. /**
  7683. * Transforms the obsolete tag into the valid tag.
  7684. * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
  7685. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
  7686. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  7687. */
  7688. abstract public function transform($tag, $config, $context);
  7689. /**
  7690. * Prepends CSS properties to the style attribute, creating the
  7691. * attribute if it doesn't exist.
  7692. * @warning Copied over from AttrTransform, be sure to keep in sync
  7693. * @param array $attr Attribute array to process (passed by reference)
  7694. * @param string $css CSS to prepend
  7695. */
  7696. protected function prependCSS(&$attr, $css)
  7697. {
  7698. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  7699. $attr['style'] = $css . $attr['style'];
  7700. }
  7701. }
  7702. /**
  7703. * Abstract base token class that all others inherit from.
  7704. */
  7705. abstract class HTMLPurifier_Token
  7706. {
  7707. /**
  7708. * Line number node was on in source document. Null if unknown.
  7709. * @type int
  7710. */
  7711. public $line;
  7712. /**
  7713. * Column of line node was on in source document. Null if unknown.
  7714. * @type int
  7715. */
  7716. public $col;
  7717. /**
  7718. * Lookup array of processing that this token is exempt from.
  7719. * Currently, valid values are "ValidateAttributes" and
  7720. * "MakeWellFormed_TagClosedError"
  7721. * @type array
  7722. */
  7723. public $armor = array();
  7724. /**
  7725. * Used during MakeWellFormed. See Note [Injector skips]
  7726. * @type
  7727. */
  7728. public $skip;
  7729. /**
  7730. * @type
  7731. */
  7732. public $rewind;
  7733. /**
  7734. * @type
  7735. */
  7736. public $carryover;
  7737. /**
  7738. * @param string $n
  7739. * @return null|string
  7740. */
  7741. public function __get($n)
  7742. {
  7743. if ($n === 'type') {
  7744. trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
  7745. switch (get_class($this)) {
  7746. case 'HTMLPurifier_Token_Start':
  7747. return 'start';
  7748. case 'HTMLPurifier_Token_Empty':
  7749. return 'empty';
  7750. case 'HTMLPurifier_Token_End':
  7751. return 'end';
  7752. case 'HTMLPurifier_Token_Text':
  7753. return 'text';
  7754. case 'HTMLPurifier_Token_Comment':
  7755. return 'comment';
  7756. default:
  7757. return null;
  7758. }
  7759. }
  7760. }
  7761. /**
  7762. * Sets the position of the token in the source document.
  7763. * @param int $l
  7764. * @param int $c
  7765. */
  7766. public function position($l = null, $c = null)
  7767. {
  7768. $this->line = $l;
  7769. $this->col = $c;
  7770. }
  7771. /**
  7772. * Convenience function for DirectLex settings line/col position.
  7773. * @param int $l
  7774. * @param int $c
  7775. */
  7776. public function rawPosition($l, $c)
  7777. {
  7778. if ($c === -1) {
  7779. $l++;
  7780. }
  7781. $this->line = $l;
  7782. $this->col = $c;
  7783. }
  7784. /**
  7785. * Converts a token into its corresponding node.
  7786. */
  7787. abstract public function toNode();
  7788. }
  7789. /**
  7790. * Factory for token generation.
  7791. *
  7792. * @note Doing some benchmarking indicates that the new operator is much
  7793. * slower than the clone operator (even discounting the cost of the
  7794. * constructor). This class is for that optimization.
  7795. * Other then that, there's not much point as we don't
  7796. * maintain parallel HTMLPurifier_Token hierarchies (the main reason why
  7797. * you'd want to use an abstract factory).
  7798. * @todo Port DirectLex to use this
  7799. */
  7800. class HTMLPurifier_TokenFactory
  7801. {
  7802. // p stands for prototype
  7803. /**
  7804. * @type HTMLPurifier_Token_Start
  7805. */
  7806. private $p_start;
  7807. /**
  7808. * @type HTMLPurifier_Token_End
  7809. */
  7810. private $p_end;
  7811. /**
  7812. * @type HTMLPurifier_Token_Empty
  7813. */
  7814. private $p_empty;
  7815. /**
  7816. * @type HTMLPurifier_Token_Text
  7817. */
  7818. private $p_text;
  7819. /**
  7820. * @type HTMLPurifier_Token_Comment
  7821. */
  7822. private $p_comment;
  7823. /**
  7824. * Generates blank prototypes for cloning.
  7825. */
  7826. public function __construct()
  7827. {
  7828. $this->p_start = new HTMLPurifier_Token_Start('', array());
  7829. $this->p_end = new HTMLPurifier_Token_End('');
  7830. $this->p_empty = new HTMLPurifier_Token_Empty('', array());
  7831. $this->p_text = new HTMLPurifier_Token_Text('');
  7832. $this->p_comment = new HTMLPurifier_Token_Comment('');
  7833. }
  7834. /**
  7835. * Creates a HTMLPurifier_Token_Start.
  7836. * @param string $name Tag name
  7837. * @param array $attr Associative array of attributes
  7838. * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
  7839. */
  7840. public function createStart($name, $attr = array())
  7841. {
  7842. $p = clone $this->p_start;
  7843. $p->__construct($name, $attr);
  7844. return $p;
  7845. }
  7846. /**
  7847. * Creates a HTMLPurifier_Token_End.
  7848. * @param string $name Tag name
  7849. * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
  7850. */
  7851. public function createEnd($name)
  7852. {
  7853. $p = clone $this->p_end;
  7854. $p->__construct($name);
  7855. return $p;
  7856. }
  7857. /**
  7858. * Creates a HTMLPurifier_Token_Empty.
  7859. * @param string $name Tag name
  7860. * @param array $attr Associative array of attributes
  7861. * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
  7862. */
  7863. public function createEmpty($name, $attr = array())
  7864. {
  7865. $p = clone $this->p_empty;
  7866. $p->__construct($name, $attr);
  7867. return $p;
  7868. }
  7869. /**
  7870. * Creates a HTMLPurifier_Token_Text.
  7871. * @param string $data Data of text token
  7872. * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
  7873. */
  7874. public function createText($data)
  7875. {
  7876. $p = clone $this->p_text;
  7877. $p->__construct($data);
  7878. return $p;
  7879. }
  7880. /**
  7881. * Creates a HTMLPurifier_Token_Comment.
  7882. * @param string $data Data of comment token
  7883. * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
  7884. */
  7885. public function createComment($data)
  7886. {
  7887. $p = clone $this->p_comment;
  7888. $p->__construct($data);
  7889. return $p;
  7890. }
  7891. }
  7892. /**
  7893. * HTML Purifier's internal representation of a URI.
  7894. * @note
  7895. * Internal data-structures are completely escaped. If the data needs
  7896. * to be used in a non-URI context (which is very unlikely), be sure
  7897. * to decode it first. The URI may not necessarily be well-formed until
  7898. * validate() is called.
  7899. */
  7900. class HTMLPurifier_URI
  7901. {
  7902. /**
  7903. * @type string
  7904. */
  7905. public $scheme;
  7906. /**
  7907. * @type string
  7908. */
  7909. public $userinfo;
  7910. /**
  7911. * @type string
  7912. */
  7913. public $host;
  7914. /**
  7915. * @type int
  7916. */
  7917. public $port;
  7918. /**
  7919. * @type string
  7920. */
  7921. public $path;
  7922. /**
  7923. * @type string
  7924. */
  7925. public $query;
  7926. /**
  7927. * @type string
  7928. */
  7929. public $fragment;
  7930. /**
  7931. * @param string $scheme
  7932. * @param string $userinfo
  7933. * @param string $host
  7934. * @param int $port
  7935. * @param string $path
  7936. * @param string $query
  7937. * @param string $fragment
  7938. * @note Automatically normalizes scheme and port
  7939. */
  7940. public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
  7941. {
  7942. $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
  7943. $this->userinfo = $userinfo;
  7944. $this->host = $host;
  7945. $this->port = is_null($port) ? $port : (int)$port;
  7946. $this->path = $path;
  7947. $this->query = $query;
  7948. $this->fragment = $fragment;
  7949. }
  7950. /**
  7951. * Retrieves a scheme object corresponding to the URI's scheme/default
  7952. * @param HTMLPurifier_Config $config
  7953. * @param HTMLPurifier_Context $context
  7954. * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
  7955. */
  7956. public function getSchemeObj($config, $context)
  7957. {
  7958. $registry = HTMLPurifier_URISchemeRegistry::instance();
  7959. if ($this->scheme !== null) {
  7960. $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
  7961. if (!$scheme_obj) {
  7962. return false;
  7963. } // invalid scheme, clean it out
  7964. } else {
  7965. // no scheme: retrieve the default one
  7966. $def = $config->getDefinition('URI');
  7967. $scheme_obj = $def->getDefaultScheme($config, $context);
  7968. if (!$scheme_obj) {
  7969. if ($def->defaultScheme !== null) {
  7970. // something funky happened to the default scheme object
  7971. trigger_error(
  7972. 'Default scheme object "' . $def->defaultScheme . '" was not readable',
  7973. E_USER_WARNING
  7974. );
  7975. } // suppress error if it's null
  7976. return false;
  7977. }
  7978. }
  7979. return $scheme_obj;
  7980. }
  7981. /**
  7982. * Generic validation method applicable for all schemes. May modify
  7983. * this URI in order to get it into a compliant form.
  7984. * @param HTMLPurifier_Config $config
  7985. * @param HTMLPurifier_Context $context
  7986. * @return bool True if validation/filtering succeeds, false if failure
  7987. */
  7988. public function validate($config, $context)
  7989. {
  7990. // ABNF definitions from RFC 3986
  7991. $chars_sub_delims = '!$&\'()*+,;=';
  7992. $chars_gen_delims = ':/?#[]@';
  7993. $chars_pchar = $chars_sub_delims . ':@';
  7994. // validate host
  7995. if (!is_null($this->host)) {
  7996. $host_def = new HTMLPurifier_AttrDef_URI_Host();
  7997. $this->host = $host_def->validate($this->host, $config, $context);
  7998. if ($this->host === false) {
  7999. $this->host = null;
  8000. }
  8001. }
  8002. // validate scheme
  8003. // NOTE: It's not appropriate to check whether or not this
  8004. // scheme is in our registry, since a URIFilter may convert a
  8005. // URI that we don't allow into one we do. So instead, we just
  8006. // check if the scheme can be dropped because there is no host
  8007. // and it is our default scheme.
  8008. if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
  8009. // support for relative paths is pretty abysmal when the
  8010. // scheme is present, so axe it when possible
  8011. $def = $config->getDefinition('URI');
  8012. if ($def->defaultScheme === $this->scheme) {
  8013. $this->scheme = null;
  8014. }
  8015. }
  8016. // validate username
  8017. if (!is_null($this->userinfo)) {
  8018. $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
  8019. $this->userinfo = $encoder->encode($this->userinfo);
  8020. }
  8021. // validate port
  8022. if (!is_null($this->port)) {
  8023. if ($this->port < 1 || $this->port > 65535) {
  8024. $this->port = null;
  8025. }
  8026. }
  8027. // validate path
  8028. $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
  8029. if (!is_null($this->host)) { // this catches $this->host === ''
  8030. // path-abempty (hier and relative)
  8031. // http://www.example.com/my/path
  8032. // //www.example.com/my/path (looks odd, but works, and
  8033. // recognized by most browsers)
  8034. // (this set is valid or invalid on a scheme by scheme
  8035. // basis, so we'll deal with it later)
  8036. // file:///my/path
  8037. // ///my/path
  8038. $this->path = $segments_encoder->encode($this->path);
  8039. } elseif ($this->path !== '') {
  8040. if ($this->path[0] === '/') {
  8041. // path-absolute (hier and relative)
  8042. // http:/my/path
  8043. // /my/path
  8044. if (strlen($this->path) >= 2 && $this->path[1] === '/') {
  8045. // This could happen if both the host gets stripped
  8046. // out
  8047. // http://my/path
  8048. // //my/path
  8049. $this->path = '';
  8050. } else {
  8051. $this->path = $segments_encoder->encode($this->path);
  8052. }
  8053. } elseif (!is_null($this->scheme)) {
  8054. // path-rootless (hier)
  8055. // http:my/path
  8056. // Short circuit evaluation means we don't need to check nz
  8057. $this->path = $segments_encoder->encode($this->path);
  8058. } else {
  8059. // path-noscheme (relative)
  8060. // my/path
  8061. // (once again, not checking nz)
  8062. $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
  8063. $c = strpos($this->path, '/');
  8064. if ($c !== false) {
  8065. $this->path =
  8066. $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
  8067. $segments_encoder->encode(substr($this->path, $c));
  8068. } else {
  8069. $this->path = $segment_nc_encoder->encode($this->path);
  8070. }
  8071. }
  8072. } else {
  8073. // path-empty (hier and relative)
  8074. $this->path = ''; // just to be safe
  8075. }
  8076. // qf = query and fragment
  8077. $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
  8078. if (!is_null($this->query)) {
  8079. $this->query = $qf_encoder->encode($this->query);
  8080. }
  8081. if (!is_null($this->fragment)) {
  8082. $this->fragment = $qf_encoder->encode($this->fragment);
  8083. }
  8084. return true;
  8085. }
  8086. /**
  8087. * Convert URI back to string
  8088. * @return string URI appropriate for output
  8089. */
  8090. public function toString()
  8091. {
  8092. // reconstruct authority
  8093. $authority = null;
  8094. // there is a rendering difference between a null authority
  8095. // (http:foo-bar) and an empty string authority
  8096. // (http:///foo-bar).
  8097. if (!is_null($this->host)) {
  8098. $authority = '';
  8099. if (!is_null($this->userinfo)) {
  8100. $authority .= $this->userinfo . '@';
  8101. }
  8102. $authority .= $this->host;
  8103. if (!is_null($this->port)) {
  8104. $authority .= ':' . $this->port;
  8105. }
  8106. }
  8107. // Reconstruct the result
  8108. // One might wonder about parsing quirks from browsers after
  8109. // this reconstruction. Unfortunately, parsing behavior depends
  8110. // on what *scheme* was employed (file:///foo is handled *very*
  8111. // differently than http:///foo), so unfortunately we have to
  8112. // defer to the schemes to do the right thing.
  8113. $result = '';
  8114. if (!is_null($this->scheme)) {
  8115. $result .= $this->scheme . ':';
  8116. }
  8117. if (!is_null($authority)) {
  8118. $result .= '//' . $authority;
  8119. }
  8120. $result .= $this->path;
  8121. if (!is_null($this->query)) {
  8122. $result .= '?' . $this->query;
  8123. }
  8124. if (!is_null($this->fragment)) {
  8125. $result .= '#' . $this->fragment;
  8126. }
  8127. return $result;
  8128. }
  8129. /**
  8130. * Returns true if this URL might be considered a 'local' URL given
  8131. * the current context. This is true when the host is null, or
  8132. * when it matches the host supplied to the configuration.
  8133. *
  8134. * Note that this does not do any scheme checking, so it is mostly
  8135. * only appropriate for metadata that doesn't care about protocol
  8136. * security. isBenign is probably what you actually want.
  8137. * @param HTMLPurifier_Config $config
  8138. * @param HTMLPurifier_Context $context
  8139. * @return bool
  8140. */
  8141. public function isLocal($config, $context)
  8142. {
  8143. if ($this->host === null) {
  8144. return true;
  8145. }
  8146. $uri_def = $config->getDefinition('URI');
  8147. if ($uri_def->host === $this->host) {
  8148. return true;
  8149. }
  8150. return false;
  8151. }
  8152. /**
  8153. * Returns true if this URL should be considered a 'benign' URL,
  8154. * that is:
  8155. *
  8156. * - It is a local URL (isLocal), and
  8157. * - It has a equal or better level of security
  8158. * @param HTMLPurifier_Config $config
  8159. * @param HTMLPurifier_Context $context
  8160. * @return bool
  8161. */
  8162. public function isBenign($config, $context)
  8163. {
  8164. if (!$this->isLocal($config, $context)) {
  8165. return false;
  8166. }
  8167. $scheme_obj = $this->getSchemeObj($config, $context);
  8168. if (!$scheme_obj) {
  8169. return false;
  8170. } // conservative approach
  8171. $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
  8172. if ($current_scheme_obj->secure) {
  8173. if (!$scheme_obj->secure) {
  8174. return false;
  8175. }
  8176. }
  8177. return true;
  8178. }
  8179. }
  8180. class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
  8181. {
  8182. public $type = 'URI';
  8183. protected $filters = array();
  8184. protected $postFilters = array();
  8185. protected $registeredFilters = array();
  8186. /**
  8187. * HTMLPurifier_URI object of the base specified at %URI.Base
  8188. */
  8189. public $base;
  8190. /**
  8191. * String host to consider "home" base, derived off of $base
  8192. */
  8193. public $host;
  8194. /**
  8195. * Name of default scheme based on %URI.DefaultScheme and %URI.Base
  8196. */
  8197. public $defaultScheme;
  8198. public function __construct()
  8199. {
  8200. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
  8201. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
  8202. $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
  8203. $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
  8204. $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
  8205. $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
  8206. $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
  8207. }
  8208. public function registerFilter($filter)
  8209. {
  8210. $this->registeredFilters[$filter->name] = $filter;
  8211. }
  8212. public function addFilter($filter, $config)
  8213. {
  8214. $r = $filter->prepare($config);
  8215. if ($r === false) return; // null is ok, for backwards compat
  8216. if ($filter->post) {
  8217. $this->postFilters[$filter->name] = $filter;
  8218. } else {
  8219. $this->filters[$filter->name] = $filter;
  8220. }
  8221. }
  8222. protected function doSetup($config)
  8223. {
  8224. $this->setupMemberVariables($config);
  8225. $this->setupFilters($config);
  8226. }
  8227. protected function setupFilters($config)
  8228. {
  8229. foreach ($this->registeredFilters as $name => $filter) {
  8230. if ($filter->always_load) {
  8231. $this->addFilter($filter, $config);
  8232. } else {
  8233. $conf = $config->get('URI.' . $name);
  8234. if ($conf !== false && $conf !== null) {
  8235. $this->addFilter($filter, $config);
  8236. }
  8237. }
  8238. }
  8239. unset($this->registeredFilters);
  8240. }
  8241. protected function setupMemberVariables($config)
  8242. {
  8243. $this->host = $config->get('URI.Host');
  8244. $base_uri = $config->get('URI.Base');
  8245. if (!is_null($base_uri)) {
  8246. $parser = new HTMLPurifier_URIParser();
  8247. $this->base = $parser->parse($base_uri);
  8248. $this->defaultScheme = $this->base->scheme;
  8249. if (is_null($this->host)) $this->host = $this->base->host;
  8250. }
  8251. if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
  8252. }
  8253. public function getDefaultScheme($config, $context)
  8254. {
  8255. return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
  8256. }
  8257. public function filter(&$uri, $config, $context)
  8258. {
  8259. foreach ($this->filters as $name => $f) {
  8260. $result = $f->filter($uri, $config, $context);
  8261. if (!$result) return false;
  8262. }
  8263. return true;
  8264. }
  8265. public function postFilter(&$uri, $config, $context)
  8266. {
  8267. foreach ($this->postFilters as $name => $f) {
  8268. $result = $f->filter($uri, $config, $context);
  8269. if (!$result) return false;
  8270. }
  8271. return true;
  8272. }
  8273. }
  8274. /**
  8275. * Chainable filters for custom URI processing.
  8276. *
  8277. * These filters can perform custom actions on a URI filter object,
  8278. * including transformation or blacklisting. A filter named Foo
  8279. * must have a corresponding configuration directive %URI.Foo,
  8280. * unless always_load is specified to be true.
  8281. *
  8282. * The following contexts may be available while URIFilters are being
  8283. * processed:
  8284. *
  8285. * - EmbeddedURI: true if URI is an embedded resource that will
  8286. * be loaded automatically on page load
  8287. * - CurrentToken: a reference to the token that is currently
  8288. * being processed
  8289. * - CurrentAttr: the name of the attribute that is currently being
  8290. * processed
  8291. * - CurrentCSSProperty: the name of the CSS property that is
  8292. * currently being processed (if applicable)
  8293. *
  8294. * @warning This filter is called before scheme object validation occurs.
  8295. * Make sure, if you require a specific scheme object, you
  8296. * you check that it exists. This allows filters to convert
  8297. * proprietary URI schemes into regular ones.
  8298. */
  8299. abstract class HTMLPurifier_URIFilter
  8300. {
  8301. /**
  8302. * Unique identifier of filter.
  8303. * @type string
  8304. */
  8305. public $name;
  8306. /**
  8307. * True if this filter should be run after scheme validation.
  8308. * @type bool
  8309. */
  8310. public $post = false;
  8311. /**
  8312. * True if this filter should always be loaded.
  8313. * This permits a filter to be named Foo without the corresponding
  8314. * %URI.Foo directive existing.
  8315. * @type bool
  8316. */
  8317. public $always_load = false;
  8318. /**
  8319. * Performs initialization for the filter. If the filter returns
  8320. * false, this means that it shouldn't be considered active.
  8321. * @param HTMLPurifier_Config $config
  8322. * @return bool
  8323. */
  8324. public function prepare($config)
  8325. {
  8326. return true;
  8327. }
  8328. /**
  8329. * Filter a URI object
  8330. * @param HTMLPurifier_URI $uri Reference to URI object variable
  8331. * @param HTMLPurifier_Config $config
  8332. * @param HTMLPurifier_Context $context
  8333. * @return bool Whether or not to continue processing: false indicates
  8334. * URL is no good, true indicates continue processing. Note that
  8335. * all changes are committed directly on the URI object
  8336. */
  8337. abstract public function filter(&$uri, $config, $context);
  8338. }
  8339. /**
  8340. * Parses a URI into the components and fragment identifier as specified
  8341. * by RFC 3986.
  8342. */
  8343. class HTMLPurifier_URIParser
  8344. {
  8345. /**
  8346. * Instance of HTMLPurifier_PercentEncoder to do normalization with.
  8347. */
  8348. protected $percentEncoder;
  8349. public function __construct()
  8350. {
  8351. $this->percentEncoder = new HTMLPurifier_PercentEncoder();
  8352. }
  8353. /**
  8354. * Parses a URI.
  8355. * @param $uri string URI to parse
  8356. * @return HTMLPurifier_URI representation of URI. This representation has
  8357. * not been validated yet and may not conform to RFC.
  8358. */
  8359. public function parse($uri)
  8360. {
  8361. $uri = $this->percentEncoder->normalize($uri);
  8362. // Regexp is as per Appendix B.
  8363. // Note that ["<>] are an addition to the RFC's recommended
  8364. // characters, because they represent external delimeters.
  8365. $r_URI = '!'.
  8366. '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
  8367. '(//([^/?#"<>]*))?'. // 4. Authority
  8368. '([^?#"<>]*)'. // 5. Path
  8369. '(\?([^#"<>]*))?'. // 7. Query
  8370. '(#([^"<>]*))?'. // 8. Fragment
  8371. '!';
  8372. $matches = array();
  8373. $result = preg_match($r_URI, $uri, $matches);
  8374. if (!$result) return false; // *really* invalid URI
  8375. // seperate out parts
  8376. $scheme = !empty($matches[1]) ? $matches[2] : null;
  8377. $authority = !empty($matches[3]) ? $matches[4] : null;
  8378. $path = $matches[5]; // always present, can be empty
  8379. $query = !empty($matches[6]) ? $matches[7] : null;
  8380. $fragment = !empty($matches[8]) ? $matches[9] : null;
  8381. // further parse authority
  8382. if ($authority !== null) {
  8383. $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
  8384. $matches = array();
  8385. preg_match($r_authority, $authority, $matches);
  8386. $userinfo = !empty($matches[1]) ? $matches[2] : null;
  8387. $host = !empty($matches[3]) ? $matches[3] : '';
  8388. $port = !empty($matches[4]) ? (int) $matches[5] : null;
  8389. } else {
  8390. $port = $host = $userinfo = null;
  8391. }
  8392. return new HTMLPurifier_URI(
  8393. $scheme, $userinfo, $host, $port, $path, $query, $fragment);
  8394. }
  8395. }
  8396. /**
  8397. * Validator for the components of a URI for a specific scheme
  8398. */
  8399. abstract class HTMLPurifier_URIScheme
  8400. {
  8401. /**
  8402. * Scheme's default port (integer). If an explicit port number is
  8403. * specified that coincides with the default port, it will be
  8404. * elided.
  8405. * @type int
  8406. */
  8407. public $default_port = null;
  8408. /**
  8409. * Whether or not URIs of this scheme are locatable by a browser
  8410. * http and ftp are accessible, while mailto and news are not.
  8411. * @type bool
  8412. */
  8413. public $browsable = false;
  8414. /**
  8415. * Whether or not data transmitted over this scheme is encrypted.
  8416. * https is secure, http is not.
  8417. * @type bool
  8418. */
  8419. public $secure = false;
  8420. /**
  8421. * Whether or not the URI always uses <hier_part>, resolves edge cases
  8422. * with making relative URIs absolute
  8423. * @type bool
  8424. */
  8425. public $hierarchical = false;
  8426. /**
  8427. * Whether or not the URI may omit a hostname when the scheme is
  8428. * explicitly specified, ala file:///path/to/file. As of writing,
  8429. * 'file' is the only scheme that browsers support his properly.
  8430. * @type bool
  8431. */
  8432. public $may_omit_host = false;
  8433. /**
  8434. * Validates the components of a URI for a specific scheme.
  8435. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8436. * @param HTMLPurifier_Config $config
  8437. * @param HTMLPurifier_Context $context
  8438. * @return bool success or failure
  8439. */
  8440. abstract public function doValidate(&$uri, $config, $context);
  8441. /**
  8442. * Public interface for validating components of a URI. Performs a
  8443. * bunch of default actions. Don't overload this method.
  8444. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8445. * @param HTMLPurifier_Config $config
  8446. * @param HTMLPurifier_Context $context
  8447. * @return bool success or failure
  8448. */
  8449. public function validate(&$uri, $config, $context)
  8450. {
  8451. if ($this->default_port == $uri->port) {
  8452. $uri->port = null;
  8453. }
  8454. // kludge: browsers do funny things when the scheme but not the
  8455. // authority is set
  8456. if (!$this->may_omit_host &&
  8457. // if the scheme is present, a missing host is always in error
  8458. (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
  8459. // if the scheme is not present, a *blank* host is in error,
  8460. // since this translates into '///path' which most browsers
  8461. // interpret as being 'http://path'.
  8462. (is_null($uri->scheme) && $uri->host === '')
  8463. ) {
  8464. do {
  8465. if (is_null($uri->scheme)) {
  8466. if (substr($uri->path, 0, 2) != '//') {
  8467. $uri->host = null;
  8468. break;
  8469. }
  8470. // URI is '////path', so we cannot nullify the
  8471. // host to preserve semantics. Try expanding the
  8472. // hostname instead (fall through)
  8473. }
  8474. // first see if we can manually insert a hostname
  8475. $host = $config->get('URI.Host');
  8476. if (!is_null($host)) {
  8477. $uri->host = $host;
  8478. } else {
  8479. // we can't do anything sensible, reject the URL.
  8480. return false;
  8481. }
  8482. } while (false);
  8483. }
  8484. return $this->doValidate($uri, $config, $context);
  8485. }
  8486. }
  8487. /**
  8488. * Registry for retrieving specific URI scheme validator objects.
  8489. */
  8490. class HTMLPurifier_URISchemeRegistry
  8491. {
  8492. /**
  8493. * Retrieve sole instance of the registry.
  8494. * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
  8495. * or bool true to reset to default registry.
  8496. * @return HTMLPurifier_URISchemeRegistry
  8497. * @note Pass a registry object $prototype with a compatible interface and
  8498. * the function will copy it and return it all further times.
  8499. */
  8500. public static function instance($prototype = null)
  8501. {
  8502. static $instance = null;
  8503. if ($prototype !== null) {
  8504. $instance = $prototype;
  8505. } elseif ($instance === null || $prototype == true) {
  8506. $instance = new HTMLPurifier_URISchemeRegistry();
  8507. }
  8508. return $instance;
  8509. }
  8510. /**
  8511. * Cache of retrieved schemes.
  8512. * @type HTMLPurifier_URIScheme[]
  8513. */
  8514. protected $schemes = array();
  8515. /**
  8516. * Retrieves a scheme validator object
  8517. * @param string $scheme String scheme name like http or mailto
  8518. * @param HTMLPurifier_Config $config
  8519. * @param HTMLPurifier_Context $context
  8520. * @return HTMLPurifier_URIScheme
  8521. */
  8522. public function getScheme($scheme, $config, $context)
  8523. {
  8524. if (!$config) {
  8525. $config = HTMLPurifier_Config::createDefault();
  8526. }
  8527. // important, otherwise attacker could include arbitrary file
  8528. $allowed_schemes = $config->get('URI.AllowedSchemes');
  8529. if (!$config->get('URI.OverrideAllowedSchemes') &&
  8530. !isset($allowed_schemes[$scheme])
  8531. ) {
  8532. return;
  8533. }
  8534. if (isset($this->schemes[$scheme])) {
  8535. return $this->schemes[$scheme];
  8536. }
  8537. if (!isset($allowed_schemes[$scheme])) {
  8538. return;
  8539. }
  8540. $class = 'HTMLPurifier_URIScheme_' . $scheme;
  8541. if (!class_exists($class)) {
  8542. return;
  8543. }
  8544. $this->schemes[$scheme] = new $class();
  8545. return $this->schemes[$scheme];
  8546. }
  8547. /**
  8548. * Registers a custom scheme to the cache, bypassing reflection.
  8549. * @param string $scheme Scheme name
  8550. * @param HTMLPurifier_URIScheme $scheme_obj
  8551. */
  8552. public function register($scheme, $scheme_obj)
  8553. {
  8554. $this->schemes[$scheme] = $scheme_obj;
  8555. }
  8556. }
  8557. /**
  8558. * Class for converting between different unit-lengths as specified by
  8559. * CSS.
  8560. */
  8561. class HTMLPurifier_UnitConverter
  8562. {
  8563. const ENGLISH = 1;
  8564. const METRIC = 2;
  8565. const DIGITAL = 3;
  8566. /**
  8567. * Units information array. Units are grouped into measuring systems
  8568. * (English, Metric), and are assigned an integer representing
  8569. * the conversion factor between that unit and the smallest unit in
  8570. * the system. Numeric indexes are actually magical constants that
  8571. * encode conversion data from one system to the next, with a O(n^2)
  8572. * constraint on memory (this is generally not a problem, since
  8573. * the number of measuring systems is small.)
  8574. */
  8575. protected static $units = array(
  8576. self::ENGLISH => array(
  8577. 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
  8578. 'pt' => 4,
  8579. 'pc' => 48,
  8580. 'in' => 288,
  8581. self::METRIC => array('pt', '0.352777778', 'mm'),
  8582. ),
  8583. self::METRIC => array(
  8584. 'mm' => 1,
  8585. 'cm' => 10,
  8586. self::ENGLISH => array('mm', '2.83464567', 'pt'),
  8587. ),
  8588. );
  8589. /**
  8590. * Minimum bcmath precision for output.
  8591. * @type int
  8592. */
  8593. protected $outputPrecision;
  8594. /**
  8595. * Bcmath precision for internal calculations.
  8596. * @type int
  8597. */
  8598. protected $internalPrecision;
  8599. /**
  8600. * Whether or not BCMath is available.
  8601. * @type bool
  8602. */
  8603. private $bcmath;
  8604. public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
  8605. {
  8606. $this->outputPrecision = $output_precision;
  8607. $this->internalPrecision = $internal_precision;
  8608. $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
  8609. }
  8610. /**
  8611. * Converts a length object of one unit into another unit.
  8612. * @param HTMLPurifier_Length $length
  8613. * Instance of HTMLPurifier_Length to convert. You must validate()
  8614. * it before passing it here!
  8615. * @param string $to_unit
  8616. * Unit to convert to.
  8617. * @return HTMLPurifier_Length|bool
  8618. * @note
  8619. * About precision: This conversion function pays very special
  8620. * attention to the incoming precision of values and attempts
  8621. * to maintain a number of significant figure. Results are
  8622. * fairly accurate up to nine digits. Some caveats:
  8623. * - If a number is zero-padded as a result of this significant
  8624. * figure tracking, the zeroes will be eliminated.
  8625. * - If a number contains less than four sigfigs ($outputPrecision)
  8626. * and this causes some decimals to be excluded, those
  8627. * decimals will be added on.
  8628. */
  8629. public function convert($length, $to_unit)
  8630. {
  8631. if (!$length->isValid()) {
  8632. return false;
  8633. }
  8634. $n = $length->getN();
  8635. $unit = $length->getUnit();
  8636. if ($n === '0' || $unit === false) {
  8637. return new HTMLPurifier_Length('0', false);
  8638. }
  8639. $state = $dest_state = false;
  8640. foreach (self::$units as $k => $x) {
  8641. if (isset($x[$unit])) {
  8642. $state = $k;
  8643. }
  8644. if (isset($x[$to_unit])) {
  8645. $dest_state = $k;
  8646. }
  8647. }
  8648. if (!$state || !$dest_state) {
  8649. return false;
  8650. }
  8651. // Some calculations about the initial precision of the number;
  8652. // this will be useful when we need to do final rounding.
  8653. $sigfigs = $this->getSigFigs($n);
  8654. if ($sigfigs < $this->outputPrecision) {
  8655. $sigfigs = $this->outputPrecision;
  8656. }
  8657. // BCMath's internal precision deals only with decimals. Use
  8658. // our default if the initial number has no decimals, or increase
  8659. // it by how ever many decimals, thus, the number of guard digits
  8660. // will always be greater than or equal to internalPrecision.
  8661. $log = (int)floor(log(abs($n), 10));
  8662. $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
  8663. for ($i = 0; $i < 2; $i++) {
  8664. // Determine what unit IN THIS SYSTEM we need to convert to
  8665. if ($dest_state === $state) {
  8666. // Simple conversion
  8667. $dest_unit = $to_unit;
  8668. } else {
  8669. // Convert to the smallest unit, pending a system shift
  8670. $dest_unit = self::$units[$state][$dest_state][0];
  8671. }
  8672. // Do the conversion if necessary
  8673. if ($dest_unit !== $unit) {
  8674. $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
  8675. $n = $this->mul($n, $factor, $cp);
  8676. $unit = $dest_unit;
  8677. }
  8678. // Output was zero, so bail out early. Shouldn't ever happen.
  8679. if ($n === '') {
  8680. $n = '0';
  8681. $unit = $to_unit;
  8682. break;
  8683. }
  8684. // It was a simple conversion, so bail out
  8685. if ($dest_state === $state) {
  8686. break;
  8687. }
  8688. if ($i !== 0) {
  8689. // Conversion failed! Apparently, the system we forwarded
  8690. // to didn't have this unit. This should never happen!
  8691. return false;
  8692. }
  8693. // Pre-condition: $i == 0
  8694. // Perform conversion to next system of units
  8695. $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
  8696. $unit = self::$units[$state][$dest_state][2];
  8697. $state = $dest_state;
  8698. // One more loop around to convert the unit in the new system.
  8699. }
  8700. // Post-condition: $unit == $to_unit
  8701. if ($unit !== $to_unit) {
  8702. return false;
  8703. }
  8704. // Useful for debugging:
  8705. //echo "<pre>n";
  8706. //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
  8707. $n = $this->round($n, $sigfigs);
  8708. if (strpos($n, '.') !== false) {
  8709. $n = rtrim($n, '0');
  8710. }
  8711. $n = rtrim($n, '.');
  8712. return new HTMLPurifier_Length($n, $unit);
  8713. }
  8714. /**
  8715. * Returns the number of significant figures in a string number.
  8716. * @param string $n Decimal number
  8717. * @return int number of sigfigs
  8718. */
  8719. public function getSigFigs($n)
  8720. {
  8721. $n = ltrim($n, '0+-');
  8722. $dp = strpos($n, '.'); // decimal position
  8723. if ($dp === false) {
  8724. $sigfigs = strlen(rtrim($n, '0'));
  8725. } else {
  8726. $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
  8727. if ($dp !== 0) {
  8728. $sigfigs--;
  8729. }
  8730. }
  8731. return $sigfigs;
  8732. }
  8733. /**
  8734. * Adds two numbers, using arbitrary precision when available.
  8735. * @param string $s1
  8736. * @param string $s2
  8737. * @param int $scale
  8738. * @return string
  8739. */
  8740. private function add($s1, $s2, $scale)
  8741. {
  8742. if ($this->bcmath) {
  8743. return bcadd($s1, $s2, $scale);
  8744. } else {
  8745. return $this->scale((float)$s1 + (float)$s2, $scale);
  8746. }
  8747. }
  8748. /**
  8749. * Multiples two numbers, using arbitrary precision when available.
  8750. * @param string $s1
  8751. * @param string $s2
  8752. * @param int $scale
  8753. * @return string
  8754. */
  8755. private function mul($s1, $s2, $scale)
  8756. {
  8757. if ($this->bcmath) {
  8758. return bcmul($s1, $s2, $scale);
  8759. } else {
  8760. return $this->scale((float)$s1 * (float)$s2, $scale);
  8761. }
  8762. }
  8763. /**
  8764. * Divides two numbers, using arbitrary precision when available.
  8765. * @param string $s1
  8766. * @param string $s2
  8767. * @param int $scale
  8768. * @return string
  8769. */
  8770. private function div($s1, $s2, $scale)
  8771. {
  8772. if ($this->bcmath) {
  8773. return bcdiv($s1, $s2, $scale);
  8774. } else {
  8775. return $this->scale((float)$s1 / (float)$s2, $scale);
  8776. }
  8777. }
  8778. /**
  8779. * Rounds a number according to the number of sigfigs it should have,
  8780. * using arbitrary precision when available.
  8781. * @param float $n
  8782. * @param int $sigfigs
  8783. * @return string
  8784. */
  8785. private function round($n, $sigfigs)
  8786. {
  8787. $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
  8788. $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
  8789. $neg = $n < 0 ? '-' : ''; // Negative sign
  8790. if ($this->bcmath) {
  8791. if ($rp >= 0) {
  8792. $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
  8793. $n = bcdiv($n, '1', $rp);
  8794. } else {
  8795. // This algorithm partially depends on the standardized
  8796. // form of numbers that comes out of bcmath.
  8797. $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
  8798. $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
  8799. }
  8800. return $n;
  8801. } else {
  8802. return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
  8803. }
  8804. }
  8805. /**
  8806. * Scales a float to $scale digits right of decimal point, like BCMath.
  8807. * @param float $r
  8808. * @param int $scale
  8809. * @return string
  8810. */
  8811. private function scale($r, $scale)
  8812. {
  8813. if ($scale < 0) {
  8814. // The f sprintf type doesn't support negative numbers, so we
  8815. // need to cludge things manually. First get the string.
  8816. $r = sprintf('%.0f', (float)$r);
  8817. // Due to floating point precision loss, $r will more than likely
  8818. // look something like 4652999999999.9234. We grab one more digit
  8819. // than we need to precise from $r and then use that to round
  8820. // appropriately.
  8821. $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
  8822. // Now we return it, truncating the zero that was rounded off.
  8823. return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
  8824. }
  8825. return sprintf('%.' . $scale . 'f', (float)$r);
  8826. }
  8827. }
  8828. /**
  8829. * Parses string representations into their corresponding native PHP
  8830. * variable type. The base implementation does a simple type-check.
  8831. */
  8832. class HTMLPurifier_VarParser
  8833. {
  8834. const STRING = 1;
  8835. const ISTRING = 2;
  8836. const TEXT = 3;
  8837. const ITEXT = 4;
  8838. const INT = 5;
  8839. const FLOAT = 6;
  8840. const BOOL = 7;
  8841. const LOOKUP = 8;
  8842. const ALIST = 9;
  8843. const HASH = 10;
  8844. const MIXED = 11;
  8845. /**
  8846. * Lookup table of allowed types. Mainly for backwards compatibility, but
  8847. * also convenient for transforming string type names to the integer constants.
  8848. */
  8849. public static $types = array(
  8850. 'string' => self::STRING,
  8851. 'istring' => self::ISTRING,
  8852. 'text' => self::TEXT,
  8853. 'itext' => self::ITEXT,
  8854. 'int' => self::INT,
  8855. 'float' => self::FLOAT,
  8856. 'bool' => self::BOOL,
  8857. 'lookup' => self::LOOKUP,
  8858. 'list' => self::ALIST,
  8859. 'hash' => self::HASH,
  8860. 'mixed' => self::MIXED
  8861. );
  8862. /**
  8863. * Lookup table of types that are string, and can have aliases or
  8864. * allowed value lists.
  8865. */
  8866. public static $stringTypes = array(
  8867. self::STRING => true,
  8868. self::ISTRING => true,
  8869. self::TEXT => true,
  8870. self::ITEXT => true,
  8871. );
  8872. /**
  8873. * Validate a variable according to type.
  8874. * It may return NULL as a valid type if $allow_null is true.
  8875. *
  8876. * @param mixed $var Variable to validate
  8877. * @param int $type Type of variable, see HTMLPurifier_VarParser->types
  8878. * @param bool $allow_null Whether or not to permit null as a value
  8879. * @return string Validated and type-coerced variable
  8880. * @throws HTMLPurifier_VarParserException
  8881. */
  8882. final public function parse($var, $type, $allow_null = false)
  8883. {
  8884. if (is_string($type)) {
  8885. if (!isset(HTMLPurifier_VarParser::$types[$type])) {
  8886. throw new HTMLPurifier_VarParserException("Invalid type '$type'");
  8887. } else {
  8888. $type = HTMLPurifier_VarParser::$types[$type];
  8889. }
  8890. }
  8891. $var = $this->parseImplementation($var, $type, $allow_null);
  8892. if ($allow_null && $var === null) {
  8893. return null;
  8894. }
  8895. // These are basic checks, to make sure nothing horribly wrong
  8896. // happened in our implementations.
  8897. switch ($type) {
  8898. case (self::STRING):
  8899. case (self::ISTRING):
  8900. case (self::TEXT):
  8901. case (self::ITEXT):
  8902. if (!is_string($var)) {
  8903. break;
  8904. }
  8905. if ($type == self::ISTRING || $type == self::ITEXT) {
  8906. $var = strtolower($var);
  8907. }
  8908. return $var;
  8909. case (self::INT):
  8910. if (!is_int($var)) {
  8911. break;
  8912. }
  8913. return $var;
  8914. case (self::FLOAT):
  8915. if (!is_float($var)) {
  8916. break;
  8917. }
  8918. return $var;
  8919. case (self::BOOL):
  8920. if (!is_bool($var)) {
  8921. break;
  8922. }
  8923. return $var;
  8924. case (self::LOOKUP):
  8925. case (self::ALIST):
  8926. case (self::HASH):
  8927. if (!is_array($var)) {
  8928. break;
  8929. }
  8930. if ($type === self::LOOKUP) {
  8931. foreach ($var as $k) {
  8932. if ($k !== true) {
  8933. $this->error('Lookup table contains value other than true');
  8934. }
  8935. }
  8936. } elseif ($type === self::ALIST) {
  8937. $keys = array_keys($var);
  8938. if (array_keys($keys) !== $keys) {
  8939. $this->error('Indices for list are not uniform');
  8940. }
  8941. }
  8942. return $var;
  8943. case (self::MIXED):
  8944. return $var;
  8945. default:
  8946. $this->errorInconsistent(get_class($this), $type);
  8947. }
  8948. $this->errorGeneric($var, $type);
  8949. }
  8950. /**
  8951. * Actually implements the parsing. Base implementation does not
  8952. * do anything to $var. Subclasses should overload this!
  8953. * @param mixed $var
  8954. * @param int $type
  8955. * @param bool $allow_null
  8956. * @return string
  8957. */
  8958. protected function parseImplementation($var, $type, $allow_null)
  8959. {
  8960. return $var;
  8961. }
  8962. /**
  8963. * Throws an exception.
  8964. * @throws HTMLPurifier_VarParserException
  8965. */
  8966. protected function error($msg)
  8967. {
  8968. throw new HTMLPurifier_VarParserException($msg);
  8969. }
  8970. /**
  8971. * Throws an inconsistency exception.
  8972. * @note This should not ever be called. It would be called if we
  8973. * extend the allowed values of HTMLPurifier_VarParser without
  8974. * updating subclasses.
  8975. * @param string $class
  8976. * @param int $type
  8977. * @throws HTMLPurifier_Exception
  8978. */
  8979. protected function errorInconsistent($class, $type)
  8980. {
  8981. throw new HTMLPurifier_Exception(
  8982. "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
  8983. " not implemented"
  8984. );
  8985. }
  8986. /**
  8987. * Generic error for if a type didn't work.
  8988. * @param mixed $var
  8989. * @param int $type
  8990. */
  8991. protected function errorGeneric($var, $type)
  8992. {
  8993. $vtype = gettype($var);
  8994. $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
  8995. }
  8996. /**
  8997. * @param int $type
  8998. * @return string
  8999. */
  9000. public static function getTypeName($type)
  9001. {
  9002. static $lookup;
  9003. if (!$lookup) {
  9004. // Lazy load the alternative lookup table
  9005. $lookup = array_flip(HTMLPurifier_VarParser::$types);
  9006. }
  9007. if (!isset($lookup[$type])) {
  9008. return 'unknown';
  9009. }
  9010. return $lookup[$type];
  9011. }
  9012. }
  9013. /**
  9014. * Exception type for HTMLPurifier_VarParser
  9015. */
  9016. class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
  9017. {
  9018. }
  9019. /**
  9020. * A zipper is a purely-functional data structure which contains
  9021. * a focus that can be efficiently manipulated. It is known as
  9022. * a "one-hole context". This mutable variant implements a zipper
  9023. * for a list as a pair of two arrays, laid out as follows:
  9024. *
  9025. * Base list: 1 2 3 4 [ ] 6 7 8 9
  9026. * Front list: 1 2 3 4
  9027. * Back list: 9 8 7 6
  9028. *
  9029. * User is expected to keep track of the "current element" and properly
  9030. * fill it back in as necessary. (ToDo: Maybe it's more user friendly
  9031. * to implicitly track the current element?)
  9032. *
  9033. * Nota bene: the current class gets confused if you try to store NULLs
  9034. * in the list.
  9035. */
  9036. class HTMLPurifier_Zipper
  9037. {
  9038. public $front, $back;
  9039. public function __construct($front, $back) {
  9040. $this->front = $front;
  9041. $this->back = $back;
  9042. }
  9043. /**
  9044. * Creates a zipper from an array, with a hole in the
  9045. * 0-index position.
  9046. * @param Array to zipper-ify.
  9047. * @return Tuple of zipper and element of first position.
  9048. */
  9049. static public function fromArray($array) {
  9050. $z = new self(array(), array_reverse($array));
  9051. $t = $z->delete(); // delete the "dummy hole"
  9052. return array($z, $t);
  9053. }
  9054. /**
  9055. * Convert zipper back into a normal array, optionally filling in
  9056. * the hole with a value. (Usually you should supply a $t, unless you
  9057. * are at the end of the array.)
  9058. */
  9059. public function toArray($t = NULL) {
  9060. $a = $this->front;
  9061. if ($t !== NULL) $a[] = $t;
  9062. for ($i = count($this->back)-1; $i >= 0; $i--) {
  9063. $a[] = $this->back[$i];
  9064. }
  9065. return $a;
  9066. }
  9067. /**
  9068. * Move hole to the next element.
  9069. * @param $t Element to fill hole with
  9070. * @return Original contents of new hole.
  9071. */
  9072. public function next($t) {
  9073. if ($t !== NULL) array_push($this->front, $t);
  9074. return empty($this->back) ? NULL : array_pop($this->back);
  9075. }
  9076. /**
  9077. * Iterated hole advancement.
  9078. * @param $t Element to fill hole with
  9079. * @param $i How many forward to advance hole
  9080. * @return Original contents of new hole, i away
  9081. */
  9082. public function advance($t, $n) {
  9083. for ($i = 0; $i < $n; $i++) {
  9084. $t = $this->next($t);
  9085. }
  9086. return $t;
  9087. }
  9088. /**
  9089. * Move hole to the previous element
  9090. * @param $t Element to fill hole with
  9091. * @return Original contents of new hole.
  9092. */
  9093. public function prev($t) {
  9094. if ($t !== NULL) array_push($this->back, $t);
  9095. return empty($this->front) ? NULL : array_pop($this->front);
  9096. }
  9097. /**
  9098. * Delete contents of current hole, shifting hole to
  9099. * next element.
  9100. * @return Original contents of new hole.
  9101. */
  9102. public function delete() {
  9103. return empty($this->back) ? NULL : array_pop($this->back);
  9104. }
  9105. /**
  9106. * Returns true if we are at the end of the list.
  9107. * @return bool
  9108. */
  9109. public function done() {
  9110. return empty($this->back);
  9111. }
  9112. /**
  9113. * Insert element before hole.
  9114. * @param Element to insert
  9115. */
  9116. public function insertBefore($t) {
  9117. if ($t !== NULL) array_push($this->front, $t);
  9118. }
  9119. /**
  9120. * Insert element after hole.
  9121. * @param Element to insert
  9122. */
  9123. public function insertAfter($t) {
  9124. if ($t !== NULL) array_push($this->back, $t);
  9125. }
  9126. /**
  9127. * Splice in multiple elements at hole. Functional specification
  9128. * in terms of array_splice:
  9129. *
  9130. * $arr1 = $arr;
  9131. * $old1 = array_splice($arr1, $i, $delete, $replacement);
  9132. *
  9133. * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
  9134. * $t = $z->advance($t, $i);
  9135. * list($old2, $t) = $z->splice($t, $delete, $replacement);
  9136. * $arr2 = $z->toArray($t);
  9137. *
  9138. * assert($old1 === $old2);
  9139. * assert($arr1 === $arr2);
  9140. *
  9141. * NB: the absolute index location after this operation is
  9142. * *unchanged!*
  9143. *
  9144. * @param Current contents of hole.
  9145. */
  9146. public function splice($t, $delete, $replacement) {
  9147. // delete
  9148. $old = array();
  9149. $r = $t;
  9150. for ($i = $delete; $i > 0; $i--) {
  9151. $old[] = $r;
  9152. $r = $this->delete();
  9153. }
  9154. // insert
  9155. for ($i = count($replacement)-1; $i >= 0; $i--) {
  9156. $this->insertAfter($r);
  9157. $r = $replacement[$i];
  9158. }
  9159. return array($old, $r);
  9160. }
  9161. }
  9162. /**
  9163. * Validates the HTML attribute style, otherwise known as CSS.
  9164. * @note We don't implement the whole CSS specification, so it might be
  9165. * difficult to reuse this component in the context of validating
  9166. * actual stylesheet declarations.
  9167. * @note If we were really serious about validating the CSS, we would
  9168. * tokenize the styles and then parse the tokens. Obviously, we
  9169. * are not doing that. Doing that could seriously harm performance,
  9170. * but would make these components a lot more viable for a CSS
  9171. * filtering solution.
  9172. */
  9173. class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
  9174. {
  9175. /**
  9176. * @param string $css
  9177. * @param HTMLPurifier_Config $config
  9178. * @param HTMLPurifier_Context $context
  9179. * @return bool|string
  9180. */
  9181. public function validate($css, $config, $context)
  9182. {
  9183. $css = $this->parseCDATA($css);
  9184. $definition = $config->getCSSDefinition();
  9185. $allow_duplicates = $config->get("CSS.AllowDuplicates");
  9186. // According to the CSS2.1 spec, the places where a
  9187. // non-delimiting semicolon can appear are in strings
  9188. // escape sequences. So here is some dumb hack to
  9189. // handle quotes.
  9190. $len = strlen($css);
  9191. $accum = "";
  9192. $declarations = array();
  9193. $quoted = false;
  9194. for ($i = 0; $i < $len; $i++) {
  9195. $c = strcspn($css, ";'\"", $i);
  9196. $accum .= substr($css, $i, $c);
  9197. $i += $c;
  9198. if ($i == $len) break;
  9199. $d = $css[$i];
  9200. if ($quoted) {
  9201. $accum .= $d;
  9202. if ($d == $quoted) {
  9203. $quoted = false;
  9204. }
  9205. } else {
  9206. if ($d == ";") {
  9207. $declarations[] = $accum;
  9208. $accum = "";
  9209. } else {
  9210. $accum .= $d;
  9211. $quoted = $d;
  9212. }
  9213. }
  9214. }
  9215. if ($accum != "") $declarations[] = $accum;
  9216. $propvalues = array();
  9217. $new_declarations = '';
  9218. /**
  9219. * Name of the current CSS property being validated.
  9220. */
  9221. $property = false;
  9222. $context->register('CurrentCSSProperty', $property);
  9223. foreach ($declarations as $declaration) {
  9224. if (!$declaration) {
  9225. continue;
  9226. }
  9227. if (!strpos($declaration, ':')) {
  9228. continue;
  9229. }
  9230. list($property, $value) = explode(':', $declaration, 2);
  9231. $property = trim($property);
  9232. $value = trim($value);
  9233. $ok = false;
  9234. do {
  9235. if (isset($definition->info[$property])) {
  9236. $ok = true;
  9237. break;
  9238. }
  9239. if (ctype_lower($property)) {
  9240. break;
  9241. }
  9242. $property = strtolower($property);
  9243. if (isset($definition->info[$property])) {
  9244. $ok = true;
  9245. break;
  9246. }
  9247. } while (0);
  9248. if (!$ok) {
  9249. continue;
  9250. }
  9251. // inefficient call, since the validator will do this again
  9252. if (strtolower(trim($value)) !== 'inherit') {
  9253. // inherit works for everything (but only on the base property)
  9254. $result = $definition->info[$property]->validate(
  9255. $value,
  9256. $config,
  9257. $context
  9258. );
  9259. } else {
  9260. $result = 'inherit';
  9261. }
  9262. if ($result === false) {
  9263. continue;
  9264. }
  9265. if ($allow_duplicates) {
  9266. $new_declarations .= "$property:$result;";
  9267. } else {
  9268. $propvalues[$property] = $result;
  9269. }
  9270. }
  9271. $context->destroy('CurrentCSSProperty');
  9272. // procedure does not write the new CSS simultaneously, so it's
  9273. // slightly inefficient, but it's the only way of getting rid of
  9274. // duplicates. Perhaps config to optimize it, but not now.
  9275. foreach ($propvalues as $prop => $value) {
  9276. $new_declarations .= "$prop:$value;";
  9277. }
  9278. return $new_declarations ? $new_declarations : false;
  9279. }
  9280. }
  9281. /**
  9282. * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
  9283. * with make.
  9284. */
  9285. class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
  9286. {
  9287. /**
  9288. * What we're cloning.
  9289. * @type HTMLPurifier_AttrDef
  9290. */
  9291. protected $clone;
  9292. /**
  9293. * @param HTMLPurifier_AttrDef $clone
  9294. */
  9295. public function __construct($clone)
  9296. {
  9297. $this->clone = $clone;
  9298. }
  9299. /**
  9300. * @param string $v
  9301. * @param HTMLPurifier_Config $config
  9302. * @param HTMLPurifier_Context $context
  9303. * @return bool|string
  9304. */
  9305. public function validate($v, $config, $context)
  9306. {
  9307. return $this->clone->validate($v, $config, $context);
  9308. }
  9309. /**
  9310. * @param string $string
  9311. * @return HTMLPurifier_AttrDef
  9312. */
  9313. public function make($string)
  9314. {
  9315. return clone $this->clone;
  9316. }
  9317. }
  9318. // Enum = Enumerated
  9319. /**
  9320. * Validates a keyword against a list of valid values.
  9321. * @warning The case-insensitive compare of this function uses PHP's
  9322. * built-in strtolower and ctype_lower functions, which may
  9323. * cause problems with international comparisons
  9324. */
  9325. class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
  9326. {
  9327. /**
  9328. * Lookup table of valid values.
  9329. * @type array
  9330. * @todo Make protected
  9331. */
  9332. public $valid_values = array();
  9333. /**
  9334. * Bool indicating whether or not enumeration is case sensitive.
  9335. * @note In general this is always case insensitive.
  9336. */
  9337. protected $case_sensitive = false; // values according to W3C spec
  9338. /**
  9339. * @param array $valid_values List of valid values
  9340. * @param bool $case_sensitive Whether or not case sensitive
  9341. */
  9342. public function __construct($valid_values = array(), $case_sensitive = false)
  9343. {
  9344. $this->valid_values = array_flip($valid_values);
  9345. $this->case_sensitive = $case_sensitive;
  9346. }
  9347. /**
  9348. * @param string $string
  9349. * @param HTMLPurifier_Config $config
  9350. * @param HTMLPurifier_Context $context
  9351. * @return bool|string
  9352. */
  9353. public function validate($string, $config, $context)
  9354. {
  9355. $string = trim($string);
  9356. if (!$this->case_sensitive) {
  9357. // we may want to do full case-insensitive libraries
  9358. $string = ctype_lower($string) ? $string : strtolower($string);
  9359. }
  9360. $result = isset($this->valid_values[$string]);
  9361. return $result ? $string : false;
  9362. }
  9363. /**
  9364. * @param string $string In form of comma-delimited list of case-insensitive
  9365. * valid values. Example: "foo,bar,baz". Prepend "s:" to make
  9366. * case sensitive
  9367. * @return HTMLPurifier_AttrDef_Enum
  9368. */
  9369. public function make($string)
  9370. {
  9371. if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
  9372. $string = substr($string, 2);
  9373. $sensitive = true;
  9374. } else {
  9375. $sensitive = false;
  9376. }
  9377. $values = explode(',', $string);
  9378. return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
  9379. }
  9380. }
  9381. /**
  9382. * Validates an integer.
  9383. * @note While this class was modeled off the CSS definition, no currently
  9384. * allowed CSS uses this type. The properties that do are: widows,
  9385. * orphans, z-index, counter-increment, counter-reset. Some of the
  9386. * HTML attributes, however, find use for a non-negative version of this.
  9387. */
  9388. class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
  9389. {
  9390. /**
  9391. * Whether or not negative values are allowed.
  9392. * @type bool
  9393. */
  9394. protected $negative = true;
  9395. /**
  9396. * Whether or not zero is allowed.
  9397. * @type bool
  9398. */
  9399. protected $zero = true;
  9400. /**
  9401. * Whether or not positive values are allowed.
  9402. * @type bool
  9403. */
  9404. protected $positive = true;
  9405. /**
  9406. * @param $negative Bool indicating whether or not negative values are allowed
  9407. * @param $zero Bool indicating whether or not zero is allowed
  9408. * @param $positive Bool indicating whether or not positive values are allowed
  9409. */
  9410. public function __construct($negative = true, $zero = true, $positive = true)
  9411. {
  9412. $this->negative = $negative;
  9413. $this->zero = $zero;
  9414. $this->positive = $positive;
  9415. }
  9416. /**
  9417. * @param string $integer
  9418. * @param HTMLPurifier_Config $config
  9419. * @param HTMLPurifier_Context $context
  9420. * @return bool|string
  9421. */
  9422. public function validate($integer, $config, $context)
  9423. {
  9424. $integer = $this->parseCDATA($integer);
  9425. if ($integer === '') {
  9426. return false;
  9427. }
  9428. // we could possibly simply typecast it to integer, but there are
  9429. // certain fringe cases that must not return an integer.
  9430. // clip leading sign
  9431. if ($this->negative && $integer[0] === '-') {
  9432. $digits = substr($integer, 1);
  9433. if ($digits === '0') {
  9434. $integer = '0';
  9435. } // rm minus sign for zero
  9436. } elseif ($this->positive && $integer[0] === '+') {
  9437. $digits = $integer = substr($integer, 1); // rm unnecessary plus
  9438. } else {
  9439. $digits = $integer;
  9440. }
  9441. // test if it's numeric
  9442. if (!ctype_digit($digits)) {
  9443. return false;
  9444. }
  9445. // perform scope tests
  9446. if (!$this->zero && $integer == 0) {
  9447. return false;
  9448. }
  9449. if (!$this->positive && $integer > 0) {
  9450. return false;
  9451. }
  9452. if (!$this->negative && $integer < 0) {
  9453. return false;
  9454. }
  9455. return $integer;
  9456. }
  9457. }
  9458. /**
  9459. * Validates the HTML attribute lang, effectively a language code.
  9460. * @note Built according to RFC 3066, which obsoleted RFC 1766
  9461. */
  9462. class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
  9463. {
  9464. /**
  9465. * @param string $string
  9466. * @param HTMLPurifier_Config $config
  9467. * @param HTMLPurifier_Context $context
  9468. * @return bool|string
  9469. */
  9470. public function validate($string, $config, $context)
  9471. {
  9472. $string = trim($string);
  9473. if (!$string) {
  9474. return false;
  9475. }
  9476. $subtags = explode('-', $string);
  9477. $num_subtags = count($subtags);
  9478. if ($num_subtags == 0) { // sanity check
  9479. return false;
  9480. }
  9481. // process primary subtag : $subtags[0]
  9482. $length = strlen($subtags[0]);
  9483. switch ($length) {
  9484. case 0:
  9485. return false;
  9486. case 1:
  9487. if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
  9488. return false;
  9489. }
  9490. break;
  9491. case 2:
  9492. case 3:
  9493. if (!ctype_alpha($subtags[0])) {
  9494. return false;
  9495. } elseif (!ctype_lower($subtags[0])) {
  9496. $subtags[0] = strtolower($subtags[0]);
  9497. }
  9498. break;
  9499. default:
  9500. return false;
  9501. }
  9502. $new_string = $subtags[0];
  9503. if ($num_subtags == 1) {
  9504. return $new_string;
  9505. }
  9506. // process second subtag : $subtags[1]
  9507. $length = strlen($subtags[1]);
  9508. if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
  9509. return $new_string;
  9510. }
  9511. if (!ctype_lower($subtags[1])) {
  9512. $subtags[1] = strtolower($subtags[1]);
  9513. }
  9514. $new_string .= '-' . $subtags[1];
  9515. if ($num_subtags == 2) {
  9516. return $new_string;
  9517. }
  9518. // process all other subtags, index 2 and up
  9519. for ($i = 2; $i < $num_subtags; $i++) {
  9520. $length = strlen($subtags[$i]);
  9521. if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
  9522. return $new_string;
  9523. }
  9524. if (!ctype_lower($subtags[$i])) {
  9525. $subtags[$i] = strtolower($subtags[$i]);
  9526. }
  9527. $new_string .= '-' . $subtags[$i];
  9528. }
  9529. return $new_string;
  9530. }
  9531. }
  9532. /**
  9533. * Decorator that, depending on a token, switches between two definitions.
  9534. */
  9535. class HTMLPurifier_AttrDef_Switch
  9536. {
  9537. /**
  9538. * @type string
  9539. */
  9540. protected $tag;
  9541. /**
  9542. * @type HTMLPurifier_AttrDef
  9543. */
  9544. protected $withTag;
  9545. /**
  9546. * @type HTMLPurifier_AttrDef
  9547. */
  9548. protected $withoutTag;
  9549. /**
  9550. * @param string $tag Tag name to switch upon
  9551. * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
  9552. * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
  9553. */
  9554. public function __construct($tag, $with_tag, $without_tag)
  9555. {
  9556. $this->tag = $tag;
  9557. $this->withTag = $with_tag;
  9558. $this->withoutTag = $without_tag;
  9559. }
  9560. /**
  9561. * @param string $string
  9562. * @param HTMLPurifier_Config $config
  9563. * @param HTMLPurifier_Context $context
  9564. * @return bool|string
  9565. */
  9566. public function validate($string, $config, $context)
  9567. {
  9568. $token = $context->get('CurrentToken', true);
  9569. if (!$token || $token->name !== $this->tag) {
  9570. return $this->withoutTag->validate($string, $config, $context);
  9571. } else {
  9572. return $this->withTag->validate($string, $config, $context);
  9573. }
  9574. }
  9575. }
  9576. /**
  9577. * Validates arbitrary text according to the HTML spec.
  9578. */
  9579. class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
  9580. {
  9581. /**
  9582. * @param string $string
  9583. * @param HTMLPurifier_Config $config
  9584. * @param HTMLPurifier_Context $context
  9585. * @return bool|string
  9586. */
  9587. public function validate($string, $config, $context)
  9588. {
  9589. return $this->parseCDATA($string);
  9590. }
  9591. }
  9592. /**
  9593. * Validates a URI as defined by RFC 3986.
  9594. * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
  9595. */
  9596. class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
  9597. {
  9598. /**
  9599. * @type HTMLPurifier_URIParser
  9600. */
  9601. protected $parser;
  9602. /**
  9603. * @type bool
  9604. */
  9605. protected $embedsResource;
  9606. /**
  9607. * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
  9608. */
  9609. public function __construct($embeds_resource = false)
  9610. {
  9611. $this->parser = new HTMLPurifier_URIParser();
  9612. $this->embedsResource = (bool)$embeds_resource;
  9613. }
  9614. /**
  9615. * @param string $string
  9616. * @return HTMLPurifier_AttrDef_URI
  9617. */
  9618. public function make($string)
  9619. {
  9620. $embeds = ($string === 'embedded');
  9621. return new HTMLPurifier_AttrDef_URI($embeds);
  9622. }
  9623. /**
  9624. * @param string $uri
  9625. * @param HTMLPurifier_Config $config
  9626. * @param HTMLPurifier_Context $context
  9627. * @return bool|string
  9628. */
  9629. public function validate($uri, $config, $context)
  9630. {
  9631. if ($config->get('URI.Disable')) {
  9632. return false;
  9633. }
  9634. $uri = $this->parseCDATA($uri);
  9635. // parse the URI
  9636. $uri = $this->parser->parse($uri);
  9637. if ($uri === false) {
  9638. return false;
  9639. }
  9640. // add embedded flag to context for validators
  9641. $context->register('EmbeddedURI', $this->embedsResource);
  9642. $ok = false;
  9643. do {
  9644. // generic validation
  9645. $result = $uri->validate($config, $context);
  9646. if (!$result) {
  9647. break;
  9648. }
  9649. // chained filtering
  9650. $uri_def = $config->getDefinition('URI');
  9651. $result = $uri_def->filter($uri, $config, $context);
  9652. if (!$result) {
  9653. break;
  9654. }
  9655. // scheme-specific validation
  9656. $scheme_obj = $uri->getSchemeObj($config, $context);
  9657. if (!$scheme_obj) {
  9658. break;
  9659. }
  9660. if ($this->embedsResource && !$scheme_obj->browsable) {
  9661. break;
  9662. }
  9663. $result = $scheme_obj->validate($uri, $config, $context);
  9664. if (!$result) {
  9665. break;
  9666. }
  9667. // Post chained filtering
  9668. $result = $uri_def->postFilter($uri, $config, $context);
  9669. if (!$result) {
  9670. break;
  9671. }
  9672. // survived gauntlet
  9673. $ok = true;
  9674. } while (false);
  9675. $context->destroy('EmbeddedURI');
  9676. if (!$ok) {
  9677. return false;
  9678. }
  9679. // back to string
  9680. return $uri->toString();
  9681. }
  9682. }
  9683. /**
  9684. * Validates a number as defined by the CSS spec.
  9685. */
  9686. class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
  9687. {
  9688. /**
  9689. * Indicates whether or not only positive values are allowed.
  9690. * @type bool
  9691. */
  9692. protected $non_negative = false;
  9693. /**
  9694. * @param bool $non_negative indicates whether negatives are forbidden
  9695. */
  9696. public function __construct($non_negative = false)
  9697. {
  9698. $this->non_negative = $non_negative;
  9699. }
  9700. /**
  9701. * @param string $number
  9702. * @param HTMLPurifier_Config $config
  9703. * @param HTMLPurifier_Context $context
  9704. * @return string|bool
  9705. * @warning Some contexts do not pass $config, $context. These
  9706. * variables should not be used without checking HTMLPurifier_Length
  9707. */
  9708. public function validate($number, $config, $context)
  9709. {
  9710. $number = $this->parseCDATA($number);
  9711. if ($number === '') {
  9712. return false;
  9713. }
  9714. if ($number === '0') {
  9715. return '0';
  9716. }
  9717. $sign = '';
  9718. switch ($number[0]) {
  9719. case '-':
  9720. if ($this->non_negative) {
  9721. return false;
  9722. }
  9723. $sign = '-';
  9724. case '+':
  9725. $number = substr($number, 1);
  9726. }
  9727. if (ctype_digit($number)) {
  9728. $number = ltrim($number, '0');
  9729. return $number ? $sign . $number : '0';
  9730. }
  9731. // Period is the only non-numeric character allowed
  9732. if (strpos($number, '.') === false) {
  9733. return false;
  9734. }
  9735. list($left, $right) = explode('.', $number, 2);
  9736. if ($left === '' && $right === '') {
  9737. return false;
  9738. }
  9739. if ($left !== '' && !ctype_digit($left)) {
  9740. return false;
  9741. }
  9742. $left = ltrim($left, '0');
  9743. $right = rtrim($right, '0');
  9744. if ($right === '') {
  9745. return $left ? $sign . $left : '0';
  9746. } elseif (!ctype_digit($right)) {
  9747. return false;
  9748. }
  9749. return $sign . $left . '.' . $right;
  9750. }
  9751. }
  9752. class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
  9753. {
  9754. public function __construct()
  9755. {
  9756. parent::__construct(false); // opacity is non-negative, but we will clamp it
  9757. }
  9758. /**
  9759. * @param string $number
  9760. * @param HTMLPurifier_Config $config
  9761. * @param HTMLPurifier_Context $context
  9762. * @return string
  9763. */
  9764. public function validate($number, $config, $context)
  9765. {
  9766. $result = parent::validate($number, $config, $context);
  9767. if ($result === false) {
  9768. return $result;
  9769. }
  9770. $float = (float)$result;
  9771. if ($float < 0.0) {
  9772. $result = '0';
  9773. }
  9774. if ($float > 1.0) {
  9775. $result = '1';
  9776. }
  9777. return $result;
  9778. }
  9779. }
  9780. /**
  9781. * Validates shorthand CSS property background.
  9782. * @warning Does not support url tokens that have internal spaces.
  9783. */
  9784. class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
  9785. {
  9786. /**
  9787. * Local copy of component validators.
  9788. * @type HTMLPurifier_AttrDef[]
  9789. * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
  9790. */
  9791. protected $info;
  9792. /**
  9793. * @param HTMLPurifier_Config $config
  9794. */
  9795. public function __construct($config)
  9796. {
  9797. $def = $config->getCSSDefinition();
  9798. $this->info['background-color'] = $def->info['background-color'];
  9799. $this->info['background-image'] = $def->info['background-image'];
  9800. $this->info['background-repeat'] = $def->info['background-repeat'];
  9801. $this->info['background-attachment'] = $def->info['background-attachment'];
  9802. $this->info['background-position'] = $def->info['background-position'];
  9803. }
  9804. /**
  9805. * @param string $string
  9806. * @param HTMLPurifier_Config $config
  9807. * @param HTMLPurifier_Context $context
  9808. * @return bool|string
  9809. */
  9810. public function validate($string, $config, $context)
  9811. {
  9812. // regular pre-processing
  9813. $string = $this->parseCDATA($string);
  9814. if ($string === '') {
  9815. return false;
  9816. }
  9817. // munge rgb() decl if necessary
  9818. $string = $this->mungeRgb($string);
  9819. // assumes URI doesn't have spaces in it
  9820. $bits = explode(' ', $string); // bits to process
  9821. $caught = array();
  9822. $caught['color'] = false;
  9823. $caught['image'] = false;
  9824. $caught['repeat'] = false;
  9825. $caught['attachment'] = false;
  9826. $caught['position'] = false;
  9827. $i = 0; // number of catches
  9828. foreach ($bits as $bit) {
  9829. if ($bit === '') {
  9830. continue;
  9831. }
  9832. foreach ($caught as $key => $status) {
  9833. if ($key != 'position') {
  9834. if ($status !== false) {
  9835. continue;
  9836. }
  9837. $r = $this->info['background-' . $key]->validate($bit, $config, $context);
  9838. } else {
  9839. $r = $bit;
  9840. }
  9841. if ($r === false) {
  9842. continue;
  9843. }
  9844. if ($key == 'position') {
  9845. if ($caught[$key] === false) {
  9846. $caught[$key] = '';
  9847. }
  9848. $caught[$key] .= $r . ' ';
  9849. } else {
  9850. $caught[$key] = $r;
  9851. }
  9852. $i++;
  9853. break;
  9854. }
  9855. }
  9856. if (!$i) {
  9857. return false;
  9858. }
  9859. if ($caught['position'] !== false) {
  9860. $caught['position'] = $this->info['background-position']->
  9861. validate($caught['position'], $config, $context);
  9862. }
  9863. $ret = array();
  9864. foreach ($caught as $value) {
  9865. if ($value === false) {
  9866. continue;
  9867. }
  9868. $ret[] = $value;
  9869. }
  9870. if (empty($ret)) {
  9871. return false;
  9872. }
  9873. return implode(' ', $ret);
  9874. }
  9875. }
  9876. /* W3C says:
  9877. [ // adjective and number must be in correct order, even if
  9878. // you could switch them without introducing ambiguity.
  9879. // some browsers support that syntax
  9880. [
  9881. <percentage> | <length> | left | center | right
  9882. ]
  9883. [
  9884. <percentage> | <length> | top | center | bottom
  9885. ]?
  9886. ] |
  9887. [ // this signifies that the vertical and horizontal adjectives
  9888. // can be arbitrarily ordered, however, there can only be two,
  9889. // one of each, or none at all
  9890. [
  9891. left | center | right
  9892. ] ||
  9893. [
  9894. top | center | bottom
  9895. ]
  9896. ]
  9897. top, left = 0%
  9898. center, (none) = 50%
  9899. bottom, right = 100%
  9900. */
  9901. /* QuirksMode says:
  9902. keyword + length/percentage must be ordered correctly, as per W3C
  9903. Internet Explorer and Opera, however, support arbitrary ordering. We
  9904. should fix it up.
  9905. Minor issue though, not strictly necessary.
  9906. */
  9907. // control freaks may appreciate the ability to convert these to
  9908. // percentages or something, but it's not necessary
  9909. /**
  9910. * Validates the value of background-position.
  9911. */
  9912. class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
  9913. {
  9914. /**
  9915. * @type HTMLPurifier_AttrDef_CSS_Length
  9916. */
  9917. protected $length;
  9918. /**
  9919. * @type HTMLPurifier_AttrDef_CSS_Percentage
  9920. */
  9921. protected $percentage;
  9922. public function __construct()
  9923. {
  9924. $this->length = new HTMLPurifier_AttrDef_CSS_Length();
  9925. $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
  9926. }
  9927. /**
  9928. * @param string $string
  9929. * @param HTMLPurifier_Config $config
  9930. * @param HTMLPurifier_Context $context
  9931. * @return bool|string
  9932. */
  9933. public function validate($string, $config, $context)
  9934. {
  9935. $string = $this->parseCDATA($string);
  9936. $bits = explode(' ', $string);
  9937. $keywords = array();
  9938. $keywords['h'] = false; // left, right
  9939. $keywords['v'] = false; // top, bottom
  9940. $keywords['ch'] = false; // center (first word)
  9941. $keywords['cv'] = false; // center (second word)
  9942. $measures = array();
  9943. $i = 0;
  9944. $lookup = array(
  9945. 'top' => 'v',
  9946. 'bottom' => 'v',
  9947. 'left' => 'h',
  9948. 'right' => 'h',
  9949. 'center' => 'c'
  9950. );
  9951. foreach ($bits as $bit) {
  9952. if ($bit === '') {
  9953. continue;
  9954. }
  9955. // test for keyword
  9956. $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
  9957. if (isset($lookup[$lbit])) {
  9958. $status = $lookup[$lbit];
  9959. if ($status == 'c') {
  9960. if ($i == 0) {
  9961. $status = 'ch';
  9962. } else {
  9963. $status = 'cv';
  9964. }
  9965. }
  9966. $keywords[$status] = $lbit;
  9967. $i++;
  9968. }
  9969. // test for length
  9970. $r = $this->length->validate($bit, $config, $context);
  9971. if ($r !== false) {
  9972. $measures[] = $r;
  9973. $i++;
  9974. }
  9975. // test for percentage
  9976. $r = $this->percentage->validate($bit, $config, $context);
  9977. if ($r !== false) {
  9978. $measures[] = $r;
  9979. $i++;
  9980. }
  9981. }
  9982. if (!$i) {
  9983. return false;
  9984. } // no valid values were caught
  9985. $ret = array();
  9986. // first keyword
  9987. if ($keywords['h']) {
  9988. $ret[] = $keywords['h'];
  9989. } elseif ($keywords['ch']) {
  9990. $ret[] = $keywords['ch'];
  9991. $keywords['cv'] = false; // prevent re-use: center = center center
  9992. } elseif (count($measures)) {
  9993. $ret[] = array_shift($measures);
  9994. }
  9995. if ($keywords['v']) {
  9996. $ret[] = $keywords['v'];
  9997. } elseif ($keywords['cv']) {
  9998. $ret[] = $keywords['cv'];
  9999. } elseif (count($measures)) {
  10000. $ret[] = array_shift($measures);
  10001. }
  10002. if (empty($ret)) {
  10003. return false;
  10004. }
  10005. return implode(' ', $ret);
  10006. }
  10007. }
  10008. /**
  10009. * Validates the border property as defined by CSS.
  10010. */
  10011. class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
  10012. {
  10013. /**
  10014. * Local copy of properties this property is shorthand for.
  10015. * @type HTMLPurifier_AttrDef[]
  10016. */
  10017. protected $info = array();
  10018. /**
  10019. * @param HTMLPurifier_Config $config
  10020. */
  10021. public function __construct($config)
  10022. {
  10023. $def = $config->getCSSDefinition();
  10024. $this->info['border-width'] = $def->info['border-width'];
  10025. $this->info['border-style'] = $def->info['border-style'];
  10026. $this->info['border-top-color'] = $def->info['border-top-color'];
  10027. }
  10028. /**
  10029. * @param string $string
  10030. * @param HTMLPurifier_Config $config
  10031. * @param HTMLPurifier_Context $context
  10032. * @return bool|string
  10033. */
  10034. public function validate($string, $config, $context)
  10035. {
  10036. $string = $this->parseCDATA($string);
  10037. $string = $this->mungeRgb($string);
  10038. $bits = explode(' ', $string);
  10039. $done = array(); // segments we've finished
  10040. $ret = ''; // return value
  10041. foreach ($bits as $bit) {
  10042. foreach ($this->info as $propname => $validator) {
  10043. if (isset($done[$propname])) {
  10044. continue;
  10045. }
  10046. $r = $validator->validate($bit, $config, $context);
  10047. if ($r !== false) {
  10048. $ret .= $r . ' ';
  10049. $done[$propname] = true;
  10050. break;
  10051. }
  10052. }
  10053. }
  10054. return rtrim($ret);
  10055. }
  10056. }
  10057. /**
  10058. * Validates Color as defined by CSS.
  10059. */
  10060. class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
  10061. {
  10062. /**
  10063. * @type HTMLPurifier_AttrDef_CSS_AlphaValue
  10064. */
  10065. protected $alpha;
  10066. public function __construct()
  10067. {
  10068. $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  10069. }
  10070. /**
  10071. * @param string $color
  10072. * @param HTMLPurifier_Config $config
  10073. * @param HTMLPurifier_Context $context
  10074. * @return bool|string
  10075. */
  10076. public function validate($color, $config, $context)
  10077. {
  10078. static $colors = null;
  10079. if ($colors === null) {
  10080. $colors = $config->get('Core.ColorKeywords');
  10081. }
  10082. $color = trim($color);
  10083. if ($color === '') {
  10084. return false;
  10085. }
  10086. $lower = strtolower($color);
  10087. if (isset($colors[$lower])) {
  10088. return $colors[$lower];
  10089. }
  10090. if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
  10091. $length = strlen($color);
  10092. if (strpos($color, ')') !== $length - 1) {
  10093. return false;
  10094. }
  10095. // get used function : rgb, rgba, hsl or hsla
  10096. $function = $matches[1];
  10097. $parameters_size = 3;
  10098. $alpha_channel = false;
  10099. if (substr($function, -1) === 'a') {
  10100. $parameters_size = 4;
  10101. $alpha_channel = true;
  10102. }
  10103. /*
  10104. * Allowed types for values :
  10105. * parameter_position => [type => max_value]
  10106. */
  10107. $allowed_types = array(
  10108. 1 => array('percentage' => 100, 'integer' => 255),
  10109. 2 => array('percentage' => 100, 'integer' => 255),
  10110. 3 => array('percentage' => 100, 'integer' => 255),
  10111. );
  10112. $allow_different_types = false;
  10113. if (strpos($function, 'hsl') !== false) {
  10114. $allowed_types = array(
  10115. 1 => array('integer' => 360),
  10116. 2 => array('percentage' => 100),
  10117. 3 => array('percentage' => 100),
  10118. );
  10119. $allow_different_types = true;
  10120. }
  10121. $values = trim(str_replace($function, '', $color), ' ()');
  10122. $parts = explode(',', $values);
  10123. if (count($parts) !== $parameters_size) {
  10124. return false;
  10125. }
  10126. $type = false;
  10127. $new_parts = array();
  10128. $i = 0;
  10129. foreach ($parts as $part) {
  10130. $i++;
  10131. $part = trim($part);
  10132. if ($part === '') {
  10133. return false;
  10134. }
  10135. // different check for alpha channel
  10136. if ($alpha_channel === true && $i === count($parts)) {
  10137. $result = $this->alpha->validate($part, $config, $context);
  10138. if ($result === false) {
  10139. return false;
  10140. }
  10141. $new_parts[] = (string)$result;
  10142. continue;
  10143. }
  10144. if (substr($part, -1) === '%') {
  10145. $current_type = 'percentage';
  10146. } else {
  10147. $current_type = 'integer';
  10148. }
  10149. if (!array_key_exists($current_type, $allowed_types[$i])) {
  10150. return false;
  10151. }
  10152. if (!$type) {
  10153. $type = $current_type;
  10154. }
  10155. if ($allow_different_types === false && $type != $current_type) {
  10156. return false;
  10157. }
  10158. $max_value = $allowed_types[$i][$current_type];
  10159. if ($current_type == 'integer') {
  10160. // Return value between range 0 -> $max_value
  10161. $new_parts[] = (int)max(min($part, $max_value), 0);
  10162. } elseif ($current_type == 'percentage') {
  10163. $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
  10164. }
  10165. }
  10166. $new_values = implode(',', $new_parts);
  10167. $color = $function . '(' . $new_values . ')';
  10168. } else {
  10169. // hexadecimal handling
  10170. if ($color[0] === '#') {
  10171. $hex = substr($color, 1);
  10172. } else {
  10173. $hex = $color;
  10174. $color = '#' . $color;
  10175. }
  10176. $length = strlen($hex);
  10177. if ($length !== 3 && $length !== 6) {
  10178. return false;
  10179. }
  10180. if (!ctype_xdigit($hex)) {
  10181. return false;
  10182. }
  10183. }
  10184. return $color;
  10185. }
  10186. }
  10187. /**
  10188. * Allows multiple validators to attempt to validate attribute.
  10189. *
  10190. * Composite is just what it sounds like: a composite of many validators.
  10191. * This means that multiple HTMLPurifier_AttrDef objects will have a whack
  10192. * at the string. If one of them passes, that's what is returned. This is
  10193. * especially useful for CSS values, which often are a choice between
  10194. * an enumerated set of predefined values or a flexible data type.
  10195. */
  10196. class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
  10197. {
  10198. /**
  10199. * List of objects that may process strings.
  10200. * @type HTMLPurifier_AttrDef[]
  10201. * @todo Make protected
  10202. */
  10203. public $defs;
  10204. /**
  10205. * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
  10206. */
  10207. public function __construct($defs)
  10208. {
  10209. $this->defs = $defs;
  10210. }
  10211. /**
  10212. * @param string $string
  10213. * @param HTMLPurifier_Config $config
  10214. * @param HTMLPurifier_Context $context
  10215. * @return bool|string
  10216. */
  10217. public function validate($string, $config, $context)
  10218. {
  10219. foreach ($this->defs as $i => $def) {
  10220. $result = $this->defs[$i]->validate($string, $config, $context);
  10221. if ($result !== false) {
  10222. return $result;
  10223. }
  10224. }
  10225. return false;
  10226. }
  10227. }
  10228. /**
  10229. * Decorator which enables CSS properties to be disabled for specific elements.
  10230. */
  10231. class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
  10232. {
  10233. /**
  10234. * @type HTMLPurifier_AttrDef
  10235. */
  10236. public $def;
  10237. /**
  10238. * @type string
  10239. */
  10240. public $element;
  10241. /**
  10242. * @param HTMLPurifier_AttrDef $def Definition to wrap
  10243. * @param string $element Element to deny
  10244. */
  10245. public function __construct($def, $element)
  10246. {
  10247. $this->def = $def;
  10248. $this->element = $element;
  10249. }
  10250. /**
  10251. * Checks if CurrentToken is set and equal to $this->element
  10252. * @param string $string
  10253. * @param HTMLPurifier_Config $config
  10254. * @param HTMLPurifier_Context $context
  10255. * @return bool|string
  10256. */
  10257. public function validate($string, $config, $context)
  10258. {
  10259. $token = $context->get('CurrentToken', true);
  10260. if ($token && $token->name == $this->element) {
  10261. return false;
  10262. }
  10263. return $this->def->validate($string, $config, $context);
  10264. }
  10265. }
  10266. /**
  10267. * Microsoft's proprietary filter: CSS property
  10268. * @note Currently supports the alpha filter. In the future, this will
  10269. * probably need an extensible framework
  10270. */
  10271. class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
  10272. {
  10273. /**
  10274. * @type HTMLPurifier_AttrDef_Integer
  10275. */
  10276. protected $intValidator;
  10277. public function __construct()
  10278. {
  10279. $this->intValidator = new HTMLPurifier_AttrDef_Integer();
  10280. }
  10281. /**
  10282. * @param string $value
  10283. * @param HTMLPurifier_Config $config
  10284. * @param HTMLPurifier_Context $context
  10285. * @return bool|string
  10286. */
  10287. public function validate($value, $config, $context)
  10288. {
  10289. $value = $this->parseCDATA($value);
  10290. if ($value === 'none') {
  10291. return $value;
  10292. }
  10293. // if we looped this we could support multiple filters
  10294. $function_length = strcspn($value, '(');
  10295. $function = trim(substr($value, 0, $function_length));
  10296. if ($function !== 'alpha' &&
  10297. $function !== 'Alpha' &&
  10298. $function !== 'progid:DXImageTransform.Microsoft.Alpha'
  10299. ) {
  10300. return false;
  10301. }
  10302. $cursor = $function_length + 1;
  10303. $parameters_length = strcspn($value, ')', $cursor);
  10304. $parameters = substr($value, $cursor, $parameters_length);
  10305. $params = explode(',', $parameters);
  10306. $ret_params = array();
  10307. $lookup = array();
  10308. foreach ($params as $param) {
  10309. list($key, $value) = explode('=', $param);
  10310. $key = trim($key);
  10311. $value = trim($value);
  10312. if (isset($lookup[$key])) {
  10313. continue;
  10314. }
  10315. if ($key !== 'opacity') {
  10316. continue;
  10317. }
  10318. $value = $this->intValidator->validate($value, $config, $context);
  10319. if ($value === false) {
  10320. continue;
  10321. }
  10322. $int = (int)$value;
  10323. if ($int > 100) {
  10324. $value = '100';
  10325. }
  10326. if ($int < 0) {
  10327. $value = '0';
  10328. }
  10329. $ret_params[] = "$key=$value";
  10330. $lookup[$key] = true;
  10331. }
  10332. $ret_parameters = implode(',', $ret_params);
  10333. $ret_function = "$function($ret_parameters)";
  10334. return $ret_function;
  10335. }
  10336. }
  10337. /**
  10338. * Validates shorthand CSS property font.
  10339. */
  10340. class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
  10341. {
  10342. /**
  10343. * Local copy of validators
  10344. * @type HTMLPurifier_AttrDef[]
  10345. * @note If we moved specific CSS property definitions to their own
  10346. * classes instead of having them be assembled at run time by
  10347. * CSSDefinition, this wouldn't be necessary. We'd instantiate
  10348. * our own copies.
  10349. */
  10350. protected $info = array();
  10351. /**
  10352. * @param HTMLPurifier_Config $config
  10353. */
  10354. public function __construct($config)
  10355. {
  10356. $def = $config->getCSSDefinition();
  10357. $this->info['font-style'] = $def->info['font-style'];
  10358. $this->info['font-variant'] = $def->info['font-variant'];
  10359. $this->info['font-weight'] = $def->info['font-weight'];
  10360. $this->info['font-size'] = $def->info['font-size'];
  10361. $this->info['line-height'] = $def->info['line-height'];
  10362. $this->info['font-family'] = $def->info['font-family'];
  10363. }
  10364. /**
  10365. * @param string $string
  10366. * @param HTMLPurifier_Config $config
  10367. * @param HTMLPurifier_Context $context
  10368. * @return bool|string
  10369. */
  10370. public function validate($string, $config, $context)
  10371. {
  10372. static $system_fonts = array(
  10373. 'caption' => true,
  10374. 'icon' => true,
  10375. 'menu' => true,
  10376. 'message-box' => true,
  10377. 'small-caption' => true,
  10378. 'status-bar' => true
  10379. );
  10380. // regular pre-processing
  10381. $string = $this->parseCDATA($string);
  10382. if ($string === '') {
  10383. return false;
  10384. }
  10385. // check if it's one of the keywords
  10386. $lowercase_string = strtolower($string);
  10387. if (isset($system_fonts[$lowercase_string])) {
  10388. return $lowercase_string;
  10389. }
  10390. $bits = explode(' ', $string); // bits to process
  10391. $stage = 0; // this indicates what we're looking for
  10392. $caught = array(); // which stage 0 properties have we caught?
  10393. $stage_1 = array('font-style', 'font-variant', 'font-weight');
  10394. $final = ''; // output
  10395. for ($i = 0, $size = count($bits); $i < $size; $i++) {
  10396. if ($bits[$i] === '') {
  10397. continue;
  10398. }
  10399. switch ($stage) {
  10400. case 0: // attempting to catch font-style, font-variant or font-weight
  10401. foreach ($stage_1 as $validator_name) {
  10402. if (isset($caught[$validator_name])) {
  10403. continue;
  10404. }
  10405. $r = $this->info[$validator_name]->validate(
  10406. $bits[$i],
  10407. $config,
  10408. $context
  10409. );
  10410. if ($r !== false) {
  10411. $final .= $r . ' ';
  10412. $caught[$validator_name] = true;
  10413. break;
  10414. }
  10415. }
  10416. // all three caught, continue on
  10417. if (count($caught) >= 3) {
  10418. $stage = 1;
  10419. }
  10420. if ($r !== false) {
  10421. break;
  10422. }
  10423. case 1: // attempting to catch font-size and perhaps line-height
  10424. $found_slash = false;
  10425. if (strpos($bits[$i], '/') !== false) {
  10426. list($font_size, $line_height) =
  10427. explode('/', $bits[$i]);
  10428. if ($line_height === '') {
  10429. // ooh, there's a space after the slash!
  10430. $line_height = false;
  10431. $found_slash = true;
  10432. }
  10433. } else {
  10434. $font_size = $bits[$i];
  10435. $line_height = false;
  10436. }
  10437. $r = $this->info['font-size']->validate(
  10438. $font_size,
  10439. $config,
  10440. $context
  10441. );
  10442. if ($r !== false) {
  10443. $final .= $r;
  10444. // attempt to catch line-height
  10445. if ($line_height === false) {
  10446. // we need to scroll forward
  10447. for ($j = $i + 1; $j < $size; $j++) {
  10448. if ($bits[$j] === '') {
  10449. continue;
  10450. }
  10451. if ($bits[$j] === '/') {
  10452. if ($found_slash) {
  10453. return false;
  10454. } else {
  10455. $found_slash = true;
  10456. continue;
  10457. }
  10458. }
  10459. $line_height = $bits[$j];
  10460. break;
  10461. }
  10462. } else {
  10463. // slash already found
  10464. $found_slash = true;
  10465. $j = $i;
  10466. }
  10467. if ($found_slash) {
  10468. $i = $j;
  10469. $r = $this->info['line-height']->validate(
  10470. $line_height,
  10471. $config,
  10472. $context
  10473. );
  10474. if ($r !== false) {
  10475. $final .= '/' . $r;
  10476. }
  10477. }
  10478. $final .= ' ';
  10479. $stage = 2;
  10480. break;
  10481. }
  10482. return false;
  10483. case 2: // attempting to catch font-family
  10484. $font_family =
  10485. implode(' ', array_slice($bits, $i, $size - $i));
  10486. $r = $this->info['font-family']->validate(
  10487. $font_family,
  10488. $config,
  10489. $context
  10490. );
  10491. if ($r !== false) {
  10492. $final .= $r . ' ';
  10493. // processing completed successfully
  10494. return rtrim($final);
  10495. }
  10496. return false;
  10497. }
  10498. }
  10499. return false;
  10500. }
  10501. }
  10502. /**
  10503. * Validates a font family list according to CSS spec
  10504. */
  10505. class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
  10506. {
  10507. protected $mask = null;
  10508. public function __construct()
  10509. {
  10510. $this->mask = '_- ';
  10511. for ($c = 'a'; $c <= 'z'; $c++) {
  10512. $this->mask .= $c;
  10513. }
  10514. for ($c = 'A'; $c <= 'Z'; $c++) {
  10515. $this->mask .= $c;
  10516. }
  10517. for ($c = '0'; $c <= '9'; $c++) {
  10518. $this->mask .= $c;
  10519. } // cast-y, but should be fine
  10520. // special bytes used by UTF-8
  10521. for ($i = 0x80; $i <= 0xFF; $i++) {
  10522. // We don't bother excluding invalid bytes in this range,
  10523. // because the our restriction of well-formed UTF-8 will
  10524. // prevent these from ever occurring.
  10525. $this->mask .= chr($i);
  10526. }
  10527. /*
  10528. PHP's internal strcspn implementation is
  10529. O(length of string * length of mask), making it inefficient
  10530. for large masks. However, it's still faster than
  10531. preg_match 8)
  10532. for (p = s1;;) {
  10533. spanp = s2;
  10534. do {
  10535. if (*spanp == c || p == s1_end) {
  10536. return p - s1;
  10537. }
  10538. } while (spanp++ < (s2_end - 1));
  10539. c = *++p;
  10540. }
  10541. */
  10542. // possible optimization: invert the mask.
  10543. }
  10544. /**
  10545. * @param string $string
  10546. * @param HTMLPurifier_Config $config
  10547. * @param HTMLPurifier_Context $context
  10548. * @return bool|string
  10549. */
  10550. public function validate($string, $config, $context)
  10551. {
  10552. static $generic_names = array(
  10553. 'serif' => true,
  10554. 'sans-serif' => true,
  10555. 'monospace' => true,
  10556. 'fantasy' => true,
  10557. 'cursive' => true
  10558. );
  10559. $allowed_fonts = $config->get('CSS.AllowedFonts');
  10560. // assume that no font names contain commas in them
  10561. $fonts = explode(',', $string);
  10562. $final = '';
  10563. foreach ($fonts as $font) {
  10564. $font = trim($font);
  10565. if ($font === '') {
  10566. continue;
  10567. }
  10568. // match a generic name
  10569. if (isset($generic_names[$font])) {
  10570. if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
  10571. $final .= $font . ', ';
  10572. }
  10573. continue;
  10574. }
  10575. // match a quoted name
  10576. if ($font[0] === '"' || $font[0] === "'") {
  10577. $length = strlen($font);
  10578. if ($length <= 2) {
  10579. continue;
  10580. }
  10581. $quote = $font[0];
  10582. if ($font[$length - 1] !== $quote) {
  10583. continue;
  10584. }
  10585. $font = substr($font, 1, $length - 2);
  10586. }
  10587. $font = $this->expandCSSEscape($font);
  10588. // $font is a pure representation of the font name
  10589. if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
  10590. continue;
  10591. }
  10592. if (ctype_alnum($font) && $font !== '') {
  10593. // very simple font, allow it in unharmed
  10594. $final .= $font . ', ';
  10595. continue;
  10596. }
  10597. // bugger out on whitespace. form feed (0C) really
  10598. // shouldn't show up regardless
  10599. $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
  10600. // Here, there are various classes of characters which need
  10601. // to be treated differently:
  10602. // - Alphanumeric characters are essentially safe. We
  10603. // handled these above.
  10604. // - Spaces require quoting, though most parsers will do
  10605. // the right thing if there aren't any characters that
  10606. // can be misinterpreted
  10607. // - Dashes rarely occur, but they fairly unproblematic
  10608. // for parsing/rendering purposes.
  10609. // The above characters cover the majority of Western font
  10610. // names.
  10611. // - Arbitrary Unicode characters not in ASCII. Because
  10612. // most parsers give little thought to Unicode, treatment
  10613. // of these codepoints is basically uniform, even for
  10614. // punctuation-like codepoints. These characters can
  10615. // show up in non-Western pages and are supported by most
  10616. // major browsers, for example: "MS 明朝" is a
  10617. // legitimate font-name
  10618. // <http://ja.wikipedia.org/wiki/MS_明朝>. See
  10619. // the CSS3 spec for more examples:
  10620. // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
  10621. // You can see live samples of these on the Internet:
  10622. // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
  10623. // However, most of these fonts have ASCII equivalents:
  10624. // for example, 'MS Mincho', and it's considered
  10625. // professional to use ASCII font names instead of
  10626. // Unicode font names. Thanks Takeshi Terada for
  10627. // providing this information.
  10628. // The following characters, to my knowledge, have not been
  10629. // used to name font names.
  10630. // - Single quote. While theoretically you might find a
  10631. // font name that has a single quote in its name (serving
  10632. // as an apostrophe, e.g. Dave's Scribble), I haven't
  10633. // been able to find any actual examples of this.
  10634. // Internet Explorer's cssText translation (which I
  10635. // believe is invoked by innerHTML) normalizes any
  10636. // quoting to single quotes, and fails to escape single
  10637. // quotes. (Note that this is not IE's behavior for all
  10638. // CSS properties, just some sort of special casing for
  10639. // font-family). So a single quote *cannot* be used
  10640. // safely in the font-family context if there will be an
  10641. // innerHTML/cssText translation. Note that Firefox 3.x
  10642. // does this too.
  10643. // - Double quote. In IE, these get normalized to
  10644. // single-quotes, no matter what the encoding. (Fun
  10645. // fact, in IE8, the 'content' CSS property gained
  10646. // support, where they special cased to preserve encoded
  10647. // double quotes, but still translate unadorned double
  10648. // quotes into single quotes.) So, because their
  10649. // fixpoint behavior is identical to single quotes, they
  10650. // cannot be allowed either. Firefox 3.x displays
  10651. // single-quote style behavior.
  10652. // - Backslashes are reduced by one (so \\ -> \) every
  10653. // iteration, so they cannot be used safely. This shows
  10654. // up in IE7, IE8 and FF3
  10655. // - Semicolons, commas and backticks are handled properly.
  10656. // - The rest of the ASCII punctuation is handled properly.
  10657. // We haven't checked what browsers do to unadorned
  10658. // versions, but this is not important as long as the
  10659. // browser doesn't /remove/ surrounding quotes (as IE does
  10660. // for HTML).
  10661. //
  10662. // With these results in hand, we conclude that there are
  10663. // various levels of safety:
  10664. // - Paranoid: alphanumeric, spaces and dashes(?)
  10665. // - International: Paranoid + non-ASCII Unicode
  10666. // - Edgy: Everything except quotes, backslashes
  10667. // - NoJS: Standards compliance, e.g. sod IE. Note that
  10668. // with some judicious character escaping (since certain
  10669. // types of escaping doesn't work) this is theoretically
  10670. // OK as long as innerHTML/cssText is not called.
  10671. // We believe that international is a reasonable default
  10672. // (that we will implement now), and once we do more
  10673. // extensive research, we may feel comfortable with dropping
  10674. // it down to edgy.
  10675. // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
  10676. // str(c)spn assumes that the string was already well formed
  10677. // Unicode (which of course it is).
  10678. if (strspn($font, $this->mask) !== strlen($font)) {
  10679. continue;
  10680. }
  10681. // Historical:
  10682. // In the absence of innerHTML/cssText, these ugly
  10683. // transforms don't pose a security risk (as \\ and \"
  10684. // might--these escapes are not supported by most browsers).
  10685. // We could try to be clever and use single-quote wrapping
  10686. // when there is a double quote present, but I have choosen
  10687. // not to implement that. (NOTE: you can reduce the amount
  10688. // of escapes by one depending on what quoting style you use)
  10689. // $font = str_replace('\\', '\\5C ', $font);
  10690. // $font = str_replace('"', '\\22 ', $font);
  10691. // $font = str_replace("'", '\\27 ', $font);
  10692. // font possibly with spaces, requires quoting
  10693. $final .= "'$font', ";
  10694. }
  10695. $final = rtrim($final, ', ');
  10696. if ($final === '') {
  10697. return false;
  10698. }
  10699. return $final;
  10700. }
  10701. }
  10702. /**
  10703. * Validates based on {ident} CSS grammar production
  10704. */
  10705. class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
  10706. {
  10707. /**
  10708. * @param string $string
  10709. * @param HTMLPurifier_Config $config
  10710. * @param HTMLPurifier_Context $context
  10711. * @return bool|string
  10712. */
  10713. public function validate($string, $config, $context)
  10714. {
  10715. $string = trim($string);
  10716. // early abort: '' and '0' (strings that convert to false) are invalid
  10717. if (!$string) {
  10718. return false;
  10719. }
  10720. $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
  10721. if (!preg_match($pattern, $string)) {
  10722. return false;
  10723. }
  10724. return $string;
  10725. }
  10726. }
  10727. /**
  10728. * Decorator which enables !important to be used in CSS values.
  10729. */
  10730. class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
  10731. {
  10732. /**
  10733. * @type HTMLPurifier_AttrDef
  10734. */
  10735. public $def;
  10736. /**
  10737. * @type bool
  10738. */
  10739. public $allow;
  10740. /**
  10741. * @param HTMLPurifier_AttrDef $def Definition to wrap
  10742. * @param bool $allow Whether or not to allow !important
  10743. */
  10744. public function __construct($def, $allow = false)
  10745. {
  10746. $this->def = $def;
  10747. $this->allow = $allow;
  10748. }
  10749. /**
  10750. * Intercepts and removes !important if necessary
  10751. * @param string $string
  10752. * @param HTMLPurifier_Config $config
  10753. * @param HTMLPurifier_Context $context
  10754. * @return bool|string
  10755. */
  10756. public function validate($string, $config, $context)
  10757. {
  10758. // test for ! and important tokens
  10759. $string = trim($string);
  10760. $is_important = false;
  10761. // :TODO: optimization: test directly for !important and ! important
  10762. if (strlen($string) >= 9 && substr($string, -9) === 'important') {
  10763. $temp = rtrim(substr($string, 0, -9));
  10764. // use a temp, because we might want to restore important
  10765. if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
  10766. $string = rtrim(substr($temp, 0, -1));
  10767. $is_important = true;
  10768. }
  10769. }
  10770. $string = $this->def->validate($string, $config, $context);
  10771. if ($this->allow && $is_important) {
  10772. $string .= ' !important';
  10773. }
  10774. return $string;
  10775. }
  10776. }
  10777. /**
  10778. * Represents a Length as defined by CSS.
  10779. */
  10780. class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
  10781. {
  10782. /**
  10783. * @type HTMLPurifier_Length|string
  10784. */
  10785. protected $min;
  10786. /**
  10787. * @type HTMLPurifier_Length|string
  10788. */
  10789. protected $max;
  10790. /**
  10791. * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
  10792. * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
  10793. */
  10794. public function __construct($min = null, $max = null)
  10795. {
  10796. $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
  10797. $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
  10798. }
  10799. /**
  10800. * @param string $string
  10801. * @param HTMLPurifier_Config $config
  10802. * @param HTMLPurifier_Context $context
  10803. * @return bool|string
  10804. */
  10805. public function validate($string, $config, $context)
  10806. {
  10807. $string = $this->parseCDATA($string);
  10808. // Optimizations
  10809. if ($string === '') {
  10810. return false;
  10811. }
  10812. if ($string === '0') {
  10813. return '0';
  10814. }
  10815. if (strlen($string) === 1) {
  10816. return false;
  10817. }
  10818. $length = HTMLPurifier_Length::make($string);
  10819. if (!$length->isValid()) {
  10820. return false;
  10821. }
  10822. if ($this->min) {
  10823. $c = $length->compareTo($this->min);
  10824. if ($c === false) {
  10825. return false;
  10826. }
  10827. if ($c < 0) {
  10828. return false;
  10829. }
  10830. }
  10831. if ($this->max) {
  10832. $c = $length->compareTo($this->max);
  10833. if ($c === false) {
  10834. return false;
  10835. }
  10836. if ($c > 0) {
  10837. return false;
  10838. }
  10839. }
  10840. return $length->toString();
  10841. }
  10842. }
  10843. /**
  10844. * Validates shorthand CSS property list-style.
  10845. * @warning Does not support url tokens that have internal spaces.
  10846. */
  10847. class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
  10848. {
  10849. /**
  10850. * Local copy of validators.
  10851. * @type HTMLPurifier_AttrDef[]
  10852. * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
  10853. */
  10854. protected $info;
  10855. /**
  10856. * @param HTMLPurifier_Config $config
  10857. */
  10858. public function __construct($config)
  10859. {
  10860. $def = $config->getCSSDefinition();
  10861. $this->info['list-style-type'] = $def->info['list-style-type'];
  10862. $this->info['list-style-position'] = $def->info['list-style-position'];
  10863. $this->info['list-style-image'] = $def->info['list-style-image'];
  10864. }
  10865. /**
  10866. * @param string $string
  10867. * @param HTMLPurifier_Config $config
  10868. * @param HTMLPurifier_Context $context
  10869. * @return bool|string
  10870. */
  10871. public function validate($string, $config, $context)
  10872. {
  10873. // regular pre-processing
  10874. $string = $this->parseCDATA($string);
  10875. if ($string === '') {
  10876. return false;
  10877. }
  10878. // assumes URI doesn't have spaces in it
  10879. $bits = explode(' ', strtolower($string)); // bits to process
  10880. $caught = array();
  10881. $caught['type'] = false;
  10882. $caught['position'] = false;
  10883. $caught['image'] = false;
  10884. $i = 0; // number of catches
  10885. $none = false;
  10886. foreach ($bits as $bit) {
  10887. if ($i >= 3) {
  10888. return;
  10889. } // optimization bit
  10890. if ($bit === '') {
  10891. continue;
  10892. }
  10893. foreach ($caught as $key => $status) {
  10894. if ($status !== false) {
  10895. continue;
  10896. }
  10897. $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
  10898. if ($r === false) {
  10899. continue;
  10900. }
  10901. if ($r === 'none') {
  10902. if ($none) {
  10903. continue;
  10904. } else {
  10905. $none = true;
  10906. }
  10907. if ($key == 'image') {
  10908. continue;
  10909. }
  10910. }
  10911. $caught[$key] = $r;
  10912. $i++;
  10913. break;
  10914. }
  10915. }
  10916. if (!$i) {
  10917. return false;
  10918. }
  10919. $ret = array();
  10920. // construct type
  10921. if ($caught['type']) {
  10922. $ret[] = $caught['type'];
  10923. }
  10924. // construct image
  10925. if ($caught['image']) {
  10926. $ret[] = $caught['image'];
  10927. }
  10928. // construct position
  10929. if ($caught['position']) {
  10930. $ret[] = $caught['position'];
  10931. }
  10932. if (empty($ret)) {
  10933. return false;
  10934. }
  10935. return implode(' ', $ret);
  10936. }
  10937. }
  10938. /**
  10939. * Framework class for strings that involve multiple values.
  10940. *
  10941. * Certain CSS properties such as border-width and margin allow multiple
  10942. * lengths to be specified. This class can take a vanilla border-width
  10943. * definition and multiply it, usually into a max of four.
  10944. *
  10945. * @note Even though the CSS specification isn't clear about it, inherit
  10946. * can only be used alone: it will never manifest as part of a multi
  10947. * shorthand declaration. Thus, this class does not allow inherit.
  10948. */
  10949. class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
  10950. {
  10951. /**
  10952. * Instance of component definition to defer validation to.
  10953. * @type HTMLPurifier_AttrDef
  10954. * @todo Make protected
  10955. */
  10956. public $single;
  10957. /**
  10958. * Max number of values allowed.
  10959. * @todo Make protected
  10960. */
  10961. public $max;
  10962. /**
  10963. * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
  10964. * @param int $max Max number of values allowed (usually four)
  10965. */
  10966. public function __construct($single, $max = 4)
  10967. {
  10968. $this->single = $single;
  10969. $this->max = $max;
  10970. }
  10971. /**
  10972. * @param string $string
  10973. * @param HTMLPurifier_Config $config
  10974. * @param HTMLPurifier_Context $context
  10975. * @return bool|string
  10976. */
  10977. public function validate($string, $config, $context)
  10978. {
  10979. $string = $this->mungeRgb($this->parseCDATA($string));
  10980. if ($string === '') {
  10981. return false;
  10982. }
  10983. $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
  10984. $length = count($parts);
  10985. $final = '';
  10986. for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
  10987. if (ctype_space($parts[$i])) {
  10988. continue;
  10989. }
  10990. $result = $this->single->validate($parts[$i], $config, $context);
  10991. if ($result !== false) {
  10992. $final .= $result . ' ';
  10993. $num++;
  10994. }
  10995. }
  10996. if ($final === '') {
  10997. return false;
  10998. }
  10999. return rtrim($final);
  11000. }
  11001. }
  11002. /**
  11003. * Validates a Percentage as defined by the CSS spec.
  11004. */
  11005. class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
  11006. {
  11007. /**
  11008. * Instance to defer number validation to.
  11009. * @type HTMLPurifier_AttrDef_CSS_Number
  11010. */
  11011. protected $number_def;
  11012. /**
  11013. * @param bool $non_negative Whether to forbid negative values
  11014. */
  11015. public function __construct($non_negative = false)
  11016. {
  11017. $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
  11018. }
  11019. /**
  11020. * @param string $string
  11021. * @param HTMLPurifier_Config $config
  11022. * @param HTMLPurifier_Context $context
  11023. * @return bool|string
  11024. */
  11025. public function validate($string, $config, $context)
  11026. {
  11027. $string = $this->parseCDATA($string);
  11028. if ($string === '') {
  11029. return false;
  11030. }
  11031. $length = strlen($string);
  11032. if ($length === 1) {
  11033. return false;
  11034. }
  11035. if ($string[$length - 1] !== '%') {
  11036. return false;
  11037. }
  11038. $number = substr($string, 0, $length - 1);
  11039. $number = $this->number_def->validate($number, $config, $context);
  11040. if ($number === false) {
  11041. return false;
  11042. }
  11043. return "$number%";
  11044. }
  11045. }
  11046. /**
  11047. * Validates the value for the CSS property text-decoration
  11048. * @note This class could be generalized into a version that acts sort of
  11049. * like Enum except you can compound the allowed values.
  11050. */
  11051. class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
  11052. {
  11053. /**
  11054. * @param string $string
  11055. * @param HTMLPurifier_Config $config
  11056. * @param HTMLPurifier_Context $context
  11057. * @return bool|string
  11058. */
  11059. public function validate($string, $config, $context)
  11060. {
  11061. static $allowed_values = array(
  11062. 'line-through' => true,
  11063. 'overline' => true,
  11064. 'underline' => true,
  11065. );
  11066. $string = strtolower($this->parseCDATA($string));
  11067. if ($string === 'none') {
  11068. return $string;
  11069. }
  11070. $parts = explode(' ', $string);
  11071. $final = '';
  11072. foreach ($parts as $part) {
  11073. if (isset($allowed_values[$part])) {
  11074. $final .= $part . ' ';
  11075. }
  11076. }
  11077. $final = rtrim($final);
  11078. if ($final === '') {
  11079. return false;
  11080. }
  11081. return $final;
  11082. }
  11083. }
  11084. /**
  11085. * Validates a URI in CSS syntax, which uses url('http://example.com')
  11086. * @note While theoretically speaking a URI in a CSS document could
  11087. * be non-embedded, as of CSS2 there is no such usage so we're
  11088. * generalizing it. This may need to be changed in the future.
  11089. * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
  11090. * the separator, you cannot put a literal semicolon in
  11091. * in the URI. Try percent encoding it, in that case.
  11092. */
  11093. class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
  11094. {
  11095. public function __construct()
  11096. {
  11097. parent::__construct(true); // always embedded
  11098. }
  11099. /**
  11100. * @param string $uri_string
  11101. * @param HTMLPurifier_Config $config
  11102. * @param HTMLPurifier_Context $context
  11103. * @return bool|string
  11104. */
  11105. public function validate($uri_string, $config, $context)
  11106. {
  11107. // parse the URI out of the string and then pass it onto
  11108. // the parent object
  11109. $uri_string = $this->parseCDATA($uri_string);
  11110. if (strpos($uri_string, 'url(') !== 0) {
  11111. return false;
  11112. }
  11113. $uri_string = substr($uri_string, 4);
  11114. if (strlen($uri_string) == 0) {
  11115. return false;
  11116. }
  11117. $new_length = strlen($uri_string) - 1;
  11118. if ($uri_string[$new_length] != ')') {
  11119. return false;
  11120. }
  11121. $uri = trim(substr($uri_string, 0, $new_length));
  11122. if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
  11123. $quote = $uri[0];
  11124. $new_length = strlen($uri) - 1;
  11125. if ($uri[$new_length] !== $quote) {
  11126. return false;
  11127. }
  11128. $uri = substr($uri, 1, $new_length - 1);
  11129. }
  11130. $uri = $this->expandCSSEscape($uri);
  11131. $result = parent::validate($uri, $config, $context);
  11132. if ($result === false) {
  11133. return false;
  11134. }
  11135. // extra sanity check; should have been done by URI
  11136. $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
  11137. // suspicious characters are ()'; we're going to percent encode
  11138. // them for safety.
  11139. $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
  11140. // there's an extra bug where ampersands lose their escaping on
  11141. // an innerHTML cycle, so a very unlucky query parameter could
  11142. // then change the meaning of the URL. Unfortunately, there's
  11143. // not much we can do about that...
  11144. return "url(\"$result\")";
  11145. }
  11146. }
  11147. /**
  11148. * Validates a boolean attribute
  11149. */
  11150. class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
  11151. {
  11152. /**
  11153. * @type bool
  11154. */
  11155. protected $name;
  11156. /**
  11157. * @type bool
  11158. */
  11159. public $minimized = true;
  11160. /**
  11161. * @param bool $name
  11162. */
  11163. public function __construct($name = false)
  11164. {
  11165. $this->name = $name;
  11166. }
  11167. /**
  11168. * @param string $string
  11169. * @param HTMLPurifier_Config $config
  11170. * @param HTMLPurifier_Context $context
  11171. * @return bool|string
  11172. */
  11173. public function validate($string, $config, $context)
  11174. {
  11175. return $this->name;
  11176. }
  11177. /**
  11178. * @param string $string Name of attribute
  11179. * @return HTMLPurifier_AttrDef_HTML_Bool
  11180. */
  11181. public function make($string)
  11182. {
  11183. return new HTMLPurifier_AttrDef_HTML_Bool($string);
  11184. }
  11185. }
  11186. /**
  11187. * Validates contents based on NMTOKENS attribute type.
  11188. */
  11189. class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
  11190. {
  11191. /**
  11192. * @param string $string
  11193. * @param HTMLPurifier_Config $config
  11194. * @param HTMLPurifier_Context $context
  11195. * @return bool|string
  11196. */
  11197. public function validate($string, $config, $context)
  11198. {
  11199. $string = trim($string);
  11200. // early abort: '' and '0' (strings that convert to false) are invalid
  11201. if (!$string) {
  11202. return false;
  11203. }
  11204. $tokens = $this->split($string, $config, $context);
  11205. $tokens = $this->filter($tokens, $config, $context);
  11206. if (empty($tokens)) {
  11207. return false;
  11208. }
  11209. return implode(' ', $tokens);
  11210. }
  11211. /**
  11212. * Splits a space separated list of tokens into its constituent parts.
  11213. * @param string $string
  11214. * @param HTMLPurifier_Config $config
  11215. * @param HTMLPurifier_Context $context
  11216. * @return array
  11217. */
  11218. protected function split($string, $config, $context)
  11219. {
  11220. // OPTIMIZABLE!
  11221. // do the preg_match, capture all subpatterns for reformulation
  11222. // we don't support U+00A1 and up codepoints or
  11223. // escaping because I don't know how to do that with regexps
  11224. // and plus it would complicate optimization efforts (you never
  11225. // see that anyway).
  11226. $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
  11227. '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
  11228. '(?:(?=\s)|\z)/'; // look ahead for space or string end
  11229. preg_match_all($pattern, $string, $matches);
  11230. return $matches[1];
  11231. }
  11232. /**
  11233. * Template method for removing certain tokens based on arbitrary criteria.
  11234. * @note If we wanted to be really functional, we'd do an array_filter
  11235. * with a callback. But... we're not.
  11236. * @param array $tokens
  11237. * @param HTMLPurifier_Config $config
  11238. * @param HTMLPurifier_Context $context
  11239. * @return array
  11240. */
  11241. protected function filter($tokens, $config, $context)
  11242. {
  11243. return $tokens;
  11244. }
  11245. }
  11246. /**
  11247. * Implements special behavior for class attribute (normally NMTOKENS)
  11248. */
  11249. class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
  11250. {
  11251. /**
  11252. * @param string $string
  11253. * @param HTMLPurifier_Config $config
  11254. * @param HTMLPurifier_Context $context
  11255. * @return bool|string
  11256. */
  11257. protected function split($string, $config, $context)
  11258. {
  11259. // really, this twiddle should be lazy loaded
  11260. $name = $config->getDefinition('HTML')->doctype->name;
  11261. if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
  11262. return parent::split($string, $config, $context);
  11263. } else {
  11264. return preg_split('/\s+/', $string);
  11265. }
  11266. }
  11267. /**
  11268. * @param array $tokens
  11269. * @param HTMLPurifier_Config $config
  11270. * @param HTMLPurifier_Context $context
  11271. * @return array
  11272. */
  11273. protected function filter($tokens, $config, $context)
  11274. {
  11275. $allowed = $config->get('Attr.AllowedClasses');
  11276. $forbidden = $config->get('Attr.ForbiddenClasses');
  11277. $ret = array();
  11278. foreach ($tokens as $token) {
  11279. if (($allowed === null || isset($allowed[$token])) &&
  11280. !isset($forbidden[$token]) &&
  11281. // We need this O(n) check because of PHP's array
  11282. // implementation that casts -0 to 0.
  11283. !in_array($token, $ret, true)
  11284. ) {
  11285. $ret[] = $token;
  11286. }
  11287. }
  11288. return $ret;
  11289. }
  11290. }
  11291. /**
  11292. * Validates a color according to the HTML spec.
  11293. */
  11294. class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
  11295. {
  11296. /**
  11297. * @param string $string
  11298. * @param HTMLPurifier_Config $config
  11299. * @param HTMLPurifier_Context $context
  11300. * @return bool|string
  11301. */
  11302. public function validate($string, $config, $context)
  11303. {
  11304. static $colors = null;
  11305. if ($colors === null) {
  11306. $colors = $config->get('Core.ColorKeywords');
  11307. }
  11308. $string = trim($string);
  11309. if (empty($string)) {
  11310. return false;
  11311. }
  11312. $lower = strtolower($string);
  11313. if (isset($colors[$lower])) {
  11314. return $colors[$lower];
  11315. }
  11316. if ($string[0] === '#') {
  11317. $hex = substr($string, 1);
  11318. } else {
  11319. $hex = $string;
  11320. }
  11321. $length = strlen($hex);
  11322. if ($length !== 3 && $length !== 6) {
  11323. return false;
  11324. }
  11325. if (!ctype_xdigit($hex)) {
  11326. return false;
  11327. }
  11328. if ($length === 3) {
  11329. $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
  11330. }
  11331. return "#$hex";
  11332. }
  11333. }
  11334. /**
  11335. * Special-case enum attribute definition that lazy loads allowed frame targets
  11336. */
  11337. class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
  11338. {
  11339. /**
  11340. * @type array
  11341. */
  11342. public $valid_values = false; // uninitialized value
  11343. /**
  11344. * @type bool
  11345. */
  11346. protected $case_sensitive = false;
  11347. public function __construct()
  11348. {
  11349. }
  11350. /**
  11351. * @param string $string
  11352. * @param HTMLPurifier_Config $config
  11353. * @param HTMLPurifier_Context $context
  11354. * @return bool|string
  11355. */
  11356. public function validate($string, $config, $context)
  11357. {
  11358. if ($this->valid_values === false) {
  11359. $this->valid_values = $config->get('Attr.AllowedFrameTargets');
  11360. }
  11361. return parent::validate($string, $config, $context);
  11362. }
  11363. }
  11364. /**
  11365. * Validates the HTML attribute ID.
  11366. * @warning Even though this is the id processor, it
  11367. * will ignore the directive Attr:IDBlacklist, since it will only
  11368. * go according to the ID accumulator. Since the accumulator is
  11369. * automatically generated, it will have already absorbed the
  11370. * blacklist. If you're hacking around, make sure you use load()!
  11371. */
  11372. class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
  11373. {
  11374. // selector is NOT a valid thing to use for IDREFs, because IDREFs
  11375. // *must* target IDs that exist, whereas selector #ids do not.
  11376. /**
  11377. * Determines whether or not we're validating an ID in a CSS
  11378. * selector context.
  11379. * @type bool
  11380. */
  11381. protected $selector;
  11382. /**
  11383. * @param bool $selector
  11384. */
  11385. public function __construct($selector = false)
  11386. {
  11387. $this->selector = $selector;
  11388. }
  11389. /**
  11390. * @param string $id
  11391. * @param HTMLPurifier_Config $config
  11392. * @param HTMLPurifier_Context $context
  11393. * @return bool|string
  11394. */
  11395. public function validate($id, $config, $context)
  11396. {
  11397. if (!$this->selector && !$config->get('Attr.EnableID')) {
  11398. return false;
  11399. }
  11400. $id = trim($id); // trim it first
  11401. if ($id === '') {
  11402. return false;
  11403. }
  11404. $prefix = $config->get('Attr.IDPrefix');
  11405. if ($prefix !== '') {
  11406. $prefix .= $config->get('Attr.IDPrefixLocal');
  11407. // prevent re-appending the prefix
  11408. if (strpos($id, $prefix) !== 0) {
  11409. $id = $prefix . $id;
  11410. }
  11411. } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
  11412. trigger_error(
  11413. '%Attr.IDPrefixLocal cannot be used unless ' .
  11414. '%Attr.IDPrefix is set',
  11415. E_USER_WARNING
  11416. );
  11417. }
  11418. if (!$this->selector) {
  11419. $id_accumulator =& $context->get('IDAccumulator');
  11420. if (isset($id_accumulator->ids[$id])) {
  11421. return false;
  11422. }
  11423. }
  11424. // we purposely avoid using regex, hopefully this is faster
  11425. if ($config->get('Attr.ID.HTML5') === true) {
  11426. if (preg_match('/[\t\n\x0b\x0c ]/', $id)) {
  11427. return false;
  11428. }
  11429. } else {
  11430. if (ctype_alpha($id)) {
  11431. // OK
  11432. } else {
  11433. if (!ctype_alpha(@$id[0])) {
  11434. return false;
  11435. }
  11436. // primitive style of regexps, I suppose
  11437. $trim = trim(
  11438. $id,
  11439. 'A..Za..z0..9:-._'
  11440. );
  11441. if ($trim !== '') {
  11442. return false;
  11443. }
  11444. }
  11445. }
  11446. $regexp = $config->get('Attr.IDBlacklistRegexp');
  11447. if ($regexp && preg_match($regexp, $id)) {
  11448. return false;
  11449. }
  11450. if (!$this->selector) {
  11451. $id_accumulator->add($id);
  11452. }
  11453. // if no change was made to the ID, return the result
  11454. // else, return the new id if stripping whitespace made it
  11455. // valid, or return false.
  11456. return $id;
  11457. }
  11458. }
  11459. /**
  11460. * Validates an integer representation of pixels according to the HTML spec.
  11461. */
  11462. class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
  11463. {
  11464. /**
  11465. * @type int
  11466. */
  11467. protected $max;
  11468. /**
  11469. * @param int $max
  11470. */
  11471. public function __construct($max = null)
  11472. {
  11473. $this->max = $max;
  11474. }
  11475. /**
  11476. * @param string $string
  11477. * @param HTMLPurifier_Config $config
  11478. * @param HTMLPurifier_Context $context
  11479. * @return bool|string
  11480. */
  11481. public function validate($string, $config, $context)
  11482. {
  11483. $string = trim($string);
  11484. if ($string === '0') {
  11485. return $string;
  11486. }
  11487. if ($string === '') {
  11488. return false;
  11489. }
  11490. $length = strlen($string);
  11491. if (substr($string, $length - 2) == 'px') {
  11492. $string = substr($string, 0, $length - 2);
  11493. }
  11494. if (!is_numeric($string)) {
  11495. return false;
  11496. }
  11497. $int = (int)$string;
  11498. if ($int < 0) {
  11499. return '0';
  11500. }
  11501. // upper-bound value, extremely high values can
  11502. // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
  11503. // WARNING, above link WILL crash you if you're using Windows
  11504. if ($this->max !== null && $int > $this->max) {
  11505. return (string)$this->max;
  11506. }
  11507. return (string)$int;
  11508. }
  11509. /**
  11510. * @param string $string
  11511. * @return HTMLPurifier_AttrDef
  11512. */
  11513. public function make($string)
  11514. {
  11515. if ($string === '') {
  11516. $max = null;
  11517. } else {
  11518. $max = (int)$string;
  11519. }
  11520. $class = get_class($this);
  11521. return new $class($max);
  11522. }
  11523. }
  11524. /**
  11525. * Validates the HTML type length (not to be confused with CSS's length).
  11526. *
  11527. * This accepts integer pixels or percentages as lengths for certain
  11528. * HTML attributes.
  11529. */
  11530. class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
  11531. {
  11532. /**
  11533. * @param string $string
  11534. * @param HTMLPurifier_Config $config
  11535. * @param HTMLPurifier_Context $context
  11536. * @return bool|string
  11537. */
  11538. public function validate($string, $config, $context)
  11539. {
  11540. $string = trim($string);
  11541. if ($string === '') {
  11542. return false;
  11543. }
  11544. $parent_result = parent::validate($string, $config, $context);
  11545. if ($parent_result !== false) {
  11546. return $parent_result;
  11547. }
  11548. $length = strlen($string);
  11549. $last_char = $string[$length - 1];
  11550. if ($last_char !== '%') {
  11551. return false;
  11552. }
  11553. $points = substr($string, 0, $length - 1);
  11554. if (!is_numeric($points)) {
  11555. return false;
  11556. }
  11557. $points = (int)$points;
  11558. if ($points < 0) {
  11559. return '0%';
  11560. }
  11561. if ($points > 100) {
  11562. return '100%';
  11563. }
  11564. return ((string)$points) . '%';
  11565. }
  11566. }
  11567. /**
  11568. * Validates a rel/rev link attribute against a directive of allowed values
  11569. * @note We cannot use Enum because link types allow multiple
  11570. * values.
  11571. * @note Assumes link types are ASCII text
  11572. */
  11573. class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
  11574. {
  11575. /**
  11576. * Name config attribute to pull.
  11577. * @type string
  11578. */
  11579. protected $name;
  11580. /**
  11581. * @param string $name
  11582. */
  11583. public function __construct($name)
  11584. {
  11585. $configLookup = array(
  11586. 'rel' => 'AllowedRel',
  11587. 'rev' => 'AllowedRev'
  11588. );
  11589. if (!isset($configLookup[$name])) {
  11590. trigger_error(
  11591. 'Unrecognized attribute name for link ' .
  11592. 'relationship.',
  11593. E_USER_ERROR
  11594. );
  11595. return;
  11596. }
  11597. $this->name = $configLookup[$name];
  11598. }
  11599. /**
  11600. * @param string $string
  11601. * @param HTMLPurifier_Config $config
  11602. * @param HTMLPurifier_Context $context
  11603. * @return bool|string
  11604. */
  11605. public function validate($string, $config, $context)
  11606. {
  11607. $allowed = $config->get('Attr.' . $this->name);
  11608. if (empty($allowed)) {
  11609. return false;
  11610. }
  11611. $string = $this->parseCDATA($string);
  11612. $parts = explode(' ', $string);
  11613. // lookup to prevent duplicates
  11614. $ret_lookup = array();
  11615. foreach ($parts as $part) {
  11616. $part = strtolower(trim($part));
  11617. if (!isset($allowed[$part])) {
  11618. continue;
  11619. }
  11620. $ret_lookup[$part] = true;
  11621. }
  11622. if (empty($ret_lookup)) {
  11623. return false;
  11624. }
  11625. $string = implode(' ', array_keys($ret_lookup));
  11626. return $string;
  11627. }
  11628. }
  11629. /**
  11630. * Validates a MultiLength as defined by the HTML spec.
  11631. *
  11632. * A multilength is either a integer (pixel count), a percentage, or
  11633. * a relative number.
  11634. */
  11635. class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
  11636. {
  11637. /**
  11638. * @param string $string
  11639. * @param HTMLPurifier_Config $config
  11640. * @param HTMLPurifier_Context $context
  11641. * @return bool|string
  11642. */
  11643. public function validate($string, $config, $context)
  11644. {
  11645. $string = trim($string);
  11646. if ($string === '') {
  11647. return false;
  11648. }
  11649. $parent_result = parent::validate($string, $config, $context);
  11650. if ($parent_result !== false) {
  11651. return $parent_result;
  11652. }
  11653. $length = strlen($string);
  11654. $last_char = $string[$length - 1];
  11655. if ($last_char !== '*') {
  11656. return false;
  11657. }
  11658. $int = substr($string, 0, $length - 1);
  11659. if ($int == '') {
  11660. return '*';
  11661. }
  11662. if (!is_numeric($int)) {
  11663. return false;
  11664. }
  11665. $int = (int)$int;
  11666. if ($int < 0) {
  11667. return false;
  11668. }
  11669. if ($int == 0) {
  11670. return '0';
  11671. }
  11672. if ($int == 1) {
  11673. return '*';
  11674. }
  11675. return ((string)$int) . '*';
  11676. }
  11677. }
  11678. abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
  11679. {
  11680. /**
  11681. * Unpacks a mailbox into its display-name and address
  11682. * @param string $string
  11683. * @return mixed
  11684. */
  11685. public function unpack($string)
  11686. {
  11687. // needs to be implemented
  11688. }
  11689. }
  11690. // sub-implementations
  11691. /**
  11692. * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
  11693. */
  11694. class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
  11695. {
  11696. /**
  11697. * IPv4 sub-validator.
  11698. * @type HTMLPurifier_AttrDef_URI_IPv4
  11699. */
  11700. protected $ipv4;
  11701. /**
  11702. * IPv6 sub-validator.
  11703. * @type HTMLPurifier_AttrDef_URI_IPv6
  11704. */
  11705. protected $ipv6;
  11706. public function __construct()
  11707. {
  11708. $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
  11709. $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
  11710. }
  11711. /**
  11712. * @param string $string
  11713. * @param HTMLPurifier_Config $config
  11714. * @param HTMLPurifier_Context $context
  11715. * @return bool|string
  11716. */
  11717. public function validate($string, $config, $context)
  11718. {
  11719. $length = strlen($string);
  11720. // empty hostname is OK; it's usually semantically equivalent:
  11721. // the default host as defined by a URI scheme is used:
  11722. //
  11723. // If the URI scheme defines a default for host, then that
  11724. // default applies when the host subcomponent is undefined
  11725. // or when the registered name is empty (zero length).
  11726. if ($string === '') {
  11727. return '';
  11728. }
  11729. if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
  11730. //IPv6
  11731. $ip = substr($string, 1, $length - 2);
  11732. $valid = $this->ipv6->validate($ip, $config, $context);
  11733. if ($valid === false) {
  11734. return false;
  11735. }
  11736. return '[' . $valid . ']';
  11737. }
  11738. // need to do checks on unusual encodings too
  11739. $ipv4 = $this->ipv4->validate($string, $config, $context);
  11740. if ($ipv4 !== false) {
  11741. return $ipv4;
  11742. }
  11743. // A regular domain name.
  11744. // This doesn't match I18N domain names, but we don't have proper IRI support,
  11745. // so force users to insert Punycode.
  11746. // There is not a good sense in which underscores should be
  11747. // allowed, since it's technically not! (And if you go as
  11748. // far to allow everything as specified by the DNS spec...
  11749. // well, that's literally everything, modulo some space limits
  11750. // for the components and the overall name (which, by the way,
  11751. // we are NOT checking!). So we (arbitrarily) decide this:
  11752. // let's allow underscores wherever we would have allowed
  11753. // hyphens, if they are enabled. This is a pretty good match
  11754. // for browser behavior, for example, a large number of browsers
  11755. // cannot handle foo_.example.com, but foo_bar.example.com is
  11756. // fairly well supported.
  11757. $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
  11758. // Based off of RFC 1738, but amended so that
  11759. // as per RFC 3696, the top label need only not be all numeric.
  11760. // The productions describing this are:
  11761. $a = '[a-z]'; // alpha
  11762. $an = '[a-z0-9]'; // alphanum
  11763. $and = "[a-z0-9-$underscore]"; // alphanum | "-"
  11764. // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
  11765. $domainlabel = "$an(?:$and*$an)?";
  11766. // AMENDED as per RFC 3696
  11767. // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum
  11768. // side condition: not all numeric
  11769. $toplabel = "$an(?:$and*$an)?";
  11770. // hostname = *( domainlabel "." ) toplabel [ "." ]
  11771. if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) {
  11772. if (!ctype_digit($matches[1])) {
  11773. return $string;
  11774. }
  11775. }
  11776. // PHP 5.3 and later support this functionality natively
  11777. if (function_exists('idn_to_ascii')) {
  11778. $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
  11779. // If we have Net_IDNA2 support, we can support IRIs by
  11780. // punycoding them. (This is the most portable thing to do,
  11781. // since otherwise we have to assume browsers support
  11782. } elseif ($config->get('Core.EnableIDNA')) {
  11783. $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
  11784. // we need to encode each period separately
  11785. $parts = explode('.', $string);
  11786. try {
  11787. $new_parts = array();
  11788. foreach ($parts as $part) {
  11789. $encodable = false;
  11790. for ($i = 0, $c = strlen($part); $i < $c; $i++) {
  11791. if (ord($part[$i]) > 0x7a) {
  11792. $encodable = true;
  11793. break;
  11794. }
  11795. }
  11796. if (!$encodable) {
  11797. $new_parts[] = $part;
  11798. } else {
  11799. $new_parts[] = $idna->encode($part);
  11800. }
  11801. }
  11802. $string = implode('.', $new_parts);
  11803. } catch (Exception $e) {
  11804. // XXX error reporting
  11805. }
  11806. }
  11807. // Try again
  11808. if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
  11809. return $string;
  11810. }
  11811. return false;
  11812. }
  11813. }
  11814. /**
  11815. * Validates an IPv4 address
  11816. * @author Feyd @ forums.devnetwork.net (public domain)
  11817. */
  11818. class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
  11819. {
  11820. /**
  11821. * IPv4 regex, protected so that IPv6 can reuse it.
  11822. * @type string
  11823. */
  11824. protected $ip4;
  11825. /**
  11826. * @param string $aIP
  11827. * @param HTMLPurifier_Config $config
  11828. * @param HTMLPurifier_Context $context
  11829. * @return bool|string
  11830. */
  11831. public function validate($aIP, $config, $context)
  11832. {
  11833. if (!$this->ip4) {
  11834. $this->_loadRegex();
  11835. }
  11836. if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
  11837. return $aIP;
  11838. }
  11839. return false;
  11840. }
  11841. /**
  11842. * Lazy load function to prevent regex from being stuffed in
  11843. * cache.
  11844. */
  11845. protected function _loadRegex()
  11846. {
  11847. $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
  11848. $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
  11849. }
  11850. }
  11851. /**
  11852. * Validates an IPv6 address.
  11853. * @author Feyd @ forums.devnetwork.net (public domain)
  11854. * @note This function requires brackets to have been removed from address
  11855. * in URI.
  11856. */
  11857. class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
  11858. {
  11859. /**
  11860. * @param string $aIP
  11861. * @param HTMLPurifier_Config $config
  11862. * @param HTMLPurifier_Context $context
  11863. * @return bool|string
  11864. */
  11865. public function validate($aIP, $config, $context)
  11866. {
  11867. if (!$this->ip4) {
  11868. $this->_loadRegex();
  11869. }
  11870. $original = $aIP;
  11871. $hex = '[0-9a-fA-F]';
  11872. $blk = '(?:' . $hex . '{1,4})';
  11873. $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
  11874. // prefix check
  11875. if (strpos($aIP, '/') !== false) {
  11876. if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
  11877. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11878. unset($find);
  11879. } else {
  11880. return false;
  11881. }
  11882. }
  11883. // IPv4-compatiblity check
  11884. if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
  11885. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11886. $ip = explode('.', $find[0]);
  11887. $ip = array_map('dechex', $ip);
  11888. $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
  11889. unset($find, $ip);
  11890. }
  11891. // compression check
  11892. $aIP = explode('::', $aIP);
  11893. $c = count($aIP);
  11894. if ($c > 2) {
  11895. return false;
  11896. } elseif ($c == 2) {
  11897. list($first, $second) = $aIP;
  11898. $first = explode(':', $first);
  11899. $second = explode(':', $second);
  11900. if (count($first) + count($second) > 8) {
  11901. return false;
  11902. }
  11903. while (count($first) < 8) {
  11904. array_push($first, '0');
  11905. }
  11906. array_splice($first, 8 - count($second), 8, $second);
  11907. $aIP = $first;
  11908. unset($first, $second);
  11909. } else {
  11910. $aIP = explode(':', $aIP[0]);
  11911. }
  11912. $c = count($aIP);
  11913. if ($c != 8) {
  11914. return false;
  11915. }
  11916. // All the pieces should be 16-bit hex strings. Are they?
  11917. foreach ($aIP as $piece) {
  11918. if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
  11919. return false;
  11920. }
  11921. }
  11922. return $original;
  11923. }
  11924. }
  11925. /**
  11926. * Primitive email validation class based on the regexp found at
  11927. * http://www.regular-expressions.info/email.html
  11928. */
  11929. class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
  11930. {
  11931. /**
  11932. * @param string $string
  11933. * @param HTMLPurifier_Config $config
  11934. * @param HTMLPurifier_Context $context
  11935. * @return bool|string
  11936. */
  11937. public function validate($string, $config, $context)
  11938. {
  11939. // no support for named mailboxes i.e. "Bob <bob@example.com>"
  11940. // that needs more percent encoding to be done
  11941. if ($string == '') {
  11942. return false;
  11943. }
  11944. $string = trim($string);
  11945. $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
  11946. return $result ? $string : false;
  11947. }
  11948. }
  11949. /**
  11950. * Pre-transform that changes proprietary background attribute to CSS.
  11951. */
  11952. class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
  11953. {
  11954. /**
  11955. * @param array $attr
  11956. * @param HTMLPurifier_Config $config
  11957. * @param HTMLPurifier_Context $context
  11958. * @return array
  11959. */
  11960. public function transform($attr, $config, $context)
  11961. {
  11962. if (!isset($attr['background'])) {
  11963. return $attr;
  11964. }
  11965. $background = $this->confiscateAttr($attr, 'background');
  11966. // some validation should happen here
  11967. $this->prependCSS($attr, "background-image:url($background);");
  11968. return $attr;
  11969. }
  11970. }
  11971. // this MUST be placed in post, as it assumes that any value in dir is valid
  11972. /**
  11973. * Post-trasnform that ensures that bdo tags have the dir attribute set.
  11974. */
  11975. class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
  11976. {
  11977. /**
  11978. * @param array $attr
  11979. * @param HTMLPurifier_Config $config
  11980. * @param HTMLPurifier_Context $context
  11981. * @return array
  11982. */
  11983. public function transform($attr, $config, $context)
  11984. {
  11985. if (isset($attr['dir'])) {
  11986. return $attr;
  11987. }
  11988. $attr['dir'] = $config->get('Attr.DefaultTextDir');
  11989. return $attr;
  11990. }
  11991. }
  11992. /**
  11993. * Pre-transform that changes deprecated bgcolor attribute to CSS.
  11994. */
  11995. class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
  11996. {
  11997. /**
  11998. * @param array $attr
  11999. * @param HTMLPurifier_Config $config
  12000. * @param HTMLPurifier_Context $context
  12001. * @return array
  12002. */
  12003. public function transform($attr, $config, $context)
  12004. {
  12005. if (!isset($attr['bgcolor'])) {
  12006. return $attr;
  12007. }
  12008. $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
  12009. // some validation should happen here
  12010. $this->prependCSS($attr, "background-color:$bgcolor;");
  12011. return $attr;
  12012. }
  12013. }
  12014. /**
  12015. * Pre-transform that changes converts a boolean attribute to fixed CSS
  12016. */
  12017. class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
  12018. {
  12019. /**
  12020. * Name of boolean attribute that is trigger.
  12021. * @type string
  12022. */
  12023. protected $attr;
  12024. /**
  12025. * CSS declarations to add to style, needs trailing semicolon.
  12026. * @type string
  12027. */
  12028. protected $css;
  12029. /**
  12030. * @param string $attr attribute name to convert from
  12031. * @param string $css CSS declarations to add to style (needs semicolon)
  12032. */
  12033. public function __construct($attr, $css)
  12034. {
  12035. $this->attr = $attr;
  12036. $this->css = $css;
  12037. }
  12038. /**
  12039. * @param array $attr
  12040. * @param HTMLPurifier_Config $config
  12041. * @param HTMLPurifier_Context $context
  12042. * @return array
  12043. */
  12044. public function transform($attr, $config, $context)
  12045. {
  12046. if (!isset($attr[$this->attr])) {
  12047. return $attr;
  12048. }
  12049. unset($attr[$this->attr]);
  12050. $this->prependCSS($attr, $this->css);
  12051. return $attr;
  12052. }
  12053. }
  12054. /**
  12055. * Pre-transform that changes deprecated border attribute to CSS.
  12056. */
  12057. class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
  12058. {
  12059. /**
  12060. * @param array $attr
  12061. * @param HTMLPurifier_Config $config
  12062. * @param HTMLPurifier_Context $context
  12063. * @return array
  12064. */
  12065. public function transform($attr, $config, $context)
  12066. {
  12067. if (!isset($attr['border'])) {
  12068. return $attr;
  12069. }
  12070. $border_width = $this->confiscateAttr($attr, 'border');
  12071. // some validation should happen here
  12072. $this->prependCSS($attr, "border:{$border_width}px solid;");
  12073. return $attr;
  12074. }
  12075. }
  12076. /**
  12077. * Generic pre-transform that converts an attribute with a fixed number of
  12078. * values (enumerated) to CSS.
  12079. */
  12080. class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
  12081. {
  12082. /**
  12083. * Name of attribute to transform from.
  12084. * @type string
  12085. */
  12086. protected $attr;
  12087. /**
  12088. * Lookup array of attribute values to CSS.
  12089. * @type array
  12090. */
  12091. protected $enumToCSS = array();
  12092. /**
  12093. * Case sensitivity of the matching.
  12094. * @type bool
  12095. * @warning Currently can only be guaranteed to work with ASCII
  12096. * values.
  12097. */
  12098. protected $caseSensitive = false;
  12099. /**
  12100. * @param string $attr Attribute name to transform from
  12101. * @param array $enum_to_css Lookup array of attribute values to CSS
  12102. * @param bool $case_sensitive Case sensitivity indicator, default false
  12103. */
  12104. public function __construct($attr, $enum_to_css, $case_sensitive = false)
  12105. {
  12106. $this->attr = $attr;
  12107. $this->enumToCSS = $enum_to_css;
  12108. $this->caseSensitive = (bool)$case_sensitive;
  12109. }
  12110. /**
  12111. * @param array $attr
  12112. * @param HTMLPurifier_Config $config
  12113. * @param HTMLPurifier_Context $context
  12114. * @return array
  12115. */
  12116. public function transform($attr, $config, $context)
  12117. {
  12118. if (!isset($attr[$this->attr])) {
  12119. return $attr;
  12120. }
  12121. $value = trim($attr[$this->attr]);
  12122. unset($attr[$this->attr]);
  12123. if (!$this->caseSensitive) {
  12124. $value = strtolower($value);
  12125. }
  12126. if (!isset($this->enumToCSS[$value])) {
  12127. return $attr;
  12128. }
  12129. $this->prependCSS($attr, $this->enumToCSS[$value]);
  12130. return $attr;
  12131. }
  12132. }
  12133. // must be called POST validation
  12134. /**
  12135. * Transform that supplies default values for the src and alt attributes
  12136. * in img tags, as well as prevents the img tag from being removed
  12137. * because of a missing alt tag. This needs to be registered as both
  12138. * a pre and post attribute transform.
  12139. */
  12140. class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
  12141. {
  12142. /**
  12143. * @param array $attr
  12144. * @param HTMLPurifier_Config $config
  12145. * @param HTMLPurifier_Context $context
  12146. * @return array
  12147. */
  12148. public function transform($attr, $config, $context)
  12149. {
  12150. $src = true;
  12151. if (!isset($attr['src'])) {
  12152. if ($config->get('Core.RemoveInvalidImg')) {
  12153. return $attr;
  12154. }
  12155. $attr['src'] = $config->get('Attr.DefaultInvalidImage');
  12156. $src = false;
  12157. }
  12158. if (!isset($attr['alt'])) {
  12159. if ($src) {
  12160. $alt = $config->get('Attr.DefaultImageAlt');
  12161. if ($alt === null) {
  12162. $attr['alt'] = basename($attr['src']);
  12163. } else {
  12164. $attr['alt'] = $alt;
  12165. }
  12166. } else {
  12167. $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
  12168. }
  12169. }
  12170. return $attr;
  12171. }
  12172. }
  12173. /**
  12174. * Pre-transform that changes deprecated hspace and vspace attributes to CSS
  12175. */
  12176. class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
  12177. {
  12178. /**
  12179. * @type string
  12180. */
  12181. protected $attr;
  12182. /**
  12183. * @type array
  12184. */
  12185. protected $css = array(
  12186. 'hspace' => array('left', 'right'),
  12187. 'vspace' => array('top', 'bottom')
  12188. );
  12189. /**
  12190. * @param string $attr
  12191. */
  12192. public function __construct($attr)
  12193. {
  12194. $this->attr = $attr;
  12195. if (!isset($this->css[$attr])) {
  12196. trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
  12197. }
  12198. }
  12199. /**
  12200. * @param array $attr
  12201. * @param HTMLPurifier_Config $config
  12202. * @param HTMLPurifier_Context $context
  12203. * @return array
  12204. */
  12205. public function transform($attr, $config, $context)
  12206. {
  12207. if (!isset($attr[$this->attr])) {
  12208. return $attr;
  12209. }
  12210. $width = $this->confiscateAttr($attr, $this->attr);
  12211. // some validation could happen here
  12212. if (!isset($this->css[$this->attr])) {
  12213. return $attr;
  12214. }
  12215. $style = '';
  12216. foreach ($this->css[$this->attr] as $suffix) {
  12217. $property = "margin-$suffix";
  12218. $style .= "$property:{$width}px;";
  12219. }
  12220. $this->prependCSS($attr, $style);
  12221. return $attr;
  12222. }
  12223. }
  12224. /**
  12225. * Performs miscellaneous cross attribute validation and filtering for
  12226. * input elements. This is meant to be a post-transform.
  12227. */
  12228. class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
  12229. {
  12230. /**
  12231. * @type HTMLPurifier_AttrDef_HTML_Pixels
  12232. */
  12233. protected $pixels;
  12234. public function __construct()
  12235. {
  12236. $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
  12237. }
  12238. /**
  12239. * @param array $attr
  12240. * @param HTMLPurifier_Config $config
  12241. * @param HTMLPurifier_Context $context
  12242. * @return array
  12243. */
  12244. public function transform($attr, $config, $context)
  12245. {
  12246. if (!isset($attr['type'])) {
  12247. $t = 'text';
  12248. } else {
  12249. $t = strtolower($attr['type']);
  12250. }
  12251. if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
  12252. unset($attr['checked']);
  12253. }
  12254. if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
  12255. unset($attr['maxlength']);
  12256. }
  12257. if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
  12258. $result = $this->pixels->validate($attr['size'], $config, $context);
  12259. if ($result === false) {
  12260. unset($attr['size']);
  12261. } else {
  12262. $attr['size'] = $result;
  12263. }
  12264. }
  12265. if (isset($attr['src']) && $t !== 'image') {
  12266. unset($attr['src']);
  12267. }
  12268. if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
  12269. $attr['value'] = '';
  12270. }
  12271. return $attr;
  12272. }
  12273. }
  12274. /**
  12275. * Post-transform that copies lang's value to xml:lang (and vice-versa)
  12276. * @note Theoretically speaking, this could be a pre-transform, but putting
  12277. * post is more efficient.
  12278. */
  12279. class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
  12280. {
  12281. /**
  12282. * @param array $attr
  12283. * @param HTMLPurifier_Config $config
  12284. * @param HTMLPurifier_Context $context
  12285. * @return array
  12286. */
  12287. public function transform($attr, $config, $context)
  12288. {
  12289. $lang = isset($attr['lang']) ? $attr['lang'] : false;
  12290. $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
  12291. if ($lang !== false && $xml_lang === false) {
  12292. $attr['xml:lang'] = $lang;
  12293. } elseif ($xml_lang !== false) {
  12294. $attr['lang'] = $xml_lang;
  12295. }
  12296. return $attr;
  12297. }
  12298. }
  12299. /**
  12300. * Class for handling width/height length attribute transformations to CSS
  12301. */
  12302. class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
  12303. {
  12304. /**
  12305. * @type string
  12306. */
  12307. protected $name;
  12308. /**
  12309. * @type string
  12310. */
  12311. protected $cssName;
  12312. public function __construct($name, $css_name = null)
  12313. {
  12314. $this->name = $name;
  12315. $this->cssName = $css_name ? $css_name : $name;
  12316. }
  12317. /**
  12318. * @param array $attr
  12319. * @param HTMLPurifier_Config $config
  12320. * @param HTMLPurifier_Context $context
  12321. * @return array
  12322. */
  12323. public function transform($attr, $config, $context)
  12324. {
  12325. if (!isset($attr[$this->name])) {
  12326. return $attr;
  12327. }
  12328. $length = $this->confiscateAttr($attr, $this->name);
  12329. if (ctype_digit($length)) {
  12330. $length .= 'px';
  12331. }
  12332. $this->prependCSS($attr, $this->cssName . ":$length;");
  12333. return $attr;
  12334. }
  12335. }
  12336. /**
  12337. * Pre-transform that changes deprecated name attribute to ID if necessary
  12338. */
  12339. class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
  12340. {
  12341. /**
  12342. * @param array $attr
  12343. * @param HTMLPurifier_Config $config
  12344. * @param HTMLPurifier_Context $context
  12345. * @return array
  12346. */
  12347. public function transform($attr, $config, $context)
  12348. {
  12349. // Abort early if we're using relaxed definition of name
  12350. if ($config->get('HTML.Attr.Name.UseCDATA')) {
  12351. return $attr;
  12352. }
  12353. if (!isset($attr['name'])) {
  12354. return $attr;
  12355. }
  12356. $id = $this->confiscateAttr($attr, 'name');
  12357. if (isset($attr['id'])) {
  12358. return $attr;
  12359. }
  12360. $attr['id'] = $id;
  12361. return $attr;
  12362. }
  12363. }
  12364. /**
  12365. * Post-transform that performs validation to the name attribute; if
  12366. * it is present with an equivalent id attribute, it is passed through;
  12367. * otherwise validation is performed.
  12368. */
  12369. class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
  12370. {
  12371. public function __construct()
  12372. {
  12373. $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
  12374. }
  12375. /**
  12376. * @param array $attr
  12377. * @param HTMLPurifier_Config $config
  12378. * @param HTMLPurifier_Context $context
  12379. * @return array
  12380. */
  12381. public function transform($attr, $config, $context)
  12382. {
  12383. if (!isset($attr['name'])) {
  12384. return $attr;
  12385. }
  12386. $name = $attr['name'];
  12387. if (isset($attr['id']) && $attr['id'] === $name) {
  12388. return $attr;
  12389. }
  12390. $result = $this->idDef->validate($name, $config, $context);
  12391. if ($result === false) {
  12392. unset($attr['name']);
  12393. } else {
  12394. $attr['name'] = $result;
  12395. }
  12396. return $attr;
  12397. }
  12398. }
  12399. // must be called POST validation
  12400. /**
  12401. * Adds rel="nofollow" to all outbound links. This transform is
  12402. * only attached if Attr.Nofollow is TRUE.
  12403. */
  12404. class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
  12405. {
  12406. /**
  12407. * @type HTMLPurifier_URIParser
  12408. */
  12409. private $parser;
  12410. public function __construct()
  12411. {
  12412. $this->parser = new HTMLPurifier_URIParser();
  12413. }
  12414. /**
  12415. * @param array $attr
  12416. * @param HTMLPurifier_Config $config
  12417. * @param HTMLPurifier_Context $context
  12418. * @return array
  12419. */
  12420. public function transform($attr, $config, $context)
  12421. {
  12422. if (!isset($attr['href'])) {
  12423. return $attr;
  12424. }
  12425. // XXX Kind of inefficient
  12426. $url = $this->parser->parse($attr['href']);
  12427. $scheme = $url->getSchemeObj($config, $context);
  12428. if ($scheme->browsable && !$url->isLocal($config, $context)) {
  12429. if (isset($attr['rel'])) {
  12430. $rels = explode(' ', $attr['rel']);
  12431. if (!in_array('nofollow', $rels)) {
  12432. $rels[] = 'nofollow';
  12433. }
  12434. $attr['rel'] = implode(' ', $rels);
  12435. } else {
  12436. $attr['rel'] = 'nofollow';
  12437. }
  12438. }
  12439. return $attr;
  12440. }
  12441. }
  12442. class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
  12443. {
  12444. /**
  12445. * @type string
  12446. */
  12447. public $name = "SafeEmbed";
  12448. /**
  12449. * @param array $attr
  12450. * @param HTMLPurifier_Config $config
  12451. * @param HTMLPurifier_Context $context
  12452. * @return array
  12453. */
  12454. public function transform($attr, $config, $context)
  12455. {
  12456. $attr['allowscriptaccess'] = 'never';
  12457. $attr['allownetworking'] = 'internal';
  12458. $attr['type'] = 'application/x-shockwave-flash';
  12459. return $attr;
  12460. }
  12461. }
  12462. /**
  12463. * Writes default type for all objects. Currently only supports flash.
  12464. */
  12465. class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
  12466. {
  12467. /**
  12468. * @type string
  12469. */
  12470. public $name = "SafeObject";
  12471. /**
  12472. * @param array $attr
  12473. * @param HTMLPurifier_Config $config
  12474. * @param HTMLPurifier_Context $context
  12475. * @return array
  12476. */
  12477. public function transform($attr, $config, $context)
  12478. {
  12479. if (!isset($attr['type'])) {
  12480. $attr['type'] = 'application/x-shockwave-flash';
  12481. }
  12482. return $attr;
  12483. }
  12484. }
  12485. /**
  12486. * Validates name/value pairs in param tags to be used in safe objects. This
  12487. * will only allow name values it recognizes, and pre-fill certain attributes
  12488. * with required values.
  12489. *
  12490. * @note
  12491. * This class only supports Flash. In the future, Quicktime support
  12492. * may be added.
  12493. *
  12494. * @warning
  12495. * This class expects an injector to add the necessary parameters tags.
  12496. */
  12497. class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
  12498. {
  12499. /**
  12500. * @type string
  12501. */
  12502. public $name = "SafeParam";
  12503. /**
  12504. * @type HTMLPurifier_AttrDef_URI
  12505. */
  12506. private $uri;
  12507. public function __construct()
  12508. {
  12509. $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
  12510. $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
  12511. }
  12512. /**
  12513. * @param array $attr
  12514. * @param HTMLPurifier_Config $config
  12515. * @param HTMLPurifier_Context $context
  12516. * @return array
  12517. */
  12518. public function transform($attr, $config, $context)
  12519. {
  12520. // If we add support for other objects, we'll need to alter the
  12521. // transforms.
  12522. switch ($attr['name']) {
  12523. // application/x-shockwave-flash
  12524. // Keep this synchronized with Injector/SafeObject.php
  12525. case 'allowScriptAccess':
  12526. $attr['value'] = 'never';
  12527. break;
  12528. case 'allowNetworking':
  12529. $attr['value'] = 'internal';
  12530. break;
  12531. case 'allowFullScreen':
  12532. if ($config->get('HTML.FlashAllowFullScreen')) {
  12533. $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
  12534. } else {
  12535. $attr['value'] = 'false';
  12536. }
  12537. break;
  12538. case 'wmode':
  12539. $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
  12540. break;
  12541. case 'movie':
  12542. case 'src':
  12543. $attr['name'] = "movie";
  12544. $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
  12545. break;
  12546. case 'flashvars':
  12547. // we're going to allow arbitrary inputs to the SWF, on
  12548. // the reasoning that it could only hack the SWF, not us.
  12549. break;
  12550. // add other cases to support other param name/value pairs
  12551. default:
  12552. $attr['name'] = $attr['value'] = null;
  12553. }
  12554. return $attr;
  12555. }
  12556. }
  12557. /**
  12558. * Implements required attribute stipulation for <script>
  12559. */
  12560. class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
  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 (!isset($attr['type'])) {
  12571. $attr['type'] = 'text/javascript';
  12572. }
  12573. return $attr;
  12574. }
  12575. }
  12576. // must be called POST validation
  12577. /**
  12578. * Adds target="blank" to all outbound links. This transform is
  12579. * only attached if Attr.TargetBlank is TRUE. This works regardless
  12580. * of whether or not Attr.AllowedFrameTargets
  12581. */
  12582. class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
  12583. {
  12584. /**
  12585. * @type HTMLPurifier_URIParser
  12586. */
  12587. private $parser;
  12588. public function __construct()
  12589. {
  12590. $this->parser = new HTMLPurifier_URIParser();
  12591. }
  12592. /**
  12593. * @param array $attr
  12594. * @param HTMLPurifier_Config $config
  12595. * @param HTMLPurifier_Context $context
  12596. * @return array
  12597. */
  12598. public function transform($attr, $config, $context)
  12599. {
  12600. if (!isset($attr['href'])) {
  12601. return $attr;
  12602. }
  12603. // XXX Kind of inefficient
  12604. $url = $this->parser->parse($attr['href']);
  12605. $scheme = $url->getSchemeObj($config, $context);
  12606. if ($scheme->browsable && !$url->isBenign($config, $context)) {
  12607. $attr['target'] = '_blank';
  12608. }
  12609. return $attr;
  12610. }
  12611. }
  12612. // must be called POST validation
  12613. /**
  12614. * Adds rel="noopener" to any links which target a different window
  12615. * than the current one. This is used to prevent malicious websites
  12616. * from silently replacing the original window, which could be used
  12617. * to do phishing.
  12618. * This transform is controlled by %HTML.TargetNoopener.
  12619. */
  12620. class HTMLPurifier_AttrTransform_TargetNoopener extends HTMLPurifier_AttrTransform
  12621. {
  12622. /**
  12623. * @param array $attr
  12624. * @param HTMLPurifier_Config $config
  12625. * @param HTMLPurifier_Context $context
  12626. * @return array
  12627. */
  12628. public function transform($attr, $config, $context)
  12629. {
  12630. if (isset($attr['rel'])) {
  12631. $rels = explode(' ', $attr['rel']);
  12632. } else {
  12633. $rels = array();
  12634. }
  12635. if (isset($attr['target']) && !in_array('noopener', $rels)) {
  12636. $rels[] = 'noopener';
  12637. }
  12638. if (!empty($rels) || isset($attr['rel'])) {
  12639. $attr['rel'] = implode(' ', $rels);
  12640. }
  12641. return $attr;
  12642. }
  12643. }
  12644. // must be called POST validation
  12645. /**
  12646. * Adds rel="noreferrer" to any links which target a different window
  12647. * than the current one. This is used to prevent malicious websites
  12648. * from silently replacing the original window, which could be used
  12649. * to do phishing.
  12650. * This transform is controlled by %HTML.TargetNoreferrer.
  12651. */
  12652. class HTMLPurifier_AttrTransform_TargetNoreferrer extends HTMLPurifier_AttrTransform
  12653. {
  12654. /**
  12655. * @param array $attr
  12656. * @param HTMLPurifier_Config $config
  12657. * @param HTMLPurifier_Context $context
  12658. * @return array
  12659. */
  12660. public function transform($attr, $config, $context)
  12661. {
  12662. if (isset($attr['rel'])) {
  12663. $rels = explode(' ', $attr['rel']);
  12664. } else {
  12665. $rels = array();
  12666. }
  12667. if (isset($attr['target']) && !in_array('noreferrer', $rels)) {
  12668. $rels[] = 'noreferrer';
  12669. }
  12670. if (!empty($rels) || isset($attr['rel'])) {
  12671. $attr['rel'] = implode(' ', $rels);
  12672. }
  12673. return $attr;
  12674. }
  12675. }
  12676. /**
  12677. * Sets height/width defaults for <textarea>
  12678. */
  12679. class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
  12680. {
  12681. /**
  12682. * @param array $attr
  12683. * @param HTMLPurifier_Config $config
  12684. * @param HTMLPurifier_Context $context
  12685. * @return array
  12686. */
  12687. public function transform($attr, $config, $context)
  12688. {
  12689. // Calculated from Firefox
  12690. if (!isset($attr['cols'])) {
  12691. $attr['cols'] = '22';
  12692. }
  12693. if (!isset($attr['rows'])) {
  12694. $attr['rows'] = '3';
  12695. }
  12696. return $attr;
  12697. }
  12698. }
  12699. /**
  12700. * Definition that uses different definitions depending on context.
  12701. *
  12702. * The del and ins tags are notable because they allow different types of
  12703. * elements depending on whether or not they're in a block or inline context.
  12704. * Chameleon allows this behavior to happen by using two different
  12705. * definitions depending on context. While this somewhat generalized,
  12706. * it is specifically intended for those two tags.
  12707. */
  12708. class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
  12709. {
  12710. /**
  12711. * Instance of the definition object to use when inline. Usually stricter.
  12712. * @type HTMLPurifier_ChildDef_Optional
  12713. */
  12714. public $inline;
  12715. /**
  12716. * Instance of the definition object to use when block.
  12717. * @type HTMLPurifier_ChildDef_Optional
  12718. */
  12719. public $block;
  12720. /**
  12721. * @type string
  12722. */
  12723. public $type = 'chameleon';
  12724. /**
  12725. * @param array $inline List of elements to allow when inline.
  12726. * @param array $block List of elements to allow when block.
  12727. */
  12728. public function __construct($inline, $block)
  12729. {
  12730. $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
  12731. $this->block = new HTMLPurifier_ChildDef_Optional($block);
  12732. $this->elements = $this->block->elements;
  12733. }
  12734. /**
  12735. * @param HTMLPurifier_Node[] $children
  12736. * @param HTMLPurifier_Config $config
  12737. * @param HTMLPurifier_Context $context
  12738. * @return bool
  12739. */
  12740. public function validateChildren($children, $config, $context)
  12741. {
  12742. if ($context->get('IsInline') === false) {
  12743. return $this->block->validateChildren(
  12744. $children,
  12745. $config,
  12746. $context
  12747. );
  12748. } else {
  12749. return $this->inline->validateChildren(
  12750. $children,
  12751. $config,
  12752. $context
  12753. );
  12754. }
  12755. }
  12756. }
  12757. /**
  12758. * Custom validation class, accepts DTD child definitions
  12759. *
  12760. * @warning Currently this class is an all or nothing proposition, that is,
  12761. * it will only give a bool return value.
  12762. */
  12763. class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
  12764. {
  12765. /**
  12766. * @type string
  12767. */
  12768. public $type = 'custom';
  12769. /**
  12770. * @type bool
  12771. */
  12772. public $allow_empty = false;
  12773. /**
  12774. * Allowed child pattern as defined by the DTD.
  12775. * @type string
  12776. */
  12777. public $dtd_regex;
  12778. /**
  12779. * PCRE regex derived from $dtd_regex.
  12780. * @type string
  12781. */
  12782. private $_pcre_regex;
  12783. /**
  12784. * @param $dtd_regex Allowed child pattern from the DTD
  12785. */
  12786. public function __construct($dtd_regex)
  12787. {
  12788. $this->dtd_regex = $dtd_regex;
  12789. $this->_compileRegex();
  12790. }
  12791. /**
  12792. * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
  12793. */
  12794. protected function _compileRegex()
  12795. {
  12796. $raw = str_replace(' ', '', $this->dtd_regex);
  12797. if ($raw{0} != '(') {
  12798. $raw = "($raw)";
  12799. }
  12800. $el = '[#a-zA-Z0-9_.-]+';
  12801. $reg = $raw;
  12802. // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
  12803. // DOING! Seriously: if there's problems, please report them.
  12804. // collect all elements into the $elements array
  12805. preg_match_all("/$el/", $reg, $matches);
  12806. foreach ($matches[0] as $match) {
  12807. $this->elements[$match] = true;
  12808. }
  12809. // setup all elements as parentheticals with leading commas
  12810. $reg = preg_replace("/$el/", '(,\\0)', $reg);
  12811. // remove commas when they were not solicited
  12812. $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
  12813. // remove all non-paranthetical commas: they are handled by first regex
  12814. $reg = preg_replace("/,\(/", '(', $reg);
  12815. $this->_pcre_regex = $reg;
  12816. }
  12817. /**
  12818. * @param HTMLPurifier_Node[] $children
  12819. * @param HTMLPurifier_Config $config
  12820. * @param HTMLPurifier_Context $context
  12821. * @return bool
  12822. */
  12823. public function validateChildren($children, $config, $context)
  12824. {
  12825. $list_of_children = '';
  12826. $nesting = 0; // depth into the nest
  12827. foreach ($children as $node) {
  12828. if (!empty($node->is_whitespace)) {
  12829. continue;
  12830. }
  12831. $list_of_children .= $node->name . ',';
  12832. }
  12833. // add leading comma to deal with stray comma declarations
  12834. $list_of_children = ',' . rtrim($list_of_children, ',');
  12835. $okay =
  12836. preg_match(
  12837. '/^,?' . $this->_pcre_regex . '$/',
  12838. $list_of_children
  12839. );
  12840. return (bool)$okay;
  12841. }
  12842. }
  12843. /**
  12844. * Definition that disallows all elements.
  12845. * @warning validateChildren() in this class is actually never called, because
  12846. * empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
  12847. * before child definitions are parsed in earnest by
  12848. * HTMLPurifier_Strategy_FixNesting.
  12849. */
  12850. class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
  12851. {
  12852. /**
  12853. * @type bool
  12854. */
  12855. public $allow_empty = true;
  12856. /**
  12857. * @type string
  12858. */
  12859. public $type = 'empty';
  12860. public function __construct()
  12861. {
  12862. }
  12863. /**
  12864. * @param HTMLPurifier_Node[] $children
  12865. * @param HTMLPurifier_Config $config
  12866. * @param HTMLPurifier_Context $context
  12867. * @return array
  12868. */
  12869. public function validateChildren($children, $config, $context)
  12870. {
  12871. return array();
  12872. }
  12873. }
  12874. /**
  12875. * Definition for list containers ul and ol.
  12876. *
  12877. * What does this do? The big thing is to handle ol/ul at the top
  12878. * level of list nodes, which should be handled specially by /folding/
  12879. * them into the previous list node. We generally shouldn't ever
  12880. * see other disallowed elements, because the autoclose behavior
  12881. * in MakeWellFormed handles it.
  12882. */
  12883. class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
  12884. {
  12885. /**
  12886. * @type string
  12887. */
  12888. public $type = 'list';
  12889. /**
  12890. * @type array
  12891. */
  12892. // lying a little bit, so that we can handle ul and ol ourselves
  12893. // XXX: This whole business with 'wrap' is all a bit unsatisfactory
  12894. public $elements = array('li' => true, 'ul' => true, 'ol' => true);
  12895. /**
  12896. * @param array $children
  12897. * @param HTMLPurifier_Config $config
  12898. * @param HTMLPurifier_Context $context
  12899. * @return array
  12900. */
  12901. public function validateChildren($children, $config, $context)
  12902. {
  12903. // Flag for subclasses
  12904. $this->whitespace = false;
  12905. // if there are no tokens, delete parent node
  12906. if (empty($children)) {
  12907. return false;
  12908. }
  12909. // if li is not allowed, delete parent node
  12910. if (!isset($config->getHTMLDefinition()->info['li'])) {
  12911. trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
  12912. return false;
  12913. }
  12914. // the new set of children
  12915. $result = array();
  12916. // a little sanity check to make sure it's not ALL whitespace
  12917. $all_whitespace = true;
  12918. $current_li = null;
  12919. foreach ($children as $node) {
  12920. if (!empty($node->is_whitespace)) {
  12921. $result[] = $node;
  12922. continue;
  12923. }
  12924. $all_whitespace = false; // phew, we're not talking about whitespace
  12925. if ($node->name === 'li') {
  12926. // good
  12927. $current_li = $node;
  12928. $result[] = $node;
  12929. } else {
  12930. // we want to tuck this into the previous li
  12931. // Invariant: we expect the node to be ol/ul
  12932. // ToDo: Make this more robust in the case of not ol/ul
  12933. // by distinguishing between existing li and li created
  12934. // to handle non-list elements; non-list elements should
  12935. // not be appended to an existing li; only li created
  12936. // for non-list. This distinction is not currently made.
  12937. if ($current_li === null) {
  12938. $current_li = new HTMLPurifier_Node_Element('li');
  12939. $result[] = $current_li;
  12940. }
  12941. $current_li->children[] = $node;
  12942. $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
  12943. }
  12944. }
  12945. if (empty($result)) {
  12946. return false;
  12947. }
  12948. if ($all_whitespace) {
  12949. return false;
  12950. }
  12951. return $result;
  12952. }
  12953. }
  12954. /**
  12955. * Definition that allows a set of elements, but disallows empty children.
  12956. */
  12957. class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
  12958. {
  12959. /**
  12960. * Lookup table of allowed elements.
  12961. * @type array
  12962. */
  12963. public $elements = array();
  12964. /**
  12965. * Whether or not the last passed node was all whitespace.
  12966. * @type bool
  12967. */
  12968. protected $whitespace = false;
  12969. /**
  12970. * @param array|string $elements List of allowed element names (lowercase).
  12971. */
  12972. public function __construct($elements)
  12973. {
  12974. if (is_string($elements)) {
  12975. $elements = str_replace(' ', '', $elements);
  12976. $elements = explode('|', $elements);
  12977. }
  12978. $keys = array_keys($elements);
  12979. if ($keys == array_keys($keys)) {
  12980. $elements = array_flip($elements);
  12981. foreach ($elements as $i => $x) {
  12982. $elements[$i] = true;
  12983. if (empty($i)) {
  12984. unset($elements[$i]);
  12985. } // remove blank
  12986. }
  12987. }
  12988. $this->elements = $elements;
  12989. }
  12990. /**
  12991. * @type bool
  12992. */
  12993. public $allow_empty = false;
  12994. /**
  12995. * @type string
  12996. */
  12997. public $type = 'required';
  12998. /**
  12999. * @param array $children
  13000. * @param HTMLPurifier_Config $config
  13001. * @param HTMLPurifier_Context $context
  13002. * @return array
  13003. */
  13004. public function validateChildren($children, $config, $context)
  13005. {
  13006. // Flag for subclasses
  13007. $this->whitespace = false;
  13008. // if there are no tokens, delete parent node
  13009. if (empty($children)) {
  13010. return false;
  13011. }
  13012. // the new set of children
  13013. $result = array();
  13014. // whether or not parsed character data is allowed
  13015. // this controls whether or not we silently drop a tag
  13016. // or generate escaped HTML from it
  13017. $pcdata_allowed = isset($this->elements['#PCDATA']);
  13018. // a little sanity check to make sure it's not ALL whitespace
  13019. $all_whitespace = true;
  13020. $stack = array_reverse($children);
  13021. while (!empty($stack)) {
  13022. $node = array_pop($stack);
  13023. if (!empty($node->is_whitespace)) {
  13024. $result[] = $node;
  13025. continue;
  13026. }
  13027. $all_whitespace = false; // phew, we're not talking about whitespace
  13028. if (!isset($this->elements[$node->name])) {
  13029. // special case text
  13030. // XXX One of these ought to be redundant or something
  13031. if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
  13032. $result[] = $node;
  13033. continue;
  13034. }
  13035. // spill the child contents in
  13036. // ToDo: Make configurable
  13037. if ($node instanceof HTMLPurifier_Node_Element) {
  13038. for ($i = count($node->children) - 1; $i >= 0; $i--) {
  13039. $stack[] = $node->children[$i];
  13040. }
  13041. continue;
  13042. }
  13043. continue;
  13044. }
  13045. $result[] = $node;
  13046. }
  13047. if (empty($result)) {
  13048. return false;
  13049. }
  13050. if ($all_whitespace) {
  13051. $this->whitespace = true;
  13052. return false;
  13053. }
  13054. return $result;
  13055. }
  13056. }
  13057. /**
  13058. * Definition that allows a set of elements, and allows no children.
  13059. * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
  13060. * really, one shouldn't inherit from the other. Only altered behavior
  13061. * is to overload a returned false with an array. Thus, it will never
  13062. * return false.
  13063. */
  13064. class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
  13065. {
  13066. /**
  13067. * @type bool
  13068. */
  13069. public $allow_empty = true;
  13070. /**
  13071. * @type string
  13072. */
  13073. public $type = 'optional';
  13074. /**
  13075. * @param array $children
  13076. * @param HTMLPurifier_Config $config
  13077. * @param HTMLPurifier_Context $context
  13078. * @return array
  13079. */
  13080. public function validateChildren($children, $config, $context)
  13081. {
  13082. $result = parent::validateChildren($children, $config, $context);
  13083. // we assume that $children is not modified
  13084. if ($result === false) {
  13085. if (empty($children)) {
  13086. return true;
  13087. } elseif ($this->whitespace) {
  13088. return $children;
  13089. } else {
  13090. return array();
  13091. }
  13092. }
  13093. return $result;
  13094. }
  13095. }
  13096. /**
  13097. * Takes the contents of blockquote when in strict and reformats for validation.
  13098. */
  13099. class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
  13100. {
  13101. /**
  13102. * @type array
  13103. */
  13104. protected $real_elements;
  13105. /**
  13106. * @type array
  13107. */
  13108. protected $fake_elements;
  13109. /**
  13110. * @type bool
  13111. */
  13112. public $allow_empty = true;
  13113. /**
  13114. * @type string
  13115. */
  13116. public $type = 'strictblockquote';
  13117. /**
  13118. * @type bool
  13119. */
  13120. protected $init = false;
  13121. /**
  13122. * @param HTMLPurifier_Config $config
  13123. * @return array
  13124. * @note We don't want MakeWellFormed to auto-close inline elements since
  13125. * they might be allowed.
  13126. */
  13127. public function getAllowedElements($config)
  13128. {
  13129. $this->init($config);
  13130. return $this->fake_elements;
  13131. }
  13132. /**
  13133. * @param array $children
  13134. * @param HTMLPurifier_Config $config
  13135. * @param HTMLPurifier_Context $context
  13136. * @return array
  13137. */
  13138. public function validateChildren($children, $config, $context)
  13139. {
  13140. $this->init($config);
  13141. // trick the parent class into thinking it allows more
  13142. $this->elements = $this->fake_elements;
  13143. $result = parent::validateChildren($children, $config, $context);
  13144. $this->elements = $this->real_elements;
  13145. if ($result === false) {
  13146. return array();
  13147. }
  13148. if ($result === true) {
  13149. $result = $children;
  13150. }
  13151. $def = $config->getHTMLDefinition();
  13152. $block_wrap_name = $def->info_block_wrapper;
  13153. $block_wrap = false;
  13154. $ret = array();
  13155. foreach ($result as $node) {
  13156. if ($block_wrap === false) {
  13157. if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
  13158. ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
  13159. $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
  13160. $ret[] = $block_wrap;
  13161. }
  13162. } else {
  13163. if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
  13164. $block_wrap = false;
  13165. }
  13166. }
  13167. if ($block_wrap) {
  13168. $block_wrap->children[] = $node;
  13169. } else {
  13170. $ret[] = $node;
  13171. }
  13172. }
  13173. return $ret;
  13174. }
  13175. /**
  13176. * @param HTMLPurifier_Config $config
  13177. */
  13178. private function init($config)
  13179. {
  13180. if (!$this->init) {
  13181. $def = $config->getHTMLDefinition();
  13182. // allow all inline elements
  13183. $this->real_elements = $this->elements;
  13184. $this->fake_elements = $def->info_content_sets['Flow'];
  13185. $this->fake_elements['#PCDATA'] = true;
  13186. $this->init = true;
  13187. }
  13188. }
  13189. }
  13190. /**
  13191. * Definition for tables. The general idea is to extract out all of the
  13192. * essential bits, and then reconstruct it later.
  13193. *
  13194. * This is a bit confusing, because the DTDs and the W3C
  13195. * validators seem to disagree on the appropriate definition. The
  13196. * DTD claims:
  13197. *
  13198. * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
  13199. *
  13200. * But actually, the HTML4 spec then has this to say:
  13201. *
  13202. * The TBODY start tag is always required except when the table
  13203. * contains only one table body and no table head or foot sections.
  13204. * The TBODY end tag may always be safely omitted.
  13205. *
  13206. * So the DTD is kind of wrong. The validator is, unfortunately, kind
  13207. * of on crack.
  13208. *
  13209. * The definition changed again in XHTML1.1; and in my opinion, this
  13210. * formulation makes the most sense.
  13211. *
  13212. * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
  13213. *
  13214. * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
  13215. * If we encounter a thead, tfoot or tbody, we are placed in the former
  13216. * mode, and we *must* wrap any stray tr segments with a tbody. But if
  13217. * we don't run into any of them, just have tr tags is OK.
  13218. */
  13219. class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
  13220. {
  13221. /**
  13222. * @type bool
  13223. */
  13224. public $allow_empty = false;
  13225. /**
  13226. * @type string
  13227. */
  13228. public $type = 'table';
  13229. /**
  13230. * @type array
  13231. */
  13232. public $elements = array(
  13233. 'tr' => true,
  13234. 'tbody' => true,
  13235. 'thead' => true,
  13236. 'tfoot' => true,
  13237. 'caption' => true,
  13238. 'colgroup' => true,
  13239. 'col' => true
  13240. );
  13241. public function __construct()
  13242. {
  13243. }
  13244. /**
  13245. * @param array $children
  13246. * @param HTMLPurifier_Config $config
  13247. * @param HTMLPurifier_Context $context
  13248. * @return array
  13249. */
  13250. public function validateChildren($children, $config, $context)
  13251. {
  13252. if (empty($children)) {
  13253. return false;
  13254. }
  13255. // only one of these elements is allowed in a table
  13256. $caption = false;
  13257. $thead = false;
  13258. $tfoot = false;
  13259. // whitespace
  13260. $initial_ws = array();
  13261. $after_caption_ws = array();
  13262. $after_thead_ws = array();
  13263. $after_tfoot_ws = array();
  13264. // as many of these as you want
  13265. $cols = array();
  13266. $content = array();
  13267. $tbody_mode = false; // if true, then we need to wrap any stray
  13268. // <tr>s with a <tbody>.
  13269. $ws_accum =& $initial_ws;
  13270. foreach ($children as $node) {
  13271. if ($node instanceof HTMLPurifier_Node_Comment) {
  13272. $ws_accum[] = $node;
  13273. continue;
  13274. }
  13275. switch ($node->name) {
  13276. case 'tbody':
  13277. $tbody_mode = true;
  13278. // fall through
  13279. case 'tr':
  13280. $content[] = $node;
  13281. $ws_accum =& $content;
  13282. break;
  13283. case 'caption':
  13284. // there can only be one caption!
  13285. if ($caption !== false) break;
  13286. $caption = $node;
  13287. $ws_accum =& $after_caption_ws;
  13288. break;
  13289. case 'thead':
  13290. $tbody_mode = true;
  13291. // XXX This breaks rendering properties with
  13292. // Firefox, which never floats a <thead> to
  13293. // the top. Ever. (Our scheme will float the
  13294. // first <thead> to the top.) So maybe
  13295. // <thead>s that are not first should be
  13296. // turned into <tbody>? Very tricky, indeed.
  13297. if ($thead === false) {
  13298. $thead = $node;
  13299. $ws_accum =& $after_thead_ws;
  13300. } else {
  13301. // Oops, there's a second one! What
  13302. // should we do? Current behavior is to
  13303. // transmutate the first and last entries into
  13304. // tbody tags, and then put into content.
  13305. // Maybe a better idea is to *attach
  13306. // it* to the existing thead or tfoot?
  13307. // We don't do this, because Firefox
  13308. // doesn't float an extra tfoot to the
  13309. // bottom like it does for the first one.
  13310. $node->name = 'tbody';
  13311. $content[] = $node;
  13312. $ws_accum =& $content;
  13313. }
  13314. break;
  13315. case 'tfoot':
  13316. // see above for some aveats
  13317. $tbody_mode = true;
  13318. if ($tfoot === false) {
  13319. $tfoot = $node;
  13320. $ws_accum =& $after_tfoot_ws;
  13321. } else {
  13322. $node->name = 'tbody';
  13323. $content[] = $node;
  13324. $ws_accum =& $content;
  13325. }
  13326. break;
  13327. case 'colgroup':
  13328. case 'col':
  13329. $cols[] = $node;
  13330. $ws_accum =& $cols;
  13331. break;
  13332. case '#PCDATA':
  13333. // How is whitespace handled? We treat is as sticky to
  13334. // the *end* of the previous element. So all of the
  13335. // nonsense we have worked on is to keep things
  13336. // together.
  13337. if (!empty($node->is_whitespace)) {
  13338. $ws_accum[] = $node;
  13339. }
  13340. break;
  13341. }
  13342. }
  13343. if (empty($content)) {
  13344. return false;
  13345. }
  13346. $ret = $initial_ws;
  13347. if ($caption !== false) {
  13348. $ret[] = $caption;
  13349. $ret = array_merge($ret, $after_caption_ws);
  13350. }
  13351. if ($cols !== false) {
  13352. $ret = array_merge($ret, $cols);
  13353. }
  13354. if ($thead !== false) {
  13355. $ret[] = $thead;
  13356. $ret = array_merge($ret, $after_thead_ws);
  13357. }
  13358. if ($tfoot !== false) {
  13359. $ret[] = $tfoot;
  13360. $ret = array_merge($ret, $after_tfoot_ws);
  13361. }
  13362. if ($tbody_mode) {
  13363. // we have to shuffle tr into tbody
  13364. $current_tr_tbody = null;
  13365. foreach($content as $node) {
  13366. switch ($node->name) {
  13367. case 'tbody':
  13368. $current_tr_tbody = null;
  13369. $ret[] = $node;
  13370. break;
  13371. case 'tr':
  13372. if ($current_tr_tbody === null) {
  13373. $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
  13374. $ret[] = $current_tr_tbody;
  13375. }
  13376. $current_tr_tbody->children[] = $node;
  13377. break;
  13378. case '#PCDATA':
  13379. //assert($node->is_whitespace);
  13380. if ($current_tr_tbody === null) {
  13381. $ret[] = $node;
  13382. } else {
  13383. $current_tr_tbody->children[] = $node;
  13384. }
  13385. break;
  13386. }
  13387. }
  13388. } else {
  13389. $ret = array_merge($ret, $content);
  13390. }
  13391. return $ret;
  13392. }
  13393. }
  13394. class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
  13395. {
  13396. /**
  13397. * Cache object we are decorating
  13398. * @type HTMLPurifier_DefinitionCache
  13399. */
  13400. public $cache;
  13401. /**
  13402. * The name of the decorator
  13403. * @var string
  13404. */
  13405. public $name;
  13406. public function __construct()
  13407. {
  13408. }
  13409. /**
  13410. * Lazy decorator function
  13411. * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
  13412. * @return HTMLPurifier_DefinitionCache_Decorator
  13413. */
  13414. public function decorate(&$cache)
  13415. {
  13416. $decorator = $this->copy();
  13417. // reference is necessary for mocks in PHP 4
  13418. $decorator->cache =& $cache;
  13419. $decorator->type = $cache->type;
  13420. return $decorator;
  13421. }
  13422. /**
  13423. * Cross-compatible clone substitute
  13424. * @return HTMLPurifier_DefinitionCache_Decorator
  13425. */
  13426. public function copy()
  13427. {
  13428. return new HTMLPurifier_DefinitionCache_Decorator();
  13429. }
  13430. /**
  13431. * @param HTMLPurifier_Definition $def
  13432. * @param HTMLPurifier_Config $config
  13433. * @return mixed
  13434. */
  13435. public function add($def, $config)
  13436. {
  13437. return $this->cache->add($def, $config);
  13438. }
  13439. /**
  13440. * @param HTMLPurifier_Definition $def
  13441. * @param HTMLPurifier_Config $config
  13442. * @return mixed
  13443. */
  13444. public function set($def, $config)
  13445. {
  13446. return $this->cache->set($def, $config);
  13447. }
  13448. /**
  13449. * @param HTMLPurifier_Definition $def
  13450. * @param HTMLPurifier_Config $config
  13451. * @return mixed
  13452. */
  13453. public function replace($def, $config)
  13454. {
  13455. return $this->cache->replace($def, $config);
  13456. }
  13457. /**
  13458. * @param HTMLPurifier_Config $config
  13459. * @return mixed
  13460. */
  13461. public function get($config)
  13462. {
  13463. return $this->cache->get($config);
  13464. }
  13465. /**
  13466. * @param HTMLPurifier_Config $config
  13467. * @return mixed
  13468. */
  13469. public function remove($config)
  13470. {
  13471. return $this->cache->remove($config);
  13472. }
  13473. /**
  13474. * @param HTMLPurifier_Config $config
  13475. * @return mixed
  13476. */
  13477. public function flush($config)
  13478. {
  13479. return $this->cache->flush($config);
  13480. }
  13481. /**
  13482. * @param HTMLPurifier_Config $config
  13483. * @return mixed
  13484. */
  13485. public function cleanup($config)
  13486. {
  13487. return $this->cache->cleanup($config);
  13488. }
  13489. }
  13490. /**
  13491. * Null cache object to use when no caching is on.
  13492. */
  13493. class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
  13494. {
  13495. /**
  13496. * @param HTMLPurifier_Definition $def
  13497. * @param HTMLPurifier_Config $config
  13498. * @return bool
  13499. */
  13500. public function add($def, $config)
  13501. {
  13502. return false;
  13503. }
  13504. /**
  13505. * @param HTMLPurifier_Definition $def
  13506. * @param HTMLPurifier_Config $config
  13507. * @return bool
  13508. */
  13509. public function set($def, $config)
  13510. {
  13511. return false;
  13512. }
  13513. /**
  13514. * @param HTMLPurifier_Definition $def
  13515. * @param HTMLPurifier_Config $config
  13516. * @return bool
  13517. */
  13518. public function replace($def, $config)
  13519. {
  13520. return false;
  13521. }
  13522. /**
  13523. * @param HTMLPurifier_Config $config
  13524. * @return bool
  13525. */
  13526. public function remove($config)
  13527. {
  13528. return false;
  13529. }
  13530. /**
  13531. * @param HTMLPurifier_Config $config
  13532. * @return bool
  13533. */
  13534. public function get($config)
  13535. {
  13536. return false;
  13537. }
  13538. /**
  13539. * @param HTMLPurifier_Config $config
  13540. * @return bool
  13541. */
  13542. public function flush($config)
  13543. {
  13544. return false;
  13545. }
  13546. /**
  13547. * @param HTMLPurifier_Config $config
  13548. * @return bool
  13549. */
  13550. public function cleanup($config)
  13551. {
  13552. return false;
  13553. }
  13554. }
  13555. class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
  13556. {
  13557. /**
  13558. * @param HTMLPurifier_Definition $def
  13559. * @param HTMLPurifier_Config $config
  13560. * @return int|bool
  13561. */
  13562. public function add($def, $config)
  13563. {
  13564. if (!$this->checkDefType($def)) {
  13565. return;
  13566. }
  13567. $file = $this->generateFilePath($config);
  13568. if (file_exists($file)) {
  13569. return false;
  13570. }
  13571. if (!$this->_prepareDir($config)) {
  13572. return false;
  13573. }
  13574. return $this->_write($file, serialize($def), $config);
  13575. }
  13576. /**
  13577. * @param HTMLPurifier_Definition $def
  13578. * @param HTMLPurifier_Config $config
  13579. * @return int|bool
  13580. */
  13581. public function set($def, $config)
  13582. {
  13583. if (!$this->checkDefType($def)) {
  13584. return;
  13585. }
  13586. $file = $this->generateFilePath($config);
  13587. if (!$this->_prepareDir($config)) {
  13588. return false;
  13589. }
  13590. return $this->_write($file, serialize($def), $config);
  13591. }
  13592. /**
  13593. * @param HTMLPurifier_Definition $def
  13594. * @param HTMLPurifier_Config $config
  13595. * @return int|bool
  13596. */
  13597. public function replace($def, $config)
  13598. {
  13599. if (!$this->checkDefType($def)) {
  13600. return;
  13601. }
  13602. $file = $this->generateFilePath($config);
  13603. if (!file_exists($file)) {
  13604. return false;
  13605. }
  13606. if (!$this->_prepareDir($config)) {
  13607. return false;
  13608. }
  13609. return $this->_write($file, serialize($def), $config);
  13610. }
  13611. /**
  13612. * @param HTMLPurifier_Config $config
  13613. * @return bool|HTMLPurifier_Config
  13614. */
  13615. public function get($config)
  13616. {
  13617. $file = $this->generateFilePath($config);
  13618. if (!file_exists($file)) {
  13619. return false;
  13620. }
  13621. return unserialize(file_get_contents($file));
  13622. }
  13623. /**
  13624. * @param HTMLPurifier_Config $config
  13625. * @return bool
  13626. */
  13627. public function remove($config)
  13628. {
  13629. $file = $this->generateFilePath($config);
  13630. if (!file_exists($file)) {
  13631. return false;
  13632. }
  13633. return unlink($file);
  13634. }
  13635. /**
  13636. * @param HTMLPurifier_Config $config
  13637. * @return bool
  13638. */
  13639. public function flush($config)
  13640. {
  13641. if (!$this->_prepareDir($config)) {
  13642. return false;
  13643. }
  13644. $dir = $this->generateDirectoryPath($config);
  13645. $dh = opendir($dir);
  13646. // Apparently, on some versions of PHP, readdir will return
  13647. // an empty string if you pass an invalid argument to readdir.
  13648. // So you need this test. See #49.
  13649. if (false === $dh) {
  13650. return false;
  13651. }
  13652. while (false !== ($filename = readdir($dh))) {
  13653. if (empty($filename)) {
  13654. continue;
  13655. }
  13656. if ($filename[0] === '.') {
  13657. continue;
  13658. }
  13659. unlink($dir . '/' . $filename);
  13660. }
  13661. closedir($dh);
  13662. return true;
  13663. }
  13664. /**
  13665. * @param HTMLPurifier_Config $config
  13666. * @return bool
  13667. */
  13668. public function cleanup($config)
  13669. {
  13670. if (!$this->_prepareDir($config)) {
  13671. return false;
  13672. }
  13673. $dir = $this->generateDirectoryPath($config);
  13674. $dh = opendir($dir);
  13675. // See #49 (and above).
  13676. if (false === $dh) {
  13677. return false;
  13678. }
  13679. while (false !== ($filename = readdir($dh))) {
  13680. if (empty($filename)) {
  13681. continue;
  13682. }
  13683. if ($filename[0] === '.') {
  13684. continue;
  13685. }
  13686. $key = substr($filename, 0, strlen($filename) - 4);
  13687. if ($this->isOld($key, $config)) {
  13688. unlink($dir . '/' . $filename);
  13689. }
  13690. }
  13691. closedir($dh);
  13692. return true;
  13693. }
  13694. /**
  13695. * Generates the file path to the serial file corresponding to
  13696. * the configuration and definition name
  13697. * @param HTMLPurifier_Config $config
  13698. * @return string
  13699. * @todo Make protected
  13700. */
  13701. public function generateFilePath($config)
  13702. {
  13703. $key = $this->generateKey($config);
  13704. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  13705. }
  13706. /**
  13707. * Generates the path to the directory contain this cache's serial files
  13708. * @param HTMLPurifier_Config $config
  13709. * @return string
  13710. * @note No trailing slash
  13711. * @todo Make protected
  13712. */
  13713. public function generateDirectoryPath($config)
  13714. {
  13715. $base = $this->generateBaseDirectoryPath($config);
  13716. return $base . '/' . $this->type;
  13717. }
  13718. /**
  13719. * Generates path to base directory that contains all definition type
  13720. * serials
  13721. * @param HTMLPurifier_Config $config
  13722. * @return mixed|string
  13723. * @todo Make protected
  13724. */
  13725. public function generateBaseDirectoryPath($config)
  13726. {
  13727. $base = $config->get('Cache.SerializerPath');
  13728. $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
  13729. return $base;
  13730. }
  13731. /**
  13732. * Convenience wrapper function for file_put_contents
  13733. * @param string $file File name to write to
  13734. * @param string $data Data to write into file
  13735. * @param HTMLPurifier_Config $config
  13736. * @return int|bool Number of bytes written if success, or false if failure.
  13737. */
  13738. private function _write($file, $data, $config)
  13739. {
  13740. $result = file_put_contents($file, $data);
  13741. if ($result !== false) {
  13742. // set permissions of the new file (no execute)
  13743. $chmod = $config->get('Cache.SerializerPermissions');
  13744. if ($chmod !== null) {
  13745. chmod($file, $chmod & 0666);
  13746. }
  13747. }
  13748. return $result;
  13749. }
  13750. /**
  13751. * Prepares the directory that this type stores the serials in
  13752. * @param HTMLPurifier_Config $config
  13753. * @return bool True if successful
  13754. */
  13755. private function _prepareDir($config)
  13756. {
  13757. $directory = $this->generateDirectoryPath($config);
  13758. $chmod = $config->get('Cache.SerializerPermissions');
  13759. if ($chmod === null) {
  13760. if (!@mkdir($directory) && !is_dir($directory)) {
  13761. trigger_error(
  13762. 'Could not create directory ' . $directory . '',
  13763. E_USER_WARNING
  13764. );
  13765. return false;
  13766. }
  13767. return true;
  13768. }
  13769. if (!is_dir($directory)) {
  13770. $base = $this->generateBaseDirectoryPath($config);
  13771. if (!is_dir($base)) {
  13772. trigger_error(
  13773. 'Base directory ' . $base . ' does not exist,
  13774. please create or change using %Cache.SerializerPath',
  13775. E_USER_WARNING
  13776. );
  13777. return false;
  13778. } elseif (!$this->_testPermissions($base, $chmod)) {
  13779. return false;
  13780. }
  13781. if (!@mkdir($directory, $chmod) && !is_dir($directory)) {
  13782. trigger_error(
  13783. 'Could not create directory ' . $directory . '',
  13784. E_USER_WARNING
  13785. );
  13786. return false;
  13787. }
  13788. if (!$this->_testPermissions($directory, $chmod)) {
  13789. return false;
  13790. }
  13791. } elseif (!$this->_testPermissions($directory, $chmod)) {
  13792. return false;
  13793. }
  13794. return true;
  13795. }
  13796. /**
  13797. * Tests permissions on a directory and throws out friendly
  13798. * error messages and attempts to chmod it itself if possible
  13799. * @param string $dir Directory path
  13800. * @param int $chmod Permissions
  13801. * @return bool True if directory is writable
  13802. */
  13803. private function _testPermissions($dir, $chmod)
  13804. {
  13805. // early abort, if it is writable, everything is hunky-dory
  13806. if (is_writable($dir)) {
  13807. return true;
  13808. }
  13809. if (!is_dir($dir)) {
  13810. // generally, you'll want to handle this beforehand
  13811. // so a more specific error message can be given
  13812. trigger_error(
  13813. 'Directory ' . $dir . ' does not exist',
  13814. E_USER_WARNING
  13815. );
  13816. return false;
  13817. }
  13818. if (function_exists('posix_getuid') && $chmod !== null) {
  13819. // POSIX system, we can give more specific advice
  13820. if (fileowner($dir) === posix_getuid()) {
  13821. // we can chmod it ourselves
  13822. $chmod = $chmod | 0700;
  13823. if (chmod($dir, $chmod)) {
  13824. return true;
  13825. }
  13826. } elseif (filegroup($dir) === posix_getgid()) {
  13827. $chmod = $chmod | 0070;
  13828. } else {
  13829. // PHP's probably running as nobody, so we'll
  13830. // need to give global permissions
  13831. $chmod = $chmod | 0777;
  13832. }
  13833. trigger_error(
  13834. 'Directory ' . $dir . ' not writable, ' .
  13835. 'please chmod to ' . decoct($chmod),
  13836. E_USER_WARNING
  13837. );
  13838. } else {
  13839. // generic error message
  13840. trigger_error(
  13841. 'Directory ' . $dir . ' not writable, ' .
  13842. 'please alter file permissions',
  13843. E_USER_WARNING
  13844. );
  13845. }
  13846. return false;
  13847. }
  13848. }
  13849. /**
  13850. * Definition cache decorator class that cleans up the cache
  13851. * whenever there is a cache miss.
  13852. */
  13853. class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
  13854. {
  13855. /**
  13856. * @type string
  13857. */
  13858. public $name = 'Cleanup';
  13859. /**
  13860. * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
  13861. */
  13862. public function copy()
  13863. {
  13864. return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
  13865. }
  13866. /**
  13867. * @param HTMLPurifier_Definition $def
  13868. * @param HTMLPurifier_Config $config
  13869. * @return mixed
  13870. */
  13871. public function add($def, $config)
  13872. {
  13873. $status = parent::add($def, $config);
  13874. if (!$status) {
  13875. parent::cleanup($config);
  13876. }
  13877. return $status;
  13878. }
  13879. /**
  13880. * @param HTMLPurifier_Definition $def
  13881. * @param HTMLPurifier_Config $config
  13882. * @return mixed
  13883. */
  13884. public function set($def, $config)
  13885. {
  13886. $status = parent::set($def, $config);
  13887. if (!$status) {
  13888. parent::cleanup($config);
  13889. }
  13890. return $status;
  13891. }
  13892. /**
  13893. * @param HTMLPurifier_Definition $def
  13894. * @param HTMLPurifier_Config $config
  13895. * @return mixed
  13896. */
  13897. public function replace($def, $config)
  13898. {
  13899. $status = parent::replace($def, $config);
  13900. if (!$status) {
  13901. parent::cleanup($config);
  13902. }
  13903. return $status;
  13904. }
  13905. /**
  13906. * @param HTMLPurifier_Config $config
  13907. * @return mixed
  13908. */
  13909. public function get($config)
  13910. {
  13911. $ret = parent::get($config);
  13912. if (!$ret) {
  13913. parent::cleanup($config);
  13914. }
  13915. return $ret;
  13916. }
  13917. }
  13918. /**
  13919. * Definition cache decorator class that saves all cache retrievals
  13920. * to PHP's memory; good for unit tests or circumstances where
  13921. * there are lots of configuration objects floating around.
  13922. */
  13923. class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
  13924. {
  13925. /**
  13926. * @type array
  13927. */
  13928. protected $definitions;
  13929. /**
  13930. * @type string
  13931. */
  13932. public $name = 'Memory';
  13933. /**
  13934. * @return HTMLPurifier_DefinitionCache_Decorator_Memory
  13935. */
  13936. public function copy()
  13937. {
  13938. return new HTMLPurifier_DefinitionCache_Decorator_Memory();
  13939. }
  13940. /**
  13941. * @param HTMLPurifier_Definition $def
  13942. * @param HTMLPurifier_Config $config
  13943. * @return mixed
  13944. */
  13945. public function add($def, $config)
  13946. {
  13947. $status = parent::add($def, $config);
  13948. if ($status) {
  13949. $this->definitions[$this->generateKey($config)] = $def;
  13950. }
  13951. return $status;
  13952. }
  13953. /**
  13954. * @param HTMLPurifier_Definition $def
  13955. * @param HTMLPurifier_Config $config
  13956. * @return mixed
  13957. */
  13958. public function set($def, $config)
  13959. {
  13960. $status = parent::set($def, $config);
  13961. if ($status) {
  13962. $this->definitions[$this->generateKey($config)] = $def;
  13963. }
  13964. return $status;
  13965. }
  13966. /**
  13967. * @param HTMLPurifier_Definition $def
  13968. * @param HTMLPurifier_Config $config
  13969. * @return mixed
  13970. */
  13971. public function replace($def, $config)
  13972. {
  13973. $status = parent::replace($def, $config);
  13974. if ($status) {
  13975. $this->definitions[$this->generateKey($config)] = $def;
  13976. }
  13977. return $status;
  13978. }
  13979. /**
  13980. * @param HTMLPurifier_Config $config
  13981. * @return mixed
  13982. */
  13983. public function get($config)
  13984. {
  13985. $key = $this->generateKey($config);
  13986. if (isset($this->definitions[$key])) {
  13987. return $this->definitions[$key];
  13988. }
  13989. $this->definitions[$key] = parent::get($config);
  13990. return $this->definitions[$key];
  13991. }
  13992. }
  13993. /**
  13994. * XHTML 1.1 Bi-directional Text Module, defines elements that
  13995. * declare directionality of content. Text Extension Module.
  13996. */
  13997. class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
  13998. {
  13999. /**
  14000. * @type string
  14001. */
  14002. public $name = 'Bdo';
  14003. /**
  14004. * @type array
  14005. */
  14006. public $attr_collections = array(
  14007. 'I18N' => array('dir' => false)
  14008. );
  14009. /**
  14010. * @param HTMLPurifier_Config $config
  14011. */
  14012. public function setup($config)
  14013. {
  14014. $bdo = $this->addElement(
  14015. 'bdo',
  14016. 'Inline',
  14017. 'Inline',
  14018. array('Core', 'Lang'),
  14019. array(
  14020. 'dir' => 'Enum#ltr,rtl', // required
  14021. // The Abstract Module specification has the attribute
  14022. // inclusions wrong for bdo: bdo allows Lang
  14023. )
  14024. );
  14025. $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
  14026. $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
  14027. }
  14028. }
  14029. class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
  14030. {
  14031. /**
  14032. * @type string
  14033. */
  14034. public $name = 'CommonAttributes';
  14035. /**
  14036. * @type array
  14037. */
  14038. public $attr_collections = array(
  14039. 'Core' => array(
  14040. 0 => array('Style'),
  14041. // 'xml:space' => false,
  14042. 'class' => 'Class',
  14043. 'id' => 'ID',
  14044. 'title' => 'CDATA',
  14045. ),
  14046. 'Lang' => array(),
  14047. 'I18N' => array(
  14048. 0 => array('Lang'), // proprietary, for xml:lang/lang
  14049. ),
  14050. 'Common' => array(
  14051. 0 => array('Core', 'I18N')
  14052. )
  14053. );
  14054. }
  14055. /**
  14056. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  14057. * Module.
  14058. */
  14059. class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
  14060. {
  14061. /**
  14062. * @type string
  14063. */
  14064. public $name = 'Edit';
  14065. /**
  14066. * @param HTMLPurifier_Config $config
  14067. */
  14068. public function setup($config)
  14069. {
  14070. $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
  14071. $attr = array(
  14072. 'cite' => 'URI',
  14073. // 'datetime' => 'Datetime', // not implemented
  14074. );
  14075. $this->addElement('del', 'Inline', $contents, 'Common', $attr);
  14076. $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
  14077. }
  14078. // HTML 4.01 specifies that ins/del must not contain block
  14079. // elements when used in an inline context, chameleon is
  14080. // a complicated workaround to acheive this effect
  14081. // Inline context ! Block context (exclamation mark is
  14082. // separator, see getChildDef for parsing)
  14083. /**
  14084. * @type bool
  14085. */
  14086. public $defines_child_def = true;
  14087. /**
  14088. * @param HTMLPurifier_ElementDef $def
  14089. * @return HTMLPurifier_ChildDef_Chameleon
  14090. */
  14091. public function getChildDef($def)
  14092. {
  14093. if ($def->content_model_type != 'chameleon') {
  14094. return false;
  14095. }
  14096. $value = explode('!', $def->content_model);
  14097. return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
  14098. }
  14099. }
  14100. /**
  14101. * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
  14102. */
  14103. class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
  14104. {
  14105. /**
  14106. * @type string
  14107. */
  14108. public $name = 'Forms';
  14109. /**
  14110. * @type bool
  14111. */
  14112. public $safe = false;
  14113. /**
  14114. * @type array
  14115. */
  14116. public $content_sets = array(
  14117. 'Block' => 'Form',
  14118. 'Inline' => 'Formctrl',
  14119. );
  14120. /**
  14121. * @param HTMLPurifier_Config $config
  14122. */
  14123. public function setup($config)
  14124. {
  14125. $form = $this->addElement(
  14126. 'form',
  14127. 'Form',
  14128. 'Required: Heading | List | Block | fieldset',
  14129. 'Common',
  14130. array(
  14131. 'accept' => 'ContentTypes',
  14132. 'accept-charset' => 'Charsets',
  14133. 'action*' => 'URI',
  14134. 'method' => 'Enum#get,post',
  14135. // really ContentType, but these two are the only ones used today
  14136. 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
  14137. )
  14138. );
  14139. $form->excludes = array('form' => true);
  14140. $input = $this->addElement(
  14141. 'input',
  14142. 'Formctrl',
  14143. 'Empty',
  14144. 'Common',
  14145. array(
  14146. 'accept' => 'ContentTypes',
  14147. 'accesskey' => 'Character',
  14148. 'alt' => 'Text',
  14149. 'checked' => 'Bool#checked',
  14150. 'disabled' => 'Bool#disabled',
  14151. 'maxlength' => 'Number',
  14152. 'name' => 'CDATA',
  14153. 'readonly' => 'Bool#readonly',
  14154. 'size' => 'Number',
  14155. 'src' => 'URI#embedded',
  14156. 'tabindex' => 'Number',
  14157. 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
  14158. 'value' => 'CDATA',
  14159. )
  14160. );
  14161. $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
  14162. $this->addElement(
  14163. 'select',
  14164. 'Formctrl',
  14165. 'Required: optgroup | option',
  14166. 'Common',
  14167. array(
  14168. 'disabled' => 'Bool#disabled',
  14169. 'multiple' => 'Bool#multiple',
  14170. 'name' => 'CDATA',
  14171. 'size' => 'Number',
  14172. 'tabindex' => 'Number',
  14173. )
  14174. );
  14175. $this->addElement(
  14176. 'option',
  14177. false,
  14178. 'Optional: #PCDATA',
  14179. 'Common',
  14180. array(
  14181. 'disabled' => 'Bool#disabled',
  14182. 'label' => 'Text',
  14183. 'selected' => 'Bool#selected',
  14184. 'value' => 'CDATA',
  14185. )
  14186. );
  14187. // It's illegal for there to be more than one selected, but not
  14188. // be multiple. Also, no selected means undefined behavior. This might
  14189. // be difficult to implement; perhaps an injector, or a context variable.
  14190. $textarea = $this->addElement(
  14191. 'textarea',
  14192. 'Formctrl',
  14193. 'Optional: #PCDATA',
  14194. 'Common',
  14195. array(
  14196. 'accesskey' => 'Character',
  14197. 'cols*' => 'Number',
  14198. 'disabled' => 'Bool#disabled',
  14199. 'name' => 'CDATA',
  14200. 'readonly' => 'Bool#readonly',
  14201. 'rows*' => 'Number',
  14202. 'tabindex' => 'Number',
  14203. )
  14204. );
  14205. $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
  14206. $button = $this->addElement(
  14207. 'button',
  14208. 'Formctrl',
  14209. 'Optional: #PCDATA | Heading | List | Block | Inline',
  14210. 'Common',
  14211. array(
  14212. 'accesskey' => 'Character',
  14213. 'disabled' => 'Bool#disabled',
  14214. 'name' => 'CDATA',
  14215. 'tabindex' => 'Number',
  14216. 'type' => 'Enum#button,submit,reset',
  14217. 'value' => 'CDATA',
  14218. )
  14219. );
  14220. // For exclusions, ideally we'd specify content sets, not literal elements
  14221. $button->excludes = $this->makeLookup(
  14222. 'form',
  14223. 'fieldset', // Form
  14224. 'input',
  14225. 'select',
  14226. 'textarea',
  14227. 'label',
  14228. 'button', // Formctrl
  14229. 'a', // as per HTML 4.01 spec, this is omitted by modularization
  14230. 'isindex',
  14231. 'iframe' // legacy items
  14232. );
  14233. // Extra exclusion: img usemap="" is not permitted within this element.
  14234. // We'll omit this for now, since we don't have any good way of
  14235. // indicating it yet.
  14236. // This is HIGHLY user-unfriendly; we need a custom child-def for this
  14237. $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
  14238. $label = $this->addElement(
  14239. 'label',
  14240. 'Formctrl',
  14241. 'Optional: #PCDATA | Inline',
  14242. 'Common',
  14243. array(
  14244. 'accesskey' => 'Character',
  14245. // 'for' => 'IDREF', // IDREF not implemented, cannot allow
  14246. )
  14247. );
  14248. $label->excludes = array('label' => true);
  14249. $this->addElement(
  14250. 'legend',
  14251. false,
  14252. 'Optional: #PCDATA | Inline',
  14253. 'Common',
  14254. array(
  14255. 'accesskey' => 'Character',
  14256. )
  14257. );
  14258. $this->addElement(
  14259. 'optgroup',
  14260. false,
  14261. 'Required: option',
  14262. 'Common',
  14263. array(
  14264. 'disabled' => 'Bool#disabled',
  14265. 'label*' => 'Text',
  14266. )
  14267. );
  14268. // Don't forget an injector for <isindex>. This one's a little complex
  14269. // because it maps to multiple elements.
  14270. }
  14271. }
  14272. /**
  14273. * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
  14274. */
  14275. class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
  14276. {
  14277. /**
  14278. * @type string
  14279. */
  14280. public $name = 'Hypertext';
  14281. /**
  14282. * @param HTMLPurifier_Config $config
  14283. */
  14284. public function setup($config)
  14285. {
  14286. $a = $this->addElement(
  14287. 'a',
  14288. 'Inline',
  14289. 'Inline',
  14290. 'Common',
  14291. array(
  14292. // 'accesskey' => 'Character',
  14293. // 'charset' => 'Charset',
  14294. 'href' => 'URI',
  14295. // 'hreflang' => 'LanguageCode',
  14296. 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
  14297. 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
  14298. // 'tabindex' => 'Number',
  14299. // 'type' => 'ContentType',
  14300. )
  14301. );
  14302. $a->formatting = true;
  14303. $a->excludes = array('a' => true);
  14304. }
  14305. }
  14306. /**
  14307. * XHTML 1.1 Iframe Module provides inline frames.
  14308. *
  14309. * @note This module is not considered safe unless an Iframe
  14310. * whitelisting mechanism is specified. Currently, the only
  14311. * such mechanism is %URL.SafeIframeRegexp
  14312. */
  14313. class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
  14314. {
  14315. /**
  14316. * @type string
  14317. */
  14318. public $name = 'Iframe';
  14319. /**
  14320. * @type bool
  14321. */
  14322. public $safe = false;
  14323. /**
  14324. * @param HTMLPurifier_Config $config
  14325. */
  14326. public function setup($config)
  14327. {
  14328. if ($config->get('HTML.SafeIframe')) {
  14329. $this->safe = true;
  14330. }
  14331. $this->addElement(
  14332. 'iframe',
  14333. 'Inline',
  14334. 'Flow',
  14335. 'Common',
  14336. array(
  14337. 'src' => 'URI#embedded',
  14338. 'width' => 'Length',
  14339. 'height' => 'Length',
  14340. 'name' => 'ID',
  14341. 'scrolling' => 'Enum#yes,no,auto',
  14342. 'frameborder' => 'Enum#0,1',
  14343. 'longdesc' => 'URI',
  14344. 'marginheight' => 'Pixels',
  14345. 'marginwidth' => 'Pixels',
  14346. )
  14347. );
  14348. }
  14349. }
  14350. /**
  14351. * XHTML 1.1 Image Module provides basic image embedding.
  14352. * @note There is specialized code for removing empty images in
  14353. * HTMLPurifier_Strategy_RemoveForeignElements
  14354. */
  14355. class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
  14356. {
  14357. /**
  14358. * @type string
  14359. */
  14360. public $name = 'Image';
  14361. /**
  14362. * @param HTMLPurifier_Config $config
  14363. */
  14364. public function setup($config)
  14365. {
  14366. $max = $config->get('HTML.MaxImgLength');
  14367. $img = $this->addElement(
  14368. 'img',
  14369. 'Inline',
  14370. 'Empty',
  14371. 'Common',
  14372. array(
  14373. 'alt*' => 'Text',
  14374. // According to the spec, it's Length, but percents can
  14375. // be abused, so we allow only Pixels.
  14376. 'height' => 'Pixels#' . $max,
  14377. 'width' => 'Pixels#' . $max,
  14378. 'longdesc' => 'URI',
  14379. 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
  14380. )
  14381. );
  14382. if ($max === null || $config->get('HTML.Trusted')) {
  14383. $img->attr['height'] =
  14384. $img->attr['width'] = 'Length';
  14385. }
  14386. // kind of strange, but splitting things up would be inefficient
  14387. $img->attr_transform_pre[] =
  14388. $img->attr_transform_post[] =
  14389. new HTMLPurifier_AttrTransform_ImgRequired();
  14390. }
  14391. }
  14392. /**
  14393. * XHTML 1.1 Legacy module defines elements that were previously
  14394. * deprecated.
  14395. *
  14396. * @note Not all legacy elements have been implemented yet, which
  14397. * is a bit of a reverse problem as compared to browsers! In
  14398. * addition, this legacy module may implement a bit more than
  14399. * mandated by XHTML 1.1.
  14400. *
  14401. * This module can be used in combination with TransformToStrict in order
  14402. * to transform as many deprecated elements as possible, but retain
  14403. * questionably deprecated elements that do not have good alternatives
  14404. * as well as transform elements that don't have an implementation.
  14405. * See docs/ref-strictness.txt for more details.
  14406. */
  14407. class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
  14408. {
  14409. /**
  14410. * @type string
  14411. */
  14412. public $name = 'Legacy';
  14413. /**
  14414. * @param HTMLPurifier_Config $config
  14415. */
  14416. public function setup($config)
  14417. {
  14418. $this->addElement(
  14419. 'basefont',
  14420. 'Inline',
  14421. 'Empty',
  14422. null,
  14423. array(
  14424. 'color' => 'Color',
  14425. 'face' => 'Text', // extremely broad, we should
  14426. 'size' => 'Text', // tighten it
  14427. 'id' => 'ID'
  14428. )
  14429. );
  14430. $this->addElement('center', 'Block', 'Flow', 'Common');
  14431. $this->addElement(
  14432. 'dir',
  14433. 'Block',
  14434. 'Required: li',
  14435. 'Common',
  14436. array(
  14437. 'compact' => 'Bool#compact'
  14438. )
  14439. );
  14440. $this->addElement(
  14441. 'font',
  14442. 'Inline',
  14443. 'Inline',
  14444. array('Core', 'I18N'),
  14445. array(
  14446. 'color' => 'Color',
  14447. 'face' => 'Text', // extremely broad, we should
  14448. 'size' => 'Text', // tighten it
  14449. )
  14450. );
  14451. $this->addElement(
  14452. 'menu',
  14453. 'Block',
  14454. 'Required: li',
  14455. 'Common',
  14456. array(
  14457. 'compact' => 'Bool#compact'
  14458. )
  14459. );
  14460. $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
  14461. $s->formatting = true;
  14462. $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
  14463. $strike->formatting = true;
  14464. $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
  14465. $u->formatting = true;
  14466. // setup modifications to old elements
  14467. $align = 'Enum#left,right,center,justify';
  14468. $address = $this->addBlankElement('address');
  14469. $address->content_model = 'Inline | #PCDATA | p';
  14470. $address->content_model_type = 'optional';
  14471. $address->child = false;
  14472. $blockquote = $this->addBlankElement('blockquote');
  14473. $blockquote->content_model = 'Flow | #PCDATA';
  14474. $blockquote->content_model_type = 'optional';
  14475. $blockquote->child = false;
  14476. $br = $this->addBlankElement('br');
  14477. $br->attr['clear'] = 'Enum#left,all,right,none';
  14478. $caption = $this->addBlankElement('caption');
  14479. $caption->attr['align'] = 'Enum#top,bottom,left,right';
  14480. $div = $this->addBlankElement('div');
  14481. $div->attr['align'] = $align;
  14482. $dl = $this->addBlankElement('dl');
  14483. $dl->attr['compact'] = 'Bool#compact';
  14484. for ($i = 1; $i <= 6; $i++) {
  14485. $h = $this->addBlankElement("h$i");
  14486. $h->attr['align'] = $align;
  14487. }
  14488. $hr = $this->addBlankElement('hr');
  14489. $hr->attr['align'] = $align;
  14490. $hr->attr['noshade'] = 'Bool#noshade';
  14491. $hr->attr['size'] = 'Pixels';
  14492. $hr->attr['width'] = 'Length';
  14493. $img = $this->addBlankElement('img');
  14494. $img->attr['align'] = 'IAlign';
  14495. $img->attr['border'] = 'Pixels';
  14496. $img->attr['hspace'] = 'Pixels';
  14497. $img->attr['vspace'] = 'Pixels';
  14498. // figure out this integer business
  14499. $li = $this->addBlankElement('li');
  14500. $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
  14501. $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
  14502. $ol = $this->addBlankElement('ol');
  14503. $ol->attr['compact'] = 'Bool#compact';
  14504. $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
  14505. $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
  14506. $p = $this->addBlankElement('p');
  14507. $p->attr['align'] = $align;
  14508. $pre = $this->addBlankElement('pre');
  14509. $pre->attr['width'] = 'Number';
  14510. // script omitted
  14511. $table = $this->addBlankElement('table');
  14512. $table->attr['align'] = 'Enum#left,center,right';
  14513. $table->attr['bgcolor'] = 'Color';
  14514. $tr = $this->addBlankElement('tr');
  14515. $tr->attr['bgcolor'] = 'Color';
  14516. $th = $this->addBlankElement('th');
  14517. $th->attr['bgcolor'] = 'Color';
  14518. $th->attr['height'] = 'Length';
  14519. $th->attr['nowrap'] = 'Bool#nowrap';
  14520. $th->attr['width'] = 'Length';
  14521. $td = $this->addBlankElement('td');
  14522. $td->attr['bgcolor'] = 'Color';
  14523. $td->attr['height'] = 'Length';
  14524. $td->attr['nowrap'] = 'Bool#nowrap';
  14525. $td->attr['width'] = 'Length';
  14526. $ul = $this->addBlankElement('ul');
  14527. $ul->attr['compact'] = 'Bool#compact';
  14528. $ul->attr['type'] = 'Enum#square,disc,circle';
  14529. // "safe" modifications to "unsafe" elements
  14530. // WARNING: If you want to add support for an unsafe, legacy
  14531. // attribute, make a new TrustedLegacy module with the trusted
  14532. // bit set appropriately
  14533. $form = $this->addBlankElement('form');
  14534. $form->content_model = 'Flow | #PCDATA';
  14535. $form->content_model_type = 'optional';
  14536. $form->attr['target'] = 'FrameTarget';
  14537. $input = $this->addBlankElement('input');
  14538. $input->attr['align'] = 'IAlign';
  14539. $legend = $this->addBlankElement('legend');
  14540. $legend->attr['align'] = 'LAlign';
  14541. }
  14542. }
  14543. /**
  14544. * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
  14545. */
  14546. class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
  14547. {
  14548. /**
  14549. * @type string
  14550. */
  14551. public $name = 'List';
  14552. // According to the abstract schema, the List content set is a fully formed
  14553. // one or more expr, but it invariably occurs in an optional declaration
  14554. // so we're not going to do that subtlety. It might cause trouble
  14555. // if a user defines "List" and expects that multiple lists are
  14556. // allowed to be specified, but then again, that's not very intuitive.
  14557. // Furthermore, the actual XML Schema may disagree. Regardless,
  14558. // we don't have support for such nested expressions without using
  14559. // the incredibly inefficient and draconic Custom ChildDef.
  14560. /**
  14561. * @type array
  14562. */
  14563. public $content_sets = array('Flow' => 'List');
  14564. /**
  14565. * @param HTMLPurifier_Config $config
  14566. */
  14567. public function setup($config)
  14568. {
  14569. $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14570. $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14571. // XXX The wrap attribute is handled by MakeWellFormed. This is all
  14572. // quite unsatisfactory, because we generated this
  14573. // *specifically* for lists, and now a big chunk of the handling
  14574. // is done properly by the List ChildDef. So actually, we just
  14575. // want enough information to make autoclosing work properly,
  14576. // and then hand off the tricky stuff to the ChildDef.
  14577. $ol->wrap = 'li';
  14578. $ul->wrap = 'li';
  14579. $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
  14580. $this->addElement('li', false, 'Flow', 'Common');
  14581. $this->addElement('dd', false, 'Flow', 'Common');
  14582. $this->addElement('dt', false, 'Inline', 'Common');
  14583. }
  14584. }
  14585. class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
  14586. {
  14587. /**
  14588. * @type string
  14589. */
  14590. public $name = 'Name';
  14591. /**
  14592. * @param HTMLPurifier_Config $config
  14593. */
  14594. public function setup($config)
  14595. {
  14596. $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
  14597. foreach ($elements as $name) {
  14598. $element = $this->addBlankElement($name);
  14599. $element->attr['name'] = 'CDATA';
  14600. if (!$config->get('HTML.Attr.Name.UseCDATA')) {
  14601. $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
  14602. }
  14603. }
  14604. }
  14605. }
  14606. /**
  14607. * Module adds the nofollow attribute transformation to a tags. It
  14608. * is enabled by HTML.Nofollow
  14609. */
  14610. class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
  14611. {
  14612. /**
  14613. * @type string
  14614. */
  14615. public $name = 'Nofollow';
  14616. /**
  14617. * @param HTMLPurifier_Config $config
  14618. */
  14619. public function setup($config)
  14620. {
  14621. $a = $this->addBlankElement('a');
  14622. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
  14623. }
  14624. }
  14625. class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
  14626. {
  14627. /**
  14628. * @type string
  14629. */
  14630. public $name = 'NonXMLCommonAttributes';
  14631. /**
  14632. * @type array
  14633. */
  14634. public $attr_collections = array(
  14635. 'Lang' => array(
  14636. 'lang' => 'LanguageCode',
  14637. )
  14638. );
  14639. }
  14640. /**
  14641. * XHTML 1.1 Object Module, defines elements for generic object inclusion
  14642. * @warning Users will commonly use <embed> to cater to legacy browsers: this
  14643. * module does not allow this sort of behavior
  14644. */
  14645. class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
  14646. {
  14647. /**
  14648. * @type string
  14649. */
  14650. public $name = 'Object';
  14651. /**
  14652. * @type bool
  14653. */
  14654. public $safe = false;
  14655. /**
  14656. * @param HTMLPurifier_Config $config
  14657. */
  14658. public function setup($config)
  14659. {
  14660. $this->addElement(
  14661. 'object',
  14662. 'Inline',
  14663. 'Optional: #PCDATA | Flow | param',
  14664. 'Common',
  14665. array(
  14666. 'archive' => 'URI',
  14667. 'classid' => 'URI',
  14668. 'codebase' => 'URI',
  14669. 'codetype' => 'Text',
  14670. 'data' => 'URI',
  14671. 'declare' => 'Bool#declare',
  14672. 'height' => 'Length',
  14673. 'name' => 'CDATA',
  14674. 'standby' => 'Text',
  14675. 'tabindex' => 'Number',
  14676. 'type' => 'ContentType',
  14677. 'width' => 'Length'
  14678. )
  14679. );
  14680. $this->addElement(
  14681. 'param',
  14682. false,
  14683. 'Empty',
  14684. null,
  14685. array(
  14686. 'id' => 'ID',
  14687. 'name*' => 'Text',
  14688. 'type' => 'Text',
  14689. 'value' => 'Text',
  14690. 'valuetype' => 'Enum#data,ref,object'
  14691. )
  14692. );
  14693. }
  14694. }
  14695. /**
  14696. * XHTML 1.1 Presentation Module, defines simple presentation-related
  14697. * markup. Text Extension Module.
  14698. * @note The official XML Schema and DTD specs further divide this into
  14699. * two modules:
  14700. * - Block Presentation (hr)
  14701. * - Inline Presentation (b, big, i, small, sub, sup, tt)
  14702. * We have chosen not to heed this distinction, as content_sets
  14703. * provides satisfactory disambiguation.
  14704. */
  14705. class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
  14706. {
  14707. /**
  14708. * @type string
  14709. */
  14710. public $name = 'Presentation';
  14711. /**
  14712. * @param HTMLPurifier_Config $config
  14713. */
  14714. public function setup($config)
  14715. {
  14716. $this->addElement('hr', 'Block', 'Empty', 'Common');
  14717. $this->addElement('sub', 'Inline', 'Inline', 'Common');
  14718. $this->addElement('sup', 'Inline', 'Inline', 'Common');
  14719. $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
  14720. $b->formatting = true;
  14721. $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
  14722. $big->formatting = true;
  14723. $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
  14724. $i->formatting = true;
  14725. $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
  14726. $small->formatting = true;
  14727. $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
  14728. $tt->formatting = true;
  14729. }
  14730. }
  14731. /**
  14732. * Module defines proprietary tags and attributes in HTML.
  14733. * @warning If this module is enabled, standards-compliance is off!
  14734. */
  14735. class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
  14736. {
  14737. /**
  14738. * @type string
  14739. */
  14740. public $name = 'Proprietary';
  14741. /**
  14742. * @param HTMLPurifier_Config $config
  14743. */
  14744. public function setup($config)
  14745. {
  14746. $this->addElement(
  14747. 'marquee',
  14748. 'Inline',
  14749. 'Flow',
  14750. 'Common',
  14751. array(
  14752. 'direction' => 'Enum#left,right,up,down',
  14753. 'behavior' => 'Enum#alternate',
  14754. 'width' => 'Length',
  14755. 'height' => 'Length',
  14756. 'scrolldelay' => 'Number',
  14757. 'scrollamount' => 'Number',
  14758. 'loop' => 'Number',
  14759. 'bgcolor' => 'Color',
  14760. 'hspace' => 'Pixels',
  14761. 'vspace' => 'Pixels',
  14762. )
  14763. );
  14764. }
  14765. }
  14766. /**
  14767. * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
  14768. * short runs of text alongside base text for annotation or pronounciation.
  14769. */
  14770. class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
  14771. {
  14772. /**
  14773. * @type string
  14774. */
  14775. public $name = 'Ruby';
  14776. /**
  14777. * @param HTMLPurifier_Config $config
  14778. */
  14779. public function setup($config)
  14780. {
  14781. $this->addElement(
  14782. 'ruby',
  14783. 'Inline',
  14784. 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
  14785. 'Common'
  14786. );
  14787. $this->addElement('rbc', false, 'Required: rb', 'Common');
  14788. $this->addElement('rtc', false, 'Required: rt', 'Common');
  14789. $rb = $this->addElement('rb', false, 'Inline', 'Common');
  14790. $rb->excludes = array('ruby' => true);
  14791. $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
  14792. $rt->excludes = array('ruby' => true);
  14793. $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
  14794. }
  14795. }
  14796. /**
  14797. * A "safe" embed module. See SafeObject. This is a proprietary element.
  14798. */
  14799. class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
  14800. {
  14801. /**
  14802. * @type string
  14803. */
  14804. public $name = 'SafeEmbed';
  14805. /**
  14806. * @param HTMLPurifier_Config $config
  14807. */
  14808. public function setup($config)
  14809. {
  14810. $max = $config->get('HTML.MaxImgLength');
  14811. $embed = $this->addElement(
  14812. 'embed',
  14813. 'Inline',
  14814. 'Empty',
  14815. 'Common',
  14816. array(
  14817. 'src*' => 'URI#embedded',
  14818. 'type' => 'Enum#application/x-shockwave-flash',
  14819. 'width' => 'Pixels#' . $max,
  14820. 'height' => 'Pixels#' . $max,
  14821. 'allowscriptaccess' => 'Enum#never',
  14822. 'allownetworking' => 'Enum#internal',
  14823. 'flashvars' => 'Text',
  14824. 'wmode' => 'Enum#window,transparent,opaque',
  14825. 'name' => 'ID',
  14826. )
  14827. );
  14828. $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
  14829. }
  14830. }
  14831. /**
  14832. * A "safe" object module. In theory, objects permitted by this module will
  14833. * be safe, and untrusted users can be allowed to embed arbitrary flash objects
  14834. * (maybe other types too, but only Flash is supported as of right now).
  14835. * Highly experimental.
  14836. */
  14837. class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
  14838. {
  14839. /**
  14840. * @type string
  14841. */
  14842. public $name = 'SafeObject';
  14843. /**
  14844. * @param HTMLPurifier_Config $config
  14845. */
  14846. public function setup($config)
  14847. {
  14848. // These definitions are not intrinsically safe: the attribute transforms
  14849. // are a vital part of ensuring safety.
  14850. $max = $config->get('HTML.MaxImgLength');
  14851. $object = $this->addElement(
  14852. 'object',
  14853. 'Inline',
  14854. 'Optional: param | Flow | #PCDATA',
  14855. 'Common',
  14856. array(
  14857. // While technically not required by the spec, we're forcing
  14858. // it to this value.
  14859. 'type' => 'Enum#application/x-shockwave-flash',
  14860. 'width' => 'Pixels#' . $max,
  14861. 'height' => 'Pixels#' . $max,
  14862. 'data' => 'URI#embedded',
  14863. 'codebase' => new HTMLPurifier_AttrDef_Enum(
  14864. array(
  14865. 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
  14866. )
  14867. ),
  14868. )
  14869. );
  14870. $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
  14871. $param = $this->addElement(
  14872. 'param',
  14873. false,
  14874. 'Empty',
  14875. false,
  14876. array(
  14877. 'id' => 'ID',
  14878. 'name*' => 'Text',
  14879. 'value' => 'Text'
  14880. )
  14881. );
  14882. $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
  14883. $this->info_injector[] = 'SafeObject';
  14884. }
  14885. }
  14886. /**
  14887. * A "safe" script module. No inline JS is allowed, and pointed to JS
  14888. * files must match whitelist.
  14889. */
  14890. class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
  14891. {
  14892. /**
  14893. * @type string
  14894. */
  14895. public $name = 'SafeScripting';
  14896. /**
  14897. * @param HTMLPurifier_Config $config
  14898. */
  14899. public function setup($config)
  14900. {
  14901. // These definitions are not intrinsically safe: the attribute transforms
  14902. // are a vital part of ensuring safety.
  14903. $allowed = $config->get('HTML.SafeScripting');
  14904. $script = $this->addElement(
  14905. 'script',
  14906. 'Inline',
  14907. 'Empty',
  14908. null,
  14909. array(
  14910. // While technically not required by the spec, we're forcing
  14911. // it to this value.
  14912. 'type' => 'Enum#text/javascript',
  14913. 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
  14914. )
  14915. );
  14916. $script->attr_transform_pre[] =
  14917. $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
  14918. }
  14919. }
  14920. /*
  14921. WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
  14922. INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
  14923. */
  14924. /**
  14925. * XHTML 1.1 Scripting module, defines elements that are used to contain
  14926. * information pertaining to executable scripts or the lack of support
  14927. * for executable scripts.
  14928. * @note This module does not contain inline scripting elements
  14929. */
  14930. class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
  14931. {
  14932. /**
  14933. * @type string
  14934. */
  14935. public $name = 'Scripting';
  14936. /**
  14937. * @type array
  14938. */
  14939. public $elements = array('script', 'noscript');
  14940. /**
  14941. * @type array
  14942. */
  14943. public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
  14944. /**
  14945. * @type bool
  14946. */
  14947. public $safe = false;
  14948. /**
  14949. * @param HTMLPurifier_Config $config
  14950. */
  14951. public function setup($config)
  14952. {
  14953. // TODO: create custom child-definition for noscript that
  14954. // auto-wraps stray #PCDATA in a similar manner to
  14955. // blockquote's custom definition (we would use it but
  14956. // blockquote's contents are optional while noscript's contents
  14957. // are required)
  14958. // TODO: convert this to new syntax, main problem is getting
  14959. // both content sets working
  14960. // In theory, this could be safe, but I don't see any reason to
  14961. // allow it.
  14962. $this->info['noscript'] = new HTMLPurifier_ElementDef();
  14963. $this->info['noscript']->attr = array(0 => array('Common'));
  14964. $this->info['noscript']->content_model = 'Heading | List | Block';
  14965. $this->info['noscript']->content_model_type = 'required';
  14966. $this->info['script'] = new HTMLPurifier_ElementDef();
  14967. $this->info['script']->attr = array(
  14968. 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
  14969. 'src' => new HTMLPurifier_AttrDef_URI(true),
  14970. 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
  14971. );
  14972. $this->info['script']->content_model = '#PCDATA';
  14973. $this->info['script']->content_model_type = 'optional';
  14974. $this->info['script']->attr_transform_pre[] =
  14975. $this->info['script']->attr_transform_post[] =
  14976. new HTMLPurifier_AttrTransform_ScriptRequired();
  14977. }
  14978. }
  14979. /**
  14980. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  14981. * Module.
  14982. */
  14983. class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
  14984. {
  14985. /**
  14986. * @type string
  14987. */
  14988. public $name = 'StyleAttribute';
  14989. /**
  14990. * @type array
  14991. */
  14992. public $attr_collections = array(
  14993. // The inclusion routine differs from the Abstract Modules but
  14994. // is in line with the DTD and XML Schemas.
  14995. 'Style' => array('style' => false), // see constructor
  14996. 'Core' => array(0 => array('Style'))
  14997. );
  14998. /**
  14999. * @param HTMLPurifier_Config $config
  15000. */
  15001. public function setup($config)
  15002. {
  15003. $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
  15004. }
  15005. }
  15006. /**
  15007. * XHTML 1.1 Tables Module, fully defines accessible table elements.
  15008. */
  15009. class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
  15010. {
  15011. /**
  15012. * @type string
  15013. */
  15014. public $name = 'Tables';
  15015. /**
  15016. * @param HTMLPurifier_Config $config
  15017. */
  15018. public function setup($config)
  15019. {
  15020. $this->addElement('caption', false, 'Inline', 'Common');
  15021. $this->addElement(
  15022. 'table',
  15023. 'Block',
  15024. new HTMLPurifier_ChildDef_Table(),
  15025. 'Common',
  15026. array(
  15027. 'border' => 'Pixels',
  15028. 'cellpadding' => 'Length',
  15029. 'cellspacing' => 'Length',
  15030. 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
  15031. 'rules' => 'Enum#none,groups,rows,cols,all',
  15032. 'summary' => 'Text',
  15033. 'width' => 'Length'
  15034. )
  15035. );
  15036. // common attributes
  15037. $cell_align = array(
  15038. 'align' => 'Enum#left,center,right,justify,char',
  15039. 'charoff' => 'Length',
  15040. 'valign' => 'Enum#top,middle,bottom,baseline',
  15041. );
  15042. $cell_t = array_merge(
  15043. array(
  15044. 'abbr' => 'Text',
  15045. 'colspan' => 'Number',
  15046. 'rowspan' => 'Number',
  15047. // Apparently, as of HTML5 this attribute only applies
  15048. // to 'th' elements.
  15049. 'scope' => 'Enum#row,col,rowgroup,colgroup',
  15050. ),
  15051. $cell_align
  15052. );
  15053. $this->addElement('td', false, 'Flow', 'Common', $cell_t);
  15054. $this->addElement('th', false, 'Flow', 'Common', $cell_t);
  15055. $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
  15056. $cell_col = array_merge(
  15057. array(
  15058. 'span' => 'Number',
  15059. 'width' => 'MultiLength',
  15060. ),
  15061. $cell_align
  15062. );
  15063. $this->addElement('col', false, 'Empty', 'Common', $cell_col);
  15064. $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
  15065. $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
  15066. $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
  15067. $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
  15068. }
  15069. }
  15070. /**
  15071. * XHTML 1.1 Target Module, defines target attribute in link elements.
  15072. */
  15073. class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
  15074. {
  15075. /**
  15076. * @type string
  15077. */
  15078. public $name = 'Target';
  15079. /**
  15080. * @param HTMLPurifier_Config $config
  15081. */
  15082. public function setup($config)
  15083. {
  15084. $elements = array('a');
  15085. foreach ($elements as $name) {
  15086. $e = $this->addBlankElement($name);
  15087. $e->attr = array(
  15088. 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
  15089. );
  15090. }
  15091. }
  15092. }
  15093. /**
  15094. * Module adds the target=blank attribute transformation to a tags. It
  15095. * is enabled by HTML.TargetBlank
  15096. */
  15097. class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
  15098. {
  15099. /**
  15100. * @type string
  15101. */
  15102. public $name = 'TargetBlank';
  15103. /**
  15104. * @param HTMLPurifier_Config $config
  15105. */
  15106. public function setup($config)
  15107. {
  15108. $a = $this->addBlankElement('a');
  15109. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
  15110. }
  15111. }
  15112. /**
  15113. * Module adds the target-based noopener attribute transformation to a tags. It
  15114. * is enabled by HTML.TargetNoopener
  15115. */
  15116. class HTMLPurifier_HTMLModule_TargetNoopener extends HTMLPurifier_HTMLModule
  15117. {
  15118. /**
  15119. * @type string
  15120. */
  15121. public $name = 'TargetNoopener';
  15122. /**
  15123. * @param HTMLPurifier_Config $config
  15124. */
  15125. public function setup($config) {
  15126. $a = $this->addBlankElement('a');
  15127. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoopener();
  15128. }
  15129. }
  15130. /**
  15131. * Module adds the target-based noreferrer attribute transformation to a tags. It
  15132. * is enabled by HTML.TargetNoreferrer
  15133. */
  15134. class HTMLPurifier_HTMLModule_TargetNoreferrer extends HTMLPurifier_HTMLModule
  15135. {
  15136. /**
  15137. * @type string
  15138. */
  15139. public $name = 'TargetNoreferrer';
  15140. /**
  15141. * @param HTMLPurifier_Config $config
  15142. */
  15143. public function setup($config) {
  15144. $a = $this->addBlankElement('a');
  15145. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetNoreferrer();
  15146. }
  15147. }
  15148. /**
  15149. * XHTML 1.1 Text Module, defines basic text containers. Core Module.
  15150. * @note In the normative XML Schema specification, this module
  15151. * is further abstracted into the following modules:
  15152. * - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
  15153. * - Block Structural (div, p)
  15154. * - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
  15155. * - Inline Structural (br, span)
  15156. * This module, functionally, does not distinguish between these
  15157. * sub-modules, but the code is internally structured to reflect
  15158. * these distinctions.
  15159. */
  15160. class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
  15161. {
  15162. /**
  15163. * @type string
  15164. */
  15165. public $name = 'Text';
  15166. /**
  15167. * @type array
  15168. */
  15169. public $content_sets = array(
  15170. 'Flow' => 'Heading | Block | Inline'
  15171. );
  15172. /**
  15173. * @param HTMLPurifier_Config $config
  15174. */
  15175. public function setup($config)
  15176. {
  15177. // Inline Phrasal -------------------------------------------------
  15178. $this->addElement('abbr', 'Inline', 'Inline', 'Common');
  15179. $this->addElement('acronym', 'Inline', 'Inline', 'Common');
  15180. $this->addElement('cite', 'Inline', 'Inline', 'Common');
  15181. $this->addElement('dfn', 'Inline', 'Inline', 'Common');
  15182. $this->addElement('kbd', 'Inline', 'Inline', 'Common');
  15183. $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
  15184. $this->addElement('samp', 'Inline', 'Inline', 'Common');
  15185. $this->addElement('var', 'Inline', 'Inline', 'Common');
  15186. $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
  15187. $em->formatting = true;
  15188. $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
  15189. $strong->formatting = true;
  15190. $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
  15191. $code->formatting = true;
  15192. // Inline Structural ----------------------------------------------
  15193. $this->addElement('span', 'Inline', 'Inline', 'Common');
  15194. $this->addElement('br', 'Inline', 'Empty', 'Core');
  15195. // Block Phrasal --------------------------------------------------
  15196. $this->addElement('address', 'Block', 'Inline', 'Common');
  15197. $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
  15198. $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
  15199. $pre->excludes = $this->makeLookup(
  15200. 'img',
  15201. 'big',
  15202. 'small',
  15203. 'object',
  15204. 'applet',
  15205. 'font',
  15206. 'basefont'
  15207. );
  15208. $this->addElement('h1', 'Heading', 'Inline', 'Common');
  15209. $this->addElement('h2', 'Heading', 'Inline', 'Common');
  15210. $this->addElement('h3', 'Heading', 'Inline', 'Common');
  15211. $this->addElement('h4', 'Heading', 'Inline', 'Common');
  15212. $this->addElement('h5', 'Heading', 'Inline', 'Common');
  15213. $this->addElement('h6', 'Heading', 'Inline', 'Common');
  15214. // Block Structural -----------------------------------------------
  15215. $p = $this->addElement('p', 'Block', 'Inline', 'Common');
  15216. $p->autoclose = array_flip(
  15217. array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
  15218. );
  15219. $this->addElement('div', 'Block', 'Flow', 'Common');
  15220. }
  15221. }
  15222. /**
  15223. * Abstract class for a set of proprietary modules that clean up (tidy)
  15224. * poorly written HTML.
  15225. * @todo Figure out how to protect some of these methods/properties
  15226. */
  15227. class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
  15228. {
  15229. /**
  15230. * List of supported levels.
  15231. * Index zero is a special case "no fixes" level.
  15232. * @type array
  15233. */
  15234. public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
  15235. /**
  15236. * Default level to place all fixes in.
  15237. * Disabled by default.
  15238. * @type string
  15239. */
  15240. public $defaultLevel = null;
  15241. /**
  15242. * Lists of fixes used by getFixesForLevel().
  15243. * Format is:
  15244. * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
  15245. * @type array
  15246. */
  15247. public $fixesForLevel = array(
  15248. 'light' => array(),
  15249. 'medium' => array(),
  15250. 'heavy' => array()
  15251. );
  15252. /**
  15253. * Lazy load constructs the module by determining the necessary
  15254. * fixes to create and then delegating to the populate() function.
  15255. * @param HTMLPurifier_Config $config
  15256. * @todo Wildcard matching and error reporting when an added or
  15257. * subtracted fix has no effect.
  15258. */
  15259. public function setup($config)
  15260. {
  15261. // create fixes, initialize fixesForLevel
  15262. $fixes = $this->makeFixes();
  15263. $this->makeFixesForLevel($fixes);
  15264. // figure out which fixes to use
  15265. $level = $config->get('HTML.TidyLevel');
  15266. $fixes_lookup = $this->getFixesForLevel($level);
  15267. // get custom fix declarations: these need namespace processing
  15268. $add_fixes = $config->get('HTML.TidyAdd');
  15269. $remove_fixes = $config->get('HTML.TidyRemove');
  15270. foreach ($fixes as $name => $fix) {
  15271. // needs to be refactored a little to implement globbing
  15272. if (isset($remove_fixes[$name]) ||
  15273. (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
  15274. unset($fixes[$name]);
  15275. }
  15276. }
  15277. // populate this module with necessary fixes
  15278. $this->populate($fixes);
  15279. }
  15280. /**
  15281. * Retrieves all fixes per a level, returning fixes for that specific
  15282. * level as well as all levels below it.
  15283. * @param string $level level identifier, see $levels for valid values
  15284. * @return array Lookup up table of fixes
  15285. */
  15286. public function getFixesForLevel($level)
  15287. {
  15288. if ($level == $this->levels[0]) {
  15289. return array();
  15290. }
  15291. $activated_levels = array();
  15292. for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
  15293. $activated_levels[] = $this->levels[$i];
  15294. if ($this->levels[$i] == $level) {
  15295. break;
  15296. }
  15297. }
  15298. if ($i == $c) {
  15299. trigger_error(
  15300. 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
  15301. E_USER_WARNING
  15302. );
  15303. return array();
  15304. }
  15305. $ret = array();
  15306. foreach ($activated_levels as $level) {
  15307. foreach ($this->fixesForLevel[$level] as $fix) {
  15308. $ret[$fix] = true;
  15309. }
  15310. }
  15311. return $ret;
  15312. }
  15313. /**
  15314. * Dynamically populates the $fixesForLevel member variable using
  15315. * the fixes array. It may be custom overloaded, used in conjunction
  15316. * with $defaultLevel, or not used at all.
  15317. * @param array $fixes
  15318. */
  15319. public function makeFixesForLevel($fixes)
  15320. {
  15321. if (!isset($this->defaultLevel)) {
  15322. return;
  15323. }
  15324. if (!isset($this->fixesForLevel[$this->defaultLevel])) {
  15325. trigger_error(
  15326. 'Default level ' . $this->defaultLevel . ' does not exist',
  15327. E_USER_ERROR
  15328. );
  15329. return;
  15330. }
  15331. $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
  15332. }
  15333. /**
  15334. * Populates the module with transforms and other special-case code
  15335. * based on a list of fixes passed to it
  15336. * @param array $fixes Lookup table of fixes to activate
  15337. */
  15338. public function populate($fixes)
  15339. {
  15340. foreach ($fixes as $name => $fix) {
  15341. // determine what the fix is for
  15342. list($type, $params) = $this->getFixType($name);
  15343. switch ($type) {
  15344. case 'attr_transform_pre':
  15345. case 'attr_transform_post':
  15346. $attr = $params['attr'];
  15347. if (isset($params['element'])) {
  15348. $element = $params['element'];
  15349. if (empty($this->info[$element])) {
  15350. $e = $this->addBlankElement($element);
  15351. } else {
  15352. $e = $this->info[$element];
  15353. }
  15354. } else {
  15355. $type = "info_$type";
  15356. $e = $this;
  15357. }
  15358. // PHP does some weird parsing when I do
  15359. // $e->$type[$attr], so I have to assign a ref.
  15360. $f =& $e->$type;
  15361. $f[$attr] = $fix;
  15362. break;
  15363. case 'tag_transform':
  15364. $this->info_tag_transform[$params['element']] = $fix;
  15365. break;
  15366. case 'child':
  15367. case 'content_model_type':
  15368. $element = $params['element'];
  15369. if (empty($this->info[$element])) {
  15370. $e = $this->addBlankElement($element);
  15371. } else {
  15372. $e = $this->info[$element];
  15373. }
  15374. $e->$type = $fix;
  15375. break;
  15376. default:
  15377. trigger_error("Fix type $type not supported", E_USER_ERROR);
  15378. break;
  15379. }
  15380. }
  15381. }
  15382. /**
  15383. * Parses a fix name and determines what kind of fix it is, as well
  15384. * as other information defined by the fix
  15385. * @param $name String name of fix
  15386. * @return array(string $fix_type, array $fix_parameters)
  15387. * @note $fix_parameters is type dependant, see populate() for usage
  15388. * of these parameters
  15389. */
  15390. public function getFixType($name)
  15391. {
  15392. // parse it
  15393. $property = $attr = null;
  15394. if (strpos($name, '#') !== false) {
  15395. list($name, $property) = explode('#', $name);
  15396. }
  15397. if (strpos($name, '@') !== false) {
  15398. list($name, $attr) = explode('@', $name);
  15399. }
  15400. // figure out the parameters
  15401. $params = array();
  15402. if ($name !== '') {
  15403. $params['element'] = $name;
  15404. }
  15405. if (!is_null($attr)) {
  15406. $params['attr'] = $attr;
  15407. }
  15408. // special case: attribute transform
  15409. if (!is_null($attr)) {
  15410. if (is_null($property)) {
  15411. $property = 'pre';
  15412. }
  15413. $type = 'attr_transform_' . $property;
  15414. return array($type, $params);
  15415. }
  15416. // special case: tag transform
  15417. if (is_null($property)) {
  15418. return array('tag_transform', $params);
  15419. }
  15420. return array($property, $params);
  15421. }
  15422. /**
  15423. * Defines all fixes the module will perform in a compact
  15424. * associative array of fix name to fix implementation.
  15425. * @return array
  15426. */
  15427. public function makeFixes()
  15428. {
  15429. }
  15430. }
  15431. class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
  15432. {
  15433. /**
  15434. * @type string
  15435. */
  15436. public $name = 'XMLCommonAttributes';
  15437. /**
  15438. * @type array
  15439. */
  15440. public $attr_collections = array(
  15441. 'Lang' => array(
  15442. 'xml:lang' => 'LanguageCode',
  15443. )
  15444. );
  15445. }
  15446. /**
  15447. * Name is deprecated, but allowed in strict doctypes, so onl
  15448. */
  15449. class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
  15450. {
  15451. /**
  15452. * @type string
  15453. */
  15454. public $name = 'Tidy_Name';
  15455. /**
  15456. * @type string
  15457. */
  15458. public $defaultLevel = 'heavy';
  15459. /**
  15460. * @return array
  15461. */
  15462. public function makeFixes()
  15463. {
  15464. $r = array();
  15465. // @name for img, a -----------------------------------------------
  15466. // Technically, it's allowed even on strict, so we allow authors to use
  15467. // it. However, it's deprecated in future versions of XHTML.
  15468. $r['img@name'] =
  15469. $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
  15470. return $r;
  15471. }
  15472. }
  15473. class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
  15474. {
  15475. /**
  15476. * @type string
  15477. */
  15478. public $name = 'Tidy_Proprietary';
  15479. /**
  15480. * @type string
  15481. */
  15482. public $defaultLevel = 'light';
  15483. /**
  15484. * @return array
  15485. */
  15486. public function makeFixes()
  15487. {
  15488. $r = array();
  15489. $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
  15490. $r['td@background'] = new HTMLPurifier_AttrTransform_Background();
  15491. $r['th@background'] = new HTMLPurifier_AttrTransform_Background();
  15492. $r['tr@background'] = new HTMLPurifier_AttrTransform_Background();
  15493. $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
  15494. $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
  15495. $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
  15496. $r['table@height'] = new HTMLPurifier_AttrTransform_Length('height');
  15497. return $r;
  15498. }
  15499. }
  15500. class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
  15501. {
  15502. /**
  15503. * @return array
  15504. */
  15505. public function makeFixes()
  15506. {
  15507. $r = array();
  15508. // == deprecated tag transforms ===================================
  15509. $r['font'] = new HTMLPurifier_TagTransform_Font();
  15510. $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
  15511. $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
  15512. $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
  15513. $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
  15514. $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15515. $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15516. // == deprecated attribute transforms =============================
  15517. $r['caption@align'] =
  15518. new HTMLPurifier_AttrTransform_EnumToCSS(
  15519. 'align',
  15520. array(
  15521. // we're following IE's behavior, not Firefox's, due
  15522. // to the fact that no one supports caption-side:right,
  15523. // W3C included (with CSS 2.1). This is a slightly
  15524. // unreasonable attribute!
  15525. 'left' => 'text-align:left;',
  15526. 'right' => 'text-align:right;',
  15527. 'top' => 'caption-side:top;',
  15528. 'bottom' => 'caption-side:bottom;' // not supported by IE
  15529. )
  15530. );
  15531. // @align for img -------------------------------------------------
  15532. $r['img@align'] =
  15533. new HTMLPurifier_AttrTransform_EnumToCSS(
  15534. 'align',
  15535. array(
  15536. 'left' => 'float:left;',
  15537. 'right' => 'float:right;',
  15538. 'top' => 'vertical-align:top;',
  15539. 'middle' => 'vertical-align:middle;',
  15540. 'bottom' => 'vertical-align:baseline;',
  15541. )
  15542. );
  15543. // @align for table -----------------------------------------------
  15544. $r['table@align'] =
  15545. new HTMLPurifier_AttrTransform_EnumToCSS(
  15546. 'align',
  15547. array(
  15548. 'left' => 'float:left;',
  15549. 'center' => 'margin-left:auto;margin-right:auto;',
  15550. 'right' => 'float:right;'
  15551. )
  15552. );
  15553. // @align for hr -----------------------------------------------
  15554. $r['hr@align'] =
  15555. new HTMLPurifier_AttrTransform_EnumToCSS(
  15556. 'align',
  15557. array(
  15558. // we use both text-align and margin because these work
  15559. // for different browsers (IE and Firefox, respectively)
  15560. // and the melange makes for a pretty cross-compatible
  15561. // solution
  15562. 'left' => 'margin-left:0;margin-right:auto;text-align:left;',
  15563. 'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
  15564. 'right' => 'margin-left:auto;margin-right:0;text-align:right;'
  15565. )
  15566. );
  15567. // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
  15568. // {{{
  15569. $align_lookup = array();
  15570. $align_values = array('left', 'right', 'center', 'justify');
  15571. foreach ($align_values as $v) {
  15572. $align_lookup[$v] = "text-align:$v;";
  15573. }
  15574. // }}}
  15575. $r['h1@align'] =
  15576. $r['h2@align'] =
  15577. $r['h3@align'] =
  15578. $r['h4@align'] =
  15579. $r['h5@align'] =
  15580. $r['h6@align'] =
  15581. $r['p@align'] =
  15582. $r['div@align'] =
  15583. new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
  15584. // @bgcolor for table, tr, td, th ---------------------------------
  15585. $r['table@bgcolor'] =
  15586. $r['td@bgcolor'] =
  15587. $r['th@bgcolor'] =
  15588. new HTMLPurifier_AttrTransform_BgColor();
  15589. // @border for img ------------------------------------------------
  15590. $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
  15591. // @clear for br --------------------------------------------------
  15592. $r['br@clear'] =
  15593. new HTMLPurifier_AttrTransform_EnumToCSS(
  15594. 'clear',
  15595. array(
  15596. 'left' => 'clear:left;',
  15597. 'right' => 'clear:right;',
  15598. 'all' => 'clear:both;',
  15599. 'none' => 'clear:none;',
  15600. )
  15601. );
  15602. // @height for td, th ---------------------------------------------
  15603. $r['td@height'] =
  15604. $r['th@height'] =
  15605. new HTMLPurifier_AttrTransform_Length('height');
  15606. // @hspace for img ------------------------------------------------
  15607. $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
  15608. // @noshade for hr ------------------------------------------------
  15609. // this transformation is not precise but often good enough.
  15610. // different browsers use different styles to designate noshade
  15611. $r['hr@noshade'] =
  15612. new HTMLPurifier_AttrTransform_BoolToCSS(
  15613. 'noshade',
  15614. 'color:#808080;background-color:#808080;border:0;'
  15615. );
  15616. // @nowrap for td, th ---------------------------------------------
  15617. $r['td@nowrap'] =
  15618. $r['th@nowrap'] =
  15619. new HTMLPurifier_AttrTransform_BoolToCSS(
  15620. 'nowrap',
  15621. 'white-space:nowrap;'
  15622. );
  15623. // @size for hr --------------------------------------------------
  15624. $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
  15625. // @type for li, ol, ul -------------------------------------------
  15626. // {{{
  15627. $ul_types = array(
  15628. 'disc' => 'list-style-type:disc;',
  15629. 'square' => 'list-style-type:square;',
  15630. 'circle' => 'list-style-type:circle;'
  15631. );
  15632. $ol_types = array(
  15633. '1' => 'list-style-type:decimal;',
  15634. 'i' => 'list-style-type:lower-roman;',
  15635. 'I' => 'list-style-type:upper-roman;',
  15636. 'a' => 'list-style-type:lower-alpha;',
  15637. 'A' => 'list-style-type:upper-alpha;'
  15638. );
  15639. $li_types = $ul_types + $ol_types;
  15640. // }}}
  15641. $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
  15642. $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
  15643. $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
  15644. // @vspace for img ------------------------------------------------
  15645. $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
  15646. // @width for hr, td, th ------------------------------------------
  15647. $r['td@width'] =
  15648. $r['th@width'] =
  15649. $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
  15650. return $r;
  15651. }
  15652. }
  15653. class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15654. {
  15655. /**
  15656. * @type string
  15657. */
  15658. public $name = 'Tidy_Strict';
  15659. /**
  15660. * @type string
  15661. */
  15662. public $defaultLevel = 'light';
  15663. /**
  15664. * @return array
  15665. */
  15666. public function makeFixes()
  15667. {
  15668. $r = parent::makeFixes();
  15669. $r['blockquote#content_model_type'] = 'strictblockquote';
  15670. return $r;
  15671. }
  15672. /**
  15673. * @type bool
  15674. */
  15675. public $defines_child_def = true;
  15676. /**
  15677. * @param HTMLPurifier_ElementDef $def
  15678. * @return HTMLPurifier_ChildDef_StrictBlockquote
  15679. */
  15680. public function getChildDef($def)
  15681. {
  15682. if ($def->content_model_type != 'strictblockquote') {
  15683. return parent::getChildDef($def);
  15684. }
  15685. return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
  15686. }
  15687. }
  15688. class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15689. {
  15690. /**
  15691. * @type string
  15692. */
  15693. public $name = 'Tidy_Transitional';
  15694. /**
  15695. * @type string
  15696. */
  15697. public $defaultLevel = 'heavy';
  15698. }
  15699. class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
  15700. {
  15701. /**
  15702. * @type string
  15703. */
  15704. public $name = 'Tidy_XHTML';
  15705. /**
  15706. * @type string
  15707. */
  15708. public $defaultLevel = 'medium';
  15709. /**
  15710. * @return array
  15711. */
  15712. public function makeFixes()
  15713. {
  15714. $r = array();
  15715. $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
  15716. return $r;
  15717. }
  15718. }
  15719. /**
  15720. * Injector that auto paragraphs text in the root node based on
  15721. * double-spacing.
  15722. * @todo Ensure all states are unit tested, including variations as well.
  15723. * @todo Make a graph of the flow control for this Injector.
  15724. */
  15725. class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
  15726. {
  15727. /**
  15728. * @type string
  15729. */
  15730. public $name = 'AutoParagraph';
  15731. /**
  15732. * @type array
  15733. */
  15734. public $needed = array('p');
  15735. /**
  15736. * @return HTMLPurifier_Token_Start
  15737. */
  15738. private function _pStart()
  15739. {
  15740. $par = new HTMLPurifier_Token_Start('p');
  15741. $par->armor['MakeWellFormed_TagClosedError'] = true;
  15742. return $par;
  15743. }
  15744. /**
  15745. * @param HTMLPurifier_Token_Text $token
  15746. */
  15747. public function handleText(&$token)
  15748. {
  15749. $text = $token->data;
  15750. // Does the current parent allow <p> tags?
  15751. if ($this->allowsElement('p')) {
  15752. if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
  15753. // Note that we have differing behavior when dealing with text
  15754. // in the anonymous root node, or a node inside the document.
  15755. // If the text as a double-newline, the treatment is the same;
  15756. // if it doesn't, see the next if-block if you're in the document.
  15757. $i = $nesting = null;
  15758. if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
  15759. // State 1.1: ... ^ (whitespace, then document end)
  15760. // ----
  15761. // This is a degenerate case
  15762. } else {
  15763. if (!$token->is_whitespace || $this->_isInline($current)) {
  15764. // State 1.2: PAR1
  15765. // ----
  15766. // State 1.3: PAR1\n\nPAR2
  15767. // ------------
  15768. // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
  15769. // ------------
  15770. $token = array($this->_pStart());
  15771. $this->_splitText($text, $token);
  15772. } else {
  15773. // State 1.5: \n<hr />
  15774. // --
  15775. }
  15776. }
  15777. } else {
  15778. // State 2: <div>PAR1... (similar to 1.4)
  15779. // ----
  15780. // We're in an element that allows paragraph tags, but we're not
  15781. // sure if we're going to need them.
  15782. if ($this->_pLookAhead()) {
  15783. // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
  15784. // ----
  15785. // Note: This will always be the first child, since any
  15786. // previous inline element would have triggered this very
  15787. // same routine, and found the double newline. One possible
  15788. // exception would be a comment.
  15789. $token = array($this->_pStart(), $token);
  15790. } else {
  15791. // State 2.2.1: <div>PAR1<div>
  15792. // ----
  15793. // State 2.2.2: <div>PAR1<b>PAR1</b></div>
  15794. // ----
  15795. }
  15796. }
  15797. // Is the current parent a <p> tag?
  15798. } elseif (!empty($this->currentNesting) &&
  15799. $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
  15800. // State 3.1: ...<p>PAR1
  15801. // ----
  15802. // State 3.2: ...<p>PAR1\n\nPAR2
  15803. // ------------
  15804. $token = array();
  15805. $this->_splitText($text, $token);
  15806. // Abort!
  15807. } else {
  15808. // State 4.1: ...<b>PAR1
  15809. // ----
  15810. // State 4.2: ...<b>PAR1\n\nPAR2
  15811. // ------------
  15812. }
  15813. }
  15814. /**
  15815. * @param HTMLPurifier_Token $token
  15816. */
  15817. public function handleElement(&$token)
  15818. {
  15819. // We don't have to check if we're already in a <p> tag for block
  15820. // tokens, because the tag would have been autoclosed by MakeWellFormed.
  15821. if ($this->allowsElement('p')) {
  15822. if (!empty($this->currentNesting)) {
  15823. if ($this->_isInline($token)) {
  15824. // State 1: <div>...<b>
  15825. // ---
  15826. // Check if this token is adjacent to the parent token
  15827. // (seek backwards until token isn't whitespace)
  15828. $i = null;
  15829. $this->backward($i, $prev);
  15830. if (!$prev instanceof HTMLPurifier_Token_Start) {
  15831. // Token wasn't adjacent
  15832. if ($prev instanceof HTMLPurifier_Token_Text &&
  15833. substr($prev->data, -2) === "\n\n"
  15834. ) {
  15835. // State 1.1.4: <div><p>PAR1</p>\n\n<b>
  15836. // ---
  15837. // Quite frankly, this should be handled by splitText
  15838. $token = array($this->_pStart(), $token);
  15839. } else {
  15840. // State 1.1.1: <div><p>PAR1</p><b>
  15841. // ---
  15842. // State 1.1.2: <div><br /><b>
  15843. // ---
  15844. // State 1.1.3: <div>PAR<b>
  15845. // ---
  15846. }
  15847. } else {
  15848. // State 1.2.1: <div><b>
  15849. // ---
  15850. // Lookahead to see if <p> is needed.
  15851. if ($this->_pLookAhead()) {
  15852. // State 1.3.1: <div><b>PAR1\n\nPAR2
  15853. // ---
  15854. $token = array($this->_pStart(), $token);
  15855. } else {
  15856. // State 1.3.2: <div><b>PAR1</b></div>
  15857. // ---
  15858. // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
  15859. // ---
  15860. }
  15861. }
  15862. } else {
  15863. // State 2.3: ...<div>
  15864. // -----
  15865. }
  15866. } else {
  15867. if ($this->_isInline($token)) {
  15868. // State 3.1: <b>
  15869. // ---
  15870. // This is where the {p} tag is inserted, not reflected in
  15871. // inputTokens yet, however.
  15872. $token = array($this->_pStart(), $token);
  15873. } else {
  15874. // State 3.2: <div>
  15875. // -----
  15876. }
  15877. $i = null;
  15878. if ($this->backward($i, $prev)) {
  15879. if (!$prev instanceof HTMLPurifier_Token_Text) {
  15880. // State 3.1.1: ...</p>{p}<b>
  15881. // ---
  15882. // State 3.2.1: ...</p><div>
  15883. // -----
  15884. if (!is_array($token)) {
  15885. $token = array($token);
  15886. }
  15887. array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
  15888. } else {
  15889. // State 3.1.2: ...</p>\n\n{p}<b>
  15890. // ---
  15891. // State 3.2.2: ...</p>\n\n<div>
  15892. // -----
  15893. // Note: PAR<ELEM> cannot occur because PAR would have been
  15894. // wrapped in <p> tags.
  15895. }
  15896. }
  15897. }
  15898. } else {
  15899. // State 2.2: <ul><li>
  15900. // ----
  15901. // State 2.4: <p><b>
  15902. // ---
  15903. }
  15904. }
  15905. /**
  15906. * Splits up a text in paragraph tokens and appends them
  15907. * to the result stream that will replace the original
  15908. * @param string $data String text data that will be processed
  15909. * into paragraphs
  15910. * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
  15911. * tags will be appended onto
  15912. */
  15913. private function _splitText($data, &$result)
  15914. {
  15915. $raw_paragraphs = explode("\n\n", $data);
  15916. $paragraphs = array(); // without empty paragraphs
  15917. $needs_start = false;
  15918. $needs_end = false;
  15919. $c = count($raw_paragraphs);
  15920. if ($c == 1) {
  15921. // There were no double-newlines, abort quickly. In theory this
  15922. // should never happen.
  15923. $result[] = new HTMLPurifier_Token_Text($data);
  15924. return;
  15925. }
  15926. for ($i = 0; $i < $c; $i++) {
  15927. $par = $raw_paragraphs[$i];
  15928. if (trim($par) !== '') {
  15929. $paragraphs[] = $par;
  15930. } else {
  15931. if ($i == 0) {
  15932. // Double newline at the front
  15933. if (empty($result)) {
  15934. // The empty result indicates that the AutoParagraph
  15935. // injector did not add any start paragraph tokens.
  15936. // This means that we have been in a paragraph for
  15937. // a while, and the newline means we should start a new one.
  15938. $result[] = new HTMLPurifier_Token_End('p');
  15939. $result[] = new HTMLPurifier_Token_Text("\n\n");
  15940. // However, the start token should only be added if
  15941. // there is more processing to be done (i.e. there are
  15942. // real paragraphs in here). If there are none, the
  15943. // next start paragraph tag will be handled by the
  15944. // next call to the injector
  15945. $needs_start = true;
  15946. } else {
  15947. // We just started a new paragraph!
  15948. // Reinstate a double-newline for presentation's sake, since
  15949. // it was in the source code.
  15950. array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
  15951. }
  15952. } elseif ($i + 1 == $c) {
  15953. // Double newline at the end
  15954. // There should be a trailing </p> when we're finally done.
  15955. $needs_end = true;
  15956. }
  15957. }
  15958. }
  15959. // Check if this was just a giant blob of whitespace. Move this earlier,
  15960. // perhaps?
  15961. if (empty($paragraphs)) {
  15962. return;
  15963. }
  15964. // Add the start tag indicated by \n\n at the beginning of $data
  15965. if ($needs_start) {
  15966. $result[] = $this->_pStart();
  15967. }
  15968. // Append the paragraphs onto the result
  15969. foreach ($paragraphs as $par) {
  15970. $result[] = new HTMLPurifier_Token_Text($par);
  15971. $result[] = new HTMLPurifier_Token_End('p');
  15972. $result[] = new HTMLPurifier_Token_Text("\n\n");
  15973. $result[] = $this->_pStart();
  15974. }
  15975. // Remove trailing start token; Injector will handle this later if
  15976. // it was indeed needed. This prevents from needing to do a lookahead,
  15977. // at the cost of a lookbehind later.
  15978. array_pop($result);
  15979. // If there is no need for an end tag, remove all of it and let
  15980. // MakeWellFormed close it later.
  15981. if (!$needs_end) {
  15982. array_pop($result); // removes \n\n
  15983. array_pop($result); // removes </p>
  15984. }
  15985. }
  15986. /**
  15987. * Returns true if passed token is inline (and, ergo, allowed in
  15988. * paragraph tags)
  15989. * @param HTMLPurifier_Token $token
  15990. * @return bool
  15991. */
  15992. private function _isInline($token)
  15993. {
  15994. return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
  15995. }
  15996. /**
  15997. * Looks ahead in the token list and determines whether or not we need
  15998. * to insert a <p> tag.
  15999. * @return bool
  16000. */
  16001. private function _pLookAhead()
  16002. {
  16003. if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
  16004. $nesting = 1;
  16005. } else {
  16006. $nesting = 0;
  16007. }
  16008. $ok = false;
  16009. $i = null;
  16010. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  16011. $result = $this->_checkNeedsP($current);
  16012. if ($result !== null) {
  16013. $ok = $result;
  16014. break;
  16015. }
  16016. }
  16017. return $ok;
  16018. }
  16019. /**
  16020. * Determines if a particular token requires an earlier inline token
  16021. * to get a paragraph. This should be used with _forwardUntilEndToken
  16022. * @param HTMLPurifier_Token $current
  16023. * @return bool
  16024. */
  16025. private function _checkNeedsP($current)
  16026. {
  16027. if ($current instanceof HTMLPurifier_Token_Start) {
  16028. if (!$this->_isInline($current)) {
  16029. // <div>PAR1<div>
  16030. // ----
  16031. // Terminate early, since we hit a block element
  16032. return false;
  16033. }
  16034. } elseif ($current instanceof HTMLPurifier_Token_Text) {
  16035. if (strpos($current->data, "\n\n") !== false) {
  16036. // <div>PAR1<b>PAR1\n\nPAR2
  16037. // ----
  16038. return true;
  16039. } else {
  16040. // <div>PAR1<b>PAR1...
  16041. // ----
  16042. }
  16043. }
  16044. return null;
  16045. }
  16046. }
  16047. /**
  16048. * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
  16049. */
  16050. class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
  16051. {
  16052. /**
  16053. * @type string
  16054. */
  16055. public $name = 'DisplayLinkURI';
  16056. /**
  16057. * @type array
  16058. */
  16059. public $needed = array('a');
  16060. /**
  16061. * @param $token
  16062. */
  16063. public function handleElement(&$token)
  16064. {
  16065. }
  16066. /**
  16067. * @param HTMLPurifier_Token $token
  16068. */
  16069. public function handleEnd(&$token)
  16070. {
  16071. if (isset($token->start->attr['href'])) {
  16072. $url = $token->start->attr['href'];
  16073. unset($token->start->attr['href']);
  16074. $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
  16075. } else {
  16076. // nothing to display
  16077. }
  16078. }
  16079. }
  16080. /**
  16081. * Injector that converts http, https and ftp text URLs to actual links.
  16082. */
  16083. class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
  16084. {
  16085. /**
  16086. * @type string
  16087. */
  16088. public $name = 'Linkify';
  16089. /**
  16090. * @type array
  16091. */
  16092. public $needed = array('a' => array('href'));
  16093. /**
  16094. * @param HTMLPurifier_Token $token
  16095. */
  16096. public function handleText(&$token)
  16097. {
  16098. if (!$this->allowsElement('a')) {
  16099. return;
  16100. }
  16101. if (strpos($token->data, '://') === false) {
  16102. // our really quick heuristic failed, abort
  16103. // this may not work so well if we want to match things like
  16104. // "google.com", but then again, most people don't
  16105. return;
  16106. }
  16107. // there is/are URL(s). Let's split the string.
  16108. // We use this regex:
  16109. // https://gist.github.com/gruber/249502
  16110. // but with @cscott's backtracking fix and also
  16111. // the Unicode characters un-Unicodified.
  16112. $bits = preg_split(
  16113. '/\\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',
  16114. $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  16115. $token = array();
  16116. // $i = index
  16117. // $c = count
  16118. // $l = is link
  16119. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  16120. if (!$l) {
  16121. if ($bits[$i] === '') {
  16122. continue;
  16123. }
  16124. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16125. } else {
  16126. $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
  16127. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16128. $token[] = new HTMLPurifier_Token_End('a');
  16129. }
  16130. }
  16131. }
  16132. }
  16133. /**
  16134. * Injector that converts configuration directive syntax %Namespace.Directive
  16135. * to links
  16136. */
  16137. class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
  16138. {
  16139. /**
  16140. * @type string
  16141. */
  16142. public $name = 'PurifierLinkify';
  16143. /**
  16144. * @type string
  16145. */
  16146. public $docURL;
  16147. /**
  16148. * @type array
  16149. */
  16150. public $needed = array('a' => array('href'));
  16151. /**
  16152. * @param HTMLPurifier_Config $config
  16153. * @param HTMLPurifier_Context $context
  16154. * @return string
  16155. */
  16156. public function prepare($config, $context)
  16157. {
  16158. $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
  16159. return parent::prepare($config, $context);
  16160. }
  16161. /**
  16162. * @param HTMLPurifier_Token $token
  16163. */
  16164. public function handleText(&$token)
  16165. {
  16166. if (!$this->allowsElement('a')) {
  16167. return;
  16168. }
  16169. if (strpos($token->data, '%') === false) {
  16170. return;
  16171. }
  16172. $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  16173. $token = array();
  16174. // $i = index
  16175. // $c = count
  16176. // $l = is link
  16177. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  16178. if (!$l) {
  16179. if ($bits[$i] === '') {
  16180. continue;
  16181. }
  16182. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  16183. } else {
  16184. $token[] = new HTMLPurifier_Token_Start(
  16185. 'a',
  16186. array('href' => str_replace('%s', $bits[$i], $this->docURL))
  16187. );
  16188. $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
  16189. $token[] = new HTMLPurifier_Token_End('a');
  16190. }
  16191. }
  16192. }
  16193. }
  16194. class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
  16195. {
  16196. /**
  16197. * @type HTMLPurifier_Context
  16198. */
  16199. private $context;
  16200. /**
  16201. * @type HTMLPurifier_Config
  16202. */
  16203. private $config;
  16204. /**
  16205. * @type HTMLPurifier_AttrValidator
  16206. */
  16207. private $attrValidator;
  16208. /**
  16209. * @type bool
  16210. */
  16211. private $removeNbsp;
  16212. /**
  16213. * @type bool
  16214. */
  16215. private $removeNbspExceptions;
  16216. /**
  16217. * Cached contents of %AutoFormat.RemoveEmpty.Predicate
  16218. * @type array
  16219. */
  16220. private $exclude;
  16221. /**
  16222. * @param HTMLPurifier_Config $config
  16223. * @param HTMLPurifier_Context $context
  16224. * @return void
  16225. */
  16226. public function prepare($config, $context)
  16227. {
  16228. parent::prepare($config, $context);
  16229. $this->config = $config;
  16230. $this->context = $context;
  16231. $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
  16232. $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
  16233. $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
  16234. foreach ($this->exclude as $key => $attrs) {
  16235. if (!is_array($attrs)) {
  16236. // HACK, see HTMLPurifier/Printer/ConfigForm.php
  16237. $this->exclude[$key] = explode(';', $attrs);
  16238. }
  16239. }
  16240. $this->attrValidator = new HTMLPurifier_AttrValidator();
  16241. }
  16242. /**
  16243. * @param HTMLPurifier_Token $token
  16244. */
  16245. public function handleElement(&$token)
  16246. {
  16247. if (!$token instanceof HTMLPurifier_Token_Start) {
  16248. return;
  16249. }
  16250. $next = false;
  16251. $deleted = 1; // the current tag
  16252. for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
  16253. $next = $this->inputZipper->back[$i];
  16254. if ($next instanceof HTMLPurifier_Token_Text) {
  16255. if ($next->is_whitespace) {
  16256. continue;
  16257. }
  16258. if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
  16259. $plain = str_replace("\xC2\xA0", "", $next->data);
  16260. $isWsOrNbsp = $plain === '' || ctype_space($plain);
  16261. if ($isWsOrNbsp) {
  16262. continue;
  16263. }
  16264. }
  16265. }
  16266. break;
  16267. }
  16268. if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
  16269. $this->attrValidator->validateToken($token, $this->config, $this->context);
  16270. $token->armor['ValidateAttributes'] = true;
  16271. if (isset($this->exclude[$token->name])) {
  16272. $r = true;
  16273. foreach ($this->exclude[$token->name] as $elem) {
  16274. if (!isset($token->attr[$elem])) $r = false;
  16275. }
  16276. if ($r) return;
  16277. }
  16278. if (isset($token->attr['id']) || isset($token->attr['name'])) {
  16279. return;
  16280. }
  16281. $token = $deleted + 1;
  16282. for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
  16283. $prev = $this->inputZipper->front[$b];
  16284. if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
  16285. continue;
  16286. }
  16287. break;
  16288. }
  16289. // This is safe because we removed the token that triggered this.
  16290. $this->rewindOffset($b+$deleted);
  16291. return;
  16292. }
  16293. }
  16294. }
  16295. /**
  16296. * Injector that removes spans with no attributes
  16297. */
  16298. class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
  16299. {
  16300. /**
  16301. * @type string
  16302. */
  16303. public $name = 'RemoveSpansWithoutAttributes';
  16304. /**
  16305. * @type array
  16306. */
  16307. public $needed = array('span');
  16308. /**
  16309. * @type HTMLPurifier_AttrValidator
  16310. */
  16311. private $attrValidator;
  16312. /**
  16313. * Used by AttrValidator.
  16314. * @type HTMLPurifier_Config
  16315. */
  16316. private $config;
  16317. /**
  16318. * @type HTMLPurifier_Context
  16319. */
  16320. private $context;
  16321. public function prepare($config, $context)
  16322. {
  16323. $this->attrValidator = new HTMLPurifier_AttrValidator();
  16324. $this->config = $config;
  16325. $this->context = $context;
  16326. return parent::prepare($config, $context);
  16327. }
  16328. /**
  16329. * @param HTMLPurifier_Token $token
  16330. */
  16331. public function handleElement(&$token)
  16332. {
  16333. if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
  16334. return;
  16335. }
  16336. // We need to validate the attributes now since this doesn't normally
  16337. // happen until after MakeWellFormed. If all the attributes are removed
  16338. // the span needs to be removed too.
  16339. $this->attrValidator->validateToken($token, $this->config, $this->context);
  16340. $token->armor['ValidateAttributes'] = true;
  16341. if (!empty($token->attr)) {
  16342. return;
  16343. }
  16344. $nesting = 0;
  16345. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  16346. }
  16347. if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
  16348. // Mark closing span tag for deletion
  16349. $current->markForDeletion = true;
  16350. // Delete open span tag
  16351. $token = false;
  16352. }
  16353. }
  16354. /**
  16355. * @param HTMLPurifier_Token $token
  16356. */
  16357. public function handleEnd(&$token)
  16358. {
  16359. if ($token->markForDeletion) {
  16360. $token = false;
  16361. }
  16362. }
  16363. }
  16364. /**
  16365. * Adds important param elements to inside of object in order to make
  16366. * things safe.
  16367. */
  16368. class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
  16369. {
  16370. /**
  16371. * @type string
  16372. */
  16373. public $name = 'SafeObject';
  16374. /**
  16375. * @type array
  16376. */
  16377. public $needed = array('object', 'param');
  16378. /**
  16379. * @type array
  16380. */
  16381. protected $objectStack = array();
  16382. /**
  16383. * @type array
  16384. */
  16385. protected $paramStack = array();
  16386. /**
  16387. * Keep this synchronized with AttrTransform/SafeParam.php.
  16388. * @type array
  16389. */
  16390. protected $addParam = array(
  16391. 'allowScriptAccess' => 'never',
  16392. 'allowNetworking' => 'internal',
  16393. );
  16394. /**
  16395. * These are all lower-case keys.
  16396. * @type array
  16397. */
  16398. protected $allowedParam = array(
  16399. 'wmode' => true,
  16400. 'movie' => true,
  16401. 'flashvars' => true,
  16402. 'src' => true,
  16403. 'allowfullscreen' => true, // if omitted, assume to be 'false'
  16404. );
  16405. /**
  16406. * @param HTMLPurifier_Config $config
  16407. * @param HTMLPurifier_Context $context
  16408. * @return void
  16409. */
  16410. public function prepare($config, $context)
  16411. {
  16412. parent::prepare($config, $context);
  16413. }
  16414. /**
  16415. * @param HTMLPurifier_Token $token
  16416. */
  16417. public function handleElement(&$token)
  16418. {
  16419. if ($token->name == 'object') {
  16420. $this->objectStack[] = $token;
  16421. $this->paramStack[] = array();
  16422. $new = array($token);
  16423. foreach ($this->addParam as $name => $value) {
  16424. $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
  16425. }
  16426. $token = $new;
  16427. } elseif ($token->name == 'param') {
  16428. $nest = count($this->currentNesting) - 1;
  16429. if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
  16430. $i = count($this->objectStack) - 1;
  16431. if (!isset($token->attr['name'])) {
  16432. $token = false;
  16433. return;
  16434. }
  16435. $n = $token->attr['name'];
  16436. // We need this fix because YouTube doesn't supply a data
  16437. // attribute, which we need if a type is specified. This is
  16438. // *very* Flash specific.
  16439. if (!isset($this->objectStack[$i]->attr['data']) &&
  16440. ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
  16441. ) {
  16442. $this->objectStack[$i]->attr['data'] = $token->attr['value'];
  16443. }
  16444. // Check if the parameter is the correct value but has not
  16445. // already been added
  16446. if (!isset($this->paramStack[$i][$n]) &&
  16447. isset($this->addParam[$n]) &&
  16448. $token->attr['name'] === $this->addParam[$n]) {
  16449. // keep token, and add to param stack
  16450. $this->paramStack[$i][$n] = true;
  16451. } elseif (isset($this->allowedParam[strtolower($n)])) {
  16452. // keep token, don't do anything to it
  16453. // (could possibly check for duplicates here)
  16454. // Note: In principle, parameters should be case sensitive.
  16455. // But it seems they are not really; so accept any case.
  16456. } else {
  16457. $token = false;
  16458. }
  16459. } else {
  16460. // not directly inside an object, DENY!
  16461. $token = false;
  16462. }
  16463. }
  16464. }
  16465. public function handleEnd(&$token)
  16466. {
  16467. // This is the WRONG way of handling the object and param stacks;
  16468. // we should be inserting them directly on the relevant object tokens
  16469. // so that the global stack handling handles it.
  16470. if ($token->name == 'object') {
  16471. array_pop($this->objectStack);
  16472. array_pop($this->paramStack);
  16473. }
  16474. }
  16475. }
  16476. /**
  16477. * Parser that uses PHP 5's DOM extension (part of the core).
  16478. *
  16479. * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
  16480. * It gives us a forgiving HTML parser, which we use to transform the HTML
  16481. * into a DOM, and then into the tokens. It is blazingly fast (for large
  16482. * documents, it performs twenty times faster than
  16483. * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
  16484. *
  16485. * @note Any empty elements will have empty tokens associated with them, even if
  16486. * this is prohibited by the spec. This is cannot be fixed until the spec
  16487. * comes into play.
  16488. *
  16489. * @note PHP's DOM extension does not actually parse any entities, we use
  16490. * our own function to do that.
  16491. *
  16492. * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
  16493. * If this is a huge problem, due to the fact that HTML is hand
  16494. * edited and you are unable to get a parser cache that caches the
  16495. * the output of HTML Purifier while keeping the original HTML lying
  16496. * around, you may want to run Tidy on the resulting output or use
  16497. * HTMLPurifier_DirectLex
  16498. */
  16499. class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
  16500. {
  16501. /**
  16502. * @type HTMLPurifier_TokenFactory
  16503. */
  16504. private $factory;
  16505. public function __construct()
  16506. {
  16507. // setup the factory
  16508. parent::__construct();
  16509. $this->factory = new HTMLPurifier_TokenFactory();
  16510. }
  16511. /**
  16512. * @param string $html
  16513. * @param HTMLPurifier_Config $config
  16514. * @param HTMLPurifier_Context $context
  16515. * @return HTMLPurifier_Token[]
  16516. */
  16517. public function tokenizeHTML($html, $config, $context)
  16518. {
  16519. $html = $this->normalize($html, $config, $context);
  16520. // attempt to armor stray angled brackets that cannot possibly
  16521. // form tags and thus are probably being used as emoticons
  16522. if ($config->get('Core.AggressivelyFixLt')) {
  16523. $char = '[^a-z!\/]';
  16524. $comment = "/<!--(.*?)(-->|\z)/is";
  16525. $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
  16526. do {
  16527. $old = $html;
  16528. $html = preg_replace("/<($char)/i", '&lt;\\1', $html);
  16529. } while ($html !== $old);
  16530. $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
  16531. }
  16532. // preprocess html, essential for UTF-8
  16533. $html = $this->wrapHTML($html, $config, $context);
  16534. $doc = new DOMDocument();
  16535. $doc->encoding = 'UTF-8'; // theoretically, the above has this covered
  16536. set_error_handler(array($this, 'muteErrorHandler'));
  16537. $doc->loadHTML($html);
  16538. restore_error_handler();
  16539. $body = $doc->getElementsByTagName('html')->item(0)-> // <html>
  16540. getElementsByTagName('body')->item(0); // <body>
  16541. $div = $body->getElementsByTagName('div')->item(0); // <div>
  16542. $tokens = array();
  16543. $this->tokenizeDOM($div, $tokens, $config);
  16544. // If the div has a sibling, that means we tripped across
  16545. // a premature </div> tag. So remove the div we parsed,
  16546. // and then tokenize the rest of body. We can't tokenize
  16547. // the sibling directly as we'll lose the tags in that case.
  16548. if ($div->nextSibling) {
  16549. $body->removeChild($div);
  16550. $this->tokenizeDOM($body, $tokens, $config);
  16551. }
  16552. return $tokens;
  16553. }
  16554. /**
  16555. * Iterative function that tokenizes a node, putting it into an accumulator.
  16556. * To iterate is human, to recurse divine - L. Peter Deutsch
  16557. * @param DOMNode $node DOMNode to be tokenized.
  16558. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16559. * @return HTMLPurifier_Token of node appended to previously passed tokens.
  16560. */
  16561. protected function tokenizeDOM($node, &$tokens, $config)
  16562. {
  16563. $level = 0;
  16564. $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  16565. $closingNodes = array();
  16566. do {
  16567. while (!$nodes[$level]->isEmpty()) {
  16568. $node = $nodes[$level]->shift(); // FIFO
  16569. $collect = $level > 0 ? true : false;
  16570. $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config);
  16571. if ($needEndingTag) {
  16572. $closingNodes[$level][] = $node;
  16573. }
  16574. if ($node->childNodes && $node->childNodes->length) {
  16575. $level++;
  16576. $nodes[$level] = new HTMLPurifier_Queue();
  16577. foreach ($node->childNodes as $childNode) {
  16578. $nodes[$level]->push($childNode);
  16579. }
  16580. }
  16581. }
  16582. $level--;
  16583. if ($level && isset($closingNodes[$level])) {
  16584. while ($node = array_pop($closingNodes[$level])) {
  16585. $this->createEndNode($node, $tokens);
  16586. }
  16587. }
  16588. } while ($level > 0);
  16589. }
  16590. /**
  16591. * Portably retrieve the tag name of a node; deals with older versions
  16592. * of libxml like 2.7.6
  16593. * @param DOMNode $node
  16594. */
  16595. protected function getTagName($node)
  16596. {
  16597. if (property_exists($node, 'tagName')) {
  16598. return $node->tagName;
  16599. } else if (property_exists($node, 'nodeName')) {
  16600. return $node->nodeName;
  16601. } else if (property_exists($node, 'localName')) {
  16602. return $node->localName;
  16603. }
  16604. return null;
  16605. }
  16606. /**
  16607. * Portably retrieve the data of a node; deals with older versions
  16608. * of libxml like 2.7.6
  16609. * @param DOMNode $node
  16610. */
  16611. protected function getData($node)
  16612. {
  16613. if (property_exists($node, 'data')) {
  16614. return $node->data;
  16615. } else if (property_exists($node, 'nodeValue')) {
  16616. return $node->nodeValue;
  16617. } else if (property_exists($node, 'textContent')) {
  16618. return $node->textContent;
  16619. }
  16620. return null;
  16621. }
  16622. /**
  16623. * @param DOMNode $node DOMNode to be tokenized.
  16624. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16625. * @param bool $collect Says whether or start and close are collected, set to
  16626. * false at first recursion because it's the implicit DIV
  16627. * tag you're dealing with.
  16628. * @return bool if the token needs an endtoken
  16629. * @todo data and tagName properties don't seem to exist in DOMNode?
  16630. */
  16631. protected function createStartNode($node, &$tokens, $collect, $config)
  16632. {
  16633. // intercept non element nodes. WE MUST catch all of them,
  16634. // but we're not getting the character reference nodes because
  16635. // those should have been preprocessed
  16636. if ($node->nodeType === XML_TEXT_NODE) {
  16637. $data = $this->getData($node); // Handle variable data property
  16638. if ($data !== null) {
  16639. $tokens[] = $this->factory->createText($data);
  16640. }
  16641. return false;
  16642. } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
  16643. // undo libxml's special treatment of <script> and <style> tags
  16644. $last = end($tokens);
  16645. $data = $node->data;
  16646. // (note $node->tagname is already normalized)
  16647. if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
  16648. $new_data = trim($data);
  16649. if (substr($new_data, 0, 4) === '<!--') {
  16650. $data = substr($new_data, 4);
  16651. if (substr($data, -3) === '-->') {
  16652. $data = substr($data, 0, -3);
  16653. } else {
  16654. // Highly suspicious! Not sure what to do...
  16655. }
  16656. }
  16657. }
  16658. $tokens[] = $this->factory->createText($this->parseText($data, $config));
  16659. return false;
  16660. } elseif ($node->nodeType === XML_COMMENT_NODE) {
  16661. // this is code is only invoked for comments in script/style in versions
  16662. // of libxml pre-2.6.28 (regular comments, of course, are still
  16663. // handled regularly)
  16664. $tokens[] = $this->factory->createComment($node->data);
  16665. return false;
  16666. } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
  16667. // not-well tested: there may be other nodes we have to grab
  16668. return false;
  16669. }
  16670. $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
  16671. $tag_name = $this->getTagName($node); // Handle variable tagName property
  16672. if (empty($tag_name)) {
  16673. return (bool) $node->childNodes->length;
  16674. }
  16675. // We still have to make sure that the element actually IS empty
  16676. if (!$node->childNodes->length) {
  16677. if ($collect) {
  16678. $tokens[] = $this->factory->createEmpty($tag_name, $attr);
  16679. }
  16680. return false;
  16681. } else {
  16682. if ($collect) {
  16683. $tokens[] = $this->factory->createStart($tag_name, $attr);
  16684. }
  16685. return true;
  16686. }
  16687. }
  16688. /**
  16689. * @param DOMNode $node
  16690. * @param HTMLPurifier_Token[] $tokens
  16691. */
  16692. protected function createEndNode($node, &$tokens)
  16693. {
  16694. $tag_name = $this->getTagName($node); // Handle variable tagName property
  16695. $tokens[] = $this->factory->createEnd($tag_name);
  16696. }
  16697. /**
  16698. * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
  16699. *
  16700. * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
  16701. * @return array Associative array of attributes.
  16702. */
  16703. protected function transformAttrToAssoc($node_map)
  16704. {
  16705. // NamedNodeMap is documented very well, so we're using undocumented
  16706. // features, namely, the fact that it implements Iterator and
  16707. // has a ->length attribute
  16708. if ($node_map->length === 0) {
  16709. return array();
  16710. }
  16711. $array = array();
  16712. foreach ($node_map as $attr) {
  16713. $array[$attr->name] = $attr->value;
  16714. }
  16715. return $array;
  16716. }
  16717. /**
  16718. * An error handler that mutes all errors
  16719. * @param int $errno
  16720. * @param string $errstr
  16721. */
  16722. public function muteErrorHandler($errno, $errstr)
  16723. {
  16724. }
  16725. /**
  16726. * Callback function for undoing escaping of stray angled brackets
  16727. * in comments
  16728. * @param array $matches
  16729. * @return string
  16730. */
  16731. public function callbackUndoCommentSubst($matches)
  16732. {
  16733. return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
  16734. }
  16735. /**
  16736. * Callback function that entity-izes ampersands in comments so that
  16737. * callbackUndoCommentSubst doesn't clobber them
  16738. * @param array $matches
  16739. * @return string
  16740. */
  16741. public function callbackArmorCommentEntities($matches)
  16742. {
  16743. return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
  16744. }
  16745. /**
  16746. * Wraps an HTML fragment in the necessary HTML
  16747. * @param string $html
  16748. * @param HTMLPurifier_Config $config
  16749. * @param HTMLPurifier_Context $context
  16750. * @return string
  16751. */
  16752. protected function wrapHTML($html, $config, $context, $use_div = true)
  16753. {
  16754. $def = $config->getDefinition('HTML');
  16755. $ret = '';
  16756. if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
  16757. $ret .= '<!DOCTYPE html ';
  16758. if (!empty($def->doctype->dtdPublic)) {
  16759. $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
  16760. }
  16761. if (!empty($def->doctype->dtdSystem)) {
  16762. $ret .= '"' . $def->doctype->dtdSystem . '" ';
  16763. }
  16764. $ret .= '>';
  16765. }
  16766. $ret .= '<html><head>';
  16767. $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  16768. // No protection if $html contains a stray </div>!
  16769. $ret .= '</head><body>';
  16770. if ($use_div) $ret .= '<div>';
  16771. $ret .= $html;
  16772. if ($use_div) $ret .= '</div>';
  16773. $ret .= '</body></html>';
  16774. return $ret;
  16775. }
  16776. }
  16777. /**
  16778. * Our in-house implementation of a parser.
  16779. *
  16780. * A pure PHP parser, DirectLex has absolutely no dependencies, making
  16781. * it a reasonably good default for PHP4. Written with efficiency in mind,
  16782. * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
  16783. * pales in comparison to HTMLPurifier_Lexer_DOMLex.
  16784. *
  16785. * @todo Reread XML spec and document differences.
  16786. */
  16787. class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
  16788. {
  16789. /**
  16790. * @type bool
  16791. */
  16792. public $tracksLineNumbers = true;
  16793. /**
  16794. * Whitespace characters for str(c)spn.
  16795. * @type string
  16796. */
  16797. protected $_whitespace = "\x20\x09\x0D\x0A";
  16798. /**
  16799. * Callback function for script CDATA fudge
  16800. * @param array $matches, in form of array(opening tag, contents, closing tag)
  16801. * @return string
  16802. */
  16803. protected function scriptCallback($matches)
  16804. {
  16805. return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
  16806. }
  16807. /**
  16808. * @param String $html
  16809. * @param HTMLPurifier_Config $config
  16810. * @param HTMLPurifier_Context $context
  16811. * @return array|HTMLPurifier_Token[]
  16812. */
  16813. public function tokenizeHTML($html, $config, $context)
  16814. {
  16815. // special normalization for script tags without any armor
  16816. // our "armor" heurstic is a < sign any number of whitespaces after
  16817. // the first script tag
  16818. if ($config->get('HTML.Trusted')) {
  16819. $html = preg_replace_callback(
  16820. '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
  16821. array($this, 'scriptCallback'),
  16822. $html
  16823. );
  16824. }
  16825. $html = $this->normalize($html, $config, $context);
  16826. $cursor = 0; // our location in the text
  16827. $inside_tag = false; // whether or not we're parsing the inside of a tag
  16828. $array = array(); // result array
  16829. // This is also treated to mean maintain *column* numbers too
  16830. $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
  16831. if ($maintain_line_numbers === null) {
  16832. // automatically determine line numbering by checking
  16833. // if error collection is on
  16834. $maintain_line_numbers = $config->get('Core.CollectErrors');
  16835. }
  16836. if ($maintain_line_numbers) {
  16837. $current_line = 1;
  16838. $current_col = 0;
  16839. $length = strlen($html);
  16840. } else {
  16841. $current_line = false;
  16842. $current_col = false;
  16843. $length = false;
  16844. }
  16845. $context->register('CurrentLine', $current_line);
  16846. $context->register('CurrentCol', $current_col);
  16847. $nl = "\n";
  16848. // how often to manually recalculate. This will ALWAYS be right,
  16849. // but it's pretty wasteful. Set to 0 to turn off
  16850. $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
  16851. $e = false;
  16852. if ($config->get('Core.CollectErrors')) {
  16853. $e =& $context->get('ErrorCollector');
  16854. }
  16855. // for testing synchronization
  16856. $loops = 0;
  16857. while (++$loops) {
  16858. // $cursor is either at the start of a token, or inside of
  16859. // a tag (i.e. there was a < immediately before it), as indicated
  16860. // by $inside_tag
  16861. if ($maintain_line_numbers) {
  16862. // $rcursor, however, is always at the start of a token.
  16863. $rcursor = $cursor - (int)$inside_tag;
  16864. // Column number is cheap, so we calculate it every round.
  16865. // We're interested at the *end* of the newline string, so
  16866. // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
  16867. // from our "rcursor" position.
  16868. $nl_pos = strrpos($html, $nl, $rcursor - $length);
  16869. $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
  16870. // recalculate lines
  16871. if ($synchronize_interval && // synchronization is on
  16872. $cursor > 0 && // cursor is further than zero
  16873. $loops % $synchronize_interval === 0) { // time to synchronize!
  16874. $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
  16875. }
  16876. }
  16877. $position_next_lt = strpos($html, '<', $cursor);
  16878. $position_next_gt = strpos($html, '>', $cursor);
  16879. // triggers on "<b>asdf</b>" but not "asdf <b></b>"
  16880. // special case to set up context
  16881. if ($position_next_lt === $cursor) {
  16882. $inside_tag = true;
  16883. $cursor++;
  16884. }
  16885. if (!$inside_tag && $position_next_lt !== false) {
  16886. // We are not inside tag and there still is another tag to parse
  16887. $token = new
  16888. HTMLPurifier_Token_Text(
  16889. $this->parseText(
  16890. substr(
  16891. $html,
  16892. $cursor,
  16893. $position_next_lt - $cursor
  16894. ), $config
  16895. )
  16896. );
  16897. if ($maintain_line_numbers) {
  16898. $token->rawPosition($current_line, $current_col);
  16899. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
  16900. }
  16901. $array[] = $token;
  16902. $cursor = $position_next_lt + 1;
  16903. $inside_tag = true;
  16904. continue;
  16905. } elseif (!$inside_tag) {
  16906. // We are not inside tag but there are no more tags
  16907. // If we're already at the end, break
  16908. if ($cursor === strlen($html)) {
  16909. break;
  16910. }
  16911. // Create Text of rest of string
  16912. $token = new
  16913. HTMLPurifier_Token_Text(
  16914. $this->parseText(
  16915. substr(
  16916. $html,
  16917. $cursor
  16918. ), $config
  16919. )
  16920. );
  16921. if ($maintain_line_numbers) {
  16922. $token->rawPosition($current_line, $current_col);
  16923. }
  16924. $array[] = $token;
  16925. break;
  16926. } elseif ($inside_tag && $position_next_gt !== false) {
  16927. // We are in tag and it is well formed
  16928. // Grab the internals of the tag
  16929. $strlen_segment = $position_next_gt - $cursor;
  16930. if ($strlen_segment < 1) {
  16931. // there's nothing to process!
  16932. $token = new HTMLPurifier_Token_Text('<');
  16933. $cursor++;
  16934. continue;
  16935. }
  16936. $segment = substr($html, $cursor, $strlen_segment);
  16937. if ($segment === false) {
  16938. // somehow, we attempted to access beyond the end of
  16939. // the string, defense-in-depth, reported by Nate Abele
  16940. break;
  16941. }
  16942. // Check if it's a comment
  16943. if (substr($segment, 0, 3) === '!--') {
  16944. // re-determine segment length, looking for -->
  16945. $position_comment_end = strpos($html, '-->', $cursor);
  16946. if ($position_comment_end === false) {
  16947. // uh oh, we have a comment that extends to
  16948. // infinity. Can't be helped: set comment
  16949. // end position to end of string
  16950. if ($e) {
  16951. $e->send(E_WARNING, 'Lexer: Unclosed comment');
  16952. }
  16953. $position_comment_end = strlen($html);
  16954. $end = true;
  16955. } else {
  16956. $end = false;
  16957. }
  16958. $strlen_segment = $position_comment_end - $cursor;
  16959. $segment = substr($html, $cursor, $strlen_segment);
  16960. $token = new
  16961. HTMLPurifier_Token_Comment(
  16962. substr(
  16963. $segment,
  16964. 3,
  16965. $strlen_segment - 3
  16966. )
  16967. );
  16968. if ($maintain_line_numbers) {
  16969. $token->rawPosition($current_line, $current_col);
  16970. $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
  16971. }
  16972. $array[] = $token;
  16973. $cursor = $end ? $position_comment_end : $position_comment_end + 3;
  16974. $inside_tag = false;
  16975. continue;
  16976. }
  16977. // Check if it's an end tag
  16978. $is_end_tag = (strpos($segment, '/') === 0);
  16979. if ($is_end_tag) {
  16980. $type = substr($segment, 1);
  16981. $token = new HTMLPurifier_Token_End($type);
  16982. if ($maintain_line_numbers) {
  16983. $token->rawPosition($current_line, $current_col);
  16984. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  16985. }
  16986. $array[] = $token;
  16987. $inside_tag = false;
  16988. $cursor = $position_next_gt + 1;
  16989. continue;
  16990. }
  16991. // Check leading character is alnum, if not, we may
  16992. // have accidently grabbed an emoticon. Translate into
  16993. // text and go our merry way
  16994. if (!ctype_alpha($segment[0])) {
  16995. // XML: $segment[0] !== '_' && $segment[0] !== ':'
  16996. if ($e) {
  16997. $e->send(E_NOTICE, 'Lexer: Unescaped lt');
  16998. }
  16999. $token = new HTMLPurifier_Token_Text('<');
  17000. if ($maintain_line_numbers) {
  17001. $token->rawPosition($current_line, $current_col);
  17002. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17003. }
  17004. $array[] = $token;
  17005. $inside_tag = false;
  17006. continue;
  17007. }
  17008. // Check if it is explicitly self closing, if so, remove
  17009. // trailing slash. Remember, we could have a tag like <br>, so
  17010. // any later token processing scripts must convert improperly
  17011. // classified EmptyTags from StartTags.
  17012. $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
  17013. if ($is_self_closing) {
  17014. $strlen_segment--;
  17015. $segment = substr($segment, 0, $strlen_segment);
  17016. }
  17017. // Check if there are any attributes
  17018. $position_first_space = strcspn($segment, $this->_whitespace);
  17019. if ($position_first_space >= $strlen_segment) {
  17020. if ($is_self_closing) {
  17021. $token = new HTMLPurifier_Token_Empty($segment);
  17022. } else {
  17023. $token = new HTMLPurifier_Token_Start($segment);
  17024. }
  17025. if ($maintain_line_numbers) {
  17026. $token->rawPosition($current_line, $current_col);
  17027. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17028. }
  17029. $array[] = $token;
  17030. $inside_tag = false;
  17031. $cursor = $position_next_gt + 1;
  17032. continue;
  17033. }
  17034. // Grab out all the data
  17035. $type = substr($segment, 0, $position_first_space);
  17036. $attribute_string =
  17037. trim(
  17038. substr(
  17039. $segment,
  17040. $position_first_space
  17041. )
  17042. );
  17043. if ($attribute_string) {
  17044. $attr = $this->parseAttributeString(
  17045. $attribute_string,
  17046. $config,
  17047. $context
  17048. );
  17049. } else {
  17050. $attr = array();
  17051. }
  17052. if ($is_self_closing) {
  17053. $token = new HTMLPurifier_Token_Empty($type, $attr);
  17054. } else {
  17055. $token = new HTMLPurifier_Token_Start($type, $attr);
  17056. }
  17057. if ($maintain_line_numbers) {
  17058. $token->rawPosition($current_line, $current_col);
  17059. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  17060. }
  17061. $array[] = $token;
  17062. $cursor = $position_next_gt + 1;
  17063. $inside_tag = false;
  17064. continue;
  17065. } else {
  17066. // inside tag, but there's no ending > sign
  17067. if ($e) {
  17068. $e->send(E_WARNING, 'Lexer: Missing gt');
  17069. }
  17070. $token = new
  17071. HTMLPurifier_Token_Text(
  17072. '<' .
  17073. $this->parseText(
  17074. substr($html, $cursor), $config
  17075. )
  17076. );
  17077. if ($maintain_line_numbers) {
  17078. $token->rawPosition($current_line, $current_col);
  17079. }
  17080. // no cursor scroll? Hmm...
  17081. $array[] = $token;
  17082. break;
  17083. }
  17084. break;
  17085. }
  17086. $context->destroy('CurrentLine');
  17087. $context->destroy('CurrentCol');
  17088. return $array;
  17089. }
  17090. /**
  17091. * PHP 5.0.x compatible substr_count that implements offset and length
  17092. * @param string $haystack
  17093. * @param string $needle
  17094. * @param int $offset
  17095. * @param int $length
  17096. * @return int
  17097. */
  17098. protected function substrCount($haystack, $needle, $offset, $length)
  17099. {
  17100. static $oldVersion;
  17101. if ($oldVersion === null) {
  17102. $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
  17103. }
  17104. if ($oldVersion) {
  17105. $haystack = substr($haystack, $offset, $length);
  17106. return substr_count($haystack, $needle);
  17107. } else {
  17108. return substr_count($haystack, $needle, $offset, $length);
  17109. }
  17110. }
  17111. /**
  17112. * Takes the inside of an HTML tag and makes an assoc array of attributes.
  17113. *
  17114. * @param string $string Inside of tag excluding name.
  17115. * @param HTMLPurifier_Config $config
  17116. * @param HTMLPurifier_Context $context
  17117. * @return array Assoc array of attributes.
  17118. */
  17119. public function parseAttributeString($string, $config, $context)
  17120. {
  17121. $string = (string)$string; // quick typecast
  17122. if ($string == '') {
  17123. return array();
  17124. } // no attributes
  17125. $e = false;
  17126. if ($config->get('Core.CollectErrors')) {
  17127. $e =& $context->get('ErrorCollector');
  17128. }
  17129. // let's see if we can abort as quickly as possible
  17130. // one equal sign, no spaces => one attribute
  17131. $num_equal = substr_count($string, '=');
  17132. $has_space = strpos($string, ' ');
  17133. if ($num_equal === 0 && !$has_space) {
  17134. // bool attribute
  17135. return array($string => $string);
  17136. } elseif ($num_equal === 1 && !$has_space) {
  17137. // only one attribute
  17138. list($key, $quoted_value) = explode('=', $string);
  17139. $quoted_value = trim($quoted_value);
  17140. if (!$key) {
  17141. if ($e) {
  17142. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17143. }
  17144. return array();
  17145. }
  17146. if (!$quoted_value) {
  17147. return array($key => '');
  17148. }
  17149. $first_char = @$quoted_value[0];
  17150. $last_char = @$quoted_value[strlen($quoted_value) - 1];
  17151. $same_quote = ($first_char == $last_char);
  17152. $open_quote = ($first_char == '"' || $first_char == "'");
  17153. if ($same_quote && $open_quote) {
  17154. // well behaved
  17155. $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
  17156. } else {
  17157. // not well behaved
  17158. if ($open_quote) {
  17159. if ($e) {
  17160. $e->send(E_ERROR, 'Lexer: Missing end quote');
  17161. }
  17162. $value = substr($quoted_value, 1);
  17163. } else {
  17164. $value = $quoted_value;
  17165. }
  17166. }
  17167. if ($value === false) {
  17168. $value = '';
  17169. }
  17170. return array($key => $this->parseAttr($value, $config));
  17171. }
  17172. // setup loop environment
  17173. $array = array(); // return assoc array of attributes
  17174. $cursor = 0; // current position in string (moves forward)
  17175. $size = strlen($string); // size of the string (stays the same)
  17176. // if we have unquoted attributes, the parser expects a terminating
  17177. // space, so let's guarantee that there's always a terminating space.
  17178. $string .= ' ';
  17179. $old_cursor = -1;
  17180. while ($cursor < $size) {
  17181. if ($old_cursor >= $cursor) {
  17182. throw new Exception("Infinite loop detected");
  17183. }
  17184. $old_cursor = $cursor;
  17185. $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
  17186. // grab the key
  17187. $key_begin = $cursor; //we're currently at the start of the key
  17188. // scroll past all characters that are the key (not whitespace or =)
  17189. $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
  17190. $key_end = $cursor; // now at the end of the key
  17191. $key = substr($string, $key_begin, $key_end - $key_begin);
  17192. if (!$key) {
  17193. if ($e) {
  17194. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17195. }
  17196. $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
  17197. continue; // empty key
  17198. }
  17199. // scroll past all whitespace
  17200. $cursor += strspn($string, $this->_whitespace, $cursor);
  17201. if ($cursor >= $size) {
  17202. $array[$key] = $key;
  17203. break;
  17204. }
  17205. // if the next character is an equal sign, we've got a regular
  17206. // pair, otherwise, it's a bool attribute
  17207. $first_char = @$string[$cursor];
  17208. if ($first_char == '=') {
  17209. // key="value"
  17210. $cursor++;
  17211. $cursor += strspn($string, $this->_whitespace, $cursor);
  17212. if ($cursor === false) {
  17213. $array[$key] = '';
  17214. break;
  17215. }
  17216. // we might be in front of a quote right now
  17217. $char = @$string[$cursor];
  17218. if ($char == '"' || $char == "'") {
  17219. // it's quoted, end bound is $char
  17220. $cursor++;
  17221. $value_begin = $cursor;
  17222. $cursor = strpos($string, $char, $cursor);
  17223. $value_end = $cursor;
  17224. } else {
  17225. // it's not quoted, end bound is whitespace
  17226. $value_begin = $cursor;
  17227. $cursor += strcspn($string, $this->_whitespace, $cursor);
  17228. $value_end = $cursor;
  17229. }
  17230. // we reached a premature end
  17231. if ($cursor === false) {
  17232. $cursor = $size;
  17233. $value_end = $cursor;
  17234. }
  17235. $value = substr($string, $value_begin, $value_end - $value_begin);
  17236. if ($value === false) {
  17237. $value = '';
  17238. }
  17239. $array[$key] = $this->parseAttr($value, $config);
  17240. $cursor++;
  17241. } else {
  17242. // boolattr
  17243. if ($key !== '') {
  17244. $array[$key] = $key;
  17245. } else {
  17246. // purely theoretical
  17247. if ($e) {
  17248. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  17249. }
  17250. }
  17251. }
  17252. }
  17253. return $array;
  17254. }
  17255. }
  17256. /**
  17257. * Concrete comment node class.
  17258. */
  17259. class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
  17260. {
  17261. /**
  17262. * Character data within comment.
  17263. * @type string
  17264. */
  17265. public $data;
  17266. /**
  17267. * @type bool
  17268. */
  17269. public $is_whitespace = true;
  17270. /**
  17271. * Transparent constructor.
  17272. *
  17273. * @param string $data String comment data.
  17274. * @param int $line
  17275. * @param int $col
  17276. */
  17277. public function __construct($data, $line = null, $col = null)
  17278. {
  17279. $this->data = $data;
  17280. $this->line = $line;
  17281. $this->col = $col;
  17282. }
  17283. public function toTokenPair() {
  17284. return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
  17285. }
  17286. }
  17287. /**
  17288. * Concrete element node class.
  17289. */
  17290. class HTMLPurifier_Node_Element extends HTMLPurifier_Node
  17291. {
  17292. /**
  17293. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  17294. *
  17295. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  17296. * be lower-casing them, but these tokens cater to HTML tags, which are
  17297. * insensitive.
  17298. * @type string
  17299. */
  17300. public $name;
  17301. /**
  17302. * Associative array of the node's attributes.
  17303. * @type array
  17304. */
  17305. public $attr = array();
  17306. /**
  17307. * List of child elements.
  17308. * @type array
  17309. */
  17310. public $children = array();
  17311. /**
  17312. * Does this use the <a></a> form or the </a> form, i.e.
  17313. * is it a pair of start/end tokens or an empty token.
  17314. * @bool
  17315. */
  17316. public $empty = false;
  17317. public $endCol = null, $endLine = null, $endArmor = array();
  17318. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
  17319. $this->name = $name;
  17320. $this->attr = $attr;
  17321. $this->line = $line;
  17322. $this->col = $col;
  17323. $this->armor = $armor;
  17324. }
  17325. public function toTokenPair() {
  17326. // XXX inefficiency here, normalization is not necessary
  17327. if ($this->empty) {
  17328. return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
  17329. } else {
  17330. $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
  17331. $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
  17332. //$end->start = $start;
  17333. return array($start, $end);
  17334. }
  17335. }
  17336. }
  17337. /**
  17338. * Concrete text token class.
  17339. *
  17340. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  17341. * character data (from the CDATA sections). Internally, their
  17342. * data is parsed with all entities expanded. Surprisingly, the text token
  17343. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  17344. * in permissible child nodes.
  17345. */
  17346. class HTMLPurifier_Node_Text extends HTMLPurifier_Node
  17347. {
  17348. /**
  17349. * PCDATA tag name compatible with DTD, see
  17350. * HTMLPurifier_ChildDef_Custom for details.
  17351. * @type string
  17352. */
  17353. public $name = '#PCDATA';
  17354. /**
  17355. * @type string
  17356. */
  17357. public $data;
  17358. /**< Parsed character data of text. */
  17359. /**
  17360. * @type bool
  17361. */
  17362. public $is_whitespace;
  17363. /**< Bool indicating if node is whitespace. */
  17364. /**
  17365. * Constructor, accepts data and determines if it is whitespace.
  17366. * @param string $data String parsed character data.
  17367. * @param int $line
  17368. * @param int $col
  17369. */
  17370. public function __construct($data, $is_whitespace, $line = null, $col = null)
  17371. {
  17372. $this->data = $data;
  17373. $this->is_whitespace = $is_whitespace;
  17374. $this->line = $line;
  17375. $this->col = $col;
  17376. }
  17377. public function toTokenPair() {
  17378. return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
  17379. }
  17380. }
  17381. /**
  17382. * Composite strategy that runs multiple strategies on tokens.
  17383. */
  17384. abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
  17385. {
  17386. /**
  17387. * List of strategies to run tokens through.
  17388. * @type HTMLPurifier_Strategy[]
  17389. */
  17390. protected $strategies = array();
  17391. /**
  17392. * @param HTMLPurifier_Token[] $tokens
  17393. * @param HTMLPurifier_Config $config
  17394. * @param HTMLPurifier_Context $context
  17395. * @return HTMLPurifier_Token[]
  17396. */
  17397. public function execute($tokens, $config, $context)
  17398. {
  17399. foreach ($this->strategies as $strategy) {
  17400. $tokens = $strategy->execute($tokens, $config, $context);
  17401. }
  17402. return $tokens;
  17403. }
  17404. }
  17405. /**
  17406. * Core strategy composed of the big four strategies.
  17407. */
  17408. class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
  17409. {
  17410. public function __construct()
  17411. {
  17412. $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
  17413. $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
  17414. $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
  17415. $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
  17416. }
  17417. }
  17418. /**
  17419. * Takes a well formed list of tokens and fixes their nesting.
  17420. *
  17421. * HTML elements dictate which elements are allowed to be their children,
  17422. * for example, you can't have a p tag in a span tag. Other elements have
  17423. * much more rigorous definitions: tables, for instance, require a specific
  17424. * order for their elements. There are also constraints not expressible by
  17425. * document type definitions, such as the chameleon nature of ins/del
  17426. * tags and global child exclusions.
  17427. *
  17428. * The first major objective of this strategy is to iterate through all
  17429. * the nodes and determine whether or not their children conform to the
  17430. * element's definition. If they do not, the child definition may
  17431. * optionally supply an amended list of elements that is valid or
  17432. * require that the entire node be deleted (and the previous node
  17433. * rescanned).
  17434. *
  17435. * The second objective is to ensure that explicitly excluded elements of
  17436. * an element do not appear in its children. Code that accomplishes this
  17437. * task is pervasive through the strategy, though the two are distinct tasks
  17438. * and could, theoretically, be seperated (although it's not recommended).
  17439. *
  17440. * @note Whether or not unrecognized children are silently dropped or
  17441. * translated into text depends on the child definitions.
  17442. *
  17443. * @todo Enable nodes to be bubbled out of the structure. This is
  17444. * easier with our new algorithm.
  17445. */
  17446. class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
  17447. {
  17448. /**
  17449. * @param HTMLPurifier_Token[] $tokens
  17450. * @param HTMLPurifier_Config $config
  17451. * @param HTMLPurifier_Context $context
  17452. * @return array|HTMLPurifier_Token[]
  17453. */
  17454. public function execute($tokens, $config, $context)
  17455. {
  17456. //####################################################################//
  17457. // Pre-processing
  17458. // O(n) pass to convert to a tree, so that we can efficiently
  17459. // refer to substrings
  17460. $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
  17461. // get a copy of the HTML definition
  17462. $definition = $config->getHTMLDefinition();
  17463. $excludes_enabled = !$config->get('Core.DisableExcludes');
  17464. // setup the context variable 'IsInline', for chameleon processing
  17465. // is 'false' when we are not inline, 'true' when it must always
  17466. // be inline, and an integer when it is inline for a certain
  17467. // branch of the document tree
  17468. $is_inline = $definition->info_parent_def->descendants_are_inline;
  17469. $context->register('IsInline', $is_inline);
  17470. // setup error collector
  17471. $e =& $context->get('ErrorCollector', true);
  17472. //####################################################################//
  17473. // Loop initialization
  17474. // stack that contains all elements that are excluded
  17475. // it is organized by parent elements, similar to $stack,
  17476. // but it is only populated when an element with exclusions is
  17477. // processed, i.e. there won't be empty exclusions.
  17478. $exclude_stack = array($definition->info_parent_def->excludes);
  17479. // variable that contains the start token while we are processing
  17480. // nodes. This enables error reporting to do its job
  17481. $node = $top_node;
  17482. // dummy token
  17483. list($token, $d) = $node->toTokenPair();
  17484. $context->register('CurrentNode', $node);
  17485. $context->register('CurrentToken', $token);
  17486. //####################################################################//
  17487. // Loop
  17488. // We need to implement a post-order traversal iteratively, to
  17489. // avoid running into stack space limits. This is pretty tricky
  17490. // to reason about, so we just manually stack-ify the recursive
  17491. // variant:
  17492. //
  17493. // function f($node) {
  17494. // foreach ($node->children as $child) {
  17495. // f($child);
  17496. // }
  17497. // validate($node);
  17498. // }
  17499. //
  17500. // Thus, we will represent a stack frame as array($node,
  17501. // $is_inline, stack of children)
  17502. // e.g. array_reverse($node->children) - already processed
  17503. // children.
  17504. $parent_def = $definition->info_parent_def;
  17505. $stack = array(
  17506. array($top_node,
  17507. $parent_def->descendants_are_inline,
  17508. $parent_def->excludes, // exclusions
  17509. 0)
  17510. );
  17511. while (!empty($stack)) {
  17512. list($node, $is_inline, $excludes, $ix) = array_pop($stack);
  17513. // recursive call
  17514. $go = false;
  17515. $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
  17516. while (isset($node->children[$ix])) {
  17517. $child = $node->children[$ix++];
  17518. if ($child instanceof HTMLPurifier_Node_Element) {
  17519. $go = true;
  17520. $stack[] = array($node, $is_inline, $excludes, $ix);
  17521. $stack[] = array($child,
  17522. // ToDo: I don't think it matters if it's def or
  17523. // child_def, but double check this...
  17524. $is_inline || $def->descendants_are_inline,
  17525. empty($def->excludes) ? $excludes
  17526. : array_merge($excludes, $def->excludes),
  17527. 0);
  17528. break;
  17529. }
  17530. };
  17531. if ($go) continue;
  17532. list($token, $d) = $node->toTokenPair();
  17533. // base case
  17534. if ($excludes_enabled && isset($excludes[$node->name])) {
  17535. $node->dead = true;
  17536. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
  17537. } else {
  17538. // XXX I suppose it would be slightly more efficient to
  17539. // avoid the allocation here and have children
  17540. // strategies handle it
  17541. $children = array();
  17542. foreach ($node->children as $child) {
  17543. if (!$child->dead) $children[] = $child;
  17544. }
  17545. $result = $def->child->validateChildren($children, $config, $context);
  17546. if ($result === true) {
  17547. // nop
  17548. $node->children = $children;
  17549. } elseif ($result === false) {
  17550. $node->dead = true;
  17551. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
  17552. } else {
  17553. $node->children = $result;
  17554. if ($e) {
  17555. // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
  17556. if (empty($result) && !empty($children)) {
  17557. $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
  17558. } else if ($result != $children) {
  17559. $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
  17560. }
  17561. }
  17562. }
  17563. }
  17564. }
  17565. //####################################################################//
  17566. // Post-processing
  17567. // remove context variables
  17568. $context->destroy('IsInline');
  17569. $context->destroy('CurrentNode');
  17570. $context->destroy('CurrentToken');
  17571. //####################################################################//
  17572. // Return
  17573. return HTMLPurifier_Arborize::flatten($node, $config, $context);
  17574. }
  17575. }
  17576. /**
  17577. * Takes tokens makes them well-formed (balance end tags, etc.)
  17578. *
  17579. * Specification of the armor attributes this strategy uses:
  17580. *
  17581. * - MakeWellFormed_TagClosedError: This armor field is used to
  17582. * suppress tag closed errors for certain tokens [TagClosedSuppress],
  17583. * in particular, if a tag was generated automatically by HTML
  17584. * Purifier, we may rely on our infrastructure to close it for us
  17585. * and shouldn't report an error to the user [TagClosedAuto].
  17586. */
  17587. class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
  17588. {
  17589. /**
  17590. * Array stream of tokens being processed.
  17591. * @type HTMLPurifier_Token[]
  17592. */
  17593. protected $tokens;
  17594. /**
  17595. * Current token.
  17596. * @type HTMLPurifier_Token
  17597. */
  17598. protected $token;
  17599. /**
  17600. * Zipper managing the true state.
  17601. * @type HTMLPurifier_Zipper
  17602. */
  17603. protected $zipper;
  17604. /**
  17605. * Current nesting of elements.
  17606. * @type array
  17607. */
  17608. protected $stack;
  17609. /**
  17610. * Injectors active in this stream processing.
  17611. * @type HTMLPurifier_Injector[]
  17612. */
  17613. protected $injectors;
  17614. /**
  17615. * Current instance of HTMLPurifier_Config.
  17616. * @type HTMLPurifier_Config
  17617. */
  17618. protected $config;
  17619. /**
  17620. * Current instance of HTMLPurifier_Context.
  17621. * @type HTMLPurifier_Context
  17622. */
  17623. protected $context;
  17624. /**
  17625. * @param HTMLPurifier_Token[] $tokens
  17626. * @param HTMLPurifier_Config $config
  17627. * @param HTMLPurifier_Context $context
  17628. * @return HTMLPurifier_Token[]
  17629. * @throws HTMLPurifier_Exception
  17630. */
  17631. public function execute($tokens, $config, $context)
  17632. {
  17633. $definition = $config->getHTMLDefinition();
  17634. // local variables
  17635. $generator = new HTMLPurifier_Generator($config, $context);
  17636. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  17637. // used for autoclose early abortion
  17638. $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
  17639. $e = $context->get('ErrorCollector', true);
  17640. $i = false; // injector index
  17641. list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
  17642. if ($token === NULL) {
  17643. return array();
  17644. }
  17645. $reprocess = false; // whether or not to reprocess the same token
  17646. $stack = array();
  17647. // member variables
  17648. $this->stack =& $stack;
  17649. $this->tokens =& $tokens;
  17650. $this->token =& $token;
  17651. $this->zipper =& $zipper;
  17652. $this->config = $config;
  17653. $this->context = $context;
  17654. // context variables
  17655. $context->register('CurrentNesting', $stack);
  17656. $context->register('InputZipper', $zipper);
  17657. $context->register('CurrentToken', $token);
  17658. // -- begin INJECTOR --
  17659. $this->injectors = array();
  17660. $injectors = $config->getBatch('AutoFormat');
  17661. $def_injectors = $definition->info_injector;
  17662. $custom_injectors = $injectors['Custom'];
  17663. unset($injectors['Custom']); // special case
  17664. foreach ($injectors as $injector => $b) {
  17665. // XXX: Fix with a legitimate lookup table of enabled filters
  17666. if (strpos($injector, '.') !== false) {
  17667. continue;
  17668. }
  17669. $injector = "HTMLPurifier_Injector_$injector";
  17670. if (!$b) {
  17671. continue;
  17672. }
  17673. $this->injectors[] = new $injector;
  17674. }
  17675. foreach ($def_injectors as $injector) {
  17676. // assumed to be objects
  17677. $this->injectors[] = $injector;
  17678. }
  17679. foreach ($custom_injectors as $injector) {
  17680. if (!$injector) {
  17681. continue;
  17682. }
  17683. if (is_string($injector)) {
  17684. $injector = "HTMLPurifier_Injector_$injector";
  17685. $injector = new $injector;
  17686. }
  17687. $this->injectors[] = $injector;
  17688. }
  17689. // give the injectors references to the definition and context
  17690. // variables for performance reasons
  17691. foreach ($this->injectors as $ix => $injector) {
  17692. $error = $injector->prepare($config, $context);
  17693. if (!$error) {
  17694. continue;
  17695. }
  17696. array_splice($this->injectors, $ix, 1); // rm the injector
  17697. trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
  17698. }
  17699. // -- end INJECTOR --
  17700. // a note on reprocessing:
  17701. // In order to reduce code duplication, whenever some code needs
  17702. // to make HTML changes in order to make things "correct", the
  17703. // new HTML gets sent through the purifier, regardless of its
  17704. // status. This means that if we add a start token, because it
  17705. // was totally necessary, we don't have to update nesting; we just
  17706. // punt ($reprocess = true; continue;) and it does that for us.
  17707. // isset is in loop because $tokens size changes during loop exec
  17708. for (;;
  17709. // only increment if we don't need to reprocess
  17710. $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
  17711. // check for a rewind
  17712. if (is_int($i)) {
  17713. // possibility: disable rewinding if the current token has a
  17714. // rewind set on it already. This would offer protection from
  17715. // infinite loop, but might hinder some advanced rewinding.
  17716. $rewind_offset = $this->injectors[$i]->getRewindOffset();
  17717. if (is_int($rewind_offset)) {
  17718. for ($j = 0; $j < $rewind_offset; $j++) {
  17719. if (empty($zipper->front)) break;
  17720. $token = $zipper->prev($token);
  17721. // indicate that other injectors should not process this token,
  17722. // but we need to reprocess it. See Note [Injector skips]
  17723. unset($token->skip[$i]);
  17724. $token->rewind = $i;
  17725. if ($token instanceof HTMLPurifier_Token_Start) {
  17726. array_pop($this->stack);
  17727. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17728. $this->stack[] = $token->start;
  17729. }
  17730. }
  17731. }
  17732. $i = false;
  17733. }
  17734. // handle case of document end
  17735. if ($token === NULL) {
  17736. // kill processing if stack is empty
  17737. if (empty($this->stack)) {
  17738. break;
  17739. }
  17740. // peek
  17741. $top_nesting = array_pop($this->stack);
  17742. $this->stack[] = $top_nesting;
  17743. // send error [TagClosedSuppress]
  17744. if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
  17745. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
  17746. }
  17747. // append, don't splice, since this is the end
  17748. $token = new HTMLPurifier_Token_End($top_nesting->name);
  17749. // punt!
  17750. $reprocess = true;
  17751. continue;
  17752. }
  17753. //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);
  17754. //flush();
  17755. // quick-check: if it's not a tag, no need to process
  17756. if (empty($token->is_tag)) {
  17757. if ($token instanceof HTMLPurifier_Token_Text) {
  17758. foreach ($this->injectors as $i => $injector) {
  17759. if (isset($token->skip[$i])) {
  17760. // See Note [Injector skips]
  17761. continue;
  17762. }
  17763. if ($token->rewind !== null && $token->rewind !== $i) {
  17764. continue;
  17765. }
  17766. // XXX fuckup
  17767. $r = $token;
  17768. $injector->handleText($r);
  17769. $token = $this->processToken($r, $i);
  17770. $reprocess = true;
  17771. break;
  17772. }
  17773. }
  17774. // another possibility is a comment
  17775. continue;
  17776. }
  17777. if (isset($definition->info[$token->name])) {
  17778. $type = $definition->info[$token->name]->child->type;
  17779. } else {
  17780. $type = false; // Type is unknown, treat accordingly
  17781. }
  17782. // quick tag checks: anything that's *not* an end tag
  17783. $ok = false;
  17784. if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
  17785. // claims to be a start tag but is empty
  17786. $token = new HTMLPurifier_Token_Empty(
  17787. $token->name,
  17788. $token->attr,
  17789. $token->line,
  17790. $token->col,
  17791. $token->armor
  17792. );
  17793. $ok = true;
  17794. } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
  17795. // claims to be empty but really is a start tag
  17796. // NB: this assignment is required
  17797. $old_token = $token;
  17798. $token = new HTMLPurifier_Token_End($token->name);
  17799. $token = $this->insertBefore(
  17800. new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
  17801. );
  17802. // punt (since we had to modify the input stream in a non-trivial way)
  17803. $reprocess = true;
  17804. continue;
  17805. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  17806. // real empty token
  17807. $ok = true;
  17808. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  17809. // start tag
  17810. // ...unless they also have to close their parent
  17811. if (!empty($this->stack)) {
  17812. // Performance note: you might think that it's rather
  17813. // inefficient, recalculating the autoclose information
  17814. // for every tag that a token closes (since when we
  17815. // do an autoclose, we push a new token into the
  17816. // stream and then /process/ that, before
  17817. // re-processing this token.) But this is
  17818. // necessary, because an injector can make an
  17819. // arbitrary transformations to the autoclosing
  17820. // tokens we introduce, so things may have changed
  17821. // in the meantime. Also, doing the inefficient thing is
  17822. // "easy" to reason about (for certain perverse definitions
  17823. // of "easy")
  17824. $parent = array_pop($this->stack);
  17825. $this->stack[] = $parent;
  17826. $parent_def = null;
  17827. $parent_elements = null;
  17828. $autoclose = false;
  17829. if (isset($definition->info[$parent->name])) {
  17830. $parent_def = $definition->info[$parent->name];
  17831. $parent_elements = $parent_def->child->getAllowedElements($config);
  17832. $autoclose = !isset($parent_elements[$token->name]);
  17833. }
  17834. if ($autoclose && $definition->info[$token->name]->wrap) {
  17835. // Check if an element can be wrapped by another
  17836. // element to make it valid in a context (for
  17837. // example, <ul><ul> needs a <li> in between)
  17838. $wrapname = $definition->info[$token->name]->wrap;
  17839. $wrapdef = $definition->info[$wrapname];
  17840. $elements = $wrapdef->child->getAllowedElements($config);
  17841. if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
  17842. $newtoken = new HTMLPurifier_Token_Start($wrapname);
  17843. $token = $this->insertBefore($newtoken);
  17844. $reprocess = true;
  17845. continue;
  17846. }
  17847. }
  17848. $carryover = false;
  17849. if ($autoclose && $parent_def->formatting) {
  17850. $carryover = true;
  17851. }
  17852. if ($autoclose) {
  17853. // check if this autoclose is doomed to fail
  17854. // (this rechecks $parent, which his harmless)
  17855. $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
  17856. if (!$autoclose_ok) {
  17857. foreach ($this->stack as $ancestor) {
  17858. $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
  17859. if (isset($elements[$token->name])) {
  17860. $autoclose_ok = true;
  17861. break;
  17862. }
  17863. if ($definition->info[$token->name]->wrap) {
  17864. $wrapname = $definition->info[$token->name]->wrap;
  17865. $wrapdef = $definition->info[$wrapname];
  17866. $wrap_elements = $wrapdef->child->getAllowedElements($config);
  17867. if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
  17868. $autoclose_ok = true;
  17869. break;
  17870. }
  17871. }
  17872. }
  17873. }
  17874. if ($autoclose_ok) {
  17875. // errors need to be updated
  17876. $new_token = new HTMLPurifier_Token_End($parent->name);
  17877. $new_token->start = $parent;
  17878. // [TagClosedSuppress]
  17879. if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
  17880. if (!$carryover) {
  17881. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
  17882. } else {
  17883. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
  17884. }
  17885. }
  17886. if ($carryover) {
  17887. $element = clone $parent;
  17888. // [TagClosedAuto]
  17889. $element->armor['MakeWellFormed_TagClosedError'] = true;
  17890. $element->carryover = true;
  17891. $token = $this->processToken(array($new_token, $token, $element));
  17892. } else {
  17893. $token = $this->insertBefore($new_token);
  17894. }
  17895. } else {
  17896. $token = $this->remove();
  17897. }
  17898. $reprocess = true;
  17899. continue;
  17900. }
  17901. }
  17902. $ok = true;
  17903. }
  17904. if ($ok) {
  17905. foreach ($this->injectors as $i => $injector) {
  17906. if (isset($token->skip[$i])) {
  17907. // See Note [Injector skips]
  17908. continue;
  17909. }
  17910. if ($token->rewind !== null && $token->rewind !== $i) {
  17911. continue;
  17912. }
  17913. $r = $token;
  17914. $injector->handleElement($r);
  17915. $token = $this->processToken($r, $i);
  17916. $reprocess = true;
  17917. break;
  17918. }
  17919. if (!$reprocess) {
  17920. // ah, nothing interesting happened; do normal processing
  17921. if ($token instanceof HTMLPurifier_Token_Start) {
  17922. $this->stack[] = $token;
  17923. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17924. throw new HTMLPurifier_Exception(
  17925. 'Improper handling of end tag in start code; possible error in MakeWellFormed'
  17926. );
  17927. }
  17928. }
  17929. continue;
  17930. }
  17931. // sanity check: we should be dealing with a closing tag
  17932. if (!$token instanceof HTMLPurifier_Token_End) {
  17933. throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
  17934. }
  17935. // make sure that we have something open
  17936. if (empty($this->stack)) {
  17937. if ($escape_invalid_tags) {
  17938. if ($e) {
  17939. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
  17940. }
  17941. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  17942. } else {
  17943. if ($e) {
  17944. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
  17945. }
  17946. $token = $this->remove();
  17947. }
  17948. $reprocess = true;
  17949. continue;
  17950. }
  17951. // first, check for the simplest case: everything closes neatly.
  17952. // Eventually, everything passes through here; if there are problems
  17953. // we modify the input stream accordingly and then punt, so that
  17954. // the tokens get processed again.
  17955. $current_parent = array_pop($this->stack);
  17956. if ($current_parent->name == $token->name) {
  17957. $token->start = $current_parent;
  17958. foreach ($this->injectors as $i => $injector) {
  17959. if (isset($token->skip[$i])) {
  17960. // See Note [Injector skips]
  17961. continue;
  17962. }
  17963. if ($token->rewind !== null && $token->rewind !== $i) {
  17964. continue;
  17965. }
  17966. $r = $token;
  17967. $injector->handleEnd($r);
  17968. $token = $this->processToken($r, $i);
  17969. $this->stack[] = $current_parent;
  17970. $reprocess = true;
  17971. break;
  17972. }
  17973. continue;
  17974. }
  17975. // okay, so we're trying to close the wrong tag
  17976. // undo the pop previous pop
  17977. $this->stack[] = $current_parent;
  17978. // scroll back the entire nest, trying to find our tag.
  17979. // (feature could be to specify how far you'd like to go)
  17980. $size = count($this->stack);
  17981. // -2 because -1 is the last element, but we already checked that
  17982. $skipped_tags = false;
  17983. for ($j = $size - 2; $j >= 0; $j--) {
  17984. if ($this->stack[$j]->name == $token->name) {
  17985. $skipped_tags = array_slice($this->stack, $j);
  17986. break;
  17987. }
  17988. }
  17989. // we didn't find the tag, so remove
  17990. if ($skipped_tags === false) {
  17991. if ($escape_invalid_tags) {
  17992. if ($e) {
  17993. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
  17994. }
  17995. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  17996. } else {
  17997. if ($e) {
  17998. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
  17999. }
  18000. $token = $this->remove();
  18001. }
  18002. $reprocess = true;
  18003. continue;
  18004. }
  18005. // do errors, in REVERSE $j order: a,b,c with </a></b></c>
  18006. $c = count($skipped_tags);
  18007. if ($e) {
  18008. for ($j = $c - 1; $j > 0; $j--) {
  18009. // notice we exclude $j == 0, i.e. the current ending tag, from
  18010. // the errors... [TagClosedSuppress]
  18011. if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
  18012. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
  18013. }
  18014. }
  18015. }
  18016. // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
  18017. $replace = array($token);
  18018. for ($j = 1; $j < $c; $j++) {
  18019. // ...as well as from the insertions
  18020. $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
  18021. $new_token->start = $skipped_tags[$j];
  18022. array_unshift($replace, $new_token);
  18023. if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
  18024. // [TagClosedAuto]
  18025. $element = clone $skipped_tags[$j];
  18026. $element->carryover = true;
  18027. $element->armor['MakeWellFormed_TagClosedError'] = true;
  18028. $replace[] = $element;
  18029. }
  18030. }
  18031. $token = $this->processToken($replace);
  18032. $reprocess = true;
  18033. continue;
  18034. }
  18035. $context->destroy('CurrentToken');
  18036. $context->destroy('CurrentNesting');
  18037. $context->destroy('InputZipper');
  18038. unset($this->injectors, $this->stack, $this->tokens);
  18039. return $zipper->toArray($token);
  18040. }
  18041. /**
  18042. * Processes arbitrary token values for complicated substitution patterns.
  18043. * In general:
  18044. *
  18045. * If $token is an array, it is a list of tokens to substitute for the
  18046. * current token. These tokens then get individually processed. If there
  18047. * is a leading integer in the list, that integer determines how many
  18048. * tokens from the stream should be removed.
  18049. *
  18050. * If $token is a regular token, it is swapped with the current token.
  18051. *
  18052. * If $token is false, the current token is deleted.
  18053. *
  18054. * If $token is an integer, that number of tokens (with the first token
  18055. * being the current one) will be deleted.
  18056. *
  18057. * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
  18058. * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
  18059. * this is not an injector related operation.
  18060. * @throws HTMLPurifier_Exception
  18061. */
  18062. protected function processToken($token, $injector = -1)
  18063. {
  18064. // Zend OpCache miscompiles $token = array($token), so
  18065. // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108
  18066. // normalize forms of token
  18067. if (is_object($token)) {
  18068. $tmp = $token;
  18069. $token = array(1, $tmp);
  18070. }
  18071. if (is_int($token)) {
  18072. $tmp = $token;
  18073. $token = array($tmp);
  18074. }
  18075. if ($token === false) {
  18076. $token = array(1);
  18077. }
  18078. if (!is_array($token)) {
  18079. throw new HTMLPurifier_Exception('Invalid token type from injector');
  18080. }
  18081. if (!is_int($token[0])) {
  18082. array_unshift($token, 1);
  18083. }
  18084. if ($token[0] === 0) {
  18085. throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
  18086. }
  18087. // $token is now an array with the following form:
  18088. // array(number nodes to delete, new node 1, new node 2, ...)
  18089. $delete = array_shift($token);
  18090. list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
  18091. if ($injector > -1) {
  18092. // See Note [Injector skips]
  18093. // Determine appropriate skips. Here's what the code does:
  18094. // *If* we deleted one or more tokens, copy the skips
  18095. // of those tokens into the skips of the new tokens (in $token).
  18096. // Also, mark the newly inserted tokens as having come from
  18097. // $injector.
  18098. $oldskip = isset($old[0]) ? $old[0]->skip : array();
  18099. foreach ($token as $object) {
  18100. $object->skip = $oldskip;
  18101. $object->skip[$injector] = true;
  18102. }
  18103. }
  18104. return $r;
  18105. }
  18106. /**
  18107. * Inserts a token before the current token. Cursor now points to
  18108. * this token. You must reprocess after this.
  18109. * @param HTMLPurifier_Token $token
  18110. */
  18111. private function insertBefore($token)
  18112. {
  18113. // NB not $this->zipper->insertBefore(), due to positioning
  18114. // differences
  18115. $splice = $this->zipper->splice($this->token, 0, array($token));
  18116. return $splice[1];
  18117. }
  18118. /**
  18119. * Removes current token. Cursor now points to new token occupying previously
  18120. * occupied space. You must reprocess after this.
  18121. */
  18122. private function remove()
  18123. {
  18124. return $this->zipper->delete();
  18125. }
  18126. }
  18127. // Note [Injector skips]
  18128. // ~~~~~~~~~~~~~~~~~~~~~
  18129. // When I originally designed this class, the idea behind the 'skip'
  18130. // property of HTMLPurifier_Token was to help avoid infinite loops
  18131. // in injector processing. For example, suppose you wrote an injector
  18132. // that bolded swear words. Naively, you might write it so that
  18133. // whenever you saw ****, you replaced it with <strong>****</strong>.
  18134. //
  18135. // When this happens, we will reprocess all of the tokens with the
  18136. // other injectors. Now there is an opportunity for infinite loop:
  18137. // if we rerun the swear-word injector on these tokens, we might
  18138. // see **** and then reprocess again to get
  18139. // <strong><strong>****</strong></strong> ad infinitum.
  18140. //
  18141. // Thus, the idea of a skip is that once we process a token with
  18142. // an injector, we mark all of those tokens as having "come from"
  18143. // the injector, and we never run the injector again on these
  18144. // tokens.
  18145. //
  18146. // There were two more complications, however:
  18147. //
  18148. // - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
  18149. // you had <b><i></i></b>, after you removed the <i></i>, you
  18150. // really would like this injector to go back and reprocess
  18151. // the <b> tag, discovering that it is now empty and can be
  18152. // removed. So we reintroduced the possibility of infinite looping
  18153. // by adding a "rewind" function, which let you go back to an
  18154. // earlier point in the token stream and reprocess it with injectors.
  18155. // Needless to say, we need to UN-skip the token so it gets
  18156. // reprocessed.
  18157. //
  18158. // - Suppose that you successfuly process a token, replace it with
  18159. // one with your skip mark, but now another injector wants to
  18160. // process the skipped token with another token. Should you continue
  18161. // to skip that new token, or reprocess it? If you reprocess,
  18162. // you can end up with an infinite loop where one injector converts
  18163. // <a> to <b>, and then another injector converts it back. So
  18164. // we inherit the skips, but for some reason, I thought that we
  18165. // should inherit the skip from the first token of the token
  18166. // that we deleted. Why? Well, it seems to work OK.
  18167. //
  18168. // If I were to redesign this functionality, I would absolutely not
  18169. // go about doing it this way: the semantics are just not very well
  18170. // defined, and in any case you probably wanted to operate on trees,
  18171. // not token streams.
  18172. /**
  18173. * Removes all unrecognized tags from the list of tokens.
  18174. *
  18175. * This strategy iterates through all the tokens and removes unrecognized
  18176. * tokens. If a token is not recognized but a TagTransform is defined for
  18177. * that element, the element will be transformed accordingly.
  18178. */
  18179. class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
  18180. {
  18181. /**
  18182. * @param HTMLPurifier_Token[] $tokens
  18183. * @param HTMLPurifier_Config $config
  18184. * @param HTMLPurifier_Context $context
  18185. * @return array|HTMLPurifier_Token[]
  18186. */
  18187. public function execute($tokens, $config, $context)
  18188. {
  18189. $definition = $config->getHTMLDefinition();
  18190. $generator = new HTMLPurifier_Generator($config, $context);
  18191. $result = array();
  18192. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  18193. $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
  18194. // currently only used to determine if comments should be kept
  18195. $trusted = $config->get('HTML.Trusted');
  18196. $comment_lookup = $config->get('HTML.AllowedComments');
  18197. $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
  18198. $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
  18199. $remove_script_contents = $config->get('Core.RemoveScriptContents');
  18200. $hidden_elements = $config->get('Core.HiddenElements');
  18201. // remove script contents compatibility
  18202. if ($remove_script_contents === true) {
  18203. $hidden_elements['script'] = true;
  18204. } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
  18205. unset($hidden_elements['script']);
  18206. }
  18207. $attr_validator = new HTMLPurifier_AttrValidator();
  18208. // removes tokens until it reaches a closing tag with its value
  18209. $remove_until = false;
  18210. // converts comments into text tokens when this is equal to a tag name
  18211. $textify_comments = false;
  18212. $token = false;
  18213. $context->register('CurrentToken', $token);
  18214. $e = false;
  18215. if ($config->get('Core.CollectErrors')) {
  18216. $e =& $context->get('ErrorCollector');
  18217. }
  18218. foreach ($tokens as $token) {
  18219. if ($remove_until) {
  18220. if (empty($token->is_tag) || $token->name !== $remove_until) {
  18221. continue;
  18222. }
  18223. }
  18224. if (!empty($token->is_tag)) {
  18225. // DEFINITION CALL
  18226. // before any processing, try to transform the element
  18227. if (isset($definition->info_tag_transform[$token->name])) {
  18228. $original_name = $token->name;
  18229. // there is a transformation for this tag
  18230. // DEFINITION CALL
  18231. $token = $definition->
  18232. info_tag_transform[$token->name]->transform($token, $config, $context);
  18233. if ($e) {
  18234. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
  18235. }
  18236. }
  18237. if (isset($definition->info[$token->name])) {
  18238. // mostly everything's good, but
  18239. // we need to make sure required attributes are in order
  18240. if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
  18241. $definition->info[$token->name]->required_attr &&
  18242. ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
  18243. ) {
  18244. $attr_validator->validateToken($token, $config, $context);
  18245. $ok = true;
  18246. foreach ($definition->info[$token->name]->required_attr as $name) {
  18247. if (!isset($token->attr[$name])) {
  18248. $ok = false;
  18249. break;
  18250. }
  18251. }
  18252. if (!$ok) {
  18253. if ($e) {
  18254. $e->send(
  18255. E_ERROR,
  18256. 'Strategy_RemoveForeignElements: Missing required attribute',
  18257. $name
  18258. );
  18259. }
  18260. continue;
  18261. }
  18262. $token->armor['ValidateAttributes'] = true;
  18263. }
  18264. if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
  18265. $textify_comments = $token->name;
  18266. } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
  18267. $textify_comments = false;
  18268. }
  18269. } elseif ($escape_invalid_tags) {
  18270. // invalid tag, generate HTML representation and insert in
  18271. if ($e) {
  18272. $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
  18273. }
  18274. $token = new HTMLPurifier_Token_Text(
  18275. $generator->generateFromToken($token)
  18276. );
  18277. } else {
  18278. // check if we need to destroy all of the tag's children
  18279. // CAN BE GENERICIZED
  18280. if (isset($hidden_elements[$token->name])) {
  18281. if ($token instanceof HTMLPurifier_Token_Start) {
  18282. $remove_until = $token->name;
  18283. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  18284. // do nothing: we're still looking
  18285. } else {
  18286. $remove_until = false;
  18287. }
  18288. if ($e) {
  18289. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
  18290. }
  18291. } else {
  18292. if ($e) {
  18293. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
  18294. }
  18295. }
  18296. continue;
  18297. }
  18298. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  18299. // textify comments in script tags when they are allowed
  18300. if ($textify_comments !== false) {
  18301. $data = $token->data;
  18302. $token = new HTMLPurifier_Token_Text($data);
  18303. } elseif ($trusted || $check_comments) {
  18304. // always cleanup comments
  18305. $trailing_hyphen = false;
  18306. if ($e) {
  18307. // perform check whether or not there's a trailing hyphen
  18308. if (substr($token->data, -1) == '-') {
  18309. $trailing_hyphen = true;
  18310. }
  18311. }
  18312. $token->data = rtrim($token->data, '-');
  18313. $found_double_hyphen = false;
  18314. while (strpos($token->data, '--') !== false) {
  18315. $found_double_hyphen = true;
  18316. $token->data = str_replace('--', '-', $token->data);
  18317. }
  18318. if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
  18319. ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
  18320. // OK good
  18321. if ($e) {
  18322. if ($trailing_hyphen) {
  18323. $e->send(
  18324. E_NOTICE,
  18325. 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
  18326. );
  18327. }
  18328. if ($found_double_hyphen) {
  18329. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
  18330. }
  18331. }
  18332. } else {
  18333. if ($e) {
  18334. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  18335. }
  18336. continue;
  18337. }
  18338. } else {
  18339. // strip comments
  18340. if ($e) {
  18341. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  18342. }
  18343. continue;
  18344. }
  18345. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  18346. } else {
  18347. continue;
  18348. }
  18349. $result[] = $token;
  18350. }
  18351. if ($remove_until && $e) {
  18352. // we removed tokens until the end, throw error
  18353. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
  18354. }
  18355. $context->destroy('CurrentToken');
  18356. return $result;
  18357. }
  18358. }
  18359. /**
  18360. * Validate all attributes in the tokens.
  18361. */
  18362. class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
  18363. {
  18364. /**
  18365. * @param HTMLPurifier_Token[] $tokens
  18366. * @param HTMLPurifier_Config $config
  18367. * @param HTMLPurifier_Context $context
  18368. * @return HTMLPurifier_Token[]
  18369. */
  18370. public function execute($tokens, $config, $context)
  18371. {
  18372. // setup validator
  18373. $validator = new HTMLPurifier_AttrValidator();
  18374. $token = false;
  18375. $context->register('CurrentToken', $token);
  18376. foreach ($tokens as $key => $token) {
  18377. // only process tokens that have attributes,
  18378. // namely start and empty tags
  18379. if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
  18380. continue;
  18381. }
  18382. // skip tokens that are armored
  18383. if (!empty($token->armor['ValidateAttributes'])) {
  18384. continue;
  18385. }
  18386. // note that we have no facilities here for removing tokens
  18387. $validator->validateToken($token, $config, $context);
  18388. }
  18389. $context->destroy('CurrentToken');
  18390. return $tokens;
  18391. }
  18392. }
  18393. /**
  18394. * Transforms FONT tags to the proper form (SPAN with CSS styling)
  18395. *
  18396. * This transformation takes the three proprietary attributes of FONT and
  18397. * transforms them into their corresponding CSS attributes. These are color,
  18398. * face, and size.
  18399. *
  18400. * @note Size is an interesting case because it doesn't map cleanly to CSS.
  18401. * Thanks to
  18402. * http://style.cleverchimp.com/font_size_intervals/altintervals.html
  18403. * for reasonable mappings.
  18404. * @warning This doesn't work completely correctly; specifically, this
  18405. * TagTransform operates before well-formedness is enforced, so
  18406. * the "active formatting elements" algorithm doesn't get applied.
  18407. */
  18408. class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
  18409. {
  18410. /**
  18411. * @type string
  18412. */
  18413. public $transform_to = 'span';
  18414. /**
  18415. * @type array
  18416. */
  18417. protected $_size_lookup = array(
  18418. '0' => 'xx-small',
  18419. '1' => 'xx-small',
  18420. '2' => 'small',
  18421. '3' => 'medium',
  18422. '4' => 'large',
  18423. '5' => 'x-large',
  18424. '6' => 'xx-large',
  18425. '7' => '300%',
  18426. '-1' => 'smaller',
  18427. '-2' => '60%',
  18428. '+1' => 'larger',
  18429. '+2' => '150%',
  18430. '+3' => '200%',
  18431. '+4' => '300%'
  18432. );
  18433. /**
  18434. * @param HTMLPurifier_Token_Tag $tag
  18435. * @param HTMLPurifier_Config $config
  18436. * @param HTMLPurifier_Context $context
  18437. * @return HTMLPurifier_Token_End|string
  18438. */
  18439. public function transform($tag, $config, $context)
  18440. {
  18441. if ($tag instanceof HTMLPurifier_Token_End) {
  18442. $new_tag = clone $tag;
  18443. $new_tag->name = $this->transform_to;
  18444. return $new_tag;
  18445. }
  18446. $attr = $tag->attr;
  18447. $prepend_style = '';
  18448. // handle color transform
  18449. if (isset($attr['color'])) {
  18450. $prepend_style .= 'color:' . $attr['color'] . ';';
  18451. unset($attr['color']);
  18452. }
  18453. // handle face transform
  18454. if (isset($attr['face'])) {
  18455. $prepend_style .= 'font-family:' . $attr['face'] . ';';
  18456. unset($attr['face']);
  18457. }
  18458. // handle size transform
  18459. if (isset($attr['size'])) {
  18460. // normalize large numbers
  18461. if ($attr['size'] !== '') {
  18462. if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
  18463. $size = (int)$attr['size'];
  18464. if ($size < -2) {
  18465. $attr['size'] = '-2';
  18466. }
  18467. if ($size > 4) {
  18468. $attr['size'] = '+4';
  18469. }
  18470. } else {
  18471. $size = (int)$attr['size'];
  18472. if ($size > 7) {
  18473. $attr['size'] = '7';
  18474. }
  18475. }
  18476. }
  18477. if (isset($this->_size_lookup[$attr['size']])) {
  18478. $prepend_style .= 'font-size:' .
  18479. $this->_size_lookup[$attr['size']] . ';';
  18480. }
  18481. unset($attr['size']);
  18482. }
  18483. if ($prepend_style) {
  18484. $attr['style'] = isset($attr['style']) ?
  18485. $prepend_style . $attr['style'] :
  18486. $prepend_style;
  18487. }
  18488. $new_tag = clone $tag;
  18489. $new_tag->name = $this->transform_to;
  18490. $new_tag->attr = $attr;
  18491. return $new_tag;
  18492. }
  18493. }
  18494. /**
  18495. * Simple transformation, just change tag name to something else,
  18496. * and possibly add some styling. This will cover most of the deprecated
  18497. * tag cases.
  18498. */
  18499. class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
  18500. {
  18501. /**
  18502. * @type string
  18503. */
  18504. protected $style;
  18505. /**
  18506. * @param string $transform_to Tag name to transform to.
  18507. * @param string $style CSS style to add to the tag
  18508. */
  18509. public function __construct($transform_to, $style = null)
  18510. {
  18511. $this->transform_to = $transform_to;
  18512. $this->style = $style;
  18513. }
  18514. /**
  18515. * @param HTMLPurifier_Token_Tag $tag
  18516. * @param HTMLPurifier_Config $config
  18517. * @param HTMLPurifier_Context $context
  18518. * @return string
  18519. */
  18520. public function transform($tag, $config, $context)
  18521. {
  18522. $new_tag = clone $tag;
  18523. $new_tag->name = $this->transform_to;
  18524. if (!is_null($this->style) &&
  18525. ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
  18526. ) {
  18527. $this->prependCSS($new_tag->attr, $this->style);
  18528. }
  18529. return $new_tag;
  18530. }
  18531. }
  18532. /**
  18533. * Concrete comment token class. Generally will be ignored.
  18534. */
  18535. class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
  18536. {
  18537. /**
  18538. * Character data within comment.
  18539. * @type string
  18540. */
  18541. public $data;
  18542. /**
  18543. * @type bool
  18544. */
  18545. public $is_whitespace = true;
  18546. /**
  18547. * Transparent constructor.
  18548. *
  18549. * @param string $data String comment data.
  18550. * @param int $line
  18551. * @param int $col
  18552. */
  18553. public function __construct($data, $line = null, $col = null)
  18554. {
  18555. $this->data = $data;
  18556. $this->line = $line;
  18557. $this->col = $col;
  18558. }
  18559. public function toNode() {
  18560. return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
  18561. }
  18562. }
  18563. /**
  18564. * Abstract class of a tag token (start, end or empty), and its behavior.
  18565. */
  18566. abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
  18567. {
  18568. /**
  18569. * Static bool marker that indicates the class is a tag.
  18570. *
  18571. * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
  18572. * without having to use a function call <tt>is_a()</tt>.
  18573. * @type bool
  18574. */
  18575. public $is_tag = true;
  18576. /**
  18577. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  18578. *
  18579. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  18580. * be lower-casing them, but these tokens cater to HTML tags, which are
  18581. * insensitive.
  18582. * @type string
  18583. */
  18584. public $name;
  18585. /**
  18586. * Associative array of the tag's attributes.
  18587. * @type array
  18588. */
  18589. public $attr = array();
  18590. /**
  18591. * Non-overloaded constructor, which lower-cases passed tag name.
  18592. *
  18593. * @param string $name String name.
  18594. * @param array $attr Associative array of attributes.
  18595. * @param int $line
  18596. * @param int $col
  18597. * @param array $armor
  18598. */
  18599. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
  18600. {
  18601. $this->name = ctype_lower($name) ? $name : strtolower($name);
  18602. foreach ($attr as $key => $value) {
  18603. // normalization only necessary when key is not lowercase
  18604. if (!ctype_lower($key)) {
  18605. $new_key = strtolower($key);
  18606. if (!isset($attr[$new_key])) {
  18607. $attr[$new_key] = $attr[$key];
  18608. }
  18609. if ($new_key !== $key) {
  18610. unset($attr[$key]);
  18611. }
  18612. }
  18613. }
  18614. $this->attr = $attr;
  18615. $this->line = $line;
  18616. $this->col = $col;
  18617. $this->armor = $armor;
  18618. }
  18619. public function toNode() {
  18620. return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
  18621. }
  18622. }
  18623. /**
  18624. * Concrete empty token class.
  18625. */
  18626. class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
  18627. {
  18628. public function toNode() {
  18629. $n = parent::toNode();
  18630. $n->empty = true;
  18631. return $n;
  18632. }
  18633. }
  18634. /**
  18635. * Concrete end token class.
  18636. *
  18637. * @warning This class accepts attributes even though end tags cannot. This
  18638. * is for optimization reasons, as under normal circumstances, the Lexers
  18639. * do not pass attributes.
  18640. */
  18641. class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
  18642. {
  18643. /**
  18644. * Token that started this node.
  18645. * Added by MakeWellFormed. Please do not edit this!
  18646. * @type HTMLPurifier_Token
  18647. */
  18648. public $start;
  18649. public function toNode() {
  18650. throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
  18651. }
  18652. }
  18653. /**
  18654. * Concrete start token class.
  18655. */
  18656. class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
  18657. {
  18658. }
  18659. /**
  18660. * Concrete text token class.
  18661. *
  18662. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  18663. * character data (from the CDATA sections). Internally, their
  18664. * data is parsed with all entities expanded. Surprisingly, the text token
  18665. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  18666. * in permissible child nodes.
  18667. */
  18668. class HTMLPurifier_Token_Text extends HTMLPurifier_Token
  18669. {
  18670. /**
  18671. * @type string
  18672. */
  18673. public $name = '#PCDATA';
  18674. /**< PCDATA tag name compatible with DTD. */
  18675. /**
  18676. * @type string
  18677. */
  18678. public $data;
  18679. /**< Parsed character data of text. */
  18680. /**
  18681. * @type bool
  18682. */
  18683. public $is_whitespace;
  18684. /**< Bool indicating if node is whitespace. */
  18685. /**
  18686. * Constructor, accepts data and determines if it is whitespace.
  18687. * @param string $data String parsed character data.
  18688. * @param int $line
  18689. * @param int $col
  18690. */
  18691. public function __construct($data, $line = null, $col = null)
  18692. {
  18693. $this->data = $data;
  18694. $this->is_whitespace = ctype_space($data);
  18695. $this->line = $line;
  18696. $this->col = $col;
  18697. }
  18698. public function toNode() {
  18699. return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
  18700. }
  18701. }
  18702. class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
  18703. {
  18704. /**
  18705. * @type string
  18706. */
  18707. public $name = 'DisableExternal';
  18708. /**
  18709. * @type array
  18710. */
  18711. protected $ourHostParts = false;
  18712. /**
  18713. * @param HTMLPurifier_Config $config
  18714. * @return void
  18715. */
  18716. public function prepare($config)
  18717. {
  18718. $our_host = $config->getDefinition('URI')->host;
  18719. if ($our_host !== null) {
  18720. $this->ourHostParts = array_reverse(explode('.', $our_host));
  18721. }
  18722. }
  18723. /**
  18724. * @param HTMLPurifier_URI $uri Reference
  18725. * @param HTMLPurifier_Config $config
  18726. * @param HTMLPurifier_Context $context
  18727. * @return bool
  18728. */
  18729. public function filter(&$uri, $config, $context)
  18730. {
  18731. if (is_null($uri->host)) {
  18732. return true;
  18733. }
  18734. if ($this->ourHostParts === false) {
  18735. return false;
  18736. }
  18737. $host_parts = array_reverse(explode('.', $uri->host));
  18738. foreach ($this->ourHostParts as $i => $x) {
  18739. if (!isset($host_parts[$i])) {
  18740. return false;
  18741. }
  18742. if ($host_parts[$i] != $this->ourHostParts[$i]) {
  18743. return false;
  18744. }
  18745. }
  18746. return true;
  18747. }
  18748. }
  18749. class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
  18750. {
  18751. /**
  18752. * @type string
  18753. */
  18754. public $name = 'DisableExternalResources';
  18755. /**
  18756. * @param HTMLPurifier_URI $uri
  18757. * @param HTMLPurifier_Config $config
  18758. * @param HTMLPurifier_Context $context
  18759. * @return bool
  18760. */
  18761. public function filter(&$uri, $config, $context)
  18762. {
  18763. if (!$context->get('EmbeddedURI', true)) {
  18764. return true;
  18765. }
  18766. return parent::filter($uri, $config, $context);
  18767. }
  18768. }
  18769. class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
  18770. {
  18771. /**
  18772. * @type string
  18773. */
  18774. public $name = 'DisableResources';
  18775. /**
  18776. * @param HTMLPurifier_URI $uri
  18777. * @param HTMLPurifier_Config $config
  18778. * @param HTMLPurifier_Context $context
  18779. * @return bool
  18780. */
  18781. public function filter(&$uri, $config, $context)
  18782. {
  18783. return !$context->get('EmbeddedURI', true);
  18784. }
  18785. }
  18786. // It's not clear to me whether or not Punycode means that hostnames
  18787. // do not have canonical forms anymore. As far as I can tell, it's
  18788. // not a problem (punycoding should be identity when no Unicode
  18789. // points are involved), but I'm not 100% sure
  18790. class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
  18791. {
  18792. /**
  18793. * @type string
  18794. */
  18795. public $name = 'HostBlacklist';
  18796. /**
  18797. * @type array
  18798. */
  18799. protected $blacklist = array();
  18800. /**
  18801. * @param HTMLPurifier_Config $config
  18802. * @return bool
  18803. */
  18804. public function prepare($config)
  18805. {
  18806. $this->blacklist = $config->get('URI.HostBlacklist');
  18807. return true;
  18808. }
  18809. /**
  18810. * @param HTMLPurifier_URI $uri
  18811. * @param HTMLPurifier_Config $config
  18812. * @param HTMLPurifier_Context $context
  18813. * @return bool
  18814. */
  18815. public function filter(&$uri, $config, $context)
  18816. {
  18817. foreach ($this->blacklist as $blacklisted_host_fragment) {
  18818. if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
  18819. return false;
  18820. }
  18821. }
  18822. return true;
  18823. }
  18824. }
  18825. // does not support network paths
  18826. class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
  18827. {
  18828. /**
  18829. * @type string
  18830. */
  18831. public $name = 'MakeAbsolute';
  18832. /**
  18833. * @type
  18834. */
  18835. protected $base;
  18836. /**
  18837. * @type array
  18838. */
  18839. protected $basePathStack = array();
  18840. /**
  18841. * @param HTMLPurifier_Config $config
  18842. * @return bool
  18843. */
  18844. public function prepare($config)
  18845. {
  18846. $def = $config->getDefinition('URI');
  18847. $this->base = $def->base;
  18848. if (is_null($this->base)) {
  18849. trigger_error(
  18850. 'URI.MakeAbsolute is being ignored due to lack of ' .
  18851. 'value for URI.Base configuration',
  18852. E_USER_WARNING
  18853. );
  18854. return false;
  18855. }
  18856. $this->base->fragment = null; // fragment is invalid for base URI
  18857. $stack = explode('/', $this->base->path);
  18858. array_pop($stack); // discard last segment
  18859. $stack = $this->_collapseStack($stack); // do pre-parsing
  18860. $this->basePathStack = $stack;
  18861. return true;
  18862. }
  18863. /**
  18864. * @param HTMLPurifier_URI $uri
  18865. * @param HTMLPurifier_Config $config
  18866. * @param HTMLPurifier_Context $context
  18867. * @return bool
  18868. */
  18869. public function filter(&$uri, $config, $context)
  18870. {
  18871. if (is_null($this->base)) {
  18872. return true;
  18873. } // abort early
  18874. if ($uri->path === '' && is_null($uri->scheme) &&
  18875. is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
  18876. // reference to current document
  18877. $uri = clone $this->base;
  18878. return true;
  18879. }
  18880. if (!is_null($uri->scheme)) {
  18881. // absolute URI already: don't change
  18882. if (!is_null($uri->host)) {
  18883. return true;
  18884. }
  18885. $scheme_obj = $uri->getSchemeObj($config, $context);
  18886. if (!$scheme_obj) {
  18887. // scheme not recognized
  18888. return false;
  18889. }
  18890. if (!$scheme_obj->hierarchical) {
  18891. // non-hierarchal URI with explicit scheme, don't change
  18892. return true;
  18893. }
  18894. // special case: had a scheme but always is hierarchical and had no authority
  18895. }
  18896. if (!is_null($uri->host)) {
  18897. // network path, don't bother
  18898. return true;
  18899. }
  18900. if ($uri->path === '') {
  18901. $uri->path = $this->base->path;
  18902. } elseif ($uri->path[0] !== '/') {
  18903. // relative path, needs more complicated processing
  18904. $stack = explode('/', $uri->path);
  18905. $new_stack = array_merge($this->basePathStack, $stack);
  18906. if ($new_stack[0] !== '' && !is_null($this->base->host)) {
  18907. array_unshift($new_stack, '');
  18908. }
  18909. $new_stack = $this->_collapseStack($new_stack);
  18910. $uri->path = implode('/', $new_stack);
  18911. } else {
  18912. // absolute path, but still we should collapse
  18913. $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
  18914. }
  18915. // re-combine
  18916. $uri->scheme = $this->base->scheme;
  18917. if (is_null($uri->userinfo)) {
  18918. $uri->userinfo = $this->base->userinfo;
  18919. }
  18920. if (is_null($uri->host)) {
  18921. $uri->host = $this->base->host;
  18922. }
  18923. if (is_null($uri->port)) {
  18924. $uri->port = $this->base->port;
  18925. }
  18926. return true;
  18927. }
  18928. /**
  18929. * Resolve dots and double-dots in a path stack
  18930. * @param array $stack
  18931. * @return array
  18932. */
  18933. private function _collapseStack($stack)
  18934. {
  18935. $result = array();
  18936. $is_folder = false;
  18937. for ($i = 0; isset($stack[$i]); $i++) {
  18938. $is_folder = false;
  18939. // absorb an internally duplicated slash
  18940. if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
  18941. continue;
  18942. }
  18943. if ($stack[$i] == '..') {
  18944. if (!empty($result)) {
  18945. $segment = array_pop($result);
  18946. if ($segment === '' && empty($result)) {
  18947. // error case: attempted to back out too far:
  18948. // restore the leading slash
  18949. $result[] = '';
  18950. } elseif ($segment === '..') {
  18951. $result[] = '..'; // cannot remove .. with ..
  18952. }
  18953. } else {
  18954. // relative path, preserve the double-dots
  18955. $result[] = '..';
  18956. }
  18957. $is_folder = true;
  18958. continue;
  18959. }
  18960. if ($stack[$i] == '.') {
  18961. // silently absorb
  18962. $is_folder = true;
  18963. continue;
  18964. }
  18965. $result[] = $stack[$i];
  18966. }
  18967. if ($is_folder) {
  18968. $result[] = '';
  18969. }
  18970. return $result;
  18971. }
  18972. }
  18973. class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
  18974. {
  18975. /**
  18976. * @type string
  18977. */
  18978. public $name = 'Munge';
  18979. /**
  18980. * @type bool
  18981. */
  18982. public $post = true;
  18983. /**
  18984. * @type string
  18985. */
  18986. private $target;
  18987. /**
  18988. * @type HTMLPurifier_URIParser
  18989. */
  18990. private $parser;
  18991. /**
  18992. * @type bool
  18993. */
  18994. private $doEmbed;
  18995. /**
  18996. * @type string
  18997. */
  18998. private $secretKey;
  18999. /**
  19000. * @type array
  19001. */
  19002. protected $replace = array();
  19003. /**
  19004. * @param HTMLPurifier_Config $config
  19005. * @return bool
  19006. */
  19007. public function prepare($config)
  19008. {
  19009. $this->target = $config->get('URI.' . $this->name);
  19010. $this->parser = new HTMLPurifier_URIParser();
  19011. $this->doEmbed = $config->get('URI.MungeResources');
  19012. $this->secretKey = $config->get('URI.MungeSecretKey');
  19013. if ($this->secretKey && !function_exists('hash_hmac')) {
  19014. throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
  19015. }
  19016. return true;
  19017. }
  19018. /**
  19019. * @param HTMLPurifier_URI $uri
  19020. * @param HTMLPurifier_Config $config
  19021. * @param HTMLPurifier_Context $context
  19022. * @return bool
  19023. */
  19024. public function filter(&$uri, $config, $context)
  19025. {
  19026. if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
  19027. return true;
  19028. }
  19029. $scheme_obj = $uri->getSchemeObj($config, $context);
  19030. if (!$scheme_obj) {
  19031. return true;
  19032. } // ignore unknown schemes, maybe another postfilter did it
  19033. if (!$scheme_obj->browsable) {
  19034. return true;
  19035. } // ignore non-browseable schemes, since we can't munge those in a reasonable way
  19036. if ($uri->isBenign($config, $context)) {
  19037. return true;
  19038. } // don't redirect if a benign URL
  19039. $this->makeReplace($uri, $config, $context);
  19040. $this->replace = array_map('rawurlencode', $this->replace);
  19041. $new_uri = strtr($this->target, $this->replace);
  19042. $new_uri = $this->parser->parse($new_uri);
  19043. // don't redirect if the target host is the same as the
  19044. // starting host
  19045. if ($uri->host === $new_uri->host) {
  19046. return true;
  19047. }
  19048. $uri = $new_uri; // overwrite
  19049. return true;
  19050. }
  19051. /**
  19052. * @param HTMLPurifier_URI $uri
  19053. * @param HTMLPurifier_Config $config
  19054. * @param HTMLPurifier_Context $context
  19055. */
  19056. protected function makeReplace($uri, $config, $context)
  19057. {
  19058. $string = $uri->toString();
  19059. // always available
  19060. $this->replace['%s'] = $string;
  19061. $this->replace['%r'] = $context->get('EmbeddedURI', true);
  19062. $token = $context->get('CurrentToken', true);
  19063. $this->replace['%n'] = $token ? $token->name : null;
  19064. $this->replace['%m'] = $context->get('CurrentAttr', true);
  19065. $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
  19066. // not always available
  19067. if ($this->secretKey) {
  19068. $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
  19069. }
  19070. }
  19071. }
  19072. /**
  19073. * Implements safety checks for safe iframes.
  19074. *
  19075. * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
  19076. * works safely.
  19077. */
  19078. class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
  19079. {
  19080. /**
  19081. * @type string
  19082. */
  19083. public $name = 'SafeIframe';
  19084. /**
  19085. * @type bool
  19086. */
  19087. public $always_load = true;
  19088. /**
  19089. * @type string
  19090. */
  19091. protected $regexp = null;
  19092. // XXX: The not so good bit about how this is all set up now is we
  19093. // can't check HTML.SafeIframe in the 'prepare' step: we have to
  19094. // defer till the actual filtering.
  19095. /**
  19096. * @param HTMLPurifier_Config $config
  19097. * @return bool
  19098. */
  19099. public function prepare($config)
  19100. {
  19101. $this->regexp = $config->get('URI.SafeIframeRegexp');
  19102. return true;
  19103. }
  19104. /**
  19105. * @param HTMLPurifier_URI $uri
  19106. * @param HTMLPurifier_Config $config
  19107. * @param HTMLPurifier_Context $context
  19108. * @return bool
  19109. */
  19110. public function filter(&$uri, $config, $context)
  19111. {
  19112. // check if filter not applicable
  19113. if (!$config->get('HTML.SafeIframe')) {
  19114. return true;
  19115. }
  19116. // check if the filter should actually trigger
  19117. if (!$context->get('EmbeddedURI', true)) {
  19118. return true;
  19119. }
  19120. $token = $context->get('CurrentToken', true);
  19121. if (!($token && $token->name == 'iframe')) {
  19122. return true;
  19123. }
  19124. // check if we actually have some whitelists enabled
  19125. if ($this->regexp === null) {
  19126. return false;
  19127. }
  19128. // actually check the whitelists
  19129. return preg_match($this->regexp, $uri->toString());
  19130. }
  19131. }
  19132. /**
  19133. * Implements data: URI for base64 encoded images supported by GD.
  19134. */
  19135. class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
  19136. {
  19137. /**
  19138. * @type bool
  19139. */
  19140. public $browsable = true;
  19141. /**
  19142. * @type array
  19143. */
  19144. public $allowed_types = array(
  19145. // you better write validation code for other types if you
  19146. // decide to allow them
  19147. 'image/jpeg' => true,
  19148. 'image/gif' => true,
  19149. 'image/png' => true,
  19150. );
  19151. // this is actually irrelevant since we only write out the path
  19152. // component
  19153. /**
  19154. * @type bool
  19155. */
  19156. public $may_omit_host = true;
  19157. /**
  19158. * @param HTMLPurifier_URI $uri
  19159. * @param HTMLPurifier_Config $config
  19160. * @param HTMLPurifier_Context $context
  19161. * @return bool
  19162. */
  19163. public function doValidate(&$uri, $config, $context)
  19164. {
  19165. $result = explode(',', $uri->path, 2);
  19166. $is_base64 = false;
  19167. $charset = null;
  19168. $content_type = null;
  19169. if (count($result) == 2) {
  19170. list($metadata, $data) = $result;
  19171. // do some legwork on the metadata
  19172. $metas = explode(';', $metadata);
  19173. while (!empty($metas)) {
  19174. $cur = array_shift($metas);
  19175. if ($cur == 'base64') {
  19176. $is_base64 = true;
  19177. break;
  19178. }
  19179. if (substr($cur, 0, 8) == 'charset=') {
  19180. // doesn't match if there are arbitrary spaces, but
  19181. // whatever dude
  19182. if ($charset !== null) {
  19183. continue;
  19184. } // garbage
  19185. $charset = substr($cur, 8); // not used
  19186. } else {
  19187. if ($content_type !== null) {
  19188. continue;
  19189. } // garbage
  19190. $content_type = $cur;
  19191. }
  19192. }
  19193. } else {
  19194. $data = $result[0];
  19195. }
  19196. if ($content_type !== null && empty($this->allowed_types[$content_type])) {
  19197. return false;
  19198. }
  19199. if ($charset !== null) {
  19200. // error; we don't allow plaintext stuff
  19201. $charset = null;
  19202. }
  19203. $data = rawurldecode($data);
  19204. if ($is_base64) {
  19205. $raw_data = base64_decode($data);
  19206. } else {
  19207. $raw_data = $data;
  19208. }
  19209. if ( strlen($raw_data) < 12 ) {
  19210. // error; exif_imagetype throws exception with small files,
  19211. // and this likely indicates a corrupt URI/failed parse anyway
  19212. return false;
  19213. }
  19214. // XXX probably want to refactor this into a general mechanism
  19215. // for filtering arbitrary content types
  19216. if (function_exists('sys_get_temp_dir')) {
  19217. $file = tempnam(sys_get_temp_dir(), "");
  19218. } else {
  19219. $file = tempnam("/tmp", "");
  19220. }
  19221. file_put_contents($file, $raw_data);
  19222. if (function_exists('exif_imagetype')) {
  19223. $image_code = exif_imagetype($file);
  19224. unlink($file);
  19225. } elseif (function_exists('getimagesize')) {
  19226. set_error_handler(array($this, 'muteErrorHandler'));
  19227. $info = getimagesize($file);
  19228. restore_error_handler();
  19229. unlink($file);
  19230. if ($info == false) {
  19231. return false;
  19232. }
  19233. $image_code = $info[2];
  19234. } else {
  19235. trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
  19236. }
  19237. $real_content_type = image_type_to_mime_type($image_code);
  19238. if ($real_content_type != $content_type) {
  19239. // we're nice guys; if the content type is something else we
  19240. // support, change it over
  19241. if (empty($this->allowed_types[$real_content_type])) {
  19242. return false;
  19243. }
  19244. $content_type = $real_content_type;
  19245. }
  19246. // ok, it's kosher, rewrite what we need
  19247. $uri->userinfo = null;
  19248. $uri->host = null;
  19249. $uri->port = null;
  19250. $uri->fragment = null;
  19251. $uri->query = null;
  19252. $uri->path = "$content_type;base64," . base64_encode($raw_data);
  19253. return true;
  19254. }
  19255. /**
  19256. * @param int $errno
  19257. * @param string $errstr
  19258. */
  19259. public function muteErrorHandler($errno, $errstr)
  19260. {
  19261. }
  19262. }
  19263. /**
  19264. * Validates file as defined by RFC 1630 and RFC 1738.
  19265. */
  19266. class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
  19267. {
  19268. /**
  19269. * Generally file:// URLs are not accessible from most
  19270. * machines, so placing them as an img src is incorrect.
  19271. * @type bool
  19272. */
  19273. public $browsable = false;
  19274. /**
  19275. * Basically the *only* URI scheme for which this is true, since
  19276. * accessing files on the local machine is very common. In fact,
  19277. * browsers on some operating systems don't understand the
  19278. * authority, though I hear it is used on Windows to refer to
  19279. * network shares.
  19280. * @type bool
  19281. */
  19282. public $may_omit_host = true;
  19283. /**
  19284. * @param HTMLPurifier_URI $uri
  19285. * @param HTMLPurifier_Config $config
  19286. * @param HTMLPurifier_Context $context
  19287. * @return bool
  19288. */
  19289. public function doValidate(&$uri, $config, $context)
  19290. {
  19291. // Authentication method is not supported
  19292. $uri->userinfo = null;
  19293. // file:// makes no provisions for accessing the resource
  19294. $uri->port = null;
  19295. // While it seems to work on Firefox, the querystring has
  19296. // no possible effect and is thus stripped.
  19297. $uri->query = null;
  19298. return true;
  19299. }
  19300. }
  19301. /**
  19302. * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
  19303. */
  19304. class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
  19305. {
  19306. /**
  19307. * @type int
  19308. */
  19309. public $default_port = 21;
  19310. /**
  19311. * @type bool
  19312. */
  19313. public $browsable = true; // usually
  19314. /**
  19315. * @type bool
  19316. */
  19317. public $hierarchical = true;
  19318. /**
  19319. * @param HTMLPurifier_URI $uri
  19320. * @param HTMLPurifier_Config $config
  19321. * @param HTMLPurifier_Context $context
  19322. * @return bool
  19323. */
  19324. public function doValidate(&$uri, $config, $context)
  19325. {
  19326. $uri->query = null;
  19327. // typecode check
  19328. $semicolon_pos = strrpos($uri->path, ';'); // reverse
  19329. if ($semicolon_pos !== false) {
  19330. $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
  19331. $uri->path = substr($uri->path, 0, $semicolon_pos);
  19332. $type_ret = '';
  19333. if (strpos($type, '=') !== false) {
  19334. // figure out whether or not the declaration is correct
  19335. list($key, $typecode) = explode('=', $type, 2);
  19336. if ($key !== 'type') {
  19337. // invalid key, tack it back on encoded
  19338. $uri->path .= '%3B' . $type;
  19339. } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
  19340. $type_ret = ";type=$typecode";
  19341. }
  19342. } else {
  19343. $uri->path .= '%3B' . $type;
  19344. }
  19345. $uri->path = str_replace(';', '%3B', $uri->path);
  19346. $uri->path .= $type_ret;
  19347. }
  19348. return true;
  19349. }
  19350. }
  19351. /**
  19352. * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
  19353. */
  19354. class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
  19355. {
  19356. /**
  19357. * @type int
  19358. */
  19359. public $default_port = 80;
  19360. /**
  19361. * @type bool
  19362. */
  19363. public $browsable = true;
  19364. /**
  19365. * @type bool
  19366. */
  19367. public $hierarchical = true;
  19368. /**
  19369. * @param HTMLPurifier_URI $uri
  19370. * @param HTMLPurifier_Config $config
  19371. * @param HTMLPurifier_Context $context
  19372. * @return bool
  19373. */
  19374. public function doValidate(&$uri, $config, $context)
  19375. {
  19376. $uri->userinfo = null;
  19377. return true;
  19378. }
  19379. }
  19380. /**
  19381. * Validates https (Secure HTTP) according to http scheme.
  19382. */
  19383. class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
  19384. {
  19385. /**
  19386. * @type int
  19387. */
  19388. public $default_port = 443;
  19389. /**
  19390. * @type bool
  19391. */
  19392. public $secure = true;
  19393. }
  19394. // VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
  19395. // email is valid, but be careful!
  19396. /**
  19397. * Validates mailto (for E-mail) according to RFC 2368
  19398. * @todo Validate the email address
  19399. * @todo Filter allowed query parameters
  19400. */
  19401. class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
  19402. {
  19403. /**
  19404. * @type bool
  19405. */
  19406. public $browsable = false;
  19407. /**
  19408. * @type bool
  19409. */
  19410. public $may_omit_host = true;
  19411. /**
  19412. * @param HTMLPurifier_URI $uri
  19413. * @param HTMLPurifier_Config $config
  19414. * @param HTMLPurifier_Context $context
  19415. * @return bool
  19416. */
  19417. public function doValidate(&$uri, $config, $context)
  19418. {
  19419. $uri->userinfo = null;
  19420. $uri->host = null;
  19421. $uri->port = null;
  19422. // we need to validate path against RFC 2368's addr-spec
  19423. return true;
  19424. }
  19425. }
  19426. /**
  19427. * Validates news (Usenet) as defined by generic RFC 1738
  19428. */
  19429. class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
  19430. {
  19431. /**
  19432. * @type bool
  19433. */
  19434. public $browsable = false;
  19435. /**
  19436. * @type bool
  19437. */
  19438. public $may_omit_host = true;
  19439. /**
  19440. * @param HTMLPurifier_URI $uri
  19441. * @param HTMLPurifier_Config $config
  19442. * @param HTMLPurifier_Context $context
  19443. * @return bool
  19444. */
  19445. public function doValidate(&$uri, $config, $context)
  19446. {
  19447. $uri->userinfo = null;
  19448. $uri->host = null;
  19449. $uri->port = null;
  19450. $uri->query = null;
  19451. // typecode check needed on path
  19452. return true;
  19453. }
  19454. }
  19455. /**
  19456. * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
  19457. */
  19458. class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
  19459. {
  19460. /**
  19461. * @type int
  19462. */
  19463. public $default_port = 119;
  19464. /**
  19465. * @type bool
  19466. */
  19467. public $browsable = false;
  19468. /**
  19469. * @param HTMLPurifier_URI $uri
  19470. * @param HTMLPurifier_Config $config
  19471. * @param HTMLPurifier_Context $context
  19472. * @return bool
  19473. */
  19474. public function doValidate(&$uri, $config, $context)
  19475. {
  19476. $uri->userinfo = null;
  19477. $uri->query = null;
  19478. return true;
  19479. }
  19480. }
  19481. /**
  19482. * Validates tel (for phone numbers).
  19483. *
  19484. * The relevant specifications for this protocol are RFC 3966 and RFC 5341,
  19485. * but this class takes a much simpler approach: we normalize phone
  19486. * numbers so that they only include (possibly) a leading plus,
  19487. * and then any number of digits and x'es.
  19488. */
  19489. class HTMLPurifier_URIScheme_tel extends HTMLPurifier_URIScheme
  19490. {
  19491. /**
  19492. * @type bool
  19493. */
  19494. public $browsable = false;
  19495. /**
  19496. * @type bool
  19497. */
  19498. public $may_omit_host = true;
  19499. /**
  19500. * @param HTMLPurifier_URI $uri
  19501. * @param HTMLPurifier_Config $config
  19502. * @param HTMLPurifier_Context $context
  19503. * @return bool
  19504. */
  19505. public function doValidate(&$uri, $config, $context)
  19506. {
  19507. $uri->userinfo = null;
  19508. $uri->host = null;
  19509. $uri->port = null;
  19510. // Delete all non-numeric characters, non-x characters
  19511. // from phone number, EXCEPT for a leading plus sign.
  19512. $uri->path = preg_replace('/(?!^\+)[^\dx]/', '',
  19513. // Normalize e(x)tension to lower-case
  19514. str_replace('X', 'x', $uri->path));
  19515. return true;
  19516. }
  19517. }
  19518. /**
  19519. * Performs safe variable parsing based on types which can be used by
  19520. * users. This may not be able to represent all possible data inputs,
  19521. * however.
  19522. */
  19523. class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
  19524. {
  19525. /**
  19526. * @param mixed $var
  19527. * @param int $type
  19528. * @param bool $allow_null
  19529. * @return array|bool|float|int|mixed|null|string
  19530. * @throws HTMLPurifier_VarParserException
  19531. */
  19532. protected function parseImplementation($var, $type, $allow_null)
  19533. {
  19534. if ($allow_null && $var === null) {
  19535. return null;
  19536. }
  19537. switch ($type) {
  19538. // Note: if code "breaks" from the switch, it triggers a generic
  19539. // exception to be thrown. Specific errors can be specifically
  19540. // done here.
  19541. case self::MIXED:
  19542. case self::ISTRING:
  19543. case self::STRING:
  19544. case self::TEXT:
  19545. case self::ITEXT:
  19546. return $var;
  19547. case self::INT:
  19548. if (is_string($var) && ctype_digit($var)) {
  19549. $var = (int)$var;
  19550. }
  19551. return $var;
  19552. case self::FLOAT:
  19553. if ((is_string($var) && is_numeric($var)) || is_int($var)) {
  19554. $var = (float)$var;
  19555. }
  19556. return $var;
  19557. case self::BOOL:
  19558. if (is_int($var) && ($var === 0 || $var === 1)) {
  19559. $var = (bool)$var;
  19560. } elseif (is_string($var)) {
  19561. if ($var == 'on' || $var == 'true' || $var == '1') {
  19562. $var = true;
  19563. } elseif ($var == 'off' || $var == 'false' || $var == '0') {
  19564. $var = false;
  19565. } else {
  19566. throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
  19567. }
  19568. }
  19569. return $var;
  19570. case self::ALIST:
  19571. case self::HASH:
  19572. case self::LOOKUP:
  19573. if (is_string($var)) {
  19574. // special case: technically, this is an array with
  19575. // a single empty string item, but having an empty
  19576. // array is more intuitive
  19577. if ($var == '') {
  19578. return array();
  19579. }
  19580. if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
  19581. // simplistic string to array method that only works
  19582. // for simple lists of tag names or alphanumeric characters
  19583. $var = explode(',', $var);
  19584. } else {
  19585. $var = preg_split('/(,|[\n\r]+)/', $var);
  19586. }
  19587. // remove spaces
  19588. foreach ($var as $i => $j) {
  19589. $var[$i] = trim($j);
  19590. }
  19591. if ($type === self::HASH) {
  19592. // key:value,key2:value2
  19593. $nvar = array();
  19594. foreach ($var as $keypair) {
  19595. $c = explode(':', $keypair, 2);
  19596. if (!isset($c[1])) {
  19597. continue;
  19598. }
  19599. $nvar[trim($c[0])] = trim($c[1]);
  19600. }
  19601. $var = $nvar;
  19602. }
  19603. }
  19604. if (!is_array($var)) {
  19605. break;
  19606. }
  19607. $keys = array_keys($var);
  19608. if ($keys === array_keys($keys)) {
  19609. if ($type == self::ALIST) {
  19610. return $var;
  19611. } elseif ($type == self::LOOKUP) {
  19612. $new = array();
  19613. foreach ($var as $key) {
  19614. $new[$key] = true;
  19615. }
  19616. return $new;
  19617. } else {
  19618. break;
  19619. }
  19620. }
  19621. if ($type === self::ALIST) {
  19622. trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
  19623. return array_values($var);
  19624. }
  19625. if ($type === self::LOOKUP) {
  19626. foreach ($var as $key => $value) {
  19627. if ($value !== true) {
  19628. trigger_error(
  19629. "Lookup array has non-true value at key '$key'; " .
  19630. "maybe your input array was not indexed numerically",
  19631. E_USER_WARNING
  19632. );
  19633. }
  19634. $var[$key] = true;
  19635. }
  19636. }
  19637. return $var;
  19638. default:
  19639. $this->errorInconsistent(__CLASS__, $type);
  19640. }
  19641. $this->errorGeneric($var, $type);
  19642. }
  19643. }
  19644. /**
  19645. * This variable parser uses PHP's internal code engine. Because it does
  19646. * this, it can represent all inputs; however, it is dangerous and cannot
  19647. * be used by users.
  19648. */
  19649. class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
  19650. {
  19651. /**
  19652. * @param mixed $var
  19653. * @param int $type
  19654. * @param bool $allow_null
  19655. * @return null|string
  19656. */
  19657. protected function parseImplementation($var, $type, $allow_null)
  19658. {
  19659. return $this->evalExpression($var);
  19660. }
  19661. /**
  19662. * @param string $expr
  19663. * @return mixed
  19664. * @throws HTMLPurifier_VarParserException
  19665. */
  19666. protected function evalExpression($expr)
  19667. {
  19668. $var = null;
  19669. $result = eval("\$var = $expr;");
  19670. if ($result === false) {
  19671. throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
  19672. }
  19673. return $var;
  19674. }
  19675. }