HTMLPurifier.standalone.php 662 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890
  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.7.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.7.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.7.0';
  72. /**
  73. * Constant with version of HTML Purifier.
  74. */
  75. const VERSION = '4.7.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 $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. // load extensions from the modules
  364. foreach ($modules as $module) {
  365. foreach ($module->attr_collections as $coll_i => $coll) {
  366. if (!isset($this->info[$coll_i])) {
  367. $this->info[$coll_i] = array();
  368. }
  369. foreach ($coll as $attr_i => $attr) {
  370. if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
  371. // merge in includes
  372. $this->info[$coll_i][$attr_i] = array_merge(
  373. $this->info[$coll_i][$attr_i],
  374. $attr
  375. );
  376. continue;
  377. }
  378. $this->info[$coll_i][$attr_i] = $attr;
  379. }
  380. }
  381. }
  382. // perform internal expansions and inclusions
  383. foreach ($this->info as $name => $attr) {
  384. // merge attribute collections that include others
  385. $this->performInclusions($this->info[$name]);
  386. // replace string identifiers with actual attribute objects
  387. $this->expandIdentifiers($this->info[$name], $attr_types);
  388. }
  389. }
  390. /**
  391. * Takes a reference to an attribute associative array and performs
  392. * all inclusions specified by the zero index.
  393. * @param array &$attr Reference to attribute array
  394. */
  395. public function performInclusions(&$attr)
  396. {
  397. if (!isset($attr[0])) {
  398. return;
  399. }
  400. $merge = $attr[0];
  401. $seen = array(); // recursion guard
  402. // loop through all the inclusions
  403. for ($i = 0; isset($merge[$i]); $i++) {
  404. if (isset($seen[$merge[$i]])) {
  405. continue;
  406. }
  407. $seen[$merge[$i]] = true;
  408. // foreach attribute of the inclusion, copy it over
  409. if (!isset($this->info[$merge[$i]])) {
  410. continue;
  411. }
  412. foreach ($this->info[$merge[$i]] as $key => $value) {
  413. if (isset($attr[$key])) {
  414. continue;
  415. } // also catches more inclusions
  416. $attr[$key] = $value;
  417. }
  418. if (isset($this->info[$merge[$i]][0])) {
  419. // recursion
  420. $merge = array_merge($merge, $this->info[$merge[$i]][0]);
  421. }
  422. }
  423. unset($attr[0]);
  424. }
  425. /**
  426. * Expands all string identifiers in an attribute array by replacing
  427. * them with the appropriate values inside HTMLPurifier_AttrTypes
  428. * @param array &$attr Reference to attribute array
  429. * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
  430. */
  431. public function expandIdentifiers(&$attr, $attr_types)
  432. {
  433. // because foreach will process new elements we add, make sure we
  434. // skip duplicates
  435. $processed = array();
  436. foreach ($attr as $def_i => $def) {
  437. // skip inclusions
  438. if ($def_i === 0) {
  439. continue;
  440. }
  441. if (isset($processed[$def_i])) {
  442. continue;
  443. }
  444. // determine whether or not attribute is required
  445. if ($required = (strpos($def_i, '*') !== false)) {
  446. // rename the definition
  447. unset($attr[$def_i]);
  448. $def_i = trim($def_i, '*');
  449. $attr[$def_i] = $def;
  450. }
  451. $processed[$def_i] = true;
  452. // if we've already got a literal object, move on
  453. if (is_object($def)) {
  454. // preserve previous required
  455. $attr[$def_i]->required = ($required || $attr[$def_i]->required);
  456. continue;
  457. }
  458. if ($def === false) {
  459. unset($attr[$def_i]);
  460. continue;
  461. }
  462. if ($t = $attr_types->get($def)) {
  463. $attr[$def_i] = $t;
  464. $attr[$def_i]->required = $required;
  465. } else {
  466. unset($attr[$def_i]);
  467. }
  468. }
  469. }
  470. }
  471. /**
  472. * Base class for all validating attribute definitions.
  473. *
  474. * This family of classes forms the core for not only HTML attribute validation,
  475. * but also any sort of string that needs to be validated or cleaned (which
  476. * means CSS properties and composite definitions are defined here too).
  477. * Besides defining (through code) what precisely makes the string valid,
  478. * subclasses are also responsible for cleaning the code if possible.
  479. */
  480. abstract class HTMLPurifier_AttrDef
  481. {
  482. /**
  483. * Tells us whether or not an HTML attribute is minimized.
  484. * Has no meaning in other contexts.
  485. * @type bool
  486. */
  487. public $minimized = false;
  488. /**
  489. * Tells us whether or not an HTML attribute is required.
  490. * Has no meaning in other contexts
  491. * @type bool
  492. */
  493. public $required = false;
  494. /**
  495. * Validates and cleans passed string according to a definition.
  496. *
  497. * @param string $string String to be validated and cleaned.
  498. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  499. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
  500. */
  501. abstract public function validate($string, $config, $context);
  502. /**
  503. * Convenience method that parses a string as if it were CDATA.
  504. *
  505. * This method process a string in the manner specified at
  506. * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
  507. * leading and trailing whitespace, ignoring line feeds, and replacing
  508. * carriage returns and tabs with spaces. While most useful for HTML
  509. * attributes specified as CDATA, it can also be applied to most CSS
  510. * values.
  511. *
  512. * @note This method is not entirely standards compliant, as trim() removes
  513. * more types of whitespace than specified in the spec. In practice,
  514. * this is rarely a problem, as those extra characters usually have
  515. * already been removed by HTMLPurifier_Encoder.
  516. *
  517. * @warning This processing is inconsistent with XML's whitespace handling
  518. * as specified by section 3.3.3 and referenced XHTML 1.0 section
  519. * 4.7. However, note that we are NOT necessarily
  520. * parsing XML, thus, this behavior may still be correct. We
  521. * assume that newlines have been normalized.
  522. */
  523. public function parseCDATA($string)
  524. {
  525. $string = trim($string);
  526. $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
  527. return $string;
  528. }
  529. /**
  530. * Factory method for creating this class from a string.
  531. * @param string $string String construction info
  532. * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
  533. */
  534. public function make($string)
  535. {
  536. // default implementation, return a flyweight of this object.
  537. // If $string has an effect on the returned object (i.e. you
  538. // need to overload this method), it is best
  539. // to clone or instantiate new copies. (Instantiation is safer.)
  540. return $this;
  541. }
  542. /**
  543. * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
  544. * properly. THIS IS A HACK!
  545. * @param string $string a CSS colour definition
  546. * @return string
  547. */
  548. protected function mungeRgb($string)
  549. {
  550. return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
  551. }
  552. /**
  553. * Parses a possibly escaped CSS string and returns the "pure"
  554. * version of it.
  555. */
  556. protected function expandCSSEscape($string)
  557. {
  558. // flexibly parse it
  559. $ret = '';
  560. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  561. if ($string[$i] === '\\') {
  562. $i++;
  563. if ($i >= $c) {
  564. $ret .= '\\';
  565. break;
  566. }
  567. if (ctype_xdigit($string[$i])) {
  568. $code = $string[$i];
  569. for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
  570. if (!ctype_xdigit($string[$i])) {
  571. break;
  572. }
  573. $code .= $string[$i];
  574. }
  575. // We have to be extremely careful when adding
  576. // new characters, to make sure we're not breaking
  577. // the encoding.
  578. $char = HTMLPurifier_Encoder::unichr(hexdec($code));
  579. if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
  580. continue;
  581. }
  582. $ret .= $char;
  583. if ($i < $c && trim($string[$i]) !== '') {
  584. $i--;
  585. }
  586. continue;
  587. }
  588. if ($string[$i] === "\n") {
  589. continue;
  590. }
  591. }
  592. $ret .= $string[$i];
  593. }
  594. return $ret;
  595. }
  596. }
  597. /**
  598. * Processes an entire attribute array for corrections needing multiple values.
  599. *
  600. * Occasionally, a certain attribute will need to be removed and popped onto
  601. * another value. Instead of creating a complex return syntax for
  602. * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
  603. * specialized object and have that do the special work. That is the
  604. * family of HTMLPurifier_AttrTransform.
  605. *
  606. * An attribute transformation can be assigned to run before or after
  607. * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
  608. * more details.
  609. */
  610. abstract class HTMLPurifier_AttrTransform
  611. {
  612. /**
  613. * Abstract: makes changes to the attributes dependent on multiple values.
  614. *
  615. * @param array $attr Assoc array of attributes, usually from
  616. * HTMLPurifier_Token_Tag::$attr
  617. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
  618. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  619. * @return array Processed attribute array.
  620. */
  621. abstract public function transform($attr, $config, $context);
  622. /**
  623. * Prepends CSS properties to the style attribute, creating the
  624. * attribute if it doesn't exist.
  625. * @param array &$attr Attribute array to process (passed by reference)
  626. * @param string $css CSS to prepend
  627. */
  628. public function prependCSS(&$attr, $css)
  629. {
  630. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  631. $attr['style'] = $css . $attr['style'];
  632. }
  633. /**
  634. * Retrieves and removes an attribute
  635. * @param array &$attr Attribute array to process (passed by reference)
  636. * @param mixed $key Key of attribute to confiscate
  637. * @return mixed
  638. */
  639. public function confiscateAttr(&$attr, $key)
  640. {
  641. if (!isset($attr[$key])) {
  642. return null;
  643. }
  644. $value = $attr[$key];
  645. unset($attr[$key]);
  646. return $value;
  647. }
  648. }
  649. /**
  650. * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
  651. */
  652. class HTMLPurifier_AttrTypes
  653. {
  654. /**
  655. * Lookup array of attribute string identifiers to concrete implementations.
  656. * @type HTMLPurifier_AttrDef[]
  657. */
  658. protected $info = array();
  659. /**
  660. * Constructs the info array, supplying default implementations for attribute
  661. * types.
  662. */
  663. public function __construct()
  664. {
  665. // XXX This is kind of poor, since we don't actually /clone/
  666. // instances; instead, we use the supplied make() attribute. So,
  667. // the underlying class must know how to deal with arguments.
  668. // With the old implementation of Enum, that ignored its
  669. // arguments when handling a make dispatch, the IAlign
  670. // definition wouldn't work.
  671. // pseudo-types, must be instantiated via shorthand
  672. $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
  673. $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
  674. $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
  675. $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
  676. $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
  677. $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
  678. $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
  679. $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
  680. $this->info['Text'] = new HTMLPurifier_AttrDef_Text();
  681. $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
  682. $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
  683. $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
  684. $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
  685. $this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
  686. $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
  687. // unimplemented aliases
  688. $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
  689. $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
  690. $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
  691. $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
  692. // "proprietary" types
  693. $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
  694. // number is really a positive integer (one or more digits)
  695. // FIXME: ^^ not always, see start and value of list items
  696. $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
  697. }
  698. private static function makeEnum($in)
  699. {
  700. return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
  701. }
  702. /**
  703. * Retrieves a type
  704. * @param string $type String type name
  705. * @return HTMLPurifier_AttrDef Object AttrDef for type
  706. */
  707. public function get($type)
  708. {
  709. // determine if there is any extra info tacked on
  710. if (strpos($type, '#') !== false) {
  711. list($type, $string) = explode('#', $type, 2);
  712. } else {
  713. $string = '';
  714. }
  715. if (!isset($this->info[$type])) {
  716. trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
  717. return;
  718. }
  719. return $this->info[$type]->make($string);
  720. }
  721. /**
  722. * Sets a new implementation for a type
  723. * @param string $type String type name
  724. * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
  725. */
  726. public function set($type, $impl)
  727. {
  728. $this->info[$type] = $impl;
  729. }
  730. }
  731. /**
  732. * Validates the attributes of a token. Doesn't manage required attributes
  733. * very well. The only reason we factored this out was because RemoveForeignElements
  734. * also needed it besides ValidateAttributes.
  735. */
  736. class HTMLPurifier_AttrValidator
  737. {
  738. /**
  739. * Validates the attributes of a token, mutating it as necessary.
  740. * that has valid tokens
  741. * @param HTMLPurifier_Token $token Token to validate.
  742. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  743. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  744. */
  745. public function validateToken($token, $config, $context)
  746. {
  747. $definition = $config->getHTMLDefinition();
  748. $e =& $context->get('ErrorCollector', true);
  749. // initialize IDAccumulator if necessary
  750. $ok =& $context->get('IDAccumulator', true);
  751. if (!$ok) {
  752. $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
  753. $context->register('IDAccumulator', $id_accumulator);
  754. }
  755. // initialize CurrentToken if necessary
  756. $current_token =& $context->get('CurrentToken', true);
  757. if (!$current_token) {
  758. $context->register('CurrentToken', $token);
  759. }
  760. if (!$token instanceof HTMLPurifier_Token_Start &&
  761. !$token instanceof HTMLPurifier_Token_Empty
  762. ) {
  763. return;
  764. }
  765. // create alias to global definition array, see also $defs
  766. // DEFINITION CALL
  767. $d_defs = $definition->info_global_attr;
  768. // don't update token until the very end, to ensure an atomic update
  769. $attr = $token->attr;
  770. // do global transformations (pre)
  771. // nothing currently utilizes this
  772. foreach ($definition->info_attr_transform_pre as $transform) {
  773. $attr = $transform->transform($o = $attr, $config, $context);
  774. if ($e) {
  775. if ($attr != $o) {
  776. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  777. }
  778. }
  779. }
  780. // do local transformations only applicable to this element (pre)
  781. // ex. <p align="right"> to <p style="text-align:right;">
  782. foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
  783. $attr = $transform->transform($o = $attr, $config, $context);
  784. if ($e) {
  785. if ($attr != $o) {
  786. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  787. }
  788. }
  789. }
  790. // create alias to this element's attribute definition array, see
  791. // also $d_defs (global attribute definition array)
  792. // DEFINITION CALL
  793. $defs = $definition->info[$token->name]->attr;
  794. $attr_key = false;
  795. $context->register('CurrentAttr', $attr_key);
  796. // iterate through all the attribute keypairs
  797. // Watch out for name collisions: $key has previously been used
  798. foreach ($attr as $attr_key => $value) {
  799. // call the definition
  800. if (isset($defs[$attr_key])) {
  801. // there is a local definition defined
  802. if ($defs[$attr_key] === false) {
  803. // We've explicitly been told not to allow this element.
  804. // This is usually when there's a global definition
  805. // that must be overridden.
  806. // Theoretically speaking, we could have a
  807. // AttrDef_DenyAll, but this is faster!
  808. $result = false;
  809. } else {
  810. // validate according to the element's definition
  811. $result = $defs[$attr_key]->validate(
  812. $value,
  813. $config,
  814. $context
  815. );
  816. }
  817. } elseif (isset($d_defs[$attr_key])) {
  818. // there is a global definition defined, validate according
  819. // to the global definition
  820. $result = $d_defs[$attr_key]->validate(
  821. $value,
  822. $config,
  823. $context
  824. );
  825. } else {
  826. // system never heard of the attribute? DELETE!
  827. $result = false;
  828. }
  829. // put the results into effect
  830. if ($result === false || $result === null) {
  831. // this is a generic error message that should replaced
  832. // with more specific ones when possible
  833. if ($e) {
  834. $e->send(E_ERROR, 'AttrValidator: Attribute removed');
  835. }
  836. // remove the attribute
  837. unset($attr[$attr_key]);
  838. } elseif (is_string($result)) {
  839. // generally, if a substitution is happening, there
  840. // was some sort of implicit correction going on. We'll
  841. // delegate it to the attribute classes to say exactly what.
  842. // simple substitution
  843. $attr[$attr_key] = $result;
  844. } else {
  845. // nothing happens
  846. }
  847. // we'd also want slightly more complicated substitution
  848. // involving an array as the return value,
  849. // although we're not sure how colliding attributes would
  850. // resolve (certain ones would be completely overriden,
  851. // others would prepend themselves).
  852. }
  853. $context->destroy('CurrentAttr');
  854. // post transforms
  855. // global (error reporting untested)
  856. foreach ($definition->info_attr_transform_post as $transform) {
  857. $attr = $transform->transform($o = $attr, $config, $context);
  858. if ($e) {
  859. if ($attr != $o) {
  860. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  861. }
  862. }
  863. }
  864. // local (error reporting untested)
  865. foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
  866. $attr = $transform->transform($o = $attr, $config, $context);
  867. if ($e) {
  868. if ($attr != $o) {
  869. $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
  870. }
  871. }
  872. }
  873. $token->attr = $attr;
  874. // destroy CurrentToken if we made it ourselves
  875. if (!$current_token) {
  876. $context->destroy('CurrentToken');
  877. }
  878. }
  879. }
  880. // constants are slow, so we use as few as possible
  881. if (!defined('HTMLPURIFIER_PREFIX')) {
  882. define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
  883. set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
  884. }
  885. // accomodations for versions earlier than 5.0.2
  886. // borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
  887. if (!defined('PHP_EOL')) {
  888. switch (strtoupper(substr(PHP_OS, 0, 3))) {
  889. case 'WIN':
  890. define('PHP_EOL', "\r\n");
  891. break;
  892. case 'DAR':
  893. define('PHP_EOL', "\r");
  894. break;
  895. default:
  896. define('PHP_EOL', "\n");
  897. }
  898. }
  899. /**
  900. * Bootstrap class that contains meta-functionality for HTML Purifier such as
  901. * the autoload function.
  902. *
  903. * @note
  904. * This class may be used without any other files from HTML Purifier.
  905. */
  906. class HTMLPurifier_Bootstrap
  907. {
  908. /**
  909. * Autoload function for HTML Purifier
  910. * @param string $class Class to load
  911. * @return bool
  912. */
  913. public static function autoload($class)
  914. {
  915. $file = HTMLPurifier_Bootstrap::getPath($class);
  916. if (!$file) {
  917. return false;
  918. }
  919. // Technically speaking, it should be ok and more efficient to
  920. // just do 'require', but Antonio Parraga reports that with
  921. // Zend extensions such as Zend debugger and APC, this invariant
  922. // may be broken. Since we have efficient alternatives, pay
  923. // the cost here and avoid the bug.
  924. require_once HTMLPURIFIER_PREFIX . '/' . $file;
  925. return true;
  926. }
  927. /**
  928. * Returns the path for a specific class.
  929. * @param string $class Class path to get
  930. * @return string
  931. */
  932. public static function getPath($class)
  933. {
  934. if (strncmp('HTMLPurifier', $class, 12) !== 0) {
  935. return false;
  936. }
  937. // Custom implementations
  938. if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
  939. $code = str_replace('_', '-', substr($class, 22));
  940. $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
  941. } else {
  942. $file = str_replace('_', '/', $class) . '.php';
  943. }
  944. if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
  945. return false;
  946. }
  947. return $file;
  948. }
  949. /**
  950. * "Pre-registers" our autoloader on the SPL stack.
  951. */
  952. public static function registerAutoload()
  953. {
  954. $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
  955. if (($funcs = spl_autoload_functions()) === false) {
  956. spl_autoload_register($autoload);
  957. } elseif (function_exists('spl_autoload_unregister')) {
  958. if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
  959. // prepend flag exists, no need for shenanigans
  960. spl_autoload_register($autoload, true, true);
  961. } else {
  962. $buggy = version_compare(PHP_VERSION, '5.2.11', '<');
  963. $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
  964. version_compare(PHP_VERSION, '5.1.0', '>=');
  965. foreach ($funcs as $func) {
  966. if ($buggy && is_array($func)) {
  967. // :TRICKY: There are some compatibility issues and some
  968. // places where we need to error out
  969. $reflector = new ReflectionMethod($func[0], $func[1]);
  970. if (!$reflector->isStatic()) {
  971. throw new Exception(
  972. 'HTML Purifier autoloader registrar is not compatible
  973. with non-static object methods due to PHP Bug #44144;
  974. Please do not use HTMLPurifier.autoload.php (or any
  975. file that includes this file); instead, place the code:
  976. spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
  977. after your own autoloaders.'
  978. );
  979. }
  980. // Suprisingly, spl_autoload_register supports the
  981. // Class::staticMethod callback format, although call_user_func doesn't
  982. if ($compat) {
  983. $func = implode('::', $func);
  984. }
  985. }
  986. spl_autoload_unregister($func);
  987. }
  988. spl_autoload_register($autoload);
  989. foreach ($funcs as $func) {
  990. spl_autoload_register($func);
  991. }
  992. }
  993. }
  994. }
  995. }
  996. /**
  997. * Super-class for definition datatype objects, implements serialization
  998. * functions for the class.
  999. */
  1000. abstract class HTMLPurifier_Definition
  1001. {
  1002. /**
  1003. * Has setup() been called yet?
  1004. * @type bool
  1005. */
  1006. public $setup = false;
  1007. /**
  1008. * If true, write out the final definition object to the cache after
  1009. * setup. This will be true only if all invocations to get a raw
  1010. * definition object are also optimized. This does not cause file
  1011. * system thrashing because on subsequent calls the cached object
  1012. * is used and any writes to the raw definition object are short
  1013. * circuited. See enduser-customize.html for the high-level
  1014. * picture.
  1015. * @type bool
  1016. */
  1017. public $optimized = null;
  1018. /**
  1019. * What type of definition is it?
  1020. * @type string
  1021. */
  1022. public $type;
  1023. /**
  1024. * Sets up the definition object into the final form, something
  1025. * not done by the constructor
  1026. * @param HTMLPurifier_Config $config
  1027. */
  1028. abstract protected function doSetup($config);
  1029. /**
  1030. * Setup function that aborts if already setup
  1031. * @param HTMLPurifier_Config $config
  1032. */
  1033. public function setup($config)
  1034. {
  1035. if ($this->setup) {
  1036. return;
  1037. }
  1038. $this->setup = true;
  1039. $this->doSetup($config);
  1040. }
  1041. }
  1042. /**
  1043. * Defines allowed CSS attributes and what their values are.
  1044. * @see HTMLPurifier_HTMLDefinition
  1045. */
  1046. class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
  1047. {
  1048. public $type = 'CSS';
  1049. /**
  1050. * Assoc array of attribute name to definition object.
  1051. * @type HTMLPurifier_AttrDef[]
  1052. */
  1053. public $info = array();
  1054. /**
  1055. * Constructs the info array. The meat of this class.
  1056. * @param HTMLPurifier_Config $config
  1057. */
  1058. protected function doSetup($config)
  1059. {
  1060. $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
  1061. array('left', 'right', 'center', 'justify'),
  1062. false
  1063. );
  1064. $border_style =
  1065. $this->info['border-bottom-style'] =
  1066. $this->info['border-right-style'] =
  1067. $this->info['border-left-style'] =
  1068. $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
  1069. array(
  1070. 'none',
  1071. 'hidden',
  1072. 'dotted',
  1073. 'dashed',
  1074. 'solid',
  1075. 'double',
  1076. 'groove',
  1077. 'ridge',
  1078. 'inset',
  1079. 'outset'
  1080. ),
  1081. false
  1082. );
  1083. $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
  1084. $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
  1085. array('none', 'left', 'right', 'both'),
  1086. false
  1087. );
  1088. $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
  1089. array('none', 'left', 'right'),
  1090. false
  1091. );
  1092. $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
  1093. array('normal', 'italic', 'oblique'),
  1094. false
  1095. );
  1096. $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
  1097. array('normal', 'small-caps'),
  1098. false
  1099. );
  1100. $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
  1101. array(
  1102. new HTMLPurifier_AttrDef_Enum(array('none')),
  1103. new HTMLPurifier_AttrDef_CSS_URI()
  1104. )
  1105. );
  1106. $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
  1107. array('inside', 'outside'),
  1108. false
  1109. );
  1110. $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
  1111. array(
  1112. 'disc',
  1113. 'circle',
  1114. 'square',
  1115. 'decimal',
  1116. 'lower-roman',
  1117. 'upper-roman',
  1118. 'lower-alpha',
  1119. 'upper-alpha',
  1120. 'none'
  1121. ),
  1122. false
  1123. );
  1124. $this->info['list-style-image'] = $uri_or_none;
  1125. $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
  1126. $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
  1127. array('capitalize', 'uppercase', 'lowercase', 'none'),
  1128. false
  1129. );
  1130. $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1131. $this->info['background-image'] = $uri_or_none;
  1132. $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
  1133. array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
  1134. );
  1135. $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
  1136. array('scroll', 'fixed')
  1137. );
  1138. $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
  1139. $border_color =
  1140. $this->info['border-top-color'] =
  1141. $this->info['border-bottom-color'] =
  1142. $this->info['border-left-color'] =
  1143. $this->info['border-right-color'] =
  1144. $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1145. array(
  1146. new HTMLPurifier_AttrDef_Enum(array('transparent')),
  1147. new HTMLPurifier_AttrDef_CSS_Color()
  1148. )
  1149. );
  1150. $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
  1151. $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
  1152. $border_width =
  1153. $this->info['border-top-width'] =
  1154. $this->info['border-bottom-width'] =
  1155. $this->info['border-left-width'] =
  1156. $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1157. array(
  1158. new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
  1159. new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
  1160. )
  1161. );
  1162. $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
  1163. $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1164. array(
  1165. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1166. new HTMLPurifier_AttrDef_CSS_Length()
  1167. )
  1168. );
  1169. $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1170. array(
  1171. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1172. new HTMLPurifier_AttrDef_CSS_Length()
  1173. )
  1174. );
  1175. $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1176. array(
  1177. new HTMLPurifier_AttrDef_Enum(
  1178. array(
  1179. 'xx-small',
  1180. 'x-small',
  1181. 'small',
  1182. 'medium',
  1183. 'large',
  1184. 'x-large',
  1185. 'xx-large',
  1186. 'larger',
  1187. 'smaller'
  1188. )
  1189. ),
  1190. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1191. new HTMLPurifier_AttrDef_CSS_Length()
  1192. )
  1193. );
  1194. $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1195. array(
  1196. new HTMLPurifier_AttrDef_Enum(array('normal')),
  1197. new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
  1198. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1199. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1200. )
  1201. );
  1202. $margin =
  1203. $this->info['margin-top'] =
  1204. $this->info['margin-bottom'] =
  1205. $this->info['margin-left'] =
  1206. $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1207. array(
  1208. new HTMLPurifier_AttrDef_CSS_Length(),
  1209. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1210. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1211. )
  1212. );
  1213. $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
  1214. // non-negative
  1215. $padding =
  1216. $this->info['padding-top'] =
  1217. $this->info['padding-bottom'] =
  1218. $this->info['padding-left'] =
  1219. $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1220. array(
  1221. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1222. new HTMLPurifier_AttrDef_CSS_Percentage(true)
  1223. )
  1224. );
  1225. $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
  1226. $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1227. array(
  1228. new HTMLPurifier_AttrDef_CSS_Length(),
  1229. new HTMLPurifier_AttrDef_CSS_Percentage()
  1230. )
  1231. );
  1232. $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
  1233. array(
  1234. new HTMLPurifier_AttrDef_CSS_Length('0'),
  1235. new HTMLPurifier_AttrDef_CSS_Percentage(true),
  1236. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1237. )
  1238. );
  1239. $max = $config->get('CSS.MaxImgLength');
  1240. $this->info['width'] =
  1241. $this->info['height'] =
  1242. $max === null ?
  1243. $trusted_wh :
  1244. new HTMLPurifier_AttrDef_Switch(
  1245. 'img',
  1246. // For img tags:
  1247. new HTMLPurifier_AttrDef_CSS_Composite(
  1248. array(
  1249. new HTMLPurifier_AttrDef_CSS_Length('0', $max),
  1250. new HTMLPurifier_AttrDef_Enum(array('auto'))
  1251. )
  1252. ),
  1253. // For everyone else:
  1254. $trusted_wh
  1255. );
  1256. $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
  1257. $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
  1258. // this could use specialized code
  1259. $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
  1260. array(
  1261. 'normal',
  1262. 'bold',
  1263. 'bolder',
  1264. 'lighter',
  1265. '100',
  1266. '200',
  1267. '300',
  1268. '400',
  1269. '500',
  1270. '600',
  1271. '700',
  1272. '800',
  1273. '900'
  1274. ),
  1275. false
  1276. );
  1277. // MUST be called after other font properties, as it references
  1278. // a CSSDefinition object
  1279. $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
  1280. // same here
  1281. $this->info['border'] =
  1282. $this->info['border-bottom'] =
  1283. $this->info['border-top'] =
  1284. $this->info['border-left'] =
  1285. $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
  1286. $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
  1287. array('collapse', 'separate')
  1288. );
  1289. $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
  1290. array('top', 'bottom')
  1291. );
  1292. $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
  1293. array('auto', 'fixed')
  1294. );
  1295. $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1296. array(
  1297. new HTMLPurifier_AttrDef_Enum(
  1298. array(
  1299. 'baseline',
  1300. 'sub',
  1301. 'super',
  1302. 'top',
  1303. 'text-top',
  1304. 'middle',
  1305. 'bottom',
  1306. 'text-bottom'
  1307. )
  1308. ),
  1309. new HTMLPurifier_AttrDef_CSS_Length(),
  1310. new HTMLPurifier_AttrDef_CSS_Percentage()
  1311. )
  1312. );
  1313. $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
  1314. // These CSS properties don't work on many browsers, but we live
  1315. // in THE FUTURE!
  1316. $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
  1317. array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
  1318. );
  1319. if ($config->get('CSS.Proprietary')) {
  1320. $this->doSetupProprietary($config);
  1321. }
  1322. if ($config->get('CSS.AllowTricky')) {
  1323. $this->doSetupTricky($config);
  1324. }
  1325. if ($config->get('CSS.Trusted')) {
  1326. $this->doSetupTrusted($config);
  1327. }
  1328. $allow_important = $config->get('CSS.AllowImportant');
  1329. // wrap all attr-defs with decorator that handles !important
  1330. foreach ($this->info as $k => $v) {
  1331. $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
  1332. }
  1333. $this->setupConfigStuff($config);
  1334. }
  1335. /**
  1336. * @param HTMLPurifier_Config $config
  1337. */
  1338. protected function doSetupProprietary($config)
  1339. {
  1340. // Internet Explorer only scrollbar colors
  1341. $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1342. $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1343. $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1344. $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1345. $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1346. $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
  1347. // vendor specific prefixes of opacity
  1348. $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1349. $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1350. // only opacity, for now
  1351. $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
  1352. // more CSS3
  1353. $this->info['page-break-after'] =
  1354. $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
  1355. array(
  1356. 'auto',
  1357. 'always',
  1358. 'avoid',
  1359. 'left',
  1360. 'right'
  1361. )
  1362. );
  1363. $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
  1364. }
  1365. /**
  1366. * @param HTMLPurifier_Config $config
  1367. */
  1368. protected function doSetupTricky($config)
  1369. {
  1370. $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
  1371. array(
  1372. 'inline',
  1373. 'block',
  1374. 'list-item',
  1375. 'run-in',
  1376. 'compact',
  1377. 'marker',
  1378. 'table',
  1379. 'inline-block',
  1380. 'inline-table',
  1381. 'table-row-group',
  1382. 'table-header-group',
  1383. 'table-footer-group',
  1384. 'table-row',
  1385. 'table-column-group',
  1386. 'table-column',
  1387. 'table-cell',
  1388. 'table-caption',
  1389. 'none'
  1390. )
  1391. );
  1392. $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
  1393. array('visible', 'hidden', 'collapse')
  1394. );
  1395. $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
  1396. $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
  1397. }
  1398. /**
  1399. * @param HTMLPurifier_Config $config
  1400. */
  1401. protected function doSetupTrusted($config)
  1402. {
  1403. $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
  1404. array('static', 'relative', 'absolute', 'fixed')
  1405. );
  1406. $this->info['top'] =
  1407. $this->info['left'] =
  1408. $this->info['right'] =
  1409. $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1410. array(
  1411. new HTMLPurifier_AttrDef_CSS_Length(),
  1412. new HTMLPurifier_AttrDef_CSS_Percentage(),
  1413. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1414. )
  1415. );
  1416. $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
  1417. array(
  1418. new HTMLPurifier_AttrDef_Integer(),
  1419. new HTMLPurifier_AttrDef_Enum(array('auto')),
  1420. )
  1421. );
  1422. }
  1423. /**
  1424. * Performs extra config-based processing. Based off of
  1425. * HTMLPurifier_HTMLDefinition.
  1426. * @param HTMLPurifier_Config $config
  1427. * @todo Refactor duplicate elements into common class (probably using
  1428. * composition, not inheritance).
  1429. */
  1430. protected function setupConfigStuff($config)
  1431. {
  1432. // setup allowed elements
  1433. $support = "(for information on implementing this, see the " .
  1434. "support forums) ";
  1435. $allowed_properties = $config->get('CSS.AllowedProperties');
  1436. if ($allowed_properties !== null) {
  1437. foreach ($this->info as $name => $d) {
  1438. if (!isset($allowed_properties[$name])) {
  1439. unset($this->info[$name]);
  1440. }
  1441. unset($allowed_properties[$name]);
  1442. }
  1443. // emit errors
  1444. foreach ($allowed_properties as $name => $d) {
  1445. // :TODO: Is this htmlspecialchars() call really necessary?
  1446. $name = htmlspecialchars($name);
  1447. trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
  1448. }
  1449. }
  1450. $forbidden_properties = $config->get('CSS.ForbiddenProperties');
  1451. if ($forbidden_properties !== null) {
  1452. foreach ($this->info as $name => $d) {
  1453. if (isset($forbidden_properties[$name])) {
  1454. unset($this->info[$name]);
  1455. }
  1456. }
  1457. }
  1458. }
  1459. }
  1460. /**
  1461. * Defines allowed child nodes and validates nodes against it.
  1462. */
  1463. abstract class HTMLPurifier_ChildDef
  1464. {
  1465. /**
  1466. * Type of child definition, usually right-most part of class name lowercase.
  1467. * Used occasionally in terms of context.
  1468. * @type string
  1469. */
  1470. public $type;
  1471. /**
  1472. * Indicates whether or not an empty array of children is okay.
  1473. *
  1474. * This is necessary for redundant checking when changes affecting
  1475. * a child node may cause a parent node to now be disallowed.
  1476. * @type bool
  1477. */
  1478. public $allow_empty;
  1479. /**
  1480. * Lookup array of all elements that this definition could possibly allow.
  1481. * @type array
  1482. */
  1483. public $elements = array();
  1484. /**
  1485. * Get lookup of tag names that should not close this element automatically.
  1486. * All other elements will do so.
  1487. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1488. * @return array
  1489. */
  1490. public function getAllowedElements($config)
  1491. {
  1492. return $this->elements;
  1493. }
  1494. /**
  1495. * Validates nodes according to definition and returns modification.
  1496. *
  1497. * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
  1498. * @param HTMLPurifier_Config $config HTMLPurifier_Config object
  1499. * @param HTMLPurifier_Context $context HTMLPurifier_Context object
  1500. * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
  1501. */
  1502. abstract public function validateChildren($children, $config, $context);
  1503. }
  1504. /**
  1505. * Configuration object that triggers customizable behavior.
  1506. *
  1507. * @warning This class is strongly defined: that means that the class
  1508. * will fail if an undefined directive is retrieved or set.
  1509. *
  1510. * @note Many classes that could (although many times don't) use the
  1511. * configuration object make it a mandatory parameter. This is
  1512. * because a configuration object should always be forwarded,
  1513. * otherwise, you run the risk of missing a parameter and then
  1514. * being stumped when a configuration directive doesn't work.
  1515. *
  1516. * @todo Reconsider some of the public member variables
  1517. */
  1518. class HTMLPurifier_Config
  1519. {
  1520. /**
  1521. * HTML Purifier's version
  1522. * @type string
  1523. */
  1524. public $version = '4.7.0';
  1525. /**
  1526. * Whether or not to automatically finalize
  1527. * the object if a read operation is done.
  1528. * @type bool
  1529. */
  1530. public $autoFinalize = true;
  1531. // protected member variables
  1532. /**
  1533. * Namespace indexed array of serials for specific namespaces.
  1534. * @see getSerial() for more info.
  1535. * @type string[]
  1536. */
  1537. protected $serials = array();
  1538. /**
  1539. * Serial for entire configuration object.
  1540. * @type string
  1541. */
  1542. protected $serial;
  1543. /**
  1544. * Parser for variables.
  1545. * @type HTMLPurifier_VarParser_Flexible
  1546. */
  1547. protected $parser = null;
  1548. /**
  1549. * Reference HTMLPurifier_ConfigSchema for value checking.
  1550. * @type HTMLPurifier_ConfigSchema
  1551. * @note This is public for introspective purposes. Please don't
  1552. * abuse!
  1553. */
  1554. public $def;
  1555. /**
  1556. * Indexed array of definitions.
  1557. * @type HTMLPurifier_Definition[]
  1558. */
  1559. protected $definitions;
  1560. /**
  1561. * Whether or not config is finalized.
  1562. * @type bool
  1563. */
  1564. protected $finalized = false;
  1565. /**
  1566. * Property list containing configuration directives.
  1567. * @type array
  1568. */
  1569. protected $plist;
  1570. /**
  1571. * Whether or not a set is taking place due to an alias lookup.
  1572. * @type bool
  1573. */
  1574. private $aliasMode;
  1575. /**
  1576. * Set to false if you do not want line and file numbers in errors.
  1577. * (useful when unit testing). This will also compress some errors
  1578. * and exceptions.
  1579. * @type bool
  1580. */
  1581. public $chatty = true;
  1582. /**
  1583. * Current lock; only gets to this namespace are allowed.
  1584. * @type string
  1585. */
  1586. private $lock;
  1587. /**
  1588. * Constructor
  1589. * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
  1590. * what directives are allowed.
  1591. * @param HTMLPurifier_PropertyList $parent
  1592. */
  1593. public function __construct($definition, $parent = null)
  1594. {
  1595. $parent = $parent ? $parent : $definition->defaultPlist;
  1596. $this->plist = new HTMLPurifier_PropertyList($parent);
  1597. $this->def = $definition; // keep a copy around for checking
  1598. $this->parser = new HTMLPurifier_VarParser_Flexible();
  1599. }
  1600. /**
  1601. * Convenience constructor that creates a config object based on a mixed var
  1602. * @param mixed $config Variable that defines the state of the config
  1603. * object. Can be: a HTMLPurifier_Config() object,
  1604. * an array of directives based on loadArray(),
  1605. * or a string filename of an ini file.
  1606. * @param HTMLPurifier_ConfigSchema $schema Schema object
  1607. * @return HTMLPurifier_Config Configured object
  1608. */
  1609. public static function create($config, $schema = null)
  1610. {
  1611. if ($config instanceof HTMLPurifier_Config) {
  1612. // pass-through
  1613. return $config;
  1614. }
  1615. if (!$schema) {
  1616. $ret = HTMLPurifier_Config::createDefault();
  1617. } else {
  1618. $ret = new HTMLPurifier_Config($schema);
  1619. }
  1620. if (is_string($config)) {
  1621. $ret->loadIni($config);
  1622. } elseif (is_array($config)) $ret->loadArray($config);
  1623. return $ret;
  1624. }
  1625. /**
  1626. * Creates a new config object that inherits from a previous one.
  1627. * @param HTMLPurifier_Config $config Configuration object to inherit from.
  1628. * @return HTMLPurifier_Config object with $config as its parent.
  1629. */
  1630. public static function inherit(HTMLPurifier_Config $config)
  1631. {
  1632. return new HTMLPurifier_Config($config->def, $config->plist);
  1633. }
  1634. /**
  1635. * Convenience constructor that creates a default configuration object.
  1636. * @return HTMLPurifier_Config default object.
  1637. */
  1638. public static function createDefault()
  1639. {
  1640. $definition = HTMLPurifier_ConfigSchema::instance();
  1641. $config = new HTMLPurifier_Config($definition);
  1642. return $config;
  1643. }
  1644. /**
  1645. * Retrieves a value from the configuration.
  1646. *
  1647. * @param string $key String key
  1648. * @param mixed $a
  1649. *
  1650. * @return mixed
  1651. */
  1652. public function get($key, $a = null)
  1653. {
  1654. if ($a !== null) {
  1655. $this->triggerError(
  1656. "Using deprecated API: use \$config->get('$key.$a') instead",
  1657. E_USER_WARNING
  1658. );
  1659. $key = "$key.$a";
  1660. }
  1661. if (!$this->finalized) {
  1662. $this->autoFinalize();
  1663. }
  1664. if (!isset($this->def->info[$key])) {
  1665. // can't add % due to SimpleTest bug
  1666. $this->triggerError(
  1667. 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
  1668. E_USER_WARNING
  1669. );
  1670. return;
  1671. }
  1672. if (isset($this->def->info[$key]->isAlias)) {
  1673. $d = $this->def->info[$key];
  1674. $this->triggerError(
  1675. 'Cannot get value from aliased directive, use real name ' . $d->key,
  1676. E_USER_ERROR
  1677. );
  1678. return;
  1679. }
  1680. if ($this->lock) {
  1681. list($ns) = explode('.', $key);
  1682. if ($ns !== $this->lock) {
  1683. $this->triggerError(
  1684. 'Cannot get value of namespace ' . $ns . ' when lock for ' .
  1685. $this->lock .
  1686. ' is active, this probably indicates a Definition setup method ' .
  1687. 'is accessing directives that are not within its namespace',
  1688. E_USER_ERROR
  1689. );
  1690. return;
  1691. }
  1692. }
  1693. return $this->plist->get($key);
  1694. }
  1695. /**
  1696. * Retrieves an array of directives to values from a given namespace
  1697. *
  1698. * @param string $namespace String namespace
  1699. *
  1700. * @return array
  1701. */
  1702. public function getBatch($namespace)
  1703. {
  1704. if (!$this->finalized) {
  1705. $this->autoFinalize();
  1706. }
  1707. $full = $this->getAll();
  1708. if (!isset($full[$namespace])) {
  1709. $this->triggerError(
  1710. 'Cannot retrieve undefined namespace ' .
  1711. htmlspecialchars($namespace),
  1712. E_USER_WARNING
  1713. );
  1714. return;
  1715. }
  1716. return $full[$namespace];
  1717. }
  1718. /**
  1719. * Returns a SHA-1 signature of a segment of the configuration object
  1720. * that uniquely identifies that particular configuration
  1721. *
  1722. * @param string $namespace Namespace to get serial for
  1723. *
  1724. * @return string
  1725. * @note Revision is handled specially and is removed from the batch
  1726. * before processing!
  1727. */
  1728. public function getBatchSerial($namespace)
  1729. {
  1730. if (empty($this->serials[$namespace])) {
  1731. $batch = $this->getBatch($namespace);
  1732. unset($batch['DefinitionRev']);
  1733. $this->serials[$namespace] = sha1(serialize($batch));
  1734. }
  1735. return $this->serials[$namespace];
  1736. }
  1737. /**
  1738. * Returns a SHA-1 signature for the entire configuration object
  1739. * that uniquely identifies that particular configuration
  1740. *
  1741. * @return string
  1742. */
  1743. public function getSerial()
  1744. {
  1745. if (empty($this->serial)) {
  1746. $this->serial = sha1(serialize($this->getAll()));
  1747. }
  1748. return $this->serial;
  1749. }
  1750. /**
  1751. * Retrieves all directives, organized by namespace
  1752. *
  1753. * @warning This is a pretty inefficient function, avoid if you can
  1754. */
  1755. public function getAll()
  1756. {
  1757. if (!$this->finalized) {
  1758. $this->autoFinalize();
  1759. }
  1760. $ret = array();
  1761. foreach ($this->plist->squash() as $name => $value) {
  1762. list($ns, $key) = explode('.', $name, 2);
  1763. $ret[$ns][$key] = $value;
  1764. }
  1765. return $ret;
  1766. }
  1767. /**
  1768. * Sets a value to configuration.
  1769. *
  1770. * @param string $key key
  1771. * @param mixed $value value
  1772. * @param mixed $a
  1773. */
  1774. public function set($key, $value, $a = null)
  1775. {
  1776. if (strpos($key, '.') === false) {
  1777. $namespace = $key;
  1778. $directive = $value;
  1779. $value = $a;
  1780. $key = "$key.$directive";
  1781. $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
  1782. } else {
  1783. list($namespace) = explode('.', $key);
  1784. }
  1785. if ($this->isFinalized('Cannot set directive after finalization')) {
  1786. return;
  1787. }
  1788. if (!isset($this->def->info[$key])) {
  1789. $this->triggerError(
  1790. 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
  1791. E_USER_WARNING
  1792. );
  1793. return;
  1794. }
  1795. $def = $this->def->info[$key];
  1796. if (isset($def->isAlias)) {
  1797. if ($this->aliasMode) {
  1798. $this->triggerError(
  1799. 'Double-aliases not allowed, please fix '.
  1800. 'ConfigSchema bug with' . $key,
  1801. E_USER_ERROR
  1802. );
  1803. return;
  1804. }
  1805. $this->aliasMode = true;
  1806. $this->set($def->key, $value);
  1807. $this->aliasMode = false;
  1808. $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
  1809. return;
  1810. }
  1811. // Raw type might be negative when using the fully optimized form
  1812. // of stdclass, which indicates allow_null == true
  1813. $rtype = is_int($def) ? $def : $def->type;
  1814. if ($rtype < 0) {
  1815. $type = -$rtype;
  1816. $allow_null = true;
  1817. } else {
  1818. $type = $rtype;
  1819. $allow_null = isset($def->allow_null);
  1820. }
  1821. try {
  1822. $value = $this->parser->parse($value, $type, $allow_null);
  1823. } catch (HTMLPurifier_VarParserException $e) {
  1824. $this->triggerError(
  1825. 'Value for ' . $key . ' is of invalid type, should be ' .
  1826. HTMLPurifier_VarParser::getTypeName($type),
  1827. E_USER_WARNING
  1828. );
  1829. return;
  1830. }
  1831. if (is_string($value) && is_object($def)) {
  1832. // resolve value alias if defined
  1833. if (isset($def->aliases[$value])) {
  1834. $value = $def->aliases[$value];
  1835. }
  1836. // check to see if the value is allowed
  1837. if (isset($def->allowed) && !isset($def->allowed[$value])) {
  1838. $this->triggerError(
  1839. 'Value not supported, valid values are: ' .
  1840. $this->_listify($def->allowed),
  1841. E_USER_WARNING
  1842. );
  1843. return;
  1844. }
  1845. }
  1846. $this->plist->set($key, $value);
  1847. // reset definitions if the directives they depend on changed
  1848. // this is a very costly process, so it's discouraged
  1849. // with finalization
  1850. if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
  1851. $this->definitions[$namespace] = null;
  1852. }
  1853. $this->serials[$namespace] = false;
  1854. }
  1855. /**
  1856. * Convenience function for error reporting
  1857. *
  1858. * @param array $lookup
  1859. *
  1860. * @return string
  1861. */
  1862. private function _listify($lookup)
  1863. {
  1864. $list = array();
  1865. foreach ($lookup as $name => $b) {
  1866. $list[] = $name;
  1867. }
  1868. return implode(', ', $list);
  1869. }
  1870. /**
  1871. * Retrieves object reference to the HTML definition.
  1872. *
  1873. * @param bool $raw Return a copy that has not been setup yet. Must be
  1874. * called before it's been setup, otherwise won't work.
  1875. * @param bool $optimized If true, this method may return null, to
  1876. * indicate that a cached version of the modified
  1877. * definition object is available and no further edits
  1878. * are necessary. Consider using
  1879. * maybeGetRawHTMLDefinition, which is more explicitly
  1880. * named, instead.
  1881. *
  1882. * @return HTMLPurifier_HTMLDefinition
  1883. */
  1884. public function getHTMLDefinition($raw = false, $optimized = false)
  1885. {
  1886. return $this->getDefinition('HTML', $raw, $optimized);
  1887. }
  1888. /**
  1889. * Retrieves object reference to the CSS definition
  1890. *
  1891. * @param bool $raw Return a copy that has not been setup yet. Must be
  1892. * called before it's been setup, otherwise won't work.
  1893. * @param bool $optimized If true, this method may return null, to
  1894. * indicate that a cached version of the modified
  1895. * definition object is available and no further edits
  1896. * are necessary. Consider using
  1897. * maybeGetRawCSSDefinition, which is more explicitly
  1898. * named, instead.
  1899. *
  1900. * @return HTMLPurifier_CSSDefinition
  1901. */
  1902. public function getCSSDefinition($raw = false, $optimized = false)
  1903. {
  1904. return $this->getDefinition('CSS', $raw, $optimized);
  1905. }
  1906. /**
  1907. * Retrieves object reference to the URI definition
  1908. *
  1909. * @param bool $raw Return a copy that has not been setup yet. Must be
  1910. * called before it's been setup, otherwise won't work.
  1911. * @param bool $optimized If true, this method may return null, to
  1912. * indicate that a cached version of the modified
  1913. * definition object is available and no further edits
  1914. * are necessary. Consider using
  1915. * maybeGetRawURIDefinition, which is more explicitly
  1916. * named, instead.
  1917. *
  1918. * @return HTMLPurifier_URIDefinition
  1919. */
  1920. public function getURIDefinition($raw = false, $optimized = false)
  1921. {
  1922. return $this->getDefinition('URI', $raw, $optimized);
  1923. }
  1924. /**
  1925. * Retrieves a definition
  1926. *
  1927. * @param string $type Type of definition: HTML, CSS, etc
  1928. * @param bool $raw Whether or not definition should be returned raw
  1929. * @param bool $optimized Only has an effect when $raw is true. Whether
  1930. * or not to return null if the result is already present in
  1931. * the cache. This is off by default for backwards
  1932. * compatibility reasons, but you need to do things this
  1933. * way in order to ensure that caching is done properly.
  1934. * Check out enduser-customize.html for more details.
  1935. * We probably won't ever change this default, as much as the
  1936. * maybe semantics is the "right thing to do."
  1937. *
  1938. * @throws HTMLPurifier_Exception
  1939. * @return HTMLPurifier_Definition
  1940. */
  1941. public function getDefinition($type, $raw = false, $optimized = false)
  1942. {
  1943. if ($optimized && !$raw) {
  1944. throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
  1945. }
  1946. if (!$this->finalized) {
  1947. $this->autoFinalize();
  1948. }
  1949. // temporarily suspend locks, so we can handle recursive definition calls
  1950. $lock = $this->lock;
  1951. $this->lock = null;
  1952. $factory = HTMLPurifier_DefinitionCacheFactory::instance();
  1953. $cache = $factory->create($type, $this);
  1954. $this->lock = $lock;
  1955. if (!$raw) {
  1956. // full definition
  1957. // ---------------
  1958. // check if definition is in memory
  1959. if (!empty($this->definitions[$type])) {
  1960. $def = $this->definitions[$type];
  1961. // check if the definition is setup
  1962. if ($def->setup) {
  1963. return $def;
  1964. } else {
  1965. $def->setup($this);
  1966. if ($def->optimized) {
  1967. $cache->add($def, $this);
  1968. }
  1969. return $def;
  1970. }
  1971. }
  1972. // check if definition is in cache
  1973. $def = $cache->get($this);
  1974. if ($def) {
  1975. // definition in cache, save to memory and return it
  1976. $this->definitions[$type] = $def;
  1977. return $def;
  1978. }
  1979. // initialize it
  1980. $def = $this->initDefinition($type);
  1981. // set it up
  1982. $this->lock = $type;
  1983. $def->setup($this);
  1984. $this->lock = null;
  1985. // save in cache
  1986. $cache->add($def, $this);
  1987. // return it
  1988. return $def;
  1989. } else {
  1990. // raw definition
  1991. // --------------
  1992. // check preconditions
  1993. $def = null;
  1994. if ($optimized) {
  1995. if (is_null($this->get($type . '.DefinitionID'))) {
  1996. // fatally error out if definition ID not set
  1997. throw new HTMLPurifier_Exception(
  1998. "Cannot retrieve raw version without specifying %$type.DefinitionID"
  1999. );
  2000. }
  2001. }
  2002. if (!empty($this->definitions[$type])) {
  2003. $def = $this->definitions[$type];
  2004. if ($def->setup && !$optimized) {
  2005. $extra = $this->chatty ?
  2006. " (try moving this code block earlier in your initialization)" :
  2007. "";
  2008. throw new HTMLPurifier_Exception(
  2009. "Cannot retrieve raw definition after it has already been setup" .
  2010. $extra
  2011. );
  2012. }
  2013. if ($def->optimized === null) {
  2014. $extra = $this->chatty ? " (try flushing your cache)" : "";
  2015. throw new HTMLPurifier_Exception(
  2016. "Optimization status of definition is unknown" . $extra
  2017. );
  2018. }
  2019. if ($def->optimized !== $optimized) {
  2020. $msg = $optimized ? "optimized" : "unoptimized";
  2021. $extra = $this->chatty ?
  2022. " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
  2023. : "";
  2024. throw new HTMLPurifier_Exception(
  2025. "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
  2026. );
  2027. }
  2028. }
  2029. // check if definition was in memory
  2030. if ($def) {
  2031. if ($def->setup) {
  2032. // invariant: $optimized === true (checked above)
  2033. return null;
  2034. } else {
  2035. return $def;
  2036. }
  2037. }
  2038. // if optimized, check if definition was in cache
  2039. // (because we do the memory check first, this formulation
  2040. // is prone to cache slamming, but I think
  2041. // guaranteeing that either /all/ of the raw
  2042. // setup code or /none/ of it is run is more important.)
  2043. if ($optimized) {
  2044. // This code path only gets run once; once we put
  2045. // something in $definitions (which is guaranteed by the
  2046. // trailing code), we always short-circuit above.
  2047. $def = $cache->get($this);
  2048. if ($def) {
  2049. // save the full definition for later, but don't
  2050. // return it yet
  2051. $this->definitions[$type] = $def;
  2052. return null;
  2053. }
  2054. }
  2055. // check invariants for creation
  2056. if (!$optimized) {
  2057. if (!is_null($this->get($type . '.DefinitionID'))) {
  2058. if ($this->chatty) {
  2059. $this->triggerError(
  2060. 'Due to a documentation error in previous version of HTML Purifier, your ' .
  2061. 'definitions are not being cached. If this is OK, you can remove the ' .
  2062. '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
  2063. 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
  2064. 'value is null before making any edits (if it is null, that means that a ' .
  2065. 'cached version is available, and no raw operations are necessary). See ' .
  2066. '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
  2067. 'Customize</a> for more details',
  2068. E_USER_WARNING
  2069. );
  2070. } else {
  2071. $this->triggerError(
  2072. "Useless DefinitionID declaration",
  2073. E_USER_WARNING
  2074. );
  2075. }
  2076. }
  2077. }
  2078. // initialize it
  2079. $def = $this->initDefinition($type);
  2080. $def->optimized = $optimized;
  2081. return $def;
  2082. }
  2083. throw new HTMLPurifier_Exception("The impossible happened!");
  2084. }
  2085. /**
  2086. * Initialise definition
  2087. *
  2088. * @param string $type What type of definition to create
  2089. *
  2090. * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
  2091. * @throws HTMLPurifier_Exception
  2092. */
  2093. private function initDefinition($type)
  2094. {
  2095. // quick checks failed, let's create the object
  2096. if ($type == 'HTML') {
  2097. $def = new HTMLPurifier_HTMLDefinition();
  2098. } elseif ($type == 'CSS') {
  2099. $def = new HTMLPurifier_CSSDefinition();
  2100. } elseif ($type == 'URI') {
  2101. $def = new HTMLPurifier_URIDefinition();
  2102. } else {
  2103. throw new HTMLPurifier_Exception(
  2104. "Definition of $type type not supported"
  2105. );
  2106. }
  2107. $this->definitions[$type] = $def;
  2108. return $def;
  2109. }
  2110. public function maybeGetRawDefinition($name)
  2111. {
  2112. return $this->getDefinition($name, true, true);
  2113. }
  2114. /**
  2115. * @return HTMLPurifier_HTMLDefinition
  2116. */
  2117. public function maybeGetRawHTMLDefinition()
  2118. {
  2119. return $this->getDefinition('HTML', true, true);
  2120. }
  2121. /**
  2122. * @return HTMLPurifier_CSSDefinition
  2123. */
  2124. public function maybeGetRawCSSDefinition()
  2125. {
  2126. return $this->getDefinition('CSS', true, true);
  2127. }
  2128. /**
  2129. * @return HTMLPurifier_URIDefinition
  2130. */
  2131. public function maybeGetRawURIDefinition()
  2132. {
  2133. return $this->getDefinition('URI', true, true);
  2134. }
  2135. /**
  2136. * Loads configuration values from an array with the following structure:
  2137. * Namespace.Directive => Value
  2138. *
  2139. * @param array $config_array Configuration associative array
  2140. */
  2141. public function loadArray($config_array)
  2142. {
  2143. if ($this->isFinalized('Cannot load directives after finalization')) {
  2144. return;
  2145. }
  2146. foreach ($config_array as $key => $value) {
  2147. $key = str_replace('_', '.', $key);
  2148. if (strpos($key, '.') !== false) {
  2149. $this->set($key, $value);
  2150. } else {
  2151. $namespace = $key;
  2152. $namespace_values = $value;
  2153. foreach ($namespace_values as $directive => $value2) {
  2154. $this->set($namespace .'.'. $directive, $value2);
  2155. }
  2156. }
  2157. }
  2158. }
  2159. /**
  2160. * Returns a list of array(namespace, directive) for all directives
  2161. * that are allowed in a web-form context as per an allowed
  2162. * namespaces/directives list.
  2163. *
  2164. * @param array $allowed List of allowed namespaces/directives
  2165. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2166. *
  2167. * @return array
  2168. */
  2169. public static function getAllowedDirectivesForForm($allowed, $schema = null)
  2170. {
  2171. if (!$schema) {
  2172. $schema = HTMLPurifier_ConfigSchema::instance();
  2173. }
  2174. if ($allowed !== true) {
  2175. if (is_string($allowed)) {
  2176. $allowed = array($allowed);
  2177. }
  2178. $allowed_ns = array();
  2179. $allowed_directives = array();
  2180. $blacklisted_directives = array();
  2181. foreach ($allowed as $ns_or_directive) {
  2182. if (strpos($ns_or_directive, '.') !== false) {
  2183. // directive
  2184. if ($ns_or_directive[0] == '-') {
  2185. $blacklisted_directives[substr($ns_or_directive, 1)] = true;
  2186. } else {
  2187. $allowed_directives[$ns_or_directive] = true;
  2188. }
  2189. } else {
  2190. // namespace
  2191. $allowed_ns[$ns_or_directive] = true;
  2192. }
  2193. }
  2194. }
  2195. $ret = array();
  2196. foreach ($schema->info as $key => $def) {
  2197. list($ns, $directive) = explode('.', $key, 2);
  2198. if ($allowed !== true) {
  2199. if (isset($blacklisted_directives["$ns.$directive"])) {
  2200. continue;
  2201. }
  2202. if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
  2203. continue;
  2204. }
  2205. }
  2206. if (isset($def->isAlias)) {
  2207. continue;
  2208. }
  2209. if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
  2210. continue;
  2211. }
  2212. $ret[] = array($ns, $directive);
  2213. }
  2214. return $ret;
  2215. }
  2216. /**
  2217. * Loads configuration values from $_GET/$_POST that were posted
  2218. * via ConfigForm
  2219. *
  2220. * @param array $array $_GET or $_POST array to import
  2221. * @param string|bool $index Index/name that the config variables are in
  2222. * @param array|bool $allowed List of allowed namespaces/directives
  2223. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2224. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2225. *
  2226. * @return mixed
  2227. */
  2228. public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2229. {
  2230. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
  2231. $config = HTMLPurifier_Config::create($ret, $schema);
  2232. return $config;
  2233. }
  2234. /**
  2235. * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
  2236. *
  2237. * @param array $array $_GET or $_POST array to import
  2238. * @param string|bool $index Index/name that the config variables are in
  2239. * @param array|bool $allowed List of allowed namespaces/directives
  2240. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2241. */
  2242. public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
  2243. {
  2244. $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
  2245. $this->loadArray($ret);
  2246. }
  2247. /**
  2248. * Prepares an array from a form into something usable for the more
  2249. * strict parts of HTMLPurifier_Config
  2250. *
  2251. * @param array $array $_GET or $_POST array to import
  2252. * @param string|bool $index Index/name that the config variables are in
  2253. * @param array|bool $allowed List of allowed namespaces/directives
  2254. * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
  2255. * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
  2256. *
  2257. * @return array
  2258. */
  2259. public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
  2260. {
  2261. if ($index !== false) {
  2262. $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
  2263. }
  2264. $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
  2265. $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
  2266. $ret = array();
  2267. foreach ($allowed as $key) {
  2268. list($ns, $directive) = $key;
  2269. $skey = "$ns.$directive";
  2270. if (!empty($array["Null_$skey"])) {
  2271. $ret[$ns][$directive] = null;
  2272. continue;
  2273. }
  2274. if (!isset($array[$skey])) {
  2275. continue;
  2276. }
  2277. $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
  2278. $ret[$ns][$directive] = $value;
  2279. }
  2280. return $ret;
  2281. }
  2282. /**
  2283. * Loads configuration values from an ini file
  2284. *
  2285. * @param string $filename Name of ini file
  2286. */
  2287. public function loadIni($filename)
  2288. {
  2289. if ($this->isFinalized('Cannot load directives after finalization')) {
  2290. return;
  2291. }
  2292. $array = parse_ini_file($filename, true);
  2293. $this->loadArray($array);
  2294. }
  2295. /**
  2296. * Checks whether or not the configuration object is finalized.
  2297. *
  2298. * @param string|bool $error String error message, or false for no error
  2299. *
  2300. * @return bool
  2301. */
  2302. public function isFinalized($error = false)
  2303. {
  2304. if ($this->finalized && $error) {
  2305. $this->triggerError($error, E_USER_ERROR);
  2306. }
  2307. return $this->finalized;
  2308. }
  2309. /**
  2310. * Finalizes configuration only if auto finalize is on and not
  2311. * already finalized
  2312. */
  2313. public function autoFinalize()
  2314. {
  2315. if ($this->autoFinalize) {
  2316. $this->finalize();
  2317. } else {
  2318. $this->plist->squash(true);
  2319. }
  2320. }
  2321. /**
  2322. * Finalizes a configuration object, prohibiting further change
  2323. */
  2324. public function finalize()
  2325. {
  2326. $this->finalized = true;
  2327. $this->parser = null;
  2328. }
  2329. /**
  2330. * Produces a nicely formatted error message by supplying the
  2331. * stack frame information OUTSIDE of HTMLPurifier_Config.
  2332. *
  2333. * @param string $msg An error message
  2334. * @param int $no An error number
  2335. */
  2336. protected function triggerError($msg, $no)
  2337. {
  2338. // determine previous stack frame
  2339. $extra = '';
  2340. if ($this->chatty) {
  2341. $trace = debug_backtrace();
  2342. // zip(tail(trace), trace) -- but PHP is not Haskell har har
  2343. for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
  2344. // XXX this is not correct on some versions of HTML Purifier
  2345. if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
  2346. continue;
  2347. }
  2348. $frame = $trace[$i];
  2349. $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
  2350. break;
  2351. }
  2352. }
  2353. trigger_error($msg . $extra, $no);
  2354. }
  2355. /**
  2356. * Returns a serialized form of the configuration object that can
  2357. * be reconstituted.
  2358. *
  2359. * @return string
  2360. */
  2361. public function serialize()
  2362. {
  2363. $this->getDefinition('HTML');
  2364. $this->getDefinition('CSS');
  2365. $this->getDefinition('URI');
  2366. return serialize($this);
  2367. }
  2368. }
  2369. /**
  2370. * Configuration definition, defines directives and their defaults.
  2371. */
  2372. class HTMLPurifier_ConfigSchema
  2373. {
  2374. /**
  2375. * Defaults of the directives and namespaces.
  2376. * @type array
  2377. * @note This shares the exact same structure as HTMLPurifier_Config::$conf
  2378. */
  2379. public $defaults = array();
  2380. /**
  2381. * The default property list. Do not edit this property list.
  2382. * @type array
  2383. */
  2384. public $defaultPlist;
  2385. /**
  2386. * Definition of the directives.
  2387. * The structure of this is:
  2388. *
  2389. * array(
  2390. * 'Namespace' => array(
  2391. * 'Directive' => new stdclass(),
  2392. * )
  2393. * )
  2394. *
  2395. * The stdclass may have the following properties:
  2396. *
  2397. * - If isAlias isn't set:
  2398. * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
  2399. * - allow_null: If set, this directive allows null values
  2400. * - aliases: If set, an associative array of value aliases to real values
  2401. * - allowed: If set, a lookup array of allowed (string) values
  2402. * - If isAlias is set:
  2403. * - namespace: Namespace this directive aliases to
  2404. * - name: Directive name this directive aliases to
  2405. *
  2406. * In certain degenerate cases, stdclass will actually be an integer. In
  2407. * that case, the value is equivalent to an stdclass with the type
  2408. * property set to the integer. If the integer is negative, type is
  2409. * equal to the absolute value of integer, and allow_null is true.
  2410. *
  2411. * This class is friendly with HTMLPurifier_Config. If you need introspection
  2412. * about the schema, you're better of using the ConfigSchema_Interchange,
  2413. * which uses more memory but has much richer information.
  2414. * @type array
  2415. */
  2416. public $info = array();
  2417. /**
  2418. * Application-wide singleton
  2419. * @type HTMLPurifier_ConfigSchema
  2420. */
  2421. protected static $singleton;
  2422. public function __construct()
  2423. {
  2424. $this->defaultPlist = new HTMLPurifier_PropertyList();
  2425. }
  2426. /**
  2427. * Unserializes the default ConfigSchema.
  2428. * @return HTMLPurifier_ConfigSchema
  2429. */
  2430. public static function makeFromSerial()
  2431. {
  2432. $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
  2433. $r = unserialize($contents);
  2434. if (!$r) {
  2435. $hash = sha1($contents);
  2436. trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
  2437. }
  2438. return $r;
  2439. }
  2440. /**
  2441. * Retrieves an instance of the application-wide configuration definition.
  2442. * @param HTMLPurifier_ConfigSchema $prototype
  2443. * @return HTMLPurifier_ConfigSchema
  2444. */
  2445. public static function instance($prototype = null)
  2446. {
  2447. if ($prototype !== null) {
  2448. HTMLPurifier_ConfigSchema::$singleton = $prototype;
  2449. } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
  2450. HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
  2451. }
  2452. return HTMLPurifier_ConfigSchema::$singleton;
  2453. }
  2454. /**
  2455. * Defines a directive for configuration
  2456. * @warning Will fail of directive's namespace is defined.
  2457. * @warning This method's signature is slightly different from the legacy
  2458. * define() static method! Beware!
  2459. * @param string $key Name of directive
  2460. * @param mixed $default Default value of directive
  2461. * @param string $type Allowed type of the directive. See
  2462. * HTMLPurifier_DirectiveDef::$type for allowed values
  2463. * @param bool $allow_null Whether or not to allow null values
  2464. */
  2465. public function add($key, $default, $type, $allow_null)
  2466. {
  2467. $obj = new stdclass();
  2468. $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
  2469. if ($allow_null) {
  2470. $obj->allow_null = true;
  2471. }
  2472. $this->info[$key] = $obj;
  2473. $this->defaults[$key] = $default;
  2474. $this->defaultPlist->set($key, $default);
  2475. }
  2476. /**
  2477. * Defines a directive value alias.
  2478. *
  2479. * Directive value aliases are convenient for developers because it lets
  2480. * them set a directive to several values and get the same result.
  2481. * @param string $key Name of Directive
  2482. * @param array $aliases Hash of aliased values to the real alias
  2483. */
  2484. public function addValueAliases($key, $aliases)
  2485. {
  2486. if (!isset($this->info[$key]->aliases)) {
  2487. $this->info[$key]->aliases = array();
  2488. }
  2489. foreach ($aliases as $alias => $real) {
  2490. $this->info[$key]->aliases[$alias] = $real;
  2491. }
  2492. }
  2493. /**
  2494. * Defines a set of allowed values for a directive.
  2495. * @warning This is slightly different from the corresponding static
  2496. * method definition.
  2497. * @param string $key Name of directive
  2498. * @param array $allowed Lookup array of allowed values
  2499. */
  2500. public function addAllowedValues($key, $allowed)
  2501. {
  2502. $this->info[$key]->allowed = $allowed;
  2503. }
  2504. /**
  2505. * Defines a directive alias for backwards compatibility
  2506. * @param string $key Directive that will be aliased
  2507. * @param string $new_key Directive that the alias will be to
  2508. */
  2509. public function addAlias($key, $new_key)
  2510. {
  2511. $obj = new stdclass;
  2512. $obj->key = $new_key;
  2513. $obj->isAlias = true;
  2514. $this->info[$key] = $obj;
  2515. }
  2516. /**
  2517. * Replaces any stdclass that only has the type property with type integer.
  2518. */
  2519. public function postProcess()
  2520. {
  2521. foreach ($this->info as $key => $v) {
  2522. if (count((array) $v) == 1) {
  2523. $this->info[$key] = $v->type;
  2524. } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
  2525. $this->info[$key] = -$v->type;
  2526. }
  2527. }
  2528. }
  2529. }
  2530. /**
  2531. * @todo Unit test
  2532. */
  2533. class HTMLPurifier_ContentSets
  2534. {
  2535. /**
  2536. * List of content set strings (pipe separators) indexed by name.
  2537. * @type array
  2538. */
  2539. public $info = array();
  2540. /**
  2541. * List of content set lookups (element => true) indexed by name.
  2542. * @type array
  2543. * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
  2544. */
  2545. public $lookup = array();
  2546. /**
  2547. * Synchronized list of defined content sets (keys of info).
  2548. * @type array
  2549. */
  2550. protected $keys = array();
  2551. /**
  2552. * Synchronized list of defined content values (values of info).
  2553. * @type array
  2554. */
  2555. protected $values = array();
  2556. /**
  2557. * Merges in module's content sets, expands identifiers in the content
  2558. * sets and populates the keys, values and lookup member variables.
  2559. * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
  2560. */
  2561. public function __construct($modules)
  2562. {
  2563. if (!is_array($modules)) {
  2564. $modules = array($modules);
  2565. }
  2566. // populate content_sets based on module hints
  2567. // sorry, no way of overloading
  2568. foreach ($modules as $module) {
  2569. foreach ($module->content_sets as $key => $value) {
  2570. $temp = $this->convertToLookup($value);
  2571. if (isset($this->lookup[$key])) {
  2572. // add it into the existing content set
  2573. $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
  2574. } else {
  2575. $this->lookup[$key] = $temp;
  2576. }
  2577. }
  2578. }
  2579. $old_lookup = false;
  2580. while ($old_lookup !== $this->lookup) {
  2581. $old_lookup = $this->lookup;
  2582. foreach ($this->lookup as $i => $set) {
  2583. $add = array();
  2584. foreach ($set as $element => $x) {
  2585. if (isset($this->lookup[$element])) {
  2586. $add += $this->lookup[$element];
  2587. unset($this->lookup[$i][$element]);
  2588. }
  2589. }
  2590. $this->lookup[$i] += $add;
  2591. }
  2592. }
  2593. foreach ($this->lookup as $key => $lookup) {
  2594. $this->info[$key] = implode(' | ', array_keys($lookup));
  2595. }
  2596. $this->keys = array_keys($this->info);
  2597. $this->values = array_values($this->info);
  2598. }
  2599. /**
  2600. * Accepts a definition; generates and assigns a ChildDef for it
  2601. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
  2602. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2603. */
  2604. public function generateChildDef(&$def, $module)
  2605. {
  2606. if (!empty($def->child)) { // already done!
  2607. return;
  2608. }
  2609. $content_model = $def->content_model;
  2610. if (is_string($content_model)) {
  2611. // Assume that $this->keys is alphanumeric
  2612. $def->content_model = preg_replace_callback(
  2613. '/\b(' . implode('|', $this->keys) . ')\b/',
  2614. array($this, 'generateChildDefCallback'),
  2615. $content_model
  2616. );
  2617. //$def->content_model = str_replace(
  2618. // $this->keys, $this->values, $content_model);
  2619. }
  2620. $def->child = $this->getChildDef($def, $module);
  2621. }
  2622. public function generateChildDefCallback($matches)
  2623. {
  2624. return $this->info[$matches[0]];
  2625. }
  2626. /**
  2627. * Instantiates a ChildDef based on content_model and content_model_type
  2628. * member variables in HTMLPurifier_ElementDef
  2629. * @note This will also defer to modules for custom HTMLPurifier_ChildDef
  2630. * subclasses that need content set expansion
  2631. * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
  2632. * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
  2633. * @return HTMLPurifier_ChildDef corresponding to ElementDef
  2634. */
  2635. public function getChildDef($def, $module)
  2636. {
  2637. $value = $def->content_model;
  2638. if (is_object($value)) {
  2639. trigger_error(
  2640. 'Literal object child definitions should be stored in '.
  2641. 'ElementDef->child not ElementDef->content_model',
  2642. E_USER_NOTICE
  2643. );
  2644. return $value;
  2645. }
  2646. switch ($def->content_model_type) {
  2647. case 'required':
  2648. return new HTMLPurifier_ChildDef_Required($value);
  2649. case 'optional':
  2650. return new HTMLPurifier_ChildDef_Optional($value);
  2651. case 'empty':
  2652. return new HTMLPurifier_ChildDef_Empty();
  2653. case 'custom':
  2654. return new HTMLPurifier_ChildDef_Custom($value);
  2655. }
  2656. // defer to its module
  2657. $return = false;
  2658. if ($module->defines_child_def) { // save a func call
  2659. $return = $module->getChildDef($def);
  2660. }
  2661. if ($return !== false) {
  2662. return $return;
  2663. }
  2664. // error-out
  2665. trigger_error(
  2666. 'Could not determine which ChildDef class to instantiate',
  2667. E_USER_ERROR
  2668. );
  2669. return false;
  2670. }
  2671. /**
  2672. * Converts a string list of elements separated by pipes into
  2673. * a lookup array.
  2674. * @param string $string List of elements
  2675. * @return array Lookup array of elements
  2676. */
  2677. protected function convertToLookup($string)
  2678. {
  2679. $array = explode('|', str_replace(' ', '', $string));
  2680. $ret = array();
  2681. foreach ($array as $k) {
  2682. $ret[$k] = true;
  2683. }
  2684. return $ret;
  2685. }
  2686. }
  2687. /**
  2688. * Registry object that contains information about the current context.
  2689. * @warning Is a bit buggy when variables are set to null: it thinks
  2690. * they don't exist! So use false instead, please.
  2691. * @note Since the variables Context deals with may not be objects,
  2692. * references are very important here! Do not remove!
  2693. */
  2694. class HTMLPurifier_Context
  2695. {
  2696. /**
  2697. * Private array that stores the references.
  2698. * @type array
  2699. */
  2700. private $_storage = array();
  2701. /**
  2702. * Registers a variable into the context.
  2703. * @param string $name String name
  2704. * @param mixed $ref Reference to variable to be registered
  2705. */
  2706. public function register($name, &$ref)
  2707. {
  2708. if (array_key_exists($name, $this->_storage)) {
  2709. trigger_error(
  2710. "Name $name produces collision, cannot re-register",
  2711. E_USER_ERROR
  2712. );
  2713. return;
  2714. }
  2715. $this->_storage[$name] =& $ref;
  2716. }
  2717. /**
  2718. * Retrieves a variable reference from the context.
  2719. * @param string $name String name
  2720. * @param bool $ignore_error Boolean whether or not to ignore error
  2721. * @return mixed
  2722. */
  2723. public function &get($name, $ignore_error = false)
  2724. {
  2725. if (!array_key_exists($name, $this->_storage)) {
  2726. if (!$ignore_error) {
  2727. trigger_error(
  2728. "Attempted to retrieve non-existent variable $name",
  2729. E_USER_ERROR
  2730. );
  2731. }
  2732. $var = null; // so we can return by reference
  2733. return $var;
  2734. }
  2735. return $this->_storage[$name];
  2736. }
  2737. /**
  2738. * Destroys a variable in the context.
  2739. * @param string $name String name
  2740. */
  2741. public function destroy($name)
  2742. {
  2743. if (!array_key_exists($name, $this->_storage)) {
  2744. trigger_error(
  2745. "Attempted to destroy non-existent variable $name",
  2746. E_USER_ERROR
  2747. );
  2748. return;
  2749. }
  2750. unset($this->_storage[$name]);
  2751. }
  2752. /**
  2753. * Checks whether or not the variable exists.
  2754. * @param string $name String name
  2755. * @return bool
  2756. */
  2757. public function exists($name)
  2758. {
  2759. return array_key_exists($name, $this->_storage);
  2760. }
  2761. /**
  2762. * Loads a series of variables from an associative array
  2763. * @param array $context_array Assoc array of variables to load
  2764. */
  2765. public function loadArray($context_array)
  2766. {
  2767. foreach ($context_array as $key => $discard) {
  2768. $this->register($key, $context_array[$key]);
  2769. }
  2770. }
  2771. }
  2772. /**
  2773. * Abstract class representing Definition cache managers that implements
  2774. * useful common methods and is a factory.
  2775. * @todo Create a separate maintenance file advanced users can use to
  2776. * cache their custom HTMLDefinition, which can be loaded
  2777. * via a configuration directive
  2778. * @todo Implement memcached
  2779. */
  2780. abstract class HTMLPurifier_DefinitionCache
  2781. {
  2782. /**
  2783. * @type string
  2784. */
  2785. public $type;
  2786. /**
  2787. * @param string $type Type of definition objects this instance of the
  2788. * cache will handle.
  2789. */
  2790. public function __construct($type)
  2791. {
  2792. $this->type = $type;
  2793. }
  2794. /**
  2795. * Generates a unique identifier for a particular configuration
  2796. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  2797. * @return string
  2798. */
  2799. public function generateKey($config)
  2800. {
  2801. return $config->version . ',' . // possibly replace with function calls
  2802. $config->getBatchSerial($this->type) . ',' .
  2803. $config->get($this->type . '.DefinitionRev');
  2804. }
  2805. /**
  2806. * Tests whether or not a key is old with respect to the configuration's
  2807. * version and revision number.
  2808. * @param string $key Key to test
  2809. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
  2810. * @return bool
  2811. */
  2812. public function isOld($key, $config)
  2813. {
  2814. if (substr_count($key, ',') < 2) {
  2815. return true;
  2816. }
  2817. list($version, $hash, $revision) = explode(',', $key, 3);
  2818. $compare = version_compare($version, $config->version);
  2819. // version mismatch, is always old
  2820. if ($compare != 0) {
  2821. return true;
  2822. }
  2823. // versions match, ids match, check revision number
  2824. if ($hash == $config->getBatchSerial($this->type) &&
  2825. $revision < $config->get($this->type . '.DefinitionRev')) {
  2826. return true;
  2827. }
  2828. return false;
  2829. }
  2830. /**
  2831. * Checks if a definition's type jives with the cache's type
  2832. * @note Throws an error on failure
  2833. * @param HTMLPurifier_Definition $def Definition object to check
  2834. * @return bool true if good, false if not
  2835. */
  2836. public function checkDefType($def)
  2837. {
  2838. if ($def->type !== $this->type) {
  2839. trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
  2840. return false;
  2841. }
  2842. return true;
  2843. }
  2844. /**
  2845. * Adds a definition object to the cache
  2846. * @param HTMLPurifier_Definition $def
  2847. * @param HTMLPurifier_Config $config
  2848. */
  2849. abstract public function add($def, $config);
  2850. /**
  2851. * Unconditionally saves a definition object to the cache
  2852. * @param HTMLPurifier_Definition $def
  2853. * @param HTMLPurifier_Config $config
  2854. */
  2855. abstract public function set($def, $config);
  2856. /**
  2857. * Replace an object in the cache
  2858. * @param HTMLPurifier_Definition $def
  2859. * @param HTMLPurifier_Config $config
  2860. */
  2861. abstract public function replace($def, $config);
  2862. /**
  2863. * Retrieves a definition object from the cache
  2864. * @param HTMLPurifier_Config $config
  2865. */
  2866. abstract public function get($config);
  2867. /**
  2868. * Removes a definition object to the cache
  2869. * @param HTMLPurifier_Config $config
  2870. */
  2871. abstract public function remove($config);
  2872. /**
  2873. * Clears all objects from cache
  2874. * @param HTMLPurifier_Config $config
  2875. */
  2876. abstract public function flush($config);
  2877. /**
  2878. * Clears all expired (older version or revision) objects from cache
  2879. * @note Be carefuly implementing this method as flush. Flush must
  2880. * not interfere with other Definition types, and cleanup()
  2881. * should not be repeatedly called by userland code.
  2882. * @param HTMLPurifier_Config $config
  2883. */
  2884. abstract public function cleanup($config);
  2885. }
  2886. /**
  2887. * Responsible for creating definition caches.
  2888. */
  2889. class HTMLPurifier_DefinitionCacheFactory
  2890. {
  2891. /**
  2892. * @type array
  2893. */
  2894. protected $caches = array('Serializer' => array());
  2895. /**
  2896. * @type array
  2897. */
  2898. protected $implementations = array();
  2899. /**
  2900. * @type HTMLPurifier_DefinitionCache_Decorator[]
  2901. */
  2902. protected $decorators = array();
  2903. /**
  2904. * Initialize default decorators
  2905. */
  2906. public function setup()
  2907. {
  2908. $this->addDecorator('Cleanup');
  2909. }
  2910. /**
  2911. * Retrieves an instance of global definition cache factory.
  2912. * @param HTMLPurifier_DefinitionCacheFactory $prototype
  2913. * @return HTMLPurifier_DefinitionCacheFactory
  2914. */
  2915. public static function instance($prototype = null)
  2916. {
  2917. static $instance;
  2918. if ($prototype !== null) {
  2919. $instance = $prototype;
  2920. } elseif ($instance === null || $prototype === true) {
  2921. $instance = new HTMLPurifier_DefinitionCacheFactory();
  2922. $instance->setup();
  2923. }
  2924. return $instance;
  2925. }
  2926. /**
  2927. * Registers a new definition cache object
  2928. * @param string $short Short name of cache object, for reference
  2929. * @param string $long Full class name of cache object, for construction
  2930. */
  2931. public function register($short, $long)
  2932. {
  2933. $this->implementations[$short] = $long;
  2934. }
  2935. /**
  2936. * Factory method that creates a cache object based on configuration
  2937. * @param string $type Name of definitions handled by cache
  2938. * @param HTMLPurifier_Config $config Config instance
  2939. * @return mixed
  2940. */
  2941. public function create($type, $config)
  2942. {
  2943. $method = $config->get('Cache.DefinitionImpl');
  2944. if ($method === null) {
  2945. return new HTMLPurifier_DefinitionCache_Null($type);
  2946. }
  2947. if (!empty($this->caches[$method][$type])) {
  2948. return $this->caches[$method][$type];
  2949. }
  2950. if (isset($this->implementations[$method]) &&
  2951. class_exists($class = $this->implementations[$method], false)) {
  2952. $cache = new $class($type);
  2953. } else {
  2954. if ($method != 'Serializer') {
  2955. trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
  2956. }
  2957. $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
  2958. }
  2959. foreach ($this->decorators as $decorator) {
  2960. $new_cache = $decorator->decorate($cache);
  2961. // prevent infinite recursion in PHP 4
  2962. unset($cache);
  2963. $cache = $new_cache;
  2964. }
  2965. $this->caches[$method][$type] = $cache;
  2966. return $this->caches[$method][$type];
  2967. }
  2968. /**
  2969. * Registers a decorator to add to all new cache objects
  2970. * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
  2971. */
  2972. public function addDecorator($decorator)
  2973. {
  2974. if (is_string($decorator)) {
  2975. $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
  2976. $decorator = new $class;
  2977. }
  2978. $this->decorators[$decorator->name] = $decorator;
  2979. }
  2980. }
  2981. /**
  2982. * Represents a document type, contains information on which modules
  2983. * need to be loaded.
  2984. * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
  2985. * If structure changes, please update that function.
  2986. */
  2987. class HTMLPurifier_Doctype
  2988. {
  2989. /**
  2990. * Full name of doctype
  2991. * @type string
  2992. */
  2993. public $name;
  2994. /**
  2995. * List of standard modules (string identifiers or literal objects)
  2996. * that this doctype uses
  2997. * @type array
  2998. */
  2999. public $modules = array();
  3000. /**
  3001. * List of modules to use for tidying up code
  3002. * @type array
  3003. */
  3004. public $tidyModules = array();
  3005. /**
  3006. * Is the language derived from XML (i.e. XHTML)?
  3007. * @type bool
  3008. */
  3009. public $xml = true;
  3010. /**
  3011. * List of aliases for this doctype
  3012. * @type array
  3013. */
  3014. public $aliases = array();
  3015. /**
  3016. * Public DTD identifier
  3017. * @type string
  3018. */
  3019. public $dtdPublic;
  3020. /**
  3021. * System DTD identifier
  3022. * @type string
  3023. */
  3024. public $dtdSystem;
  3025. public function __construct(
  3026. $name = null,
  3027. $xml = true,
  3028. $modules = array(),
  3029. $tidyModules = array(),
  3030. $aliases = array(),
  3031. $dtd_public = null,
  3032. $dtd_system = null
  3033. ) {
  3034. $this->name = $name;
  3035. $this->xml = $xml;
  3036. $this->modules = $modules;
  3037. $this->tidyModules = $tidyModules;
  3038. $this->aliases = $aliases;
  3039. $this->dtdPublic = $dtd_public;
  3040. $this->dtdSystem = $dtd_system;
  3041. }
  3042. }
  3043. class HTMLPurifier_DoctypeRegistry
  3044. {
  3045. /**
  3046. * Hash of doctype names to doctype objects.
  3047. * @type array
  3048. */
  3049. protected $doctypes;
  3050. /**
  3051. * Lookup table of aliases to real doctype names.
  3052. * @type array
  3053. */
  3054. protected $aliases;
  3055. /**
  3056. * Registers a doctype to the registry
  3057. * @note Accepts a fully-formed doctype object, or the
  3058. * parameters for constructing a doctype object
  3059. * @param string $doctype Name of doctype or literal doctype object
  3060. * @param bool $xml
  3061. * @param array $modules Modules doctype will load
  3062. * @param array $tidy_modules Modules doctype will load for certain modes
  3063. * @param array $aliases Alias names for doctype
  3064. * @param string $dtd_public
  3065. * @param string $dtd_system
  3066. * @return HTMLPurifier_Doctype Editable registered doctype
  3067. */
  3068. public function register(
  3069. $doctype,
  3070. $xml = true,
  3071. $modules = array(),
  3072. $tidy_modules = array(),
  3073. $aliases = array(),
  3074. $dtd_public = null,
  3075. $dtd_system = null
  3076. ) {
  3077. if (!is_array($modules)) {
  3078. $modules = array($modules);
  3079. }
  3080. if (!is_array($tidy_modules)) {
  3081. $tidy_modules = array($tidy_modules);
  3082. }
  3083. if (!is_array($aliases)) {
  3084. $aliases = array($aliases);
  3085. }
  3086. if (!is_object($doctype)) {
  3087. $doctype = new HTMLPurifier_Doctype(
  3088. $doctype,
  3089. $xml,
  3090. $modules,
  3091. $tidy_modules,
  3092. $aliases,
  3093. $dtd_public,
  3094. $dtd_system
  3095. );
  3096. }
  3097. $this->doctypes[$doctype->name] = $doctype;
  3098. $name = $doctype->name;
  3099. // hookup aliases
  3100. foreach ($doctype->aliases as $alias) {
  3101. if (isset($this->doctypes[$alias])) {
  3102. continue;
  3103. }
  3104. $this->aliases[$alias] = $name;
  3105. }
  3106. // remove old aliases
  3107. if (isset($this->aliases[$name])) {
  3108. unset($this->aliases[$name]);
  3109. }
  3110. return $doctype;
  3111. }
  3112. /**
  3113. * Retrieves reference to a doctype of a certain name
  3114. * @note This function resolves aliases
  3115. * @note When possible, use the more fully-featured make()
  3116. * @param string $doctype Name of doctype
  3117. * @return HTMLPurifier_Doctype Editable doctype object
  3118. */
  3119. public function get($doctype)
  3120. {
  3121. if (isset($this->aliases[$doctype])) {
  3122. $doctype = $this->aliases[$doctype];
  3123. }
  3124. if (!isset($this->doctypes[$doctype])) {
  3125. trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
  3126. $anon = new HTMLPurifier_Doctype($doctype);
  3127. return $anon;
  3128. }
  3129. return $this->doctypes[$doctype];
  3130. }
  3131. /**
  3132. * Creates a doctype based on a configuration object,
  3133. * will perform initialization on the doctype
  3134. * @note Use this function to get a copy of doctype that config
  3135. * can hold on to (this is necessary in order to tell
  3136. * Generator whether or not the current document is XML
  3137. * based or not).
  3138. * @param HTMLPurifier_Config $config
  3139. * @return HTMLPurifier_Doctype
  3140. */
  3141. public function make($config)
  3142. {
  3143. return clone $this->get($this->getDoctypeFromConfig($config));
  3144. }
  3145. /**
  3146. * Retrieves the doctype from the configuration object
  3147. * @param HTMLPurifier_Config $config
  3148. * @return string
  3149. */
  3150. public function getDoctypeFromConfig($config)
  3151. {
  3152. // recommended test
  3153. $doctype = $config->get('HTML.Doctype');
  3154. if (!empty($doctype)) {
  3155. return $doctype;
  3156. }
  3157. $doctype = $config->get('HTML.CustomDoctype');
  3158. if (!empty($doctype)) {
  3159. return $doctype;
  3160. }
  3161. // backwards-compatibility
  3162. if ($config->get('HTML.XHTML')) {
  3163. $doctype = 'XHTML 1.0';
  3164. } else {
  3165. $doctype = 'HTML 4.01';
  3166. }
  3167. if ($config->get('HTML.Strict')) {
  3168. $doctype .= ' Strict';
  3169. } else {
  3170. $doctype .= ' Transitional';
  3171. }
  3172. return $doctype;
  3173. }
  3174. }
  3175. /**
  3176. * Structure that stores an HTML element definition. Used by
  3177. * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
  3178. * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
  3179. * Please update that class too.
  3180. * @warning If you add new properties to this class, you MUST update
  3181. * the mergeIn() method.
  3182. */
  3183. class HTMLPurifier_ElementDef
  3184. {
  3185. /**
  3186. * Does the definition work by itself, or is it created solely
  3187. * for the purpose of merging into another definition?
  3188. * @type bool
  3189. */
  3190. public $standalone = true;
  3191. /**
  3192. * Associative array of attribute name to HTMLPurifier_AttrDef.
  3193. * @type array
  3194. * @note Before being processed by HTMLPurifier_AttrCollections
  3195. * when modules are finalized during
  3196. * HTMLPurifier_HTMLDefinition->setup(), this array may also
  3197. * contain an array at index 0 that indicates which attribute
  3198. * collections to load into the full array. It may also
  3199. * contain string indentifiers in lieu of HTMLPurifier_AttrDef,
  3200. * see HTMLPurifier_AttrTypes on how they are expanded during
  3201. * HTMLPurifier_HTMLDefinition->setup() processing.
  3202. */
  3203. public $attr = array();
  3204. // XXX: Design note: currently, it's not possible to override
  3205. // previously defined AttrTransforms without messing around with
  3206. // the final generated config. This is by design; a previous version
  3207. // used an associated list of attr_transform, but it was extremely
  3208. // easy to accidentally override other attribute transforms by
  3209. // forgetting to specify an index (and just using 0.) While we
  3210. // could check this by checking the index number and complaining,
  3211. // there is a second problem which is that it is not at all easy to
  3212. // tell when something is getting overridden. Combine this with a
  3213. // codebase where this isn't really being used, and it's perfect for
  3214. // nuking.
  3215. /**
  3216. * List of tags HTMLPurifier_AttrTransform to be done before validation.
  3217. * @type array
  3218. */
  3219. public $attr_transform_pre = array();
  3220. /**
  3221. * List of tags HTMLPurifier_AttrTransform to be done after validation.
  3222. * @type array
  3223. */
  3224. public $attr_transform_post = array();
  3225. /**
  3226. * HTMLPurifier_ChildDef of this tag.
  3227. * @type HTMLPurifier_ChildDef
  3228. */
  3229. public $child;
  3230. /**
  3231. * Abstract string representation of internal ChildDef rules.
  3232. * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
  3233. * into an HTMLPurifier_ChildDef.
  3234. * @warning This is a temporary variable that is not available after
  3235. * being processed by HTMLDefinition
  3236. * @type string
  3237. */
  3238. public $content_model;
  3239. /**
  3240. * Value of $child->type, used to determine which ChildDef to use,
  3241. * used in combination with $content_model.
  3242. * @warning This must be lowercase
  3243. * @warning This is a temporary variable that is not available after
  3244. * being processed by HTMLDefinition
  3245. * @type string
  3246. */
  3247. public $content_model_type;
  3248. /**
  3249. * Does the element have a content model (#PCDATA | Inline)*? This
  3250. * is important for chameleon ins and del processing in
  3251. * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
  3252. * have to worry about this one.
  3253. * @type bool
  3254. */
  3255. public $descendants_are_inline = false;
  3256. /**
  3257. * List of the names of required attributes this element has.
  3258. * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
  3259. * @type array
  3260. */
  3261. public $required_attr = array();
  3262. /**
  3263. * Lookup table of tags excluded from all descendants of this tag.
  3264. * @type array
  3265. * @note SGML permits exclusions for all descendants, but this is
  3266. * not possible with DTDs or XML Schemas. W3C has elected to
  3267. * use complicated compositions of content_models to simulate
  3268. * exclusion for children, but we go the simpler, SGML-style
  3269. * route of flat-out exclusions, which correctly apply to
  3270. * all descendants and not just children. Note that the XHTML
  3271. * Modularization Abstract Modules are blithely unaware of such
  3272. * distinctions.
  3273. */
  3274. public $excludes = array();
  3275. /**
  3276. * This tag is explicitly auto-closed by the following tags.
  3277. * @type array
  3278. */
  3279. public $autoclose = array();
  3280. /**
  3281. * If a foreign element is found in this element, test if it is
  3282. * allowed by this sub-element; if it is, instead of closing the
  3283. * current element, place it inside this element.
  3284. * @type string
  3285. */
  3286. public $wrap;
  3287. /**
  3288. * Whether or not this is a formatting element affected by the
  3289. * "Active Formatting Elements" algorithm.
  3290. * @type bool
  3291. */
  3292. public $formatting;
  3293. /**
  3294. * Low-level factory constructor for creating new standalone element defs
  3295. */
  3296. public static function create($content_model, $content_model_type, $attr)
  3297. {
  3298. $def = new HTMLPurifier_ElementDef();
  3299. $def->content_model = $content_model;
  3300. $def->content_model_type = $content_model_type;
  3301. $def->attr = $attr;
  3302. return $def;
  3303. }
  3304. /**
  3305. * Merges the values of another element definition into this one.
  3306. * Values from the new element def take precedence if a value is
  3307. * not mergeable.
  3308. * @param HTMLPurifier_ElementDef $def
  3309. */
  3310. public function mergeIn($def)
  3311. {
  3312. // later keys takes precedence
  3313. foreach ($def->attr as $k => $v) {
  3314. if ($k === 0) {
  3315. // merge in the includes
  3316. // sorry, no way to override an include
  3317. foreach ($v as $v2) {
  3318. $this->attr[0][] = $v2;
  3319. }
  3320. continue;
  3321. }
  3322. if ($v === false) {
  3323. if (isset($this->attr[$k])) {
  3324. unset($this->attr[$k]);
  3325. }
  3326. continue;
  3327. }
  3328. $this->attr[$k] = $v;
  3329. }
  3330. $this->_mergeAssocArray($this->excludes, $def->excludes);
  3331. $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
  3332. $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
  3333. if (!empty($def->content_model)) {
  3334. $this->content_model =
  3335. str_replace("#SUPER", $this->content_model, $def->content_model);
  3336. $this->child = false;
  3337. }
  3338. if (!empty($def->content_model_type)) {
  3339. $this->content_model_type = $def->content_model_type;
  3340. $this->child = false;
  3341. }
  3342. if (!is_null($def->child)) {
  3343. $this->child = $def->child;
  3344. }
  3345. if (!is_null($def->formatting)) {
  3346. $this->formatting = $def->formatting;
  3347. }
  3348. if ($def->descendants_are_inline) {
  3349. $this->descendants_are_inline = $def->descendants_are_inline;
  3350. }
  3351. }
  3352. /**
  3353. * Merges one array into another, removes values which equal false
  3354. * @param $a1 Array by reference that is merged into
  3355. * @param $a2 Array that merges into $a1
  3356. */
  3357. private function _mergeAssocArray(&$a1, $a2)
  3358. {
  3359. foreach ($a2 as $k => $v) {
  3360. if ($v === false) {
  3361. if (isset($a1[$k])) {
  3362. unset($a1[$k]);
  3363. }
  3364. continue;
  3365. }
  3366. $a1[$k] = $v;
  3367. }
  3368. }
  3369. }
  3370. /**
  3371. * A UTF-8 specific character encoder that handles cleaning and transforming.
  3372. * @note All functions in this class should be static.
  3373. */
  3374. class HTMLPurifier_Encoder
  3375. {
  3376. /**
  3377. * Constructor throws fatal error if you attempt to instantiate class
  3378. */
  3379. private function __construct()
  3380. {
  3381. trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
  3382. }
  3383. /**
  3384. * Error-handler that mutes errors, alternative to shut-up operator.
  3385. */
  3386. public static function muteErrorHandler()
  3387. {
  3388. }
  3389. /**
  3390. * iconv wrapper which mutes errors, but doesn't work around bugs.
  3391. * @param string $in Input encoding
  3392. * @param string $out Output encoding
  3393. * @param string $text The text to convert
  3394. * @return string
  3395. */
  3396. public static function unsafeIconv($in, $out, $text)
  3397. {
  3398. set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
  3399. $r = iconv($in, $out, $text);
  3400. restore_error_handler();
  3401. return $r;
  3402. }
  3403. /**
  3404. * iconv wrapper which mutes errors and works around bugs.
  3405. * @param string $in Input encoding
  3406. * @param string $out Output encoding
  3407. * @param string $text The text to convert
  3408. * @param int $max_chunk_size
  3409. * @return string
  3410. */
  3411. public static function iconv($in, $out, $text, $max_chunk_size = 8000)
  3412. {
  3413. $code = self::testIconvTruncateBug();
  3414. if ($code == self::ICONV_OK) {
  3415. return self::unsafeIconv($in, $out, $text);
  3416. } elseif ($code == self::ICONV_TRUNCATES) {
  3417. // we can only work around this if the input character set
  3418. // is utf-8
  3419. if ($in == 'utf-8') {
  3420. if ($max_chunk_size < 4) {
  3421. trigger_error('max_chunk_size is too small', E_USER_WARNING);
  3422. return false;
  3423. }
  3424. // split into 8000 byte chunks, but be careful to handle
  3425. // multibyte boundaries properly
  3426. if (($c = strlen($text)) <= $max_chunk_size) {
  3427. return self::unsafeIconv($in, $out, $text);
  3428. }
  3429. $r = '';
  3430. $i = 0;
  3431. while (true) {
  3432. if ($i + $max_chunk_size >= $c) {
  3433. $r .= self::unsafeIconv($in, $out, substr($text, $i));
  3434. break;
  3435. }
  3436. // wibble the boundary
  3437. if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
  3438. $chunk_size = $max_chunk_size;
  3439. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
  3440. $chunk_size = $max_chunk_size - 1;
  3441. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
  3442. $chunk_size = $max_chunk_size - 2;
  3443. } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
  3444. $chunk_size = $max_chunk_size - 3;
  3445. } else {
  3446. return false; // rather confusing UTF-8...
  3447. }
  3448. $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
  3449. $r .= self::unsafeIconv($in, $out, $chunk);
  3450. $i += $chunk_size;
  3451. }
  3452. return $r;
  3453. } else {
  3454. return false;
  3455. }
  3456. } else {
  3457. return false;
  3458. }
  3459. }
  3460. /**
  3461. * Cleans a UTF-8 string for well-formedness and SGML validity
  3462. *
  3463. * It will parse according to UTF-8 and return a valid UTF8 string, with
  3464. * non-SGML codepoints excluded.
  3465. *
  3466. * @param string $str The string to clean
  3467. * @param bool $force_php
  3468. * @return string
  3469. *
  3470. * @note Just for reference, the non-SGML code points are 0 to 31 and
  3471. * 127 to 159, inclusive. However, we allow code points 9, 10
  3472. * and 13, which are the tab, line feed and carriage return
  3473. * respectively. 128 and above the code points map to multibyte
  3474. * UTF-8 representations.
  3475. *
  3476. * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
  3477. * hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
  3478. * LGPL license. Notes on what changed are inside, but in general,
  3479. * the original code transformed UTF-8 text into an array of integer
  3480. * Unicode codepoints. Understandably, transforming that back to
  3481. * a string would be somewhat expensive, so the function was modded to
  3482. * directly operate on the string. However, this discourages code
  3483. * reuse, and the logic enumerated here would be useful for any
  3484. * function that needs to be able to understand UTF-8 characters.
  3485. * As of right now, only smart lossless character encoding converters
  3486. * would need that, and I'm probably not going to implement them.
  3487. * Once again, PHP 6 should solve all our problems.
  3488. */
  3489. public static function cleanUTF8($str, $force_php = false)
  3490. {
  3491. // UTF-8 validity is checked since PHP 4.3.5
  3492. // This is an optimization: if the string is already valid UTF-8, no
  3493. // need to do PHP stuff. 99% of the time, this will be the case.
  3494. // The regexp matches the XML char production, as well as well as excluding
  3495. // non-SGML codepoints U+007F to U+009F
  3496. if (preg_match(
  3497. '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
  3498. $str
  3499. )) {
  3500. return $str;
  3501. }
  3502. $mState = 0; // cached expected number of octets after the current octet
  3503. // until the beginning of the next UTF8 character sequence
  3504. $mUcs4 = 0; // cached Unicode character
  3505. $mBytes = 1; // cached expected number of octets in the current sequence
  3506. // original code involved an $out that was an array of Unicode
  3507. // codepoints. Instead of having to convert back into UTF-8, we've
  3508. // decided to directly append valid UTF-8 characters onto a string
  3509. // $out once they're done. $char accumulates raw bytes, while $mUcs4
  3510. // turns into the Unicode code point, so there's some redundancy.
  3511. $out = '';
  3512. $char = '';
  3513. $len = strlen($str);
  3514. for ($i = 0; $i < $len; $i++) {
  3515. $in = ord($str{$i});
  3516. $char .= $str[$i]; // append byte to char
  3517. if (0 == $mState) {
  3518. // When mState is zero we expect either a US-ASCII character
  3519. // or a multi-octet sequence.
  3520. if (0 == (0x80 & ($in))) {
  3521. // US-ASCII, pass straight through.
  3522. if (($in <= 31 || $in == 127) &&
  3523. !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
  3524. ) {
  3525. // control characters, remove
  3526. } else {
  3527. $out .= $char;
  3528. }
  3529. // reset
  3530. $char = '';
  3531. $mBytes = 1;
  3532. } elseif (0xC0 == (0xE0 & ($in))) {
  3533. // First octet of 2 octet sequence
  3534. $mUcs4 = ($in);
  3535. $mUcs4 = ($mUcs4 & 0x1F) << 6;
  3536. $mState = 1;
  3537. $mBytes = 2;
  3538. } elseif (0xE0 == (0xF0 & ($in))) {
  3539. // First octet of 3 octet sequence
  3540. $mUcs4 = ($in);
  3541. $mUcs4 = ($mUcs4 & 0x0F) << 12;
  3542. $mState = 2;
  3543. $mBytes = 3;
  3544. } elseif (0xF0 == (0xF8 & ($in))) {
  3545. // First octet of 4 octet sequence
  3546. $mUcs4 = ($in);
  3547. $mUcs4 = ($mUcs4 & 0x07) << 18;
  3548. $mState = 3;
  3549. $mBytes = 4;
  3550. } elseif (0xF8 == (0xFC & ($in))) {
  3551. // First octet of 5 octet sequence.
  3552. //
  3553. // This is illegal because the encoded codepoint must be
  3554. // either:
  3555. // (a) not the shortest form or
  3556. // (b) outside the Unicode range of 0-0x10FFFF.
  3557. // Rather than trying to resynchronize, we will carry on
  3558. // until the end of the sequence and let the later error
  3559. // handling code catch it.
  3560. $mUcs4 = ($in);
  3561. $mUcs4 = ($mUcs4 & 0x03) << 24;
  3562. $mState = 4;
  3563. $mBytes = 5;
  3564. } elseif (0xFC == (0xFE & ($in))) {
  3565. // First octet of 6 octet sequence, see comments for 5
  3566. // octet sequence.
  3567. $mUcs4 = ($in);
  3568. $mUcs4 = ($mUcs4 & 1) << 30;
  3569. $mState = 5;
  3570. $mBytes = 6;
  3571. } else {
  3572. // Current octet is neither in the US-ASCII range nor a
  3573. // legal first octet of a multi-octet sequence.
  3574. $mState = 0;
  3575. $mUcs4 = 0;
  3576. $mBytes = 1;
  3577. $char = '';
  3578. }
  3579. } else {
  3580. // When mState is non-zero, we expect a continuation of the
  3581. // multi-octet sequence
  3582. if (0x80 == (0xC0 & ($in))) {
  3583. // Legal continuation.
  3584. $shift = ($mState - 1) * 6;
  3585. $tmp = $in;
  3586. $tmp = ($tmp & 0x0000003F) << $shift;
  3587. $mUcs4 |= $tmp;
  3588. if (0 == --$mState) {
  3589. // End of the multi-octet sequence. mUcs4 now contains
  3590. // the final Unicode codepoint to be output
  3591. // Check for illegal sequences and codepoints.
  3592. // From Unicode 3.1, non-shortest form is illegal
  3593. if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
  3594. ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
  3595. ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
  3596. (4 < $mBytes) ||
  3597. // From Unicode 3.2, surrogate characters = illegal
  3598. (($mUcs4 & 0xFFFFF800) == 0xD800) ||
  3599. // Codepoints outside the Unicode range are illegal
  3600. ($mUcs4 > 0x10FFFF)
  3601. ) {
  3602. } elseif (0xFEFF != $mUcs4 && // omit BOM
  3603. // check for valid Char unicode codepoints
  3604. (
  3605. 0x9 == $mUcs4 ||
  3606. 0xA == $mUcs4 ||
  3607. 0xD == $mUcs4 ||
  3608. (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
  3609. // 7F-9F is not strictly prohibited by XML,
  3610. // but it is non-SGML, and thus we don't allow it
  3611. (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
  3612. (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
  3613. )
  3614. ) {
  3615. $out .= $char;
  3616. }
  3617. // initialize UTF8 cache (reset)
  3618. $mState = 0;
  3619. $mUcs4 = 0;
  3620. $mBytes = 1;
  3621. $char = '';
  3622. }
  3623. } else {
  3624. // ((0xC0 & (*in) != 0x80) && (mState != 0))
  3625. // Incomplete multi-octet sequence.
  3626. // used to result in complete fail, but we'll reset
  3627. $mState = 0;
  3628. $mUcs4 = 0;
  3629. $mBytes = 1;
  3630. $char ='';
  3631. }
  3632. }
  3633. }
  3634. return $out;
  3635. }
  3636. /**
  3637. * Translates a Unicode codepoint into its corresponding UTF-8 character.
  3638. * @note Based on Feyd's function at
  3639. * <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
  3640. * which is in public domain.
  3641. * @note While we're going to do code point parsing anyway, a good
  3642. * optimization would be to refuse to translate code points that
  3643. * are non-SGML characters. However, this could lead to duplication.
  3644. * @note This is very similar to the unichr function in
  3645. * maintenance/generate-entity-file.php (although this is superior,
  3646. * due to its sanity checks).
  3647. */
  3648. // +----------+----------+----------+----------+
  3649. // | 33222222 | 22221111 | 111111 | |
  3650. // | 10987654 | 32109876 | 54321098 | 76543210 | bit
  3651. // +----------+----------+----------+----------+
  3652. // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
  3653. // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
  3654. // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
  3655. // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
  3656. // +----------+----------+----------+----------+
  3657. // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
  3658. // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
  3659. // +----------+----------+----------+----------+
  3660. public static function unichr($code)
  3661. {
  3662. if ($code > 1114111 or $code < 0 or
  3663. ($code >= 55296 and $code <= 57343) ) {
  3664. // bits are set outside the "valid" range as defined
  3665. // by UNICODE 4.1.0
  3666. return '';
  3667. }
  3668. $x = $y = $z = $w = 0;
  3669. if ($code < 128) {
  3670. // regular ASCII character
  3671. $x = $code;
  3672. } else {
  3673. // set up bits for UTF-8
  3674. $x = ($code & 63) | 128;
  3675. if ($code < 2048) {
  3676. $y = (($code & 2047) >> 6) | 192;
  3677. } else {
  3678. $y = (($code & 4032) >> 6) | 128;
  3679. if ($code < 65536) {
  3680. $z = (($code >> 12) & 15) | 224;
  3681. } else {
  3682. $z = (($code >> 12) & 63) | 128;
  3683. $w = (($code >> 18) & 7) | 240;
  3684. }
  3685. }
  3686. }
  3687. // set up the actual character
  3688. $ret = '';
  3689. if ($w) {
  3690. $ret .= chr($w);
  3691. }
  3692. if ($z) {
  3693. $ret .= chr($z);
  3694. }
  3695. if ($y) {
  3696. $ret .= chr($y);
  3697. }
  3698. $ret .= chr($x);
  3699. return $ret;
  3700. }
  3701. /**
  3702. * @return bool
  3703. */
  3704. public static function iconvAvailable()
  3705. {
  3706. static $iconv = null;
  3707. if ($iconv === null) {
  3708. $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
  3709. }
  3710. return $iconv;
  3711. }
  3712. /**
  3713. * Convert a string to UTF-8 based on configuration.
  3714. * @param string $str The string to convert
  3715. * @param HTMLPurifier_Config $config
  3716. * @param HTMLPurifier_Context $context
  3717. * @return string
  3718. */
  3719. public static function convertToUTF8($str, $config, $context)
  3720. {
  3721. $encoding = $config->get('Core.Encoding');
  3722. if ($encoding === 'utf-8') {
  3723. return $str;
  3724. }
  3725. static $iconv = null;
  3726. if ($iconv === null) {
  3727. $iconv = self::iconvAvailable();
  3728. }
  3729. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3730. // unaffected by bugs, since UTF-8 support all characters
  3731. $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
  3732. if ($str === false) {
  3733. // $encoding is not a valid encoding
  3734. trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
  3735. return '';
  3736. }
  3737. // If the string is bjorked by Shift_JIS or a similar encoding
  3738. // that doesn't support all of ASCII, convert the naughty
  3739. // characters to their true byte-wise ASCII/UTF-8 equivalents.
  3740. $str = strtr($str, self::testEncodingSupportsASCII($encoding));
  3741. return $str;
  3742. } elseif ($encoding === 'iso-8859-1') {
  3743. $str = utf8_encode($str);
  3744. return $str;
  3745. }
  3746. $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
  3747. if ($bug == self::ICONV_OK) {
  3748. trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
  3749. } else {
  3750. trigger_error(
  3751. 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
  3752. 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
  3753. E_USER_ERROR
  3754. );
  3755. }
  3756. }
  3757. /**
  3758. * Converts a string from UTF-8 based on configuration.
  3759. * @param string $str The string to convert
  3760. * @param HTMLPurifier_Config $config
  3761. * @param HTMLPurifier_Context $context
  3762. * @return string
  3763. * @note Currently, this is a lossy conversion, with unexpressable
  3764. * characters being omitted.
  3765. */
  3766. public static function convertFromUTF8($str, $config, $context)
  3767. {
  3768. $encoding = $config->get('Core.Encoding');
  3769. if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
  3770. $str = self::convertToASCIIDumbLossless($str);
  3771. }
  3772. if ($encoding === 'utf-8') {
  3773. return $str;
  3774. }
  3775. static $iconv = null;
  3776. if ($iconv === null) {
  3777. $iconv = self::iconvAvailable();
  3778. }
  3779. if ($iconv && !$config->get('Test.ForceNoIconv')) {
  3780. // Undo our previous fix in convertToUTF8, otherwise iconv will barf
  3781. $ascii_fix = self::testEncodingSupportsASCII($encoding);
  3782. if (!$escape && !empty($ascii_fix)) {
  3783. $clear_fix = array();
  3784. foreach ($ascii_fix as $utf8 => $native) {
  3785. $clear_fix[$utf8] = '';
  3786. }
  3787. $str = strtr($str, $clear_fix);
  3788. }
  3789. $str = strtr($str, array_flip($ascii_fix));
  3790. // Normal stuff
  3791. $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
  3792. return $str;
  3793. } elseif ($encoding === 'iso-8859-1') {
  3794. $str = utf8_decode($str);
  3795. return $str;
  3796. }
  3797. trigger_error('Encoding not supported', E_USER_ERROR);
  3798. // You might be tempted to assume that the ASCII representation
  3799. // might be OK, however, this is *not* universally true over all
  3800. // encodings. So we take the conservative route here, rather
  3801. // than forcibly turn on %Core.EscapeNonASCIICharacters
  3802. }
  3803. /**
  3804. * Lossless (character-wise) conversion of HTML to ASCII
  3805. * @param string $str UTF-8 string to be converted to ASCII
  3806. * @return string ASCII encoded string with non-ASCII character entity-ized
  3807. * @warning Adapted from MediaWiki, claiming fair use: this is a common
  3808. * algorithm. If you disagree with this license fudgery,
  3809. * implement it yourself.
  3810. * @note Uses decimal numeric entities since they are best supported.
  3811. * @note This is a DUMB function: it has no concept of keeping
  3812. * character entities that the projected character encoding
  3813. * can allow. We could possibly implement a smart version
  3814. * but that would require it to also know which Unicode
  3815. * codepoints the charset supported (not an easy task).
  3816. * @note Sort of with cleanUTF8() but it assumes that $str is
  3817. * well-formed UTF-8
  3818. */
  3819. public static function convertToASCIIDumbLossless($str)
  3820. {
  3821. $bytesleft = 0;
  3822. $result = '';
  3823. $working = 0;
  3824. $len = strlen($str);
  3825. for ($i = 0; $i < $len; $i++) {
  3826. $bytevalue = ord($str[$i]);
  3827. if ($bytevalue <= 0x7F) { //0xxx xxxx
  3828. $result .= chr($bytevalue);
  3829. $bytesleft = 0;
  3830. } elseif ($bytevalue <= 0xBF) { //10xx xxxx
  3831. $working = $working << 6;
  3832. $working += ($bytevalue & 0x3F);
  3833. $bytesleft--;
  3834. if ($bytesleft <= 0) {
  3835. $result .= "&#" . $working . ";";
  3836. }
  3837. } elseif ($bytevalue <= 0xDF) { //110x xxxx
  3838. $working = $bytevalue & 0x1F;
  3839. $bytesleft = 1;
  3840. } elseif ($bytevalue <= 0xEF) { //1110 xxxx
  3841. $working = $bytevalue & 0x0F;
  3842. $bytesleft = 2;
  3843. } else { //1111 0xxx
  3844. $working = $bytevalue & 0x07;
  3845. $bytesleft = 3;
  3846. }
  3847. }
  3848. return $result;
  3849. }
  3850. /** No bugs detected in iconv. */
  3851. const ICONV_OK = 0;
  3852. /** Iconv truncates output if converting from UTF-8 to another
  3853. * character set with //IGNORE, and a non-encodable character is found */
  3854. const ICONV_TRUNCATES = 1;
  3855. /** Iconv does not support //IGNORE, making it unusable for
  3856. * transcoding purposes */
  3857. const ICONV_UNUSABLE = 2;
  3858. /**
  3859. * glibc iconv has a known bug where it doesn't handle the magic
  3860. * //IGNORE stanza correctly. In particular, rather than ignore
  3861. * characters, it will return an EILSEQ after consuming some number
  3862. * of characters, and expect you to restart iconv as if it were
  3863. * an E2BIG. Old versions of PHP did not respect the errno, and
  3864. * returned the fragment, so as a result you would see iconv
  3865. * mysteriously truncating output. We can work around this by
  3866. * manually chopping our input into segments of about 8000
  3867. * characters, as long as PHP ignores the error code. If PHP starts
  3868. * paying attention to the error code, iconv becomes unusable.
  3869. *
  3870. * @return int Error code indicating severity of bug.
  3871. */
  3872. public static function testIconvTruncateBug()
  3873. {
  3874. static $code = null;
  3875. if ($code === null) {
  3876. // better not use iconv, otherwise infinite loop!
  3877. $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
  3878. if ($r === false) {
  3879. $code = self::ICONV_UNUSABLE;
  3880. } elseif (($c = strlen($r)) < 9000) {
  3881. $code = self::ICONV_TRUNCATES;
  3882. } elseif ($c > 9000) {
  3883. trigger_error(
  3884. 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
  3885. 'include your iconv version as per phpversion()',
  3886. E_USER_ERROR
  3887. );
  3888. } else {
  3889. $code = self::ICONV_OK;
  3890. }
  3891. }
  3892. return $code;
  3893. }
  3894. /**
  3895. * This expensive function tests whether or not a given character
  3896. * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
  3897. * fail this test, and require special processing. Variable width
  3898. * encodings shouldn't ever fail.
  3899. *
  3900. * @param string $encoding Encoding name to test, as per iconv format
  3901. * @param bool $bypass Whether or not to bypass the precompiled arrays.
  3902. * @return Array of UTF-8 characters to their corresponding ASCII,
  3903. * which can be used to "undo" any overzealous iconv action.
  3904. */
  3905. public static function testEncodingSupportsASCII($encoding, $bypass = false)
  3906. {
  3907. // All calls to iconv here are unsafe, proof by case analysis:
  3908. // If ICONV_OK, no difference.
  3909. // If ICONV_TRUNCATE, all calls involve one character inputs,
  3910. // so bug is not triggered.
  3911. // If ICONV_UNUSABLE, this call is irrelevant
  3912. static $encodings = array();
  3913. if (!$bypass) {
  3914. if (isset($encodings[$encoding])) {
  3915. return $encodings[$encoding];
  3916. }
  3917. $lenc = strtolower($encoding);
  3918. switch ($lenc) {
  3919. case 'shift_jis':
  3920. return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
  3921. case 'johab':
  3922. return array("\xE2\x82\xA9" => '\\');
  3923. }
  3924. if (strpos($lenc, 'iso-8859-') === 0) {
  3925. return array();
  3926. }
  3927. }
  3928. $ret = array();
  3929. if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
  3930. return false;
  3931. }
  3932. for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
  3933. $c = chr($i); // UTF-8 char
  3934. $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
  3935. if ($r === '' ||
  3936. // This line is needed for iconv implementations that do not
  3937. // omit characters that do not exist in the target character set
  3938. ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
  3939. ) {
  3940. // Reverse engineer: what's the UTF-8 equiv of this byte
  3941. // sequence? This assumes that there's no variable width
  3942. // encoding that doesn't support ASCII.
  3943. $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
  3944. }
  3945. }
  3946. $encodings[$encoding] = $ret;
  3947. return $ret;
  3948. }
  3949. }
  3950. /**
  3951. * Object that provides entity lookup table from entity name to character
  3952. */
  3953. class HTMLPurifier_EntityLookup
  3954. {
  3955. /**
  3956. * Assoc array of entity name to character represented.
  3957. * @type array
  3958. */
  3959. public $table;
  3960. /**
  3961. * Sets up the entity lookup table from the serialized file contents.
  3962. * @param bool $file
  3963. * @note The serialized contents are versioned, but were generated
  3964. * using the maintenance script generate_entity_file.php
  3965. * @warning This is not in constructor to help enforce the Singleton
  3966. */
  3967. public function setup($file = false)
  3968. {
  3969. if (!$file) {
  3970. $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
  3971. }
  3972. $this->table = unserialize(file_get_contents($file));
  3973. }
  3974. /**
  3975. * Retrieves sole instance of the object.
  3976. * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
  3977. * @return HTMLPurifier_EntityLookup
  3978. */
  3979. public static function instance($prototype = false)
  3980. {
  3981. // no references, since PHP doesn't copy unless modified
  3982. static $instance = null;
  3983. if ($prototype) {
  3984. $instance = $prototype;
  3985. } elseif (!$instance) {
  3986. $instance = new HTMLPurifier_EntityLookup();
  3987. $instance->setup();
  3988. }
  3989. return $instance;
  3990. }
  3991. }
  3992. // if want to implement error collecting here, we'll need to use some sort
  3993. // of global data (probably trigger_error) because it's impossible to pass
  3994. // $config or $context to the callback functions.
  3995. /**
  3996. * Handles referencing and derefencing character entities
  3997. */
  3998. class HTMLPurifier_EntityParser
  3999. {
  4000. /**
  4001. * Reference to entity lookup table.
  4002. * @type HTMLPurifier_EntityLookup
  4003. */
  4004. protected $_entity_lookup;
  4005. /**
  4006. * Callback regex string for parsing entities.
  4007. * @type string
  4008. */
  4009. protected $_substituteEntitiesRegex =
  4010. '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
  4011. // 1. hex 2. dec 3. string (XML style)
  4012. /**
  4013. * Decimal to parsed string conversion table for special entities.
  4014. * @type array
  4015. */
  4016. protected $_special_dec2str =
  4017. array(
  4018. 34 => '"',
  4019. 38 => '&',
  4020. 39 => "'",
  4021. 60 => '<',
  4022. 62 => '>'
  4023. );
  4024. /**
  4025. * Stripped entity names to decimal conversion table for special entities.
  4026. * @type array
  4027. */
  4028. protected $_special_ent2dec =
  4029. array(
  4030. 'quot' => 34,
  4031. 'amp' => 38,
  4032. 'lt' => 60,
  4033. 'gt' => 62
  4034. );
  4035. /**
  4036. * Substitutes non-special entities with their parsed equivalents. Since
  4037. * running this whenever you have parsed character is t3h 5uck, we run
  4038. * it before everything else.
  4039. *
  4040. * @param string $string String to have non-special entities parsed.
  4041. * @return string Parsed string.
  4042. */
  4043. public function substituteNonSpecialEntities($string)
  4044. {
  4045. // it will try to detect missing semicolons, but don't rely on it
  4046. return preg_replace_callback(
  4047. $this->_substituteEntitiesRegex,
  4048. array($this, 'nonSpecialEntityCallback'),
  4049. $string
  4050. );
  4051. }
  4052. /**
  4053. * Callback function for substituteNonSpecialEntities() that does the work.
  4054. *
  4055. * @param array $matches PCRE matches array, with 0 the entire match, and
  4056. * either index 1, 2 or 3 set with a hex value, dec value,
  4057. * or string (respectively).
  4058. * @return string Replacement string.
  4059. */
  4060. protected function nonSpecialEntityCallback($matches)
  4061. {
  4062. // replaces all but big five
  4063. $entity = $matches[0];
  4064. $is_num = (@$matches[0][1] === '#');
  4065. if ($is_num) {
  4066. $is_hex = (@$entity[2] === 'x');
  4067. $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4068. // abort for special characters
  4069. if (isset($this->_special_dec2str[$code])) {
  4070. return $entity;
  4071. }
  4072. return HTMLPurifier_Encoder::unichr($code);
  4073. } else {
  4074. if (isset($this->_special_ent2dec[$matches[3]])) {
  4075. return $entity;
  4076. }
  4077. if (!$this->_entity_lookup) {
  4078. $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
  4079. }
  4080. if (isset($this->_entity_lookup->table[$matches[3]])) {
  4081. return $this->_entity_lookup->table[$matches[3]];
  4082. } else {
  4083. return $entity;
  4084. }
  4085. }
  4086. }
  4087. /**
  4088. * Substitutes only special entities with their parsed equivalents.
  4089. *
  4090. * @notice We try to avoid calling this function because otherwise, it
  4091. * would have to be called a lot (for every parsed section).
  4092. *
  4093. * @param string $string String to have non-special entities parsed.
  4094. * @return string Parsed string.
  4095. */
  4096. public function substituteSpecialEntities($string)
  4097. {
  4098. return preg_replace_callback(
  4099. $this->_substituteEntitiesRegex,
  4100. array($this, 'specialEntityCallback'),
  4101. $string
  4102. );
  4103. }
  4104. /**
  4105. * Callback function for substituteSpecialEntities() that does the work.
  4106. *
  4107. * This callback has same syntax as nonSpecialEntityCallback().
  4108. *
  4109. * @param array $matches PCRE-style matches array, with 0 the entire match, and
  4110. * either index 1, 2 or 3 set with a hex value, dec value,
  4111. * or string (respectively).
  4112. * @return string Replacement string.
  4113. */
  4114. protected function specialEntityCallback($matches)
  4115. {
  4116. $entity = $matches[0];
  4117. $is_num = (@$matches[0][1] === '#');
  4118. if ($is_num) {
  4119. $is_hex = (@$entity[2] === 'x');
  4120. $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
  4121. return isset($this->_special_dec2str[$int]) ?
  4122. $this->_special_dec2str[$int] :
  4123. $entity;
  4124. } else {
  4125. return isset($this->_special_ent2dec[$matches[3]]) ?
  4126. $this->_special_ent2dec[$matches[3]] :
  4127. $entity;
  4128. }
  4129. }
  4130. }
  4131. /**
  4132. * Error collection class that enables HTML Purifier to report HTML
  4133. * problems back to the user
  4134. */
  4135. class HTMLPurifier_ErrorCollector
  4136. {
  4137. /**
  4138. * Identifiers for the returned error array. These are purposely numeric
  4139. * so list() can be used.
  4140. */
  4141. const LINENO = 0;
  4142. const SEVERITY = 1;
  4143. const MESSAGE = 2;
  4144. const CHILDREN = 3;
  4145. /**
  4146. * @type array
  4147. */
  4148. protected $errors;
  4149. /**
  4150. * @type array
  4151. */
  4152. protected $_current;
  4153. /**
  4154. * @type array
  4155. */
  4156. protected $_stacks = array(array());
  4157. /**
  4158. * @type HTMLPurifier_Language
  4159. */
  4160. protected $locale;
  4161. /**
  4162. * @type HTMLPurifier_Generator
  4163. */
  4164. protected $generator;
  4165. /**
  4166. * @type HTMLPurifier_Context
  4167. */
  4168. protected $context;
  4169. /**
  4170. * @type array
  4171. */
  4172. protected $lines = array();
  4173. /**
  4174. * @param HTMLPurifier_Context $context
  4175. */
  4176. public function __construct($context)
  4177. {
  4178. $this->locale =& $context->get('Locale');
  4179. $this->context = $context;
  4180. $this->_current =& $this->_stacks[0];
  4181. $this->errors =& $this->_stacks[0];
  4182. }
  4183. /**
  4184. * Sends an error message to the collector for later use
  4185. * @param int $severity Error severity, PHP error style (don't use E_USER_)
  4186. * @param string $msg Error message text
  4187. */
  4188. public function send($severity, $msg)
  4189. {
  4190. $args = array();
  4191. if (func_num_args() > 2) {
  4192. $args = func_get_args();
  4193. array_shift($args);
  4194. unset($args[0]);
  4195. }
  4196. $token = $this->context->get('CurrentToken', true);
  4197. $line = $token ? $token->line : $this->context->get('CurrentLine', true);
  4198. $col = $token ? $token->col : $this->context->get('CurrentCol', true);
  4199. $attr = $this->context->get('CurrentAttr', true);
  4200. // perform special substitutions, also add custom parameters
  4201. $subst = array();
  4202. if (!is_null($token)) {
  4203. $args['CurrentToken'] = $token;
  4204. }
  4205. if (!is_null($attr)) {
  4206. $subst['$CurrentAttr.Name'] = $attr;
  4207. if (isset($token->attr[$attr])) {
  4208. $subst['$CurrentAttr.Value'] = $token->attr[$attr];
  4209. }
  4210. }
  4211. if (empty($args)) {
  4212. $msg = $this->locale->getMessage($msg);
  4213. } else {
  4214. $msg = $this->locale->formatMessage($msg, $args);
  4215. }
  4216. if (!empty($subst)) {
  4217. $msg = strtr($msg, $subst);
  4218. }
  4219. // (numerically indexed)
  4220. $error = array(
  4221. self::LINENO => $line,
  4222. self::SEVERITY => $severity,
  4223. self::MESSAGE => $msg,
  4224. self::CHILDREN => array()
  4225. );
  4226. $this->_current[] = $error;
  4227. // NEW CODE BELOW ...
  4228. // Top-level errors are either:
  4229. // TOKEN type, if $value is set appropriately, or
  4230. // "syntax" type, if $value is null
  4231. $new_struct = new HTMLPurifier_ErrorStruct();
  4232. $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
  4233. if ($token) {
  4234. $new_struct->value = clone $token;
  4235. }
  4236. if (is_int($line) && is_int($col)) {
  4237. if (isset($this->lines[$line][$col])) {
  4238. $struct = $this->lines[$line][$col];
  4239. } else {
  4240. $struct = $this->lines[$line][$col] = $new_struct;
  4241. }
  4242. // These ksorts may present a performance problem
  4243. ksort($this->lines[$line], SORT_NUMERIC);
  4244. } else {
  4245. if (isset($this->lines[-1])) {
  4246. $struct = $this->lines[-1];
  4247. } else {
  4248. $struct = $this->lines[-1] = $new_struct;
  4249. }
  4250. }
  4251. ksort($this->lines, SORT_NUMERIC);
  4252. // Now, check if we need to operate on a lower structure
  4253. if (!empty($attr)) {
  4254. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
  4255. if (!$struct->value) {
  4256. $struct->value = array($attr, 'PUT VALUE HERE');
  4257. }
  4258. }
  4259. if (!empty($cssprop)) {
  4260. $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
  4261. if (!$struct->value) {
  4262. // if we tokenize CSS this might be a little more difficult to do
  4263. $struct->value = array($cssprop, 'PUT VALUE HERE');
  4264. }
  4265. }
  4266. // Ok, structs are all setup, now time to register the error
  4267. $struct->addError($severity, $msg);
  4268. }
  4269. /**
  4270. * Retrieves raw error data for custom formatter to use
  4271. */
  4272. public function getRaw()
  4273. {
  4274. return $this->errors;
  4275. }
  4276. /**
  4277. * Default HTML formatting implementation for error messages
  4278. * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
  4279. * @param array $errors Errors array to display; used for recursion.
  4280. * @return string
  4281. */
  4282. public function getHTMLFormatted($config, $errors = null)
  4283. {
  4284. $ret = array();
  4285. $this->generator = new HTMLPurifier_Generator($config, $this->context);
  4286. if ($errors === null) {
  4287. $errors = $this->errors;
  4288. }
  4289. // 'At line' message needs to be removed
  4290. // generation code for new structure goes here. It needs to be recursive.
  4291. foreach ($this->lines as $line => $col_array) {
  4292. if ($line == -1) {
  4293. continue;
  4294. }
  4295. foreach ($col_array as $col => $struct) {
  4296. $this->_renderStruct($ret, $struct, $line, $col);
  4297. }
  4298. }
  4299. if (isset($this->lines[-1])) {
  4300. $this->_renderStruct($ret, $this->lines[-1]);
  4301. }
  4302. if (empty($errors)) {
  4303. return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
  4304. } else {
  4305. return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
  4306. }
  4307. }
  4308. private function _renderStruct(&$ret, $struct, $line = null, $col = null)
  4309. {
  4310. $stack = array($struct);
  4311. $context_stack = array(array());
  4312. while ($current = array_pop($stack)) {
  4313. $context = array_pop($context_stack);
  4314. foreach ($current->errors as $error) {
  4315. list($severity, $msg) = $error;
  4316. $string = '';
  4317. $string .= '<div>';
  4318. // W3C uses an icon to indicate the severity of the error.
  4319. $error = $this->locale->getErrorName($severity);
  4320. $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
  4321. if (!is_null($line) && !is_null($col)) {
  4322. $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
  4323. } else {
  4324. $string .= '<em class="location">End of Document: </em> ';
  4325. }
  4326. $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
  4327. $string .= '</div>';
  4328. // Here, have a marker for the character on the column appropriate.
  4329. // Be sure to clip extremely long lines.
  4330. //$string .= '<pre>';
  4331. //$string .= '';
  4332. //$string .= '</pre>';
  4333. $ret[] = $string;
  4334. }
  4335. foreach ($current->children as $array) {
  4336. $context[] = $current;
  4337. $stack = array_merge($stack, array_reverse($array, true));
  4338. for ($i = count($array); $i > 0; $i--) {
  4339. $context_stack[] = $context;
  4340. }
  4341. }
  4342. }
  4343. }
  4344. }
  4345. /**
  4346. * Records errors for particular segments of an HTML document such as tokens,
  4347. * attributes or CSS properties. They can contain error structs (which apply
  4348. * to components of what they represent), but their main purpose is to hold
  4349. * errors applying to whatever struct is being used.
  4350. */
  4351. class HTMLPurifier_ErrorStruct
  4352. {
  4353. /**
  4354. * Possible values for $children first-key. Note that top-level structures
  4355. * are automatically token-level.
  4356. */
  4357. const TOKEN = 0;
  4358. const ATTR = 1;
  4359. const CSSPROP = 2;
  4360. /**
  4361. * Type of this struct.
  4362. * @type string
  4363. */
  4364. public $type;
  4365. /**
  4366. * Value of the struct we are recording errors for. There are various
  4367. * values for this:
  4368. * - TOKEN: Instance of HTMLPurifier_Token
  4369. * - ATTR: array('attr-name', 'value')
  4370. * - CSSPROP: array('prop-name', 'value')
  4371. * @type mixed
  4372. */
  4373. public $value;
  4374. /**
  4375. * Errors registered for this structure.
  4376. * @type array
  4377. */
  4378. public $errors = array();
  4379. /**
  4380. * Child ErrorStructs that are from this structure. For example, a TOKEN
  4381. * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
  4382. * array in structure: [TYPE]['identifier']
  4383. * @type array
  4384. */
  4385. public $children = array();
  4386. /**
  4387. * @param string $type
  4388. * @param string $id
  4389. * @return mixed
  4390. */
  4391. public function getChild($type, $id)
  4392. {
  4393. if (!isset($this->children[$type][$id])) {
  4394. $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
  4395. $this->children[$type][$id]->type = $type;
  4396. }
  4397. return $this->children[$type][$id];
  4398. }
  4399. /**
  4400. * @param int $severity
  4401. * @param string $message
  4402. */
  4403. public function addError($severity, $message)
  4404. {
  4405. $this->errors[] = array($severity, $message);
  4406. }
  4407. }
  4408. /**
  4409. * Global exception class for HTML Purifier; any exceptions we throw
  4410. * are from here.
  4411. */
  4412. class HTMLPurifier_Exception extends Exception
  4413. {
  4414. }
  4415. /**
  4416. * Represents a pre or post processing filter on HTML Purifier's output
  4417. *
  4418. * Sometimes, a little ad-hoc fixing of HTML has to be done before
  4419. * it gets sent through HTML Purifier: you can use filters to acheive
  4420. * this effect. For instance, YouTube videos can be preserved using
  4421. * this manner. You could have used a decorator for this task, but
  4422. * PHP's support for them is not terribly robust, so we're going
  4423. * to just loop through the filters.
  4424. *
  4425. * Filters should be exited first in, last out. If there are three filters,
  4426. * named 1, 2 and 3, the order of execution should go 1->preFilter,
  4427. * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
  4428. * 1->postFilter.
  4429. *
  4430. * @note Methods are not declared abstract as it is perfectly legitimate
  4431. * for an implementation not to want anything to happen on a step
  4432. */
  4433. class HTMLPurifier_Filter
  4434. {
  4435. /**
  4436. * Name of the filter for identification purposes.
  4437. * @type string
  4438. */
  4439. public $name;
  4440. /**
  4441. * Pre-processor function, handles HTML before HTML Purifier
  4442. * @param string $html
  4443. * @param HTMLPurifier_Config $config
  4444. * @param HTMLPurifier_Context $context
  4445. * @return string
  4446. */
  4447. public function preFilter($html, $config, $context)
  4448. {
  4449. return $html;
  4450. }
  4451. /**
  4452. * Post-processor function, handles HTML after HTML Purifier
  4453. * @param string $html
  4454. * @param HTMLPurifier_Config $config
  4455. * @param HTMLPurifier_Context $context
  4456. * @return string
  4457. */
  4458. public function postFilter($html, $config, $context)
  4459. {
  4460. return $html;
  4461. }
  4462. }
  4463. /**
  4464. * Generates HTML from tokens.
  4465. * @todo Refactor interface so that configuration/context is determined
  4466. * upon instantiation, no need for messy generateFromTokens() calls
  4467. * @todo Make some of the more internal functions protected, and have
  4468. * unit tests work around that
  4469. */
  4470. class HTMLPurifier_Generator
  4471. {
  4472. /**
  4473. * Whether or not generator should produce XML output.
  4474. * @type bool
  4475. */
  4476. private $_xhtml = true;
  4477. /**
  4478. * :HACK: Whether or not generator should comment the insides of <script> tags.
  4479. * @type bool
  4480. */
  4481. private $_scriptFix = false;
  4482. /**
  4483. * Cache of HTMLDefinition during HTML output to determine whether or
  4484. * not attributes should be minimized.
  4485. * @type HTMLPurifier_HTMLDefinition
  4486. */
  4487. private $_def;
  4488. /**
  4489. * Cache of %Output.SortAttr.
  4490. * @type bool
  4491. */
  4492. private $_sortAttr;
  4493. /**
  4494. * Cache of %Output.FlashCompat.
  4495. * @type bool
  4496. */
  4497. private $_flashCompat;
  4498. /**
  4499. * Cache of %Output.FixInnerHTML.
  4500. * @type bool
  4501. */
  4502. private $_innerHTMLFix;
  4503. /**
  4504. * Stack for keeping track of object information when outputting IE
  4505. * compatibility code.
  4506. * @type array
  4507. */
  4508. private $_flashStack = array();
  4509. /**
  4510. * Configuration for the generator
  4511. * @type HTMLPurifier_Config
  4512. */
  4513. protected $config;
  4514. /**
  4515. * @param HTMLPurifier_Config $config
  4516. * @param HTMLPurifier_Context $context
  4517. */
  4518. public function __construct($config, $context)
  4519. {
  4520. $this->config = $config;
  4521. $this->_scriptFix = $config->get('Output.CommentScriptContents');
  4522. $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
  4523. $this->_sortAttr = $config->get('Output.SortAttr');
  4524. $this->_flashCompat = $config->get('Output.FlashCompat');
  4525. $this->_def = $config->getHTMLDefinition();
  4526. $this->_xhtml = $this->_def->doctype->xml;
  4527. }
  4528. /**
  4529. * Generates HTML from an array of tokens.
  4530. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
  4531. * @return string Generated HTML
  4532. */
  4533. public function generateFromTokens($tokens)
  4534. {
  4535. if (!$tokens) {
  4536. return '';
  4537. }
  4538. // Basic algorithm
  4539. $html = '';
  4540. for ($i = 0, $size = count($tokens); $i < $size; $i++) {
  4541. if ($this->_scriptFix && $tokens[$i]->name === 'script'
  4542. && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
  4543. // script special case
  4544. // the contents of the script block must be ONE token
  4545. // for this to work.
  4546. $html .= $this->generateFromToken($tokens[$i++]);
  4547. $html .= $this->generateScriptFromToken($tokens[$i++]);
  4548. }
  4549. $html .= $this->generateFromToken($tokens[$i]);
  4550. }
  4551. // Tidy cleanup
  4552. if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
  4553. $tidy = new Tidy;
  4554. $tidy->parseString(
  4555. $html,
  4556. array(
  4557. 'indent'=> true,
  4558. 'output-xhtml' => $this->_xhtml,
  4559. 'show-body-only' => true,
  4560. 'indent-spaces' => 2,
  4561. 'wrap' => 68,
  4562. ),
  4563. 'utf8'
  4564. );
  4565. $tidy->cleanRepair();
  4566. $html = (string) $tidy; // explicit cast necessary
  4567. }
  4568. // Normalize newlines to system defined value
  4569. if ($this->config->get('Core.NormalizeNewlines')) {
  4570. $nl = $this->config->get('Output.Newline');
  4571. if ($nl === null) {
  4572. $nl = PHP_EOL;
  4573. }
  4574. if ($nl !== "\n") {
  4575. $html = str_replace("\n", $nl, $html);
  4576. }
  4577. }
  4578. return $html;
  4579. }
  4580. /**
  4581. * Generates HTML from a single token.
  4582. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4583. * @return string Generated HTML
  4584. */
  4585. public function generateFromToken($token)
  4586. {
  4587. if (!$token instanceof HTMLPurifier_Token) {
  4588. trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
  4589. return '';
  4590. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  4591. $attr = $this->generateAttributes($token->attr, $token->name);
  4592. if ($this->_flashCompat) {
  4593. if ($token->name == "object") {
  4594. $flash = new stdclass();
  4595. $flash->attr = $token->attr;
  4596. $flash->param = array();
  4597. $this->_flashStack[] = $flash;
  4598. }
  4599. }
  4600. return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
  4601. } elseif ($token instanceof HTMLPurifier_Token_End) {
  4602. $_extra = '';
  4603. if ($this->_flashCompat) {
  4604. if ($token->name == "object" && !empty($this->_flashStack)) {
  4605. // doesn't do anything for now
  4606. }
  4607. }
  4608. return $_extra . '</' . $token->name . '>';
  4609. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  4610. if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
  4611. $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
  4612. }
  4613. $attr = $this->generateAttributes($token->attr, $token->name);
  4614. return '<' . $token->name . ($attr ? ' ' : '') . $attr .
  4615. ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
  4616. . '>';
  4617. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  4618. return $this->escape($token->data, ENT_NOQUOTES);
  4619. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  4620. return '<!--' . $token->data . '-->';
  4621. } else {
  4622. return '';
  4623. }
  4624. }
  4625. /**
  4626. * Special case processor for the contents of script tags
  4627. * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  4628. * @return string
  4629. * @warning This runs into problems if there's already a literal
  4630. * --> somewhere inside the script contents.
  4631. */
  4632. public function generateScriptFromToken($token)
  4633. {
  4634. if (!$token instanceof HTMLPurifier_Token_Text) {
  4635. return $this->generateFromToken($token);
  4636. }
  4637. // Thanks <http://lachy.id.au/log/2005/05/script-comments>
  4638. $data = preg_replace('#//\s*$#', '', $token->data);
  4639. return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
  4640. }
  4641. /**
  4642. * Generates attribute declarations from attribute array.
  4643. * @note This does not include the leading or trailing space.
  4644. * @param array $assoc_array_of_attributes Attribute array
  4645. * @param string $element Name of element attributes are for, used to check
  4646. * attribute minimization.
  4647. * @return string Generated HTML fragment for insertion.
  4648. */
  4649. public function generateAttributes($assoc_array_of_attributes, $element = '')
  4650. {
  4651. $html = '';
  4652. if ($this->_sortAttr) {
  4653. ksort($assoc_array_of_attributes);
  4654. }
  4655. foreach ($assoc_array_of_attributes as $key => $value) {
  4656. if (!$this->_xhtml) {
  4657. // Remove namespaced attributes
  4658. if (strpos($key, ':') !== false) {
  4659. continue;
  4660. }
  4661. // Check if we should minimize the attribute: val="val" -> val
  4662. if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
  4663. $html .= $key . ' ';
  4664. continue;
  4665. }
  4666. }
  4667. // Workaround for Internet Explorer innerHTML bug.
  4668. // Essentially, Internet Explorer, when calculating
  4669. // innerHTML, omits quotes if there are no instances of
  4670. // angled brackets, quotes or spaces. However, when parsing
  4671. // HTML (for example, when you assign to innerHTML), it
  4672. // treats backticks as quotes. Thus,
  4673. // <img alt="``" />
  4674. // becomes
  4675. // <img alt=`` />
  4676. // becomes
  4677. // <img alt='' />
  4678. // Fortunately, all we need to do is trigger an appropriate
  4679. // quoting style, which we do by adding an extra space.
  4680. // This also is consistent with the W3C spec, which states
  4681. // that user agents may ignore leading or trailing
  4682. // whitespace (in fact, most don't, at least for attributes
  4683. // like alt, but an extra space at the end is barely
  4684. // noticeable). Still, we have a configuration knob for
  4685. // this, since this transformation is not necesary if you
  4686. // don't process user input with innerHTML or you don't plan
  4687. // on supporting Internet Explorer.
  4688. if ($this->_innerHTMLFix) {
  4689. if (strpos($value, '`') !== false) {
  4690. // check if correct quoting style would not already be
  4691. // triggered
  4692. if (strcspn($value, '"\' <>') === strlen($value)) {
  4693. // protect!
  4694. $value .= ' ';
  4695. }
  4696. }
  4697. }
  4698. $html .= $key.'="'.$this->escape($value).'" ';
  4699. }
  4700. return rtrim($html);
  4701. }
  4702. /**
  4703. * Escapes raw text data.
  4704. * @todo This really ought to be protected, but until we have a facility
  4705. * for properly generating HTML here w/o using tokens, it stays
  4706. * public.
  4707. * @param string $string String data to escape for HTML.
  4708. * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
  4709. * permissible for non-attribute output.
  4710. * @return string escaped data.
  4711. */
  4712. public function escape($string, $quote = null)
  4713. {
  4714. // Workaround for APC bug on Mac Leopard reported by sidepodcast
  4715. // http://htmlpurifier.org/phorum/read.php?3,4823,4846
  4716. if ($quote === null) {
  4717. $quote = ENT_COMPAT;
  4718. }
  4719. return htmlspecialchars($string, $quote, 'UTF-8');
  4720. }
  4721. }
  4722. /**
  4723. * Definition of the purified HTML that describes allowed children,
  4724. * attributes, and many other things.
  4725. *
  4726. * Conventions:
  4727. *
  4728. * All member variables that are prefixed with info
  4729. * (including the main $info array) are used by HTML Purifier internals
  4730. * and should not be directly edited when customizing the HTMLDefinition.
  4731. * They can usually be set via configuration directives or custom
  4732. * modules.
  4733. *
  4734. * On the other hand, member variables without the info prefix are used
  4735. * internally by the HTMLDefinition and MUST NOT be used by other HTML
  4736. * Purifier internals. Many of them, however, are public, and may be
  4737. * edited by userspace code to tweak the behavior of HTMLDefinition.
  4738. *
  4739. * @note This class is inspected by Printer_HTMLDefinition; please
  4740. * update that class if things here change.
  4741. *
  4742. * @warning Directives that change this object's structure must be in
  4743. * the HTML or Attr namespace!
  4744. */
  4745. class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
  4746. {
  4747. // FULLY-PUBLIC VARIABLES ---------------------------------------------
  4748. /**
  4749. * Associative array of element names to HTMLPurifier_ElementDef.
  4750. * @type HTMLPurifier_ElementDef[]
  4751. */
  4752. public $info = array();
  4753. /**
  4754. * Associative array of global attribute name to attribute definition.
  4755. * @type array
  4756. */
  4757. public $info_global_attr = array();
  4758. /**
  4759. * String name of parent element HTML will be going into.
  4760. * @type string
  4761. */
  4762. public $info_parent = 'div';
  4763. /**
  4764. * Definition for parent element, allows parent element to be a
  4765. * tag that's not allowed inside the HTML fragment.
  4766. * @type HTMLPurifier_ElementDef
  4767. */
  4768. public $info_parent_def;
  4769. /**
  4770. * String name of element used to wrap inline elements in block context.
  4771. * @type string
  4772. * @note This is rarely used except for BLOCKQUOTEs in strict mode
  4773. */
  4774. public $info_block_wrapper = 'p';
  4775. /**
  4776. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  4777. * @type array
  4778. */
  4779. public $info_tag_transform = array();
  4780. /**
  4781. * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
  4782. * @type HTMLPurifier_AttrTransform[]
  4783. */
  4784. public $info_attr_transform_pre = array();
  4785. /**
  4786. * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
  4787. * @type HTMLPurifier_AttrTransform[]
  4788. */
  4789. public $info_attr_transform_post = array();
  4790. /**
  4791. * Nested lookup array of content set name (Block, Inline) to
  4792. * element name to whether or not it belongs in that content set.
  4793. * @type array
  4794. */
  4795. public $info_content_sets = array();
  4796. /**
  4797. * Indexed list of HTMLPurifier_Injector to be used.
  4798. * @type HTMLPurifier_Injector[]
  4799. */
  4800. public $info_injector = array();
  4801. /**
  4802. * Doctype object
  4803. * @type HTMLPurifier_Doctype
  4804. */
  4805. public $doctype;
  4806. // RAW CUSTOMIZATION STUFF --------------------------------------------
  4807. /**
  4808. * Adds a custom attribute to a pre-existing element
  4809. * @note This is strictly convenience, and does not have a corresponding
  4810. * method in HTMLPurifier_HTMLModule
  4811. * @param string $element_name Element name to add attribute to
  4812. * @param string $attr_name Name of attribute
  4813. * @param mixed $def Attribute definition, can be string or object, see
  4814. * HTMLPurifier_AttrTypes for details
  4815. */
  4816. public function addAttribute($element_name, $attr_name, $def)
  4817. {
  4818. $module = $this->getAnonymousModule();
  4819. if (!isset($module->info[$element_name])) {
  4820. $element = $module->addBlankElement($element_name);
  4821. } else {
  4822. $element = $module->info[$element_name];
  4823. }
  4824. $element->attr[$attr_name] = $def;
  4825. }
  4826. /**
  4827. * Adds a custom element to your HTML definition
  4828. * @see HTMLPurifier_HTMLModule::addElement() for detailed
  4829. * parameter and return value descriptions.
  4830. */
  4831. public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
  4832. {
  4833. $module = $this->getAnonymousModule();
  4834. // assume that if the user is calling this, the element
  4835. // is safe. This may not be a good idea
  4836. $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
  4837. return $element;
  4838. }
  4839. /**
  4840. * Adds a blank element to your HTML definition, for overriding
  4841. * existing behavior
  4842. * @param string $element_name
  4843. * @return HTMLPurifier_ElementDef
  4844. * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
  4845. * parameter and return value descriptions.
  4846. */
  4847. public function addBlankElement($element_name)
  4848. {
  4849. $module = $this->getAnonymousModule();
  4850. $element = $module->addBlankElement($element_name);
  4851. return $element;
  4852. }
  4853. /**
  4854. * Retrieves a reference to the anonymous module, so you can
  4855. * bust out advanced features without having to make your own
  4856. * module.
  4857. * @return HTMLPurifier_HTMLModule
  4858. */
  4859. public function getAnonymousModule()
  4860. {
  4861. if (!$this->_anonModule) {
  4862. $this->_anonModule = new HTMLPurifier_HTMLModule();
  4863. $this->_anonModule->name = 'Anonymous';
  4864. }
  4865. return $this->_anonModule;
  4866. }
  4867. private $_anonModule = null;
  4868. // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
  4869. /**
  4870. * @type string
  4871. */
  4872. public $type = 'HTML';
  4873. /**
  4874. * @type HTMLPurifier_HTMLModuleManager
  4875. */
  4876. public $manager;
  4877. /**
  4878. * Performs low-cost, preliminary initialization.
  4879. */
  4880. public function __construct()
  4881. {
  4882. $this->manager = new HTMLPurifier_HTMLModuleManager();
  4883. }
  4884. /**
  4885. * @param HTMLPurifier_Config $config
  4886. */
  4887. protected function doSetup($config)
  4888. {
  4889. $this->processModules($config);
  4890. $this->setupConfigStuff($config);
  4891. unset($this->manager);
  4892. // cleanup some of the element definitions
  4893. foreach ($this->info as $k => $v) {
  4894. unset($this->info[$k]->content_model);
  4895. unset($this->info[$k]->content_model_type);
  4896. }
  4897. }
  4898. /**
  4899. * Extract out the information from the manager
  4900. * @param HTMLPurifier_Config $config
  4901. */
  4902. protected function processModules($config)
  4903. {
  4904. if ($this->_anonModule) {
  4905. // for user specific changes
  4906. // this is late-loaded so we don't have to deal with PHP4
  4907. // reference wonky-ness
  4908. $this->manager->addModule($this->_anonModule);
  4909. unset($this->_anonModule);
  4910. }
  4911. $this->manager->setup($config);
  4912. $this->doctype = $this->manager->doctype;
  4913. foreach ($this->manager->modules as $module) {
  4914. foreach ($module->info_tag_transform as $k => $v) {
  4915. if ($v === false) {
  4916. unset($this->info_tag_transform[$k]);
  4917. } else {
  4918. $this->info_tag_transform[$k] = $v;
  4919. }
  4920. }
  4921. foreach ($module->info_attr_transform_pre as $k => $v) {
  4922. if ($v === false) {
  4923. unset($this->info_attr_transform_pre[$k]);
  4924. } else {
  4925. $this->info_attr_transform_pre[$k] = $v;
  4926. }
  4927. }
  4928. foreach ($module->info_attr_transform_post as $k => $v) {
  4929. if ($v === false) {
  4930. unset($this->info_attr_transform_post[$k]);
  4931. } else {
  4932. $this->info_attr_transform_post[$k] = $v;
  4933. }
  4934. }
  4935. foreach ($module->info_injector as $k => $v) {
  4936. if ($v === false) {
  4937. unset($this->info_injector[$k]);
  4938. } else {
  4939. $this->info_injector[$k] = $v;
  4940. }
  4941. }
  4942. }
  4943. $this->info = $this->manager->getElements();
  4944. $this->info_content_sets = $this->manager->contentSets->lookup;
  4945. }
  4946. /**
  4947. * Sets up stuff based on config. We need a better way of doing this.
  4948. * @param HTMLPurifier_Config $config
  4949. */
  4950. protected function setupConfigStuff($config)
  4951. {
  4952. $block_wrapper = $config->get('HTML.BlockWrapper');
  4953. if (isset($this->info_content_sets['Block'][$block_wrapper])) {
  4954. $this->info_block_wrapper = $block_wrapper;
  4955. } else {
  4956. trigger_error(
  4957. 'Cannot use non-block element as block wrapper',
  4958. E_USER_ERROR
  4959. );
  4960. }
  4961. $parent = $config->get('HTML.Parent');
  4962. $def = $this->manager->getElement($parent, true);
  4963. if ($def) {
  4964. $this->info_parent = $parent;
  4965. $this->info_parent_def = $def;
  4966. } else {
  4967. trigger_error(
  4968. 'Cannot use unrecognized element as parent',
  4969. E_USER_ERROR
  4970. );
  4971. $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
  4972. }
  4973. // support template text
  4974. $support = "(for information on implementing this, see the support forums) ";
  4975. // setup allowed elements -----------------------------------------
  4976. $allowed_elements = $config->get('HTML.AllowedElements');
  4977. $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
  4978. if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
  4979. $allowed = $config->get('HTML.Allowed');
  4980. if (is_string($allowed)) {
  4981. list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
  4982. }
  4983. }
  4984. if (is_array($allowed_elements)) {
  4985. foreach ($this->info as $name => $d) {
  4986. if (!isset($allowed_elements[$name])) {
  4987. unset($this->info[$name]);
  4988. }
  4989. unset($allowed_elements[$name]);
  4990. }
  4991. // emit errors
  4992. foreach ($allowed_elements as $element => $d) {
  4993. $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
  4994. trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
  4995. }
  4996. }
  4997. // setup allowed attributes ---------------------------------------
  4998. $allowed_attributes_mutable = $allowed_attributes; // by copy!
  4999. if (is_array($allowed_attributes)) {
  5000. // This actually doesn't do anything, since we went away from
  5001. // global attributes. It's possible that userland code uses
  5002. // it, but HTMLModuleManager doesn't!
  5003. foreach ($this->info_global_attr as $attr => $x) {
  5004. $keys = array($attr, "*@$attr", "*.$attr");
  5005. $delete = true;
  5006. foreach ($keys as $key) {
  5007. if ($delete && isset($allowed_attributes[$key])) {
  5008. $delete = false;
  5009. }
  5010. if (isset($allowed_attributes_mutable[$key])) {
  5011. unset($allowed_attributes_mutable[$key]);
  5012. }
  5013. }
  5014. if ($delete) {
  5015. unset($this->info_global_attr[$attr]);
  5016. }
  5017. }
  5018. foreach ($this->info as $tag => $info) {
  5019. foreach ($info->attr as $attr => $x) {
  5020. $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
  5021. $delete = true;
  5022. foreach ($keys as $key) {
  5023. if ($delete && isset($allowed_attributes[$key])) {
  5024. $delete = false;
  5025. }
  5026. if (isset($allowed_attributes_mutable[$key])) {
  5027. unset($allowed_attributes_mutable[$key]);
  5028. }
  5029. }
  5030. if ($delete) {
  5031. if ($this->info[$tag]->attr[$attr]->required) {
  5032. trigger_error(
  5033. "Required attribute '$attr' in element '$tag' " .
  5034. "was not allowed, which means '$tag' will not be allowed either",
  5035. E_USER_WARNING
  5036. );
  5037. }
  5038. unset($this->info[$tag]->attr[$attr]);
  5039. }
  5040. }
  5041. }
  5042. // emit errors
  5043. foreach ($allowed_attributes_mutable as $elattr => $d) {
  5044. $bits = preg_split('/[.@]/', $elattr, 2);
  5045. $c = count($bits);
  5046. switch ($c) {
  5047. case 2:
  5048. if ($bits[0] !== '*') {
  5049. $element = htmlspecialchars($bits[0]);
  5050. $attribute = htmlspecialchars($bits[1]);
  5051. if (!isset($this->info[$element])) {
  5052. trigger_error(
  5053. "Cannot allow attribute '$attribute' if element " .
  5054. "'$element' is not allowed/supported $support"
  5055. );
  5056. } else {
  5057. trigger_error(
  5058. "Attribute '$attribute' in element '$element' not supported $support",
  5059. E_USER_WARNING
  5060. );
  5061. }
  5062. break;
  5063. }
  5064. // otherwise fall through
  5065. case 1:
  5066. $attribute = htmlspecialchars($bits[0]);
  5067. trigger_error(
  5068. "Global attribute '$attribute' is not ".
  5069. "supported in any elements $support",
  5070. E_USER_WARNING
  5071. );
  5072. break;
  5073. }
  5074. }
  5075. }
  5076. // setup forbidden elements ---------------------------------------
  5077. $forbidden_elements = $config->get('HTML.ForbiddenElements');
  5078. $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
  5079. foreach ($this->info as $tag => $info) {
  5080. if (isset($forbidden_elements[$tag])) {
  5081. unset($this->info[$tag]);
  5082. continue;
  5083. }
  5084. foreach ($info->attr as $attr => $x) {
  5085. if (isset($forbidden_attributes["$tag@$attr"]) ||
  5086. isset($forbidden_attributes["*@$attr"]) ||
  5087. isset($forbidden_attributes[$attr])
  5088. ) {
  5089. unset($this->info[$tag]->attr[$attr]);
  5090. continue;
  5091. } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
  5092. // $tag.$attr are not user supplied, so no worries!
  5093. trigger_error(
  5094. "Error with $tag.$attr: tag.attr syntax not supported for " .
  5095. "HTML.ForbiddenAttributes; use tag@attr instead",
  5096. E_USER_WARNING
  5097. );
  5098. }
  5099. }
  5100. }
  5101. foreach ($forbidden_attributes as $key => $v) {
  5102. if (strlen($key) < 2) {
  5103. continue;
  5104. }
  5105. if ($key[0] != '*') {
  5106. continue;
  5107. }
  5108. if ($key[1] == '.') {
  5109. trigger_error(
  5110. "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
  5111. E_USER_WARNING
  5112. );
  5113. }
  5114. }
  5115. // setup injectors -----------------------------------------------------
  5116. foreach ($this->info_injector as $i => $injector) {
  5117. if ($injector->checkNeeded($config) !== false) {
  5118. // remove injector that does not have it's required
  5119. // elements/attributes present, and is thus not needed.
  5120. unset($this->info_injector[$i]);
  5121. }
  5122. }
  5123. }
  5124. /**
  5125. * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
  5126. * separate lists for processing. Format is element[attr1|attr2],element2...
  5127. * @warning Although it's largely drawn from TinyMCE's implementation,
  5128. * it is different, and you'll probably have to modify your lists
  5129. * @param array $list String list to parse
  5130. * @return array
  5131. * @todo Give this its own class, probably static interface
  5132. */
  5133. public function parseTinyMCEAllowedList($list)
  5134. {
  5135. $list = str_replace(array(' ', "\t"), '', $list);
  5136. $elements = array();
  5137. $attributes = array();
  5138. $chunks = preg_split('/(,|[\n\r]+)/', $list);
  5139. foreach ($chunks as $chunk) {
  5140. if (empty($chunk)) {
  5141. continue;
  5142. }
  5143. // remove TinyMCE element control characters
  5144. if (!strpos($chunk, '[')) {
  5145. $element = $chunk;
  5146. $attr = false;
  5147. } else {
  5148. list($element, $attr) = explode('[', $chunk);
  5149. }
  5150. if ($element !== '*') {
  5151. $elements[$element] = true;
  5152. }
  5153. if (!$attr) {
  5154. continue;
  5155. }
  5156. $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
  5157. $attr = explode('|', $attr);
  5158. foreach ($attr as $key) {
  5159. $attributes["$element.$key"] = true;
  5160. }
  5161. }
  5162. return array($elements, $attributes);
  5163. }
  5164. }
  5165. /**
  5166. * Represents an XHTML 1.1 module, with information on elements, tags
  5167. * and attributes.
  5168. * @note Even though this is technically XHTML 1.1, it is also used for
  5169. * regular HTML parsing. We are using modulization as a convenient
  5170. * way to represent the internals of HTMLDefinition, and our
  5171. * implementation is by no means conforming and does not directly
  5172. * use the normative DTDs or XML schemas.
  5173. * @note The public variables in a module should almost directly
  5174. * correspond to the variables in HTMLPurifier_HTMLDefinition.
  5175. * However, the prefix info carries no special meaning in these
  5176. * objects (include it anyway if that's the correspondence though).
  5177. * @todo Consider making some member functions protected
  5178. */
  5179. class HTMLPurifier_HTMLModule
  5180. {
  5181. // -- Overloadable ----------------------------------------------------
  5182. /**
  5183. * Short unique string identifier of the module.
  5184. * @type string
  5185. */
  5186. public $name;
  5187. /**
  5188. * Informally, a list of elements this module changes.
  5189. * Not used in any significant way.
  5190. * @type array
  5191. */
  5192. public $elements = array();
  5193. /**
  5194. * Associative array of element names to element definitions.
  5195. * Some definitions may be incomplete, to be merged in later
  5196. * with the full definition.
  5197. * @type array
  5198. */
  5199. public $info = array();
  5200. /**
  5201. * Associative array of content set names to content set additions.
  5202. * This is commonly used to, say, add an A element to the Inline
  5203. * content set. This corresponds to an internal variable $content_sets
  5204. * and NOT info_content_sets member variable of HTMLDefinition.
  5205. * @type array
  5206. */
  5207. public $content_sets = array();
  5208. /**
  5209. * Associative array of attribute collection names to attribute
  5210. * collection additions. More rarely used for adding attributes to
  5211. * the global collections. Example is the StyleAttribute module adding
  5212. * the style attribute to the Core. Corresponds to HTMLDefinition's
  5213. * attr_collections->info, since the object's data is only info,
  5214. * with extra behavior associated with it.
  5215. * @type array
  5216. */
  5217. public $attr_collections = array();
  5218. /**
  5219. * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
  5220. * @type array
  5221. */
  5222. public $info_tag_transform = array();
  5223. /**
  5224. * List of HTMLPurifier_AttrTransform to be performed before validation.
  5225. * @type array
  5226. */
  5227. public $info_attr_transform_pre = array();
  5228. /**
  5229. * List of HTMLPurifier_AttrTransform to be performed after validation.
  5230. * @type array
  5231. */
  5232. public $info_attr_transform_post = array();
  5233. /**
  5234. * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
  5235. * An injector will only be invoked if all of it's pre-requisites are met;
  5236. * if an injector fails setup, there will be no error; it will simply be
  5237. * silently disabled.
  5238. * @type array
  5239. */
  5240. public $info_injector = array();
  5241. /**
  5242. * Boolean flag that indicates whether or not getChildDef is implemented.
  5243. * For optimization reasons: may save a call to a function. Be sure
  5244. * to set it if you do implement getChildDef(), otherwise it will have
  5245. * no effect!
  5246. * @type bool
  5247. */
  5248. public $defines_child_def = false;
  5249. /**
  5250. * Boolean flag whether or not this module is safe. If it is not safe, all
  5251. * of its members are unsafe. Modules are safe by default (this might be
  5252. * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
  5253. * which is based off of safe HTML, to explicitly say, "This is safe," even
  5254. * though there are modules which are "unsafe")
  5255. *
  5256. * @type bool
  5257. * @note Previously, safety could be applied at an element level granularity.
  5258. * We've removed this ability, so in order to add "unsafe" elements
  5259. * or attributes, a dedicated module with this property set to false
  5260. * must be used.
  5261. */
  5262. public $safe = true;
  5263. /**
  5264. * Retrieves a proper HTMLPurifier_ChildDef subclass based on
  5265. * content_model and content_model_type member variables of
  5266. * the HTMLPurifier_ElementDef class. There is a similar function
  5267. * in HTMLPurifier_HTMLDefinition.
  5268. * @param HTMLPurifier_ElementDef $def
  5269. * @return HTMLPurifier_ChildDef subclass
  5270. */
  5271. public function getChildDef($def)
  5272. {
  5273. return false;
  5274. }
  5275. // -- Convenience -----------------------------------------------------
  5276. /**
  5277. * Convenience function that sets up a new element
  5278. * @param string $element Name of element to add
  5279. * @param string|bool $type What content set should element be registered to?
  5280. * Set as false to skip this step.
  5281. * @param string $contents Allowed children in form of:
  5282. * "$content_model_type: $content_model"
  5283. * @param array $attr_includes What attribute collections to register to
  5284. * element?
  5285. * @param array $attr What unique attributes does the element define?
  5286. * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
  5287. * @return HTMLPurifier_ElementDef Created element definition object, so you
  5288. * can set advanced parameters
  5289. */
  5290. public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
  5291. {
  5292. $this->elements[] = $element;
  5293. // parse content_model
  5294. list($content_model_type, $content_model) = $this->parseContents($contents);
  5295. // merge in attribute inclusions
  5296. $this->mergeInAttrIncludes($attr, $attr_includes);
  5297. // add element to content sets
  5298. if ($type) {
  5299. $this->addElementToContentSet($element, $type);
  5300. }
  5301. // create element
  5302. $this->info[$element] = HTMLPurifier_ElementDef::create(
  5303. $content_model,
  5304. $content_model_type,
  5305. $attr
  5306. );
  5307. // literal object $contents means direct child manipulation
  5308. if (!is_string($contents)) {
  5309. $this->info[$element]->child = $contents;
  5310. }
  5311. return $this->info[$element];
  5312. }
  5313. /**
  5314. * Convenience function that creates a totally blank, non-standalone
  5315. * element.
  5316. * @param string $element Name of element to create
  5317. * @return HTMLPurifier_ElementDef Created element
  5318. */
  5319. public function addBlankElement($element)
  5320. {
  5321. if (!isset($this->info[$element])) {
  5322. $this->elements[] = $element;
  5323. $this->info[$element] = new HTMLPurifier_ElementDef();
  5324. $this->info[$element]->standalone = false;
  5325. } else {
  5326. trigger_error("Definition for $element already exists in module, cannot redefine");
  5327. }
  5328. return $this->info[$element];
  5329. }
  5330. /**
  5331. * Convenience function that registers an element to a content set
  5332. * @param string $element Element to register
  5333. * @param string $type Name content set (warning: case sensitive, usually upper-case
  5334. * first letter)
  5335. */
  5336. public function addElementToContentSet($element, $type)
  5337. {
  5338. if (!isset($this->content_sets[$type])) {
  5339. $this->content_sets[$type] = '';
  5340. } else {
  5341. $this->content_sets[$type] .= ' | ';
  5342. }
  5343. $this->content_sets[$type] .= $element;
  5344. }
  5345. /**
  5346. * Convenience function that transforms single-string contents
  5347. * into separate content model and content model type
  5348. * @param string $contents Allowed children in form of:
  5349. * "$content_model_type: $content_model"
  5350. * @return array
  5351. * @note If contents is an object, an array of two nulls will be
  5352. * returned, and the callee needs to take the original $contents
  5353. * and use it directly.
  5354. */
  5355. public function parseContents($contents)
  5356. {
  5357. if (!is_string($contents)) {
  5358. return array(null, null);
  5359. } // defer
  5360. switch ($contents) {
  5361. // check for shorthand content model forms
  5362. case 'Empty':
  5363. return array('empty', '');
  5364. case 'Inline':
  5365. return array('optional', 'Inline | #PCDATA');
  5366. case 'Flow':
  5367. return array('optional', 'Flow | #PCDATA');
  5368. }
  5369. list($content_model_type, $content_model) = explode(':', $contents);
  5370. $content_model_type = strtolower(trim($content_model_type));
  5371. $content_model = trim($content_model);
  5372. return array($content_model_type, $content_model);
  5373. }
  5374. /**
  5375. * Convenience function that merges a list of attribute includes into
  5376. * an attribute array.
  5377. * @param array $attr Reference to attr array to modify
  5378. * @param array $attr_includes Array of includes / string include to merge in
  5379. */
  5380. public function mergeInAttrIncludes(&$attr, $attr_includes)
  5381. {
  5382. if (!is_array($attr_includes)) {
  5383. if (empty($attr_includes)) {
  5384. $attr_includes = array();
  5385. } else {
  5386. $attr_includes = array($attr_includes);
  5387. }
  5388. }
  5389. $attr[0] = $attr_includes;
  5390. }
  5391. /**
  5392. * Convenience function that generates a lookup table with boolean
  5393. * true as value.
  5394. * @param string $list List of values to turn into a lookup
  5395. * @note You can also pass an arbitrary number of arguments in
  5396. * place of the regular argument
  5397. * @return array array equivalent of list
  5398. */
  5399. public function makeLookup($list)
  5400. {
  5401. if (is_string($list)) {
  5402. $list = func_get_args();
  5403. }
  5404. $ret = array();
  5405. foreach ($list as $value) {
  5406. if (is_null($value)) {
  5407. continue;
  5408. }
  5409. $ret[$value] = true;
  5410. }
  5411. return $ret;
  5412. }
  5413. /**
  5414. * Lazy load construction of the module after determining whether
  5415. * or not it's needed, and also when a finalized configuration object
  5416. * is available.
  5417. * @param HTMLPurifier_Config $config
  5418. */
  5419. public function setup($config)
  5420. {
  5421. }
  5422. }
  5423. class HTMLPurifier_HTMLModuleManager
  5424. {
  5425. /**
  5426. * @type HTMLPurifier_DoctypeRegistry
  5427. */
  5428. public $doctypes;
  5429. /**
  5430. * Instance of current doctype.
  5431. * @type string
  5432. */
  5433. public $doctype;
  5434. /**
  5435. * @type HTMLPurifier_AttrTypes
  5436. */
  5437. public $attrTypes;
  5438. /**
  5439. * Active instances of modules for the specified doctype are
  5440. * indexed, by name, in this array.
  5441. * @type HTMLPurifier_HTMLModule[]
  5442. */
  5443. public $modules = array();
  5444. /**
  5445. * Array of recognized HTMLPurifier_HTMLModule instances,
  5446. * indexed by module's class name. This array is usually lazy loaded, but a
  5447. * user can overload a module by pre-emptively registering it.
  5448. * @type HTMLPurifier_HTMLModule[]
  5449. */
  5450. public $registeredModules = array();
  5451. /**
  5452. * List of extra modules that were added by the user
  5453. * using addModule(). These get unconditionally merged into the current doctype, whatever
  5454. * it may be.
  5455. * @type HTMLPurifier_HTMLModule[]
  5456. */
  5457. public $userModules = array();
  5458. /**
  5459. * Associative array of element name to list of modules that have
  5460. * definitions for the element; this array is dynamically filled.
  5461. * @type array
  5462. */
  5463. public $elementLookup = array();
  5464. /**
  5465. * List of prefixes we should use for registering small names.
  5466. * @type array
  5467. */
  5468. public $prefixes = array('HTMLPurifier_HTMLModule_');
  5469. /**
  5470. * @type HTMLPurifier_ContentSets
  5471. */
  5472. public $contentSets;
  5473. /**
  5474. * @type HTMLPurifier_AttrCollections
  5475. */
  5476. public $attrCollections;
  5477. /**
  5478. * If set to true, unsafe elements and attributes will be allowed.
  5479. * @type bool
  5480. */
  5481. public $trusted = false;
  5482. public function __construct()
  5483. {
  5484. // editable internal objects
  5485. $this->attrTypes = new HTMLPurifier_AttrTypes();
  5486. $this->doctypes = new HTMLPurifier_DoctypeRegistry();
  5487. // setup basic modules
  5488. $common = array(
  5489. 'CommonAttributes', 'Text', 'Hypertext', 'List',
  5490. 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
  5491. 'StyleAttribute',
  5492. // Unsafe:
  5493. 'Scripting', 'Object', 'Forms',
  5494. // Sorta legacy, but present in strict:
  5495. 'Name',
  5496. );
  5497. $transitional = array('Legacy', 'Target', 'Iframe');
  5498. $xml = array('XMLCommonAttributes');
  5499. $non_xml = array('NonXMLCommonAttributes');
  5500. // setup basic doctypes
  5501. $this->doctypes->register(
  5502. 'HTML 4.01 Transitional',
  5503. false,
  5504. array_merge($common, $transitional, $non_xml),
  5505. array('Tidy_Transitional', 'Tidy_Proprietary'),
  5506. array(),
  5507. '-//W3C//DTD HTML 4.01 Transitional//EN',
  5508. 'http://www.w3.org/TR/html4/loose.dtd'
  5509. );
  5510. $this->doctypes->register(
  5511. 'HTML 4.01 Strict',
  5512. false,
  5513. array_merge($common, $non_xml),
  5514. array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5515. array(),
  5516. '-//W3C//DTD HTML 4.01//EN',
  5517. 'http://www.w3.org/TR/html4/strict.dtd'
  5518. );
  5519. $this->doctypes->register(
  5520. 'XHTML 1.0 Transitional',
  5521. true,
  5522. array_merge($common, $transitional, $xml, $non_xml),
  5523. array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
  5524. array(),
  5525. '-//W3C//DTD XHTML 1.0 Transitional//EN',
  5526. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
  5527. );
  5528. $this->doctypes->register(
  5529. 'XHTML 1.0 Strict',
  5530. true,
  5531. array_merge($common, $xml, $non_xml),
  5532. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
  5533. array(),
  5534. '-//W3C//DTD XHTML 1.0 Strict//EN',
  5535. 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
  5536. );
  5537. $this->doctypes->register(
  5538. 'XHTML 1.1',
  5539. true,
  5540. // Iframe is a real XHTML 1.1 module, despite being
  5541. // "transitional"!
  5542. array_merge($common, $xml, array('Ruby', 'Iframe')),
  5543. array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
  5544. array(),
  5545. '-//W3C//DTD XHTML 1.1//EN',
  5546. 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
  5547. );
  5548. }
  5549. /**
  5550. * Registers a module to the recognized module list, useful for
  5551. * overloading pre-existing modules.
  5552. * @param $module Mixed: string module name, with or without
  5553. * HTMLPurifier_HTMLModule prefix, or instance of
  5554. * subclass of HTMLPurifier_HTMLModule.
  5555. * @param $overload Boolean whether or not to overload previous modules.
  5556. * If this is not set, and you do overload a module,
  5557. * HTML Purifier will complain with a warning.
  5558. * @note This function will not call autoload, you must instantiate
  5559. * (and thus invoke) autoload outside the method.
  5560. * @note If a string is passed as a module name, different variants
  5561. * will be tested in this order:
  5562. * - Check for HTMLPurifier_HTMLModule_$name
  5563. * - Check all prefixes with $name in order they were added
  5564. * - Check for literal object name
  5565. * - Throw fatal error
  5566. * If your object name collides with an internal class, specify
  5567. * your module manually. All modules must have been included
  5568. * externally: registerModule will not perform inclusions for you!
  5569. */
  5570. public function registerModule($module, $overload = false)
  5571. {
  5572. if (is_string($module)) {
  5573. // attempt to load the module
  5574. $original_module = $module;
  5575. $ok = false;
  5576. foreach ($this->prefixes as $prefix) {
  5577. $module = $prefix . $original_module;
  5578. if (class_exists($module)) {
  5579. $ok = true;
  5580. break;
  5581. }
  5582. }
  5583. if (!$ok) {
  5584. $module = $original_module;
  5585. if (!class_exists($module)) {
  5586. trigger_error(
  5587. $original_module . ' module does not exist',
  5588. E_USER_ERROR
  5589. );
  5590. return;
  5591. }
  5592. }
  5593. $module = new $module();
  5594. }
  5595. if (empty($module->name)) {
  5596. trigger_error('Module instance of ' . get_class($module) . ' must have name');
  5597. return;
  5598. }
  5599. if (!$overload && isset($this->registeredModules[$module->name])) {
  5600. trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
  5601. }
  5602. $this->registeredModules[$module->name] = $module;
  5603. }
  5604. /**
  5605. * Adds a module to the current doctype by first registering it,
  5606. * and then tacking it on to the active doctype
  5607. */
  5608. public function addModule($module)
  5609. {
  5610. $this->registerModule($module);
  5611. if (is_object($module)) {
  5612. $module = $module->name;
  5613. }
  5614. $this->userModules[] = $module;
  5615. }
  5616. /**
  5617. * Adds a class prefix that registerModule() will use to resolve a
  5618. * string name to a concrete class
  5619. */
  5620. public function addPrefix($prefix)
  5621. {
  5622. $this->prefixes[] = $prefix;
  5623. }
  5624. /**
  5625. * Performs processing on modules, after being called you may
  5626. * use getElement() and getElements()
  5627. * @param HTMLPurifier_Config $config
  5628. */
  5629. public function setup($config)
  5630. {
  5631. $this->trusted = $config->get('HTML.Trusted');
  5632. // generate
  5633. $this->doctype = $this->doctypes->make($config);
  5634. $modules = $this->doctype->modules;
  5635. // take out the default modules that aren't allowed
  5636. $lookup = $config->get('HTML.AllowedModules');
  5637. $special_cases = $config->get('HTML.CoreModules');
  5638. if (is_array($lookup)) {
  5639. foreach ($modules as $k => $m) {
  5640. if (isset($special_cases[$m])) {
  5641. continue;
  5642. }
  5643. if (!isset($lookup[$m])) {
  5644. unset($modules[$k]);
  5645. }
  5646. }
  5647. }
  5648. // custom modules
  5649. if ($config->get('HTML.Proprietary')) {
  5650. $modules[] = 'Proprietary';
  5651. }
  5652. if ($config->get('HTML.SafeObject')) {
  5653. $modules[] = 'SafeObject';
  5654. }
  5655. if ($config->get('HTML.SafeEmbed')) {
  5656. $modules[] = 'SafeEmbed';
  5657. }
  5658. if ($config->get('HTML.SafeScripting') !== array()) {
  5659. $modules[] = 'SafeScripting';
  5660. }
  5661. if ($config->get('HTML.Nofollow')) {
  5662. $modules[] = 'Nofollow';
  5663. }
  5664. if ($config->get('HTML.TargetBlank')) {
  5665. $modules[] = 'TargetBlank';
  5666. }
  5667. // merge in custom modules
  5668. $modules = array_merge($modules, $this->userModules);
  5669. foreach ($modules as $module) {
  5670. $this->processModule($module);
  5671. $this->modules[$module]->setup($config);
  5672. }
  5673. foreach ($this->doctype->tidyModules as $module) {
  5674. $this->processModule($module);
  5675. $this->modules[$module]->setup($config);
  5676. }
  5677. // prepare any injectors
  5678. foreach ($this->modules as $module) {
  5679. $n = array();
  5680. foreach ($module->info_injector as $injector) {
  5681. if (!is_object($injector)) {
  5682. $class = "HTMLPurifier_Injector_$injector";
  5683. $injector = new $class;
  5684. }
  5685. $n[$injector->name] = $injector;
  5686. }
  5687. $module->info_injector = $n;
  5688. }
  5689. // setup lookup table based on all valid modules
  5690. foreach ($this->modules as $module) {
  5691. foreach ($module->info as $name => $def) {
  5692. if (!isset($this->elementLookup[$name])) {
  5693. $this->elementLookup[$name] = array();
  5694. }
  5695. $this->elementLookup[$name][] = $module->name;
  5696. }
  5697. }
  5698. // note the different choice
  5699. $this->contentSets = new HTMLPurifier_ContentSets(
  5700. // content set assembly deals with all possible modules,
  5701. // not just ones deemed to be "safe"
  5702. $this->modules
  5703. );
  5704. $this->attrCollections = new HTMLPurifier_AttrCollections(
  5705. $this->attrTypes,
  5706. // there is no way to directly disable a global attribute,
  5707. // but using AllowedAttributes or simply not including
  5708. // the module in your custom doctype should be sufficient
  5709. $this->modules
  5710. );
  5711. }
  5712. /**
  5713. * Takes a module and adds it to the active module collection,
  5714. * registering it if necessary.
  5715. */
  5716. public function processModule($module)
  5717. {
  5718. if (!isset($this->registeredModules[$module]) || is_object($module)) {
  5719. $this->registerModule($module);
  5720. }
  5721. $this->modules[$module] = $this->registeredModules[$module];
  5722. }
  5723. /**
  5724. * Retrieves merged element definitions.
  5725. * @return Array of HTMLPurifier_ElementDef
  5726. */
  5727. public function getElements()
  5728. {
  5729. $elements = array();
  5730. foreach ($this->modules as $module) {
  5731. if (!$this->trusted && !$module->safe) {
  5732. continue;
  5733. }
  5734. foreach ($module->info as $name => $v) {
  5735. if (isset($elements[$name])) {
  5736. continue;
  5737. }
  5738. $elements[$name] = $this->getElement($name);
  5739. }
  5740. }
  5741. // remove dud elements, this happens when an element that
  5742. // appeared to be safe actually wasn't
  5743. foreach ($elements as $n => $v) {
  5744. if ($v === false) {
  5745. unset($elements[$n]);
  5746. }
  5747. }
  5748. return $elements;
  5749. }
  5750. /**
  5751. * Retrieves a single merged element definition
  5752. * @param string $name Name of element
  5753. * @param bool $trusted Boolean trusted overriding parameter: set to true
  5754. * if you want the full version of an element
  5755. * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef
  5756. * @note You may notice that modules are getting iterated over twice (once
  5757. * in getElements() and once here). This
  5758. * is because
  5759. */
  5760. public function getElement($name, $trusted = null)
  5761. {
  5762. if (!isset($this->elementLookup[$name])) {
  5763. return false;
  5764. }
  5765. // setup global state variables
  5766. $def = false;
  5767. if ($trusted === null) {
  5768. $trusted = $this->trusted;
  5769. }
  5770. // iterate through each module that has registered itself to this
  5771. // element
  5772. foreach ($this->elementLookup[$name] as $module_name) {
  5773. $module = $this->modules[$module_name];
  5774. // refuse to create/merge from a module that is deemed unsafe--
  5775. // pretend the module doesn't exist--when trusted mode is not on.
  5776. if (!$trusted && !$module->safe) {
  5777. continue;
  5778. }
  5779. // clone is used because, ideally speaking, the original
  5780. // definition should not be modified. Usually, this will
  5781. // make no difference, but for consistency's sake
  5782. $new_def = clone $module->info[$name];
  5783. if (!$def && $new_def->standalone) {
  5784. $def = $new_def;
  5785. } elseif ($def) {
  5786. // This will occur even if $new_def is standalone. In practice,
  5787. // this will usually result in a full replacement.
  5788. $def->mergeIn($new_def);
  5789. } else {
  5790. // :TODO:
  5791. // non-standalone definitions that don't have a standalone
  5792. // to merge into could be deferred to the end
  5793. // HOWEVER, it is perfectly valid for a non-standalone
  5794. // definition to lack a standalone definition, even
  5795. // after all processing: this allows us to safely
  5796. // specify extra attributes for elements that may not be
  5797. // enabled all in one place. In particular, this might
  5798. // be the case for trusted elements. WARNING: care must
  5799. // be taken that the /extra/ definitions are all safe.
  5800. continue;
  5801. }
  5802. // attribute value expansions
  5803. $this->attrCollections->performInclusions($def->attr);
  5804. $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
  5805. // descendants_are_inline, for ChildDef_Chameleon
  5806. if (is_string($def->content_model) &&
  5807. strpos($def->content_model, 'Inline') !== false) {
  5808. if ($name != 'del' && $name != 'ins') {
  5809. // this is for you, ins/del
  5810. $def->descendants_are_inline = true;
  5811. }
  5812. }
  5813. $this->contentSets->generateChildDef($def, $module);
  5814. }
  5815. // This can occur if there is a blank definition, but no base to
  5816. // mix it in with
  5817. if (!$def) {
  5818. return false;
  5819. }
  5820. // add information on required attributes
  5821. foreach ($def->attr as $attr_name => $attr_def) {
  5822. if ($attr_def->required) {
  5823. $def->required_attr[] = $attr_name;
  5824. }
  5825. }
  5826. return $def;
  5827. }
  5828. }
  5829. /**
  5830. * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
  5831. * @note In Slashdot-speak, dupe means duplicate.
  5832. * @note The default constructor does not accept $config or $context objects:
  5833. * use must use the static build() factory method to perform initialization.
  5834. */
  5835. class HTMLPurifier_IDAccumulator
  5836. {
  5837. /**
  5838. * Lookup table of IDs we've accumulated.
  5839. * @public
  5840. */
  5841. public $ids = array();
  5842. /**
  5843. * Builds an IDAccumulator, also initializing the default blacklist
  5844. * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
  5845. * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
  5846. * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator
  5847. */
  5848. public static function build($config, $context)
  5849. {
  5850. $id_accumulator = new HTMLPurifier_IDAccumulator();
  5851. $id_accumulator->load($config->get('Attr.IDBlacklist'));
  5852. return $id_accumulator;
  5853. }
  5854. /**
  5855. * Add an ID to the lookup table.
  5856. * @param string $id ID to be added.
  5857. * @return bool status, true if success, false if there's a dupe
  5858. */
  5859. public function add($id)
  5860. {
  5861. if (isset($this->ids[$id])) {
  5862. return false;
  5863. }
  5864. return $this->ids[$id] = true;
  5865. }
  5866. /**
  5867. * Load a list of IDs into the lookup table
  5868. * @param $array_of_ids Array of IDs to load
  5869. * @note This function doesn't care about duplicates
  5870. */
  5871. public function load($array_of_ids)
  5872. {
  5873. foreach ($array_of_ids as $id) {
  5874. $this->ids[$id] = true;
  5875. }
  5876. }
  5877. }
  5878. /**
  5879. * Injects tokens into the document while parsing for well-formedness.
  5880. * This enables "formatter-like" functionality such as auto-paragraphing,
  5881. * smiley-ification and linkification to take place.
  5882. *
  5883. * A note on how handlers create changes; this is done by assigning a new
  5884. * value to the $token reference. These values can take a variety of forms and
  5885. * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
  5886. * documentation.
  5887. *
  5888. * @todo Allow injectors to request a re-run on their output. This
  5889. * would help if an operation is recursive.
  5890. */
  5891. abstract class HTMLPurifier_Injector
  5892. {
  5893. /**
  5894. * Advisory name of injector, this is for friendly error messages.
  5895. * @type string
  5896. */
  5897. public $name;
  5898. /**
  5899. * @type HTMLPurifier_HTMLDefinition
  5900. */
  5901. protected $htmlDefinition;
  5902. /**
  5903. * Reference to CurrentNesting variable in Context. This is an array
  5904. * list of tokens that we are currently "inside"
  5905. * @type array
  5906. */
  5907. protected $currentNesting;
  5908. /**
  5909. * Reference to current token.
  5910. * @type HTMLPurifier_Token
  5911. */
  5912. protected $currentToken;
  5913. /**
  5914. * Reference to InputZipper variable in Context.
  5915. * @type HTMLPurifier_Zipper
  5916. */
  5917. protected $inputZipper;
  5918. /**
  5919. * Array of elements and attributes this injector creates and therefore
  5920. * need to be allowed by the definition. Takes form of
  5921. * array('element' => array('attr', 'attr2'), 'element2')
  5922. * @type array
  5923. */
  5924. public $needed = array();
  5925. /**
  5926. * Number of elements to rewind backwards (relative).
  5927. * @type bool|int
  5928. */
  5929. protected $rewindOffset = false;
  5930. /**
  5931. * Rewind to a spot to re-perform processing. This is useful if you
  5932. * deleted a node, and now need to see if this change affected any
  5933. * earlier nodes. Rewinding does not affect other injectors, and can
  5934. * result in infinite loops if not used carefully.
  5935. * @param bool|int $offset
  5936. * @warning HTML Purifier will prevent you from fast-forwarding with this
  5937. * function.
  5938. */
  5939. public function rewindOffset($offset)
  5940. {
  5941. $this->rewindOffset = $offset;
  5942. }
  5943. /**
  5944. * Retrieves rewind offset, and then unsets it.
  5945. * @return bool|int
  5946. */
  5947. public function getRewindOffset()
  5948. {
  5949. $r = $this->rewindOffset;
  5950. $this->rewindOffset = false;
  5951. return $r;
  5952. }
  5953. /**
  5954. * Prepares the injector by giving it the config and context objects:
  5955. * this allows references to important variables to be made within
  5956. * the injector. This function also checks if the HTML environment
  5957. * will work with the Injector (see checkNeeded()).
  5958. * @param HTMLPurifier_Config $config
  5959. * @param HTMLPurifier_Context $context
  5960. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  5961. */
  5962. public function prepare($config, $context)
  5963. {
  5964. $this->htmlDefinition = $config->getHTMLDefinition();
  5965. // Even though this might fail, some unit tests ignore this and
  5966. // still test checkNeeded, so be careful. Maybe get rid of that
  5967. // dependency.
  5968. $result = $this->checkNeeded($config);
  5969. if ($result !== false) {
  5970. return $result;
  5971. }
  5972. $this->currentNesting =& $context->get('CurrentNesting');
  5973. $this->currentToken =& $context->get('CurrentToken');
  5974. $this->inputZipper =& $context->get('InputZipper');
  5975. return false;
  5976. }
  5977. /**
  5978. * This function checks if the HTML environment
  5979. * will work with the Injector: if p tags are not allowed, the
  5980. * Auto-Paragraphing injector should not be enabled.
  5981. * @param HTMLPurifier_Config $config
  5982. * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
  5983. */
  5984. public function checkNeeded($config)
  5985. {
  5986. $def = $config->getHTMLDefinition();
  5987. foreach ($this->needed as $element => $attributes) {
  5988. if (is_int($element)) {
  5989. $element = $attributes;
  5990. }
  5991. if (!isset($def->info[$element])) {
  5992. return $element;
  5993. }
  5994. if (!is_array($attributes)) {
  5995. continue;
  5996. }
  5997. foreach ($attributes as $name) {
  5998. if (!isset($def->info[$element]->attr[$name])) {
  5999. return "$element.$name";
  6000. }
  6001. }
  6002. }
  6003. return false;
  6004. }
  6005. /**
  6006. * Tests if the context node allows a certain element
  6007. * @param string $name Name of element to test for
  6008. * @return bool True if element is allowed, false if it is not
  6009. */
  6010. public function allowsElement($name)
  6011. {
  6012. if (!empty($this->currentNesting)) {
  6013. $parent_token = array_pop($this->currentNesting);
  6014. $this->currentNesting[] = $parent_token;
  6015. $parent = $this->htmlDefinition->info[$parent_token->name];
  6016. } else {
  6017. $parent = $this->htmlDefinition->info_parent_def;
  6018. }
  6019. if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
  6020. return false;
  6021. }
  6022. // check for exclusion
  6023. for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
  6024. $node = $this->currentNesting[$i];
  6025. $def = $this->htmlDefinition->info[$node->name];
  6026. if (isset($def->excludes[$name])) {
  6027. return false;
  6028. }
  6029. }
  6030. return true;
  6031. }
  6032. /**
  6033. * Iterator function, which starts with the next token and continues until
  6034. * you reach the end of the input tokens.
  6035. * @warning Please prevent previous references from interfering with this
  6036. * functions by setting $i = null beforehand!
  6037. * @param int $i Current integer index variable for inputTokens
  6038. * @param HTMLPurifier_Token $current Current token variable.
  6039. * Do NOT use $token, as that variable is also a reference
  6040. * @return bool
  6041. */
  6042. protected function forward(&$i, &$current)
  6043. {
  6044. if ($i === null) {
  6045. $i = count($this->inputZipper->back) - 1;
  6046. } else {
  6047. $i--;
  6048. }
  6049. if ($i < 0) {
  6050. return false;
  6051. }
  6052. $current = $this->inputZipper->back[$i];
  6053. return true;
  6054. }
  6055. /**
  6056. * Similar to _forward, but accepts a third parameter $nesting (which
  6057. * should be initialized at 0) and stops when we hit the end tag
  6058. * for the node $this->inputIndex starts in.
  6059. * @param int $i Current integer index variable for inputTokens
  6060. * @param HTMLPurifier_Token $current Current token variable.
  6061. * Do NOT use $token, as that variable is also a reference
  6062. * @param int $nesting
  6063. * @return bool
  6064. */
  6065. protected function forwardUntilEndToken(&$i, &$current, &$nesting)
  6066. {
  6067. $result = $this->forward($i, $current);
  6068. if (!$result) {
  6069. return false;
  6070. }
  6071. if ($nesting === null) {
  6072. $nesting = 0;
  6073. }
  6074. if ($current instanceof HTMLPurifier_Token_Start) {
  6075. $nesting++;
  6076. } elseif ($current instanceof HTMLPurifier_Token_End) {
  6077. if ($nesting <= 0) {
  6078. return false;
  6079. }
  6080. $nesting--;
  6081. }
  6082. return true;
  6083. }
  6084. /**
  6085. * Iterator function, starts with the previous token and continues until
  6086. * you reach the beginning of input tokens.
  6087. * @warning Please prevent previous references from interfering with this
  6088. * functions by setting $i = null beforehand!
  6089. * @param int $i Current integer index variable for inputTokens
  6090. * @param HTMLPurifier_Token $current Current token variable.
  6091. * Do NOT use $token, as that variable is also a reference
  6092. * @return bool
  6093. */
  6094. protected function backward(&$i, &$current)
  6095. {
  6096. if ($i === null) {
  6097. $i = count($this->inputZipper->front) - 1;
  6098. } else {
  6099. $i--;
  6100. }
  6101. if ($i < 0) {
  6102. return false;
  6103. }
  6104. $current = $this->inputZipper->front[$i];
  6105. return true;
  6106. }
  6107. /**
  6108. * Handler that is called when a text token is processed
  6109. */
  6110. public function handleText(&$token)
  6111. {
  6112. }
  6113. /**
  6114. * Handler that is called when a start or empty token is processed
  6115. */
  6116. public function handleElement(&$token)
  6117. {
  6118. }
  6119. /**
  6120. * Handler that is called when an end token is processed
  6121. */
  6122. public function handleEnd(&$token)
  6123. {
  6124. $this->notifyEnd($token);
  6125. }
  6126. /**
  6127. * Notifier that is called when an end token is processed
  6128. * @param HTMLPurifier_Token $token Current token variable.
  6129. * @note This differs from handlers in that the token is read-only
  6130. * @deprecated
  6131. */
  6132. public function notifyEnd($token)
  6133. {
  6134. }
  6135. }
  6136. /**
  6137. * Represents a language and defines localizable string formatting and
  6138. * other functions, as well as the localized messages for HTML Purifier.
  6139. */
  6140. class HTMLPurifier_Language
  6141. {
  6142. /**
  6143. * ISO 639 language code of language. Prefers shortest possible version.
  6144. * @type string
  6145. */
  6146. public $code = 'en';
  6147. /**
  6148. * Fallback language code.
  6149. * @type bool|string
  6150. */
  6151. public $fallback = false;
  6152. /**
  6153. * Array of localizable messages.
  6154. * @type array
  6155. */
  6156. public $messages = array();
  6157. /**
  6158. * Array of localizable error codes.
  6159. * @type array
  6160. */
  6161. public $errorNames = array();
  6162. /**
  6163. * True if no message file was found for this language, so English
  6164. * is being used instead. Check this if you'd like to notify the
  6165. * user that they've used a non-supported language.
  6166. * @type bool
  6167. */
  6168. public $error = false;
  6169. /**
  6170. * Has the language object been loaded yet?
  6171. * @type bool
  6172. * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
  6173. */
  6174. public $_loaded = false;
  6175. /**
  6176. * @type HTMLPurifier_Config
  6177. */
  6178. protected $config;
  6179. /**
  6180. * @type HTMLPurifier_Context
  6181. */
  6182. protected $context;
  6183. /**
  6184. * @param HTMLPurifier_Config $config
  6185. * @param HTMLPurifier_Context $context
  6186. */
  6187. public function __construct($config, $context)
  6188. {
  6189. $this->config = $config;
  6190. $this->context = $context;
  6191. }
  6192. /**
  6193. * Loads language object with necessary info from factory cache
  6194. * @note This is a lazy loader
  6195. */
  6196. public function load()
  6197. {
  6198. if ($this->_loaded) {
  6199. return;
  6200. }
  6201. $factory = HTMLPurifier_LanguageFactory::instance();
  6202. $factory->loadLanguage($this->code);
  6203. foreach ($factory->keys as $key) {
  6204. $this->$key = $factory->cache[$this->code][$key];
  6205. }
  6206. $this->_loaded = true;
  6207. }
  6208. /**
  6209. * Retrieves a localised message.
  6210. * @param string $key string identifier of message
  6211. * @return string localised message
  6212. */
  6213. public function getMessage($key)
  6214. {
  6215. if (!$this->_loaded) {
  6216. $this->load();
  6217. }
  6218. if (!isset($this->messages[$key])) {
  6219. return "[$key]";
  6220. }
  6221. return $this->messages[$key];
  6222. }
  6223. /**
  6224. * Retrieves a localised error name.
  6225. * @param int $int error number, corresponding to PHP's error reporting
  6226. * @return string localised message
  6227. */
  6228. public function getErrorName($int)
  6229. {
  6230. if (!$this->_loaded) {
  6231. $this->load();
  6232. }
  6233. if (!isset($this->errorNames[$int])) {
  6234. return "[Error: $int]";
  6235. }
  6236. return $this->errorNames[$int];
  6237. }
  6238. /**
  6239. * Converts an array list into a string readable representation
  6240. * @param array $array
  6241. * @return string
  6242. */
  6243. public function listify($array)
  6244. {
  6245. $sep = $this->getMessage('Item separator');
  6246. $sep_last = $this->getMessage('Item separator last');
  6247. $ret = '';
  6248. for ($i = 0, $c = count($array); $i < $c; $i++) {
  6249. if ($i == 0) {
  6250. } elseif ($i + 1 < $c) {
  6251. $ret .= $sep;
  6252. } else {
  6253. $ret .= $sep_last;
  6254. }
  6255. $ret .= $array[$i];
  6256. }
  6257. return $ret;
  6258. }
  6259. /**
  6260. * Formats a localised message with passed parameters
  6261. * @param string $key string identifier of message
  6262. * @param array $args Parameters to substitute in
  6263. * @return string localised message
  6264. * @todo Implement conditionals? Right now, some messages make
  6265. * reference to line numbers, but those aren't always available
  6266. */
  6267. public function formatMessage($key, $args = array())
  6268. {
  6269. if (!$this->_loaded) {
  6270. $this->load();
  6271. }
  6272. if (!isset($this->messages[$key])) {
  6273. return "[$key]";
  6274. }
  6275. $raw = $this->messages[$key];
  6276. $subst = array();
  6277. $generator = false;
  6278. foreach ($args as $i => $value) {
  6279. if (is_object($value)) {
  6280. if ($value instanceof HTMLPurifier_Token) {
  6281. // factor this out some time
  6282. if (!$generator) {
  6283. $generator = $this->context->get('Generator');
  6284. }
  6285. if (isset($value->name)) {
  6286. $subst['$'.$i.'.Name'] = $value->name;
  6287. }
  6288. if (isset($value->data)) {
  6289. $subst['$'.$i.'.Data'] = $value->data;
  6290. }
  6291. $subst['$'.$i.'.Compact'] =
  6292. $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
  6293. // a more complex algorithm for compact representation
  6294. // could be introduced for all types of tokens. This
  6295. // may need to be factored out into a dedicated class
  6296. if (!empty($value->attr)) {
  6297. $stripped_token = clone $value;
  6298. $stripped_token->attr = array();
  6299. $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
  6300. }
  6301. $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
  6302. }
  6303. continue;
  6304. } elseif (is_array($value)) {
  6305. $keys = array_keys($value);
  6306. if (array_keys($keys) === $keys) {
  6307. // list
  6308. $subst['$'.$i] = $this->listify($value);
  6309. } else {
  6310. // associative array
  6311. // no $i implementation yet, sorry
  6312. $subst['$'.$i.'.Keys'] = $this->listify($keys);
  6313. $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
  6314. }
  6315. continue;
  6316. }
  6317. $subst['$' . $i] = $value;
  6318. }
  6319. return strtr($raw, $subst);
  6320. }
  6321. }
  6322. /**
  6323. * Class responsible for generating HTMLPurifier_Language objects, managing
  6324. * caching and fallbacks.
  6325. * @note Thanks to MediaWiki for the general logic, although this version
  6326. * has been entirely rewritten
  6327. * @todo Serialized cache for languages
  6328. */
  6329. class HTMLPurifier_LanguageFactory
  6330. {
  6331. /**
  6332. * Cache of language code information used to load HTMLPurifier_Language objects.
  6333. * Structure is: $factory->cache[$language_code][$key] = $value
  6334. * @type array
  6335. */
  6336. public $cache;
  6337. /**
  6338. * Valid keys in the HTMLPurifier_Language object. Designates which
  6339. * variables to slurp out of a message file.
  6340. * @type array
  6341. */
  6342. public $keys = array('fallback', 'messages', 'errorNames');
  6343. /**
  6344. * Instance to validate language codes.
  6345. * @type HTMLPurifier_AttrDef_Lang
  6346. *
  6347. */
  6348. protected $validator;
  6349. /**
  6350. * Cached copy of dirname(__FILE__), directory of current file without
  6351. * trailing slash.
  6352. * @type string
  6353. */
  6354. protected $dir;
  6355. /**
  6356. * Keys whose contents are a hash map and can be merged.
  6357. * @type array
  6358. */
  6359. protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
  6360. /**
  6361. * Keys whose contents are a list and can be merged.
  6362. * @value array lookup
  6363. */
  6364. protected $mergeable_keys_list = array();
  6365. /**
  6366. * Retrieve sole instance of the factory.
  6367. * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
  6368. * or bool true to reset to default factory.
  6369. * @return HTMLPurifier_LanguageFactory
  6370. */
  6371. public static function instance($prototype = null)
  6372. {
  6373. static $instance = null;
  6374. if ($prototype !== null) {
  6375. $instance = $prototype;
  6376. } elseif ($instance === null || $prototype == true) {
  6377. $instance = new HTMLPurifier_LanguageFactory();
  6378. $instance->setup();
  6379. }
  6380. return $instance;
  6381. }
  6382. /**
  6383. * Sets up the singleton, much like a constructor
  6384. * @note Prevents people from getting this outside of the singleton
  6385. */
  6386. public function setup()
  6387. {
  6388. $this->validator = new HTMLPurifier_AttrDef_Lang();
  6389. $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
  6390. }
  6391. /**
  6392. * Creates a language object, handles class fallbacks
  6393. * @param HTMLPurifier_Config $config
  6394. * @param HTMLPurifier_Context $context
  6395. * @param bool|string $code Code to override configuration with. Private parameter.
  6396. * @return HTMLPurifier_Language
  6397. */
  6398. public function create($config, $context, $code = false)
  6399. {
  6400. // validate language code
  6401. if ($code === false) {
  6402. $code = $this->validator->validate(
  6403. $config->get('Core.Language'),
  6404. $config,
  6405. $context
  6406. );
  6407. } else {
  6408. $code = $this->validator->validate($code, $config, $context);
  6409. }
  6410. if ($code === false) {
  6411. $code = 'en'; // malformed code becomes English
  6412. }
  6413. $pcode = str_replace('-', '_', $code); // make valid PHP classname
  6414. static $depth = 0; // recursion protection
  6415. if ($code == 'en') {
  6416. $lang = new HTMLPurifier_Language($config, $context);
  6417. } else {
  6418. $class = 'HTMLPurifier_Language_' . $pcode;
  6419. $file = $this->dir . '/Language/classes/' . $code . '.php';
  6420. if (file_exists($file) || class_exists($class, false)) {
  6421. $lang = new $class($config, $context);
  6422. } else {
  6423. // Go fallback
  6424. $raw_fallback = $this->getFallbackFor($code);
  6425. $fallback = $raw_fallback ? $raw_fallback : 'en';
  6426. $depth++;
  6427. $lang = $this->create($config, $context, $fallback);
  6428. if (!$raw_fallback) {
  6429. $lang->error = true;
  6430. }
  6431. $depth--;
  6432. }
  6433. }
  6434. $lang->code = $code;
  6435. return $lang;
  6436. }
  6437. /**
  6438. * Returns the fallback language for language
  6439. * @note Loads the original language into cache
  6440. * @param string $code language code
  6441. * @return string|bool
  6442. */
  6443. public function getFallbackFor($code)
  6444. {
  6445. $this->loadLanguage($code);
  6446. return $this->cache[$code]['fallback'];
  6447. }
  6448. /**
  6449. * Loads language into the cache, handles message file and fallbacks
  6450. * @param string $code language code
  6451. */
  6452. public function loadLanguage($code)
  6453. {
  6454. static $languages_seen = array(); // recursion guard
  6455. // abort if we've already loaded it
  6456. if (isset($this->cache[$code])) {
  6457. return;
  6458. }
  6459. // generate filename
  6460. $filename = $this->dir . '/Language/messages/' . $code . '.php';
  6461. // default fallback : may be overwritten by the ensuing include
  6462. $fallback = ($code != 'en') ? 'en' : false;
  6463. // load primary localisation
  6464. if (!file_exists($filename)) {
  6465. // skip the include: will rely solely on fallback
  6466. $filename = $this->dir . '/Language/messages/en.php';
  6467. $cache = array();
  6468. } else {
  6469. include $filename;
  6470. $cache = compact($this->keys);
  6471. }
  6472. // load fallback localisation
  6473. if (!empty($fallback)) {
  6474. // infinite recursion guard
  6475. if (isset($languages_seen[$code])) {
  6476. trigger_error(
  6477. 'Circular fallback reference in language ' .
  6478. $code,
  6479. E_USER_ERROR
  6480. );
  6481. $fallback = 'en';
  6482. }
  6483. $language_seen[$code] = true;
  6484. // load the fallback recursively
  6485. $this->loadLanguage($fallback);
  6486. $fallback_cache = $this->cache[$fallback];
  6487. // merge fallback with current language
  6488. foreach ($this->keys as $key) {
  6489. if (isset($cache[$key]) && isset($fallback_cache[$key])) {
  6490. if (isset($this->mergeable_keys_map[$key])) {
  6491. $cache[$key] = $cache[$key] + $fallback_cache[$key];
  6492. } elseif (isset($this->mergeable_keys_list[$key])) {
  6493. $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
  6494. }
  6495. } else {
  6496. $cache[$key] = $fallback_cache[$key];
  6497. }
  6498. }
  6499. }
  6500. // save to cache for later retrieval
  6501. $this->cache[$code] = $cache;
  6502. return;
  6503. }
  6504. }
  6505. /**
  6506. * Represents a measurable length, with a string numeric magnitude
  6507. * and a unit. This object is immutable.
  6508. */
  6509. class HTMLPurifier_Length
  6510. {
  6511. /**
  6512. * String numeric magnitude.
  6513. * @type string
  6514. */
  6515. protected $n;
  6516. /**
  6517. * String unit. False is permitted if $n = 0.
  6518. * @type string|bool
  6519. */
  6520. protected $unit;
  6521. /**
  6522. * Whether or not this length is valid. Null if not calculated yet.
  6523. * @type bool
  6524. */
  6525. protected $isValid;
  6526. /**
  6527. * Array Lookup array of units recognized by CSS 2.1
  6528. * @type array
  6529. */
  6530. protected static $allowedUnits = array(
  6531. 'em' => true, 'ex' => true, 'px' => true, 'in' => true,
  6532. 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
  6533. );
  6534. /**
  6535. * @param string $n Magnitude
  6536. * @param bool|string $u Unit
  6537. */
  6538. public function __construct($n = '0', $u = false)
  6539. {
  6540. $this->n = (string) $n;
  6541. $this->unit = $u !== false ? (string) $u : false;
  6542. }
  6543. /**
  6544. * @param string $s Unit string, like '2em' or '3.4in'
  6545. * @return HTMLPurifier_Length
  6546. * @warning Does not perform validation.
  6547. */
  6548. public static function make($s)
  6549. {
  6550. if ($s instanceof HTMLPurifier_Length) {
  6551. return $s;
  6552. }
  6553. $n_length = strspn($s, '1234567890.+-');
  6554. $n = substr($s, 0, $n_length);
  6555. $unit = substr($s, $n_length);
  6556. if ($unit === '') {
  6557. $unit = false;
  6558. }
  6559. return new HTMLPurifier_Length($n, $unit);
  6560. }
  6561. /**
  6562. * Validates the number and unit.
  6563. * @return bool
  6564. */
  6565. protected function validate()
  6566. {
  6567. // Special case:
  6568. if ($this->n === '+0' || $this->n === '-0') {
  6569. $this->n = '0';
  6570. }
  6571. if ($this->n === '0' && $this->unit === false) {
  6572. return true;
  6573. }
  6574. if (!ctype_lower($this->unit)) {
  6575. $this->unit = strtolower($this->unit);
  6576. }
  6577. if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
  6578. return false;
  6579. }
  6580. // Hack:
  6581. $def = new HTMLPurifier_AttrDef_CSS_Number();
  6582. $result = $def->validate($this->n, false, false);
  6583. if ($result === false) {
  6584. return false;
  6585. }
  6586. $this->n = $result;
  6587. return true;
  6588. }
  6589. /**
  6590. * Returns string representation of number.
  6591. * @return string
  6592. */
  6593. public function toString()
  6594. {
  6595. if (!$this->isValid()) {
  6596. return false;
  6597. }
  6598. return $this->n . $this->unit;
  6599. }
  6600. /**
  6601. * Retrieves string numeric magnitude.
  6602. * @return string
  6603. */
  6604. public function getN()
  6605. {
  6606. return $this->n;
  6607. }
  6608. /**
  6609. * Retrieves string unit.
  6610. * @return string
  6611. */
  6612. public function getUnit()
  6613. {
  6614. return $this->unit;
  6615. }
  6616. /**
  6617. * Returns true if this length unit is valid.
  6618. * @return bool
  6619. */
  6620. public function isValid()
  6621. {
  6622. if ($this->isValid === null) {
  6623. $this->isValid = $this->validate();
  6624. }
  6625. return $this->isValid;
  6626. }
  6627. /**
  6628. * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
  6629. * @param HTMLPurifier_Length $l
  6630. * @return int
  6631. * @warning If both values are too large or small, this calculation will
  6632. * not work properly
  6633. */
  6634. public function compareTo($l)
  6635. {
  6636. if ($l === false) {
  6637. return false;
  6638. }
  6639. if ($l->unit !== $this->unit) {
  6640. $converter = new HTMLPurifier_UnitConverter();
  6641. $l = $converter->convert($l, $this->unit);
  6642. if ($l === false) {
  6643. return false;
  6644. }
  6645. }
  6646. return $this->n - $l->n;
  6647. }
  6648. }
  6649. /**
  6650. * Forgivingly lexes HTML (SGML-style) markup into tokens.
  6651. *
  6652. * A lexer parses a string of SGML-style markup and converts them into
  6653. * corresponding tokens. It doesn't check for well-formedness, although its
  6654. * internal mechanism may make this automatic (such as the case of
  6655. * HTMLPurifier_Lexer_DOMLex). There are several implementations to choose
  6656. * from.
  6657. *
  6658. * A lexer is HTML-oriented: it might work with XML, but it's not
  6659. * recommended, as we adhere to a subset of the specification for optimization
  6660. * reasons. This might change in the future. Also, most tokenizers are not
  6661. * expected to handle DTDs or PIs.
  6662. *
  6663. * This class should not be directly instantiated, but you may use create() to
  6664. * retrieve a default copy of the lexer. Being a supertype, this class
  6665. * does not actually define any implementation, but offers commonly used
  6666. * convenience functions for subclasses.
  6667. *
  6668. * @note The unit tests will instantiate this class for testing purposes, as
  6669. * many of the utility functions require a class to be instantiated.
  6670. * This means that, even though this class is not runnable, it will
  6671. * not be declared abstract.
  6672. *
  6673. * @par
  6674. *
  6675. * @note
  6676. * We use tokens rather than create a DOM representation because DOM would:
  6677. *
  6678. * @par
  6679. * -# Require more processing and memory to create,
  6680. * -# Is not streamable, and
  6681. * -# Has the entire document structure (html and body not needed).
  6682. *
  6683. * @par
  6684. * However, DOM is helpful in that it makes it easy to move around nodes
  6685. * without a lot of lookaheads to see when a tag is closed. This is a
  6686. * limitation of the token system and some workarounds would be nice.
  6687. */
  6688. class HTMLPurifier_Lexer
  6689. {
  6690. /**
  6691. * Whether or not this lexer implements line-number/column-number tracking.
  6692. * If it does, set to true.
  6693. */
  6694. public $tracksLineNumbers = false;
  6695. // -- STATIC ----------------------------------------------------------
  6696. /**
  6697. * Retrieves or sets the default Lexer as a Prototype Factory.
  6698. *
  6699. * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
  6700. * a few exceptions involving special features that only DirectLex
  6701. * implements.
  6702. *
  6703. * @note The behavior of this class has changed, rather than accepting
  6704. * a prototype object, it now accepts a configuration object.
  6705. * To specify your own prototype, set %Core.LexerImpl to it.
  6706. * This change in behavior de-singletonizes the lexer object.
  6707. *
  6708. * @param HTMLPurifier_Config $config
  6709. * @return HTMLPurifier_Lexer
  6710. * @throws HTMLPurifier_Exception
  6711. */
  6712. public static function create($config)
  6713. {
  6714. if (!($config instanceof HTMLPurifier_Config)) {
  6715. $lexer = $config;
  6716. trigger_error(
  6717. "Passing a prototype to
  6718. HTMLPurifier_Lexer::create() is deprecated, please instead
  6719. use %Core.LexerImpl",
  6720. E_USER_WARNING
  6721. );
  6722. } else {
  6723. $lexer = $config->get('Core.LexerImpl');
  6724. }
  6725. $needs_tracking =
  6726. $config->get('Core.MaintainLineNumbers') ||
  6727. $config->get('Core.CollectErrors');
  6728. $inst = null;
  6729. if (is_object($lexer)) {
  6730. $inst = $lexer;
  6731. } else {
  6732. if (is_null($lexer)) {
  6733. do {
  6734. // auto-detection algorithm
  6735. if ($needs_tracking) {
  6736. $lexer = 'DirectLex';
  6737. break;
  6738. }
  6739. if (class_exists('DOMDocument') &&
  6740. method_exists('DOMDocument', 'loadHTML') &&
  6741. !extension_loaded('domxml')
  6742. ) {
  6743. // check for DOM support, because while it's part of the
  6744. // core, it can be disabled compile time. Also, the PECL
  6745. // domxml extension overrides the default DOM, and is evil
  6746. // and nasty and we shan't bother to support it
  6747. $lexer = 'DOMLex';
  6748. } else {
  6749. $lexer = 'DirectLex';
  6750. }
  6751. } while (0);
  6752. } // do..while so we can break
  6753. // instantiate recognized string names
  6754. switch ($lexer) {
  6755. case 'DOMLex':
  6756. $inst = new HTMLPurifier_Lexer_DOMLex();
  6757. break;
  6758. case 'DirectLex':
  6759. $inst = new HTMLPurifier_Lexer_DirectLex();
  6760. break;
  6761. case 'PH5P':
  6762. $inst = new HTMLPurifier_Lexer_PH5P();
  6763. break;
  6764. default:
  6765. throw new HTMLPurifier_Exception(
  6766. "Cannot instantiate unrecognized Lexer type " .
  6767. htmlspecialchars($lexer)
  6768. );
  6769. }
  6770. }
  6771. if (!$inst) {
  6772. throw new HTMLPurifier_Exception('No lexer was instantiated');
  6773. }
  6774. // once PHP DOM implements native line numbers, or we
  6775. // hack out something using XSLT, remove this stipulation
  6776. if ($needs_tracking && !$inst->tracksLineNumbers) {
  6777. throw new HTMLPurifier_Exception(
  6778. 'Cannot use lexer that does not support line numbers with ' .
  6779. 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
  6780. );
  6781. }
  6782. return $inst;
  6783. }
  6784. // -- CONVENIENCE MEMBERS ---------------------------------------------
  6785. public function __construct()
  6786. {
  6787. $this->_entity_parser = new HTMLPurifier_EntityParser();
  6788. }
  6789. /**
  6790. * Most common entity to raw value conversion table for special entities.
  6791. * @type array
  6792. */
  6793. protected $_special_entity2str =
  6794. array(
  6795. '&quot;' => '"',
  6796. '&amp;' => '&',
  6797. '&lt;' => '<',
  6798. '&gt;' => '>',
  6799. '&#39;' => "'",
  6800. '&#039;' => "'",
  6801. '&#x27;' => "'"
  6802. );
  6803. /**
  6804. * Parses special entities into the proper characters.
  6805. *
  6806. * This string will translate escaped versions of the special characters
  6807. * into the correct ones.
  6808. *
  6809. * @warning
  6810. * You should be able to treat the output of this function as
  6811. * completely parsed, but that's only because all other entities should
  6812. * have been handled previously in substituteNonSpecialEntities()
  6813. *
  6814. * @param string $string String character data to be parsed.
  6815. * @return string Parsed character data.
  6816. */
  6817. public function parseData($string)
  6818. {
  6819. // following functions require at least one character
  6820. if ($string === '') {
  6821. return '';
  6822. }
  6823. // subtracts amps that cannot possibly be escaped
  6824. $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
  6825. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  6826. if (!$num_amp) {
  6827. return $string;
  6828. } // abort if no entities
  6829. $num_esc_amp = substr_count($string, '&amp;');
  6830. $string = strtr($string, $this->_special_entity2str);
  6831. // code duplication for sake of optimization, see above
  6832. $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
  6833. ($string[strlen($string) - 1] === '&' ? 1 : 0);
  6834. if ($num_amp_2 <= $num_esc_amp) {
  6835. return $string;
  6836. }
  6837. // hmm... now we have some uncommon entities. Use the callback.
  6838. $string = $this->_entity_parser->substituteSpecialEntities($string);
  6839. return $string;
  6840. }
  6841. /**
  6842. * Lexes an HTML string into tokens.
  6843. * @param $string String HTML.
  6844. * @param HTMLPurifier_Config $config
  6845. * @param HTMLPurifier_Context $context
  6846. * @return HTMLPurifier_Token[] array representation of HTML.
  6847. */
  6848. public function tokenizeHTML($string, $config, $context)
  6849. {
  6850. trigger_error('Call to abstract class', E_USER_ERROR);
  6851. }
  6852. /**
  6853. * Translates CDATA sections into regular sections (through escaping).
  6854. * @param string $string HTML string to process.
  6855. * @return string HTML with CDATA sections escaped.
  6856. */
  6857. protected static function escapeCDATA($string)
  6858. {
  6859. return preg_replace_callback(
  6860. '/<!\[CDATA\[(.+?)\]\]>/s',
  6861. array('HTMLPurifier_Lexer', 'CDATACallback'),
  6862. $string
  6863. );
  6864. }
  6865. /**
  6866. * Special CDATA case that is especially convoluted for <script>
  6867. * @param string $string HTML string to process.
  6868. * @return string HTML with CDATA sections escaped.
  6869. */
  6870. protected static function escapeCommentedCDATA($string)
  6871. {
  6872. return preg_replace_callback(
  6873. '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
  6874. array('HTMLPurifier_Lexer', 'CDATACallback'),
  6875. $string
  6876. );
  6877. }
  6878. /**
  6879. * Special Internet Explorer conditional comments should be removed.
  6880. * @param string $string HTML string to process.
  6881. * @return string HTML with conditional comments removed.
  6882. */
  6883. protected static function removeIEConditional($string)
  6884. {
  6885. return preg_replace(
  6886. '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
  6887. '',
  6888. $string
  6889. );
  6890. }
  6891. /**
  6892. * Callback function for escapeCDATA() that does the work.
  6893. *
  6894. * @warning Though this is public in order to let the callback happen,
  6895. * calling it directly is not recommended.
  6896. * @param array $matches PCRE matches array, with index 0 the entire match
  6897. * and 1 the inside of the CDATA section.
  6898. * @return string Escaped internals of the CDATA section.
  6899. */
  6900. protected static function CDATACallback($matches)
  6901. {
  6902. // not exactly sure why the character set is needed, but whatever
  6903. return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
  6904. }
  6905. /**
  6906. * Takes a piece of HTML and normalizes it by converting entities, fixing
  6907. * encoding, extracting bits, and other good stuff.
  6908. * @param string $html HTML.
  6909. * @param HTMLPurifier_Config $config
  6910. * @param HTMLPurifier_Context $context
  6911. * @return string
  6912. * @todo Consider making protected
  6913. */
  6914. public function normalize($html, $config, $context)
  6915. {
  6916. // normalize newlines to \n
  6917. if ($config->get('Core.NormalizeNewlines')) {
  6918. $html = str_replace("\r\n", "\n", $html);
  6919. $html = str_replace("\r", "\n", $html);
  6920. }
  6921. if ($config->get('HTML.Trusted')) {
  6922. // escape convoluted CDATA
  6923. $html = $this->escapeCommentedCDATA($html);
  6924. }
  6925. // escape CDATA
  6926. $html = $this->escapeCDATA($html);
  6927. $html = $this->removeIEConditional($html);
  6928. // extract body from document if applicable
  6929. if ($config->get('Core.ConvertDocumentToFragment')) {
  6930. $e = false;
  6931. if ($config->get('Core.CollectErrors')) {
  6932. $e =& $context->get('ErrorCollector');
  6933. }
  6934. $new_html = $this->extractBody($html);
  6935. if ($e && $new_html != $html) {
  6936. $e->send(E_WARNING, 'Lexer: Extracted body');
  6937. }
  6938. $html = $new_html;
  6939. }
  6940. // expand entities that aren't the big five
  6941. $html = $this->_entity_parser->substituteNonSpecialEntities($html);
  6942. // clean into wellformed UTF-8 string for an SGML context: this has
  6943. // to be done after entity expansion because the entities sometimes
  6944. // represent non-SGML characters (horror, horror!)
  6945. $html = HTMLPurifier_Encoder::cleanUTF8($html);
  6946. // if processing instructions are to removed, remove them now
  6947. if ($config->get('Core.RemoveProcessingInstructions')) {
  6948. $html = preg_replace('#<\?.+?\?>#s', '', $html);
  6949. }
  6950. return $html;
  6951. }
  6952. /**
  6953. * Takes a string of HTML (fragment or document) and returns the content
  6954. * @todo Consider making protected
  6955. */
  6956. public function extractBody($html)
  6957. {
  6958. $matches = array();
  6959. $result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches);
  6960. if ($result) {
  6961. return $matches[1];
  6962. } else {
  6963. return $html;
  6964. }
  6965. }
  6966. }
  6967. /**
  6968. * Abstract base node class that all others inherit from.
  6969. *
  6970. * Why do we not use the DOM extension? (1) It is not always available,
  6971. * (2) it has funny constraints on the data it can represent,
  6972. * whereas we want a maximally flexible representation, and (3) its
  6973. * interface is a bit cumbersome.
  6974. */
  6975. abstract class HTMLPurifier_Node
  6976. {
  6977. /**
  6978. * Line number of the start token in the source document
  6979. * @type int
  6980. */
  6981. public $line;
  6982. /**
  6983. * Column number of the start token in the source document. Null if unknown.
  6984. * @type int
  6985. */
  6986. public $col;
  6987. /**
  6988. * Lookup array of processing that this token is exempt from.
  6989. * Currently, valid values are "ValidateAttributes".
  6990. * @type array
  6991. */
  6992. public $armor = array();
  6993. /**
  6994. * When true, this node should be ignored as non-existent.
  6995. *
  6996. * Who is responsible for ignoring dead nodes? FixNesting is
  6997. * responsible for removing them before passing on to child
  6998. * validators.
  6999. */
  7000. public $dead = false;
  7001. /**
  7002. * Returns a pair of start and end tokens, where the end token
  7003. * is null if it is not necessary. Does not include children.
  7004. * @type array
  7005. */
  7006. abstract public function toTokenPair();
  7007. }
  7008. /**
  7009. * Class that handles operations involving percent-encoding in URIs.
  7010. *
  7011. * @warning
  7012. * Be careful when reusing instances of PercentEncoder. The object
  7013. * you use for normalize() SHOULD NOT be used for encode(), or
  7014. * vice-versa.
  7015. */
  7016. class HTMLPurifier_PercentEncoder
  7017. {
  7018. /**
  7019. * Reserved characters to preserve when using encode().
  7020. * @type array
  7021. */
  7022. protected $preserve = array();
  7023. /**
  7024. * String of characters that should be preserved while using encode().
  7025. * @param bool $preserve
  7026. */
  7027. public function __construct($preserve = false)
  7028. {
  7029. // unreserved letters, ought to const-ify
  7030. for ($i = 48; $i <= 57; $i++) { // digits
  7031. $this->preserve[$i] = true;
  7032. }
  7033. for ($i = 65; $i <= 90; $i++) { // upper-case
  7034. $this->preserve[$i] = true;
  7035. }
  7036. for ($i = 97; $i <= 122; $i++) { // lower-case
  7037. $this->preserve[$i] = true;
  7038. }
  7039. $this->preserve[45] = true; // Dash -
  7040. $this->preserve[46] = true; // Period .
  7041. $this->preserve[95] = true; // Underscore _
  7042. $this->preserve[126]= true; // Tilde ~
  7043. // extra letters not to escape
  7044. if ($preserve !== false) {
  7045. for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
  7046. $this->preserve[ord($preserve[$i])] = true;
  7047. }
  7048. }
  7049. }
  7050. /**
  7051. * Our replacement for urlencode, it encodes all non-reserved characters,
  7052. * as well as any extra characters that were instructed to be preserved.
  7053. * @note
  7054. * Assumes that the string has already been normalized, making any
  7055. * and all percent escape sequences valid. Percents will not be
  7056. * re-escaped, regardless of their status in $preserve
  7057. * @param string $string String to be encoded
  7058. * @return string Encoded string.
  7059. */
  7060. public function encode($string)
  7061. {
  7062. $ret = '';
  7063. for ($i = 0, $c = strlen($string); $i < $c; $i++) {
  7064. if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
  7065. $ret .= '%' . sprintf('%02X', $int);
  7066. } else {
  7067. $ret .= $string[$i];
  7068. }
  7069. }
  7070. return $ret;
  7071. }
  7072. /**
  7073. * Fix up percent-encoding by decoding unreserved characters and normalizing.
  7074. * @warning This function is affected by $preserve, even though the
  7075. * usual desired behavior is for this not to preserve those
  7076. * characters. Be careful when reusing instances of PercentEncoder!
  7077. * @param string $string String to normalize
  7078. * @return string
  7079. */
  7080. public function normalize($string)
  7081. {
  7082. if ($string == '') {
  7083. return '';
  7084. }
  7085. $parts = explode('%', $string);
  7086. $ret = array_shift($parts);
  7087. foreach ($parts as $part) {
  7088. $length = strlen($part);
  7089. if ($length < 2) {
  7090. $ret .= '%25' . $part;
  7091. continue;
  7092. }
  7093. $encoding = substr($part, 0, 2);
  7094. $text = substr($part, 2);
  7095. if (!ctype_xdigit($encoding)) {
  7096. $ret .= '%25' . $part;
  7097. continue;
  7098. }
  7099. $int = hexdec($encoding);
  7100. if (isset($this->preserve[$int])) {
  7101. $ret .= chr($int) . $text;
  7102. continue;
  7103. }
  7104. $encoding = strtoupper($encoding);
  7105. $ret .= '%' . $encoding . $text;
  7106. }
  7107. return $ret;
  7108. }
  7109. }
  7110. /**
  7111. * Generic property list implementation
  7112. */
  7113. class HTMLPurifier_PropertyList
  7114. {
  7115. /**
  7116. * Internal data-structure for properties.
  7117. * @type array
  7118. */
  7119. protected $data = array();
  7120. /**
  7121. * Parent plist.
  7122. * @type HTMLPurifier_PropertyList
  7123. */
  7124. protected $parent;
  7125. /**
  7126. * Cache.
  7127. * @type array
  7128. */
  7129. protected $cache;
  7130. /**
  7131. * @param HTMLPurifier_PropertyList $parent Parent plist
  7132. */
  7133. public function __construct($parent = null)
  7134. {
  7135. $this->parent = $parent;
  7136. }
  7137. /**
  7138. * Recursively retrieves the value for a key
  7139. * @param string $name
  7140. * @throws HTMLPurifier_Exception
  7141. */
  7142. public function get($name)
  7143. {
  7144. if ($this->has($name)) {
  7145. return $this->data[$name];
  7146. }
  7147. // possible performance bottleneck, convert to iterative if necessary
  7148. if ($this->parent) {
  7149. return $this->parent->get($name);
  7150. }
  7151. throw new HTMLPurifier_Exception("Key '$name' not found");
  7152. }
  7153. /**
  7154. * Sets the value of a key, for this plist
  7155. * @param string $name
  7156. * @param mixed $value
  7157. */
  7158. public function set($name, $value)
  7159. {
  7160. $this->data[$name] = $value;
  7161. }
  7162. /**
  7163. * Returns true if a given key exists
  7164. * @param string $name
  7165. * @return bool
  7166. */
  7167. public function has($name)
  7168. {
  7169. return array_key_exists($name, $this->data);
  7170. }
  7171. /**
  7172. * Resets a value to the value of it's parent, usually the default. If
  7173. * no value is specified, the entire plist is reset.
  7174. * @param string $name
  7175. */
  7176. public function reset($name = null)
  7177. {
  7178. if ($name == null) {
  7179. $this->data = array();
  7180. } else {
  7181. unset($this->data[$name]);
  7182. }
  7183. }
  7184. /**
  7185. * Squashes this property list and all of its property lists into a single
  7186. * array, and returns the array. This value is cached by default.
  7187. * @param bool $force If true, ignores the cache and regenerates the array.
  7188. * @return array
  7189. */
  7190. public function squash($force = false)
  7191. {
  7192. if ($this->cache !== null && !$force) {
  7193. return $this->cache;
  7194. }
  7195. if ($this->parent) {
  7196. return $this->cache = array_merge($this->parent->squash($force), $this->data);
  7197. } else {
  7198. return $this->cache = $this->data;
  7199. }
  7200. }
  7201. /**
  7202. * Returns the parent plist.
  7203. * @return HTMLPurifier_PropertyList
  7204. */
  7205. public function getParent()
  7206. {
  7207. return $this->parent;
  7208. }
  7209. /**
  7210. * Sets the parent plist.
  7211. * @param HTMLPurifier_PropertyList $plist Parent plist
  7212. */
  7213. public function setParent($plist)
  7214. {
  7215. $this->parent = $plist;
  7216. }
  7217. }
  7218. /**
  7219. * Property list iterator. Do not instantiate this class directly.
  7220. */
  7221. class HTMLPurifier_PropertyListIterator extends FilterIterator
  7222. {
  7223. /**
  7224. * @type int
  7225. */
  7226. protected $l;
  7227. /**
  7228. * @type string
  7229. */
  7230. protected $filter;
  7231. /**
  7232. * @param Iterator $iterator Array of data to iterate over
  7233. * @param string $filter Optional prefix to only allow values of
  7234. */
  7235. public function __construct(Iterator $iterator, $filter = null)
  7236. {
  7237. parent::__construct($iterator);
  7238. $this->l = strlen($filter);
  7239. $this->filter = $filter;
  7240. }
  7241. /**
  7242. * @return bool
  7243. */
  7244. public function accept()
  7245. {
  7246. $key = $this->getInnerIterator()->key();
  7247. if (strncmp($key, $this->filter, $this->l) !== 0) {
  7248. return false;
  7249. }
  7250. return true;
  7251. }
  7252. }
  7253. /**
  7254. * A simple array-backed queue, based off of the classic Okasaki
  7255. * persistent amortized queue. The basic idea is to maintain two
  7256. * stacks: an input stack and an output stack. When the output
  7257. * stack runs out, reverse the input stack and use it as the output
  7258. * stack.
  7259. *
  7260. * We don't use the SPL implementation because it's only supported
  7261. * on PHP 5.3 and later.
  7262. *
  7263. * Exercise: Prove that push/pop on this queue take amortized O(1) time.
  7264. *
  7265. * Exercise: Extend this queue to be a deque, while preserving amortized
  7266. * O(1) time. Some care must be taken on rebalancing to avoid quadratic
  7267. * behaviour caused by repeatedly shuffling data from the input stack
  7268. * to the output stack and back.
  7269. */
  7270. class HTMLPurifier_Queue {
  7271. private $input;
  7272. private $output;
  7273. public function __construct($input = array()) {
  7274. $this->input = $input;
  7275. $this->output = array();
  7276. }
  7277. /**
  7278. * Shifts an element off the front of the queue.
  7279. */
  7280. public function shift() {
  7281. if (empty($this->output)) {
  7282. $this->output = array_reverse($this->input);
  7283. $this->input = array();
  7284. }
  7285. if (empty($this->output)) {
  7286. return NULL;
  7287. }
  7288. return array_pop($this->output);
  7289. }
  7290. /**
  7291. * Pushes an element onto the front of the queue.
  7292. */
  7293. public function push($x) {
  7294. array_push($this->input, $x);
  7295. }
  7296. /**
  7297. * Checks if it's empty.
  7298. */
  7299. public function isEmpty() {
  7300. return empty($this->input) && empty($this->output);
  7301. }
  7302. }
  7303. /**
  7304. * Supertype for classes that define a strategy for modifying/purifying tokens.
  7305. *
  7306. * While HTMLPurifier's core purpose is fixing HTML into something proper,
  7307. * strategies provide plug points for extra configuration or even extra
  7308. * features, such as custom tags, custom parsing of text, etc.
  7309. */
  7310. abstract class HTMLPurifier_Strategy
  7311. {
  7312. /**
  7313. * Executes the strategy on the tokens.
  7314. *
  7315. * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
  7316. * @param HTMLPurifier_Config $config
  7317. * @param HTMLPurifier_Context $context
  7318. * @return HTMLPurifier_Token[] Processed array of token objects.
  7319. */
  7320. abstract public function execute($tokens, $config, $context);
  7321. }
  7322. /**
  7323. * This is in almost every respect equivalent to an array except
  7324. * that it keeps track of which keys were accessed.
  7325. *
  7326. * @warning For the sake of backwards compatibility with early versions
  7327. * of PHP 5, you must not use the $hash[$key] syntax; if you do
  7328. * our version of offsetGet is never called.
  7329. */
  7330. class HTMLPurifier_StringHash extends ArrayObject
  7331. {
  7332. /**
  7333. * @type array
  7334. */
  7335. protected $accessed = array();
  7336. /**
  7337. * Retrieves a value, and logs the access.
  7338. * @param mixed $index
  7339. * @return mixed
  7340. */
  7341. public function offsetGet($index)
  7342. {
  7343. $this->accessed[$index] = true;
  7344. return parent::offsetGet($index);
  7345. }
  7346. /**
  7347. * Returns a lookup array of all array indexes that have been accessed.
  7348. * @return array in form array($index => true).
  7349. */
  7350. public function getAccessed()
  7351. {
  7352. return $this->accessed;
  7353. }
  7354. /**
  7355. * Resets the access array.
  7356. */
  7357. public function resetAccessed()
  7358. {
  7359. $this->accessed = array();
  7360. }
  7361. }
  7362. /**
  7363. * Parses string hash files. File format is as such:
  7364. *
  7365. * DefaultKeyValue
  7366. * KEY: Value
  7367. * KEY2: Value2
  7368. * --MULTILINE-KEY--
  7369. * Multiline
  7370. * value.
  7371. *
  7372. * Which would output something similar to:
  7373. *
  7374. * array(
  7375. * 'ID' => 'DefaultKeyValue',
  7376. * 'KEY' => 'Value',
  7377. * 'KEY2' => 'Value2',
  7378. * 'MULTILINE-KEY' => "Multiline\nvalue.\n",
  7379. * )
  7380. *
  7381. * We use this as an easy to use file-format for configuration schema
  7382. * files, but the class itself is usage agnostic.
  7383. *
  7384. * You can use ---- to forcibly terminate parsing of a single string-hash;
  7385. * this marker is used in multi string-hashes to delimit boundaries.
  7386. */
  7387. class HTMLPurifier_StringHashParser
  7388. {
  7389. /**
  7390. * @type string
  7391. */
  7392. public $default = 'ID';
  7393. /**
  7394. * Parses a file that contains a single string-hash.
  7395. * @param string $file
  7396. * @return array
  7397. */
  7398. public function parseFile($file)
  7399. {
  7400. if (!file_exists($file)) {
  7401. return false;
  7402. }
  7403. $fh = fopen($file, 'r');
  7404. if (!$fh) {
  7405. return false;
  7406. }
  7407. $ret = $this->parseHandle($fh);
  7408. fclose($fh);
  7409. return $ret;
  7410. }
  7411. /**
  7412. * Parses a file that contains multiple string-hashes delimited by '----'
  7413. * @param string $file
  7414. * @return array
  7415. */
  7416. public function parseMultiFile($file)
  7417. {
  7418. if (!file_exists($file)) {
  7419. return false;
  7420. }
  7421. $ret = array();
  7422. $fh = fopen($file, 'r');
  7423. if (!$fh) {
  7424. return false;
  7425. }
  7426. while (!feof($fh)) {
  7427. $ret[] = $this->parseHandle($fh);
  7428. }
  7429. fclose($fh);
  7430. return $ret;
  7431. }
  7432. /**
  7433. * Internal parser that acepts a file handle.
  7434. * @note While it's possible to simulate in-memory parsing by using
  7435. * custom stream wrappers, if such a use-case arises we should
  7436. * factor out the file handle into its own class.
  7437. * @param resource $fh File handle with pointer at start of valid string-hash
  7438. * block.
  7439. * @return array
  7440. */
  7441. protected function parseHandle($fh)
  7442. {
  7443. $state = false;
  7444. $single = false;
  7445. $ret = array();
  7446. do {
  7447. $line = fgets($fh);
  7448. if ($line === false) {
  7449. break;
  7450. }
  7451. $line = rtrim($line, "\n\r");
  7452. if (!$state && $line === '') {
  7453. continue;
  7454. }
  7455. if ($line === '----') {
  7456. break;
  7457. }
  7458. if (strncmp('--#', $line, 3) === 0) {
  7459. // Comment
  7460. continue;
  7461. } elseif (strncmp('--', $line, 2) === 0) {
  7462. // Multiline declaration
  7463. $state = trim($line, '- ');
  7464. if (!isset($ret[$state])) {
  7465. $ret[$state] = '';
  7466. }
  7467. continue;
  7468. } elseif (!$state) {
  7469. $single = true;
  7470. if (strpos($line, ':') !== false) {
  7471. // Single-line declaration
  7472. list($state, $line) = explode(':', $line, 2);
  7473. $line = trim($line);
  7474. } else {
  7475. // Use default declaration
  7476. $state = $this->default;
  7477. }
  7478. }
  7479. if ($single) {
  7480. $ret[$state] = $line;
  7481. $single = false;
  7482. $state = false;
  7483. } else {
  7484. $ret[$state] .= "$line\n";
  7485. }
  7486. } while (!feof($fh));
  7487. return $ret;
  7488. }
  7489. }
  7490. /**
  7491. * Defines a mutation of an obsolete tag into a valid tag.
  7492. */
  7493. abstract class HTMLPurifier_TagTransform
  7494. {
  7495. /**
  7496. * Tag name to transform the tag to.
  7497. * @type string
  7498. */
  7499. public $transform_to;
  7500. /**
  7501. * Transforms the obsolete tag into the valid tag.
  7502. * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
  7503. * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
  7504. * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
  7505. */
  7506. abstract public function transform($tag, $config, $context);
  7507. /**
  7508. * Prepends CSS properties to the style attribute, creating the
  7509. * attribute if it doesn't exist.
  7510. * @warning Copied over from AttrTransform, be sure to keep in sync
  7511. * @param array $attr Attribute array to process (passed by reference)
  7512. * @param string $css CSS to prepend
  7513. */
  7514. protected function prependCSS(&$attr, $css)
  7515. {
  7516. $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
  7517. $attr['style'] = $css . $attr['style'];
  7518. }
  7519. }
  7520. /**
  7521. * Abstract base token class that all others inherit from.
  7522. */
  7523. abstract class HTMLPurifier_Token
  7524. {
  7525. /**
  7526. * Line number node was on in source document. Null if unknown.
  7527. * @type int
  7528. */
  7529. public $line;
  7530. /**
  7531. * Column of line node was on in source document. Null if unknown.
  7532. * @type int
  7533. */
  7534. public $col;
  7535. /**
  7536. * Lookup array of processing that this token is exempt from.
  7537. * Currently, valid values are "ValidateAttributes" and
  7538. * "MakeWellFormed_TagClosedError"
  7539. * @type array
  7540. */
  7541. public $armor = array();
  7542. /**
  7543. * Used during MakeWellFormed.
  7544. * @type
  7545. */
  7546. public $skip;
  7547. /**
  7548. * @type
  7549. */
  7550. public $rewind;
  7551. /**
  7552. * @type
  7553. */
  7554. public $carryover;
  7555. /**
  7556. * @param string $n
  7557. * @return null|string
  7558. */
  7559. public function __get($n)
  7560. {
  7561. if ($n === 'type') {
  7562. trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
  7563. switch (get_class($this)) {
  7564. case 'HTMLPurifier_Token_Start':
  7565. return 'start';
  7566. case 'HTMLPurifier_Token_Empty':
  7567. return 'empty';
  7568. case 'HTMLPurifier_Token_End':
  7569. return 'end';
  7570. case 'HTMLPurifier_Token_Text':
  7571. return 'text';
  7572. case 'HTMLPurifier_Token_Comment':
  7573. return 'comment';
  7574. default:
  7575. return null;
  7576. }
  7577. }
  7578. }
  7579. /**
  7580. * Sets the position of the token in the source document.
  7581. * @param int $l
  7582. * @param int $c
  7583. */
  7584. public function position($l = null, $c = null)
  7585. {
  7586. $this->line = $l;
  7587. $this->col = $c;
  7588. }
  7589. /**
  7590. * Convenience function for DirectLex settings line/col position.
  7591. * @param int $l
  7592. * @param int $c
  7593. */
  7594. public function rawPosition($l, $c)
  7595. {
  7596. if ($c === -1) {
  7597. $l++;
  7598. }
  7599. $this->line = $l;
  7600. $this->col = $c;
  7601. }
  7602. /**
  7603. * Converts a token into its corresponding node.
  7604. */
  7605. abstract public function toNode();
  7606. }
  7607. /**
  7608. * Factory for token generation.
  7609. *
  7610. * @note Doing some benchmarking indicates that the new operator is much
  7611. * slower than the clone operator (even discounting the cost of the
  7612. * constructor). This class is for that optimization.
  7613. * Other then that, there's not much point as we don't
  7614. * maintain parallel HTMLPurifier_Token hierarchies (the main reason why
  7615. * you'd want to use an abstract factory).
  7616. * @todo Port DirectLex to use this
  7617. */
  7618. class HTMLPurifier_TokenFactory
  7619. {
  7620. // p stands for prototype
  7621. /**
  7622. * @type HTMLPurifier_Token_Start
  7623. */
  7624. private $p_start;
  7625. /**
  7626. * @type HTMLPurifier_Token_End
  7627. */
  7628. private $p_end;
  7629. /**
  7630. * @type HTMLPurifier_Token_Empty
  7631. */
  7632. private $p_empty;
  7633. /**
  7634. * @type HTMLPurifier_Token_Text
  7635. */
  7636. private $p_text;
  7637. /**
  7638. * @type HTMLPurifier_Token_Comment
  7639. */
  7640. private $p_comment;
  7641. /**
  7642. * Generates blank prototypes for cloning.
  7643. */
  7644. public function __construct()
  7645. {
  7646. $this->p_start = new HTMLPurifier_Token_Start('', array());
  7647. $this->p_end = new HTMLPurifier_Token_End('');
  7648. $this->p_empty = new HTMLPurifier_Token_Empty('', array());
  7649. $this->p_text = new HTMLPurifier_Token_Text('');
  7650. $this->p_comment = new HTMLPurifier_Token_Comment('');
  7651. }
  7652. /**
  7653. * Creates a HTMLPurifier_Token_Start.
  7654. * @param string $name Tag name
  7655. * @param array $attr Associative array of attributes
  7656. * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
  7657. */
  7658. public function createStart($name, $attr = array())
  7659. {
  7660. $p = clone $this->p_start;
  7661. $p->__construct($name, $attr);
  7662. return $p;
  7663. }
  7664. /**
  7665. * Creates a HTMLPurifier_Token_End.
  7666. * @param string $name Tag name
  7667. * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
  7668. */
  7669. public function createEnd($name)
  7670. {
  7671. $p = clone $this->p_end;
  7672. $p->__construct($name);
  7673. return $p;
  7674. }
  7675. /**
  7676. * Creates a HTMLPurifier_Token_Empty.
  7677. * @param string $name Tag name
  7678. * @param array $attr Associative array of attributes
  7679. * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
  7680. */
  7681. public function createEmpty($name, $attr = array())
  7682. {
  7683. $p = clone $this->p_empty;
  7684. $p->__construct($name, $attr);
  7685. return $p;
  7686. }
  7687. /**
  7688. * Creates a HTMLPurifier_Token_Text.
  7689. * @param string $data Data of text token
  7690. * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
  7691. */
  7692. public function createText($data)
  7693. {
  7694. $p = clone $this->p_text;
  7695. $p->__construct($data);
  7696. return $p;
  7697. }
  7698. /**
  7699. * Creates a HTMLPurifier_Token_Comment.
  7700. * @param string $data Data of comment token
  7701. * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
  7702. */
  7703. public function createComment($data)
  7704. {
  7705. $p = clone $this->p_comment;
  7706. $p->__construct($data);
  7707. return $p;
  7708. }
  7709. }
  7710. /**
  7711. * HTML Purifier's internal representation of a URI.
  7712. * @note
  7713. * Internal data-structures are completely escaped. If the data needs
  7714. * to be used in a non-URI context (which is very unlikely), be sure
  7715. * to decode it first. The URI may not necessarily be well-formed until
  7716. * validate() is called.
  7717. */
  7718. class HTMLPurifier_URI
  7719. {
  7720. /**
  7721. * @type string
  7722. */
  7723. public $scheme;
  7724. /**
  7725. * @type string
  7726. */
  7727. public $userinfo;
  7728. /**
  7729. * @type string
  7730. */
  7731. public $host;
  7732. /**
  7733. * @type int
  7734. */
  7735. public $port;
  7736. /**
  7737. * @type string
  7738. */
  7739. public $path;
  7740. /**
  7741. * @type string
  7742. */
  7743. public $query;
  7744. /**
  7745. * @type string
  7746. */
  7747. public $fragment;
  7748. /**
  7749. * @param string $scheme
  7750. * @param string $userinfo
  7751. * @param string $host
  7752. * @param int $port
  7753. * @param string $path
  7754. * @param string $query
  7755. * @param string $fragment
  7756. * @note Automatically normalizes scheme and port
  7757. */
  7758. public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
  7759. {
  7760. $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
  7761. $this->userinfo = $userinfo;
  7762. $this->host = $host;
  7763. $this->port = is_null($port) ? $port : (int)$port;
  7764. $this->path = $path;
  7765. $this->query = $query;
  7766. $this->fragment = $fragment;
  7767. }
  7768. /**
  7769. * Retrieves a scheme object corresponding to the URI's scheme/default
  7770. * @param HTMLPurifier_Config $config
  7771. * @param HTMLPurifier_Context $context
  7772. * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
  7773. */
  7774. public function getSchemeObj($config, $context)
  7775. {
  7776. $registry = HTMLPurifier_URISchemeRegistry::instance();
  7777. if ($this->scheme !== null) {
  7778. $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
  7779. if (!$scheme_obj) {
  7780. return false;
  7781. } // invalid scheme, clean it out
  7782. } else {
  7783. // no scheme: retrieve the default one
  7784. $def = $config->getDefinition('URI');
  7785. $scheme_obj = $def->getDefaultScheme($config, $context);
  7786. if (!$scheme_obj) {
  7787. // something funky happened to the default scheme object
  7788. trigger_error(
  7789. 'Default scheme object "' . $def->defaultScheme . '" was not readable',
  7790. E_USER_WARNING
  7791. );
  7792. return false;
  7793. }
  7794. }
  7795. return $scheme_obj;
  7796. }
  7797. /**
  7798. * Generic validation method applicable for all schemes. May modify
  7799. * this URI in order to get it into a compliant form.
  7800. * @param HTMLPurifier_Config $config
  7801. * @param HTMLPurifier_Context $context
  7802. * @return bool True if validation/filtering succeeds, false if failure
  7803. */
  7804. public function validate($config, $context)
  7805. {
  7806. // ABNF definitions from RFC 3986
  7807. $chars_sub_delims = '!$&\'()*+,;=';
  7808. $chars_gen_delims = ':/?#[]@';
  7809. $chars_pchar = $chars_sub_delims . ':@';
  7810. // validate host
  7811. if (!is_null($this->host)) {
  7812. $host_def = new HTMLPurifier_AttrDef_URI_Host();
  7813. $this->host = $host_def->validate($this->host, $config, $context);
  7814. if ($this->host === false) {
  7815. $this->host = null;
  7816. }
  7817. }
  7818. // validate scheme
  7819. // NOTE: It's not appropriate to check whether or not this
  7820. // scheme is in our registry, since a URIFilter may convert a
  7821. // URI that we don't allow into one we do. So instead, we just
  7822. // check if the scheme can be dropped because there is no host
  7823. // and it is our default scheme.
  7824. if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
  7825. // support for relative paths is pretty abysmal when the
  7826. // scheme is present, so axe it when possible
  7827. $def = $config->getDefinition('URI');
  7828. if ($def->defaultScheme === $this->scheme) {
  7829. $this->scheme = null;
  7830. }
  7831. }
  7832. // validate username
  7833. if (!is_null($this->userinfo)) {
  7834. $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
  7835. $this->userinfo = $encoder->encode($this->userinfo);
  7836. }
  7837. // validate port
  7838. if (!is_null($this->port)) {
  7839. if ($this->port < 1 || $this->port > 65535) {
  7840. $this->port = null;
  7841. }
  7842. }
  7843. // validate path
  7844. $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
  7845. if (!is_null($this->host)) { // this catches $this->host === ''
  7846. // path-abempty (hier and relative)
  7847. // http://www.example.com/my/path
  7848. // //www.example.com/my/path (looks odd, but works, and
  7849. // recognized by most browsers)
  7850. // (this set is valid or invalid on a scheme by scheme
  7851. // basis, so we'll deal with it later)
  7852. // file:///my/path
  7853. // ///my/path
  7854. $this->path = $segments_encoder->encode($this->path);
  7855. } elseif ($this->path !== '') {
  7856. if ($this->path[0] === '/') {
  7857. // path-absolute (hier and relative)
  7858. // http:/my/path
  7859. // /my/path
  7860. if (strlen($this->path) >= 2 && $this->path[1] === '/') {
  7861. // This could happen if both the host gets stripped
  7862. // out
  7863. // http://my/path
  7864. // //my/path
  7865. $this->path = '';
  7866. } else {
  7867. $this->path = $segments_encoder->encode($this->path);
  7868. }
  7869. } elseif (!is_null($this->scheme)) {
  7870. // path-rootless (hier)
  7871. // http:my/path
  7872. // Short circuit evaluation means we don't need to check nz
  7873. $this->path = $segments_encoder->encode($this->path);
  7874. } else {
  7875. // path-noscheme (relative)
  7876. // my/path
  7877. // (once again, not checking nz)
  7878. $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
  7879. $c = strpos($this->path, '/');
  7880. if ($c !== false) {
  7881. $this->path =
  7882. $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
  7883. $segments_encoder->encode(substr($this->path, $c));
  7884. } else {
  7885. $this->path = $segment_nc_encoder->encode($this->path);
  7886. }
  7887. }
  7888. } else {
  7889. // path-empty (hier and relative)
  7890. $this->path = ''; // just to be safe
  7891. }
  7892. // qf = query and fragment
  7893. $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
  7894. if (!is_null($this->query)) {
  7895. $this->query = $qf_encoder->encode($this->query);
  7896. }
  7897. if (!is_null($this->fragment)) {
  7898. $this->fragment = $qf_encoder->encode($this->fragment);
  7899. }
  7900. return true;
  7901. }
  7902. /**
  7903. * Convert URI back to string
  7904. * @return string URI appropriate for output
  7905. */
  7906. public function toString()
  7907. {
  7908. // reconstruct authority
  7909. $authority = null;
  7910. // there is a rendering difference between a null authority
  7911. // (http:foo-bar) and an empty string authority
  7912. // (http:///foo-bar).
  7913. if (!is_null($this->host)) {
  7914. $authority = '';
  7915. if (!is_null($this->userinfo)) {
  7916. $authority .= $this->userinfo . '@';
  7917. }
  7918. $authority .= $this->host;
  7919. if (!is_null($this->port)) {
  7920. $authority .= ':' . $this->port;
  7921. }
  7922. }
  7923. // Reconstruct the result
  7924. // One might wonder about parsing quirks from browsers after
  7925. // this reconstruction. Unfortunately, parsing behavior depends
  7926. // on what *scheme* was employed (file:///foo is handled *very*
  7927. // differently than http:///foo), so unfortunately we have to
  7928. // defer to the schemes to do the right thing.
  7929. $result = '';
  7930. if (!is_null($this->scheme)) {
  7931. $result .= $this->scheme . ':';
  7932. }
  7933. if (!is_null($authority)) {
  7934. $result .= '//' . $authority;
  7935. }
  7936. $result .= $this->path;
  7937. if (!is_null($this->query)) {
  7938. $result .= '?' . $this->query;
  7939. }
  7940. if (!is_null($this->fragment)) {
  7941. $result .= '#' . $this->fragment;
  7942. }
  7943. return $result;
  7944. }
  7945. /**
  7946. * Returns true if this URL might be considered a 'local' URL given
  7947. * the current context. This is true when the host is null, or
  7948. * when it matches the host supplied to the configuration.
  7949. *
  7950. * Note that this does not do any scheme checking, so it is mostly
  7951. * only appropriate for metadata that doesn't care about protocol
  7952. * security. isBenign is probably what you actually want.
  7953. * @param HTMLPurifier_Config $config
  7954. * @param HTMLPurifier_Context $context
  7955. * @return bool
  7956. */
  7957. public function isLocal($config, $context)
  7958. {
  7959. if ($this->host === null) {
  7960. return true;
  7961. }
  7962. $uri_def = $config->getDefinition('URI');
  7963. if ($uri_def->host === $this->host) {
  7964. return true;
  7965. }
  7966. return false;
  7967. }
  7968. /**
  7969. * Returns true if this URL should be considered a 'benign' URL,
  7970. * that is:
  7971. *
  7972. * - It is a local URL (isLocal), and
  7973. * - It has a equal or better level of security
  7974. * @param HTMLPurifier_Config $config
  7975. * @param HTMLPurifier_Context $context
  7976. * @return bool
  7977. */
  7978. public function isBenign($config, $context)
  7979. {
  7980. if (!$this->isLocal($config, $context)) {
  7981. return false;
  7982. }
  7983. $scheme_obj = $this->getSchemeObj($config, $context);
  7984. if (!$scheme_obj) {
  7985. return false;
  7986. } // conservative approach
  7987. $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
  7988. if ($current_scheme_obj->secure) {
  7989. if (!$scheme_obj->secure) {
  7990. return false;
  7991. }
  7992. }
  7993. return true;
  7994. }
  7995. }
  7996. class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
  7997. {
  7998. public $type = 'URI';
  7999. protected $filters = array();
  8000. protected $postFilters = array();
  8001. protected $registeredFilters = array();
  8002. /**
  8003. * HTMLPurifier_URI object of the base specified at %URI.Base
  8004. */
  8005. public $base;
  8006. /**
  8007. * String host to consider "home" base, derived off of $base
  8008. */
  8009. public $host;
  8010. /**
  8011. * Name of default scheme based on %URI.DefaultScheme and %URI.Base
  8012. */
  8013. public $defaultScheme;
  8014. public function __construct()
  8015. {
  8016. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
  8017. $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
  8018. $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
  8019. $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
  8020. $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
  8021. $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
  8022. $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
  8023. }
  8024. public function registerFilter($filter)
  8025. {
  8026. $this->registeredFilters[$filter->name] = $filter;
  8027. }
  8028. public function addFilter($filter, $config)
  8029. {
  8030. $r = $filter->prepare($config);
  8031. if ($r === false) return; // null is ok, for backwards compat
  8032. if ($filter->post) {
  8033. $this->postFilters[$filter->name] = $filter;
  8034. } else {
  8035. $this->filters[$filter->name] = $filter;
  8036. }
  8037. }
  8038. protected function doSetup($config)
  8039. {
  8040. $this->setupMemberVariables($config);
  8041. $this->setupFilters($config);
  8042. }
  8043. protected function setupFilters($config)
  8044. {
  8045. foreach ($this->registeredFilters as $name => $filter) {
  8046. if ($filter->always_load) {
  8047. $this->addFilter($filter, $config);
  8048. } else {
  8049. $conf = $config->get('URI.' . $name);
  8050. if ($conf !== false && $conf !== null) {
  8051. $this->addFilter($filter, $config);
  8052. }
  8053. }
  8054. }
  8055. unset($this->registeredFilters);
  8056. }
  8057. protected function setupMemberVariables($config)
  8058. {
  8059. $this->host = $config->get('URI.Host');
  8060. $base_uri = $config->get('URI.Base');
  8061. if (!is_null($base_uri)) {
  8062. $parser = new HTMLPurifier_URIParser();
  8063. $this->base = $parser->parse($base_uri);
  8064. $this->defaultScheme = $this->base->scheme;
  8065. if (is_null($this->host)) $this->host = $this->base->host;
  8066. }
  8067. if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
  8068. }
  8069. public function getDefaultScheme($config, $context)
  8070. {
  8071. return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
  8072. }
  8073. public function filter(&$uri, $config, $context)
  8074. {
  8075. foreach ($this->filters as $name => $f) {
  8076. $result = $f->filter($uri, $config, $context);
  8077. if (!$result) return false;
  8078. }
  8079. return true;
  8080. }
  8081. public function postFilter(&$uri, $config, $context)
  8082. {
  8083. foreach ($this->postFilters as $name => $f) {
  8084. $result = $f->filter($uri, $config, $context);
  8085. if (!$result) return false;
  8086. }
  8087. return true;
  8088. }
  8089. }
  8090. /**
  8091. * Chainable filters for custom URI processing.
  8092. *
  8093. * These filters can perform custom actions on a URI filter object,
  8094. * including transformation or blacklisting. A filter named Foo
  8095. * must have a corresponding configuration directive %URI.Foo,
  8096. * unless always_load is specified to be true.
  8097. *
  8098. * The following contexts may be available while URIFilters are being
  8099. * processed:
  8100. *
  8101. * - EmbeddedURI: true if URI is an embedded resource that will
  8102. * be loaded automatically on page load
  8103. * - CurrentToken: a reference to the token that is currently
  8104. * being processed
  8105. * - CurrentAttr: the name of the attribute that is currently being
  8106. * processed
  8107. * - CurrentCSSProperty: the name of the CSS property that is
  8108. * currently being processed (if applicable)
  8109. *
  8110. * @warning This filter is called before scheme object validation occurs.
  8111. * Make sure, if you require a specific scheme object, you
  8112. * you check that it exists. This allows filters to convert
  8113. * proprietary URI schemes into regular ones.
  8114. */
  8115. abstract class HTMLPurifier_URIFilter
  8116. {
  8117. /**
  8118. * Unique identifier of filter.
  8119. * @type string
  8120. */
  8121. public $name;
  8122. /**
  8123. * True if this filter should be run after scheme validation.
  8124. * @type bool
  8125. */
  8126. public $post = false;
  8127. /**
  8128. * True if this filter should always be loaded.
  8129. * This permits a filter to be named Foo without the corresponding
  8130. * %URI.Foo directive existing.
  8131. * @type bool
  8132. */
  8133. public $always_load = false;
  8134. /**
  8135. * Performs initialization for the filter. If the filter returns
  8136. * false, this means that it shouldn't be considered active.
  8137. * @param HTMLPurifier_Config $config
  8138. * @return bool
  8139. */
  8140. public function prepare($config)
  8141. {
  8142. return true;
  8143. }
  8144. /**
  8145. * Filter a URI object
  8146. * @param HTMLPurifier_URI $uri Reference to URI object variable
  8147. * @param HTMLPurifier_Config $config
  8148. * @param HTMLPurifier_Context $context
  8149. * @return bool Whether or not to continue processing: false indicates
  8150. * URL is no good, true indicates continue processing. Note that
  8151. * all changes are committed directly on the URI object
  8152. */
  8153. abstract public function filter(&$uri, $config, $context);
  8154. }
  8155. /**
  8156. * Parses a URI into the components and fragment identifier as specified
  8157. * by RFC 3986.
  8158. */
  8159. class HTMLPurifier_URIParser
  8160. {
  8161. /**
  8162. * Instance of HTMLPurifier_PercentEncoder to do normalization with.
  8163. */
  8164. protected $percentEncoder;
  8165. public function __construct()
  8166. {
  8167. $this->percentEncoder = new HTMLPurifier_PercentEncoder();
  8168. }
  8169. /**
  8170. * Parses a URI.
  8171. * @param $uri string URI to parse
  8172. * @return HTMLPurifier_URI representation of URI. This representation has
  8173. * not been validated yet and may not conform to RFC.
  8174. */
  8175. public function parse($uri)
  8176. {
  8177. $uri = $this->percentEncoder->normalize($uri);
  8178. // Regexp is as per Appendix B.
  8179. // Note that ["<>] are an addition to the RFC's recommended
  8180. // characters, because they represent external delimeters.
  8181. $r_URI = '!'.
  8182. '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
  8183. '(//([^/?#"<>]*))?'. // 4. Authority
  8184. '([^?#"<>]*)'. // 5. Path
  8185. '(\?([^#"<>]*))?'. // 7. Query
  8186. '(#([^"<>]*))?'. // 8. Fragment
  8187. '!';
  8188. $matches = array();
  8189. $result = preg_match($r_URI, $uri, $matches);
  8190. if (!$result) return false; // *really* invalid URI
  8191. // seperate out parts
  8192. $scheme = !empty($matches[1]) ? $matches[2] : null;
  8193. $authority = !empty($matches[3]) ? $matches[4] : null;
  8194. $path = $matches[5]; // always present, can be empty
  8195. $query = !empty($matches[6]) ? $matches[7] : null;
  8196. $fragment = !empty($matches[8]) ? $matches[9] : null;
  8197. // further parse authority
  8198. if ($authority !== null) {
  8199. $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
  8200. $matches = array();
  8201. preg_match($r_authority, $authority, $matches);
  8202. $userinfo = !empty($matches[1]) ? $matches[2] : null;
  8203. $host = !empty($matches[3]) ? $matches[3] : '';
  8204. $port = !empty($matches[4]) ? (int) $matches[5] : null;
  8205. } else {
  8206. $port = $host = $userinfo = null;
  8207. }
  8208. return new HTMLPurifier_URI(
  8209. $scheme, $userinfo, $host, $port, $path, $query, $fragment);
  8210. }
  8211. }
  8212. /**
  8213. * Validator for the components of a URI for a specific scheme
  8214. */
  8215. abstract class HTMLPurifier_URIScheme
  8216. {
  8217. /**
  8218. * Scheme's default port (integer). If an explicit port number is
  8219. * specified that coincides with the default port, it will be
  8220. * elided.
  8221. * @type int
  8222. */
  8223. public $default_port = null;
  8224. /**
  8225. * Whether or not URIs of this scheme are locatable by a browser
  8226. * http and ftp are accessible, while mailto and news are not.
  8227. * @type bool
  8228. */
  8229. public $browsable = false;
  8230. /**
  8231. * Whether or not data transmitted over this scheme is encrypted.
  8232. * https is secure, http is not.
  8233. * @type bool
  8234. */
  8235. public $secure = false;
  8236. /**
  8237. * Whether or not the URI always uses <hier_part>, resolves edge cases
  8238. * with making relative URIs absolute
  8239. * @type bool
  8240. */
  8241. public $hierarchical = false;
  8242. /**
  8243. * Whether or not the URI may omit a hostname when the scheme is
  8244. * explicitly specified, ala file:///path/to/file. As of writing,
  8245. * 'file' is the only scheme that browsers support his properly.
  8246. * @type bool
  8247. */
  8248. public $may_omit_host = false;
  8249. /**
  8250. * Validates the components of a URI for a specific scheme.
  8251. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8252. * @param HTMLPurifier_Config $config
  8253. * @param HTMLPurifier_Context $context
  8254. * @return bool success or failure
  8255. */
  8256. abstract public function doValidate(&$uri, $config, $context);
  8257. /**
  8258. * Public interface for validating components of a URI. Performs a
  8259. * bunch of default actions. Don't overload this method.
  8260. * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
  8261. * @param HTMLPurifier_Config $config
  8262. * @param HTMLPurifier_Context $context
  8263. * @return bool success or failure
  8264. */
  8265. public function validate(&$uri, $config, $context)
  8266. {
  8267. if ($this->default_port == $uri->port) {
  8268. $uri->port = null;
  8269. }
  8270. // kludge: browsers do funny things when the scheme but not the
  8271. // authority is set
  8272. if (!$this->may_omit_host &&
  8273. // if the scheme is present, a missing host is always in error
  8274. (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
  8275. // if the scheme is not present, a *blank* host is in error,
  8276. // since this translates into '///path' which most browsers
  8277. // interpret as being 'http://path'.
  8278. (is_null($uri->scheme) && $uri->host === '')
  8279. ) {
  8280. do {
  8281. if (is_null($uri->scheme)) {
  8282. if (substr($uri->path, 0, 2) != '//') {
  8283. $uri->host = null;
  8284. break;
  8285. }
  8286. // URI is '////path', so we cannot nullify the
  8287. // host to preserve semantics. Try expanding the
  8288. // hostname instead (fall through)
  8289. }
  8290. // first see if we can manually insert a hostname
  8291. $host = $config->get('URI.Host');
  8292. if (!is_null($host)) {
  8293. $uri->host = $host;
  8294. } else {
  8295. // we can't do anything sensible, reject the URL.
  8296. return false;
  8297. }
  8298. } while (false);
  8299. }
  8300. return $this->doValidate($uri, $config, $context);
  8301. }
  8302. }
  8303. /**
  8304. * Registry for retrieving specific URI scheme validator objects.
  8305. */
  8306. class HTMLPurifier_URISchemeRegistry
  8307. {
  8308. /**
  8309. * Retrieve sole instance of the registry.
  8310. * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
  8311. * or bool true to reset to default registry.
  8312. * @return HTMLPurifier_URISchemeRegistry
  8313. * @note Pass a registry object $prototype with a compatible interface and
  8314. * the function will copy it and return it all further times.
  8315. */
  8316. public static function instance($prototype = null)
  8317. {
  8318. static $instance = null;
  8319. if ($prototype !== null) {
  8320. $instance = $prototype;
  8321. } elseif ($instance === null || $prototype == true) {
  8322. $instance = new HTMLPurifier_URISchemeRegistry();
  8323. }
  8324. return $instance;
  8325. }
  8326. /**
  8327. * Cache of retrieved schemes.
  8328. * @type HTMLPurifier_URIScheme[]
  8329. */
  8330. protected $schemes = array();
  8331. /**
  8332. * Retrieves a scheme validator object
  8333. * @param string $scheme String scheme name like http or mailto
  8334. * @param HTMLPurifier_Config $config
  8335. * @param HTMLPurifier_Context $context
  8336. * @return HTMLPurifier_URIScheme
  8337. */
  8338. public function getScheme($scheme, $config, $context)
  8339. {
  8340. if (!$config) {
  8341. $config = HTMLPurifier_Config::createDefault();
  8342. }
  8343. // important, otherwise attacker could include arbitrary file
  8344. $allowed_schemes = $config->get('URI.AllowedSchemes');
  8345. if (!$config->get('URI.OverrideAllowedSchemes') &&
  8346. !isset($allowed_schemes[$scheme])
  8347. ) {
  8348. return;
  8349. }
  8350. if (isset($this->schemes[$scheme])) {
  8351. return $this->schemes[$scheme];
  8352. }
  8353. if (!isset($allowed_schemes[$scheme])) {
  8354. return;
  8355. }
  8356. $class = 'HTMLPurifier_URIScheme_' . $scheme;
  8357. if (!class_exists($class)) {
  8358. return;
  8359. }
  8360. $this->schemes[$scheme] = new $class();
  8361. return $this->schemes[$scheme];
  8362. }
  8363. /**
  8364. * Registers a custom scheme to the cache, bypassing reflection.
  8365. * @param string $scheme Scheme name
  8366. * @param HTMLPurifier_URIScheme $scheme_obj
  8367. */
  8368. public function register($scheme, $scheme_obj)
  8369. {
  8370. $this->schemes[$scheme] = $scheme_obj;
  8371. }
  8372. }
  8373. /**
  8374. * Class for converting between different unit-lengths as specified by
  8375. * CSS.
  8376. */
  8377. class HTMLPurifier_UnitConverter
  8378. {
  8379. const ENGLISH = 1;
  8380. const METRIC = 2;
  8381. const DIGITAL = 3;
  8382. /**
  8383. * Units information array. Units are grouped into measuring systems
  8384. * (English, Metric), and are assigned an integer representing
  8385. * the conversion factor between that unit and the smallest unit in
  8386. * the system. Numeric indexes are actually magical constants that
  8387. * encode conversion data from one system to the next, with a O(n^2)
  8388. * constraint on memory (this is generally not a problem, since
  8389. * the number of measuring systems is small.)
  8390. */
  8391. protected static $units = array(
  8392. self::ENGLISH => array(
  8393. 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
  8394. 'pt' => 4,
  8395. 'pc' => 48,
  8396. 'in' => 288,
  8397. self::METRIC => array('pt', '0.352777778', 'mm'),
  8398. ),
  8399. self::METRIC => array(
  8400. 'mm' => 1,
  8401. 'cm' => 10,
  8402. self::ENGLISH => array('mm', '2.83464567', 'pt'),
  8403. ),
  8404. );
  8405. /**
  8406. * Minimum bcmath precision for output.
  8407. * @type int
  8408. */
  8409. protected $outputPrecision;
  8410. /**
  8411. * Bcmath precision for internal calculations.
  8412. * @type int
  8413. */
  8414. protected $internalPrecision;
  8415. /**
  8416. * Whether or not BCMath is available.
  8417. * @type bool
  8418. */
  8419. private $bcmath;
  8420. public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
  8421. {
  8422. $this->outputPrecision = $output_precision;
  8423. $this->internalPrecision = $internal_precision;
  8424. $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
  8425. }
  8426. /**
  8427. * Converts a length object of one unit into another unit.
  8428. * @param HTMLPurifier_Length $length
  8429. * Instance of HTMLPurifier_Length to convert. You must validate()
  8430. * it before passing it here!
  8431. * @param string $to_unit
  8432. * Unit to convert to.
  8433. * @return HTMLPurifier_Length|bool
  8434. * @note
  8435. * About precision: This conversion function pays very special
  8436. * attention to the incoming precision of values and attempts
  8437. * to maintain a number of significant figure. Results are
  8438. * fairly accurate up to nine digits. Some caveats:
  8439. * - If a number is zero-padded as a result of this significant
  8440. * figure tracking, the zeroes will be eliminated.
  8441. * - If a number contains less than four sigfigs ($outputPrecision)
  8442. * and this causes some decimals to be excluded, those
  8443. * decimals will be added on.
  8444. */
  8445. public function convert($length, $to_unit)
  8446. {
  8447. if (!$length->isValid()) {
  8448. return false;
  8449. }
  8450. $n = $length->getN();
  8451. $unit = $length->getUnit();
  8452. if ($n === '0' || $unit === false) {
  8453. return new HTMLPurifier_Length('0', false);
  8454. }
  8455. $state = $dest_state = false;
  8456. foreach (self::$units as $k => $x) {
  8457. if (isset($x[$unit])) {
  8458. $state = $k;
  8459. }
  8460. if (isset($x[$to_unit])) {
  8461. $dest_state = $k;
  8462. }
  8463. }
  8464. if (!$state || !$dest_state) {
  8465. return false;
  8466. }
  8467. // Some calculations about the initial precision of the number;
  8468. // this will be useful when we need to do final rounding.
  8469. $sigfigs = $this->getSigFigs($n);
  8470. if ($sigfigs < $this->outputPrecision) {
  8471. $sigfigs = $this->outputPrecision;
  8472. }
  8473. // BCMath's internal precision deals only with decimals. Use
  8474. // our default if the initial number has no decimals, or increase
  8475. // it by how ever many decimals, thus, the number of guard digits
  8476. // will always be greater than or equal to internalPrecision.
  8477. $log = (int)floor(log(abs($n), 10));
  8478. $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
  8479. for ($i = 0; $i < 2; $i++) {
  8480. // Determine what unit IN THIS SYSTEM we need to convert to
  8481. if ($dest_state === $state) {
  8482. // Simple conversion
  8483. $dest_unit = $to_unit;
  8484. } else {
  8485. // Convert to the smallest unit, pending a system shift
  8486. $dest_unit = self::$units[$state][$dest_state][0];
  8487. }
  8488. // Do the conversion if necessary
  8489. if ($dest_unit !== $unit) {
  8490. $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
  8491. $n = $this->mul($n, $factor, $cp);
  8492. $unit = $dest_unit;
  8493. }
  8494. // Output was zero, so bail out early. Shouldn't ever happen.
  8495. if ($n === '') {
  8496. $n = '0';
  8497. $unit = $to_unit;
  8498. break;
  8499. }
  8500. // It was a simple conversion, so bail out
  8501. if ($dest_state === $state) {
  8502. break;
  8503. }
  8504. if ($i !== 0) {
  8505. // Conversion failed! Apparently, the system we forwarded
  8506. // to didn't have this unit. This should never happen!
  8507. return false;
  8508. }
  8509. // Pre-condition: $i == 0
  8510. // Perform conversion to next system of units
  8511. $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
  8512. $unit = self::$units[$state][$dest_state][2];
  8513. $state = $dest_state;
  8514. // One more loop around to convert the unit in the new system.
  8515. }
  8516. // Post-condition: $unit == $to_unit
  8517. if ($unit !== $to_unit) {
  8518. return false;
  8519. }
  8520. // Useful for debugging:
  8521. //echo "<pre>n";
  8522. //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
  8523. $n = $this->round($n, $sigfigs);
  8524. if (strpos($n, '.') !== false) {
  8525. $n = rtrim($n, '0');
  8526. }
  8527. $n = rtrim($n, '.');
  8528. return new HTMLPurifier_Length($n, $unit);
  8529. }
  8530. /**
  8531. * Returns the number of significant figures in a string number.
  8532. * @param string $n Decimal number
  8533. * @return int number of sigfigs
  8534. */
  8535. public function getSigFigs($n)
  8536. {
  8537. $n = ltrim($n, '0+-');
  8538. $dp = strpos($n, '.'); // decimal position
  8539. if ($dp === false) {
  8540. $sigfigs = strlen(rtrim($n, '0'));
  8541. } else {
  8542. $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
  8543. if ($dp !== 0) {
  8544. $sigfigs--;
  8545. }
  8546. }
  8547. return $sigfigs;
  8548. }
  8549. /**
  8550. * Adds two numbers, using arbitrary precision when available.
  8551. * @param string $s1
  8552. * @param string $s2
  8553. * @param int $scale
  8554. * @return string
  8555. */
  8556. private function add($s1, $s2, $scale)
  8557. {
  8558. if ($this->bcmath) {
  8559. return bcadd($s1, $s2, $scale);
  8560. } else {
  8561. return $this->scale((float)$s1 + (float)$s2, $scale);
  8562. }
  8563. }
  8564. /**
  8565. * Multiples two numbers, using arbitrary precision when available.
  8566. * @param string $s1
  8567. * @param string $s2
  8568. * @param int $scale
  8569. * @return string
  8570. */
  8571. private function mul($s1, $s2, $scale)
  8572. {
  8573. if ($this->bcmath) {
  8574. return bcmul($s1, $s2, $scale);
  8575. } else {
  8576. return $this->scale((float)$s1 * (float)$s2, $scale);
  8577. }
  8578. }
  8579. /**
  8580. * Divides two numbers, using arbitrary precision when available.
  8581. * @param string $s1
  8582. * @param string $s2
  8583. * @param int $scale
  8584. * @return string
  8585. */
  8586. private function div($s1, $s2, $scale)
  8587. {
  8588. if ($this->bcmath) {
  8589. return bcdiv($s1, $s2, $scale);
  8590. } else {
  8591. return $this->scale((float)$s1 / (float)$s2, $scale);
  8592. }
  8593. }
  8594. /**
  8595. * Rounds a number according to the number of sigfigs it should have,
  8596. * using arbitrary precision when available.
  8597. * @param float $n
  8598. * @param int $sigfigs
  8599. * @return string
  8600. */
  8601. private function round($n, $sigfigs)
  8602. {
  8603. $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
  8604. $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
  8605. $neg = $n < 0 ? '-' : ''; // Negative sign
  8606. if ($this->bcmath) {
  8607. if ($rp >= 0) {
  8608. $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
  8609. $n = bcdiv($n, '1', $rp);
  8610. } else {
  8611. // This algorithm partially depends on the standardized
  8612. // form of numbers that comes out of bcmath.
  8613. $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
  8614. $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
  8615. }
  8616. return $n;
  8617. } else {
  8618. return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
  8619. }
  8620. }
  8621. /**
  8622. * Scales a float to $scale digits right of decimal point, like BCMath.
  8623. * @param float $r
  8624. * @param int $scale
  8625. * @return string
  8626. */
  8627. private function scale($r, $scale)
  8628. {
  8629. if ($scale < 0) {
  8630. // The f sprintf type doesn't support negative numbers, so we
  8631. // need to cludge things manually. First get the string.
  8632. $r = sprintf('%.0f', (float)$r);
  8633. // Due to floating point precision loss, $r will more than likely
  8634. // look something like 4652999999999.9234. We grab one more digit
  8635. // than we need to precise from $r and then use that to round
  8636. // appropriately.
  8637. $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
  8638. // Now we return it, truncating the zero that was rounded off.
  8639. return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
  8640. }
  8641. return sprintf('%.' . $scale . 'f', (float)$r);
  8642. }
  8643. }
  8644. /**
  8645. * Parses string representations into their corresponding native PHP
  8646. * variable type. The base implementation does a simple type-check.
  8647. */
  8648. class HTMLPurifier_VarParser
  8649. {
  8650. const STRING = 1;
  8651. const ISTRING = 2;
  8652. const TEXT = 3;
  8653. const ITEXT = 4;
  8654. const INT = 5;
  8655. const FLOAT = 6;
  8656. const BOOL = 7;
  8657. const LOOKUP = 8;
  8658. const ALIST = 9;
  8659. const HASH = 10;
  8660. const MIXED = 11;
  8661. /**
  8662. * Lookup table of allowed types. Mainly for backwards compatibility, but
  8663. * also convenient for transforming string type names to the integer constants.
  8664. */
  8665. public static $types = array(
  8666. 'string' => self::STRING,
  8667. 'istring' => self::ISTRING,
  8668. 'text' => self::TEXT,
  8669. 'itext' => self::ITEXT,
  8670. 'int' => self::INT,
  8671. 'float' => self::FLOAT,
  8672. 'bool' => self::BOOL,
  8673. 'lookup' => self::LOOKUP,
  8674. 'list' => self::ALIST,
  8675. 'hash' => self::HASH,
  8676. 'mixed' => self::MIXED
  8677. );
  8678. /**
  8679. * Lookup table of types that are string, and can have aliases or
  8680. * allowed value lists.
  8681. */
  8682. public static $stringTypes = array(
  8683. self::STRING => true,
  8684. self::ISTRING => true,
  8685. self::TEXT => true,
  8686. self::ITEXT => true,
  8687. );
  8688. /**
  8689. * Validate a variable according to type.
  8690. * It may return NULL as a valid type if $allow_null is true.
  8691. *
  8692. * @param mixed $var Variable to validate
  8693. * @param int $type Type of variable, see HTMLPurifier_VarParser->types
  8694. * @param bool $allow_null Whether or not to permit null as a value
  8695. * @return string Validated and type-coerced variable
  8696. * @throws HTMLPurifier_VarParserException
  8697. */
  8698. final public function parse($var, $type, $allow_null = false)
  8699. {
  8700. if (is_string($type)) {
  8701. if (!isset(HTMLPurifier_VarParser::$types[$type])) {
  8702. throw new HTMLPurifier_VarParserException("Invalid type '$type'");
  8703. } else {
  8704. $type = HTMLPurifier_VarParser::$types[$type];
  8705. }
  8706. }
  8707. $var = $this->parseImplementation($var, $type, $allow_null);
  8708. if ($allow_null && $var === null) {
  8709. return null;
  8710. }
  8711. // These are basic checks, to make sure nothing horribly wrong
  8712. // happened in our implementations.
  8713. switch ($type) {
  8714. case (self::STRING):
  8715. case (self::ISTRING):
  8716. case (self::TEXT):
  8717. case (self::ITEXT):
  8718. if (!is_string($var)) {
  8719. break;
  8720. }
  8721. if ($type == self::ISTRING || $type == self::ITEXT) {
  8722. $var = strtolower($var);
  8723. }
  8724. return $var;
  8725. case (self::INT):
  8726. if (!is_int($var)) {
  8727. break;
  8728. }
  8729. return $var;
  8730. case (self::FLOAT):
  8731. if (!is_float($var)) {
  8732. break;
  8733. }
  8734. return $var;
  8735. case (self::BOOL):
  8736. if (!is_bool($var)) {
  8737. break;
  8738. }
  8739. return $var;
  8740. case (self::LOOKUP):
  8741. case (self::ALIST):
  8742. case (self::HASH):
  8743. if (!is_array($var)) {
  8744. break;
  8745. }
  8746. if ($type === self::LOOKUP) {
  8747. foreach ($var as $k) {
  8748. if ($k !== true) {
  8749. $this->error('Lookup table contains value other than true');
  8750. }
  8751. }
  8752. } elseif ($type === self::ALIST) {
  8753. $keys = array_keys($var);
  8754. if (array_keys($keys) !== $keys) {
  8755. $this->error('Indices for list are not uniform');
  8756. }
  8757. }
  8758. return $var;
  8759. case (self::MIXED):
  8760. return $var;
  8761. default:
  8762. $this->errorInconsistent(get_class($this), $type);
  8763. }
  8764. $this->errorGeneric($var, $type);
  8765. }
  8766. /**
  8767. * Actually implements the parsing. Base implementation does not
  8768. * do anything to $var. Subclasses should overload this!
  8769. * @param mixed $var
  8770. * @param int $type
  8771. * @param bool $allow_null
  8772. * @return string
  8773. */
  8774. protected function parseImplementation($var, $type, $allow_null)
  8775. {
  8776. return $var;
  8777. }
  8778. /**
  8779. * Throws an exception.
  8780. * @throws HTMLPurifier_VarParserException
  8781. */
  8782. protected function error($msg)
  8783. {
  8784. throw new HTMLPurifier_VarParserException($msg);
  8785. }
  8786. /**
  8787. * Throws an inconsistency exception.
  8788. * @note This should not ever be called. It would be called if we
  8789. * extend the allowed values of HTMLPurifier_VarParser without
  8790. * updating subclasses.
  8791. * @param string $class
  8792. * @param int $type
  8793. * @throws HTMLPurifier_Exception
  8794. */
  8795. protected function errorInconsistent($class, $type)
  8796. {
  8797. throw new HTMLPurifier_Exception(
  8798. "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
  8799. " not implemented"
  8800. );
  8801. }
  8802. /**
  8803. * Generic error for if a type didn't work.
  8804. * @param mixed $var
  8805. * @param int $type
  8806. */
  8807. protected function errorGeneric($var, $type)
  8808. {
  8809. $vtype = gettype($var);
  8810. $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
  8811. }
  8812. /**
  8813. * @param int $type
  8814. * @return string
  8815. */
  8816. public static function getTypeName($type)
  8817. {
  8818. static $lookup;
  8819. if (!$lookup) {
  8820. // Lazy load the alternative lookup table
  8821. $lookup = array_flip(HTMLPurifier_VarParser::$types);
  8822. }
  8823. if (!isset($lookup[$type])) {
  8824. return 'unknown';
  8825. }
  8826. return $lookup[$type];
  8827. }
  8828. }
  8829. /**
  8830. * Exception type for HTMLPurifier_VarParser
  8831. */
  8832. class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
  8833. {
  8834. }
  8835. /**
  8836. * A zipper is a purely-functional data structure which contains
  8837. * a focus that can be efficiently manipulated. It is known as
  8838. * a "one-hole context". This mutable variant implements a zipper
  8839. * for a list as a pair of two arrays, laid out as follows:
  8840. *
  8841. * Base list: 1 2 3 4 [ ] 6 7 8 9
  8842. * Front list: 1 2 3 4
  8843. * Back list: 9 8 7 6
  8844. *
  8845. * User is expected to keep track of the "current element" and properly
  8846. * fill it back in as necessary. (ToDo: Maybe it's more user friendly
  8847. * to implicitly track the current element?)
  8848. *
  8849. * Nota bene: the current class gets confused if you try to store NULLs
  8850. * in the list.
  8851. */
  8852. class HTMLPurifier_Zipper
  8853. {
  8854. public $front, $back;
  8855. public function __construct($front, $back) {
  8856. $this->front = $front;
  8857. $this->back = $back;
  8858. }
  8859. /**
  8860. * Creates a zipper from an array, with a hole in the
  8861. * 0-index position.
  8862. * @param Array to zipper-ify.
  8863. * @return Tuple of zipper and element of first position.
  8864. */
  8865. static public function fromArray($array) {
  8866. $z = new self(array(), array_reverse($array));
  8867. $t = $z->delete(); // delete the "dummy hole"
  8868. return array($z, $t);
  8869. }
  8870. /**
  8871. * Convert zipper back into a normal array, optionally filling in
  8872. * the hole with a value. (Usually you should supply a $t, unless you
  8873. * are at the end of the array.)
  8874. */
  8875. public function toArray($t = NULL) {
  8876. $a = $this->front;
  8877. if ($t !== NULL) $a[] = $t;
  8878. for ($i = count($this->back)-1; $i >= 0; $i--) {
  8879. $a[] = $this->back[$i];
  8880. }
  8881. return $a;
  8882. }
  8883. /**
  8884. * Move hole to the next element.
  8885. * @param $t Element to fill hole with
  8886. * @return Original contents of new hole.
  8887. */
  8888. public function next($t) {
  8889. if ($t !== NULL) array_push($this->front, $t);
  8890. return empty($this->back) ? NULL : array_pop($this->back);
  8891. }
  8892. /**
  8893. * Iterated hole advancement.
  8894. * @param $t Element to fill hole with
  8895. * @param $i How many forward to advance hole
  8896. * @return Original contents of new hole, i away
  8897. */
  8898. public function advance($t, $n) {
  8899. for ($i = 0; $i < $n; $i++) {
  8900. $t = $this->next($t);
  8901. }
  8902. return $t;
  8903. }
  8904. /**
  8905. * Move hole to the previous element
  8906. * @param $t Element to fill hole with
  8907. * @return Original contents of new hole.
  8908. */
  8909. public function prev($t) {
  8910. if ($t !== NULL) array_push($this->back, $t);
  8911. return empty($this->front) ? NULL : array_pop($this->front);
  8912. }
  8913. /**
  8914. * Delete contents of current hole, shifting hole to
  8915. * next element.
  8916. * @return Original contents of new hole.
  8917. */
  8918. public function delete() {
  8919. return empty($this->back) ? NULL : array_pop($this->back);
  8920. }
  8921. /**
  8922. * Returns true if we are at the end of the list.
  8923. * @return bool
  8924. */
  8925. public function done() {
  8926. return empty($this->back);
  8927. }
  8928. /**
  8929. * Insert element before hole.
  8930. * @param Element to insert
  8931. */
  8932. public function insertBefore($t) {
  8933. if ($t !== NULL) array_push($this->front, $t);
  8934. }
  8935. /**
  8936. * Insert element after hole.
  8937. * @param Element to insert
  8938. */
  8939. public function insertAfter($t) {
  8940. if ($t !== NULL) array_push($this->back, $t);
  8941. }
  8942. /**
  8943. * Splice in multiple elements at hole. Functional specification
  8944. * in terms of array_splice:
  8945. *
  8946. * $arr1 = $arr;
  8947. * $old1 = array_splice($arr1, $i, $delete, $replacement);
  8948. *
  8949. * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
  8950. * $t = $z->advance($t, $i);
  8951. * list($old2, $t) = $z->splice($t, $delete, $replacement);
  8952. * $arr2 = $z->toArray($t);
  8953. *
  8954. * assert($old1 === $old2);
  8955. * assert($arr1 === $arr2);
  8956. *
  8957. * NB: the absolute index location after this operation is
  8958. * *unchanged!*
  8959. *
  8960. * @param Current contents of hole.
  8961. */
  8962. public function splice($t, $delete, $replacement) {
  8963. // delete
  8964. $old = array();
  8965. $r = $t;
  8966. for ($i = $delete; $i > 0; $i--) {
  8967. $old[] = $r;
  8968. $r = $this->delete();
  8969. }
  8970. // insert
  8971. for ($i = count($replacement)-1; $i >= 0; $i--) {
  8972. $this->insertAfter($r);
  8973. $r = $replacement[$i];
  8974. }
  8975. return array($old, $r);
  8976. }
  8977. }
  8978. /**
  8979. * Validates the HTML attribute style, otherwise known as CSS.
  8980. * @note We don't implement the whole CSS specification, so it might be
  8981. * difficult to reuse this component in the context of validating
  8982. * actual stylesheet declarations.
  8983. * @note If we were really serious about validating the CSS, we would
  8984. * tokenize the styles and then parse the tokens. Obviously, we
  8985. * are not doing that. Doing that could seriously harm performance,
  8986. * but would make these components a lot more viable for a CSS
  8987. * filtering solution.
  8988. */
  8989. class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
  8990. {
  8991. /**
  8992. * @param string $css
  8993. * @param HTMLPurifier_Config $config
  8994. * @param HTMLPurifier_Context $context
  8995. * @return bool|string
  8996. */
  8997. public function validate($css, $config, $context)
  8998. {
  8999. $css = $this->parseCDATA($css);
  9000. $definition = $config->getCSSDefinition();
  9001. // we're going to break the spec and explode by semicolons.
  9002. // This is because semicolon rarely appears in escaped form
  9003. // Doing this is generally flaky but fast
  9004. // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
  9005. // for details
  9006. $declarations = explode(';', $css);
  9007. $propvalues = array();
  9008. /**
  9009. * Name of the current CSS property being validated.
  9010. */
  9011. $property = false;
  9012. $context->register('CurrentCSSProperty', $property);
  9013. foreach ($declarations as $declaration) {
  9014. if (!$declaration) {
  9015. continue;
  9016. }
  9017. if (!strpos($declaration, ':')) {
  9018. continue;
  9019. }
  9020. list($property, $value) = explode(':', $declaration, 2);
  9021. $property = trim($property);
  9022. $value = trim($value);
  9023. $ok = false;
  9024. do {
  9025. if (isset($definition->info[$property])) {
  9026. $ok = true;
  9027. break;
  9028. }
  9029. if (ctype_lower($property)) {
  9030. break;
  9031. }
  9032. $property = strtolower($property);
  9033. if (isset($definition->info[$property])) {
  9034. $ok = true;
  9035. break;
  9036. }
  9037. } while (0);
  9038. if (!$ok) {
  9039. continue;
  9040. }
  9041. // inefficient call, since the validator will do this again
  9042. if (strtolower(trim($value)) !== 'inherit') {
  9043. // inherit works for everything (but only on the base property)
  9044. $result = $definition->info[$property]->validate(
  9045. $value,
  9046. $config,
  9047. $context
  9048. );
  9049. } else {
  9050. $result = 'inherit';
  9051. }
  9052. if ($result === false) {
  9053. continue;
  9054. }
  9055. $propvalues[$property] = $result;
  9056. }
  9057. $context->destroy('CurrentCSSProperty');
  9058. // procedure does not write the new CSS simultaneously, so it's
  9059. // slightly inefficient, but it's the only way of getting rid of
  9060. // duplicates. Perhaps config to optimize it, but not now.
  9061. $new_declarations = '';
  9062. foreach ($propvalues as $prop => $value) {
  9063. $new_declarations .= "$prop:$value;";
  9064. }
  9065. return $new_declarations ? $new_declarations : false;
  9066. }
  9067. }
  9068. /**
  9069. * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
  9070. * with make.
  9071. */
  9072. class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
  9073. {
  9074. /**
  9075. * What we're cloning.
  9076. * @type HTMLPurifier_AttrDef
  9077. */
  9078. protected $clone;
  9079. /**
  9080. * @param HTMLPurifier_AttrDef $clone
  9081. */
  9082. public function __construct($clone)
  9083. {
  9084. $this->clone = $clone;
  9085. }
  9086. /**
  9087. * @param string $v
  9088. * @param HTMLPurifier_Config $config
  9089. * @param HTMLPurifier_Context $context
  9090. * @return bool|string
  9091. */
  9092. public function validate($v, $config, $context)
  9093. {
  9094. return $this->clone->validate($v, $config, $context);
  9095. }
  9096. /**
  9097. * @param string $string
  9098. * @return HTMLPurifier_AttrDef
  9099. */
  9100. public function make($string)
  9101. {
  9102. return clone $this->clone;
  9103. }
  9104. }
  9105. // Enum = Enumerated
  9106. /**
  9107. * Validates a keyword against a list of valid values.
  9108. * @warning The case-insensitive compare of this function uses PHP's
  9109. * built-in strtolower and ctype_lower functions, which may
  9110. * cause problems with international comparisons
  9111. */
  9112. class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
  9113. {
  9114. /**
  9115. * Lookup table of valid values.
  9116. * @type array
  9117. * @todo Make protected
  9118. */
  9119. public $valid_values = array();
  9120. /**
  9121. * Bool indicating whether or not enumeration is case sensitive.
  9122. * @note In general this is always case insensitive.
  9123. */
  9124. protected $case_sensitive = false; // values according to W3C spec
  9125. /**
  9126. * @param array $valid_values List of valid values
  9127. * @param bool $case_sensitive Whether or not case sensitive
  9128. */
  9129. public function __construct($valid_values = array(), $case_sensitive = false)
  9130. {
  9131. $this->valid_values = array_flip($valid_values);
  9132. $this->case_sensitive = $case_sensitive;
  9133. }
  9134. /**
  9135. * @param string $string
  9136. * @param HTMLPurifier_Config $config
  9137. * @param HTMLPurifier_Context $context
  9138. * @return bool|string
  9139. */
  9140. public function validate($string, $config, $context)
  9141. {
  9142. $string = trim($string);
  9143. if (!$this->case_sensitive) {
  9144. // we may want to do full case-insensitive libraries
  9145. $string = ctype_lower($string) ? $string : strtolower($string);
  9146. }
  9147. $result = isset($this->valid_values[$string]);
  9148. return $result ? $string : false;
  9149. }
  9150. /**
  9151. * @param string $string In form of comma-delimited list of case-insensitive
  9152. * valid values. Example: "foo,bar,baz". Prepend "s:" to make
  9153. * case sensitive
  9154. * @return HTMLPurifier_AttrDef_Enum
  9155. */
  9156. public function make($string)
  9157. {
  9158. if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
  9159. $string = substr($string, 2);
  9160. $sensitive = true;
  9161. } else {
  9162. $sensitive = false;
  9163. }
  9164. $values = explode(',', $string);
  9165. return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
  9166. }
  9167. }
  9168. /**
  9169. * Validates an integer.
  9170. * @note While this class was modeled off the CSS definition, no currently
  9171. * allowed CSS uses this type. The properties that do are: widows,
  9172. * orphans, z-index, counter-increment, counter-reset. Some of the
  9173. * HTML attributes, however, find use for a non-negative version of this.
  9174. */
  9175. class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
  9176. {
  9177. /**
  9178. * Whether or not negative values are allowed.
  9179. * @type bool
  9180. */
  9181. protected $negative = true;
  9182. /**
  9183. * Whether or not zero is allowed.
  9184. * @type bool
  9185. */
  9186. protected $zero = true;
  9187. /**
  9188. * Whether or not positive values are allowed.
  9189. * @type bool
  9190. */
  9191. protected $positive = true;
  9192. /**
  9193. * @param $negative Bool indicating whether or not negative values are allowed
  9194. * @param $zero Bool indicating whether or not zero is allowed
  9195. * @param $positive Bool indicating whether or not positive values are allowed
  9196. */
  9197. public function __construct($negative = true, $zero = true, $positive = true)
  9198. {
  9199. $this->negative = $negative;
  9200. $this->zero = $zero;
  9201. $this->positive = $positive;
  9202. }
  9203. /**
  9204. * @param string $integer
  9205. * @param HTMLPurifier_Config $config
  9206. * @param HTMLPurifier_Context $context
  9207. * @return bool|string
  9208. */
  9209. public function validate($integer, $config, $context)
  9210. {
  9211. $integer = $this->parseCDATA($integer);
  9212. if ($integer === '') {
  9213. return false;
  9214. }
  9215. // we could possibly simply typecast it to integer, but there are
  9216. // certain fringe cases that must not return an integer.
  9217. // clip leading sign
  9218. if ($this->negative && $integer[0] === '-') {
  9219. $digits = substr($integer, 1);
  9220. if ($digits === '0') {
  9221. $integer = '0';
  9222. } // rm minus sign for zero
  9223. } elseif ($this->positive && $integer[0] === '+') {
  9224. $digits = $integer = substr($integer, 1); // rm unnecessary plus
  9225. } else {
  9226. $digits = $integer;
  9227. }
  9228. // test if it's numeric
  9229. if (!ctype_digit($digits)) {
  9230. return false;
  9231. }
  9232. // perform scope tests
  9233. if (!$this->zero && $integer == 0) {
  9234. return false;
  9235. }
  9236. if (!$this->positive && $integer > 0) {
  9237. return false;
  9238. }
  9239. if (!$this->negative && $integer < 0) {
  9240. return false;
  9241. }
  9242. return $integer;
  9243. }
  9244. }
  9245. /**
  9246. * Validates the HTML attribute lang, effectively a language code.
  9247. * @note Built according to RFC 3066, which obsoleted RFC 1766
  9248. */
  9249. class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
  9250. {
  9251. /**
  9252. * @param string $string
  9253. * @param HTMLPurifier_Config $config
  9254. * @param HTMLPurifier_Context $context
  9255. * @return bool|string
  9256. */
  9257. public function validate($string, $config, $context)
  9258. {
  9259. $string = trim($string);
  9260. if (!$string) {
  9261. return false;
  9262. }
  9263. $subtags = explode('-', $string);
  9264. $num_subtags = count($subtags);
  9265. if ($num_subtags == 0) { // sanity check
  9266. return false;
  9267. }
  9268. // process primary subtag : $subtags[0]
  9269. $length = strlen($subtags[0]);
  9270. switch ($length) {
  9271. case 0:
  9272. return false;
  9273. case 1:
  9274. if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
  9275. return false;
  9276. }
  9277. break;
  9278. case 2:
  9279. case 3:
  9280. if (!ctype_alpha($subtags[0])) {
  9281. return false;
  9282. } elseif (!ctype_lower($subtags[0])) {
  9283. $subtags[0] = strtolower($subtags[0]);
  9284. }
  9285. break;
  9286. default:
  9287. return false;
  9288. }
  9289. $new_string = $subtags[0];
  9290. if ($num_subtags == 1) {
  9291. return $new_string;
  9292. }
  9293. // process second subtag : $subtags[1]
  9294. $length = strlen($subtags[1]);
  9295. if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
  9296. return $new_string;
  9297. }
  9298. if (!ctype_lower($subtags[1])) {
  9299. $subtags[1] = strtolower($subtags[1]);
  9300. }
  9301. $new_string .= '-' . $subtags[1];
  9302. if ($num_subtags == 2) {
  9303. return $new_string;
  9304. }
  9305. // process all other subtags, index 2 and up
  9306. for ($i = 2; $i < $num_subtags; $i++) {
  9307. $length = strlen($subtags[$i]);
  9308. if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
  9309. return $new_string;
  9310. }
  9311. if (!ctype_lower($subtags[$i])) {
  9312. $subtags[$i] = strtolower($subtags[$i]);
  9313. }
  9314. $new_string .= '-' . $subtags[$i];
  9315. }
  9316. return $new_string;
  9317. }
  9318. }
  9319. /**
  9320. * Decorator that, depending on a token, switches between two definitions.
  9321. */
  9322. class HTMLPurifier_AttrDef_Switch
  9323. {
  9324. /**
  9325. * @type string
  9326. */
  9327. protected $tag;
  9328. /**
  9329. * @type HTMLPurifier_AttrDef
  9330. */
  9331. protected $withTag;
  9332. /**
  9333. * @type HTMLPurifier_AttrDef
  9334. */
  9335. protected $withoutTag;
  9336. /**
  9337. * @param string $tag Tag name to switch upon
  9338. * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
  9339. * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
  9340. */
  9341. public function __construct($tag, $with_tag, $without_tag)
  9342. {
  9343. $this->tag = $tag;
  9344. $this->withTag = $with_tag;
  9345. $this->withoutTag = $without_tag;
  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. $token = $context->get('CurrentToken', true);
  9356. if (!$token || $token->name !== $this->tag) {
  9357. return $this->withoutTag->validate($string, $config, $context);
  9358. } else {
  9359. return $this->withTag->validate($string, $config, $context);
  9360. }
  9361. }
  9362. }
  9363. /**
  9364. * Validates arbitrary text according to the HTML spec.
  9365. */
  9366. class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
  9367. {
  9368. /**
  9369. * @param string $string
  9370. * @param HTMLPurifier_Config $config
  9371. * @param HTMLPurifier_Context $context
  9372. * @return bool|string
  9373. */
  9374. public function validate($string, $config, $context)
  9375. {
  9376. return $this->parseCDATA($string);
  9377. }
  9378. }
  9379. /**
  9380. * Validates a URI as defined by RFC 3986.
  9381. * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
  9382. */
  9383. class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
  9384. {
  9385. /**
  9386. * @type HTMLPurifier_URIParser
  9387. */
  9388. protected $parser;
  9389. /**
  9390. * @type bool
  9391. */
  9392. protected $embedsResource;
  9393. /**
  9394. * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
  9395. */
  9396. public function __construct($embeds_resource = false)
  9397. {
  9398. $this->parser = new HTMLPurifier_URIParser();
  9399. $this->embedsResource = (bool)$embeds_resource;
  9400. }
  9401. /**
  9402. * @param string $string
  9403. * @return HTMLPurifier_AttrDef_URI
  9404. */
  9405. public function make($string)
  9406. {
  9407. $embeds = ($string === 'embedded');
  9408. return new HTMLPurifier_AttrDef_URI($embeds);
  9409. }
  9410. /**
  9411. * @param string $uri
  9412. * @param HTMLPurifier_Config $config
  9413. * @param HTMLPurifier_Context $context
  9414. * @return bool|string
  9415. */
  9416. public function validate($uri, $config, $context)
  9417. {
  9418. if ($config->get('URI.Disable')) {
  9419. return false;
  9420. }
  9421. $uri = $this->parseCDATA($uri);
  9422. // parse the URI
  9423. $uri = $this->parser->parse($uri);
  9424. if ($uri === false) {
  9425. return false;
  9426. }
  9427. // add embedded flag to context for validators
  9428. $context->register('EmbeddedURI', $this->embedsResource);
  9429. $ok = false;
  9430. do {
  9431. // generic validation
  9432. $result = $uri->validate($config, $context);
  9433. if (!$result) {
  9434. break;
  9435. }
  9436. // chained filtering
  9437. $uri_def = $config->getDefinition('URI');
  9438. $result = $uri_def->filter($uri, $config, $context);
  9439. if (!$result) {
  9440. break;
  9441. }
  9442. // scheme-specific validation
  9443. $scheme_obj = $uri->getSchemeObj($config, $context);
  9444. if (!$scheme_obj) {
  9445. break;
  9446. }
  9447. if ($this->embedsResource && !$scheme_obj->browsable) {
  9448. break;
  9449. }
  9450. $result = $scheme_obj->validate($uri, $config, $context);
  9451. if (!$result) {
  9452. break;
  9453. }
  9454. // Post chained filtering
  9455. $result = $uri_def->postFilter($uri, $config, $context);
  9456. if (!$result) {
  9457. break;
  9458. }
  9459. // survived gauntlet
  9460. $ok = true;
  9461. } while (false);
  9462. $context->destroy('EmbeddedURI');
  9463. if (!$ok) {
  9464. return false;
  9465. }
  9466. // back to string
  9467. return $uri->toString();
  9468. }
  9469. }
  9470. /**
  9471. * Validates a number as defined by the CSS spec.
  9472. */
  9473. class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
  9474. {
  9475. /**
  9476. * Indicates whether or not only positive values are allowed.
  9477. * @type bool
  9478. */
  9479. protected $non_negative = false;
  9480. /**
  9481. * @param bool $non_negative indicates whether negatives are forbidden
  9482. */
  9483. public function __construct($non_negative = false)
  9484. {
  9485. $this->non_negative = $non_negative;
  9486. }
  9487. /**
  9488. * @param string $number
  9489. * @param HTMLPurifier_Config $config
  9490. * @param HTMLPurifier_Context $context
  9491. * @return string|bool
  9492. * @warning Some contexts do not pass $config, $context. These
  9493. * variables should not be used without checking HTMLPurifier_Length
  9494. */
  9495. public function validate($number, $config, $context)
  9496. {
  9497. $number = $this->parseCDATA($number);
  9498. if ($number === '') {
  9499. return false;
  9500. }
  9501. if ($number === '0') {
  9502. return '0';
  9503. }
  9504. $sign = '';
  9505. switch ($number[0]) {
  9506. case '-':
  9507. if ($this->non_negative) {
  9508. return false;
  9509. }
  9510. $sign = '-';
  9511. case '+':
  9512. $number = substr($number, 1);
  9513. }
  9514. if (ctype_digit($number)) {
  9515. $number = ltrim($number, '0');
  9516. return $number ? $sign . $number : '0';
  9517. }
  9518. // Period is the only non-numeric character allowed
  9519. if (strpos($number, '.') === false) {
  9520. return false;
  9521. }
  9522. list($left, $right) = explode('.', $number, 2);
  9523. if ($left === '' && $right === '') {
  9524. return false;
  9525. }
  9526. if ($left !== '' && !ctype_digit($left)) {
  9527. return false;
  9528. }
  9529. $left = ltrim($left, '0');
  9530. $right = rtrim($right, '0');
  9531. if ($right === '') {
  9532. return $left ? $sign . $left : '0';
  9533. } elseif (!ctype_digit($right)) {
  9534. return false;
  9535. }
  9536. return $sign . $left . '.' . $right;
  9537. }
  9538. }
  9539. class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
  9540. {
  9541. public function __construct()
  9542. {
  9543. parent::__construct(false); // opacity is non-negative, but we will clamp it
  9544. }
  9545. /**
  9546. * @param string $number
  9547. * @param HTMLPurifier_Config $config
  9548. * @param HTMLPurifier_Context $context
  9549. * @return string
  9550. */
  9551. public function validate($number, $config, $context)
  9552. {
  9553. $result = parent::validate($number, $config, $context);
  9554. if ($result === false) {
  9555. return $result;
  9556. }
  9557. $float = (float)$result;
  9558. if ($float < 0.0) {
  9559. $result = '0';
  9560. }
  9561. if ($float > 1.0) {
  9562. $result = '1';
  9563. }
  9564. return $result;
  9565. }
  9566. }
  9567. /**
  9568. * Validates shorthand CSS property background.
  9569. * @warning Does not support url tokens that have internal spaces.
  9570. */
  9571. class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
  9572. {
  9573. /**
  9574. * Local copy of component validators.
  9575. * @type HTMLPurifier_AttrDef[]
  9576. * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
  9577. */
  9578. protected $info;
  9579. /**
  9580. * @param HTMLPurifier_Config $config
  9581. */
  9582. public function __construct($config)
  9583. {
  9584. $def = $config->getCSSDefinition();
  9585. $this->info['background-color'] = $def->info['background-color'];
  9586. $this->info['background-image'] = $def->info['background-image'];
  9587. $this->info['background-repeat'] = $def->info['background-repeat'];
  9588. $this->info['background-attachment'] = $def->info['background-attachment'];
  9589. $this->info['background-position'] = $def->info['background-position'];
  9590. }
  9591. /**
  9592. * @param string $string
  9593. * @param HTMLPurifier_Config $config
  9594. * @param HTMLPurifier_Context $context
  9595. * @return bool|string
  9596. */
  9597. public function validate($string, $config, $context)
  9598. {
  9599. // regular pre-processing
  9600. $string = $this->parseCDATA($string);
  9601. if ($string === '') {
  9602. return false;
  9603. }
  9604. // munge rgb() decl if necessary
  9605. $string = $this->mungeRgb($string);
  9606. // assumes URI doesn't have spaces in it
  9607. $bits = explode(' ', $string); // bits to process
  9608. $caught = array();
  9609. $caught['color'] = false;
  9610. $caught['image'] = false;
  9611. $caught['repeat'] = false;
  9612. $caught['attachment'] = false;
  9613. $caught['position'] = false;
  9614. $i = 0; // number of catches
  9615. foreach ($bits as $bit) {
  9616. if ($bit === '') {
  9617. continue;
  9618. }
  9619. foreach ($caught as $key => $status) {
  9620. if ($key != 'position') {
  9621. if ($status !== false) {
  9622. continue;
  9623. }
  9624. $r = $this->info['background-' . $key]->validate($bit, $config, $context);
  9625. } else {
  9626. $r = $bit;
  9627. }
  9628. if ($r === false) {
  9629. continue;
  9630. }
  9631. if ($key == 'position') {
  9632. if ($caught[$key] === false) {
  9633. $caught[$key] = '';
  9634. }
  9635. $caught[$key] .= $r . ' ';
  9636. } else {
  9637. $caught[$key] = $r;
  9638. }
  9639. $i++;
  9640. break;
  9641. }
  9642. }
  9643. if (!$i) {
  9644. return false;
  9645. }
  9646. if ($caught['position'] !== false) {
  9647. $caught['position'] = $this->info['background-position']->
  9648. validate($caught['position'], $config, $context);
  9649. }
  9650. $ret = array();
  9651. foreach ($caught as $value) {
  9652. if ($value === false) {
  9653. continue;
  9654. }
  9655. $ret[] = $value;
  9656. }
  9657. if (empty($ret)) {
  9658. return false;
  9659. }
  9660. return implode(' ', $ret);
  9661. }
  9662. }
  9663. /* W3C says:
  9664. [ // adjective and number must be in correct order, even if
  9665. // you could switch them without introducing ambiguity.
  9666. // some browsers support that syntax
  9667. [
  9668. <percentage> | <length> | left | center | right
  9669. ]
  9670. [
  9671. <percentage> | <length> | top | center | bottom
  9672. ]?
  9673. ] |
  9674. [ // this signifies that the vertical and horizontal adjectives
  9675. // can be arbitrarily ordered, however, there can only be two,
  9676. // one of each, or none at all
  9677. [
  9678. left | center | right
  9679. ] ||
  9680. [
  9681. top | center | bottom
  9682. ]
  9683. ]
  9684. top, left = 0%
  9685. center, (none) = 50%
  9686. bottom, right = 100%
  9687. */
  9688. /* QuirksMode says:
  9689. keyword + length/percentage must be ordered correctly, as per W3C
  9690. Internet Explorer and Opera, however, support arbitrary ordering. We
  9691. should fix it up.
  9692. Minor issue though, not strictly necessary.
  9693. */
  9694. // control freaks may appreciate the ability to convert these to
  9695. // percentages or something, but it's not necessary
  9696. /**
  9697. * Validates the value of background-position.
  9698. */
  9699. class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
  9700. {
  9701. /**
  9702. * @type HTMLPurifier_AttrDef_CSS_Length
  9703. */
  9704. protected $length;
  9705. /**
  9706. * @type HTMLPurifier_AttrDef_CSS_Percentage
  9707. */
  9708. protected $percentage;
  9709. public function __construct()
  9710. {
  9711. $this->length = new HTMLPurifier_AttrDef_CSS_Length();
  9712. $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
  9713. }
  9714. /**
  9715. * @param string $string
  9716. * @param HTMLPurifier_Config $config
  9717. * @param HTMLPurifier_Context $context
  9718. * @return bool|string
  9719. */
  9720. public function validate($string, $config, $context)
  9721. {
  9722. $string = $this->parseCDATA($string);
  9723. $bits = explode(' ', $string);
  9724. $keywords = array();
  9725. $keywords['h'] = false; // left, right
  9726. $keywords['v'] = false; // top, bottom
  9727. $keywords['ch'] = false; // center (first word)
  9728. $keywords['cv'] = false; // center (second word)
  9729. $measures = array();
  9730. $i = 0;
  9731. $lookup = array(
  9732. 'top' => 'v',
  9733. 'bottom' => 'v',
  9734. 'left' => 'h',
  9735. 'right' => 'h',
  9736. 'center' => 'c'
  9737. );
  9738. foreach ($bits as $bit) {
  9739. if ($bit === '') {
  9740. continue;
  9741. }
  9742. // test for keyword
  9743. $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
  9744. if (isset($lookup[$lbit])) {
  9745. $status = $lookup[$lbit];
  9746. if ($status == 'c') {
  9747. if ($i == 0) {
  9748. $status = 'ch';
  9749. } else {
  9750. $status = 'cv';
  9751. }
  9752. }
  9753. $keywords[$status] = $lbit;
  9754. $i++;
  9755. }
  9756. // test for length
  9757. $r = $this->length->validate($bit, $config, $context);
  9758. if ($r !== false) {
  9759. $measures[] = $r;
  9760. $i++;
  9761. }
  9762. // test for percentage
  9763. $r = $this->percentage->validate($bit, $config, $context);
  9764. if ($r !== false) {
  9765. $measures[] = $r;
  9766. $i++;
  9767. }
  9768. }
  9769. if (!$i) {
  9770. return false;
  9771. } // no valid values were caught
  9772. $ret = array();
  9773. // first keyword
  9774. if ($keywords['h']) {
  9775. $ret[] = $keywords['h'];
  9776. } elseif ($keywords['ch']) {
  9777. $ret[] = $keywords['ch'];
  9778. $keywords['cv'] = false; // prevent re-use: center = center center
  9779. } elseif (count($measures)) {
  9780. $ret[] = array_shift($measures);
  9781. }
  9782. if ($keywords['v']) {
  9783. $ret[] = $keywords['v'];
  9784. } elseif ($keywords['cv']) {
  9785. $ret[] = $keywords['cv'];
  9786. } elseif (count($measures)) {
  9787. $ret[] = array_shift($measures);
  9788. }
  9789. if (empty($ret)) {
  9790. return false;
  9791. }
  9792. return implode(' ', $ret);
  9793. }
  9794. }
  9795. /**
  9796. * Validates the border property as defined by CSS.
  9797. */
  9798. class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
  9799. {
  9800. /**
  9801. * Local copy of properties this property is shorthand for.
  9802. * @type HTMLPurifier_AttrDef[]
  9803. */
  9804. protected $info = array();
  9805. /**
  9806. * @param HTMLPurifier_Config $config
  9807. */
  9808. public function __construct($config)
  9809. {
  9810. $def = $config->getCSSDefinition();
  9811. $this->info['border-width'] = $def->info['border-width'];
  9812. $this->info['border-style'] = $def->info['border-style'];
  9813. $this->info['border-top-color'] = $def->info['border-top-color'];
  9814. }
  9815. /**
  9816. * @param string $string
  9817. * @param HTMLPurifier_Config $config
  9818. * @param HTMLPurifier_Context $context
  9819. * @return bool|string
  9820. */
  9821. public function validate($string, $config, $context)
  9822. {
  9823. $string = $this->parseCDATA($string);
  9824. $string = $this->mungeRgb($string);
  9825. $bits = explode(' ', $string);
  9826. $done = array(); // segments we've finished
  9827. $ret = ''; // return value
  9828. foreach ($bits as $bit) {
  9829. foreach ($this->info as $propname => $validator) {
  9830. if (isset($done[$propname])) {
  9831. continue;
  9832. }
  9833. $r = $validator->validate($bit, $config, $context);
  9834. if ($r !== false) {
  9835. $ret .= $r . ' ';
  9836. $done[$propname] = true;
  9837. break;
  9838. }
  9839. }
  9840. }
  9841. return rtrim($ret);
  9842. }
  9843. }
  9844. /**
  9845. * Validates Color as defined by CSS.
  9846. */
  9847. class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
  9848. {
  9849. /**
  9850. * @param string $color
  9851. * @param HTMLPurifier_Config $config
  9852. * @param HTMLPurifier_Context $context
  9853. * @return bool|string
  9854. */
  9855. public function validate($color, $config, $context)
  9856. {
  9857. static $colors = null;
  9858. if ($colors === null) {
  9859. $colors = $config->get('Core.ColorKeywords');
  9860. }
  9861. $color = trim($color);
  9862. if ($color === '') {
  9863. return false;
  9864. }
  9865. $lower = strtolower($color);
  9866. if (isset($colors[$lower])) {
  9867. return $colors[$lower];
  9868. }
  9869. if (strpos($color, 'rgb(') !== false) {
  9870. // rgb literal handling
  9871. $length = strlen($color);
  9872. if (strpos($color, ')') !== $length - 1) {
  9873. return false;
  9874. }
  9875. $triad = substr($color, 4, $length - 4 - 1);
  9876. $parts = explode(',', $triad);
  9877. if (count($parts) !== 3) {
  9878. return false;
  9879. }
  9880. $type = false; // to ensure that they're all the same type
  9881. $new_parts = array();
  9882. foreach ($parts as $part) {
  9883. $part = trim($part);
  9884. if ($part === '') {
  9885. return false;
  9886. }
  9887. $length = strlen($part);
  9888. if ($part[$length - 1] === '%') {
  9889. // handle percents
  9890. if (!$type) {
  9891. $type = 'percentage';
  9892. } elseif ($type !== 'percentage') {
  9893. return false;
  9894. }
  9895. $num = (float)substr($part, 0, $length - 1);
  9896. if ($num < 0) {
  9897. $num = 0;
  9898. }
  9899. if ($num > 100) {
  9900. $num = 100;
  9901. }
  9902. $new_parts[] = "$num%";
  9903. } else {
  9904. // handle integers
  9905. if (!$type) {
  9906. $type = 'integer';
  9907. } elseif ($type !== 'integer') {
  9908. return false;
  9909. }
  9910. $num = (int)$part;
  9911. if ($num < 0) {
  9912. $num = 0;
  9913. }
  9914. if ($num > 255) {
  9915. $num = 255;
  9916. }
  9917. $new_parts[] = (string)$num;
  9918. }
  9919. }
  9920. $new_triad = implode(',', $new_parts);
  9921. $color = "rgb($new_triad)";
  9922. } else {
  9923. // hexadecimal handling
  9924. if ($color[0] === '#') {
  9925. $hex = substr($color, 1);
  9926. } else {
  9927. $hex = $color;
  9928. $color = '#' . $color;
  9929. }
  9930. $length = strlen($hex);
  9931. if ($length !== 3 && $length !== 6) {
  9932. return false;
  9933. }
  9934. if (!ctype_xdigit($hex)) {
  9935. return false;
  9936. }
  9937. }
  9938. return $color;
  9939. }
  9940. }
  9941. /**
  9942. * Allows multiple validators to attempt to validate attribute.
  9943. *
  9944. * Composite is just what it sounds like: a composite of many validators.
  9945. * This means that multiple HTMLPurifier_AttrDef objects will have a whack
  9946. * at the string. If one of them passes, that's what is returned. This is
  9947. * especially useful for CSS values, which often are a choice between
  9948. * an enumerated set of predefined values or a flexible data type.
  9949. */
  9950. class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
  9951. {
  9952. /**
  9953. * List of objects that may process strings.
  9954. * @type HTMLPurifier_AttrDef[]
  9955. * @todo Make protected
  9956. */
  9957. public $defs;
  9958. /**
  9959. * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
  9960. */
  9961. public function __construct($defs)
  9962. {
  9963. $this->defs = $defs;
  9964. }
  9965. /**
  9966. * @param string $string
  9967. * @param HTMLPurifier_Config $config
  9968. * @param HTMLPurifier_Context $context
  9969. * @return bool|string
  9970. */
  9971. public function validate($string, $config, $context)
  9972. {
  9973. foreach ($this->defs as $i => $def) {
  9974. $result = $this->defs[$i]->validate($string, $config, $context);
  9975. if ($result !== false) {
  9976. return $result;
  9977. }
  9978. }
  9979. return false;
  9980. }
  9981. }
  9982. /**
  9983. * Decorator which enables CSS properties to be disabled for specific elements.
  9984. */
  9985. class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
  9986. {
  9987. /**
  9988. * @type HTMLPurifier_AttrDef
  9989. */
  9990. public $def;
  9991. /**
  9992. * @type string
  9993. */
  9994. public $element;
  9995. /**
  9996. * @param HTMLPurifier_AttrDef $def Definition to wrap
  9997. * @param string $element Element to deny
  9998. */
  9999. public function __construct($def, $element)
  10000. {
  10001. $this->def = $def;
  10002. $this->element = $element;
  10003. }
  10004. /**
  10005. * Checks if CurrentToken is set and equal to $this->element
  10006. * @param string $string
  10007. * @param HTMLPurifier_Config $config
  10008. * @param HTMLPurifier_Context $context
  10009. * @return bool|string
  10010. */
  10011. public function validate($string, $config, $context)
  10012. {
  10013. $token = $context->get('CurrentToken', true);
  10014. if ($token && $token->name == $this->element) {
  10015. return false;
  10016. }
  10017. return $this->def->validate($string, $config, $context);
  10018. }
  10019. }
  10020. /**
  10021. * Microsoft's proprietary filter: CSS property
  10022. * @note Currently supports the alpha filter. In the future, this will
  10023. * probably need an extensible framework
  10024. */
  10025. class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
  10026. {
  10027. /**
  10028. * @type HTMLPurifier_AttrDef_Integer
  10029. */
  10030. protected $intValidator;
  10031. public function __construct()
  10032. {
  10033. $this->intValidator = new HTMLPurifier_AttrDef_Integer();
  10034. }
  10035. /**
  10036. * @param string $value
  10037. * @param HTMLPurifier_Config $config
  10038. * @param HTMLPurifier_Context $context
  10039. * @return bool|string
  10040. */
  10041. public function validate($value, $config, $context)
  10042. {
  10043. $value = $this->parseCDATA($value);
  10044. if ($value === 'none') {
  10045. return $value;
  10046. }
  10047. // if we looped this we could support multiple filters
  10048. $function_length = strcspn($value, '(');
  10049. $function = trim(substr($value, 0, $function_length));
  10050. if ($function !== 'alpha' &&
  10051. $function !== 'Alpha' &&
  10052. $function !== 'progid:DXImageTransform.Microsoft.Alpha'
  10053. ) {
  10054. return false;
  10055. }
  10056. $cursor = $function_length + 1;
  10057. $parameters_length = strcspn($value, ')', $cursor);
  10058. $parameters = substr($value, $cursor, $parameters_length);
  10059. $params = explode(',', $parameters);
  10060. $ret_params = array();
  10061. $lookup = array();
  10062. foreach ($params as $param) {
  10063. list($key, $value) = explode('=', $param);
  10064. $key = trim($key);
  10065. $value = trim($value);
  10066. if (isset($lookup[$key])) {
  10067. continue;
  10068. }
  10069. if ($key !== 'opacity') {
  10070. continue;
  10071. }
  10072. $value = $this->intValidator->validate($value, $config, $context);
  10073. if ($value === false) {
  10074. continue;
  10075. }
  10076. $int = (int)$value;
  10077. if ($int > 100) {
  10078. $value = '100';
  10079. }
  10080. if ($int < 0) {
  10081. $value = '0';
  10082. }
  10083. $ret_params[] = "$key=$value";
  10084. $lookup[$key] = true;
  10085. }
  10086. $ret_parameters = implode(',', $ret_params);
  10087. $ret_function = "$function($ret_parameters)";
  10088. return $ret_function;
  10089. }
  10090. }
  10091. /**
  10092. * Validates shorthand CSS property font.
  10093. */
  10094. class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
  10095. {
  10096. /**
  10097. * Local copy of validators
  10098. * @type HTMLPurifier_AttrDef[]
  10099. * @note If we moved specific CSS property definitions to their own
  10100. * classes instead of having them be assembled at run time by
  10101. * CSSDefinition, this wouldn't be necessary. We'd instantiate
  10102. * our own copies.
  10103. */
  10104. protected $info = array();
  10105. /**
  10106. * @param HTMLPurifier_Config $config
  10107. */
  10108. public function __construct($config)
  10109. {
  10110. $def = $config->getCSSDefinition();
  10111. $this->info['font-style'] = $def->info['font-style'];
  10112. $this->info['font-variant'] = $def->info['font-variant'];
  10113. $this->info['font-weight'] = $def->info['font-weight'];
  10114. $this->info['font-size'] = $def->info['font-size'];
  10115. $this->info['line-height'] = $def->info['line-height'];
  10116. $this->info['font-family'] = $def->info['font-family'];
  10117. }
  10118. /**
  10119. * @param string $string
  10120. * @param HTMLPurifier_Config $config
  10121. * @param HTMLPurifier_Context $context
  10122. * @return bool|string
  10123. */
  10124. public function validate($string, $config, $context)
  10125. {
  10126. static $system_fonts = array(
  10127. 'caption' => true,
  10128. 'icon' => true,
  10129. 'menu' => true,
  10130. 'message-box' => true,
  10131. 'small-caption' => true,
  10132. 'status-bar' => true
  10133. );
  10134. // regular pre-processing
  10135. $string = $this->parseCDATA($string);
  10136. if ($string === '') {
  10137. return false;
  10138. }
  10139. // check if it's one of the keywords
  10140. $lowercase_string = strtolower($string);
  10141. if (isset($system_fonts[$lowercase_string])) {
  10142. return $lowercase_string;
  10143. }
  10144. $bits = explode(' ', $string); // bits to process
  10145. $stage = 0; // this indicates what we're looking for
  10146. $caught = array(); // which stage 0 properties have we caught?
  10147. $stage_1 = array('font-style', 'font-variant', 'font-weight');
  10148. $final = ''; // output
  10149. for ($i = 0, $size = count($bits); $i < $size; $i++) {
  10150. if ($bits[$i] === '') {
  10151. continue;
  10152. }
  10153. switch ($stage) {
  10154. case 0: // attempting to catch font-style, font-variant or font-weight
  10155. foreach ($stage_1 as $validator_name) {
  10156. if (isset($caught[$validator_name])) {
  10157. continue;
  10158. }
  10159. $r = $this->info[$validator_name]->validate(
  10160. $bits[$i],
  10161. $config,
  10162. $context
  10163. );
  10164. if ($r !== false) {
  10165. $final .= $r . ' ';
  10166. $caught[$validator_name] = true;
  10167. break;
  10168. }
  10169. }
  10170. // all three caught, continue on
  10171. if (count($caught) >= 3) {
  10172. $stage = 1;
  10173. }
  10174. if ($r !== false) {
  10175. break;
  10176. }
  10177. case 1: // attempting to catch font-size and perhaps line-height
  10178. $found_slash = false;
  10179. if (strpos($bits[$i], '/') !== false) {
  10180. list($font_size, $line_height) =
  10181. explode('/', $bits[$i]);
  10182. if ($line_height === '') {
  10183. // ooh, there's a space after the slash!
  10184. $line_height = false;
  10185. $found_slash = true;
  10186. }
  10187. } else {
  10188. $font_size = $bits[$i];
  10189. $line_height = false;
  10190. }
  10191. $r = $this->info['font-size']->validate(
  10192. $font_size,
  10193. $config,
  10194. $context
  10195. );
  10196. if ($r !== false) {
  10197. $final .= $r;
  10198. // attempt to catch line-height
  10199. if ($line_height === false) {
  10200. // we need to scroll forward
  10201. for ($j = $i + 1; $j < $size; $j++) {
  10202. if ($bits[$j] === '') {
  10203. continue;
  10204. }
  10205. if ($bits[$j] === '/') {
  10206. if ($found_slash) {
  10207. return false;
  10208. } else {
  10209. $found_slash = true;
  10210. continue;
  10211. }
  10212. }
  10213. $line_height = $bits[$j];
  10214. break;
  10215. }
  10216. } else {
  10217. // slash already found
  10218. $found_slash = true;
  10219. $j = $i;
  10220. }
  10221. if ($found_slash) {
  10222. $i = $j;
  10223. $r = $this->info['line-height']->validate(
  10224. $line_height,
  10225. $config,
  10226. $context
  10227. );
  10228. if ($r !== false) {
  10229. $final .= '/' . $r;
  10230. }
  10231. }
  10232. $final .= ' ';
  10233. $stage = 2;
  10234. break;
  10235. }
  10236. return false;
  10237. case 2: // attempting to catch font-family
  10238. $font_family =
  10239. implode(' ', array_slice($bits, $i, $size - $i));
  10240. $r = $this->info['font-family']->validate(
  10241. $font_family,
  10242. $config,
  10243. $context
  10244. );
  10245. if ($r !== false) {
  10246. $final .= $r . ' ';
  10247. // processing completed successfully
  10248. return rtrim($final);
  10249. }
  10250. return false;
  10251. }
  10252. }
  10253. return false;
  10254. }
  10255. }
  10256. /**
  10257. * Validates a font family list according to CSS spec
  10258. */
  10259. class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
  10260. {
  10261. protected $mask = null;
  10262. public function __construct()
  10263. {
  10264. $this->mask = '_- ';
  10265. for ($c = 'a'; $c <= 'z'; $c++) {
  10266. $this->mask .= $c;
  10267. }
  10268. for ($c = 'A'; $c <= 'Z'; $c++) {
  10269. $this->mask .= $c;
  10270. }
  10271. for ($c = '0'; $c <= '9'; $c++) {
  10272. $this->mask .= $c;
  10273. } // cast-y, but should be fine
  10274. // special bytes used by UTF-8
  10275. for ($i = 0x80; $i <= 0xFF; $i++) {
  10276. // We don't bother excluding invalid bytes in this range,
  10277. // because the our restriction of well-formed UTF-8 will
  10278. // prevent these from ever occurring.
  10279. $this->mask .= chr($i);
  10280. }
  10281. /*
  10282. PHP's internal strcspn implementation is
  10283. O(length of string * length of mask), making it inefficient
  10284. for large masks. However, it's still faster than
  10285. preg_match 8)
  10286. for (p = s1;;) {
  10287. spanp = s2;
  10288. do {
  10289. if (*spanp == c || p == s1_end) {
  10290. return p - s1;
  10291. }
  10292. } while (spanp++ < (s2_end - 1));
  10293. c = *++p;
  10294. }
  10295. */
  10296. // possible optimization: invert the mask.
  10297. }
  10298. /**
  10299. * @param string $string
  10300. * @param HTMLPurifier_Config $config
  10301. * @param HTMLPurifier_Context $context
  10302. * @return bool|string
  10303. */
  10304. public function validate($string, $config, $context)
  10305. {
  10306. static $generic_names = array(
  10307. 'serif' => true,
  10308. 'sans-serif' => true,
  10309. 'monospace' => true,
  10310. 'fantasy' => true,
  10311. 'cursive' => true
  10312. );
  10313. $allowed_fonts = $config->get('CSS.AllowedFonts');
  10314. // assume that no font names contain commas in them
  10315. $fonts = explode(',', $string);
  10316. $final = '';
  10317. foreach ($fonts as $font) {
  10318. $font = trim($font);
  10319. if ($font === '') {
  10320. continue;
  10321. }
  10322. // match a generic name
  10323. if (isset($generic_names[$font])) {
  10324. if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
  10325. $final .= $font . ', ';
  10326. }
  10327. continue;
  10328. }
  10329. // match a quoted name
  10330. if ($font[0] === '"' || $font[0] === "'") {
  10331. $length = strlen($font);
  10332. if ($length <= 2) {
  10333. continue;
  10334. }
  10335. $quote = $font[0];
  10336. if ($font[$length - 1] !== $quote) {
  10337. continue;
  10338. }
  10339. $font = substr($font, 1, $length - 2);
  10340. }
  10341. $font = $this->expandCSSEscape($font);
  10342. // $font is a pure representation of the font name
  10343. if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
  10344. continue;
  10345. }
  10346. if (ctype_alnum($font) && $font !== '') {
  10347. // very simple font, allow it in unharmed
  10348. $final .= $font . ', ';
  10349. continue;
  10350. }
  10351. // bugger out on whitespace. form feed (0C) really
  10352. // shouldn't show up regardless
  10353. $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
  10354. // Here, there are various classes of characters which need
  10355. // to be treated differently:
  10356. // - Alphanumeric characters are essentially safe. We
  10357. // handled these above.
  10358. // - Spaces require quoting, though most parsers will do
  10359. // the right thing if there aren't any characters that
  10360. // can be misinterpreted
  10361. // - Dashes rarely occur, but they fairly unproblematic
  10362. // for parsing/rendering purposes.
  10363. // The above characters cover the majority of Western font
  10364. // names.
  10365. // - Arbitrary Unicode characters not in ASCII. Because
  10366. // most parsers give little thought to Unicode, treatment
  10367. // of these codepoints is basically uniform, even for
  10368. // punctuation-like codepoints. These characters can
  10369. // show up in non-Western pages and are supported by most
  10370. // major browsers, for example: "MS 明朝" is a
  10371. // legitimate font-name
  10372. // <http://ja.wikipedia.org/wiki/MS_明朝>. See
  10373. // the CSS3 spec for more examples:
  10374. // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
  10375. // You can see live samples of these on the Internet:
  10376. // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
  10377. // However, most of these fonts have ASCII equivalents:
  10378. // for example, 'MS Mincho', and it's considered
  10379. // professional to use ASCII font names instead of
  10380. // Unicode font names. Thanks Takeshi Terada for
  10381. // providing this information.
  10382. // The following characters, to my knowledge, have not been
  10383. // used to name font names.
  10384. // - Single quote. While theoretically you might find a
  10385. // font name that has a single quote in its name (serving
  10386. // as an apostrophe, e.g. Dave's Scribble), I haven't
  10387. // been able to find any actual examples of this.
  10388. // Internet Explorer's cssText translation (which I
  10389. // believe is invoked by innerHTML) normalizes any
  10390. // quoting to single quotes, and fails to escape single
  10391. // quotes. (Note that this is not IE's behavior for all
  10392. // CSS properties, just some sort of special casing for
  10393. // font-family). So a single quote *cannot* be used
  10394. // safely in the font-family context if there will be an
  10395. // innerHTML/cssText translation. Note that Firefox 3.x
  10396. // does this too.
  10397. // - Double quote. In IE, these get normalized to
  10398. // single-quotes, no matter what the encoding. (Fun
  10399. // fact, in IE8, the 'content' CSS property gained
  10400. // support, where they special cased to preserve encoded
  10401. // double quotes, but still translate unadorned double
  10402. // quotes into single quotes.) So, because their
  10403. // fixpoint behavior is identical to single quotes, they
  10404. // cannot be allowed either. Firefox 3.x displays
  10405. // single-quote style behavior.
  10406. // - Backslashes are reduced by one (so \\ -> \) every
  10407. // iteration, so they cannot be used safely. This shows
  10408. // up in IE7, IE8 and FF3
  10409. // - Semicolons, commas and backticks are handled properly.
  10410. // - The rest of the ASCII punctuation is handled properly.
  10411. // We haven't checked what browsers do to unadorned
  10412. // versions, but this is not important as long as the
  10413. // browser doesn't /remove/ surrounding quotes (as IE does
  10414. // for HTML).
  10415. //
  10416. // With these results in hand, we conclude that there are
  10417. // various levels of safety:
  10418. // - Paranoid: alphanumeric, spaces and dashes(?)
  10419. // - International: Paranoid + non-ASCII Unicode
  10420. // - Edgy: Everything except quotes, backslashes
  10421. // - NoJS: Standards compliance, e.g. sod IE. Note that
  10422. // with some judicious character escaping (since certain
  10423. // types of escaping doesn't work) this is theoretically
  10424. // OK as long as innerHTML/cssText is not called.
  10425. // We believe that international is a reasonable default
  10426. // (that we will implement now), and once we do more
  10427. // extensive research, we may feel comfortable with dropping
  10428. // it down to edgy.
  10429. // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
  10430. // str(c)spn assumes that the string was already well formed
  10431. // Unicode (which of course it is).
  10432. if (strspn($font, $this->mask) !== strlen($font)) {
  10433. continue;
  10434. }
  10435. // Historical:
  10436. // In the absence of innerHTML/cssText, these ugly
  10437. // transforms don't pose a security risk (as \\ and \"
  10438. // might--these escapes are not supported by most browsers).
  10439. // We could try to be clever and use single-quote wrapping
  10440. // when there is a double quote present, but I have choosen
  10441. // not to implement that. (NOTE: you can reduce the amount
  10442. // of escapes by one depending on what quoting style you use)
  10443. // $font = str_replace('\\', '\\5C ', $font);
  10444. // $font = str_replace('"', '\\22 ', $font);
  10445. // $font = str_replace("'", '\\27 ', $font);
  10446. // font possibly with spaces, requires quoting
  10447. $final .= "'$font', ";
  10448. }
  10449. $final = rtrim($final, ', ');
  10450. if ($final === '') {
  10451. return false;
  10452. }
  10453. return $final;
  10454. }
  10455. }
  10456. /**
  10457. * Validates based on {ident} CSS grammar production
  10458. */
  10459. class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
  10460. {
  10461. /**
  10462. * @param string $string
  10463. * @param HTMLPurifier_Config $config
  10464. * @param HTMLPurifier_Context $context
  10465. * @return bool|string
  10466. */
  10467. public function validate($string, $config, $context)
  10468. {
  10469. $string = trim($string);
  10470. // early abort: '' and '0' (strings that convert to false) are invalid
  10471. if (!$string) {
  10472. return false;
  10473. }
  10474. $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
  10475. if (!preg_match($pattern, $string)) {
  10476. return false;
  10477. }
  10478. return $string;
  10479. }
  10480. }
  10481. /**
  10482. * Decorator which enables !important to be used in CSS values.
  10483. */
  10484. class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
  10485. {
  10486. /**
  10487. * @type HTMLPurifier_AttrDef
  10488. */
  10489. public $def;
  10490. /**
  10491. * @type bool
  10492. */
  10493. public $allow;
  10494. /**
  10495. * @param HTMLPurifier_AttrDef $def Definition to wrap
  10496. * @param bool $allow Whether or not to allow !important
  10497. */
  10498. public function __construct($def, $allow = false)
  10499. {
  10500. $this->def = $def;
  10501. $this->allow = $allow;
  10502. }
  10503. /**
  10504. * Intercepts and removes !important if necessary
  10505. * @param string $string
  10506. * @param HTMLPurifier_Config $config
  10507. * @param HTMLPurifier_Context $context
  10508. * @return bool|string
  10509. */
  10510. public function validate($string, $config, $context)
  10511. {
  10512. // test for ! and important tokens
  10513. $string = trim($string);
  10514. $is_important = false;
  10515. // :TODO: optimization: test directly for !important and ! important
  10516. if (strlen($string) >= 9 && substr($string, -9) === 'important') {
  10517. $temp = rtrim(substr($string, 0, -9));
  10518. // use a temp, because we might want to restore important
  10519. if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
  10520. $string = rtrim(substr($temp, 0, -1));
  10521. $is_important = true;
  10522. }
  10523. }
  10524. $string = $this->def->validate($string, $config, $context);
  10525. if ($this->allow && $is_important) {
  10526. $string .= ' !important';
  10527. }
  10528. return $string;
  10529. }
  10530. }
  10531. /**
  10532. * Represents a Length as defined by CSS.
  10533. */
  10534. class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
  10535. {
  10536. /**
  10537. * @type HTMLPurifier_Length|string
  10538. */
  10539. protected $min;
  10540. /**
  10541. * @type HTMLPurifier_Length|string
  10542. */
  10543. protected $max;
  10544. /**
  10545. * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
  10546. * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
  10547. */
  10548. public function __construct($min = null, $max = null)
  10549. {
  10550. $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
  10551. $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
  10552. }
  10553. /**
  10554. * @param string $string
  10555. * @param HTMLPurifier_Config $config
  10556. * @param HTMLPurifier_Context $context
  10557. * @return bool|string
  10558. */
  10559. public function validate($string, $config, $context)
  10560. {
  10561. $string = $this->parseCDATA($string);
  10562. // Optimizations
  10563. if ($string === '') {
  10564. return false;
  10565. }
  10566. if ($string === '0') {
  10567. return '0';
  10568. }
  10569. if (strlen($string) === 1) {
  10570. return false;
  10571. }
  10572. $length = HTMLPurifier_Length::make($string);
  10573. if (!$length->isValid()) {
  10574. return false;
  10575. }
  10576. if ($this->min) {
  10577. $c = $length->compareTo($this->min);
  10578. if ($c === false) {
  10579. return false;
  10580. }
  10581. if ($c < 0) {
  10582. return false;
  10583. }
  10584. }
  10585. if ($this->max) {
  10586. $c = $length->compareTo($this->max);
  10587. if ($c === false) {
  10588. return false;
  10589. }
  10590. if ($c > 0) {
  10591. return false;
  10592. }
  10593. }
  10594. return $length->toString();
  10595. }
  10596. }
  10597. /**
  10598. * Validates shorthand CSS property list-style.
  10599. * @warning Does not support url tokens that have internal spaces.
  10600. */
  10601. class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
  10602. {
  10603. /**
  10604. * Local copy of validators.
  10605. * @type HTMLPurifier_AttrDef[]
  10606. * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
  10607. */
  10608. protected $info;
  10609. /**
  10610. * @param HTMLPurifier_Config $config
  10611. */
  10612. public function __construct($config)
  10613. {
  10614. $def = $config->getCSSDefinition();
  10615. $this->info['list-style-type'] = $def->info['list-style-type'];
  10616. $this->info['list-style-position'] = $def->info['list-style-position'];
  10617. $this->info['list-style-image'] = $def->info['list-style-image'];
  10618. }
  10619. /**
  10620. * @param string $string
  10621. * @param HTMLPurifier_Config $config
  10622. * @param HTMLPurifier_Context $context
  10623. * @return bool|string
  10624. */
  10625. public function validate($string, $config, $context)
  10626. {
  10627. // regular pre-processing
  10628. $string = $this->parseCDATA($string);
  10629. if ($string === '') {
  10630. return false;
  10631. }
  10632. // assumes URI doesn't have spaces in it
  10633. $bits = explode(' ', strtolower($string)); // bits to process
  10634. $caught = array();
  10635. $caught['type'] = false;
  10636. $caught['position'] = false;
  10637. $caught['image'] = false;
  10638. $i = 0; // number of catches
  10639. $none = false;
  10640. foreach ($bits as $bit) {
  10641. if ($i >= 3) {
  10642. return;
  10643. } // optimization bit
  10644. if ($bit === '') {
  10645. continue;
  10646. }
  10647. foreach ($caught as $key => $status) {
  10648. if ($status !== false) {
  10649. continue;
  10650. }
  10651. $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
  10652. if ($r === false) {
  10653. continue;
  10654. }
  10655. if ($r === 'none') {
  10656. if ($none) {
  10657. continue;
  10658. } else {
  10659. $none = true;
  10660. }
  10661. if ($key == 'image') {
  10662. continue;
  10663. }
  10664. }
  10665. $caught[$key] = $r;
  10666. $i++;
  10667. break;
  10668. }
  10669. }
  10670. if (!$i) {
  10671. return false;
  10672. }
  10673. $ret = array();
  10674. // construct type
  10675. if ($caught['type']) {
  10676. $ret[] = $caught['type'];
  10677. }
  10678. // construct image
  10679. if ($caught['image']) {
  10680. $ret[] = $caught['image'];
  10681. }
  10682. // construct position
  10683. if ($caught['position']) {
  10684. $ret[] = $caught['position'];
  10685. }
  10686. if (empty($ret)) {
  10687. return false;
  10688. }
  10689. return implode(' ', $ret);
  10690. }
  10691. }
  10692. /**
  10693. * Framework class for strings that involve multiple values.
  10694. *
  10695. * Certain CSS properties such as border-width and margin allow multiple
  10696. * lengths to be specified. This class can take a vanilla border-width
  10697. * definition and multiply it, usually into a max of four.
  10698. *
  10699. * @note Even though the CSS specification isn't clear about it, inherit
  10700. * can only be used alone: it will never manifest as part of a multi
  10701. * shorthand declaration. Thus, this class does not allow inherit.
  10702. */
  10703. class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
  10704. {
  10705. /**
  10706. * Instance of component definition to defer validation to.
  10707. * @type HTMLPurifier_AttrDef
  10708. * @todo Make protected
  10709. */
  10710. public $single;
  10711. /**
  10712. * Max number of values allowed.
  10713. * @todo Make protected
  10714. */
  10715. public $max;
  10716. /**
  10717. * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
  10718. * @param int $max Max number of values allowed (usually four)
  10719. */
  10720. public function __construct($single, $max = 4)
  10721. {
  10722. $this->single = $single;
  10723. $this->max = $max;
  10724. }
  10725. /**
  10726. * @param string $string
  10727. * @param HTMLPurifier_Config $config
  10728. * @param HTMLPurifier_Context $context
  10729. * @return bool|string
  10730. */
  10731. public function validate($string, $config, $context)
  10732. {
  10733. $string = $this->mungeRgb($this->parseCDATA($string));
  10734. if ($string === '') {
  10735. return false;
  10736. }
  10737. $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
  10738. $length = count($parts);
  10739. $final = '';
  10740. for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
  10741. if (ctype_space($parts[$i])) {
  10742. continue;
  10743. }
  10744. $result = $this->single->validate($parts[$i], $config, $context);
  10745. if ($result !== false) {
  10746. $final .= $result . ' ';
  10747. $num++;
  10748. }
  10749. }
  10750. if ($final === '') {
  10751. return false;
  10752. }
  10753. return rtrim($final);
  10754. }
  10755. }
  10756. /**
  10757. * Validates a Percentage as defined by the CSS spec.
  10758. */
  10759. class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
  10760. {
  10761. /**
  10762. * Instance to defer number validation to.
  10763. * @type HTMLPurifier_AttrDef_CSS_Number
  10764. */
  10765. protected $number_def;
  10766. /**
  10767. * @param bool $non_negative Whether to forbid negative values
  10768. */
  10769. public function __construct($non_negative = false)
  10770. {
  10771. $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
  10772. }
  10773. /**
  10774. * @param string $string
  10775. * @param HTMLPurifier_Config $config
  10776. * @param HTMLPurifier_Context $context
  10777. * @return bool|string
  10778. */
  10779. public function validate($string, $config, $context)
  10780. {
  10781. $string = $this->parseCDATA($string);
  10782. if ($string === '') {
  10783. return false;
  10784. }
  10785. $length = strlen($string);
  10786. if ($length === 1) {
  10787. return false;
  10788. }
  10789. if ($string[$length - 1] !== '%') {
  10790. return false;
  10791. }
  10792. $number = substr($string, 0, $length - 1);
  10793. $number = $this->number_def->validate($number, $config, $context);
  10794. if ($number === false) {
  10795. return false;
  10796. }
  10797. return "$number%";
  10798. }
  10799. }
  10800. /**
  10801. * Validates the value for the CSS property text-decoration
  10802. * @note This class could be generalized into a version that acts sort of
  10803. * like Enum except you can compound the allowed values.
  10804. */
  10805. class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
  10806. {
  10807. /**
  10808. * @param string $string
  10809. * @param HTMLPurifier_Config $config
  10810. * @param HTMLPurifier_Context $context
  10811. * @return bool|string
  10812. */
  10813. public function validate($string, $config, $context)
  10814. {
  10815. static $allowed_values = array(
  10816. 'line-through' => true,
  10817. 'overline' => true,
  10818. 'underline' => true,
  10819. );
  10820. $string = strtolower($this->parseCDATA($string));
  10821. if ($string === 'none') {
  10822. return $string;
  10823. }
  10824. $parts = explode(' ', $string);
  10825. $final = '';
  10826. foreach ($parts as $part) {
  10827. if (isset($allowed_values[$part])) {
  10828. $final .= $part . ' ';
  10829. }
  10830. }
  10831. $final = rtrim($final);
  10832. if ($final === '') {
  10833. return false;
  10834. }
  10835. return $final;
  10836. }
  10837. }
  10838. /**
  10839. * Validates a URI in CSS syntax, which uses url('http://example.com')
  10840. * @note While theoretically speaking a URI in a CSS document could
  10841. * be non-embedded, as of CSS2 there is no such usage so we're
  10842. * generalizing it. This may need to be changed in the future.
  10843. * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
  10844. * the separator, you cannot put a literal semicolon in
  10845. * in the URI. Try percent encoding it, in that case.
  10846. */
  10847. class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
  10848. {
  10849. public function __construct()
  10850. {
  10851. parent::__construct(true); // always embedded
  10852. }
  10853. /**
  10854. * @param string $uri_string
  10855. * @param HTMLPurifier_Config $config
  10856. * @param HTMLPurifier_Context $context
  10857. * @return bool|string
  10858. */
  10859. public function validate($uri_string, $config, $context)
  10860. {
  10861. // parse the URI out of the string and then pass it onto
  10862. // the parent object
  10863. $uri_string = $this->parseCDATA($uri_string);
  10864. if (strpos($uri_string, 'url(') !== 0) {
  10865. return false;
  10866. }
  10867. $uri_string = substr($uri_string, 4);
  10868. $new_length = strlen($uri_string) - 1;
  10869. if ($uri_string[$new_length] != ')') {
  10870. return false;
  10871. }
  10872. $uri = trim(substr($uri_string, 0, $new_length));
  10873. if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
  10874. $quote = $uri[0];
  10875. $new_length = strlen($uri) - 1;
  10876. if ($uri[$new_length] !== $quote) {
  10877. return false;
  10878. }
  10879. $uri = substr($uri, 1, $new_length - 1);
  10880. }
  10881. $uri = $this->expandCSSEscape($uri);
  10882. $result = parent::validate($uri, $config, $context);
  10883. if ($result === false) {
  10884. return false;
  10885. }
  10886. // extra sanity check; should have been done by URI
  10887. $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
  10888. // suspicious characters are ()'; we're going to percent encode
  10889. // them for safety.
  10890. $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
  10891. // there's an extra bug where ampersands lose their escaping on
  10892. // an innerHTML cycle, so a very unlucky query parameter could
  10893. // then change the meaning of the URL. Unfortunately, there's
  10894. // not much we can do about that...
  10895. return "url(\"$result\")";
  10896. }
  10897. }
  10898. /**
  10899. * Validates a boolean attribute
  10900. */
  10901. class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
  10902. {
  10903. /**
  10904. * @type bool
  10905. */
  10906. protected $name;
  10907. /**
  10908. * @type bool
  10909. */
  10910. public $minimized = true;
  10911. /**
  10912. * @param bool $name
  10913. */
  10914. public function __construct($name = false)
  10915. {
  10916. $this->name = $name;
  10917. }
  10918. /**
  10919. * @param string $string
  10920. * @param HTMLPurifier_Config $config
  10921. * @param HTMLPurifier_Context $context
  10922. * @return bool|string
  10923. */
  10924. public function validate($string, $config, $context)
  10925. {
  10926. return $this->name;
  10927. }
  10928. /**
  10929. * @param string $string Name of attribute
  10930. * @return HTMLPurifier_AttrDef_HTML_Bool
  10931. */
  10932. public function make($string)
  10933. {
  10934. return new HTMLPurifier_AttrDef_HTML_Bool($string);
  10935. }
  10936. }
  10937. /**
  10938. * Validates contents based on NMTOKENS attribute type.
  10939. */
  10940. class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
  10941. {
  10942. /**
  10943. * @param string $string
  10944. * @param HTMLPurifier_Config $config
  10945. * @param HTMLPurifier_Context $context
  10946. * @return bool|string
  10947. */
  10948. public function validate($string, $config, $context)
  10949. {
  10950. $string = trim($string);
  10951. // early abort: '' and '0' (strings that convert to false) are invalid
  10952. if (!$string) {
  10953. return false;
  10954. }
  10955. $tokens = $this->split($string, $config, $context);
  10956. $tokens = $this->filter($tokens, $config, $context);
  10957. if (empty($tokens)) {
  10958. return false;
  10959. }
  10960. return implode(' ', $tokens);
  10961. }
  10962. /**
  10963. * Splits a space separated list of tokens into its constituent parts.
  10964. * @param string $string
  10965. * @param HTMLPurifier_Config $config
  10966. * @param HTMLPurifier_Context $context
  10967. * @return array
  10968. */
  10969. protected function split($string, $config, $context)
  10970. {
  10971. // OPTIMIZABLE!
  10972. // do the preg_match, capture all subpatterns for reformulation
  10973. // we don't support U+00A1 and up codepoints or
  10974. // escaping because I don't know how to do that with regexps
  10975. // and plus it would complicate optimization efforts (you never
  10976. // see that anyway).
  10977. $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
  10978. '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
  10979. '(?:(?=\s)|\z)/'; // look ahead for space or string end
  10980. preg_match_all($pattern, $string, $matches);
  10981. return $matches[1];
  10982. }
  10983. /**
  10984. * Template method for removing certain tokens based on arbitrary criteria.
  10985. * @note If we wanted to be really functional, we'd do an array_filter
  10986. * with a callback. But... we're not.
  10987. * @param array $tokens
  10988. * @param HTMLPurifier_Config $config
  10989. * @param HTMLPurifier_Context $context
  10990. * @return array
  10991. */
  10992. protected function filter($tokens, $config, $context)
  10993. {
  10994. return $tokens;
  10995. }
  10996. }
  10997. /**
  10998. * Implements special behavior for class attribute (normally NMTOKENS)
  10999. */
  11000. class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
  11001. {
  11002. /**
  11003. * @param string $string
  11004. * @param HTMLPurifier_Config $config
  11005. * @param HTMLPurifier_Context $context
  11006. * @return bool|string
  11007. */
  11008. protected function split($string, $config, $context)
  11009. {
  11010. // really, this twiddle should be lazy loaded
  11011. $name = $config->getDefinition('HTML')->doctype->name;
  11012. if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
  11013. return parent::split($string, $config, $context);
  11014. } else {
  11015. return preg_split('/\s+/', $string);
  11016. }
  11017. }
  11018. /**
  11019. * @param array $tokens
  11020. * @param HTMLPurifier_Config $config
  11021. * @param HTMLPurifier_Context $context
  11022. * @return array
  11023. */
  11024. protected function filter($tokens, $config, $context)
  11025. {
  11026. $allowed = $config->get('Attr.AllowedClasses');
  11027. $forbidden = $config->get('Attr.ForbiddenClasses');
  11028. $ret = array();
  11029. foreach ($tokens as $token) {
  11030. if (($allowed === null || isset($allowed[$token])) &&
  11031. !isset($forbidden[$token]) &&
  11032. // We need this O(n) check because of PHP's array
  11033. // implementation that casts -0 to 0.
  11034. !in_array($token, $ret, true)
  11035. ) {
  11036. $ret[] = $token;
  11037. }
  11038. }
  11039. return $ret;
  11040. }
  11041. }
  11042. /**
  11043. * Validates a color according to the HTML spec.
  11044. */
  11045. class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
  11046. {
  11047. /**
  11048. * @param string $string
  11049. * @param HTMLPurifier_Config $config
  11050. * @param HTMLPurifier_Context $context
  11051. * @return bool|string
  11052. */
  11053. public function validate($string, $config, $context)
  11054. {
  11055. static $colors = null;
  11056. if ($colors === null) {
  11057. $colors = $config->get('Core.ColorKeywords');
  11058. }
  11059. $string = trim($string);
  11060. if (empty($string)) {
  11061. return false;
  11062. }
  11063. $lower = strtolower($string);
  11064. if (isset($colors[$lower])) {
  11065. return $colors[$lower];
  11066. }
  11067. if ($string[0] === '#') {
  11068. $hex = substr($string, 1);
  11069. } else {
  11070. $hex = $string;
  11071. }
  11072. $length = strlen($hex);
  11073. if ($length !== 3 && $length !== 6) {
  11074. return false;
  11075. }
  11076. if (!ctype_xdigit($hex)) {
  11077. return false;
  11078. }
  11079. if ($length === 3) {
  11080. $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
  11081. }
  11082. return "#$hex";
  11083. }
  11084. }
  11085. /**
  11086. * Special-case enum attribute definition that lazy loads allowed frame targets
  11087. */
  11088. class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
  11089. {
  11090. /**
  11091. * @type array
  11092. */
  11093. public $valid_values = false; // uninitialized value
  11094. /**
  11095. * @type bool
  11096. */
  11097. protected $case_sensitive = false;
  11098. public function __construct()
  11099. {
  11100. }
  11101. /**
  11102. * @param string $string
  11103. * @param HTMLPurifier_Config $config
  11104. * @param HTMLPurifier_Context $context
  11105. * @return bool|string
  11106. */
  11107. public function validate($string, $config, $context)
  11108. {
  11109. if ($this->valid_values === false) {
  11110. $this->valid_values = $config->get('Attr.AllowedFrameTargets');
  11111. }
  11112. return parent::validate($string, $config, $context);
  11113. }
  11114. }
  11115. /**
  11116. * Validates the HTML attribute ID.
  11117. * @warning Even though this is the id processor, it
  11118. * will ignore the directive Attr:IDBlacklist, since it will only
  11119. * go according to the ID accumulator. Since the accumulator is
  11120. * automatically generated, it will have already absorbed the
  11121. * blacklist. If you're hacking around, make sure you use load()!
  11122. */
  11123. class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
  11124. {
  11125. // selector is NOT a valid thing to use for IDREFs, because IDREFs
  11126. // *must* target IDs that exist, whereas selector #ids do not.
  11127. /**
  11128. * Determines whether or not we're validating an ID in a CSS
  11129. * selector context.
  11130. * @type bool
  11131. */
  11132. protected $selector;
  11133. /**
  11134. * @param bool $selector
  11135. */
  11136. public function __construct($selector = false)
  11137. {
  11138. $this->selector = $selector;
  11139. }
  11140. /**
  11141. * @param string $id
  11142. * @param HTMLPurifier_Config $config
  11143. * @param HTMLPurifier_Context $context
  11144. * @return bool|string
  11145. */
  11146. public function validate($id, $config, $context)
  11147. {
  11148. if (!$this->selector && !$config->get('Attr.EnableID')) {
  11149. return false;
  11150. }
  11151. $id = trim($id); // trim it first
  11152. if ($id === '') {
  11153. return false;
  11154. }
  11155. $prefix = $config->get('Attr.IDPrefix');
  11156. if ($prefix !== '') {
  11157. $prefix .= $config->get('Attr.IDPrefixLocal');
  11158. // prevent re-appending the prefix
  11159. if (strpos($id, $prefix) !== 0) {
  11160. $id = $prefix . $id;
  11161. }
  11162. } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
  11163. trigger_error(
  11164. '%Attr.IDPrefixLocal cannot be used unless ' .
  11165. '%Attr.IDPrefix is set',
  11166. E_USER_WARNING
  11167. );
  11168. }
  11169. if (!$this->selector) {
  11170. $id_accumulator =& $context->get('IDAccumulator');
  11171. if (isset($id_accumulator->ids[$id])) {
  11172. return false;
  11173. }
  11174. }
  11175. // we purposely avoid using regex, hopefully this is faster
  11176. if (ctype_alpha($id)) {
  11177. $result = true;
  11178. } else {
  11179. if (!ctype_alpha(@$id[0])) {
  11180. return false;
  11181. }
  11182. // primitive style of regexps, I suppose
  11183. $trim = trim(
  11184. $id,
  11185. 'A..Za..z0..9:-._'
  11186. );
  11187. $result = ($trim === '');
  11188. }
  11189. $regexp = $config->get('Attr.IDBlacklistRegexp');
  11190. if ($regexp && preg_match($regexp, $id)) {
  11191. return false;
  11192. }
  11193. if (!$this->selector && $result) {
  11194. $id_accumulator->add($id);
  11195. }
  11196. // if no change was made to the ID, return the result
  11197. // else, return the new id if stripping whitespace made it
  11198. // valid, or return false.
  11199. return $result ? $id : false;
  11200. }
  11201. }
  11202. /**
  11203. * Validates an integer representation of pixels according to the HTML spec.
  11204. */
  11205. class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
  11206. {
  11207. /**
  11208. * @type int
  11209. */
  11210. protected $max;
  11211. /**
  11212. * @param int $max
  11213. */
  11214. public function __construct($max = null)
  11215. {
  11216. $this->max = $max;
  11217. }
  11218. /**
  11219. * @param string $string
  11220. * @param HTMLPurifier_Config $config
  11221. * @param HTMLPurifier_Context $context
  11222. * @return bool|string
  11223. */
  11224. public function validate($string, $config, $context)
  11225. {
  11226. $string = trim($string);
  11227. if ($string === '0') {
  11228. return $string;
  11229. }
  11230. if ($string === '') {
  11231. return false;
  11232. }
  11233. $length = strlen($string);
  11234. if (substr($string, $length - 2) == 'px') {
  11235. $string = substr($string, 0, $length - 2);
  11236. }
  11237. if (!is_numeric($string)) {
  11238. return false;
  11239. }
  11240. $int = (int)$string;
  11241. if ($int < 0) {
  11242. return '0';
  11243. }
  11244. // upper-bound value, extremely high values can
  11245. // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
  11246. // WARNING, above link WILL crash you if you're using Windows
  11247. if ($this->max !== null && $int > $this->max) {
  11248. return (string)$this->max;
  11249. }
  11250. return (string)$int;
  11251. }
  11252. /**
  11253. * @param string $string
  11254. * @return HTMLPurifier_AttrDef
  11255. */
  11256. public function make($string)
  11257. {
  11258. if ($string === '') {
  11259. $max = null;
  11260. } else {
  11261. $max = (int)$string;
  11262. }
  11263. $class = get_class($this);
  11264. return new $class($max);
  11265. }
  11266. }
  11267. /**
  11268. * Validates the HTML type length (not to be confused with CSS's length).
  11269. *
  11270. * This accepts integer pixels or percentages as lengths for certain
  11271. * HTML attributes.
  11272. */
  11273. class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
  11274. {
  11275. /**
  11276. * @param string $string
  11277. * @param HTMLPurifier_Config $config
  11278. * @param HTMLPurifier_Context $context
  11279. * @return bool|string
  11280. */
  11281. public function validate($string, $config, $context)
  11282. {
  11283. $string = trim($string);
  11284. if ($string === '') {
  11285. return false;
  11286. }
  11287. $parent_result = parent::validate($string, $config, $context);
  11288. if ($parent_result !== false) {
  11289. return $parent_result;
  11290. }
  11291. $length = strlen($string);
  11292. $last_char = $string[$length - 1];
  11293. if ($last_char !== '%') {
  11294. return false;
  11295. }
  11296. $points = substr($string, 0, $length - 1);
  11297. if (!is_numeric($points)) {
  11298. return false;
  11299. }
  11300. $points = (int)$points;
  11301. if ($points < 0) {
  11302. return '0%';
  11303. }
  11304. if ($points > 100) {
  11305. return '100%';
  11306. }
  11307. return ((string)$points) . '%';
  11308. }
  11309. }
  11310. /**
  11311. * Validates a rel/rev link attribute against a directive of allowed values
  11312. * @note We cannot use Enum because link types allow multiple
  11313. * values.
  11314. * @note Assumes link types are ASCII text
  11315. */
  11316. class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
  11317. {
  11318. /**
  11319. * Name config attribute to pull.
  11320. * @type string
  11321. */
  11322. protected $name;
  11323. /**
  11324. * @param string $name
  11325. */
  11326. public function __construct($name)
  11327. {
  11328. $configLookup = array(
  11329. 'rel' => 'AllowedRel',
  11330. 'rev' => 'AllowedRev'
  11331. );
  11332. if (!isset($configLookup[$name])) {
  11333. trigger_error(
  11334. 'Unrecognized attribute name for link ' .
  11335. 'relationship.',
  11336. E_USER_ERROR
  11337. );
  11338. return;
  11339. }
  11340. $this->name = $configLookup[$name];
  11341. }
  11342. /**
  11343. * @param string $string
  11344. * @param HTMLPurifier_Config $config
  11345. * @param HTMLPurifier_Context $context
  11346. * @return bool|string
  11347. */
  11348. public function validate($string, $config, $context)
  11349. {
  11350. $allowed = $config->get('Attr.' . $this->name);
  11351. if (empty($allowed)) {
  11352. return false;
  11353. }
  11354. $string = $this->parseCDATA($string);
  11355. $parts = explode(' ', $string);
  11356. // lookup to prevent duplicates
  11357. $ret_lookup = array();
  11358. foreach ($parts as $part) {
  11359. $part = strtolower(trim($part));
  11360. if (!isset($allowed[$part])) {
  11361. continue;
  11362. }
  11363. $ret_lookup[$part] = true;
  11364. }
  11365. if (empty($ret_lookup)) {
  11366. return false;
  11367. }
  11368. $string = implode(' ', array_keys($ret_lookup));
  11369. return $string;
  11370. }
  11371. }
  11372. /**
  11373. * Validates a MultiLength as defined by the HTML spec.
  11374. *
  11375. * A multilength is either a integer (pixel count), a percentage, or
  11376. * a relative number.
  11377. */
  11378. class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
  11379. {
  11380. /**
  11381. * @param string $string
  11382. * @param HTMLPurifier_Config $config
  11383. * @param HTMLPurifier_Context $context
  11384. * @return bool|string
  11385. */
  11386. public function validate($string, $config, $context)
  11387. {
  11388. $string = trim($string);
  11389. if ($string === '') {
  11390. return false;
  11391. }
  11392. $parent_result = parent::validate($string, $config, $context);
  11393. if ($parent_result !== false) {
  11394. return $parent_result;
  11395. }
  11396. $length = strlen($string);
  11397. $last_char = $string[$length - 1];
  11398. if ($last_char !== '*') {
  11399. return false;
  11400. }
  11401. $int = substr($string, 0, $length - 1);
  11402. if ($int == '') {
  11403. return '*';
  11404. }
  11405. if (!is_numeric($int)) {
  11406. return false;
  11407. }
  11408. $int = (int)$int;
  11409. if ($int < 0) {
  11410. return false;
  11411. }
  11412. if ($int == 0) {
  11413. return '0';
  11414. }
  11415. if ($int == 1) {
  11416. return '*';
  11417. }
  11418. return ((string)$int) . '*';
  11419. }
  11420. }
  11421. abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
  11422. {
  11423. /**
  11424. * Unpacks a mailbox into its display-name and address
  11425. * @param string $string
  11426. * @return mixed
  11427. */
  11428. public function unpack($string)
  11429. {
  11430. // needs to be implemented
  11431. }
  11432. }
  11433. // sub-implementations
  11434. /**
  11435. * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
  11436. */
  11437. class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
  11438. {
  11439. /**
  11440. * IPv4 sub-validator.
  11441. * @type HTMLPurifier_AttrDef_URI_IPv4
  11442. */
  11443. protected $ipv4;
  11444. /**
  11445. * IPv6 sub-validator.
  11446. * @type HTMLPurifier_AttrDef_URI_IPv6
  11447. */
  11448. protected $ipv6;
  11449. public function __construct()
  11450. {
  11451. $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
  11452. $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
  11453. }
  11454. /**
  11455. * @param string $string
  11456. * @param HTMLPurifier_Config $config
  11457. * @param HTMLPurifier_Context $context
  11458. * @return bool|string
  11459. */
  11460. public function validate($string, $config, $context)
  11461. {
  11462. $length = strlen($string);
  11463. // empty hostname is OK; it's usually semantically equivalent:
  11464. // the default host as defined by a URI scheme is used:
  11465. //
  11466. // If the URI scheme defines a default for host, then that
  11467. // default applies when the host subcomponent is undefined
  11468. // or when the registered name is empty (zero length).
  11469. if ($string === '') {
  11470. return '';
  11471. }
  11472. if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
  11473. //IPv6
  11474. $ip = substr($string, 1, $length - 2);
  11475. $valid = $this->ipv6->validate($ip, $config, $context);
  11476. if ($valid === false) {
  11477. return false;
  11478. }
  11479. return '[' . $valid . ']';
  11480. }
  11481. // need to do checks on unusual encodings too
  11482. $ipv4 = $this->ipv4->validate($string, $config, $context);
  11483. if ($ipv4 !== false) {
  11484. return $ipv4;
  11485. }
  11486. // A regular domain name.
  11487. // This doesn't match I18N domain names, but we don't have proper IRI support,
  11488. // so force users to insert Punycode.
  11489. // There is not a good sense in which underscores should be
  11490. // allowed, since it's technically not! (And if you go as
  11491. // far to allow everything as specified by the DNS spec...
  11492. // well, that's literally everything, modulo some space limits
  11493. // for the components and the overall name (which, by the way,
  11494. // we are NOT checking!). So we (arbitrarily) decide this:
  11495. // let's allow underscores wherever we would have allowed
  11496. // hyphens, if they are enabled. This is a pretty good match
  11497. // for browser behavior, for example, a large number of browsers
  11498. // cannot handle foo_.example.com, but foo_bar.example.com is
  11499. // fairly well supported.
  11500. $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
  11501. // The productions describing this are:
  11502. $a = '[a-z]'; // alpha
  11503. $an = '[a-z0-9]'; // alphanum
  11504. $and = "[a-z0-9-$underscore]"; // alphanum | "-"
  11505. // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
  11506. $domainlabel = "$an($and*$an)?";
  11507. // toplabel = alpha | alpha *( alphanum | "-" ) alphanum
  11508. $toplabel = "$a($and*$an)?";
  11509. // hostname = *( domainlabel "." ) toplabel [ "." ]
  11510. if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
  11511. return $string;
  11512. }
  11513. // If we have Net_IDNA2 support, we can support IRIs by
  11514. // punycoding them. (This is the most portable thing to do,
  11515. // since otherwise we have to assume browsers support
  11516. if ($config->get('Core.EnableIDNA')) {
  11517. $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
  11518. // we need to encode each period separately
  11519. $parts = explode('.', $string);
  11520. try {
  11521. $new_parts = array();
  11522. foreach ($parts as $part) {
  11523. $encodable = false;
  11524. for ($i = 0, $c = strlen($part); $i < $c; $i++) {
  11525. if (ord($part[$i]) > 0x7a) {
  11526. $encodable = true;
  11527. break;
  11528. }
  11529. }
  11530. if (!$encodable) {
  11531. $new_parts[] = $part;
  11532. } else {
  11533. $new_parts[] = $idna->encode($part);
  11534. }
  11535. }
  11536. $string = implode('.', $new_parts);
  11537. if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
  11538. return $string;
  11539. }
  11540. } catch (Exception $e) {
  11541. // XXX error reporting
  11542. }
  11543. }
  11544. return false;
  11545. }
  11546. }
  11547. /**
  11548. * Validates an IPv4 address
  11549. * @author Feyd @ forums.devnetwork.net (public domain)
  11550. */
  11551. class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
  11552. {
  11553. /**
  11554. * IPv4 regex, protected so that IPv6 can reuse it.
  11555. * @type string
  11556. */
  11557. protected $ip4;
  11558. /**
  11559. * @param string $aIP
  11560. * @param HTMLPurifier_Config $config
  11561. * @param HTMLPurifier_Context $context
  11562. * @return bool|string
  11563. */
  11564. public function validate($aIP, $config, $context)
  11565. {
  11566. if (!$this->ip4) {
  11567. $this->_loadRegex();
  11568. }
  11569. if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
  11570. return $aIP;
  11571. }
  11572. return false;
  11573. }
  11574. /**
  11575. * Lazy load function to prevent regex from being stuffed in
  11576. * cache.
  11577. */
  11578. protected function _loadRegex()
  11579. {
  11580. $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
  11581. $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
  11582. }
  11583. }
  11584. /**
  11585. * Validates an IPv6 address.
  11586. * @author Feyd @ forums.devnetwork.net (public domain)
  11587. * @note This function requires brackets to have been removed from address
  11588. * in URI.
  11589. */
  11590. class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
  11591. {
  11592. /**
  11593. * @param string $aIP
  11594. * @param HTMLPurifier_Config $config
  11595. * @param HTMLPurifier_Context $context
  11596. * @return bool|string
  11597. */
  11598. public function validate($aIP, $config, $context)
  11599. {
  11600. if (!$this->ip4) {
  11601. $this->_loadRegex();
  11602. }
  11603. $original = $aIP;
  11604. $hex = '[0-9a-fA-F]';
  11605. $blk = '(?:' . $hex . '{1,4})';
  11606. $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
  11607. // prefix check
  11608. if (strpos($aIP, '/') !== false) {
  11609. if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
  11610. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11611. unset($find);
  11612. } else {
  11613. return false;
  11614. }
  11615. }
  11616. // IPv4-compatiblity check
  11617. if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
  11618. $aIP = substr($aIP, 0, 0 - strlen($find[0]));
  11619. $ip = explode('.', $find[0]);
  11620. $ip = array_map('dechex', $ip);
  11621. $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
  11622. unset($find, $ip);
  11623. }
  11624. // compression check
  11625. $aIP = explode('::', $aIP);
  11626. $c = count($aIP);
  11627. if ($c > 2) {
  11628. return false;
  11629. } elseif ($c == 2) {
  11630. list($first, $second) = $aIP;
  11631. $first = explode(':', $first);
  11632. $second = explode(':', $second);
  11633. if (count($first) + count($second) > 8) {
  11634. return false;
  11635. }
  11636. while (count($first) < 8) {
  11637. array_push($first, '0');
  11638. }
  11639. array_splice($first, 8 - count($second), 8, $second);
  11640. $aIP = $first;
  11641. unset($first, $second);
  11642. } else {
  11643. $aIP = explode(':', $aIP[0]);
  11644. }
  11645. $c = count($aIP);
  11646. if ($c != 8) {
  11647. return false;
  11648. }
  11649. // All the pieces should be 16-bit hex strings. Are they?
  11650. foreach ($aIP as $piece) {
  11651. if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
  11652. return false;
  11653. }
  11654. }
  11655. return $original;
  11656. }
  11657. }
  11658. /**
  11659. * Primitive email validation class based on the regexp found at
  11660. * http://www.regular-expressions.info/email.html
  11661. */
  11662. class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
  11663. {
  11664. /**
  11665. * @param string $string
  11666. * @param HTMLPurifier_Config $config
  11667. * @param HTMLPurifier_Context $context
  11668. * @return bool|string
  11669. */
  11670. public function validate($string, $config, $context)
  11671. {
  11672. // no support for named mailboxes i.e. "Bob <bob@example.com>"
  11673. // that needs more percent encoding to be done
  11674. if ($string == '') {
  11675. return false;
  11676. }
  11677. $string = trim($string);
  11678. $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
  11679. return $result ? $string : false;
  11680. }
  11681. }
  11682. /**
  11683. * Pre-transform that changes proprietary background attribute to CSS.
  11684. */
  11685. class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
  11686. {
  11687. /**
  11688. * @param array $attr
  11689. * @param HTMLPurifier_Config $config
  11690. * @param HTMLPurifier_Context $context
  11691. * @return array
  11692. */
  11693. public function transform($attr, $config, $context)
  11694. {
  11695. if (!isset($attr['background'])) {
  11696. return $attr;
  11697. }
  11698. $background = $this->confiscateAttr($attr, 'background');
  11699. // some validation should happen here
  11700. $this->prependCSS($attr, "background-image:url($background);");
  11701. return $attr;
  11702. }
  11703. }
  11704. // this MUST be placed in post, as it assumes that any value in dir is valid
  11705. /**
  11706. * Post-trasnform that ensures that bdo tags have the dir attribute set.
  11707. */
  11708. class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
  11709. {
  11710. /**
  11711. * @param array $attr
  11712. * @param HTMLPurifier_Config $config
  11713. * @param HTMLPurifier_Context $context
  11714. * @return array
  11715. */
  11716. public function transform($attr, $config, $context)
  11717. {
  11718. if (isset($attr['dir'])) {
  11719. return $attr;
  11720. }
  11721. $attr['dir'] = $config->get('Attr.DefaultTextDir');
  11722. return $attr;
  11723. }
  11724. }
  11725. /**
  11726. * Pre-transform that changes deprecated bgcolor attribute to CSS.
  11727. */
  11728. class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
  11729. {
  11730. /**
  11731. * @param array $attr
  11732. * @param HTMLPurifier_Config $config
  11733. * @param HTMLPurifier_Context $context
  11734. * @return array
  11735. */
  11736. public function transform($attr, $config, $context)
  11737. {
  11738. if (!isset($attr['bgcolor'])) {
  11739. return $attr;
  11740. }
  11741. $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
  11742. // some validation should happen here
  11743. $this->prependCSS($attr, "background-color:$bgcolor;");
  11744. return $attr;
  11745. }
  11746. }
  11747. /**
  11748. * Pre-transform that changes converts a boolean attribute to fixed CSS
  11749. */
  11750. class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
  11751. {
  11752. /**
  11753. * Name of boolean attribute that is trigger.
  11754. * @type string
  11755. */
  11756. protected $attr;
  11757. /**
  11758. * CSS declarations to add to style, needs trailing semicolon.
  11759. * @type string
  11760. */
  11761. protected $css;
  11762. /**
  11763. * @param string $attr attribute name to convert from
  11764. * @param string $css CSS declarations to add to style (needs semicolon)
  11765. */
  11766. public function __construct($attr, $css)
  11767. {
  11768. $this->attr = $attr;
  11769. $this->css = $css;
  11770. }
  11771. /**
  11772. * @param array $attr
  11773. * @param HTMLPurifier_Config $config
  11774. * @param HTMLPurifier_Context $context
  11775. * @return array
  11776. */
  11777. public function transform($attr, $config, $context)
  11778. {
  11779. if (!isset($attr[$this->attr])) {
  11780. return $attr;
  11781. }
  11782. unset($attr[$this->attr]);
  11783. $this->prependCSS($attr, $this->css);
  11784. return $attr;
  11785. }
  11786. }
  11787. /**
  11788. * Pre-transform that changes deprecated border attribute to CSS.
  11789. */
  11790. class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
  11791. {
  11792. /**
  11793. * @param array $attr
  11794. * @param HTMLPurifier_Config $config
  11795. * @param HTMLPurifier_Context $context
  11796. * @return array
  11797. */
  11798. public function transform($attr, $config, $context)
  11799. {
  11800. if (!isset($attr['border'])) {
  11801. return $attr;
  11802. }
  11803. $border_width = $this->confiscateAttr($attr, 'border');
  11804. // some validation should happen here
  11805. $this->prependCSS($attr, "border:{$border_width}px solid;");
  11806. return $attr;
  11807. }
  11808. }
  11809. /**
  11810. * Generic pre-transform that converts an attribute with a fixed number of
  11811. * values (enumerated) to CSS.
  11812. */
  11813. class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
  11814. {
  11815. /**
  11816. * Name of attribute to transform from.
  11817. * @type string
  11818. */
  11819. protected $attr;
  11820. /**
  11821. * Lookup array of attribute values to CSS.
  11822. * @type array
  11823. */
  11824. protected $enumToCSS = array();
  11825. /**
  11826. * Case sensitivity of the matching.
  11827. * @type bool
  11828. * @warning Currently can only be guaranteed to work with ASCII
  11829. * values.
  11830. */
  11831. protected $caseSensitive = false;
  11832. /**
  11833. * @param string $attr Attribute name to transform from
  11834. * @param array $enum_to_css Lookup array of attribute values to CSS
  11835. * @param bool $case_sensitive Case sensitivity indicator, default false
  11836. */
  11837. public function __construct($attr, $enum_to_css, $case_sensitive = false)
  11838. {
  11839. $this->attr = $attr;
  11840. $this->enumToCSS = $enum_to_css;
  11841. $this->caseSensitive = (bool)$case_sensitive;
  11842. }
  11843. /**
  11844. * @param array $attr
  11845. * @param HTMLPurifier_Config $config
  11846. * @param HTMLPurifier_Context $context
  11847. * @return array
  11848. */
  11849. public function transform($attr, $config, $context)
  11850. {
  11851. if (!isset($attr[$this->attr])) {
  11852. return $attr;
  11853. }
  11854. $value = trim($attr[$this->attr]);
  11855. unset($attr[$this->attr]);
  11856. if (!$this->caseSensitive) {
  11857. $value = strtolower($value);
  11858. }
  11859. if (!isset($this->enumToCSS[$value])) {
  11860. return $attr;
  11861. }
  11862. $this->prependCSS($attr, $this->enumToCSS[$value]);
  11863. return $attr;
  11864. }
  11865. }
  11866. // must be called POST validation
  11867. /**
  11868. * Transform that supplies default values for the src and alt attributes
  11869. * in img tags, as well as prevents the img tag from being removed
  11870. * because of a missing alt tag. This needs to be registered as both
  11871. * a pre and post attribute transform.
  11872. */
  11873. class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
  11874. {
  11875. /**
  11876. * @param array $attr
  11877. * @param HTMLPurifier_Config $config
  11878. * @param HTMLPurifier_Context $context
  11879. * @return array
  11880. */
  11881. public function transform($attr, $config, $context)
  11882. {
  11883. $src = true;
  11884. if (!isset($attr['src'])) {
  11885. if ($config->get('Core.RemoveInvalidImg')) {
  11886. return $attr;
  11887. }
  11888. $attr['src'] = $config->get('Attr.DefaultInvalidImage');
  11889. $src = false;
  11890. }
  11891. if (!isset($attr['alt'])) {
  11892. if ($src) {
  11893. $alt = $config->get('Attr.DefaultImageAlt');
  11894. if ($alt === null) {
  11895. // truncate if the alt is too long
  11896. $attr['alt'] = substr(basename($attr['src']), 0, 40);
  11897. } else {
  11898. $attr['alt'] = $alt;
  11899. }
  11900. } else {
  11901. $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
  11902. }
  11903. }
  11904. return $attr;
  11905. }
  11906. }
  11907. /**
  11908. * Pre-transform that changes deprecated hspace and vspace attributes to CSS
  11909. */
  11910. class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
  11911. {
  11912. /**
  11913. * @type string
  11914. */
  11915. protected $attr;
  11916. /**
  11917. * @type array
  11918. */
  11919. protected $css = array(
  11920. 'hspace' => array('left', 'right'),
  11921. 'vspace' => array('top', 'bottom')
  11922. );
  11923. /**
  11924. * @param string $attr
  11925. */
  11926. public function __construct($attr)
  11927. {
  11928. $this->attr = $attr;
  11929. if (!isset($this->css[$attr])) {
  11930. trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
  11931. }
  11932. }
  11933. /**
  11934. * @param array $attr
  11935. * @param HTMLPurifier_Config $config
  11936. * @param HTMLPurifier_Context $context
  11937. * @return array
  11938. */
  11939. public function transform($attr, $config, $context)
  11940. {
  11941. if (!isset($attr[$this->attr])) {
  11942. return $attr;
  11943. }
  11944. $width = $this->confiscateAttr($attr, $this->attr);
  11945. // some validation could happen here
  11946. if (!isset($this->css[$this->attr])) {
  11947. return $attr;
  11948. }
  11949. $style = '';
  11950. foreach ($this->css[$this->attr] as $suffix) {
  11951. $property = "margin-$suffix";
  11952. $style .= "$property:{$width}px;";
  11953. }
  11954. $this->prependCSS($attr, $style);
  11955. return $attr;
  11956. }
  11957. }
  11958. /**
  11959. * Performs miscellaneous cross attribute validation and filtering for
  11960. * input elements. This is meant to be a post-transform.
  11961. */
  11962. class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
  11963. {
  11964. /**
  11965. * @type HTMLPurifier_AttrDef_HTML_Pixels
  11966. */
  11967. protected $pixels;
  11968. public function __construct()
  11969. {
  11970. $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
  11971. }
  11972. /**
  11973. * @param array $attr
  11974. * @param HTMLPurifier_Config $config
  11975. * @param HTMLPurifier_Context $context
  11976. * @return array
  11977. */
  11978. public function transform($attr, $config, $context)
  11979. {
  11980. if (!isset($attr['type'])) {
  11981. $t = 'text';
  11982. } else {
  11983. $t = strtolower($attr['type']);
  11984. }
  11985. if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
  11986. unset($attr['checked']);
  11987. }
  11988. if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
  11989. unset($attr['maxlength']);
  11990. }
  11991. if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
  11992. $result = $this->pixels->validate($attr['size'], $config, $context);
  11993. if ($result === false) {
  11994. unset($attr['size']);
  11995. } else {
  11996. $attr['size'] = $result;
  11997. }
  11998. }
  11999. if (isset($attr['src']) && $t !== 'image') {
  12000. unset($attr['src']);
  12001. }
  12002. if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
  12003. $attr['value'] = '';
  12004. }
  12005. return $attr;
  12006. }
  12007. }
  12008. /**
  12009. * Post-transform that copies lang's value to xml:lang (and vice-versa)
  12010. * @note Theoretically speaking, this could be a pre-transform, but putting
  12011. * post is more efficient.
  12012. */
  12013. class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
  12014. {
  12015. /**
  12016. * @param array $attr
  12017. * @param HTMLPurifier_Config $config
  12018. * @param HTMLPurifier_Context $context
  12019. * @return array
  12020. */
  12021. public function transform($attr, $config, $context)
  12022. {
  12023. $lang = isset($attr['lang']) ? $attr['lang'] : false;
  12024. $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
  12025. if ($lang !== false && $xml_lang === false) {
  12026. $attr['xml:lang'] = $lang;
  12027. } elseif ($xml_lang !== false) {
  12028. $attr['lang'] = $xml_lang;
  12029. }
  12030. return $attr;
  12031. }
  12032. }
  12033. /**
  12034. * Class for handling width/height length attribute transformations to CSS
  12035. */
  12036. class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
  12037. {
  12038. /**
  12039. * @type string
  12040. */
  12041. protected $name;
  12042. /**
  12043. * @type string
  12044. */
  12045. protected $cssName;
  12046. public function __construct($name, $css_name = null)
  12047. {
  12048. $this->name = $name;
  12049. $this->cssName = $css_name ? $css_name : $name;
  12050. }
  12051. /**
  12052. * @param array $attr
  12053. * @param HTMLPurifier_Config $config
  12054. * @param HTMLPurifier_Context $context
  12055. * @return array
  12056. */
  12057. public function transform($attr, $config, $context)
  12058. {
  12059. if (!isset($attr[$this->name])) {
  12060. return $attr;
  12061. }
  12062. $length = $this->confiscateAttr($attr, $this->name);
  12063. if (ctype_digit($length)) {
  12064. $length .= 'px';
  12065. }
  12066. $this->prependCSS($attr, $this->cssName . ":$length;");
  12067. return $attr;
  12068. }
  12069. }
  12070. /**
  12071. * Pre-transform that changes deprecated name attribute to ID if necessary
  12072. */
  12073. class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
  12074. {
  12075. /**
  12076. * @param array $attr
  12077. * @param HTMLPurifier_Config $config
  12078. * @param HTMLPurifier_Context $context
  12079. * @return array
  12080. */
  12081. public function transform($attr, $config, $context)
  12082. {
  12083. // Abort early if we're using relaxed definition of name
  12084. if ($config->get('HTML.Attr.Name.UseCDATA')) {
  12085. return $attr;
  12086. }
  12087. if (!isset($attr['name'])) {
  12088. return $attr;
  12089. }
  12090. $id = $this->confiscateAttr($attr, 'name');
  12091. if (isset($attr['id'])) {
  12092. return $attr;
  12093. }
  12094. $attr['id'] = $id;
  12095. return $attr;
  12096. }
  12097. }
  12098. /**
  12099. * Post-transform that performs validation to the name attribute; if
  12100. * it is present with an equivalent id attribute, it is passed through;
  12101. * otherwise validation is performed.
  12102. */
  12103. class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
  12104. {
  12105. public function __construct()
  12106. {
  12107. $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
  12108. }
  12109. /**
  12110. * @param array $attr
  12111. * @param HTMLPurifier_Config $config
  12112. * @param HTMLPurifier_Context $context
  12113. * @return array
  12114. */
  12115. public function transform($attr, $config, $context)
  12116. {
  12117. if (!isset($attr['name'])) {
  12118. return $attr;
  12119. }
  12120. $name = $attr['name'];
  12121. if (isset($attr['id']) && $attr['id'] === $name) {
  12122. return $attr;
  12123. }
  12124. $result = $this->idDef->validate($name, $config, $context);
  12125. if ($result === false) {
  12126. unset($attr['name']);
  12127. } else {
  12128. $attr['name'] = $result;
  12129. }
  12130. return $attr;
  12131. }
  12132. }
  12133. // must be called POST validation
  12134. /**
  12135. * Adds rel="nofollow" to all outbound links. This transform is
  12136. * only attached if Attr.Nofollow is TRUE.
  12137. */
  12138. class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
  12139. {
  12140. /**
  12141. * @type HTMLPurifier_URIParser
  12142. */
  12143. private $parser;
  12144. public function __construct()
  12145. {
  12146. $this->parser = new HTMLPurifier_URIParser();
  12147. }
  12148. /**
  12149. * @param array $attr
  12150. * @param HTMLPurifier_Config $config
  12151. * @param HTMLPurifier_Context $context
  12152. * @return array
  12153. */
  12154. public function transform($attr, $config, $context)
  12155. {
  12156. if (!isset($attr['href'])) {
  12157. return $attr;
  12158. }
  12159. // XXX Kind of inefficient
  12160. $url = $this->parser->parse($attr['href']);
  12161. $scheme = $url->getSchemeObj($config, $context);
  12162. if ($scheme->browsable && !$url->isLocal($config, $context)) {
  12163. if (isset($attr['rel'])) {
  12164. $rels = explode(' ', $attr['rel']);
  12165. if (!in_array('nofollow', $rels)) {
  12166. $rels[] = 'nofollow';
  12167. }
  12168. $attr['rel'] = implode(' ', $rels);
  12169. } else {
  12170. $attr['rel'] = 'nofollow';
  12171. }
  12172. }
  12173. return $attr;
  12174. }
  12175. }
  12176. class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
  12177. {
  12178. /**
  12179. * @type string
  12180. */
  12181. public $name = "SafeEmbed";
  12182. /**
  12183. * @param array $attr
  12184. * @param HTMLPurifier_Config $config
  12185. * @param HTMLPurifier_Context $context
  12186. * @return array
  12187. */
  12188. public function transform($attr, $config, $context)
  12189. {
  12190. $attr['allowscriptaccess'] = 'never';
  12191. $attr['allownetworking'] = 'internal';
  12192. $attr['type'] = 'application/x-shockwave-flash';
  12193. return $attr;
  12194. }
  12195. }
  12196. /**
  12197. * Writes default type for all objects. Currently only supports flash.
  12198. */
  12199. class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
  12200. {
  12201. /**
  12202. * @type string
  12203. */
  12204. public $name = "SafeObject";
  12205. /**
  12206. * @param array $attr
  12207. * @param HTMLPurifier_Config $config
  12208. * @param HTMLPurifier_Context $context
  12209. * @return array
  12210. */
  12211. public function transform($attr, $config, $context)
  12212. {
  12213. if (!isset($attr['type'])) {
  12214. $attr['type'] = 'application/x-shockwave-flash';
  12215. }
  12216. return $attr;
  12217. }
  12218. }
  12219. /**
  12220. * Validates name/value pairs in param tags to be used in safe objects. This
  12221. * will only allow name values it recognizes, and pre-fill certain attributes
  12222. * with required values.
  12223. *
  12224. * @note
  12225. * This class only supports Flash. In the future, Quicktime support
  12226. * may be added.
  12227. *
  12228. * @warning
  12229. * This class expects an injector to add the necessary parameters tags.
  12230. */
  12231. class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
  12232. {
  12233. /**
  12234. * @type string
  12235. */
  12236. public $name = "SafeParam";
  12237. /**
  12238. * @type HTMLPurifier_AttrDef_URI
  12239. */
  12240. private $uri;
  12241. public function __construct()
  12242. {
  12243. $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
  12244. $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
  12245. }
  12246. /**
  12247. * @param array $attr
  12248. * @param HTMLPurifier_Config $config
  12249. * @param HTMLPurifier_Context $context
  12250. * @return array
  12251. */
  12252. public function transform($attr, $config, $context)
  12253. {
  12254. // If we add support for other objects, we'll need to alter the
  12255. // transforms.
  12256. switch ($attr['name']) {
  12257. // application/x-shockwave-flash
  12258. // Keep this synchronized with Injector/SafeObject.php
  12259. case 'allowScriptAccess':
  12260. $attr['value'] = 'never';
  12261. break;
  12262. case 'allowNetworking':
  12263. $attr['value'] = 'internal';
  12264. break;
  12265. case 'allowFullScreen':
  12266. if ($config->get('HTML.FlashAllowFullScreen')) {
  12267. $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
  12268. } else {
  12269. $attr['value'] = 'false';
  12270. }
  12271. break;
  12272. case 'wmode':
  12273. $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
  12274. break;
  12275. case 'movie':
  12276. case 'src':
  12277. $attr['name'] = "movie";
  12278. $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
  12279. break;
  12280. case 'flashvars':
  12281. // we're going to allow arbitrary inputs to the SWF, on
  12282. // the reasoning that it could only hack the SWF, not us.
  12283. break;
  12284. // add other cases to support other param name/value pairs
  12285. default:
  12286. $attr['name'] = $attr['value'] = null;
  12287. }
  12288. return $attr;
  12289. }
  12290. }
  12291. /**
  12292. * Implements required attribute stipulation for <script>
  12293. */
  12294. class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
  12295. {
  12296. /**
  12297. * @param array $attr
  12298. * @param HTMLPurifier_Config $config
  12299. * @param HTMLPurifier_Context $context
  12300. * @return array
  12301. */
  12302. public function transform($attr, $config, $context)
  12303. {
  12304. if (!isset($attr['type'])) {
  12305. $attr['type'] = 'text/javascript';
  12306. }
  12307. return $attr;
  12308. }
  12309. }
  12310. // must be called POST validation
  12311. /**
  12312. * Adds target="blank" to all outbound links. This transform is
  12313. * only attached if Attr.TargetBlank is TRUE. This works regardless
  12314. * of whether or not Attr.AllowedFrameTargets
  12315. */
  12316. class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
  12317. {
  12318. /**
  12319. * @type HTMLPurifier_URIParser
  12320. */
  12321. private $parser;
  12322. public function __construct()
  12323. {
  12324. $this->parser = new HTMLPurifier_URIParser();
  12325. }
  12326. /**
  12327. * @param array $attr
  12328. * @param HTMLPurifier_Config $config
  12329. * @param HTMLPurifier_Context $context
  12330. * @return array
  12331. */
  12332. public function transform($attr, $config, $context)
  12333. {
  12334. if (!isset($attr['href'])) {
  12335. return $attr;
  12336. }
  12337. // XXX Kind of inefficient
  12338. $url = $this->parser->parse($attr['href']);
  12339. $scheme = $url->getSchemeObj($config, $context);
  12340. if ($scheme->browsable && !$url->isBenign($config, $context)) {
  12341. $attr['target'] = '_blank';
  12342. }
  12343. return $attr;
  12344. }
  12345. }
  12346. /**
  12347. * Sets height/width defaults for <textarea>
  12348. */
  12349. class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
  12350. {
  12351. /**
  12352. * @param array $attr
  12353. * @param HTMLPurifier_Config $config
  12354. * @param HTMLPurifier_Context $context
  12355. * @return array
  12356. */
  12357. public function transform($attr, $config, $context)
  12358. {
  12359. // Calculated from Firefox
  12360. if (!isset($attr['cols'])) {
  12361. $attr['cols'] = '22';
  12362. }
  12363. if (!isset($attr['rows'])) {
  12364. $attr['rows'] = '3';
  12365. }
  12366. return $attr;
  12367. }
  12368. }
  12369. /**
  12370. * Definition that uses different definitions depending on context.
  12371. *
  12372. * The del and ins tags are notable because they allow different types of
  12373. * elements depending on whether or not they're in a block or inline context.
  12374. * Chameleon allows this behavior to happen by using two different
  12375. * definitions depending on context. While this somewhat generalized,
  12376. * it is specifically intended for those two tags.
  12377. */
  12378. class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
  12379. {
  12380. /**
  12381. * Instance of the definition object to use when inline. Usually stricter.
  12382. * @type HTMLPurifier_ChildDef_Optional
  12383. */
  12384. public $inline;
  12385. /**
  12386. * Instance of the definition object to use when block.
  12387. * @type HTMLPurifier_ChildDef_Optional
  12388. */
  12389. public $block;
  12390. /**
  12391. * @type string
  12392. */
  12393. public $type = 'chameleon';
  12394. /**
  12395. * @param array $inline List of elements to allow when inline.
  12396. * @param array $block List of elements to allow when block.
  12397. */
  12398. public function __construct($inline, $block)
  12399. {
  12400. $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
  12401. $this->block = new HTMLPurifier_ChildDef_Optional($block);
  12402. $this->elements = $this->block->elements;
  12403. }
  12404. /**
  12405. * @param HTMLPurifier_Node[] $children
  12406. * @param HTMLPurifier_Config $config
  12407. * @param HTMLPurifier_Context $context
  12408. * @return bool
  12409. */
  12410. public function validateChildren($children, $config, $context)
  12411. {
  12412. if ($context->get('IsInline') === false) {
  12413. return $this->block->validateChildren(
  12414. $children,
  12415. $config,
  12416. $context
  12417. );
  12418. } else {
  12419. return $this->inline->validateChildren(
  12420. $children,
  12421. $config,
  12422. $context
  12423. );
  12424. }
  12425. }
  12426. }
  12427. /**
  12428. * Custom validation class, accepts DTD child definitions
  12429. *
  12430. * @warning Currently this class is an all or nothing proposition, that is,
  12431. * it will only give a bool return value.
  12432. */
  12433. class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
  12434. {
  12435. /**
  12436. * @type string
  12437. */
  12438. public $type = 'custom';
  12439. /**
  12440. * @type bool
  12441. */
  12442. public $allow_empty = false;
  12443. /**
  12444. * Allowed child pattern as defined by the DTD.
  12445. * @type string
  12446. */
  12447. public $dtd_regex;
  12448. /**
  12449. * PCRE regex derived from $dtd_regex.
  12450. * @type string
  12451. */
  12452. private $_pcre_regex;
  12453. /**
  12454. * @param $dtd_regex Allowed child pattern from the DTD
  12455. */
  12456. public function __construct($dtd_regex)
  12457. {
  12458. $this->dtd_regex = $dtd_regex;
  12459. $this->_compileRegex();
  12460. }
  12461. /**
  12462. * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
  12463. */
  12464. protected function _compileRegex()
  12465. {
  12466. $raw = str_replace(' ', '', $this->dtd_regex);
  12467. if ($raw{0} != '(') {
  12468. $raw = "($raw)";
  12469. }
  12470. $el = '[#a-zA-Z0-9_.-]+';
  12471. $reg = $raw;
  12472. // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
  12473. // DOING! Seriously: if there's problems, please report them.
  12474. // collect all elements into the $elements array
  12475. preg_match_all("/$el/", $reg, $matches);
  12476. foreach ($matches[0] as $match) {
  12477. $this->elements[$match] = true;
  12478. }
  12479. // setup all elements as parentheticals with leading commas
  12480. $reg = preg_replace("/$el/", '(,\\0)', $reg);
  12481. // remove commas when they were not solicited
  12482. $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
  12483. // remove all non-paranthetical commas: they are handled by first regex
  12484. $reg = preg_replace("/,\(/", '(', $reg);
  12485. $this->_pcre_regex = $reg;
  12486. }
  12487. /**
  12488. * @param HTMLPurifier_Node[] $children
  12489. * @param HTMLPurifier_Config $config
  12490. * @param HTMLPurifier_Context $context
  12491. * @return bool
  12492. */
  12493. public function validateChildren($children, $config, $context)
  12494. {
  12495. $list_of_children = '';
  12496. $nesting = 0; // depth into the nest
  12497. foreach ($children as $node) {
  12498. if (!empty($node->is_whitespace)) {
  12499. continue;
  12500. }
  12501. $list_of_children .= $node->name . ',';
  12502. }
  12503. // add leading comma to deal with stray comma declarations
  12504. $list_of_children = ',' . rtrim($list_of_children, ',');
  12505. $okay =
  12506. preg_match(
  12507. '/^,?' . $this->_pcre_regex . '$/',
  12508. $list_of_children
  12509. );
  12510. return (bool)$okay;
  12511. }
  12512. }
  12513. /**
  12514. * Definition that disallows all elements.
  12515. * @warning validateChildren() in this class is actually never called, because
  12516. * empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
  12517. * before child definitions are parsed in earnest by
  12518. * HTMLPurifier_Strategy_FixNesting.
  12519. */
  12520. class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
  12521. {
  12522. /**
  12523. * @type bool
  12524. */
  12525. public $allow_empty = true;
  12526. /**
  12527. * @type string
  12528. */
  12529. public $type = 'empty';
  12530. public function __construct()
  12531. {
  12532. }
  12533. /**
  12534. * @param HTMLPurifier_Node[] $children
  12535. * @param HTMLPurifier_Config $config
  12536. * @param HTMLPurifier_Context $context
  12537. * @return array
  12538. */
  12539. public function validateChildren($children, $config, $context)
  12540. {
  12541. return array();
  12542. }
  12543. }
  12544. /**
  12545. * Definition for list containers ul and ol.
  12546. *
  12547. * What does this do? The big thing is to handle ol/ul at the top
  12548. * level of list nodes, which should be handled specially by /folding/
  12549. * them into the previous list node. We generally shouldn't ever
  12550. * see other disallowed elements, because the autoclose behavior
  12551. * in MakeWellFormed handles it.
  12552. */
  12553. class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
  12554. {
  12555. /**
  12556. * @type string
  12557. */
  12558. public $type = 'list';
  12559. /**
  12560. * @type array
  12561. */
  12562. // lying a little bit, so that we can handle ul and ol ourselves
  12563. // XXX: This whole business with 'wrap' is all a bit unsatisfactory
  12564. public $elements = array('li' => true, 'ul' => true, 'ol' => true);
  12565. /**
  12566. * @param array $children
  12567. * @param HTMLPurifier_Config $config
  12568. * @param HTMLPurifier_Context $context
  12569. * @return array
  12570. */
  12571. public function validateChildren($children, $config, $context)
  12572. {
  12573. // Flag for subclasses
  12574. $this->whitespace = false;
  12575. // if there are no tokens, delete parent node
  12576. if (empty($children)) {
  12577. return false;
  12578. }
  12579. // the new set of children
  12580. $result = array();
  12581. // a little sanity check to make sure it's not ALL whitespace
  12582. $all_whitespace = true;
  12583. $current_li = false;
  12584. foreach ($children as $node) {
  12585. if (!empty($node->is_whitespace)) {
  12586. $result[] = $node;
  12587. continue;
  12588. }
  12589. $all_whitespace = false; // phew, we're not talking about whitespace
  12590. if ($node->name === 'li') {
  12591. // good
  12592. $current_li = $node;
  12593. $result[] = $node;
  12594. } else {
  12595. // we want to tuck this into the previous li
  12596. // Invariant: we expect the node to be ol/ul
  12597. // ToDo: Make this more robust in the case of not ol/ul
  12598. // by distinguishing between existing li and li created
  12599. // to handle non-list elements; non-list elements should
  12600. // not be appended to an existing li; only li created
  12601. // for non-list. This distinction is not currently made.
  12602. if ($current_li === false) {
  12603. $current_li = new HTMLPurifier_Node_Element('li');
  12604. $result[] = $current_li;
  12605. }
  12606. $current_li->children[] = $node;
  12607. $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
  12608. }
  12609. }
  12610. if (empty($result)) {
  12611. return false;
  12612. }
  12613. if ($all_whitespace) {
  12614. return false;
  12615. }
  12616. return $result;
  12617. }
  12618. }
  12619. /**
  12620. * Definition that allows a set of elements, but disallows empty children.
  12621. */
  12622. class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
  12623. {
  12624. /**
  12625. * Lookup table of allowed elements.
  12626. * @type array
  12627. */
  12628. public $elements = array();
  12629. /**
  12630. * Whether or not the last passed node was all whitespace.
  12631. * @type bool
  12632. */
  12633. protected $whitespace = false;
  12634. /**
  12635. * @param array|string $elements List of allowed element names (lowercase).
  12636. */
  12637. public function __construct($elements)
  12638. {
  12639. if (is_string($elements)) {
  12640. $elements = str_replace(' ', '', $elements);
  12641. $elements = explode('|', $elements);
  12642. }
  12643. $keys = array_keys($elements);
  12644. if ($keys == array_keys($keys)) {
  12645. $elements = array_flip($elements);
  12646. foreach ($elements as $i => $x) {
  12647. $elements[$i] = true;
  12648. if (empty($i)) {
  12649. unset($elements[$i]);
  12650. } // remove blank
  12651. }
  12652. }
  12653. $this->elements = $elements;
  12654. }
  12655. /**
  12656. * @type bool
  12657. */
  12658. public $allow_empty = false;
  12659. /**
  12660. * @type string
  12661. */
  12662. public $type = 'required';
  12663. /**
  12664. * @param array $children
  12665. * @param HTMLPurifier_Config $config
  12666. * @param HTMLPurifier_Context $context
  12667. * @return array
  12668. */
  12669. public function validateChildren($children, $config, $context)
  12670. {
  12671. // Flag for subclasses
  12672. $this->whitespace = false;
  12673. // if there are no tokens, delete parent node
  12674. if (empty($children)) {
  12675. return false;
  12676. }
  12677. // the new set of children
  12678. $result = array();
  12679. // whether or not parsed character data is allowed
  12680. // this controls whether or not we silently drop a tag
  12681. // or generate escaped HTML from it
  12682. $pcdata_allowed = isset($this->elements['#PCDATA']);
  12683. // a little sanity check to make sure it's not ALL whitespace
  12684. $all_whitespace = true;
  12685. $stack = array_reverse($children);
  12686. while (!empty($stack)) {
  12687. $node = array_pop($stack);
  12688. if (!empty($node->is_whitespace)) {
  12689. $result[] = $node;
  12690. continue;
  12691. }
  12692. $all_whitespace = false; // phew, we're not talking about whitespace
  12693. if (!isset($this->elements[$node->name])) {
  12694. // special case text
  12695. // XXX One of these ought to be redundant or something
  12696. if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
  12697. $result[] = $node;
  12698. continue;
  12699. }
  12700. // spill the child contents in
  12701. // ToDo: Make configurable
  12702. if ($node instanceof HTMLPurifier_Node_Element) {
  12703. for ($i = count($node->children) - 1; $i >= 0; $i--) {
  12704. $stack[] = $node->children[$i];
  12705. }
  12706. continue;
  12707. }
  12708. continue;
  12709. }
  12710. $result[] = $node;
  12711. }
  12712. if (empty($result)) {
  12713. return false;
  12714. }
  12715. if ($all_whitespace) {
  12716. $this->whitespace = true;
  12717. return false;
  12718. }
  12719. return $result;
  12720. }
  12721. }
  12722. /**
  12723. * Definition that allows a set of elements, and allows no children.
  12724. * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
  12725. * really, one shouldn't inherit from the other. Only altered behavior
  12726. * is to overload a returned false with an array. Thus, it will never
  12727. * return false.
  12728. */
  12729. class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
  12730. {
  12731. /**
  12732. * @type bool
  12733. */
  12734. public $allow_empty = true;
  12735. /**
  12736. * @type string
  12737. */
  12738. public $type = 'optional';
  12739. /**
  12740. * @param array $children
  12741. * @param HTMLPurifier_Config $config
  12742. * @param HTMLPurifier_Context $context
  12743. * @return array
  12744. */
  12745. public function validateChildren($children, $config, $context)
  12746. {
  12747. $result = parent::validateChildren($children, $config, $context);
  12748. // we assume that $children is not modified
  12749. if ($result === false) {
  12750. if (empty($children)) {
  12751. return true;
  12752. } elseif ($this->whitespace) {
  12753. return $children;
  12754. } else {
  12755. return array();
  12756. }
  12757. }
  12758. return $result;
  12759. }
  12760. }
  12761. /**
  12762. * Takes the contents of blockquote when in strict and reformats for validation.
  12763. */
  12764. class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
  12765. {
  12766. /**
  12767. * @type array
  12768. */
  12769. protected $real_elements;
  12770. /**
  12771. * @type array
  12772. */
  12773. protected $fake_elements;
  12774. /**
  12775. * @type bool
  12776. */
  12777. public $allow_empty = true;
  12778. /**
  12779. * @type string
  12780. */
  12781. public $type = 'strictblockquote';
  12782. /**
  12783. * @type bool
  12784. */
  12785. protected $init = false;
  12786. /**
  12787. * @param HTMLPurifier_Config $config
  12788. * @return array
  12789. * @note We don't want MakeWellFormed to auto-close inline elements since
  12790. * they might be allowed.
  12791. */
  12792. public function getAllowedElements($config)
  12793. {
  12794. $this->init($config);
  12795. return $this->fake_elements;
  12796. }
  12797. /**
  12798. * @param array $children
  12799. * @param HTMLPurifier_Config $config
  12800. * @param HTMLPurifier_Context $context
  12801. * @return array
  12802. */
  12803. public function validateChildren($children, $config, $context)
  12804. {
  12805. $this->init($config);
  12806. // trick the parent class into thinking it allows more
  12807. $this->elements = $this->fake_elements;
  12808. $result = parent::validateChildren($children, $config, $context);
  12809. $this->elements = $this->real_elements;
  12810. if ($result === false) {
  12811. return array();
  12812. }
  12813. if ($result === true) {
  12814. $result = $children;
  12815. }
  12816. $def = $config->getHTMLDefinition();
  12817. $block_wrap_name = $def->info_block_wrapper;
  12818. $block_wrap = false;
  12819. $ret = array();
  12820. foreach ($result as $node) {
  12821. if ($block_wrap === false) {
  12822. if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
  12823. ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
  12824. $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
  12825. $ret[] = $block_wrap;
  12826. }
  12827. } else {
  12828. if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
  12829. $block_wrap = false;
  12830. }
  12831. }
  12832. if ($block_wrap) {
  12833. $block_wrap->children[] = $node;
  12834. } else {
  12835. $ret[] = $node;
  12836. }
  12837. }
  12838. return $ret;
  12839. }
  12840. /**
  12841. * @param HTMLPurifier_Config $config
  12842. */
  12843. private function init($config)
  12844. {
  12845. if (!$this->init) {
  12846. $def = $config->getHTMLDefinition();
  12847. // allow all inline elements
  12848. $this->real_elements = $this->elements;
  12849. $this->fake_elements = $def->info_content_sets['Flow'];
  12850. $this->fake_elements['#PCDATA'] = true;
  12851. $this->init = true;
  12852. }
  12853. }
  12854. }
  12855. /**
  12856. * Definition for tables. The general idea is to extract out all of the
  12857. * essential bits, and then reconstruct it later.
  12858. *
  12859. * This is a bit confusing, because the DTDs and the W3C
  12860. * validators seem to disagree on the appropriate definition. The
  12861. * DTD claims:
  12862. *
  12863. * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
  12864. *
  12865. * But actually, the HTML4 spec then has this to say:
  12866. *
  12867. * The TBODY start tag is always required except when the table
  12868. * contains only one table body and no table head or foot sections.
  12869. * The TBODY end tag may always be safely omitted.
  12870. *
  12871. * So the DTD is kind of wrong. The validator is, unfortunately, kind
  12872. * of on crack.
  12873. *
  12874. * The definition changed again in XHTML1.1; and in my opinion, this
  12875. * formulation makes the most sense.
  12876. *
  12877. * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
  12878. *
  12879. * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
  12880. * If we encounter a thead, tfoot or tbody, we are placed in the former
  12881. * mode, and we *must* wrap any stray tr segments with a tbody. But if
  12882. * we don't run into any of them, just have tr tags is OK.
  12883. */
  12884. class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
  12885. {
  12886. /**
  12887. * @type bool
  12888. */
  12889. public $allow_empty = false;
  12890. /**
  12891. * @type string
  12892. */
  12893. public $type = 'table';
  12894. /**
  12895. * @type array
  12896. */
  12897. public $elements = array(
  12898. 'tr' => true,
  12899. 'tbody' => true,
  12900. 'thead' => true,
  12901. 'tfoot' => true,
  12902. 'caption' => true,
  12903. 'colgroup' => true,
  12904. 'col' => true
  12905. );
  12906. public function __construct()
  12907. {
  12908. }
  12909. /**
  12910. * @param array $children
  12911. * @param HTMLPurifier_Config $config
  12912. * @param HTMLPurifier_Context $context
  12913. * @return array
  12914. */
  12915. public function validateChildren($children, $config, $context)
  12916. {
  12917. if (empty($children)) {
  12918. return false;
  12919. }
  12920. // only one of these elements is allowed in a table
  12921. $caption = false;
  12922. $thead = false;
  12923. $tfoot = false;
  12924. // whitespace
  12925. $initial_ws = array();
  12926. $after_caption_ws = array();
  12927. $after_thead_ws = array();
  12928. $after_tfoot_ws = array();
  12929. // as many of these as you want
  12930. $cols = array();
  12931. $content = array();
  12932. $tbody_mode = false; // if true, then we need to wrap any stray
  12933. // <tr>s with a <tbody>.
  12934. $ws_accum =& $initial_ws;
  12935. foreach ($children as $node) {
  12936. if ($node instanceof HTMLPurifier_Node_Comment) {
  12937. $ws_accum[] = $node;
  12938. continue;
  12939. }
  12940. switch ($node->name) {
  12941. case 'tbody':
  12942. $tbody_mode = true;
  12943. // fall through
  12944. case 'tr':
  12945. $content[] = $node;
  12946. $ws_accum =& $content;
  12947. break;
  12948. case 'caption':
  12949. // there can only be one caption!
  12950. if ($caption !== false) break;
  12951. $caption = $node;
  12952. $ws_accum =& $after_caption_ws;
  12953. break;
  12954. case 'thead':
  12955. $tbody_mode = true;
  12956. // XXX This breaks rendering properties with
  12957. // Firefox, which never floats a <thead> to
  12958. // the top. Ever. (Our scheme will float the
  12959. // first <thead> to the top.) So maybe
  12960. // <thead>s that are not first should be
  12961. // turned into <tbody>? Very tricky, indeed.
  12962. if ($thead === false) {
  12963. $thead = $node;
  12964. $ws_accum =& $after_thead_ws;
  12965. } else {
  12966. // Oops, there's a second one! What
  12967. // should we do? Current behavior is to
  12968. // transmutate the first and last entries into
  12969. // tbody tags, and then put into content.
  12970. // Maybe a better idea is to *attach
  12971. // it* to the existing thead or tfoot?
  12972. // We don't do this, because Firefox
  12973. // doesn't float an extra tfoot to the
  12974. // bottom like it does for the first one.
  12975. $node->name = 'tbody';
  12976. $content[] = $node;
  12977. $ws_accum =& $content;
  12978. }
  12979. break;
  12980. case 'tfoot':
  12981. // see above for some aveats
  12982. $tbody_mode = true;
  12983. if ($tfoot === false) {
  12984. $tfoot = $node;
  12985. $ws_accum =& $after_tfoot_ws;
  12986. } else {
  12987. $node->name = 'tbody';
  12988. $content[] = $node;
  12989. $ws_accum =& $content;
  12990. }
  12991. break;
  12992. case 'colgroup':
  12993. case 'col':
  12994. $cols[] = $node;
  12995. $ws_accum =& $cols;
  12996. break;
  12997. case '#PCDATA':
  12998. // How is whitespace handled? We treat is as sticky to
  12999. // the *end* of the previous element. So all of the
  13000. // nonsense we have worked on is to keep things
  13001. // together.
  13002. if (!empty($node->is_whitespace)) {
  13003. $ws_accum[] = $node;
  13004. }
  13005. break;
  13006. }
  13007. }
  13008. if (empty($content)) {
  13009. return false;
  13010. }
  13011. $ret = $initial_ws;
  13012. if ($caption !== false) {
  13013. $ret[] = $caption;
  13014. $ret = array_merge($ret, $after_caption_ws);
  13015. }
  13016. if ($cols !== false) {
  13017. $ret = array_merge($ret, $cols);
  13018. }
  13019. if ($thead !== false) {
  13020. $ret[] = $thead;
  13021. $ret = array_merge($ret, $after_thead_ws);
  13022. }
  13023. if ($tfoot !== false) {
  13024. $ret[] = $tfoot;
  13025. $ret = array_merge($ret, $after_tfoot_ws);
  13026. }
  13027. if ($tbody_mode) {
  13028. // we have to shuffle tr into tbody
  13029. $current_tr_tbody = null;
  13030. foreach($content as $node) {
  13031. switch ($node->name) {
  13032. case 'tbody':
  13033. $current_tr_tbody = null;
  13034. $ret[] = $node;
  13035. break;
  13036. case 'tr':
  13037. if ($current_tr_tbody === null) {
  13038. $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
  13039. $ret[] = $current_tr_tbody;
  13040. }
  13041. $current_tr_tbody->children[] = $node;
  13042. break;
  13043. case '#PCDATA':
  13044. assert($node->is_whitespace);
  13045. if ($current_tr_tbody === null) {
  13046. $ret[] = $node;
  13047. } else {
  13048. $current_tr_tbody->children[] = $node;
  13049. }
  13050. break;
  13051. }
  13052. }
  13053. } else {
  13054. $ret = array_merge($ret, $content);
  13055. }
  13056. return $ret;
  13057. }
  13058. }
  13059. class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
  13060. {
  13061. /**
  13062. * Cache object we are decorating
  13063. * @type HTMLPurifier_DefinitionCache
  13064. */
  13065. public $cache;
  13066. /**
  13067. * The name of the decorator
  13068. * @var string
  13069. */
  13070. public $name;
  13071. public function __construct()
  13072. {
  13073. }
  13074. /**
  13075. * Lazy decorator function
  13076. * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
  13077. * @return HTMLPurifier_DefinitionCache_Decorator
  13078. */
  13079. public function decorate(&$cache)
  13080. {
  13081. $decorator = $this->copy();
  13082. // reference is necessary for mocks in PHP 4
  13083. $decorator->cache =& $cache;
  13084. $decorator->type = $cache->type;
  13085. return $decorator;
  13086. }
  13087. /**
  13088. * Cross-compatible clone substitute
  13089. * @return HTMLPurifier_DefinitionCache_Decorator
  13090. */
  13091. public function copy()
  13092. {
  13093. return new HTMLPurifier_DefinitionCache_Decorator();
  13094. }
  13095. /**
  13096. * @param HTMLPurifier_Definition $def
  13097. * @param HTMLPurifier_Config $config
  13098. * @return mixed
  13099. */
  13100. public function add($def, $config)
  13101. {
  13102. return $this->cache->add($def, $config);
  13103. }
  13104. /**
  13105. * @param HTMLPurifier_Definition $def
  13106. * @param HTMLPurifier_Config $config
  13107. * @return mixed
  13108. */
  13109. public function set($def, $config)
  13110. {
  13111. return $this->cache->set($def, $config);
  13112. }
  13113. /**
  13114. * @param HTMLPurifier_Definition $def
  13115. * @param HTMLPurifier_Config $config
  13116. * @return mixed
  13117. */
  13118. public function replace($def, $config)
  13119. {
  13120. return $this->cache->replace($def, $config);
  13121. }
  13122. /**
  13123. * @param HTMLPurifier_Config $config
  13124. * @return mixed
  13125. */
  13126. public function get($config)
  13127. {
  13128. return $this->cache->get($config);
  13129. }
  13130. /**
  13131. * @param HTMLPurifier_Config $config
  13132. * @return mixed
  13133. */
  13134. public function remove($config)
  13135. {
  13136. return $this->cache->remove($config);
  13137. }
  13138. /**
  13139. * @param HTMLPurifier_Config $config
  13140. * @return mixed
  13141. */
  13142. public function flush($config)
  13143. {
  13144. return $this->cache->flush($config);
  13145. }
  13146. /**
  13147. * @param HTMLPurifier_Config $config
  13148. * @return mixed
  13149. */
  13150. public function cleanup($config)
  13151. {
  13152. return $this->cache->cleanup($config);
  13153. }
  13154. }
  13155. /**
  13156. * Null cache object to use when no caching is on.
  13157. */
  13158. class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
  13159. {
  13160. /**
  13161. * @param HTMLPurifier_Definition $def
  13162. * @param HTMLPurifier_Config $config
  13163. * @return bool
  13164. */
  13165. public function add($def, $config)
  13166. {
  13167. return false;
  13168. }
  13169. /**
  13170. * @param HTMLPurifier_Definition $def
  13171. * @param HTMLPurifier_Config $config
  13172. * @return bool
  13173. */
  13174. public function set($def, $config)
  13175. {
  13176. return false;
  13177. }
  13178. /**
  13179. * @param HTMLPurifier_Definition $def
  13180. * @param HTMLPurifier_Config $config
  13181. * @return bool
  13182. */
  13183. public function replace($def, $config)
  13184. {
  13185. return false;
  13186. }
  13187. /**
  13188. * @param HTMLPurifier_Config $config
  13189. * @return bool
  13190. */
  13191. public function remove($config)
  13192. {
  13193. return false;
  13194. }
  13195. /**
  13196. * @param HTMLPurifier_Config $config
  13197. * @return bool
  13198. */
  13199. public function get($config)
  13200. {
  13201. return false;
  13202. }
  13203. /**
  13204. * @param HTMLPurifier_Config $config
  13205. * @return bool
  13206. */
  13207. public function flush($config)
  13208. {
  13209. return false;
  13210. }
  13211. /**
  13212. * @param HTMLPurifier_Config $config
  13213. * @return bool
  13214. */
  13215. public function cleanup($config)
  13216. {
  13217. return false;
  13218. }
  13219. }
  13220. class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
  13221. {
  13222. /**
  13223. * @param HTMLPurifier_Definition $def
  13224. * @param HTMLPurifier_Config $config
  13225. * @return int|bool
  13226. */
  13227. public function add($def, $config)
  13228. {
  13229. if (!$this->checkDefType($def)) {
  13230. return;
  13231. }
  13232. $file = $this->generateFilePath($config);
  13233. if (file_exists($file)) {
  13234. return false;
  13235. }
  13236. if (!$this->_prepareDir($config)) {
  13237. return false;
  13238. }
  13239. return $this->_write($file, serialize($def), $config);
  13240. }
  13241. /**
  13242. * @param HTMLPurifier_Definition $def
  13243. * @param HTMLPurifier_Config $config
  13244. * @return int|bool
  13245. */
  13246. public function set($def, $config)
  13247. {
  13248. if (!$this->checkDefType($def)) {
  13249. return;
  13250. }
  13251. $file = $this->generateFilePath($config);
  13252. if (!$this->_prepareDir($config)) {
  13253. return false;
  13254. }
  13255. return $this->_write($file, serialize($def), $config);
  13256. }
  13257. /**
  13258. * @param HTMLPurifier_Definition $def
  13259. * @param HTMLPurifier_Config $config
  13260. * @return int|bool
  13261. */
  13262. public function replace($def, $config)
  13263. {
  13264. if (!$this->checkDefType($def)) {
  13265. return;
  13266. }
  13267. $file = $this->generateFilePath($config);
  13268. if (!file_exists($file)) {
  13269. return false;
  13270. }
  13271. if (!$this->_prepareDir($config)) {
  13272. return false;
  13273. }
  13274. return $this->_write($file, serialize($def), $config);
  13275. }
  13276. /**
  13277. * @param HTMLPurifier_Config $config
  13278. * @return bool|HTMLPurifier_Config
  13279. */
  13280. public function get($config)
  13281. {
  13282. $file = $this->generateFilePath($config);
  13283. if (!file_exists($file)) {
  13284. return false;
  13285. }
  13286. return unserialize(file_get_contents($file));
  13287. }
  13288. /**
  13289. * @param HTMLPurifier_Config $config
  13290. * @return bool
  13291. */
  13292. public function remove($config)
  13293. {
  13294. $file = $this->generateFilePath($config);
  13295. if (!file_exists($file)) {
  13296. return false;
  13297. }
  13298. return unlink($file);
  13299. }
  13300. /**
  13301. * @param HTMLPurifier_Config $config
  13302. * @return bool
  13303. */
  13304. public function flush($config)
  13305. {
  13306. if (!$this->_prepareDir($config)) {
  13307. return false;
  13308. }
  13309. $dir = $this->generateDirectoryPath($config);
  13310. $dh = opendir($dir);
  13311. while (false !== ($filename = readdir($dh))) {
  13312. if (empty($filename)) {
  13313. continue;
  13314. }
  13315. if ($filename[0] === '.') {
  13316. continue;
  13317. }
  13318. unlink($dir . '/' . $filename);
  13319. }
  13320. }
  13321. /**
  13322. * @param HTMLPurifier_Config $config
  13323. * @return bool
  13324. */
  13325. public function cleanup($config)
  13326. {
  13327. if (!$this->_prepareDir($config)) {
  13328. return false;
  13329. }
  13330. $dir = $this->generateDirectoryPath($config);
  13331. $dh = opendir($dir);
  13332. while (false !== ($filename = readdir($dh))) {
  13333. if (empty($filename)) {
  13334. continue;
  13335. }
  13336. if ($filename[0] === '.') {
  13337. continue;
  13338. }
  13339. $key = substr($filename, 0, strlen($filename) - 4);
  13340. if ($this->isOld($key, $config)) {
  13341. unlink($dir . '/' . $filename);
  13342. }
  13343. }
  13344. }
  13345. /**
  13346. * Generates the file path to the serial file corresponding to
  13347. * the configuration and definition name
  13348. * @param HTMLPurifier_Config $config
  13349. * @return string
  13350. * @todo Make protected
  13351. */
  13352. public function generateFilePath($config)
  13353. {
  13354. $key = $this->generateKey($config);
  13355. return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
  13356. }
  13357. /**
  13358. * Generates the path to the directory contain this cache's serial files
  13359. * @param HTMLPurifier_Config $config
  13360. * @return string
  13361. * @note No trailing slash
  13362. * @todo Make protected
  13363. */
  13364. public function generateDirectoryPath($config)
  13365. {
  13366. $base = $this->generateBaseDirectoryPath($config);
  13367. return $base . '/' . $this->type;
  13368. }
  13369. /**
  13370. * Generates path to base directory that contains all definition type
  13371. * serials
  13372. * @param HTMLPurifier_Config $config
  13373. * @return mixed|string
  13374. * @todo Make protected
  13375. */
  13376. public function generateBaseDirectoryPath($config)
  13377. {
  13378. $base = $config->get('Cache.SerializerPath');
  13379. $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
  13380. return $base;
  13381. }
  13382. /**
  13383. * Convenience wrapper function for file_put_contents
  13384. * @param string $file File name to write to
  13385. * @param string $data Data to write into file
  13386. * @param HTMLPurifier_Config $config
  13387. * @return int|bool Number of bytes written if success, or false if failure.
  13388. */
  13389. private function _write($file, $data, $config)
  13390. {
  13391. $result = file_put_contents($file, $data);
  13392. if ($result !== false) {
  13393. // set permissions of the new file (no execute)
  13394. $chmod = $config->get('Cache.SerializerPermissions');
  13395. if (!$chmod) {
  13396. $chmod = 0644; // invalid config or simpletest
  13397. }
  13398. $chmod = $chmod & 0666;
  13399. chmod($file, $chmod);
  13400. }
  13401. return $result;
  13402. }
  13403. /**
  13404. * Prepares the directory that this type stores the serials in
  13405. * @param HTMLPurifier_Config $config
  13406. * @return bool True if successful
  13407. */
  13408. private function _prepareDir($config)
  13409. {
  13410. $directory = $this->generateDirectoryPath($config);
  13411. $chmod = $config->get('Cache.SerializerPermissions');
  13412. if (!$chmod) {
  13413. $chmod = 0755; // invalid config or simpletest
  13414. }
  13415. if (!is_dir($directory)) {
  13416. $base = $this->generateBaseDirectoryPath($config);
  13417. if (!is_dir($base)) {
  13418. trigger_error(
  13419. 'Base directory ' . $base . ' does not exist,
  13420. please create or change using %Cache.SerializerPath',
  13421. E_USER_WARNING
  13422. );
  13423. return false;
  13424. } elseif (!$this->_testPermissions($base, $chmod)) {
  13425. return false;
  13426. }
  13427. mkdir($directory, $chmod);
  13428. if (!$this->_testPermissions($directory, $chmod)) {
  13429. trigger_error(
  13430. 'Base directory ' . $base . ' does not exist,
  13431. please create or change using %Cache.SerializerPath',
  13432. E_USER_WARNING
  13433. );
  13434. return false;
  13435. }
  13436. } elseif (!$this->_testPermissions($directory, $chmod)) {
  13437. return false;
  13438. }
  13439. return true;
  13440. }
  13441. /**
  13442. * Tests permissions on a directory and throws out friendly
  13443. * error messages and attempts to chmod it itself if possible
  13444. * @param string $dir Directory path
  13445. * @param int $chmod Permissions
  13446. * @return bool True if directory is writable
  13447. */
  13448. private function _testPermissions($dir, $chmod)
  13449. {
  13450. // early abort, if it is writable, everything is hunky-dory
  13451. if (is_writable($dir)) {
  13452. return true;
  13453. }
  13454. if (!is_dir($dir)) {
  13455. // generally, you'll want to handle this beforehand
  13456. // so a more specific error message can be given
  13457. trigger_error(
  13458. 'Directory ' . $dir . ' does not exist',
  13459. E_USER_WARNING
  13460. );
  13461. return false;
  13462. }
  13463. if (function_exists('posix_getuid')) {
  13464. // POSIX system, we can give more specific advice
  13465. if (fileowner($dir) === posix_getuid()) {
  13466. // we can chmod it ourselves
  13467. $chmod = $chmod | 0700;
  13468. if (chmod($dir, $chmod)) {
  13469. return true;
  13470. }
  13471. } elseif (filegroup($dir) === posix_getgid()) {
  13472. $chmod = $chmod | 0070;
  13473. } else {
  13474. // PHP's probably running as nobody, so we'll
  13475. // need to give global permissions
  13476. $chmod = $chmod | 0777;
  13477. }
  13478. trigger_error(
  13479. 'Directory ' . $dir . ' not writable, ' .
  13480. 'please chmod to ' . decoct($chmod),
  13481. E_USER_WARNING
  13482. );
  13483. } else {
  13484. // generic error message
  13485. trigger_error(
  13486. 'Directory ' . $dir . ' not writable, ' .
  13487. 'please alter file permissions',
  13488. E_USER_WARNING
  13489. );
  13490. }
  13491. return false;
  13492. }
  13493. }
  13494. /**
  13495. * Definition cache decorator class that cleans up the cache
  13496. * whenever there is a cache miss.
  13497. */
  13498. class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
  13499. {
  13500. /**
  13501. * @type string
  13502. */
  13503. public $name = 'Cleanup';
  13504. /**
  13505. * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
  13506. */
  13507. public function copy()
  13508. {
  13509. return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
  13510. }
  13511. /**
  13512. * @param HTMLPurifier_Definition $def
  13513. * @param HTMLPurifier_Config $config
  13514. * @return mixed
  13515. */
  13516. public function add($def, $config)
  13517. {
  13518. $status = parent::add($def, $config);
  13519. if (!$status) {
  13520. parent::cleanup($config);
  13521. }
  13522. return $status;
  13523. }
  13524. /**
  13525. * @param HTMLPurifier_Definition $def
  13526. * @param HTMLPurifier_Config $config
  13527. * @return mixed
  13528. */
  13529. public function set($def, $config)
  13530. {
  13531. $status = parent::set($def, $config);
  13532. if (!$status) {
  13533. parent::cleanup($config);
  13534. }
  13535. return $status;
  13536. }
  13537. /**
  13538. * @param HTMLPurifier_Definition $def
  13539. * @param HTMLPurifier_Config $config
  13540. * @return mixed
  13541. */
  13542. public function replace($def, $config)
  13543. {
  13544. $status = parent::replace($def, $config);
  13545. if (!$status) {
  13546. parent::cleanup($config);
  13547. }
  13548. return $status;
  13549. }
  13550. /**
  13551. * @param HTMLPurifier_Config $config
  13552. * @return mixed
  13553. */
  13554. public function get($config)
  13555. {
  13556. $ret = parent::get($config);
  13557. if (!$ret) {
  13558. parent::cleanup($config);
  13559. }
  13560. return $ret;
  13561. }
  13562. }
  13563. /**
  13564. * Definition cache decorator class that saves all cache retrievals
  13565. * to PHP's memory; good for unit tests or circumstances where
  13566. * there are lots of configuration objects floating around.
  13567. */
  13568. class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
  13569. {
  13570. /**
  13571. * @type array
  13572. */
  13573. protected $definitions;
  13574. /**
  13575. * @type string
  13576. */
  13577. public $name = 'Memory';
  13578. /**
  13579. * @return HTMLPurifier_DefinitionCache_Decorator_Memory
  13580. */
  13581. public function copy()
  13582. {
  13583. return new HTMLPurifier_DefinitionCache_Decorator_Memory();
  13584. }
  13585. /**
  13586. * @param HTMLPurifier_Definition $def
  13587. * @param HTMLPurifier_Config $config
  13588. * @return mixed
  13589. */
  13590. public function add($def, $config)
  13591. {
  13592. $status = parent::add($def, $config);
  13593. if ($status) {
  13594. $this->definitions[$this->generateKey($config)] = $def;
  13595. }
  13596. return $status;
  13597. }
  13598. /**
  13599. * @param HTMLPurifier_Definition $def
  13600. * @param HTMLPurifier_Config $config
  13601. * @return mixed
  13602. */
  13603. public function set($def, $config)
  13604. {
  13605. $status = parent::set($def, $config);
  13606. if ($status) {
  13607. $this->definitions[$this->generateKey($config)] = $def;
  13608. }
  13609. return $status;
  13610. }
  13611. /**
  13612. * @param HTMLPurifier_Definition $def
  13613. * @param HTMLPurifier_Config $config
  13614. * @return mixed
  13615. */
  13616. public function replace($def, $config)
  13617. {
  13618. $status = parent::replace($def, $config);
  13619. if ($status) {
  13620. $this->definitions[$this->generateKey($config)] = $def;
  13621. }
  13622. return $status;
  13623. }
  13624. /**
  13625. * @param HTMLPurifier_Config $config
  13626. * @return mixed
  13627. */
  13628. public function get($config)
  13629. {
  13630. $key = $this->generateKey($config);
  13631. if (isset($this->definitions[$key])) {
  13632. return $this->definitions[$key];
  13633. }
  13634. $this->definitions[$key] = parent::get($config);
  13635. return $this->definitions[$key];
  13636. }
  13637. }
  13638. /**
  13639. * XHTML 1.1 Bi-directional Text Module, defines elements that
  13640. * declare directionality of content. Text Extension Module.
  13641. */
  13642. class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
  13643. {
  13644. /**
  13645. * @type string
  13646. */
  13647. public $name = 'Bdo';
  13648. /**
  13649. * @type array
  13650. */
  13651. public $attr_collections = array(
  13652. 'I18N' => array('dir' => false)
  13653. );
  13654. /**
  13655. * @param HTMLPurifier_Config $config
  13656. */
  13657. public function setup($config)
  13658. {
  13659. $bdo = $this->addElement(
  13660. 'bdo',
  13661. 'Inline',
  13662. 'Inline',
  13663. array('Core', 'Lang'),
  13664. array(
  13665. 'dir' => 'Enum#ltr,rtl', // required
  13666. // The Abstract Module specification has the attribute
  13667. // inclusions wrong for bdo: bdo allows Lang
  13668. )
  13669. );
  13670. $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
  13671. $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
  13672. }
  13673. }
  13674. class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
  13675. {
  13676. /**
  13677. * @type string
  13678. */
  13679. public $name = 'CommonAttributes';
  13680. /**
  13681. * @type array
  13682. */
  13683. public $attr_collections = array(
  13684. 'Core' => array(
  13685. 0 => array('Style'),
  13686. // 'xml:space' => false,
  13687. 'class' => 'Class',
  13688. 'id' => 'ID',
  13689. 'title' => 'CDATA',
  13690. ),
  13691. 'Lang' => array(),
  13692. 'I18N' => array(
  13693. 0 => array('Lang'), // proprietary, for xml:lang/lang
  13694. ),
  13695. 'Common' => array(
  13696. 0 => array('Core', 'I18N')
  13697. )
  13698. );
  13699. }
  13700. /**
  13701. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  13702. * Module.
  13703. */
  13704. class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
  13705. {
  13706. /**
  13707. * @type string
  13708. */
  13709. public $name = 'Edit';
  13710. /**
  13711. * @param HTMLPurifier_Config $config
  13712. */
  13713. public function setup($config)
  13714. {
  13715. $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
  13716. $attr = array(
  13717. 'cite' => 'URI',
  13718. // 'datetime' => 'Datetime', // not implemented
  13719. );
  13720. $this->addElement('del', 'Inline', $contents, 'Common', $attr);
  13721. $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
  13722. }
  13723. // HTML 4.01 specifies that ins/del must not contain block
  13724. // elements when used in an inline context, chameleon is
  13725. // a complicated workaround to acheive this effect
  13726. // Inline context ! Block context (exclamation mark is
  13727. // separator, see getChildDef for parsing)
  13728. /**
  13729. * @type bool
  13730. */
  13731. public $defines_child_def = true;
  13732. /**
  13733. * @param HTMLPurifier_ElementDef $def
  13734. * @return HTMLPurifier_ChildDef_Chameleon
  13735. */
  13736. public function getChildDef($def)
  13737. {
  13738. if ($def->content_model_type != 'chameleon') {
  13739. return false;
  13740. }
  13741. $value = explode('!', $def->content_model);
  13742. return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
  13743. }
  13744. }
  13745. /**
  13746. * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
  13747. */
  13748. class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
  13749. {
  13750. /**
  13751. * @type string
  13752. */
  13753. public $name = 'Forms';
  13754. /**
  13755. * @type bool
  13756. */
  13757. public $safe = false;
  13758. /**
  13759. * @type array
  13760. */
  13761. public $content_sets = array(
  13762. 'Block' => 'Form',
  13763. 'Inline' => 'Formctrl',
  13764. );
  13765. /**
  13766. * @param HTMLPurifier_Config $config
  13767. */
  13768. public function setup($config)
  13769. {
  13770. $form = $this->addElement(
  13771. 'form',
  13772. 'Form',
  13773. 'Required: Heading | List | Block | fieldset',
  13774. 'Common',
  13775. array(
  13776. 'accept' => 'ContentTypes',
  13777. 'accept-charset' => 'Charsets',
  13778. 'action*' => 'URI',
  13779. 'method' => 'Enum#get,post',
  13780. // really ContentType, but these two are the only ones used today
  13781. 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
  13782. )
  13783. );
  13784. $form->excludes = array('form' => true);
  13785. $input = $this->addElement(
  13786. 'input',
  13787. 'Formctrl',
  13788. 'Empty',
  13789. 'Common',
  13790. array(
  13791. 'accept' => 'ContentTypes',
  13792. 'accesskey' => 'Character',
  13793. 'alt' => 'Text',
  13794. 'checked' => 'Bool#checked',
  13795. 'disabled' => 'Bool#disabled',
  13796. 'maxlength' => 'Number',
  13797. 'name' => 'CDATA',
  13798. 'readonly' => 'Bool#readonly',
  13799. 'size' => 'Number',
  13800. 'src' => 'URI#embedded',
  13801. 'tabindex' => 'Number',
  13802. 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
  13803. 'value' => 'CDATA',
  13804. )
  13805. );
  13806. $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
  13807. $this->addElement(
  13808. 'select',
  13809. 'Formctrl',
  13810. 'Required: optgroup | option',
  13811. 'Common',
  13812. array(
  13813. 'disabled' => 'Bool#disabled',
  13814. 'multiple' => 'Bool#multiple',
  13815. 'name' => 'CDATA',
  13816. 'size' => 'Number',
  13817. 'tabindex' => 'Number',
  13818. )
  13819. );
  13820. $this->addElement(
  13821. 'option',
  13822. false,
  13823. 'Optional: #PCDATA',
  13824. 'Common',
  13825. array(
  13826. 'disabled' => 'Bool#disabled',
  13827. 'label' => 'Text',
  13828. 'selected' => 'Bool#selected',
  13829. 'value' => 'CDATA',
  13830. )
  13831. );
  13832. // It's illegal for there to be more than one selected, but not
  13833. // be multiple. Also, no selected means undefined behavior. This might
  13834. // be difficult to implement; perhaps an injector, or a context variable.
  13835. $textarea = $this->addElement(
  13836. 'textarea',
  13837. 'Formctrl',
  13838. 'Optional: #PCDATA',
  13839. 'Common',
  13840. array(
  13841. 'accesskey' => 'Character',
  13842. 'cols*' => 'Number',
  13843. 'disabled' => 'Bool#disabled',
  13844. 'name' => 'CDATA',
  13845. 'readonly' => 'Bool#readonly',
  13846. 'rows*' => 'Number',
  13847. 'tabindex' => 'Number',
  13848. )
  13849. );
  13850. $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
  13851. $button = $this->addElement(
  13852. 'button',
  13853. 'Formctrl',
  13854. 'Optional: #PCDATA | Heading | List | Block | Inline',
  13855. 'Common',
  13856. array(
  13857. 'accesskey' => 'Character',
  13858. 'disabled' => 'Bool#disabled',
  13859. 'name' => 'CDATA',
  13860. 'tabindex' => 'Number',
  13861. 'type' => 'Enum#button,submit,reset',
  13862. 'value' => 'CDATA',
  13863. )
  13864. );
  13865. // For exclusions, ideally we'd specify content sets, not literal elements
  13866. $button->excludes = $this->makeLookup(
  13867. 'form',
  13868. 'fieldset', // Form
  13869. 'input',
  13870. 'select',
  13871. 'textarea',
  13872. 'label',
  13873. 'button', // Formctrl
  13874. 'a', // as per HTML 4.01 spec, this is omitted by modularization
  13875. 'isindex',
  13876. 'iframe' // legacy items
  13877. );
  13878. // Extra exclusion: img usemap="" is not permitted within this element.
  13879. // We'll omit this for now, since we don't have any good way of
  13880. // indicating it yet.
  13881. // This is HIGHLY user-unfriendly; we need a custom child-def for this
  13882. $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
  13883. $label = $this->addElement(
  13884. 'label',
  13885. 'Formctrl',
  13886. 'Optional: #PCDATA | Inline',
  13887. 'Common',
  13888. array(
  13889. 'accesskey' => 'Character',
  13890. // 'for' => 'IDREF', // IDREF not implemented, cannot allow
  13891. )
  13892. );
  13893. $label->excludes = array('label' => true);
  13894. $this->addElement(
  13895. 'legend',
  13896. false,
  13897. 'Optional: #PCDATA | Inline',
  13898. 'Common',
  13899. array(
  13900. 'accesskey' => 'Character',
  13901. )
  13902. );
  13903. $this->addElement(
  13904. 'optgroup',
  13905. false,
  13906. 'Required: option',
  13907. 'Common',
  13908. array(
  13909. 'disabled' => 'Bool#disabled',
  13910. 'label*' => 'Text',
  13911. )
  13912. );
  13913. // Don't forget an injector for <isindex>. This one's a little complex
  13914. // because it maps to multiple elements.
  13915. }
  13916. }
  13917. /**
  13918. * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
  13919. */
  13920. class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
  13921. {
  13922. /**
  13923. * @type string
  13924. */
  13925. public $name = 'Hypertext';
  13926. /**
  13927. * @param HTMLPurifier_Config $config
  13928. */
  13929. public function setup($config)
  13930. {
  13931. $a = $this->addElement(
  13932. 'a',
  13933. 'Inline',
  13934. 'Inline',
  13935. 'Common',
  13936. array(
  13937. // 'accesskey' => 'Character',
  13938. // 'charset' => 'Charset',
  13939. 'href' => 'URI',
  13940. // 'hreflang' => 'LanguageCode',
  13941. 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
  13942. 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
  13943. // 'tabindex' => 'Number',
  13944. // 'type' => 'ContentType',
  13945. )
  13946. );
  13947. $a->formatting = true;
  13948. $a->excludes = array('a' => true);
  13949. }
  13950. }
  13951. /**
  13952. * XHTML 1.1 Iframe Module provides inline frames.
  13953. *
  13954. * @note This module is not considered safe unless an Iframe
  13955. * whitelisting mechanism is specified. Currently, the only
  13956. * such mechanism is %URL.SafeIframeRegexp
  13957. */
  13958. class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
  13959. {
  13960. /**
  13961. * @type string
  13962. */
  13963. public $name = 'Iframe';
  13964. /**
  13965. * @type bool
  13966. */
  13967. public $safe = false;
  13968. /**
  13969. * @param HTMLPurifier_Config $config
  13970. */
  13971. public function setup($config)
  13972. {
  13973. if ($config->get('HTML.SafeIframe')) {
  13974. $this->safe = true;
  13975. }
  13976. $this->addElement(
  13977. 'iframe',
  13978. 'Inline',
  13979. 'Flow',
  13980. 'Common',
  13981. array(
  13982. 'src' => 'URI#embedded',
  13983. 'width' => 'Length',
  13984. 'height' => 'Length',
  13985. 'name' => 'ID',
  13986. 'scrolling' => 'Enum#yes,no,auto',
  13987. 'frameborder' => 'Enum#0,1',
  13988. 'longdesc' => 'URI',
  13989. 'marginheight' => 'Pixels',
  13990. 'marginwidth' => 'Pixels',
  13991. )
  13992. );
  13993. }
  13994. }
  13995. /**
  13996. * XHTML 1.1 Image Module provides basic image embedding.
  13997. * @note There is specialized code for removing empty images in
  13998. * HTMLPurifier_Strategy_RemoveForeignElements
  13999. */
  14000. class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
  14001. {
  14002. /**
  14003. * @type string
  14004. */
  14005. public $name = 'Image';
  14006. /**
  14007. * @param HTMLPurifier_Config $config
  14008. */
  14009. public function setup($config)
  14010. {
  14011. $max = $config->get('HTML.MaxImgLength');
  14012. $img = $this->addElement(
  14013. 'img',
  14014. 'Inline',
  14015. 'Empty',
  14016. 'Common',
  14017. array(
  14018. 'alt*' => 'Text',
  14019. // According to the spec, it's Length, but percents can
  14020. // be abused, so we allow only Pixels.
  14021. 'height' => 'Pixels#' . $max,
  14022. 'width' => 'Pixels#' . $max,
  14023. 'longdesc' => 'URI',
  14024. 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
  14025. )
  14026. );
  14027. if ($max === null || $config->get('HTML.Trusted')) {
  14028. $img->attr['height'] =
  14029. $img->attr['width'] = 'Length';
  14030. }
  14031. // kind of strange, but splitting things up would be inefficient
  14032. $img->attr_transform_pre[] =
  14033. $img->attr_transform_post[] =
  14034. new HTMLPurifier_AttrTransform_ImgRequired();
  14035. }
  14036. }
  14037. /**
  14038. * XHTML 1.1 Legacy module defines elements that were previously
  14039. * deprecated.
  14040. *
  14041. * @note Not all legacy elements have been implemented yet, which
  14042. * is a bit of a reverse problem as compared to browsers! In
  14043. * addition, this legacy module may implement a bit more than
  14044. * mandated by XHTML 1.1.
  14045. *
  14046. * This module can be used in combination with TransformToStrict in order
  14047. * to transform as many deprecated elements as possible, but retain
  14048. * questionably deprecated elements that do not have good alternatives
  14049. * as well as transform elements that don't have an implementation.
  14050. * See docs/ref-strictness.txt for more details.
  14051. */
  14052. class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
  14053. {
  14054. /**
  14055. * @type string
  14056. */
  14057. public $name = 'Legacy';
  14058. /**
  14059. * @param HTMLPurifier_Config $config
  14060. */
  14061. public function setup($config)
  14062. {
  14063. $this->addElement(
  14064. 'basefont',
  14065. 'Inline',
  14066. 'Empty',
  14067. null,
  14068. array(
  14069. 'color' => 'Color',
  14070. 'face' => 'Text', // extremely broad, we should
  14071. 'size' => 'Text', // tighten it
  14072. 'id' => 'ID'
  14073. )
  14074. );
  14075. $this->addElement('center', 'Block', 'Flow', 'Common');
  14076. $this->addElement(
  14077. 'dir',
  14078. 'Block',
  14079. 'Required: li',
  14080. 'Common',
  14081. array(
  14082. 'compact' => 'Bool#compact'
  14083. )
  14084. );
  14085. $this->addElement(
  14086. 'font',
  14087. 'Inline',
  14088. 'Inline',
  14089. array('Core', 'I18N'),
  14090. array(
  14091. 'color' => 'Color',
  14092. 'face' => 'Text', // extremely broad, we should
  14093. 'size' => 'Text', // tighten it
  14094. )
  14095. );
  14096. $this->addElement(
  14097. 'menu',
  14098. 'Block',
  14099. 'Required: li',
  14100. 'Common',
  14101. array(
  14102. 'compact' => 'Bool#compact'
  14103. )
  14104. );
  14105. $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
  14106. $s->formatting = true;
  14107. $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
  14108. $strike->formatting = true;
  14109. $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
  14110. $u->formatting = true;
  14111. // setup modifications to old elements
  14112. $align = 'Enum#left,right,center,justify';
  14113. $address = $this->addBlankElement('address');
  14114. $address->content_model = 'Inline | #PCDATA | p';
  14115. $address->content_model_type = 'optional';
  14116. $address->child = false;
  14117. $blockquote = $this->addBlankElement('blockquote');
  14118. $blockquote->content_model = 'Flow | #PCDATA';
  14119. $blockquote->content_model_type = 'optional';
  14120. $blockquote->child = false;
  14121. $br = $this->addBlankElement('br');
  14122. $br->attr['clear'] = 'Enum#left,all,right,none';
  14123. $caption = $this->addBlankElement('caption');
  14124. $caption->attr['align'] = 'Enum#top,bottom,left,right';
  14125. $div = $this->addBlankElement('div');
  14126. $div->attr['align'] = $align;
  14127. $dl = $this->addBlankElement('dl');
  14128. $dl->attr['compact'] = 'Bool#compact';
  14129. for ($i = 1; $i <= 6; $i++) {
  14130. $h = $this->addBlankElement("h$i");
  14131. $h->attr['align'] = $align;
  14132. }
  14133. $hr = $this->addBlankElement('hr');
  14134. $hr->attr['align'] = $align;
  14135. $hr->attr['noshade'] = 'Bool#noshade';
  14136. $hr->attr['size'] = 'Pixels';
  14137. $hr->attr['width'] = 'Length';
  14138. $img = $this->addBlankElement('img');
  14139. $img->attr['align'] = 'IAlign';
  14140. $img->attr['border'] = 'Pixels';
  14141. $img->attr['hspace'] = 'Pixels';
  14142. $img->attr['vspace'] = 'Pixels';
  14143. // figure out this integer business
  14144. $li = $this->addBlankElement('li');
  14145. $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
  14146. $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
  14147. $ol = $this->addBlankElement('ol');
  14148. $ol->attr['compact'] = 'Bool#compact';
  14149. $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
  14150. $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
  14151. $p = $this->addBlankElement('p');
  14152. $p->attr['align'] = $align;
  14153. $pre = $this->addBlankElement('pre');
  14154. $pre->attr['width'] = 'Number';
  14155. // script omitted
  14156. $table = $this->addBlankElement('table');
  14157. $table->attr['align'] = 'Enum#left,center,right';
  14158. $table->attr['bgcolor'] = 'Color';
  14159. $tr = $this->addBlankElement('tr');
  14160. $tr->attr['bgcolor'] = 'Color';
  14161. $th = $this->addBlankElement('th');
  14162. $th->attr['bgcolor'] = 'Color';
  14163. $th->attr['height'] = 'Length';
  14164. $th->attr['nowrap'] = 'Bool#nowrap';
  14165. $th->attr['width'] = 'Length';
  14166. $td = $this->addBlankElement('td');
  14167. $td->attr['bgcolor'] = 'Color';
  14168. $td->attr['height'] = 'Length';
  14169. $td->attr['nowrap'] = 'Bool#nowrap';
  14170. $td->attr['width'] = 'Length';
  14171. $ul = $this->addBlankElement('ul');
  14172. $ul->attr['compact'] = 'Bool#compact';
  14173. $ul->attr['type'] = 'Enum#square,disc,circle';
  14174. // "safe" modifications to "unsafe" elements
  14175. // WARNING: If you want to add support for an unsafe, legacy
  14176. // attribute, make a new TrustedLegacy module with the trusted
  14177. // bit set appropriately
  14178. $form = $this->addBlankElement('form');
  14179. $form->content_model = 'Flow | #PCDATA';
  14180. $form->content_model_type = 'optional';
  14181. $form->attr['target'] = 'FrameTarget';
  14182. $input = $this->addBlankElement('input');
  14183. $input->attr['align'] = 'IAlign';
  14184. $legend = $this->addBlankElement('legend');
  14185. $legend->attr['align'] = 'LAlign';
  14186. }
  14187. }
  14188. /**
  14189. * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
  14190. */
  14191. class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
  14192. {
  14193. /**
  14194. * @type string
  14195. */
  14196. public $name = 'List';
  14197. // According to the abstract schema, the List content set is a fully formed
  14198. // one or more expr, but it invariably occurs in an optional declaration
  14199. // so we're not going to do that subtlety. It might cause trouble
  14200. // if a user defines "List" and expects that multiple lists are
  14201. // allowed to be specified, but then again, that's not very intuitive.
  14202. // Furthermore, the actual XML Schema may disagree. Regardless,
  14203. // we don't have support for such nested expressions without using
  14204. // the incredibly inefficient and draconic Custom ChildDef.
  14205. /**
  14206. * @type array
  14207. */
  14208. public $content_sets = array('Flow' => 'List');
  14209. /**
  14210. * @param HTMLPurifier_Config $config
  14211. */
  14212. public function setup($config)
  14213. {
  14214. $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14215. $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
  14216. // XXX The wrap attribute is handled by MakeWellFormed. This is all
  14217. // quite unsatisfactory, because we generated this
  14218. // *specifically* for lists, and now a big chunk of the handling
  14219. // is done properly by the List ChildDef. So actually, we just
  14220. // want enough information to make autoclosing work properly,
  14221. // and then hand off the tricky stuff to the ChildDef.
  14222. $ol->wrap = 'li';
  14223. $ul->wrap = 'li';
  14224. $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
  14225. $this->addElement('li', false, 'Flow', 'Common');
  14226. $this->addElement('dd', false, 'Flow', 'Common');
  14227. $this->addElement('dt', false, 'Inline', 'Common');
  14228. }
  14229. }
  14230. class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
  14231. {
  14232. /**
  14233. * @type string
  14234. */
  14235. public $name = 'Name';
  14236. /**
  14237. * @param HTMLPurifier_Config $config
  14238. */
  14239. public function setup($config)
  14240. {
  14241. $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
  14242. foreach ($elements as $name) {
  14243. $element = $this->addBlankElement($name);
  14244. $element->attr['name'] = 'CDATA';
  14245. if (!$config->get('HTML.Attr.Name.UseCDATA')) {
  14246. $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
  14247. }
  14248. }
  14249. }
  14250. }
  14251. /**
  14252. * Module adds the nofollow attribute transformation to a tags. It
  14253. * is enabled by HTML.Nofollow
  14254. */
  14255. class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
  14256. {
  14257. /**
  14258. * @type string
  14259. */
  14260. public $name = 'Nofollow';
  14261. /**
  14262. * @param HTMLPurifier_Config $config
  14263. */
  14264. public function setup($config)
  14265. {
  14266. $a = $this->addBlankElement('a');
  14267. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
  14268. }
  14269. }
  14270. class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
  14271. {
  14272. /**
  14273. * @type string
  14274. */
  14275. public $name = 'NonXMLCommonAttributes';
  14276. /**
  14277. * @type array
  14278. */
  14279. public $attr_collections = array(
  14280. 'Lang' => array(
  14281. 'lang' => 'LanguageCode',
  14282. )
  14283. );
  14284. }
  14285. /**
  14286. * XHTML 1.1 Object Module, defines elements for generic object inclusion
  14287. * @warning Users will commonly use <embed> to cater to legacy browsers: this
  14288. * module does not allow this sort of behavior
  14289. */
  14290. class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
  14291. {
  14292. /**
  14293. * @type string
  14294. */
  14295. public $name = 'Object';
  14296. /**
  14297. * @type bool
  14298. */
  14299. public $safe = false;
  14300. /**
  14301. * @param HTMLPurifier_Config $config
  14302. */
  14303. public function setup($config)
  14304. {
  14305. $this->addElement(
  14306. 'object',
  14307. 'Inline',
  14308. 'Optional: #PCDATA | Flow | param',
  14309. 'Common',
  14310. array(
  14311. 'archive' => 'URI',
  14312. 'classid' => 'URI',
  14313. 'codebase' => 'URI',
  14314. 'codetype' => 'Text',
  14315. 'data' => 'URI',
  14316. 'declare' => 'Bool#declare',
  14317. 'height' => 'Length',
  14318. 'name' => 'CDATA',
  14319. 'standby' => 'Text',
  14320. 'tabindex' => 'Number',
  14321. 'type' => 'ContentType',
  14322. 'width' => 'Length'
  14323. )
  14324. );
  14325. $this->addElement(
  14326. 'param',
  14327. false,
  14328. 'Empty',
  14329. null,
  14330. array(
  14331. 'id' => 'ID',
  14332. 'name*' => 'Text',
  14333. 'type' => 'Text',
  14334. 'value' => 'Text',
  14335. 'valuetype' => 'Enum#data,ref,object'
  14336. )
  14337. );
  14338. }
  14339. }
  14340. /**
  14341. * XHTML 1.1 Presentation Module, defines simple presentation-related
  14342. * markup. Text Extension Module.
  14343. * @note The official XML Schema and DTD specs further divide this into
  14344. * two modules:
  14345. * - Block Presentation (hr)
  14346. * - Inline Presentation (b, big, i, small, sub, sup, tt)
  14347. * We have chosen not to heed this distinction, as content_sets
  14348. * provides satisfactory disambiguation.
  14349. */
  14350. class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
  14351. {
  14352. /**
  14353. * @type string
  14354. */
  14355. public $name = 'Presentation';
  14356. /**
  14357. * @param HTMLPurifier_Config $config
  14358. */
  14359. public function setup($config)
  14360. {
  14361. $this->addElement('hr', 'Block', 'Empty', 'Common');
  14362. $this->addElement('sub', 'Inline', 'Inline', 'Common');
  14363. $this->addElement('sup', 'Inline', 'Inline', 'Common');
  14364. $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
  14365. $b->formatting = true;
  14366. $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
  14367. $big->formatting = true;
  14368. $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
  14369. $i->formatting = true;
  14370. $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
  14371. $small->formatting = true;
  14372. $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
  14373. $tt->formatting = true;
  14374. }
  14375. }
  14376. /**
  14377. * Module defines proprietary tags and attributes in HTML.
  14378. * @warning If this module is enabled, standards-compliance is off!
  14379. */
  14380. class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
  14381. {
  14382. /**
  14383. * @type string
  14384. */
  14385. public $name = 'Proprietary';
  14386. /**
  14387. * @param HTMLPurifier_Config $config
  14388. */
  14389. public function setup($config)
  14390. {
  14391. $this->addElement(
  14392. 'marquee',
  14393. 'Inline',
  14394. 'Flow',
  14395. 'Common',
  14396. array(
  14397. 'direction' => 'Enum#left,right,up,down',
  14398. 'behavior' => 'Enum#alternate',
  14399. 'width' => 'Length',
  14400. 'height' => 'Length',
  14401. 'scrolldelay' => 'Number',
  14402. 'scrollamount' => 'Number',
  14403. 'loop' => 'Number',
  14404. 'bgcolor' => 'Color',
  14405. 'hspace' => 'Pixels',
  14406. 'vspace' => 'Pixels',
  14407. )
  14408. );
  14409. }
  14410. }
  14411. /**
  14412. * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
  14413. * short runs of text alongside base text for annotation or pronounciation.
  14414. */
  14415. class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
  14416. {
  14417. /**
  14418. * @type string
  14419. */
  14420. public $name = 'Ruby';
  14421. /**
  14422. * @param HTMLPurifier_Config $config
  14423. */
  14424. public function setup($config)
  14425. {
  14426. $this->addElement(
  14427. 'ruby',
  14428. 'Inline',
  14429. 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
  14430. 'Common'
  14431. );
  14432. $this->addElement('rbc', false, 'Required: rb', 'Common');
  14433. $this->addElement('rtc', false, 'Required: rt', 'Common');
  14434. $rb = $this->addElement('rb', false, 'Inline', 'Common');
  14435. $rb->excludes = array('ruby' => true);
  14436. $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
  14437. $rt->excludes = array('ruby' => true);
  14438. $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
  14439. }
  14440. }
  14441. /**
  14442. * A "safe" embed module. See SafeObject. This is a proprietary element.
  14443. */
  14444. class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
  14445. {
  14446. /**
  14447. * @type string
  14448. */
  14449. public $name = 'SafeEmbed';
  14450. /**
  14451. * @param HTMLPurifier_Config $config
  14452. */
  14453. public function setup($config)
  14454. {
  14455. $max = $config->get('HTML.MaxImgLength');
  14456. $embed = $this->addElement(
  14457. 'embed',
  14458. 'Inline',
  14459. 'Empty',
  14460. 'Common',
  14461. array(
  14462. 'src*' => 'URI#embedded',
  14463. 'type' => 'Enum#application/x-shockwave-flash',
  14464. 'width' => 'Pixels#' . $max,
  14465. 'height' => 'Pixels#' . $max,
  14466. 'allowscriptaccess' => 'Enum#never',
  14467. 'allownetworking' => 'Enum#internal',
  14468. 'flashvars' => 'Text',
  14469. 'wmode' => 'Enum#window,transparent,opaque',
  14470. 'name' => 'ID',
  14471. )
  14472. );
  14473. $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
  14474. }
  14475. }
  14476. /**
  14477. * A "safe" object module. In theory, objects permitted by this module will
  14478. * be safe, and untrusted users can be allowed to embed arbitrary flash objects
  14479. * (maybe other types too, but only Flash is supported as of right now).
  14480. * Highly experimental.
  14481. */
  14482. class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
  14483. {
  14484. /**
  14485. * @type string
  14486. */
  14487. public $name = 'SafeObject';
  14488. /**
  14489. * @param HTMLPurifier_Config $config
  14490. */
  14491. public function setup($config)
  14492. {
  14493. // These definitions are not intrinsically safe: the attribute transforms
  14494. // are a vital part of ensuring safety.
  14495. $max = $config->get('HTML.MaxImgLength');
  14496. $object = $this->addElement(
  14497. 'object',
  14498. 'Inline',
  14499. 'Optional: param | Flow | #PCDATA',
  14500. 'Common',
  14501. array(
  14502. // While technically not required by the spec, we're forcing
  14503. // it to this value.
  14504. 'type' => 'Enum#application/x-shockwave-flash',
  14505. 'width' => 'Pixels#' . $max,
  14506. 'height' => 'Pixels#' . $max,
  14507. 'data' => 'URI#embedded',
  14508. 'codebase' => new HTMLPurifier_AttrDef_Enum(
  14509. array(
  14510. 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
  14511. )
  14512. ),
  14513. )
  14514. );
  14515. $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
  14516. $param = $this->addElement(
  14517. 'param',
  14518. false,
  14519. 'Empty',
  14520. false,
  14521. array(
  14522. 'id' => 'ID',
  14523. 'name*' => 'Text',
  14524. 'value' => 'Text'
  14525. )
  14526. );
  14527. $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
  14528. $this->info_injector[] = 'SafeObject';
  14529. }
  14530. }
  14531. /**
  14532. * A "safe" script module. No inline JS is allowed, and pointed to JS
  14533. * files must match whitelist.
  14534. */
  14535. class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
  14536. {
  14537. /**
  14538. * @type string
  14539. */
  14540. public $name = 'SafeScripting';
  14541. /**
  14542. * @param HTMLPurifier_Config $config
  14543. */
  14544. public function setup($config)
  14545. {
  14546. // These definitions are not intrinsically safe: the attribute transforms
  14547. // are a vital part of ensuring safety.
  14548. $allowed = $config->get('HTML.SafeScripting');
  14549. $script = $this->addElement(
  14550. 'script',
  14551. 'Inline',
  14552. 'Empty',
  14553. null,
  14554. array(
  14555. // While technically not required by the spec, we're forcing
  14556. // it to this value.
  14557. 'type' => 'Enum#text/javascript',
  14558. 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
  14559. )
  14560. );
  14561. $script->attr_transform_pre[] =
  14562. $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
  14563. }
  14564. }
  14565. /*
  14566. WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
  14567. INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
  14568. */
  14569. /**
  14570. * XHTML 1.1 Scripting module, defines elements that are used to contain
  14571. * information pertaining to executable scripts or the lack of support
  14572. * for executable scripts.
  14573. * @note This module does not contain inline scripting elements
  14574. */
  14575. class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
  14576. {
  14577. /**
  14578. * @type string
  14579. */
  14580. public $name = 'Scripting';
  14581. /**
  14582. * @type array
  14583. */
  14584. public $elements = array('script', 'noscript');
  14585. /**
  14586. * @type array
  14587. */
  14588. public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
  14589. /**
  14590. * @type bool
  14591. */
  14592. public $safe = false;
  14593. /**
  14594. * @param HTMLPurifier_Config $config
  14595. */
  14596. public function setup($config)
  14597. {
  14598. // TODO: create custom child-definition for noscript that
  14599. // auto-wraps stray #PCDATA in a similar manner to
  14600. // blockquote's custom definition (we would use it but
  14601. // blockquote's contents are optional while noscript's contents
  14602. // are required)
  14603. // TODO: convert this to new syntax, main problem is getting
  14604. // both content sets working
  14605. // In theory, this could be safe, but I don't see any reason to
  14606. // allow it.
  14607. $this->info['noscript'] = new HTMLPurifier_ElementDef();
  14608. $this->info['noscript']->attr = array(0 => array('Common'));
  14609. $this->info['noscript']->content_model = 'Heading | List | Block';
  14610. $this->info['noscript']->content_model_type = 'required';
  14611. $this->info['script'] = new HTMLPurifier_ElementDef();
  14612. $this->info['script']->attr = array(
  14613. 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
  14614. 'src' => new HTMLPurifier_AttrDef_URI(true),
  14615. 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
  14616. );
  14617. $this->info['script']->content_model = '#PCDATA';
  14618. $this->info['script']->content_model_type = 'optional';
  14619. $this->info['script']->attr_transform_pre[] =
  14620. $this->info['script']->attr_transform_post[] =
  14621. new HTMLPurifier_AttrTransform_ScriptRequired();
  14622. }
  14623. }
  14624. /**
  14625. * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
  14626. * Module.
  14627. */
  14628. class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
  14629. {
  14630. /**
  14631. * @type string
  14632. */
  14633. public $name = 'StyleAttribute';
  14634. /**
  14635. * @type array
  14636. */
  14637. public $attr_collections = array(
  14638. // The inclusion routine differs from the Abstract Modules but
  14639. // is in line with the DTD and XML Schemas.
  14640. 'Style' => array('style' => false), // see constructor
  14641. 'Core' => array(0 => array('Style'))
  14642. );
  14643. /**
  14644. * @param HTMLPurifier_Config $config
  14645. */
  14646. public function setup($config)
  14647. {
  14648. $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
  14649. }
  14650. }
  14651. /**
  14652. * XHTML 1.1 Tables Module, fully defines accessible table elements.
  14653. */
  14654. class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
  14655. {
  14656. /**
  14657. * @type string
  14658. */
  14659. public $name = 'Tables';
  14660. /**
  14661. * @param HTMLPurifier_Config $config
  14662. */
  14663. public function setup($config)
  14664. {
  14665. $this->addElement('caption', false, 'Inline', 'Common');
  14666. $this->addElement(
  14667. 'table',
  14668. 'Block',
  14669. new HTMLPurifier_ChildDef_Table(),
  14670. 'Common',
  14671. array(
  14672. 'border' => 'Pixels',
  14673. 'cellpadding' => 'Length',
  14674. 'cellspacing' => 'Length',
  14675. 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
  14676. 'rules' => 'Enum#none,groups,rows,cols,all',
  14677. 'summary' => 'Text',
  14678. 'width' => 'Length'
  14679. )
  14680. );
  14681. // common attributes
  14682. $cell_align = array(
  14683. 'align' => 'Enum#left,center,right,justify,char',
  14684. 'charoff' => 'Length',
  14685. 'valign' => 'Enum#top,middle,bottom,baseline',
  14686. );
  14687. $cell_t = array_merge(
  14688. array(
  14689. 'abbr' => 'Text',
  14690. 'colspan' => 'Number',
  14691. 'rowspan' => 'Number',
  14692. // Apparently, as of HTML5 this attribute only applies
  14693. // to 'th' elements.
  14694. 'scope' => 'Enum#row,col,rowgroup,colgroup',
  14695. ),
  14696. $cell_align
  14697. );
  14698. $this->addElement('td', false, 'Flow', 'Common', $cell_t);
  14699. $this->addElement('th', false, 'Flow', 'Common', $cell_t);
  14700. $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
  14701. $cell_col = array_merge(
  14702. array(
  14703. 'span' => 'Number',
  14704. 'width' => 'MultiLength',
  14705. ),
  14706. $cell_align
  14707. );
  14708. $this->addElement('col', false, 'Empty', 'Common', $cell_col);
  14709. $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
  14710. $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
  14711. $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
  14712. $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
  14713. }
  14714. }
  14715. /**
  14716. * XHTML 1.1 Target Module, defines target attribute in link elements.
  14717. */
  14718. class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
  14719. {
  14720. /**
  14721. * @type string
  14722. */
  14723. public $name = 'Target';
  14724. /**
  14725. * @param HTMLPurifier_Config $config
  14726. */
  14727. public function setup($config)
  14728. {
  14729. $elements = array('a');
  14730. foreach ($elements as $name) {
  14731. $e = $this->addBlankElement($name);
  14732. $e->attr = array(
  14733. 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
  14734. );
  14735. }
  14736. }
  14737. }
  14738. /**
  14739. * Module adds the target=blank attribute transformation to a tags. It
  14740. * is enabled by HTML.TargetBlank
  14741. */
  14742. class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
  14743. {
  14744. /**
  14745. * @type string
  14746. */
  14747. public $name = 'TargetBlank';
  14748. /**
  14749. * @param HTMLPurifier_Config $config
  14750. */
  14751. public function setup($config)
  14752. {
  14753. $a = $this->addBlankElement('a');
  14754. $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
  14755. }
  14756. }
  14757. /**
  14758. * XHTML 1.1 Text Module, defines basic text containers. Core Module.
  14759. * @note In the normative XML Schema specification, this module
  14760. * is further abstracted into the following modules:
  14761. * - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
  14762. * - Block Structural (div, p)
  14763. * - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
  14764. * - Inline Structural (br, span)
  14765. * This module, functionally, does not distinguish between these
  14766. * sub-modules, but the code is internally structured to reflect
  14767. * these distinctions.
  14768. */
  14769. class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
  14770. {
  14771. /**
  14772. * @type string
  14773. */
  14774. public $name = 'Text';
  14775. /**
  14776. * @type array
  14777. */
  14778. public $content_sets = array(
  14779. 'Flow' => 'Heading | Block | Inline'
  14780. );
  14781. /**
  14782. * @param HTMLPurifier_Config $config
  14783. */
  14784. public function setup($config)
  14785. {
  14786. // Inline Phrasal -------------------------------------------------
  14787. $this->addElement('abbr', 'Inline', 'Inline', 'Common');
  14788. $this->addElement('acronym', 'Inline', 'Inline', 'Common');
  14789. $this->addElement('cite', 'Inline', 'Inline', 'Common');
  14790. $this->addElement('dfn', 'Inline', 'Inline', 'Common');
  14791. $this->addElement('kbd', 'Inline', 'Inline', 'Common');
  14792. $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
  14793. $this->addElement('samp', 'Inline', 'Inline', 'Common');
  14794. $this->addElement('var', 'Inline', 'Inline', 'Common');
  14795. $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
  14796. $em->formatting = true;
  14797. $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
  14798. $strong->formatting = true;
  14799. $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
  14800. $code->formatting = true;
  14801. // Inline Structural ----------------------------------------------
  14802. $this->addElement('span', 'Inline', 'Inline', 'Common');
  14803. $this->addElement('br', 'Inline', 'Empty', 'Core');
  14804. // Block Phrasal --------------------------------------------------
  14805. $this->addElement('address', 'Block', 'Inline', 'Common');
  14806. $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
  14807. $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
  14808. $pre->excludes = $this->makeLookup(
  14809. 'img',
  14810. 'big',
  14811. 'small',
  14812. 'object',
  14813. 'applet',
  14814. 'font',
  14815. 'basefont'
  14816. );
  14817. $this->addElement('h1', 'Heading', 'Inline', 'Common');
  14818. $this->addElement('h2', 'Heading', 'Inline', 'Common');
  14819. $this->addElement('h3', 'Heading', 'Inline', 'Common');
  14820. $this->addElement('h4', 'Heading', 'Inline', 'Common');
  14821. $this->addElement('h5', 'Heading', 'Inline', 'Common');
  14822. $this->addElement('h6', 'Heading', 'Inline', 'Common');
  14823. // Block Structural -----------------------------------------------
  14824. $p = $this->addElement('p', 'Block', 'Inline', 'Common');
  14825. $p->autoclose = array_flip(
  14826. array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
  14827. );
  14828. $this->addElement('div', 'Block', 'Flow', 'Common');
  14829. }
  14830. }
  14831. /**
  14832. * Abstract class for a set of proprietary modules that clean up (tidy)
  14833. * poorly written HTML.
  14834. * @todo Figure out how to protect some of these methods/properties
  14835. */
  14836. class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
  14837. {
  14838. /**
  14839. * List of supported levels.
  14840. * Index zero is a special case "no fixes" level.
  14841. * @type array
  14842. */
  14843. public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
  14844. /**
  14845. * Default level to place all fixes in.
  14846. * Disabled by default.
  14847. * @type string
  14848. */
  14849. public $defaultLevel = null;
  14850. /**
  14851. * Lists of fixes used by getFixesForLevel().
  14852. * Format is:
  14853. * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
  14854. * @type array
  14855. */
  14856. public $fixesForLevel = array(
  14857. 'light' => array(),
  14858. 'medium' => array(),
  14859. 'heavy' => array()
  14860. );
  14861. /**
  14862. * Lazy load constructs the module by determining the necessary
  14863. * fixes to create and then delegating to the populate() function.
  14864. * @param HTMLPurifier_Config $config
  14865. * @todo Wildcard matching and error reporting when an added or
  14866. * subtracted fix has no effect.
  14867. */
  14868. public function setup($config)
  14869. {
  14870. // create fixes, initialize fixesForLevel
  14871. $fixes = $this->makeFixes();
  14872. $this->makeFixesForLevel($fixes);
  14873. // figure out which fixes to use
  14874. $level = $config->get('HTML.TidyLevel');
  14875. $fixes_lookup = $this->getFixesForLevel($level);
  14876. // get custom fix declarations: these need namespace processing
  14877. $add_fixes = $config->get('HTML.TidyAdd');
  14878. $remove_fixes = $config->get('HTML.TidyRemove');
  14879. foreach ($fixes as $name => $fix) {
  14880. // needs to be refactored a little to implement globbing
  14881. if (isset($remove_fixes[$name]) ||
  14882. (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
  14883. unset($fixes[$name]);
  14884. }
  14885. }
  14886. // populate this module with necessary fixes
  14887. $this->populate($fixes);
  14888. }
  14889. /**
  14890. * Retrieves all fixes per a level, returning fixes for that specific
  14891. * level as well as all levels below it.
  14892. * @param string $level level identifier, see $levels for valid values
  14893. * @return array Lookup up table of fixes
  14894. */
  14895. public function getFixesForLevel($level)
  14896. {
  14897. if ($level == $this->levels[0]) {
  14898. return array();
  14899. }
  14900. $activated_levels = array();
  14901. for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
  14902. $activated_levels[] = $this->levels[$i];
  14903. if ($this->levels[$i] == $level) {
  14904. break;
  14905. }
  14906. }
  14907. if ($i == $c) {
  14908. trigger_error(
  14909. 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
  14910. E_USER_WARNING
  14911. );
  14912. return array();
  14913. }
  14914. $ret = array();
  14915. foreach ($activated_levels as $level) {
  14916. foreach ($this->fixesForLevel[$level] as $fix) {
  14917. $ret[$fix] = true;
  14918. }
  14919. }
  14920. return $ret;
  14921. }
  14922. /**
  14923. * Dynamically populates the $fixesForLevel member variable using
  14924. * the fixes array. It may be custom overloaded, used in conjunction
  14925. * with $defaultLevel, or not used at all.
  14926. * @param array $fixes
  14927. */
  14928. public function makeFixesForLevel($fixes)
  14929. {
  14930. if (!isset($this->defaultLevel)) {
  14931. return;
  14932. }
  14933. if (!isset($this->fixesForLevel[$this->defaultLevel])) {
  14934. trigger_error(
  14935. 'Default level ' . $this->defaultLevel . ' does not exist',
  14936. E_USER_ERROR
  14937. );
  14938. return;
  14939. }
  14940. $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
  14941. }
  14942. /**
  14943. * Populates the module with transforms and other special-case code
  14944. * based on a list of fixes passed to it
  14945. * @param array $fixes Lookup table of fixes to activate
  14946. */
  14947. public function populate($fixes)
  14948. {
  14949. foreach ($fixes as $name => $fix) {
  14950. // determine what the fix is for
  14951. list($type, $params) = $this->getFixType($name);
  14952. switch ($type) {
  14953. case 'attr_transform_pre':
  14954. case 'attr_transform_post':
  14955. $attr = $params['attr'];
  14956. if (isset($params['element'])) {
  14957. $element = $params['element'];
  14958. if (empty($this->info[$element])) {
  14959. $e = $this->addBlankElement($element);
  14960. } else {
  14961. $e = $this->info[$element];
  14962. }
  14963. } else {
  14964. $type = "info_$type";
  14965. $e = $this;
  14966. }
  14967. // PHP does some weird parsing when I do
  14968. // $e->$type[$attr], so I have to assign a ref.
  14969. $f =& $e->$type;
  14970. $f[$attr] = $fix;
  14971. break;
  14972. case 'tag_transform':
  14973. $this->info_tag_transform[$params['element']] = $fix;
  14974. break;
  14975. case 'child':
  14976. case 'content_model_type':
  14977. $element = $params['element'];
  14978. if (empty($this->info[$element])) {
  14979. $e = $this->addBlankElement($element);
  14980. } else {
  14981. $e = $this->info[$element];
  14982. }
  14983. $e->$type = $fix;
  14984. break;
  14985. default:
  14986. trigger_error("Fix type $type not supported", E_USER_ERROR);
  14987. break;
  14988. }
  14989. }
  14990. }
  14991. /**
  14992. * Parses a fix name and determines what kind of fix it is, as well
  14993. * as other information defined by the fix
  14994. * @param $name String name of fix
  14995. * @return array(string $fix_type, array $fix_parameters)
  14996. * @note $fix_parameters is type dependant, see populate() for usage
  14997. * of these parameters
  14998. */
  14999. public function getFixType($name)
  15000. {
  15001. // parse it
  15002. $property = $attr = null;
  15003. if (strpos($name, '#') !== false) {
  15004. list($name, $property) = explode('#', $name);
  15005. }
  15006. if (strpos($name, '@') !== false) {
  15007. list($name, $attr) = explode('@', $name);
  15008. }
  15009. // figure out the parameters
  15010. $params = array();
  15011. if ($name !== '') {
  15012. $params['element'] = $name;
  15013. }
  15014. if (!is_null($attr)) {
  15015. $params['attr'] = $attr;
  15016. }
  15017. // special case: attribute transform
  15018. if (!is_null($attr)) {
  15019. if (is_null($property)) {
  15020. $property = 'pre';
  15021. }
  15022. $type = 'attr_transform_' . $property;
  15023. return array($type, $params);
  15024. }
  15025. // special case: tag transform
  15026. if (is_null($property)) {
  15027. return array('tag_transform', $params);
  15028. }
  15029. return array($property, $params);
  15030. }
  15031. /**
  15032. * Defines all fixes the module will perform in a compact
  15033. * associative array of fix name to fix implementation.
  15034. * @return array
  15035. */
  15036. public function makeFixes()
  15037. {
  15038. }
  15039. }
  15040. class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
  15041. {
  15042. /**
  15043. * @type string
  15044. */
  15045. public $name = 'XMLCommonAttributes';
  15046. /**
  15047. * @type array
  15048. */
  15049. public $attr_collections = array(
  15050. 'Lang' => array(
  15051. 'xml:lang' => 'LanguageCode',
  15052. )
  15053. );
  15054. }
  15055. /**
  15056. * Name is deprecated, but allowed in strict doctypes, so onl
  15057. */
  15058. class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
  15059. {
  15060. /**
  15061. * @type string
  15062. */
  15063. public $name = 'Tidy_Name';
  15064. /**
  15065. * @type string
  15066. */
  15067. public $defaultLevel = 'heavy';
  15068. /**
  15069. * @return array
  15070. */
  15071. public function makeFixes()
  15072. {
  15073. $r = array();
  15074. // @name for img, a -----------------------------------------------
  15075. // Technically, it's allowed even on strict, so we allow authors to use
  15076. // it. However, it's deprecated in future versions of XHTML.
  15077. $r['img@name'] =
  15078. $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
  15079. return $r;
  15080. }
  15081. }
  15082. class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
  15083. {
  15084. /**
  15085. * @type string
  15086. */
  15087. public $name = 'Tidy_Proprietary';
  15088. /**
  15089. * @type string
  15090. */
  15091. public $defaultLevel = 'light';
  15092. /**
  15093. * @return array
  15094. */
  15095. public function makeFixes()
  15096. {
  15097. $r = array();
  15098. $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
  15099. $r['td@background'] = new HTMLPurifier_AttrTransform_Background();
  15100. $r['th@background'] = new HTMLPurifier_AttrTransform_Background();
  15101. $r['tr@background'] = new HTMLPurifier_AttrTransform_Background();
  15102. $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
  15103. $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
  15104. $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
  15105. $r['table@height'] = new HTMLPurifier_AttrTransform_Length('height');
  15106. return $r;
  15107. }
  15108. }
  15109. class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
  15110. {
  15111. /**
  15112. * @return array
  15113. */
  15114. public function makeFixes()
  15115. {
  15116. $r = array();
  15117. // == deprecated tag transforms ===================================
  15118. $r['font'] = new HTMLPurifier_TagTransform_Font();
  15119. $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
  15120. $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
  15121. $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
  15122. $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
  15123. $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15124. $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
  15125. // == deprecated attribute transforms =============================
  15126. $r['caption@align'] =
  15127. new HTMLPurifier_AttrTransform_EnumToCSS(
  15128. 'align',
  15129. array(
  15130. // we're following IE's behavior, not Firefox's, due
  15131. // to the fact that no one supports caption-side:right,
  15132. // W3C included (with CSS 2.1). This is a slightly
  15133. // unreasonable attribute!
  15134. 'left' => 'text-align:left;',
  15135. 'right' => 'text-align:right;',
  15136. 'top' => 'caption-side:top;',
  15137. 'bottom' => 'caption-side:bottom;' // not supported by IE
  15138. )
  15139. );
  15140. // @align for img -------------------------------------------------
  15141. $r['img@align'] =
  15142. new HTMLPurifier_AttrTransform_EnumToCSS(
  15143. 'align',
  15144. array(
  15145. 'left' => 'float:left;',
  15146. 'right' => 'float:right;',
  15147. 'top' => 'vertical-align:top;',
  15148. 'middle' => 'vertical-align:middle;',
  15149. 'bottom' => 'vertical-align:baseline;',
  15150. )
  15151. );
  15152. // @align for table -----------------------------------------------
  15153. $r['table@align'] =
  15154. new HTMLPurifier_AttrTransform_EnumToCSS(
  15155. 'align',
  15156. array(
  15157. 'left' => 'float:left;',
  15158. 'center' => 'margin-left:auto;margin-right:auto;',
  15159. 'right' => 'float:right;'
  15160. )
  15161. );
  15162. // @align for hr -----------------------------------------------
  15163. $r['hr@align'] =
  15164. new HTMLPurifier_AttrTransform_EnumToCSS(
  15165. 'align',
  15166. array(
  15167. // we use both text-align and margin because these work
  15168. // for different browsers (IE and Firefox, respectively)
  15169. // and the melange makes for a pretty cross-compatible
  15170. // solution
  15171. 'left' => 'margin-left:0;margin-right:auto;text-align:left;',
  15172. 'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
  15173. 'right' => 'margin-left:auto;margin-right:0;text-align:right;'
  15174. )
  15175. );
  15176. // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
  15177. // {{{
  15178. $align_lookup = array();
  15179. $align_values = array('left', 'right', 'center', 'justify');
  15180. foreach ($align_values as $v) {
  15181. $align_lookup[$v] = "text-align:$v;";
  15182. }
  15183. // }}}
  15184. $r['h1@align'] =
  15185. $r['h2@align'] =
  15186. $r['h3@align'] =
  15187. $r['h4@align'] =
  15188. $r['h5@align'] =
  15189. $r['h6@align'] =
  15190. $r['p@align'] =
  15191. $r['div@align'] =
  15192. new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
  15193. // @bgcolor for table, tr, td, th ---------------------------------
  15194. $r['table@bgcolor'] =
  15195. $r['td@bgcolor'] =
  15196. $r['th@bgcolor'] =
  15197. new HTMLPurifier_AttrTransform_BgColor();
  15198. // @border for img ------------------------------------------------
  15199. $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
  15200. // @clear for br --------------------------------------------------
  15201. $r['br@clear'] =
  15202. new HTMLPurifier_AttrTransform_EnumToCSS(
  15203. 'clear',
  15204. array(
  15205. 'left' => 'clear:left;',
  15206. 'right' => 'clear:right;',
  15207. 'all' => 'clear:both;',
  15208. 'none' => 'clear:none;',
  15209. )
  15210. );
  15211. // @height for td, th ---------------------------------------------
  15212. $r['td@height'] =
  15213. $r['th@height'] =
  15214. new HTMLPurifier_AttrTransform_Length('height');
  15215. // @hspace for img ------------------------------------------------
  15216. $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
  15217. // @noshade for hr ------------------------------------------------
  15218. // this transformation is not precise but often good enough.
  15219. // different browsers use different styles to designate noshade
  15220. $r['hr@noshade'] =
  15221. new HTMLPurifier_AttrTransform_BoolToCSS(
  15222. 'noshade',
  15223. 'color:#808080;background-color:#808080;border:0;'
  15224. );
  15225. // @nowrap for td, th ---------------------------------------------
  15226. $r['td@nowrap'] =
  15227. $r['th@nowrap'] =
  15228. new HTMLPurifier_AttrTransform_BoolToCSS(
  15229. 'nowrap',
  15230. 'white-space:nowrap;'
  15231. );
  15232. // @size for hr --------------------------------------------------
  15233. $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
  15234. // @type for li, ol, ul -------------------------------------------
  15235. // {{{
  15236. $ul_types = array(
  15237. 'disc' => 'list-style-type:disc;',
  15238. 'square' => 'list-style-type:square;',
  15239. 'circle' => 'list-style-type:circle;'
  15240. );
  15241. $ol_types = array(
  15242. '1' => 'list-style-type:decimal;',
  15243. 'i' => 'list-style-type:lower-roman;',
  15244. 'I' => 'list-style-type:upper-roman;',
  15245. 'a' => 'list-style-type:lower-alpha;',
  15246. 'A' => 'list-style-type:upper-alpha;'
  15247. );
  15248. $li_types = $ul_types + $ol_types;
  15249. // }}}
  15250. $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
  15251. $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
  15252. $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
  15253. // @vspace for img ------------------------------------------------
  15254. $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
  15255. // @width for hr, td, th ------------------------------------------
  15256. $r['td@width'] =
  15257. $r['th@width'] =
  15258. $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
  15259. return $r;
  15260. }
  15261. }
  15262. class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15263. {
  15264. /**
  15265. * @type string
  15266. */
  15267. public $name = 'Tidy_Strict';
  15268. /**
  15269. * @type string
  15270. */
  15271. public $defaultLevel = 'light';
  15272. /**
  15273. * @return array
  15274. */
  15275. public function makeFixes()
  15276. {
  15277. $r = parent::makeFixes();
  15278. $r['blockquote#content_model_type'] = 'strictblockquote';
  15279. return $r;
  15280. }
  15281. /**
  15282. * @type bool
  15283. */
  15284. public $defines_child_def = true;
  15285. /**
  15286. * @param HTMLPurifier_ElementDef $def
  15287. * @return HTMLPurifier_ChildDef_StrictBlockquote
  15288. */
  15289. public function getChildDef($def)
  15290. {
  15291. if ($def->content_model_type != 'strictblockquote') {
  15292. return parent::getChildDef($def);
  15293. }
  15294. return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
  15295. }
  15296. }
  15297. class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
  15298. {
  15299. /**
  15300. * @type string
  15301. */
  15302. public $name = 'Tidy_Transitional';
  15303. /**
  15304. * @type string
  15305. */
  15306. public $defaultLevel = 'heavy';
  15307. }
  15308. class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
  15309. {
  15310. /**
  15311. * @type string
  15312. */
  15313. public $name = 'Tidy_XHTML';
  15314. /**
  15315. * @type string
  15316. */
  15317. public $defaultLevel = 'medium';
  15318. /**
  15319. * @return array
  15320. */
  15321. public function makeFixes()
  15322. {
  15323. $r = array();
  15324. $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
  15325. return $r;
  15326. }
  15327. }
  15328. /**
  15329. * Injector that auto paragraphs text in the root node based on
  15330. * double-spacing.
  15331. * @todo Ensure all states are unit tested, including variations as well.
  15332. * @todo Make a graph of the flow control for this Injector.
  15333. */
  15334. class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
  15335. {
  15336. /**
  15337. * @type string
  15338. */
  15339. public $name = 'AutoParagraph';
  15340. /**
  15341. * @type array
  15342. */
  15343. public $needed = array('p');
  15344. /**
  15345. * @return HTMLPurifier_Token_Start
  15346. */
  15347. private function _pStart()
  15348. {
  15349. $par = new HTMLPurifier_Token_Start('p');
  15350. $par->armor['MakeWellFormed_TagClosedError'] = true;
  15351. return $par;
  15352. }
  15353. /**
  15354. * @param HTMLPurifier_Token_Text $token
  15355. */
  15356. public function handleText(&$token)
  15357. {
  15358. $text = $token->data;
  15359. // Does the current parent allow <p> tags?
  15360. if ($this->allowsElement('p')) {
  15361. if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
  15362. // Note that we have differing behavior when dealing with text
  15363. // in the anonymous root node, or a node inside the document.
  15364. // If the text as a double-newline, the treatment is the same;
  15365. // if it doesn't, see the next if-block if you're in the document.
  15366. $i = $nesting = null;
  15367. if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
  15368. // State 1.1: ... ^ (whitespace, then document end)
  15369. // ----
  15370. // This is a degenerate case
  15371. } else {
  15372. if (!$token->is_whitespace || $this->_isInline($current)) {
  15373. // State 1.2: PAR1
  15374. // ----
  15375. // State 1.3: PAR1\n\nPAR2
  15376. // ------------
  15377. // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
  15378. // ------------
  15379. $token = array($this->_pStart());
  15380. $this->_splitText($text, $token);
  15381. } else {
  15382. // State 1.5: \n<hr />
  15383. // --
  15384. }
  15385. }
  15386. } else {
  15387. // State 2: <div>PAR1... (similar to 1.4)
  15388. // ----
  15389. // We're in an element that allows paragraph tags, but we're not
  15390. // sure if we're going to need them.
  15391. if ($this->_pLookAhead()) {
  15392. // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
  15393. // ----
  15394. // Note: This will always be the first child, since any
  15395. // previous inline element would have triggered this very
  15396. // same routine, and found the double newline. One possible
  15397. // exception would be a comment.
  15398. $token = array($this->_pStart(), $token);
  15399. } else {
  15400. // State 2.2.1: <div>PAR1<div>
  15401. // ----
  15402. // State 2.2.2: <div>PAR1<b>PAR1</b></div>
  15403. // ----
  15404. }
  15405. }
  15406. // Is the current parent a <p> tag?
  15407. } elseif (!empty($this->currentNesting) &&
  15408. $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
  15409. // State 3.1: ...<p>PAR1
  15410. // ----
  15411. // State 3.2: ...<p>PAR1\n\nPAR2
  15412. // ------------
  15413. $token = array();
  15414. $this->_splitText($text, $token);
  15415. // Abort!
  15416. } else {
  15417. // State 4.1: ...<b>PAR1
  15418. // ----
  15419. // State 4.2: ...<b>PAR1\n\nPAR2
  15420. // ------------
  15421. }
  15422. }
  15423. /**
  15424. * @param HTMLPurifier_Token $token
  15425. */
  15426. public function handleElement(&$token)
  15427. {
  15428. // We don't have to check if we're already in a <p> tag for block
  15429. // tokens, because the tag would have been autoclosed by MakeWellFormed.
  15430. if ($this->allowsElement('p')) {
  15431. if (!empty($this->currentNesting)) {
  15432. if ($this->_isInline($token)) {
  15433. // State 1: <div>...<b>
  15434. // ---
  15435. // Check if this token is adjacent to the parent token
  15436. // (seek backwards until token isn't whitespace)
  15437. $i = null;
  15438. $this->backward($i, $prev);
  15439. if (!$prev instanceof HTMLPurifier_Token_Start) {
  15440. // Token wasn't adjacent
  15441. if ($prev instanceof HTMLPurifier_Token_Text &&
  15442. substr($prev->data, -2) === "\n\n"
  15443. ) {
  15444. // State 1.1.4: <div><p>PAR1</p>\n\n<b>
  15445. // ---
  15446. // Quite frankly, this should be handled by splitText
  15447. $token = array($this->_pStart(), $token);
  15448. } else {
  15449. // State 1.1.1: <div><p>PAR1</p><b>
  15450. // ---
  15451. // State 1.1.2: <div><br /><b>
  15452. // ---
  15453. // State 1.1.3: <div>PAR<b>
  15454. // ---
  15455. }
  15456. } else {
  15457. // State 1.2.1: <div><b>
  15458. // ---
  15459. // Lookahead to see if <p> is needed.
  15460. if ($this->_pLookAhead()) {
  15461. // State 1.3.1: <div><b>PAR1\n\nPAR2
  15462. // ---
  15463. $token = array($this->_pStart(), $token);
  15464. } else {
  15465. // State 1.3.2: <div><b>PAR1</b></div>
  15466. // ---
  15467. // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
  15468. // ---
  15469. }
  15470. }
  15471. } else {
  15472. // State 2.3: ...<div>
  15473. // -----
  15474. }
  15475. } else {
  15476. if ($this->_isInline($token)) {
  15477. // State 3.1: <b>
  15478. // ---
  15479. // This is where the {p} tag is inserted, not reflected in
  15480. // inputTokens yet, however.
  15481. $token = array($this->_pStart(), $token);
  15482. } else {
  15483. // State 3.2: <div>
  15484. // -----
  15485. }
  15486. $i = null;
  15487. if ($this->backward($i, $prev)) {
  15488. if (!$prev instanceof HTMLPurifier_Token_Text) {
  15489. // State 3.1.1: ...</p>{p}<b>
  15490. // ---
  15491. // State 3.2.1: ...</p><div>
  15492. // -----
  15493. if (!is_array($token)) {
  15494. $token = array($token);
  15495. }
  15496. array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
  15497. } else {
  15498. // State 3.1.2: ...</p>\n\n{p}<b>
  15499. // ---
  15500. // State 3.2.2: ...</p>\n\n<div>
  15501. // -----
  15502. // Note: PAR<ELEM> cannot occur because PAR would have been
  15503. // wrapped in <p> tags.
  15504. }
  15505. }
  15506. }
  15507. } else {
  15508. // State 2.2: <ul><li>
  15509. // ----
  15510. // State 2.4: <p><b>
  15511. // ---
  15512. }
  15513. }
  15514. /**
  15515. * Splits up a text in paragraph tokens and appends them
  15516. * to the result stream that will replace the original
  15517. * @param string $data String text data that will be processed
  15518. * into paragraphs
  15519. * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
  15520. * tags will be appended onto
  15521. */
  15522. private function _splitText($data, &$result)
  15523. {
  15524. $raw_paragraphs = explode("\n\n", $data);
  15525. $paragraphs = array(); // without empty paragraphs
  15526. $needs_start = false;
  15527. $needs_end = false;
  15528. $c = count($raw_paragraphs);
  15529. if ($c == 1) {
  15530. // There were no double-newlines, abort quickly. In theory this
  15531. // should never happen.
  15532. $result[] = new HTMLPurifier_Token_Text($data);
  15533. return;
  15534. }
  15535. for ($i = 0; $i < $c; $i++) {
  15536. $par = $raw_paragraphs[$i];
  15537. if (trim($par) !== '') {
  15538. $paragraphs[] = $par;
  15539. } else {
  15540. if ($i == 0) {
  15541. // Double newline at the front
  15542. if (empty($result)) {
  15543. // The empty result indicates that the AutoParagraph
  15544. // injector did not add any start paragraph tokens.
  15545. // This means that we have been in a paragraph for
  15546. // a while, and the newline means we should start a new one.
  15547. $result[] = new HTMLPurifier_Token_End('p');
  15548. $result[] = new HTMLPurifier_Token_Text("\n\n");
  15549. // However, the start token should only be added if
  15550. // there is more processing to be done (i.e. there are
  15551. // real paragraphs in here). If there are none, the
  15552. // next start paragraph tag will be handled by the
  15553. // next call to the injector
  15554. $needs_start = true;
  15555. } else {
  15556. // We just started a new paragraph!
  15557. // Reinstate a double-newline for presentation's sake, since
  15558. // it was in the source code.
  15559. array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
  15560. }
  15561. } elseif ($i + 1 == $c) {
  15562. // Double newline at the end
  15563. // There should be a trailing </p> when we're finally done.
  15564. $needs_end = true;
  15565. }
  15566. }
  15567. }
  15568. // Check if this was just a giant blob of whitespace. Move this earlier,
  15569. // perhaps?
  15570. if (empty($paragraphs)) {
  15571. return;
  15572. }
  15573. // Add the start tag indicated by \n\n at the beginning of $data
  15574. if ($needs_start) {
  15575. $result[] = $this->_pStart();
  15576. }
  15577. // Append the paragraphs onto the result
  15578. foreach ($paragraphs as $par) {
  15579. $result[] = new HTMLPurifier_Token_Text($par);
  15580. $result[] = new HTMLPurifier_Token_End('p');
  15581. $result[] = new HTMLPurifier_Token_Text("\n\n");
  15582. $result[] = $this->_pStart();
  15583. }
  15584. // Remove trailing start token; Injector will handle this later if
  15585. // it was indeed needed. This prevents from needing to do a lookahead,
  15586. // at the cost of a lookbehind later.
  15587. array_pop($result);
  15588. // If there is no need for an end tag, remove all of it and let
  15589. // MakeWellFormed close it later.
  15590. if (!$needs_end) {
  15591. array_pop($result); // removes \n\n
  15592. array_pop($result); // removes </p>
  15593. }
  15594. }
  15595. /**
  15596. * Returns true if passed token is inline (and, ergo, allowed in
  15597. * paragraph tags)
  15598. * @param HTMLPurifier_Token $token
  15599. * @return bool
  15600. */
  15601. private function _isInline($token)
  15602. {
  15603. return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
  15604. }
  15605. /**
  15606. * Looks ahead in the token list and determines whether or not we need
  15607. * to insert a <p> tag.
  15608. * @return bool
  15609. */
  15610. private function _pLookAhead()
  15611. {
  15612. if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
  15613. $nesting = 1;
  15614. } else {
  15615. $nesting = 0;
  15616. }
  15617. $ok = false;
  15618. $i = null;
  15619. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  15620. $result = $this->_checkNeedsP($current);
  15621. if ($result !== null) {
  15622. $ok = $result;
  15623. break;
  15624. }
  15625. }
  15626. return $ok;
  15627. }
  15628. /**
  15629. * Determines if a particular token requires an earlier inline token
  15630. * to get a paragraph. This should be used with _forwardUntilEndToken
  15631. * @param HTMLPurifier_Token $current
  15632. * @return bool
  15633. */
  15634. private function _checkNeedsP($current)
  15635. {
  15636. if ($current instanceof HTMLPurifier_Token_Start) {
  15637. if (!$this->_isInline($current)) {
  15638. // <div>PAR1<div>
  15639. // ----
  15640. // Terminate early, since we hit a block element
  15641. return false;
  15642. }
  15643. } elseif ($current instanceof HTMLPurifier_Token_Text) {
  15644. if (strpos($current->data, "\n\n") !== false) {
  15645. // <div>PAR1<b>PAR1\n\nPAR2
  15646. // ----
  15647. return true;
  15648. } else {
  15649. // <div>PAR1<b>PAR1...
  15650. // ----
  15651. }
  15652. }
  15653. return null;
  15654. }
  15655. }
  15656. /**
  15657. * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
  15658. */
  15659. class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
  15660. {
  15661. /**
  15662. * @type string
  15663. */
  15664. public $name = 'DisplayLinkURI';
  15665. /**
  15666. * @type array
  15667. */
  15668. public $needed = array('a');
  15669. /**
  15670. * @param $token
  15671. */
  15672. public function handleElement(&$token)
  15673. {
  15674. }
  15675. /**
  15676. * @param HTMLPurifier_Token $token
  15677. */
  15678. public function handleEnd(&$token)
  15679. {
  15680. if (isset($token->start->attr['href'])) {
  15681. $url = $token->start->attr['href'];
  15682. unset($token->start->attr['href']);
  15683. $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
  15684. } else {
  15685. // nothing to display
  15686. }
  15687. }
  15688. }
  15689. /**
  15690. * Injector that converts http, https and ftp text URLs to actual links.
  15691. */
  15692. class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
  15693. {
  15694. /**
  15695. * @type string
  15696. */
  15697. public $name = 'Linkify';
  15698. /**
  15699. * @type array
  15700. */
  15701. public $needed = array('a' => array('href'));
  15702. /**
  15703. * @param HTMLPurifier_Token $token
  15704. */
  15705. public function handleText(&$token)
  15706. {
  15707. if (!$this->allowsElement('a')) {
  15708. return;
  15709. }
  15710. if (strpos($token->data, '://') === false) {
  15711. // our really quick heuristic failed, abort
  15712. // this may not work so well if we want to match things like
  15713. // "google.com", but then again, most people don't
  15714. return;
  15715. }
  15716. // there is/are URL(s). Let's split the string:
  15717. // Note: this regex is extremely permissive
  15718. $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  15719. $token = array();
  15720. // $i = index
  15721. // $c = count
  15722. // $l = is link
  15723. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  15724. if (!$l) {
  15725. if ($bits[$i] === '') {
  15726. continue;
  15727. }
  15728. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  15729. } else {
  15730. $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
  15731. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  15732. $token[] = new HTMLPurifier_Token_End('a');
  15733. }
  15734. }
  15735. }
  15736. }
  15737. /**
  15738. * Injector that converts configuration directive syntax %Namespace.Directive
  15739. * to links
  15740. */
  15741. class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
  15742. {
  15743. /**
  15744. * @type string
  15745. */
  15746. public $name = 'PurifierLinkify';
  15747. /**
  15748. * @type string
  15749. */
  15750. public $docURL;
  15751. /**
  15752. * @type array
  15753. */
  15754. public $needed = array('a' => array('href'));
  15755. /**
  15756. * @param HTMLPurifier_Config $config
  15757. * @param HTMLPurifier_Context $context
  15758. * @return string
  15759. */
  15760. public function prepare($config, $context)
  15761. {
  15762. $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
  15763. return parent::prepare($config, $context);
  15764. }
  15765. /**
  15766. * @param HTMLPurifier_Token $token
  15767. */
  15768. public function handleText(&$token)
  15769. {
  15770. if (!$this->allowsElement('a')) {
  15771. return;
  15772. }
  15773. if (strpos($token->data, '%') === false) {
  15774. return;
  15775. }
  15776. $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
  15777. $token = array();
  15778. // $i = index
  15779. // $c = count
  15780. // $l = is link
  15781. for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
  15782. if (!$l) {
  15783. if ($bits[$i] === '') {
  15784. continue;
  15785. }
  15786. $token[] = new HTMLPurifier_Token_Text($bits[$i]);
  15787. } else {
  15788. $token[] = new HTMLPurifier_Token_Start(
  15789. 'a',
  15790. array('href' => str_replace('%s', $bits[$i], $this->docURL))
  15791. );
  15792. $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
  15793. $token[] = new HTMLPurifier_Token_End('a');
  15794. }
  15795. }
  15796. }
  15797. }
  15798. class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
  15799. {
  15800. /**
  15801. * @type HTMLPurifier_Context
  15802. */
  15803. private $context;
  15804. /**
  15805. * @type HTMLPurifier_Config
  15806. */
  15807. private $config;
  15808. /**
  15809. * @type HTMLPurifier_AttrValidator
  15810. */
  15811. private $attrValidator;
  15812. /**
  15813. * @type bool
  15814. */
  15815. private $removeNbsp;
  15816. /**
  15817. * @type bool
  15818. */
  15819. private $removeNbspExceptions;
  15820. /**
  15821. * Cached contents of %AutoFormat.RemoveEmpty.Predicate
  15822. * @type array
  15823. */
  15824. private $exclude;
  15825. /**
  15826. * @param HTMLPurifier_Config $config
  15827. * @param HTMLPurifier_Context $context
  15828. * @return void
  15829. */
  15830. public function prepare($config, $context)
  15831. {
  15832. parent::prepare($config, $context);
  15833. $this->config = $config;
  15834. $this->context = $context;
  15835. $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
  15836. $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
  15837. $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
  15838. $this->attrValidator = new HTMLPurifier_AttrValidator();
  15839. }
  15840. /**
  15841. * @param HTMLPurifier_Token $token
  15842. */
  15843. public function handleElement(&$token)
  15844. {
  15845. if (!$token instanceof HTMLPurifier_Token_Start) {
  15846. return;
  15847. }
  15848. $next = false;
  15849. $deleted = 1; // the current tag
  15850. for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
  15851. $next = $this->inputZipper->back[$i];
  15852. if ($next instanceof HTMLPurifier_Token_Text) {
  15853. if ($next->is_whitespace) {
  15854. continue;
  15855. }
  15856. if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
  15857. $plain = str_replace("\xC2\xA0", "", $next->data);
  15858. $isWsOrNbsp = $plain === '' || ctype_space($plain);
  15859. if ($isWsOrNbsp) {
  15860. continue;
  15861. }
  15862. }
  15863. }
  15864. break;
  15865. }
  15866. if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
  15867. $this->attrValidator->validateToken($token, $this->config, $this->context);
  15868. $token->armor['ValidateAttributes'] = true;
  15869. if (isset($this->exclude[$token->name])) {
  15870. $r = true;
  15871. foreach ($this->exclude[$token->name] as $elem) {
  15872. if (!isset($token->attr[$elem])) $r = false;
  15873. }
  15874. if ($r) return;
  15875. }
  15876. if (isset($token->attr['id']) || isset($token->attr['name'])) {
  15877. return;
  15878. }
  15879. $token = $deleted + 1;
  15880. for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
  15881. $prev = $this->inputZipper->front[$b];
  15882. if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
  15883. continue;
  15884. }
  15885. break;
  15886. }
  15887. // This is safe because we removed the token that triggered this.
  15888. $this->rewindOffset($b+$deleted);
  15889. return;
  15890. }
  15891. }
  15892. }
  15893. /**
  15894. * Injector that removes spans with no attributes
  15895. */
  15896. class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
  15897. {
  15898. /**
  15899. * @type string
  15900. */
  15901. public $name = 'RemoveSpansWithoutAttributes';
  15902. /**
  15903. * @type array
  15904. */
  15905. public $needed = array('span');
  15906. /**
  15907. * @type HTMLPurifier_AttrValidator
  15908. */
  15909. private $attrValidator;
  15910. /**
  15911. * Used by AttrValidator.
  15912. * @type HTMLPurifier_Config
  15913. */
  15914. private $config;
  15915. /**
  15916. * @type HTMLPurifier_Context
  15917. */
  15918. private $context;
  15919. public function prepare($config, $context)
  15920. {
  15921. $this->attrValidator = new HTMLPurifier_AttrValidator();
  15922. $this->config = $config;
  15923. $this->context = $context;
  15924. return parent::prepare($config, $context);
  15925. }
  15926. /**
  15927. * @param HTMLPurifier_Token $token
  15928. */
  15929. public function handleElement(&$token)
  15930. {
  15931. if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
  15932. return;
  15933. }
  15934. // We need to validate the attributes now since this doesn't normally
  15935. // happen until after MakeWellFormed. If all the attributes are removed
  15936. // the span needs to be removed too.
  15937. $this->attrValidator->validateToken($token, $this->config, $this->context);
  15938. $token->armor['ValidateAttributes'] = true;
  15939. if (!empty($token->attr)) {
  15940. return;
  15941. }
  15942. $nesting = 0;
  15943. while ($this->forwardUntilEndToken($i, $current, $nesting)) {
  15944. }
  15945. if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
  15946. // Mark closing span tag for deletion
  15947. $current->markForDeletion = true;
  15948. // Delete open span tag
  15949. $token = false;
  15950. }
  15951. }
  15952. /**
  15953. * @param HTMLPurifier_Token $token
  15954. */
  15955. public function handleEnd(&$token)
  15956. {
  15957. if ($token->markForDeletion) {
  15958. $token = false;
  15959. }
  15960. }
  15961. }
  15962. /**
  15963. * Adds important param elements to inside of object in order to make
  15964. * things safe.
  15965. */
  15966. class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
  15967. {
  15968. /**
  15969. * @type string
  15970. */
  15971. public $name = 'SafeObject';
  15972. /**
  15973. * @type array
  15974. */
  15975. public $needed = array('object', 'param');
  15976. /**
  15977. * @type array
  15978. */
  15979. protected $objectStack = array();
  15980. /**
  15981. * @type array
  15982. */
  15983. protected $paramStack = array();
  15984. /**
  15985. * Keep this synchronized with AttrTransform/SafeParam.php.
  15986. * @type array
  15987. */
  15988. protected $addParam = array(
  15989. 'allowScriptAccess' => 'never',
  15990. 'allowNetworking' => 'internal',
  15991. );
  15992. /**
  15993. * @type array
  15994. */
  15995. protected $allowedParam = array(
  15996. 'wmode' => true,
  15997. 'movie' => true,
  15998. 'flashvars' => true,
  15999. 'src' => true,
  16000. 'allowFullScreen' => true, // if omitted, assume to be 'false'
  16001. );
  16002. /**
  16003. * @param HTMLPurifier_Config $config
  16004. * @param HTMLPurifier_Context $context
  16005. * @return void
  16006. */
  16007. public function prepare($config, $context)
  16008. {
  16009. parent::prepare($config, $context);
  16010. }
  16011. /**
  16012. * @param HTMLPurifier_Token $token
  16013. */
  16014. public function handleElement(&$token)
  16015. {
  16016. if ($token->name == 'object') {
  16017. $this->objectStack[] = $token;
  16018. $this->paramStack[] = array();
  16019. $new = array($token);
  16020. foreach ($this->addParam as $name => $value) {
  16021. $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
  16022. }
  16023. $token = $new;
  16024. } elseif ($token->name == 'param') {
  16025. $nest = count($this->currentNesting) - 1;
  16026. if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
  16027. $i = count($this->objectStack) - 1;
  16028. if (!isset($token->attr['name'])) {
  16029. $token = false;
  16030. return;
  16031. }
  16032. $n = $token->attr['name'];
  16033. // We need this fix because YouTube doesn't supply a data
  16034. // attribute, which we need if a type is specified. This is
  16035. // *very* Flash specific.
  16036. if (!isset($this->objectStack[$i]->attr['data']) &&
  16037. ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
  16038. ) {
  16039. $this->objectStack[$i]->attr['data'] = $token->attr['value'];
  16040. }
  16041. // Check if the parameter is the correct value but has not
  16042. // already been added
  16043. if (!isset($this->paramStack[$i][$n]) &&
  16044. isset($this->addParam[$n]) &&
  16045. $token->attr['name'] === $this->addParam[$n]) {
  16046. // keep token, and add to param stack
  16047. $this->paramStack[$i][$n] = true;
  16048. } elseif (isset($this->allowedParam[$n])) {
  16049. // keep token, don't do anything to it
  16050. // (could possibly check for duplicates here)
  16051. } else {
  16052. $token = false;
  16053. }
  16054. } else {
  16055. // not directly inside an object, DENY!
  16056. $token = false;
  16057. }
  16058. }
  16059. }
  16060. public function handleEnd(&$token)
  16061. {
  16062. // This is the WRONG way of handling the object and param stacks;
  16063. // we should be inserting them directly on the relevant object tokens
  16064. // so that the global stack handling handles it.
  16065. if ($token->name == 'object') {
  16066. array_pop($this->objectStack);
  16067. array_pop($this->paramStack);
  16068. }
  16069. }
  16070. }
  16071. /**
  16072. * Parser that uses PHP 5's DOM extension (part of the core).
  16073. *
  16074. * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
  16075. * It gives us a forgiving HTML parser, which we use to transform the HTML
  16076. * into a DOM, and then into the tokens. It is blazingly fast (for large
  16077. * documents, it performs twenty times faster than
  16078. * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
  16079. *
  16080. * @note Any empty elements will have empty tokens associated with them, even if
  16081. * this is prohibited by the spec. This is cannot be fixed until the spec
  16082. * comes into play.
  16083. *
  16084. * @note PHP's DOM extension does not actually parse any entities, we use
  16085. * our own function to do that.
  16086. *
  16087. * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
  16088. * If this is a huge problem, due to the fact that HTML is hand
  16089. * edited and you are unable to get a parser cache that caches the
  16090. * the output of HTML Purifier while keeping the original HTML lying
  16091. * around, you may want to run Tidy on the resulting output or use
  16092. * HTMLPurifier_DirectLex
  16093. */
  16094. class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
  16095. {
  16096. /**
  16097. * @type HTMLPurifier_TokenFactory
  16098. */
  16099. private $factory;
  16100. public function __construct()
  16101. {
  16102. // setup the factory
  16103. parent::__construct();
  16104. $this->factory = new HTMLPurifier_TokenFactory();
  16105. }
  16106. /**
  16107. * @param string $html
  16108. * @param HTMLPurifier_Config $config
  16109. * @param HTMLPurifier_Context $context
  16110. * @return HTMLPurifier_Token[]
  16111. */
  16112. public function tokenizeHTML($html, $config, $context)
  16113. {
  16114. $html = $this->normalize($html, $config, $context);
  16115. // attempt to armor stray angled brackets that cannot possibly
  16116. // form tags and thus are probably being used as emoticons
  16117. if ($config->get('Core.AggressivelyFixLt')) {
  16118. $char = '[^a-z!\/]';
  16119. $comment = "/<!--(.*?)(-->|\z)/is";
  16120. $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
  16121. do {
  16122. $old = $html;
  16123. $html = preg_replace("/<($char)/i", '&lt;\\1', $html);
  16124. } while ($html !== $old);
  16125. $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
  16126. }
  16127. // preprocess html, essential for UTF-8
  16128. $html = $this->wrapHTML($html, $config, $context);
  16129. $doc = new DOMDocument();
  16130. $doc->encoding = 'UTF-8'; // theoretically, the above has this covered
  16131. set_error_handler(array($this, 'muteErrorHandler'));
  16132. $doc->loadHTML($html);
  16133. restore_error_handler();
  16134. $tokens = array();
  16135. $this->tokenizeDOM(
  16136. $doc->getElementsByTagName('html')->item(0)-> // <html>
  16137. getElementsByTagName('body')->item(0), // <body>
  16138. $tokens
  16139. );
  16140. return $tokens;
  16141. }
  16142. /**
  16143. * Iterative function that tokenizes a node, putting it into an accumulator.
  16144. * To iterate is human, to recurse divine - L. Peter Deutsch
  16145. * @param DOMNode $node DOMNode to be tokenized.
  16146. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16147. * @return HTMLPurifier_Token of node appended to previously passed tokens.
  16148. */
  16149. protected function tokenizeDOM($node, &$tokens)
  16150. {
  16151. $level = 0;
  16152. $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  16153. $closingNodes = array();
  16154. do {
  16155. while (!$nodes[$level]->isEmpty()) {
  16156. $node = $nodes[$level]->shift(); // FIFO
  16157. $collect = $level > 0 ? true : false;
  16158. $needEndingTag = $this->createStartNode($node, $tokens, $collect);
  16159. if ($needEndingTag) {
  16160. $closingNodes[$level][] = $node;
  16161. }
  16162. if ($node->childNodes && $node->childNodes->length) {
  16163. $level++;
  16164. $nodes[$level] = new HTMLPurifier_Queue();
  16165. foreach ($node->childNodes as $childNode) {
  16166. $nodes[$level]->push($childNode);
  16167. }
  16168. }
  16169. }
  16170. $level--;
  16171. if ($level && isset($closingNodes[$level])) {
  16172. while ($node = array_pop($closingNodes[$level])) {
  16173. $this->createEndNode($node, $tokens);
  16174. }
  16175. }
  16176. } while ($level > 0);
  16177. }
  16178. /**
  16179. * @param DOMNode $node DOMNode to be tokenized.
  16180. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
  16181. * @param bool $collect Says whether or start and close are collected, set to
  16182. * false at first recursion because it's the implicit DIV
  16183. * tag you're dealing with.
  16184. * @return bool if the token needs an endtoken
  16185. * @todo data and tagName properties don't seem to exist in DOMNode?
  16186. */
  16187. protected function createStartNode($node, &$tokens, $collect)
  16188. {
  16189. // intercept non element nodes. WE MUST catch all of them,
  16190. // but we're not getting the character reference nodes because
  16191. // those should have been preprocessed
  16192. if ($node->nodeType === XML_TEXT_NODE) {
  16193. $tokens[] = $this->factory->createText($node->data);
  16194. return false;
  16195. } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
  16196. // undo libxml's special treatment of <script> and <style> tags
  16197. $last = end($tokens);
  16198. $data = $node->data;
  16199. // (note $node->tagname is already normalized)
  16200. if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
  16201. $new_data = trim($data);
  16202. if (substr($new_data, 0, 4) === '<!--') {
  16203. $data = substr($new_data, 4);
  16204. if (substr($data, -3) === '-->') {
  16205. $data = substr($data, 0, -3);
  16206. } else {
  16207. // Highly suspicious! Not sure what to do...
  16208. }
  16209. }
  16210. }
  16211. $tokens[] = $this->factory->createText($this->parseData($data));
  16212. return false;
  16213. } elseif ($node->nodeType === XML_COMMENT_NODE) {
  16214. // this is code is only invoked for comments in script/style in versions
  16215. // of libxml pre-2.6.28 (regular comments, of course, are still
  16216. // handled regularly)
  16217. $tokens[] = $this->factory->createComment($node->data);
  16218. return false;
  16219. } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
  16220. // not-well tested: there may be other nodes we have to grab
  16221. return false;
  16222. }
  16223. $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
  16224. // We still have to make sure that the element actually IS empty
  16225. if (!$node->childNodes->length) {
  16226. if ($collect) {
  16227. $tokens[] = $this->factory->createEmpty($node->tagName, $attr);
  16228. }
  16229. return false;
  16230. } else {
  16231. if ($collect) {
  16232. $tokens[] = $this->factory->createStart(
  16233. $tag_name = $node->tagName, // somehow, it get's dropped
  16234. $attr
  16235. );
  16236. }
  16237. return true;
  16238. }
  16239. }
  16240. /**
  16241. * @param DOMNode $node
  16242. * @param HTMLPurifier_Token[] $tokens
  16243. */
  16244. protected function createEndNode($node, &$tokens)
  16245. {
  16246. $tokens[] = $this->factory->createEnd($node->tagName);
  16247. }
  16248. /**
  16249. * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
  16250. *
  16251. * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
  16252. * @return array Associative array of attributes.
  16253. */
  16254. protected function transformAttrToAssoc($node_map)
  16255. {
  16256. // NamedNodeMap is documented very well, so we're using undocumented
  16257. // features, namely, the fact that it implements Iterator and
  16258. // has a ->length attribute
  16259. if ($node_map->length === 0) {
  16260. return array();
  16261. }
  16262. $array = array();
  16263. foreach ($node_map as $attr) {
  16264. $array[$attr->name] = $attr->value;
  16265. }
  16266. return $array;
  16267. }
  16268. /**
  16269. * An error handler that mutes all errors
  16270. * @param int $errno
  16271. * @param string $errstr
  16272. */
  16273. public function muteErrorHandler($errno, $errstr)
  16274. {
  16275. }
  16276. /**
  16277. * Callback function for undoing escaping of stray angled brackets
  16278. * in comments
  16279. * @param array $matches
  16280. * @return string
  16281. */
  16282. public function callbackUndoCommentSubst($matches)
  16283. {
  16284. return '<!--' . strtr($matches[1], array('&amp;' => '&', '&lt;' => '<')) . $matches[2];
  16285. }
  16286. /**
  16287. * Callback function that entity-izes ampersands in comments so that
  16288. * callbackUndoCommentSubst doesn't clobber them
  16289. * @param array $matches
  16290. * @return string
  16291. */
  16292. public function callbackArmorCommentEntities($matches)
  16293. {
  16294. return '<!--' . str_replace('&', '&amp;', $matches[1]) . $matches[2];
  16295. }
  16296. /**
  16297. * Wraps an HTML fragment in the necessary HTML
  16298. * @param string $html
  16299. * @param HTMLPurifier_Config $config
  16300. * @param HTMLPurifier_Context $context
  16301. * @return string
  16302. */
  16303. protected function wrapHTML($html, $config, $context)
  16304. {
  16305. $def = $config->getDefinition('HTML');
  16306. $ret = '';
  16307. if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
  16308. $ret .= '<!DOCTYPE html ';
  16309. if (!empty($def->doctype->dtdPublic)) {
  16310. $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
  16311. }
  16312. if (!empty($def->doctype->dtdSystem)) {
  16313. $ret .= '"' . $def->doctype->dtdSystem . '" ';
  16314. }
  16315. $ret .= '>';
  16316. }
  16317. $ret .= '<html><head>';
  16318. $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  16319. // No protection if $html contains a stray </div>!
  16320. $ret .= '</head><body>' . $html . '</body></html>';
  16321. return $ret;
  16322. }
  16323. }
  16324. /**
  16325. * Our in-house implementation of a parser.
  16326. *
  16327. * A pure PHP parser, DirectLex has absolutely no dependencies, making
  16328. * it a reasonably good default for PHP4. Written with efficiency in mind,
  16329. * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
  16330. * pales in comparison to HTMLPurifier_Lexer_DOMLex.
  16331. *
  16332. * @todo Reread XML spec and document differences.
  16333. */
  16334. class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
  16335. {
  16336. /**
  16337. * @type bool
  16338. */
  16339. public $tracksLineNumbers = true;
  16340. /**
  16341. * Whitespace characters for str(c)spn.
  16342. * @type string
  16343. */
  16344. protected $_whitespace = "\x20\x09\x0D\x0A";
  16345. /**
  16346. * Callback function for script CDATA fudge
  16347. * @param array $matches, in form of array(opening tag, contents, closing tag)
  16348. * @return string
  16349. */
  16350. protected function scriptCallback($matches)
  16351. {
  16352. return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
  16353. }
  16354. /**
  16355. * @param String $html
  16356. * @param HTMLPurifier_Config $config
  16357. * @param HTMLPurifier_Context $context
  16358. * @return array|HTMLPurifier_Token[]
  16359. */
  16360. public function tokenizeHTML($html, $config, $context)
  16361. {
  16362. // special normalization for script tags without any armor
  16363. // our "armor" heurstic is a < sign any number of whitespaces after
  16364. // the first script tag
  16365. if ($config->get('HTML.Trusted')) {
  16366. $html = preg_replace_callback(
  16367. '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
  16368. array($this, 'scriptCallback'),
  16369. $html
  16370. );
  16371. }
  16372. $html = $this->normalize($html, $config, $context);
  16373. $cursor = 0; // our location in the text
  16374. $inside_tag = false; // whether or not we're parsing the inside of a tag
  16375. $array = array(); // result array
  16376. // This is also treated to mean maintain *column* numbers too
  16377. $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
  16378. if ($maintain_line_numbers === null) {
  16379. // automatically determine line numbering by checking
  16380. // if error collection is on
  16381. $maintain_line_numbers = $config->get('Core.CollectErrors');
  16382. }
  16383. if ($maintain_line_numbers) {
  16384. $current_line = 1;
  16385. $current_col = 0;
  16386. $length = strlen($html);
  16387. } else {
  16388. $current_line = false;
  16389. $current_col = false;
  16390. $length = false;
  16391. }
  16392. $context->register('CurrentLine', $current_line);
  16393. $context->register('CurrentCol', $current_col);
  16394. $nl = "\n";
  16395. // how often to manually recalculate. This will ALWAYS be right,
  16396. // but it's pretty wasteful. Set to 0 to turn off
  16397. $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
  16398. $e = false;
  16399. if ($config->get('Core.CollectErrors')) {
  16400. $e =& $context->get('ErrorCollector');
  16401. }
  16402. // for testing synchronization
  16403. $loops = 0;
  16404. while (++$loops) {
  16405. // $cursor is either at the start of a token, or inside of
  16406. // a tag (i.e. there was a < immediately before it), as indicated
  16407. // by $inside_tag
  16408. if ($maintain_line_numbers) {
  16409. // $rcursor, however, is always at the start of a token.
  16410. $rcursor = $cursor - (int)$inside_tag;
  16411. // Column number is cheap, so we calculate it every round.
  16412. // We're interested at the *end* of the newline string, so
  16413. // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
  16414. // from our "rcursor" position.
  16415. $nl_pos = strrpos($html, $nl, $rcursor - $length);
  16416. $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
  16417. // recalculate lines
  16418. if ($synchronize_interval && // synchronization is on
  16419. $cursor > 0 && // cursor is further than zero
  16420. $loops % $synchronize_interval === 0) { // time to synchronize!
  16421. $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
  16422. }
  16423. }
  16424. $position_next_lt = strpos($html, '<', $cursor);
  16425. $position_next_gt = strpos($html, '>', $cursor);
  16426. // triggers on "<b>asdf</b>" but not "asdf <b></b>"
  16427. // special case to set up context
  16428. if ($position_next_lt === $cursor) {
  16429. $inside_tag = true;
  16430. $cursor++;
  16431. }
  16432. if (!$inside_tag && $position_next_lt !== false) {
  16433. // We are not inside tag and there still is another tag to parse
  16434. $token = new
  16435. HTMLPurifier_Token_Text(
  16436. $this->parseData(
  16437. substr(
  16438. $html,
  16439. $cursor,
  16440. $position_next_lt - $cursor
  16441. )
  16442. )
  16443. );
  16444. if ($maintain_line_numbers) {
  16445. $token->rawPosition($current_line, $current_col);
  16446. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
  16447. }
  16448. $array[] = $token;
  16449. $cursor = $position_next_lt + 1;
  16450. $inside_tag = true;
  16451. continue;
  16452. } elseif (!$inside_tag) {
  16453. // We are not inside tag but there are no more tags
  16454. // If we're already at the end, break
  16455. if ($cursor === strlen($html)) {
  16456. break;
  16457. }
  16458. // Create Text of rest of string
  16459. $token = new
  16460. HTMLPurifier_Token_Text(
  16461. $this->parseData(
  16462. substr(
  16463. $html,
  16464. $cursor
  16465. )
  16466. )
  16467. );
  16468. if ($maintain_line_numbers) {
  16469. $token->rawPosition($current_line, $current_col);
  16470. }
  16471. $array[] = $token;
  16472. break;
  16473. } elseif ($inside_tag && $position_next_gt !== false) {
  16474. // We are in tag and it is well formed
  16475. // Grab the internals of the tag
  16476. $strlen_segment = $position_next_gt - $cursor;
  16477. if ($strlen_segment < 1) {
  16478. // there's nothing to process!
  16479. $token = new HTMLPurifier_Token_Text('<');
  16480. $cursor++;
  16481. continue;
  16482. }
  16483. $segment = substr($html, $cursor, $strlen_segment);
  16484. if ($segment === false) {
  16485. // somehow, we attempted to access beyond the end of
  16486. // the string, defense-in-depth, reported by Nate Abele
  16487. break;
  16488. }
  16489. // Check if it's a comment
  16490. if (substr($segment, 0, 3) === '!--') {
  16491. // re-determine segment length, looking for -->
  16492. $position_comment_end = strpos($html, '-->', $cursor);
  16493. if ($position_comment_end === false) {
  16494. // uh oh, we have a comment that extends to
  16495. // infinity. Can't be helped: set comment
  16496. // end position to end of string
  16497. if ($e) {
  16498. $e->send(E_WARNING, 'Lexer: Unclosed comment');
  16499. }
  16500. $position_comment_end = strlen($html);
  16501. $end = true;
  16502. } else {
  16503. $end = false;
  16504. }
  16505. $strlen_segment = $position_comment_end - $cursor;
  16506. $segment = substr($html, $cursor, $strlen_segment);
  16507. $token = new
  16508. HTMLPurifier_Token_Comment(
  16509. substr(
  16510. $segment,
  16511. 3,
  16512. $strlen_segment - 3
  16513. )
  16514. );
  16515. if ($maintain_line_numbers) {
  16516. $token->rawPosition($current_line, $current_col);
  16517. $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
  16518. }
  16519. $array[] = $token;
  16520. $cursor = $end ? $position_comment_end : $position_comment_end + 3;
  16521. $inside_tag = false;
  16522. continue;
  16523. }
  16524. // Check if it's an end tag
  16525. $is_end_tag = (strpos($segment, '/') === 0);
  16526. if ($is_end_tag) {
  16527. $type = substr($segment, 1);
  16528. $token = new HTMLPurifier_Token_End($type);
  16529. if ($maintain_line_numbers) {
  16530. $token->rawPosition($current_line, $current_col);
  16531. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  16532. }
  16533. $array[] = $token;
  16534. $inside_tag = false;
  16535. $cursor = $position_next_gt + 1;
  16536. continue;
  16537. }
  16538. // Check leading character is alnum, if not, we may
  16539. // have accidently grabbed an emoticon. Translate into
  16540. // text and go our merry way
  16541. if (!ctype_alpha($segment[0])) {
  16542. // XML: $segment[0] !== '_' && $segment[0] !== ':'
  16543. if ($e) {
  16544. $e->send(E_NOTICE, 'Lexer: Unescaped lt');
  16545. }
  16546. $token = new HTMLPurifier_Token_Text('<');
  16547. if ($maintain_line_numbers) {
  16548. $token->rawPosition($current_line, $current_col);
  16549. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  16550. }
  16551. $array[] = $token;
  16552. $inside_tag = false;
  16553. continue;
  16554. }
  16555. // Check if it is explicitly self closing, if so, remove
  16556. // trailing slash. Remember, we could have a tag like <br>, so
  16557. // any later token processing scripts must convert improperly
  16558. // classified EmptyTags from StartTags.
  16559. $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
  16560. if ($is_self_closing) {
  16561. $strlen_segment--;
  16562. $segment = substr($segment, 0, $strlen_segment);
  16563. }
  16564. // Check if there are any attributes
  16565. $position_first_space = strcspn($segment, $this->_whitespace);
  16566. if ($position_first_space >= $strlen_segment) {
  16567. if ($is_self_closing) {
  16568. $token = new HTMLPurifier_Token_Empty($segment);
  16569. } else {
  16570. $token = new HTMLPurifier_Token_Start($segment);
  16571. }
  16572. if ($maintain_line_numbers) {
  16573. $token->rawPosition($current_line, $current_col);
  16574. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  16575. }
  16576. $array[] = $token;
  16577. $inside_tag = false;
  16578. $cursor = $position_next_gt + 1;
  16579. continue;
  16580. }
  16581. // Grab out all the data
  16582. $type = substr($segment, 0, $position_first_space);
  16583. $attribute_string =
  16584. trim(
  16585. substr(
  16586. $segment,
  16587. $position_first_space
  16588. )
  16589. );
  16590. if ($attribute_string) {
  16591. $attr = $this->parseAttributeString(
  16592. $attribute_string,
  16593. $config,
  16594. $context
  16595. );
  16596. } else {
  16597. $attr = array();
  16598. }
  16599. if ($is_self_closing) {
  16600. $token = new HTMLPurifier_Token_Empty($type, $attr);
  16601. } else {
  16602. $token = new HTMLPurifier_Token_Start($type, $attr);
  16603. }
  16604. if ($maintain_line_numbers) {
  16605. $token->rawPosition($current_line, $current_col);
  16606. $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
  16607. }
  16608. $array[] = $token;
  16609. $cursor = $position_next_gt + 1;
  16610. $inside_tag = false;
  16611. continue;
  16612. } else {
  16613. // inside tag, but there's no ending > sign
  16614. if ($e) {
  16615. $e->send(E_WARNING, 'Lexer: Missing gt');
  16616. }
  16617. $token = new
  16618. HTMLPurifier_Token_Text(
  16619. '<' .
  16620. $this->parseData(
  16621. substr($html, $cursor)
  16622. )
  16623. );
  16624. if ($maintain_line_numbers) {
  16625. $token->rawPosition($current_line, $current_col);
  16626. }
  16627. // no cursor scroll? Hmm...
  16628. $array[] = $token;
  16629. break;
  16630. }
  16631. break;
  16632. }
  16633. $context->destroy('CurrentLine');
  16634. $context->destroy('CurrentCol');
  16635. return $array;
  16636. }
  16637. /**
  16638. * PHP 5.0.x compatible substr_count that implements offset and length
  16639. * @param string $haystack
  16640. * @param string $needle
  16641. * @param int $offset
  16642. * @param int $length
  16643. * @return int
  16644. */
  16645. protected function substrCount($haystack, $needle, $offset, $length)
  16646. {
  16647. static $oldVersion;
  16648. if ($oldVersion === null) {
  16649. $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
  16650. }
  16651. if ($oldVersion) {
  16652. $haystack = substr($haystack, $offset, $length);
  16653. return substr_count($haystack, $needle);
  16654. } else {
  16655. return substr_count($haystack, $needle, $offset, $length);
  16656. }
  16657. }
  16658. /**
  16659. * Takes the inside of an HTML tag and makes an assoc array of attributes.
  16660. *
  16661. * @param string $string Inside of tag excluding name.
  16662. * @param HTMLPurifier_Config $config
  16663. * @param HTMLPurifier_Context $context
  16664. * @return array Assoc array of attributes.
  16665. */
  16666. public function parseAttributeString($string, $config, $context)
  16667. {
  16668. $string = (string)$string; // quick typecast
  16669. if ($string == '') {
  16670. return array();
  16671. } // no attributes
  16672. $e = false;
  16673. if ($config->get('Core.CollectErrors')) {
  16674. $e =& $context->get('ErrorCollector');
  16675. }
  16676. // let's see if we can abort as quickly as possible
  16677. // one equal sign, no spaces => one attribute
  16678. $num_equal = substr_count($string, '=');
  16679. $has_space = strpos($string, ' ');
  16680. if ($num_equal === 0 && !$has_space) {
  16681. // bool attribute
  16682. return array($string => $string);
  16683. } elseif ($num_equal === 1 && !$has_space) {
  16684. // only one attribute
  16685. list($key, $quoted_value) = explode('=', $string);
  16686. $quoted_value = trim($quoted_value);
  16687. if (!$key) {
  16688. if ($e) {
  16689. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  16690. }
  16691. return array();
  16692. }
  16693. if (!$quoted_value) {
  16694. return array($key => '');
  16695. }
  16696. $first_char = @$quoted_value[0];
  16697. $last_char = @$quoted_value[strlen($quoted_value) - 1];
  16698. $same_quote = ($first_char == $last_char);
  16699. $open_quote = ($first_char == '"' || $first_char == "'");
  16700. if ($same_quote && $open_quote) {
  16701. // well behaved
  16702. $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
  16703. } else {
  16704. // not well behaved
  16705. if ($open_quote) {
  16706. if ($e) {
  16707. $e->send(E_ERROR, 'Lexer: Missing end quote');
  16708. }
  16709. $value = substr($quoted_value, 1);
  16710. } else {
  16711. $value = $quoted_value;
  16712. }
  16713. }
  16714. if ($value === false) {
  16715. $value = '';
  16716. }
  16717. return array($key => $this->parseData($value));
  16718. }
  16719. // setup loop environment
  16720. $array = array(); // return assoc array of attributes
  16721. $cursor = 0; // current position in string (moves forward)
  16722. $size = strlen($string); // size of the string (stays the same)
  16723. // if we have unquoted attributes, the parser expects a terminating
  16724. // space, so let's guarantee that there's always a terminating space.
  16725. $string .= ' ';
  16726. $old_cursor = -1;
  16727. while ($cursor < $size) {
  16728. if ($old_cursor >= $cursor) {
  16729. throw new Exception("Infinite loop detected");
  16730. }
  16731. $old_cursor = $cursor;
  16732. $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
  16733. // grab the key
  16734. $key_begin = $cursor; //we're currently at the start of the key
  16735. // scroll past all characters that are the key (not whitespace or =)
  16736. $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
  16737. $key_end = $cursor; // now at the end of the key
  16738. $key = substr($string, $key_begin, $key_end - $key_begin);
  16739. if (!$key) {
  16740. if ($e) {
  16741. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  16742. }
  16743. $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
  16744. continue; // empty key
  16745. }
  16746. // scroll past all whitespace
  16747. $cursor += strspn($string, $this->_whitespace, $cursor);
  16748. if ($cursor >= $size) {
  16749. $array[$key] = $key;
  16750. break;
  16751. }
  16752. // if the next character is an equal sign, we've got a regular
  16753. // pair, otherwise, it's a bool attribute
  16754. $first_char = @$string[$cursor];
  16755. if ($first_char == '=') {
  16756. // key="value"
  16757. $cursor++;
  16758. $cursor += strspn($string, $this->_whitespace, $cursor);
  16759. if ($cursor === false) {
  16760. $array[$key] = '';
  16761. break;
  16762. }
  16763. // we might be in front of a quote right now
  16764. $char = @$string[$cursor];
  16765. if ($char == '"' || $char == "'") {
  16766. // it's quoted, end bound is $char
  16767. $cursor++;
  16768. $value_begin = $cursor;
  16769. $cursor = strpos($string, $char, $cursor);
  16770. $value_end = $cursor;
  16771. } else {
  16772. // it's not quoted, end bound is whitespace
  16773. $value_begin = $cursor;
  16774. $cursor += strcspn($string, $this->_whitespace, $cursor);
  16775. $value_end = $cursor;
  16776. }
  16777. // we reached a premature end
  16778. if ($cursor === false) {
  16779. $cursor = $size;
  16780. $value_end = $cursor;
  16781. }
  16782. $value = substr($string, $value_begin, $value_end - $value_begin);
  16783. if ($value === false) {
  16784. $value = '';
  16785. }
  16786. $array[$key] = $this->parseData($value);
  16787. $cursor++;
  16788. } else {
  16789. // boolattr
  16790. if ($key !== '') {
  16791. $array[$key] = $key;
  16792. } else {
  16793. // purely theoretical
  16794. if ($e) {
  16795. $e->send(E_ERROR, 'Lexer: Missing attribute key');
  16796. }
  16797. }
  16798. }
  16799. }
  16800. return $array;
  16801. }
  16802. }
  16803. /**
  16804. * Concrete comment node class.
  16805. */
  16806. class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
  16807. {
  16808. /**
  16809. * Character data within comment.
  16810. * @type string
  16811. */
  16812. public $data;
  16813. /**
  16814. * @type bool
  16815. */
  16816. public $is_whitespace = true;
  16817. /**
  16818. * Transparent constructor.
  16819. *
  16820. * @param string $data String comment data.
  16821. * @param int $line
  16822. * @param int $col
  16823. */
  16824. public function __construct($data, $line = null, $col = null)
  16825. {
  16826. $this->data = $data;
  16827. $this->line = $line;
  16828. $this->col = $col;
  16829. }
  16830. public function toTokenPair() {
  16831. return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
  16832. }
  16833. }
  16834. /**
  16835. * Concrete element node class.
  16836. */
  16837. class HTMLPurifier_Node_Element extends HTMLPurifier_Node
  16838. {
  16839. /**
  16840. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  16841. *
  16842. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  16843. * be lower-casing them, but these tokens cater to HTML tags, which are
  16844. * insensitive.
  16845. * @type string
  16846. */
  16847. public $name;
  16848. /**
  16849. * Associative array of the node's attributes.
  16850. * @type array
  16851. */
  16852. public $attr = array();
  16853. /**
  16854. * List of child elements.
  16855. * @type array
  16856. */
  16857. public $children = array();
  16858. /**
  16859. * Does this use the <a></a> form or the </a> form, i.e.
  16860. * is it a pair of start/end tokens or an empty token.
  16861. * @bool
  16862. */
  16863. public $empty = false;
  16864. public $endCol = null, $endLine = null, $endArmor = array();
  16865. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
  16866. $this->name = $name;
  16867. $this->attr = $attr;
  16868. $this->line = $line;
  16869. $this->col = $col;
  16870. $this->armor = $armor;
  16871. }
  16872. public function toTokenPair() {
  16873. // XXX inefficiency here, normalization is not necessary
  16874. if ($this->empty) {
  16875. return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
  16876. } else {
  16877. $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
  16878. $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
  16879. //$end->start = $start;
  16880. return array($start, $end);
  16881. }
  16882. }
  16883. }
  16884. /**
  16885. * Concrete text token class.
  16886. *
  16887. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  16888. * character data (from the CDATA sections). Internally, their
  16889. * data is parsed with all entities expanded. Surprisingly, the text token
  16890. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  16891. * in permissible child nodes.
  16892. */
  16893. class HTMLPurifier_Node_Text extends HTMLPurifier_Node
  16894. {
  16895. /**
  16896. * PCDATA tag name compatible with DTD, see
  16897. * HTMLPurifier_ChildDef_Custom for details.
  16898. * @type string
  16899. */
  16900. public $name = '#PCDATA';
  16901. /**
  16902. * @type string
  16903. */
  16904. public $data;
  16905. /**< Parsed character data of text. */
  16906. /**
  16907. * @type bool
  16908. */
  16909. public $is_whitespace;
  16910. /**< Bool indicating if node is whitespace. */
  16911. /**
  16912. * Constructor, accepts data and determines if it is whitespace.
  16913. * @param string $data String parsed character data.
  16914. * @param int $line
  16915. * @param int $col
  16916. */
  16917. public function __construct($data, $is_whitespace, $line = null, $col = null)
  16918. {
  16919. $this->data = $data;
  16920. $this->is_whitespace = $is_whitespace;
  16921. $this->line = $line;
  16922. $this->col = $col;
  16923. }
  16924. public function toTokenPair() {
  16925. return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
  16926. }
  16927. }
  16928. /**
  16929. * Composite strategy that runs multiple strategies on tokens.
  16930. */
  16931. abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
  16932. {
  16933. /**
  16934. * List of strategies to run tokens through.
  16935. * @type HTMLPurifier_Strategy[]
  16936. */
  16937. protected $strategies = array();
  16938. /**
  16939. * @param HTMLPurifier_Token[] $tokens
  16940. * @param HTMLPurifier_Config $config
  16941. * @param HTMLPurifier_Context $context
  16942. * @return HTMLPurifier_Token[]
  16943. */
  16944. public function execute($tokens, $config, $context)
  16945. {
  16946. foreach ($this->strategies as $strategy) {
  16947. $tokens = $strategy->execute($tokens, $config, $context);
  16948. }
  16949. return $tokens;
  16950. }
  16951. }
  16952. /**
  16953. * Core strategy composed of the big four strategies.
  16954. */
  16955. class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
  16956. {
  16957. public function __construct()
  16958. {
  16959. $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
  16960. $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
  16961. $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
  16962. $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
  16963. }
  16964. }
  16965. /**
  16966. * Takes a well formed list of tokens and fixes their nesting.
  16967. *
  16968. * HTML elements dictate which elements are allowed to be their children,
  16969. * for example, you can't have a p tag in a span tag. Other elements have
  16970. * much more rigorous definitions: tables, for instance, require a specific
  16971. * order for their elements. There are also constraints not expressible by
  16972. * document type definitions, such as the chameleon nature of ins/del
  16973. * tags and global child exclusions.
  16974. *
  16975. * The first major objective of this strategy is to iterate through all
  16976. * the nodes and determine whether or not their children conform to the
  16977. * element's definition. If they do not, the child definition may
  16978. * optionally supply an amended list of elements that is valid or
  16979. * require that the entire node be deleted (and the previous node
  16980. * rescanned).
  16981. *
  16982. * The second objective is to ensure that explicitly excluded elements of
  16983. * an element do not appear in its children. Code that accomplishes this
  16984. * task is pervasive through the strategy, though the two are distinct tasks
  16985. * and could, theoretically, be seperated (although it's not recommended).
  16986. *
  16987. * @note Whether or not unrecognized children are silently dropped or
  16988. * translated into text depends on the child definitions.
  16989. *
  16990. * @todo Enable nodes to be bubbled out of the structure. This is
  16991. * easier with our new algorithm.
  16992. */
  16993. class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
  16994. {
  16995. /**
  16996. * @param HTMLPurifier_Token[] $tokens
  16997. * @param HTMLPurifier_Config $config
  16998. * @param HTMLPurifier_Context $context
  16999. * @return array|HTMLPurifier_Token[]
  17000. */
  17001. public function execute($tokens, $config, $context)
  17002. {
  17003. //####################################################################//
  17004. // Pre-processing
  17005. // O(n) pass to convert to a tree, so that we can efficiently
  17006. // refer to substrings
  17007. $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
  17008. // get a copy of the HTML definition
  17009. $definition = $config->getHTMLDefinition();
  17010. $excludes_enabled = !$config->get('Core.DisableExcludes');
  17011. // setup the context variable 'IsInline', for chameleon processing
  17012. // is 'false' when we are not inline, 'true' when it must always
  17013. // be inline, and an integer when it is inline for a certain
  17014. // branch of the document tree
  17015. $is_inline = $definition->info_parent_def->descendants_are_inline;
  17016. $context->register('IsInline', $is_inline);
  17017. // setup error collector
  17018. $e =& $context->get('ErrorCollector', true);
  17019. //####################################################################//
  17020. // Loop initialization
  17021. // stack that contains all elements that are excluded
  17022. // it is organized by parent elements, similar to $stack,
  17023. // but it is only populated when an element with exclusions is
  17024. // processed, i.e. there won't be empty exclusions.
  17025. $exclude_stack = array($definition->info_parent_def->excludes);
  17026. // variable that contains the start token while we are processing
  17027. // nodes. This enables error reporting to do its job
  17028. $node = $top_node;
  17029. // dummy token
  17030. list($token, $d) = $node->toTokenPair();
  17031. $context->register('CurrentNode', $node);
  17032. $context->register('CurrentToken', $token);
  17033. //####################################################################//
  17034. // Loop
  17035. // We need to implement a post-order traversal iteratively, to
  17036. // avoid running into stack space limits. This is pretty tricky
  17037. // to reason about, so we just manually stack-ify the recursive
  17038. // variant:
  17039. //
  17040. // function f($node) {
  17041. // foreach ($node->children as $child) {
  17042. // f($child);
  17043. // }
  17044. // validate($node);
  17045. // }
  17046. //
  17047. // Thus, we will represent a stack frame as array($node,
  17048. // $is_inline, stack of children)
  17049. // e.g. array_reverse($node->children) - already processed
  17050. // children.
  17051. $parent_def = $definition->info_parent_def;
  17052. $stack = array(
  17053. array($top_node,
  17054. $parent_def->descendants_are_inline,
  17055. $parent_def->excludes, // exclusions
  17056. 0)
  17057. );
  17058. while (!empty($stack)) {
  17059. list($node, $is_inline, $excludes, $ix) = array_pop($stack);
  17060. // recursive call
  17061. $go = false;
  17062. $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
  17063. while (isset($node->children[$ix])) {
  17064. $child = $node->children[$ix++];
  17065. if ($child instanceof HTMLPurifier_Node_Element) {
  17066. $go = true;
  17067. $stack[] = array($node, $is_inline, $excludes, $ix);
  17068. $stack[] = array($child,
  17069. // ToDo: I don't think it matters if it's def or
  17070. // child_def, but double check this...
  17071. $is_inline || $def->descendants_are_inline,
  17072. empty($def->excludes) ? $excludes
  17073. : array_merge($excludes, $def->excludes),
  17074. 0);
  17075. break;
  17076. }
  17077. };
  17078. if ($go) continue;
  17079. list($token, $d) = $node->toTokenPair();
  17080. // base case
  17081. if ($excludes_enabled && isset($excludes[$node->name])) {
  17082. $node->dead = true;
  17083. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
  17084. } else {
  17085. // XXX I suppose it would be slightly more efficient to
  17086. // avoid the allocation here and have children
  17087. // strategies handle it
  17088. $children = array();
  17089. foreach ($node->children as $child) {
  17090. if (!$child->dead) $children[] = $child;
  17091. }
  17092. $result = $def->child->validateChildren($children, $config, $context);
  17093. if ($result === true) {
  17094. // nop
  17095. $node->children = $children;
  17096. } elseif ($result === false) {
  17097. $node->dead = true;
  17098. if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
  17099. } else {
  17100. $node->children = $result;
  17101. if ($e) {
  17102. // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
  17103. if (empty($result) && !empty($children)) {
  17104. $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
  17105. } else if ($result != $children) {
  17106. $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
  17107. }
  17108. }
  17109. }
  17110. }
  17111. }
  17112. //####################################################################//
  17113. // Post-processing
  17114. // remove context variables
  17115. $context->destroy('IsInline');
  17116. $context->destroy('CurrentNode');
  17117. $context->destroy('CurrentToken');
  17118. //####################################################################//
  17119. // Return
  17120. return HTMLPurifier_Arborize::flatten($node, $config, $context);
  17121. }
  17122. }
  17123. /**
  17124. * Takes tokens makes them well-formed (balance end tags, etc.)
  17125. *
  17126. * Specification of the armor attributes this strategy uses:
  17127. *
  17128. * - MakeWellFormed_TagClosedError: This armor field is used to
  17129. * suppress tag closed errors for certain tokens [TagClosedSuppress],
  17130. * in particular, if a tag was generated automatically by HTML
  17131. * Purifier, we may rely on our infrastructure to close it for us
  17132. * and shouldn't report an error to the user [TagClosedAuto].
  17133. */
  17134. class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
  17135. {
  17136. /**
  17137. * Array stream of tokens being processed.
  17138. * @type HTMLPurifier_Token[]
  17139. */
  17140. protected $tokens;
  17141. /**
  17142. * Current token.
  17143. * @type HTMLPurifier_Token
  17144. */
  17145. protected $token;
  17146. /**
  17147. * Zipper managing the true state.
  17148. * @type HTMLPurifier_Zipper
  17149. */
  17150. protected $zipper;
  17151. /**
  17152. * Current nesting of elements.
  17153. * @type array
  17154. */
  17155. protected $stack;
  17156. /**
  17157. * Injectors active in this stream processing.
  17158. * @type HTMLPurifier_Injector[]
  17159. */
  17160. protected $injectors;
  17161. /**
  17162. * Current instance of HTMLPurifier_Config.
  17163. * @type HTMLPurifier_Config
  17164. */
  17165. protected $config;
  17166. /**
  17167. * Current instance of HTMLPurifier_Context.
  17168. * @type HTMLPurifier_Context
  17169. */
  17170. protected $context;
  17171. /**
  17172. * @param HTMLPurifier_Token[] $tokens
  17173. * @param HTMLPurifier_Config $config
  17174. * @param HTMLPurifier_Context $context
  17175. * @return HTMLPurifier_Token[]
  17176. * @throws HTMLPurifier_Exception
  17177. */
  17178. public function execute($tokens, $config, $context)
  17179. {
  17180. $definition = $config->getHTMLDefinition();
  17181. // local variables
  17182. $generator = new HTMLPurifier_Generator($config, $context);
  17183. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  17184. // used for autoclose early abortion
  17185. $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
  17186. $e = $context->get('ErrorCollector', true);
  17187. $i = false; // injector index
  17188. list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
  17189. if ($token === NULL) {
  17190. return array();
  17191. }
  17192. $reprocess = false; // whether or not to reprocess the same token
  17193. $stack = array();
  17194. // member variables
  17195. $this->stack =& $stack;
  17196. $this->tokens =& $tokens;
  17197. $this->token =& $token;
  17198. $this->zipper =& $zipper;
  17199. $this->config = $config;
  17200. $this->context = $context;
  17201. // context variables
  17202. $context->register('CurrentNesting', $stack);
  17203. $context->register('InputZipper', $zipper);
  17204. $context->register('CurrentToken', $token);
  17205. // -- begin INJECTOR --
  17206. $this->injectors = array();
  17207. $injectors = $config->getBatch('AutoFormat');
  17208. $def_injectors = $definition->info_injector;
  17209. $custom_injectors = $injectors['Custom'];
  17210. unset($injectors['Custom']); // special case
  17211. foreach ($injectors as $injector => $b) {
  17212. // XXX: Fix with a legitimate lookup table of enabled filters
  17213. if (strpos($injector, '.') !== false) {
  17214. continue;
  17215. }
  17216. $injector = "HTMLPurifier_Injector_$injector";
  17217. if (!$b) {
  17218. continue;
  17219. }
  17220. $this->injectors[] = new $injector;
  17221. }
  17222. foreach ($def_injectors as $injector) {
  17223. // assumed to be objects
  17224. $this->injectors[] = $injector;
  17225. }
  17226. foreach ($custom_injectors as $injector) {
  17227. if (!$injector) {
  17228. continue;
  17229. }
  17230. if (is_string($injector)) {
  17231. $injector = "HTMLPurifier_Injector_$injector";
  17232. $injector = new $injector;
  17233. }
  17234. $this->injectors[] = $injector;
  17235. }
  17236. // give the injectors references to the definition and context
  17237. // variables for performance reasons
  17238. foreach ($this->injectors as $ix => $injector) {
  17239. $error = $injector->prepare($config, $context);
  17240. if (!$error) {
  17241. continue;
  17242. }
  17243. array_splice($this->injectors, $ix, 1); // rm the injector
  17244. trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
  17245. }
  17246. // -- end INJECTOR --
  17247. // a note on reprocessing:
  17248. // In order to reduce code duplication, whenever some code needs
  17249. // to make HTML changes in order to make things "correct", the
  17250. // new HTML gets sent through the purifier, regardless of its
  17251. // status. This means that if we add a start token, because it
  17252. // was totally necessary, we don't have to update nesting; we just
  17253. // punt ($reprocess = true; continue;) and it does that for us.
  17254. // isset is in loop because $tokens size changes during loop exec
  17255. for (;;
  17256. // only increment if we don't need to reprocess
  17257. $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
  17258. // check for a rewind
  17259. if (is_int($i)) {
  17260. // possibility: disable rewinding if the current token has a
  17261. // rewind set on it already. This would offer protection from
  17262. // infinite loop, but might hinder some advanced rewinding.
  17263. $rewind_offset = $this->injectors[$i]->getRewindOffset();
  17264. if (is_int($rewind_offset)) {
  17265. for ($j = 0; $j < $rewind_offset; $j++) {
  17266. if (empty($zipper->front)) break;
  17267. $token = $zipper->prev($token);
  17268. // indicate that other injectors should not process this token,
  17269. // but we need to reprocess it
  17270. unset($token->skip[$i]);
  17271. $token->rewind = $i;
  17272. if ($token instanceof HTMLPurifier_Token_Start) {
  17273. array_pop($this->stack);
  17274. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17275. $this->stack[] = $token->start;
  17276. }
  17277. }
  17278. }
  17279. $i = false;
  17280. }
  17281. // handle case of document end
  17282. if ($token === NULL) {
  17283. // kill processing if stack is empty
  17284. if (empty($this->stack)) {
  17285. break;
  17286. }
  17287. // peek
  17288. $top_nesting = array_pop($this->stack);
  17289. $this->stack[] = $top_nesting;
  17290. // send error [TagClosedSuppress]
  17291. if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
  17292. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
  17293. }
  17294. // append, don't splice, since this is the end
  17295. $token = new HTMLPurifier_Token_End($top_nesting->name);
  17296. // punt!
  17297. $reprocess = true;
  17298. continue;
  17299. }
  17300. //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);
  17301. //flush();
  17302. // quick-check: if it's not a tag, no need to process
  17303. if (empty($token->is_tag)) {
  17304. if ($token instanceof HTMLPurifier_Token_Text) {
  17305. foreach ($this->injectors as $i => $injector) {
  17306. if (isset($token->skip[$i])) {
  17307. continue;
  17308. }
  17309. if ($token->rewind !== null && $token->rewind !== $i) {
  17310. continue;
  17311. }
  17312. // XXX fuckup
  17313. $r = $token;
  17314. $injector->handleText($r);
  17315. $token = $this->processToken($r, $i);
  17316. $reprocess = true;
  17317. break;
  17318. }
  17319. }
  17320. // another possibility is a comment
  17321. continue;
  17322. }
  17323. if (isset($definition->info[$token->name])) {
  17324. $type = $definition->info[$token->name]->child->type;
  17325. } else {
  17326. $type = false; // Type is unknown, treat accordingly
  17327. }
  17328. // quick tag checks: anything that's *not* an end tag
  17329. $ok = false;
  17330. if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
  17331. // claims to be a start tag but is empty
  17332. $token = new HTMLPurifier_Token_Empty(
  17333. $token->name,
  17334. $token->attr,
  17335. $token->line,
  17336. $token->col,
  17337. $token->armor
  17338. );
  17339. $ok = true;
  17340. } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
  17341. // claims to be empty but really is a start tag
  17342. // NB: this assignment is required
  17343. $old_token = $token;
  17344. $token = new HTMLPurifier_Token_End($token->name);
  17345. $token = $this->insertBefore(
  17346. new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
  17347. );
  17348. // punt (since we had to modify the input stream in a non-trivial way)
  17349. $reprocess = true;
  17350. continue;
  17351. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  17352. // real empty token
  17353. $ok = true;
  17354. } elseif ($token instanceof HTMLPurifier_Token_Start) {
  17355. // start tag
  17356. // ...unless they also have to close their parent
  17357. if (!empty($this->stack)) {
  17358. // Performance note: you might think that it's rather
  17359. // inefficient, recalculating the autoclose information
  17360. // for every tag that a token closes (since when we
  17361. // do an autoclose, we push a new token into the
  17362. // stream and then /process/ that, before
  17363. // re-processing this token.) But this is
  17364. // necessary, because an injector can make an
  17365. // arbitrary transformations to the autoclosing
  17366. // tokens we introduce, so things may have changed
  17367. // in the meantime. Also, doing the inefficient thing is
  17368. // "easy" to reason about (for certain perverse definitions
  17369. // of "easy")
  17370. $parent = array_pop($this->stack);
  17371. $this->stack[] = $parent;
  17372. $parent_def = null;
  17373. $parent_elements = null;
  17374. $autoclose = false;
  17375. if (isset($definition->info[$parent->name])) {
  17376. $parent_def = $definition->info[$parent->name];
  17377. $parent_elements = $parent_def->child->getAllowedElements($config);
  17378. $autoclose = !isset($parent_elements[$token->name]);
  17379. }
  17380. if ($autoclose && $definition->info[$token->name]->wrap) {
  17381. // Check if an element can be wrapped by another
  17382. // element to make it valid in a context (for
  17383. // example, <ul><ul> needs a <li> in between)
  17384. $wrapname = $definition->info[$token->name]->wrap;
  17385. $wrapdef = $definition->info[$wrapname];
  17386. $elements = $wrapdef->child->getAllowedElements($config);
  17387. if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
  17388. $newtoken = new HTMLPurifier_Token_Start($wrapname);
  17389. $token = $this->insertBefore($newtoken);
  17390. $reprocess = true;
  17391. continue;
  17392. }
  17393. }
  17394. $carryover = false;
  17395. if ($autoclose && $parent_def->formatting) {
  17396. $carryover = true;
  17397. }
  17398. if ($autoclose) {
  17399. // check if this autoclose is doomed to fail
  17400. // (this rechecks $parent, which his harmless)
  17401. $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
  17402. if (!$autoclose_ok) {
  17403. foreach ($this->stack as $ancestor) {
  17404. $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
  17405. if (isset($elements[$token->name])) {
  17406. $autoclose_ok = true;
  17407. break;
  17408. }
  17409. if ($definition->info[$token->name]->wrap) {
  17410. $wrapname = $definition->info[$token->name]->wrap;
  17411. $wrapdef = $definition->info[$wrapname];
  17412. $wrap_elements = $wrapdef->child->getAllowedElements($config);
  17413. if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
  17414. $autoclose_ok = true;
  17415. break;
  17416. }
  17417. }
  17418. }
  17419. }
  17420. if ($autoclose_ok) {
  17421. // errors need to be updated
  17422. $new_token = new HTMLPurifier_Token_End($parent->name);
  17423. $new_token->start = $parent;
  17424. // [TagClosedSuppress]
  17425. if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
  17426. if (!$carryover) {
  17427. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
  17428. } else {
  17429. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
  17430. }
  17431. }
  17432. if ($carryover) {
  17433. $element = clone $parent;
  17434. // [TagClosedAuto]
  17435. $element->armor['MakeWellFormed_TagClosedError'] = true;
  17436. $element->carryover = true;
  17437. $token = $this->processToken(array($new_token, $token, $element));
  17438. } else {
  17439. $token = $this->insertBefore($new_token);
  17440. }
  17441. } else {
  17442. $token = $this->remove();
  17443. }
  17444. $reprocess = true;
  17445. continue;
  17446. }
  17447. }
  17448. $ok = true;
  17449. }
  17450. if ($ok) {
  17451. foreach ($this->injectors as $i => $injector) {
  17452. if (isset($token->skip[$i])) {
  17453. continue;
  17454. }
  17455. if ($token->rewind !== null && $token->rewind !== $i) {
  17456. continue;
  17457. }
  17458. $r = $token;
  17459. $injector->handleElement($r);
  17460. $token = $this->processToken($r, $i);
  17461. $reprocess = true;
  17462. break;
  17463. }
  17464. if (!$reprocess) {
  17465. // ah, nothing interesting happened; do normal processing
  17466. if ($token instanceof HTMLPurifier_Token_Start) {
  17467. $this->stack[] = $token;
  17468. } elseif ($token instanceof HTMLPurifier_Token_End) {
  17469. throw new HTMLPurifier_Exception(
  17470. 'Improper handling of end tag in start code; possible error in MakeWellFormed'
  17471. );
  17472. }
  17473. }
  17474. continue;
  17475. }
  17476. // sanity check: we should be dealing with a closing tag
  17477. if (!$token instanceof HTMLPurifier_Token_End) {
  17478. throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
  17479. }
  17480. // make sure that we have something open
  17481. if (empty($this->stack)) {
  17482. if ($escape_invalid_tags) {
  17483. if ($e) {
  17484. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
  17485. }
  17486. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  17487. } else {
  17488. if ($e) {
  17489. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
  17490. }
  17491. $token = $this->remove();
  17492. }
  17493. $reprocess = true;
  17494. continue;
  17495. }
  17496. // first, check for the simplest case: everything closes neatly.
  17497. // Eventually, everything passes through here; if there are problems
  17498. // we modify the input stream accordingly and then punt, so that
  17499. // the tokens get processed again.
  17500. $current_parent = array_pop($this->stack);
  17501. if ($current_parent->name == $token->name) {
  17502. $token->start = $current_parent;
  17503. foreach ($this->injectors as $i => $injector) {
  17504. if (isset($token->skip[$i])) {
  17505. continue;
  17506. }
  17507. if ($token->rewind !== null && $token->rewind !== $i) {
  17508. continue;
  17509. }
  17510. $r = $token;
  17511. $injector->handleEnd($r);
  17512. $token = $this->processToken($r, $i);
  17513. $this->stack[] = $current_parent;
  17514. $reprocess = true;
  17515. break;
  17516. }
  17517. continue;
  17518. }
  17519. // okay, so we're trying to close the wrong tag
  17520. // undo the pop previous pop
  17521. $this->stack[] = $current_parent;
  17522. // scroll back the entire nest, trying to find our tag.
  17523. // (feature could be to specify how far you'd like to go)
  17524. $size = count($this->stack);
  17525. // -2 because -1 is the last element, but we already checked that
  17526. $skipped_tags = false;
  17527. for ($j = $size - 2; $j >= 0; $j--) {
  17528. if ($this->stack[$j]->name == $token->name) {
  17529. $skipped_tags = array_slice($this->stack, $j);
  17530. break;
  17531. }
  17532. }
  17533. // we didn't find the tag, so remove
  17534. if ($skipped_tags === false) {
  17535. if ($escape_invalid_tags) {
  17536. if ($e) {
  17537. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
  17538. }
  17539. $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
  17540. } else {
  17541. if ($e) {
  17542. $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
  17543. }
  17544. $token = $this->remove();
  17545. }
  17546. $reprocess = true;
  17547. continue;
  17548. }
  17549. // do errors, in REVERSE $j order: a,b,c with </a></b></c>
  17550. $c = count($skipped_tags);
  17551. if ($e) {
  17552. for ($j = $c - 1; $j > 0; $j--) {
  17553. // notice we exclude $j == 0, i.e. the current ending tag, from
  17554. // the errors... [TagClosedSuppress]
  17555. if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
  17556. $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
  17557. }
  17558. }
  17559. }
  17560. // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
  17561. $replace = array($token);
  17562. for ($j = 1; $j < $c; $j++) {
  17563. // ...as well as from the insertions
  17564. $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
  17565. $new_token->start = $skipped_tags[$j];
  17566. array_unshift($replace, $new_token);
  17567. if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
  17568. // [TagClosedAuto]
  17569. $element = clone $skipped_tags[$j];
  17570. $element->carryover = true;
  17571. $element->armor['MakeWellFormed_TagClosedError'] = true;
  17572. $replace[] = $element;
  17573. }
  17574. }
  17575. $token = $this->processToken($replace);
  17576. $reprocess = true;
  17577. continue;
  17578. }
  17579. $context->destroy('CurrentToken');
  17580. $context->destroy('CurrentNesting');
  17581. $context->destroy('InputZipper');
  17582. unset($this->injectors, $this->stack, $this->tokens);
  17583. return $zipper->toArray($token);
  17584. }
  17585. /**
  17586. * Processes arbitrary token values for complicated substitution patterns.
  17587. * In general:
  17588. *
  17589. * If $token is an array, it is a list of tokens to substitute for the
  17590. * current token. These tokens then get individually processed. If there
  17591. * is a leading integer in the list, that integer determines how many
  17592. * tokens from the stream should be removed.
  17593. *
  17594. * If $token is a regular token, it is swapped with the current token.
  17595. *
  17596. * If $token is false, the current token is deleted.
  17597. *
  17598. * If $token is an integer, that number of tokens (with the first token
  17599. * being the current one) will be deleted.
  17600. *
  17601. * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
  17602. * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
  17603. * this is not an injector related operation.
  17604. * @throws HTMLPurifier_Exception
  17605. */
  17606. protected function processToken($token, $injector = -1)
  17607. {
  17608. // normalize forms of token
  17609. if (is_object($token)) {
  17610. $token = array(1, $token);
  17611. }
  17612. if (is_int($token)) {
  17613. $token = array($token);
  17614. }
  17615. if ($token === false) {
  17616. $token = array(1);
  17617. }
  17618. if (!is_array($token)) {
  17619. throw new HTMLPurifier_Exception('Invalid token type from injector');
  17620. }
  17621. if (!is_int($token[0])) {
  17622. array_unshift($token, 1);
  17623. }
  17624. if ($token[0] === 0) {
  17625. throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
  17626. }
  17627. // $token is now an array with the following form:
  17628. // array(number nodes to delete, new node 1, new node 2, ...)
  17629. $delete = array_shift($token);
  17630. list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
  17631. if ($injector > -1) {
  17632. // determine appropriate skips
  17633. $oldskip = isset($old[0]) ? $old[0]->skip : array();
  17634. foreach ($token as $object) {
  17635. $object->skip = $oldskip;
  17636. $object->skip[$injector] = true;
  17637. }
  17638. }
  17639. return $r;
  17640. }
  17641. /**
  17642. * Inserts a token before the current token. Cursor now points to
  17643. * this token. You must reprocess after this.
  17644. * @param HTMLPurifier_Token $token
  17645. */
  17646. private function insertBefore($token)
  17647. {
  17648. // NB not $this->zipper->insertBefore(), due to positioning
  17649. // differences
  17650. $splice = $this->zipper->splice($this->token, 0, array($token));
  17651. return $splice[1];
  17652. }
  17653. /**
  17654. * Removes current token. Cursor now points to new token occupying previously
  17655. * occupied space. You must reprocess after this.
  17656. */
  17657. private function remove()
  17658. {
  17659. return $this->zipper->delete();
  17660. }
  17661. }
  17662. /**
  17663. * Removes all unrecognized tags from the list of tokens.
  17664. *
  17665. * This strategy iterates through all the tokens and removes unrecognized
  17666. * tokens. If a token is not recognized but a TagTransform is defined for
  17667. * that element, the element will be transformed accordingly.
  17668. */
  17669. class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
  17670. {
  17671. /**
  17672. * @param HTMLPurifier_Token[] $tokens
  17673. * @param HTMLPurifier_Config $config
  17674. * @param HTMLPurifier_Context $context
  17675. * @return array|HTMLPurifier_Token[]
  17676. */
  17677. public function execute($tokens, $config, $context)
  17678. {
  17679. $definition = $config->getHTMLDefinition();
  17680. $generator = new HTMLPurifier_Generator($config, $context);
  17681. $result = array();
  17682. $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
  17683. $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
  17684. // currently only used to determine if comments should be kept
  17685. $trusted = $config->get('HTML.Trusted');
  17686. $comment_lookup = $config->get('HTML.AllowedComments');
  17687. $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
  17688. $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
  17689. $remove_script_contents = $config->get('Core.RemoveScriptContents');
  17690. $hidden_elements = $config->get('Core.HiddenElements');
  17691. // remove script contents compatibility
  17692. if ($remove_script_contents === true) {
  17693. $hidden_elements['script'] = true;
  17694. } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
  17695. unset($hidden_elements['script']);
  17696. }
  17697. $attr_validator = new HTMLPurifier_AttrValidator();
  17698. // removes tokens until it reaches a closing tag with its value
  17699. $remove_until = false;
  17700. // converts comments into text tokens when this is equal to a tag name
  17701. $textify_comments = false;
  17702. $token = false;
  17703. $context->register('CurrentToken', $token);
  17704. $e = false;
  17705. if ($config->get('Core.CollectErrors')) {
  17706. $e =& $context->get('ErrorCollector');
  17707. }
  17708. foreach ($tokens as $token) {
  17709. if ($remove_until) {
  17710. if (empty($token->is_tag) || $token->name !== $remove_until) {
  17711. continue;
  17712. }
  17713. }
  17714. if (!empty($token->is_tag)) {
  17715. // DEFINITION CALL
  17716. // before any processing, try to transform the element
  17717. if (isset($definition->info_tag_transform[$token->name])) {
  17718. $original_name = $token->name;
  17719. // there is a transformation for this tag
  17720. // DEFINITION CALL
  17721. $token = $definition->
  17722. info_tag_transform[$token->name]->transform($token, $config, $context);
  17723. if ($e) {
  17724. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
  17725. }
  17726. }
  17727. if (isset($definition->info[$token->name])) {
  17728. // mostly everything's good, but
  17729. // we need to make sure required attributes are in order
  17730. if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
  17731. $definition->info[$token->name]->required_attr &&
  17732. ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
  17733. ) {
  17734. $attr_validator->validateToken($token, $config, $context);
  17735. $ok = true;
  17736. foreach ($definition->info[$token->name]->required_attr as $name) {
  17737. if (!isset($token->attr[$name])) {
  17738. $ok = false;
  17739. break;
  17740. }
  17741. }
  17742. if (!$ok) {
  17743. if ($e) {
  17744. $e->send(
  17745. E_ERROR,
  17746. 'Strategy_RemoveForeignElements: Missing required attribute',
  17747. $name
  17748. );
  17749. }
  17750. continue;
  17751. }
  17752. $token->armor['ValidateAttributes'] = true;
  17753. }
  17754. if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
  17755. $textify_comments = $token->name;
  17756. } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
  17757. $textify_comments = false;
  17758. }
  17759. } elseif ($escape_invalid_tags) {
  17760. // invalid tag, generate HTML representation and insert in
  17761. if ($e) {
  17762. $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
  17763. }
  17764. $token = new HTMLPurifier_Token_Text(
  17765. $generator->generateFromToken($token)
  17766. );
  17767. } else {
  17768. // check if we need to destroy all of the tag's children
  17769. // CAN BE GENERICIZED
  17770. if (isset($hidden_elements[$token->name])) {
  17771. if ($token instanceof HTMLPurifier_Token_Start) {
  17772. $remove_until = $token->name;
  17773. } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  17774. // do nothing: we're still looking
  17775. } else {
  17776. $remove_until = false;
  17777. }
  17778. if ($e) {
  17779. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
  17780. }
  17781. } else {
  17782. if ($e) {
  17783. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
  17784. }
  17785. }
  17786. continue;
  17787. }
  17788. } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  17789. // textify comments in script tags when they are allowed
  17790. if ($textify_comments !== false) {
  17791. $data = $token->data;
  17792. $token = new HTMLPurifier_Token_Text($data);
  17793. } elseif ($trusted || $check_comments) {
  17794. // always cleanup comments
  17795. $trailing_hyphen = false;
  17796. if ($e) {
  17797. // perform check whether or not there's a trailing hyphen
  17798. if (substr($token->data, -1) == '-') {
  17799. $trailing_hyphen = true;
  17800. }
  17801. }
  17802. $token->data = rtrim($token->data, '-');
  17803. $found_double_hyphen = false;
  17804. while (strpos($token->data, '--') !== false) {
  17805. $found_double_hyphen = true;
  17806. $token->data = str_replace('--', '-', $token->data);
  17807. }
  17808. if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
  17809. ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
  17810. // OK good
  17811. if ($e) {
  17812. if ($trailing_hyphen) {
  17813. $e->send(
  17814. E_NOTICE,
  17815. 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
  17816. );
  17817. }
  17818. if ($found_double_hyphen) {
  17819. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
  17820. }
  17821. }
  17822. } else {
  17823. if ($e) {
  17824. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  17825. }
  17826. continue;
  17827. }
  17828. } else {
  17829. // strip comments
  17830. if ($e) {
  17831. $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
  17832. }
  17833. continue;
  17834. }
  17835. } elseif ($token instanceof HTMLPurifier_Token_Text) {
  17836. } else {
  17837. continue;
  17838. }
  17839. $result[] = $token;
  17840. }
  17841. if ($remove_until && $e) {
  17842. // we removed tokens until the end, throw error
  17843. $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
  17844. }
  17845. $context->destroy('CurrentToken');
  17846. return $result;
  17847. }
  17848. }
  17849. /**
  17850. * Validate all attributes in the tokens.
  17851. */
  17852. class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
  17853. {
  17854. /**
  17855. * @param HTMLPurifier_Token[] $tokens
  17856. * @param HTMLPurifier_Config $config
  17857. * @param HTMLPurifier_Context $context
  17858. * @return HTMLPurifier_Token[]
  17859. */
  17860. public function execute($tokens, $config, $context)
  17861. {
  17862. // setup validator
  17863. $validator = new HTMLPurifier_AttrValidator();
  17864. $token = false;
  17865. $context->register('CurrentToken', $token);
  17866. foreach ($tokens as $key => $token) {
  17867. // only process tokens that have attributes,
  17868. // namely start and empty tags
  17869. if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
  17870. continue;
  17871. }
  17872. // skip tokens that are armored
  17873. if (!empty($token->armor['ValidateAttributes'])) {
  17874. continue;
  17875. }
  17876. // note that we have no facilities here for removing tokens
  17877. $validator->validateToken($token, $config, $context);
  17878. }
  17879. $context->destroy('CurrentToken');
  17880. return $tokens;
  17881. }
  17882. }
  17883. /**
  17884. * Transforms FONT tags to the proper form (SPAN with CSS styling)
  17885. *
  17886. * This transformation takes the three proprietary attributes of FONT and
  17887. * transforms them into their corresponding CSS attributes. These are color,
  17888. * face, and size.
  17889. *
  17890. * @note Size is an interesting case because it doesn't map cleanly to CSS.
  17891. * Thanks to
  17892. * http://style.cleverchimp.com/font_size_intervals/altintervals.html
  17893. * for reasonable mappings.
  17894. * @warning This doesn't work completely correctly; specifically, this
  17895. * TagTransform operates before well-formedness is enforced, so
  17896. * the "active formatting elements" algorithm doesn't get applied.
  17897. */
  17898. class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
  17899. {
  17900. /**
  17901. * @type string
  17902. */
  17903. public $transform_to = 'span';
  17904. /**
  17905. * @type array
  17906. */
  17907. protected $_size_lookup = array(
  17908. '0' => 'xx-small',
  17909. '1' => 'xx-small',
  17910. '2' => 'small',
  17911. '3' => 'medium',
  17912. '4' => 'large',
  17913. '5' => 'x-large',
  17914. '6' => 'xx-large',
  17915. '7' => '300%',
  17916. '-1' => 'smaller',
  17917. '-2' => '60%',
  17918. '+1' => 'larger',
  17919. '+2' => '150%',
  17920. '+3' => '200%',
  17921. '+4' => '300%'
  17922. );
  17923. /**
  17924. * @param HTMLPurifier_Token_Tag $tag
  17925. * @param HTMLPurifier_Config $config
  17926. * @param HTMLPurifier_Context $context
  17927. * @return HTMLPurifier_Token_End|string
  17928. */
  17929. public function transform($tag, $config, $context)
  17930. {
  17931. if ($tag instanceof HTMLPurifier_Token_End) {
  17932. $new_tag = clone $tag;
  17933. $new_tag->name = $this->transform_to;
  17934. return $new_tag;
  17935. }
  17936. $attr = $tag->attr;
  17937. $prepend_style = '';
  17938. // handle color transform
  17939. if (isset($attr['color'])) {
  17940. $prepend_style .= 'color:' . $attr['color'] . ';';
  17941. unset($attr['color']);
  17942. }
  17943. // handle face transform
  17944. if (isset($attr['face'])) {
  17945. $prepend_style .= 'font-family:' . $attr['face'] . ';';
  17946. unset($attr['face']);
  17947. }
  17948. // handle size transform
  17949. if (isset($attr['size'])) {
  17950. // normalize large numbers
  17951. if ($attr['size'] !== '') {
  17952. if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
  17953. $size = (int)$attr['size'];
  17954. if ($size < -2) {
  17955. $attr['size'] = '-2';
  17956. }
  17957. if ($size > 4) {
  17958. $attr['size'] = '+4';
  17959. }
  17960. } else {
  17961. $size = (int)$attr['size'];
  17962. if ($size > 7) {
  17963. $attr['size'] = '7';
  17964. }
  17965. }
  17966. }
  17967. if (isset($this->_size_lookup[$attr['size']])) {
  17968. $prepend_style .= 'font-size:' .
  17969. $this->_size_lookup[$attr['size']] . ';';
  17970. }
  17971. unset($attr['size']);
  17972. }
  17973. if ($prepend_style) {
  17974. $attr['style'] = isset($attr['style']) ?
  17975. $prepend_style . $attr['style'] :
  17976. $prepend_style;
  17977. }
  17978. $new_tag = clone $tag;
  17979. $new_tag->name = $this->transform_to;
  17980. $new_tag->attr = $attr;
  17981. return $new_tag;
  17982. }
  17983. }
  17984. /**
  17985. * Simple transformation, just change tag name to something else,
  17986. * and possibly add some styling. This will cover most of the deprecated
  17987. * tag cases.
  17988. */
  17989. class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
  17990. {
  17991. /**
  17992. * @type string
  17993. */
  17994. protected $style;
  17995. /**
  17996. * @param string $transform_to Tag name to transform to.
  17997. * @param string $style CSS style to add to the tag
  17998. */
  17999. public function __construct($transform_to, $style = null)
  18000. {
  18001. $this->transform_to = $transform_to;
  18002. $this->style = $style;
  18003. }
  18004. /**
  18005. * @param HTMLPurifier_Token_Tag $tag
  18006. * @param HTMLPurifier_Config $config
  18007. * @param HTMLPurifier_Context $context
  18008. * @return string
  18009. */
  18010. public function transform($tag, $config, $context)
  18011. {
  18012. $new_tag = clone $tag;
  18013. $new_tag->name = $this->transform_to;
  18014. if (!is_null($this->style) &&
  18015. ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
  18016. ) {
  18017. $this->prependCSS($new_tag->attr, $this->style);
  18018. }
  18019. return $new_tag;
  18020. }
  18021. }
  18022. /**
  18023. * Concrete comment token class. Generally will be ignored.
  18024. */
  18025. class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
  18026. {
  18027. /**
  18028. * Character data within comment.
  18029. * @type string
  18030. */
  18031. public $data;
  18032. /**
  18033. * @type bool
  18034. */
  18035. public $is_whitespace = true;
  18036. /**
  18037. * Transparent constructor.
  18038. *
  18039. * @param string $data String comment data.
  18040. * @param int $line
  18041. * @param int $col
  18042. */
  18043. public function __construct($data, $line = null, $col = null)
  18044. {
  18045. $this->data = $data;
  18046. $this->line = $line;
  18047. $this->col = $col;
  18048. }
  18049. public function toNode() {
  18050. return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
  18051. }
  18052. }
  18053. /**
  18054. * Abstract class of a tag token (start, end or empty), and its behavior.
  18055. */
  18056. abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
  18057. {
  18058. /**
  18059. * Static bool marker that indicates the class is a tag.
  18060. *
  18061. * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
  18062. * without having to use a function call <tt>is_a()</tt>.
  18063. * @type bool
  18064. */
  18065. public $is_tag = true;
  18066. /**
  18067. * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
  18068. *
  18069. * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
  18070. * be lower-casing them, but these tokens cater to HTML tags, which are
  18071. * insensitive.
  18072. * @type string
  18073. */
  18074. public $name;
  18075. /**
  18076. * Associative array of the tag's attributes.
  18077. * @type array
  18078. */
  18079. public $attr = array();
  18080. /**
  18081. * Non-overloaded constructor, which lower-cases passed tag name.
  18082. *
  18083. * @param string $name String name.
  18084. * @param array $attr Associative array of attributes.
  18085. * @param int $line
  18086. * @param int $col
  18087. * @param array $armor
  18088. */
  18089. public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
  18090. {
  18091. $this->name = ctype_lower($name) ? $name : strtolower($name);
  18092. foreach ($attr as $key => $value) {
  18093. // normalization only necessary when key is not lowercase
  18094. if (!ctype_lower($key)) {
  18095. $new_key = strtolower($key);
  18096. if (!isset($attr[$new_key])) {
  18097. $attr[$new_key] = $attr[$key];
  18098. }
  18099. if ($new_key !== $key) {
  18100. unset($attr[$key]);
  18101. }
  18102. }
  18103. }
  18104. $this->attr = $attr;
  18105. $this->line = $line;
  18106. $this->col = $col;
  18107. $this->armor = $armor;
  18108. }
  18109. public function toNode() {
  18110. return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
  18111. }
  18112. }
  18113. /**
  18114. * Concrete empty token class.
  18115. */
  18116. class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
  18117. {
  18118. public function toNode() {
  18119. $n = parent::toNode();
  18120. $n->empty = true;
  18121. return $n;
  18122. }
  18123. }
  18124. /**
  18125. * Concrete end token class.
  18126. *
  18127. * @warning This class accepts attributes even though end tags cannot. This
  18128. * is for optimization reasons, as under normal circumstances, the Lexers
  18129. * do not pass attributes.
  18130. */
  18131. class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
  18132. {
  18133. /**
  18134. * Token that started this node.
  18135. * Added by MakeWellFormed. Please do not edit this!
  18136. * @type HTMLPurifier_Token
  18137. */
  18138. public $start;
  18139. public function toNode() {
  18140. throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
  18141. }
  18142. }
  18143. /**
  18144. * Concrete start token class.
  18145. */
  18146. class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
  18147. {
  18148. }
  18149. /**
  18150. * Concrete text token class.
  18151. *
  18152. * Text tokens comprise of regular parsed character data (PCDATA) and raw
  18153. * character data (from the CDATA sections). Internally, their
  18154. * data is parsed with all entities expanded. Surprisingly, the text token
  18155. * does have a "tag name" called #PCDATA, which is how the DTD represents it
  18156. * in permissible child nodes.
  18157. */
  18158. class HTMLPurifier_Token_Text extends HTMLPurifier_Token
  18159. {
  18160. /**
  18161. * @type string
  18162. */
  18163. public $name = '#PCDATA';
  18164. /**< PCDATA tag name compatible with DTD. */
  18165. /**
  18166. * @type string
  18167. */
  18168. public $data;
  18169. /**< Parsed character data of text. */
  18170. /**
  18171. * @type bool
  18172. */
  18173. public $is_whitespace;
  18174. /**< Bool indicating if node is whitespace. */
  18175. /**
  18176. * Constructor, accepts data and determines if it is whitespace.
  18177. * @param string $data String parsed character data.
  18178. * @param int $line
  18179. * @param int $col
  18180. */
  18181. public function __construct($data, $line = null, $col = null)
  18182. {
  18183. $this->data = $data;
  18184. $this->is_whitespace = ctype_space($data);
  18185. $this->line = $line;
  18186. $this->col = $col;
  18187. }
  18188. public function toNode() {
  18189. return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
  18190. }
  18191. }
  18192. class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
  18193. {
  18194. /**
  18195. * @type string
  18196. */
  18197. public $name = 'DisableExternal';
  18198. /**
  18199. * @type array
  18200. */
  18201. protected $ourHostParts = false;
  18202. /**
  18203. * @param HTMLPurifier_Config $config
  18204. * @return void
  18205. */
  18206. public function prepare($config)
  18207. {
  18208. $our_host = $config->getDefinition('URI')->host;
  18209. if ($our_host !== null) {
  18210. $this->ourHostParts = array_reverse(explode('.', $our_host));
  18211. }
  18212. }
  18213. /**
  18214. * @param HTMLPurifier_URI $uri Reference
  18215. * @param HTMLPurifier_Config $config
  18216. * @param HTMLPurifier_Context $context
  18217. * @return bool
  18218. */
  18219. public function filter(&$uri, $config, $context)
  18220. {
  18221. if (is_null($uri->host)) {
  18222. return true;
  18223. }
  18224. if ($this->ourHostParts === false) {
  18225. return false;
  18226. }
  18227. $host_parts = array_reverse(explode('.', $uri->host));
  18228. foreach ($this->ourHostParts as $i => $x) {
  18229. if (!isset($host_parts[$i])) {
  18230. return false;
  18231. }
  18232. if ($host_parts[$i] != $this->ourHostParts[$i]) {
  18233. return false;
  18234. }
  18235. }
  18236. return true;
  18237. }
  18238. }
  18239. class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
  18240. {
  18241. /**
  18242. * @type string
  18243. */
  18244. public $name = 'DisableExternalResources';
  18245. /**
  18246. * @param HTMLPurifier_URI $uri
  18247. * @param HTMLPurifier_Config $config
  18248. * @param HTMLPurifier_Context $context
  18249. * @return bool
  18250. */
  18251. public function filter(&$uri, $config, $context)
  18252. {
  18253. if (!$context->get('EmbeddedURI', true)) {
  18254. return true;
  18255. }
  18256. return parent::filter($uri, $config, $context);
  18257. }
  18258. }
  18259. class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
  18260. {
  18261. /**
  18262. * @type string
  18263. */
  18264. public $name = 'DisableResources';
  18265. /**
  18266. * @param HTMLPurifier_URI $uri
  18267. * @param HTMLPurifier_Config $config
  18268. * @param HTMLPurifier_Context $context
  18269. * @return bool
  18270. */
  18271. public function filter(&$uri, $config, $context)
  18272. {
  18273. return !$context->get('EmbeddedURI', true);
  18274. }
  18275. }
  18276. // It's not clear to me whether or not Punycode means that hostnames
  18277. // do not have canonical forms anymore. As far as I can tell, it's
  18278. // not a problem (punycoding should be identity when no Unicode
  18279. // points are involved), but I'm not 100% sure
  18280. class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
  18281. {
  18282. /**
  18283. * @type string
  18284. */
  18285. public $name = 'HostBlacklist';
  18286. /**
  18287. * @type array
  18288. */
  18289. protected $blacklist = array();
  18290. /**
  18291. * @param HTMLPurifier_Config $config
  18292. * @return bool
  18293. */
  18294. public function prepare($config)
  18295. {
  18296. $this->blacklist = $config->get('URI.HostBlacklist');
  18297. return true;
  18298. }
  18299. /**
  18300. * @param HTMLPurifier_URI $uri
  18301. * @param HTMLPurifier_Config $config
  18302. * @param HTMLPurifier_Context $context
  18303. * @return bool
  18304. */
  18305. public function filter(&$uri, $config, $context)
  18306. {
  18307. foreach ($this->blacklist as $blacklisted_host_fragment) {
  18308. if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
  18309. return false;
  18310. }
  18311. }
  18312. return true;
  18313. }
  18314. }
  18315. // does not support network paths
  18316. class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
  18317. {
  18318. /**
  18319. * @type string
  18320. */
  18321. public $name = 'MakeAbsolute';
  18322. /**
  18323. * @type
  18324. */
  18325. protected $base;
  18326. /**
  18327. * @type array
  18328. */
  18329. protected $basePathStack = array();
  18330. /**
  18331. * @param HTMLPurifier_Config $config
  18332. * @return bool
  18333. */
  18334. public function prepare($config)
  18335. {
  18336. $def = $config->getDefinition('URI');
  18337. $this->base = $def->base;
  18338. if (is_null($this->base)) {
  18339. trigger_error(
  18340. 'URI.MakeAbsolute is being ignored due to lack of ' .
  18341. 'value for URI.Base configuration',
  18342. E_USER_WARNING
  18343. );
  18344. return false;
  18345. }
  18346. $this->base->fragment = null; // fragment is invalid for base URI
  18347. $stack = explode('/', $this->base->path);
  18348. array_pop($stack); // discard last segment
  18349. $stack = $this->_collapseStack($stack); // do pre-parsing
  18350. $this->basePathStack = $stack;
  18351. return true;
  18352. }
  18353. /**
  18354. * @param HTMLPurifier_URI $uri
  18355. * @param HTMLPurifier_Config $config
  18356. * @param HTMLPurifier_Context $context
  18357. * @return bool
  18358. */
  18359. public function filter(&$uri, $config, $context)
  18360. {
  18361. if (is_null($this->base)) {
  18362. return true;
  18363. } // abort early
  18364. if ($uri->path === '' && is_null($uri->scheme) &&
  18365. is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
  18366. // reference to current document
  18367. $uri = clone $this->base;
  18368. return true;
  18369. }
  18370. if (!is_null($uri->scheme)) {
  18371. // absolute URI already: don't change
  18372. if (!is_null($uri->host)) {
  18373. return true;
  18374. }
  18375. $scheme_obj = $uri->getSchemeObj($config, $context);
  18376. if (!$scheme_obj) {
  18377. // scheme not recognized
  18378. return false;
  18379. }
  18380. if (!$scheme_obj->hierarchical) {
  18381. // non-hierarchal URI with explicit scheme, don't change
  18382. return true;
  18383. }
  18384. // special case: had a scheme but always is hierarchical and had no authority
  18385. }
  18386. if (!is_null($uri->host)) {
  18387. // network path, don't bother
  18388. return true;
  18389. }
  18390. if ($uri->path === '') {
  18391. $uri->path = $this->base->path;
  18392. } elseif ($uri->path[0] !== '/') {
  18393. // relative path, needs more complicated processing
  18394. $stack = explode('/', $uri->path);
  18395. $new_stack = array_merge($this->basePathStack, $stack);
  18396. if ($new_stack[0] !== '' && !is_null($this->base->host)) {
  18397. array_unshift($new_stack, '');
  18398. }
  18399. $new_stack = $this->_collapseStack($new_stack);
  18400. $uri->path = implode('/', $new_stack);
  18401. } else {
  18402. // absolute path, but still we should collapse
  18403. $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
  18404. }
  18405. // re-combine
  18406. $uri->scheme = $this->base->scheme;
  18407. if (is_null($uri->userinfo)) {
  18408. $uri->userinfo = $this->base->userinfo;
  18409. }
  18410. if (is_null($uri->host)) {
  18411. $uri->host = $this->base->host;
  18412. }
  18413. if (is_null($uri->port)) {
  18414. $uri->port = $this->base->port;
  18415. }
  18416. return true;
  18417. }
  18418. /**
  18419. * Resolve dots and double-dots in a path stack
  18420. * @param array $stack
  18421. * @return array
  18422. */
  18423. private function _collapseStack($stack)
  18424. {
  18425. $result = array();
  18426. $is_folder = false;
  18427. for ($i = 0; isset($stack[$i]); $i++) {
  18428. $is_folder = false;
  18429. // absorb an internally duplicated slash
  18430. if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
  18431. continue;
  18432. }
  18433. if ($stack[$i] == '..') {
  18434. if (!empty($result)) {
  18435. $segment = array_pop($result);
  18436. if ($segment === '' && empty($result)) {
  18437. // error case: attempted to back out too far:
  18438. // restore the leading slash
  18439. $result[] = '';
  18440. } elseif ($segment === '..') {
  18441. $result[] = '..'; // cannot remove .. with ..
  18442. }
  18443. } else {
  18444. // relative path, preserve the double-dots
  18445. $result[] = '..';
  18446. }
  18447. $is_folder = true;
  18448. continue;
  18449. }
  18450. if ($stack[$i] == '.') {
  18451. // silently absorb
  18452. $is_folder = true;
  18453. continue;
  18454. }
  18455. $result[] = $stack[$i];
  18456. }
  18457. if ($is_folder) {
  18458. $result[] = '';
  18459. }
  18460. return $result;
  18461. }
  18462. }
  18463. class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
  18464. {
  18465. /**
  18466. * @type string
  18467. */
  18468. public $name = 'Munge';
  18469. /**
  18470. * @type bool
  18471. */
  18472. public $post = true;
  18473. /**
  18474. * @type string
  18475. */
  18476. private $target;
  18477. /**
  18478. * @type HTMLPurifier_URIParser
  18479. */
  18480. private $parser;
  18481. /**
  18482. * @type bool
  18483. */
  18484. private $doEmbed;
  18485. /**
  18486. * @type string
  18487. */
  18488. private $secretKey;
  18489. /**
  18490. * @type array
  18491. */
  18492. protected $replace = array();
  18493. /**
  18494. * @param HTMLPurifier_Config $config
  18495. * @return bool
  18496. */
  18497. public function prepare($config)
  18498. {
  18499. $this->target = $config->get('URI.' . $this->name);
  18500. $this->parser = new HTMLPurifier_URIParser();
  18501. $this->doEmbed = $config->get('URI.MungeResources');
  18502. $this->secretKey = $config->get('URI.MungeSecretKey');
  18503. if ($this->secretKey && !function_exists('hash_hmac')) {
  18504. throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
  18505. }
  18506. return true;
  18507. }
  18508. /**
  18509. * @param HTMLPurifier_URI $uri
  18510. * @param HTMLPurifier_Config $config
  18511. * @param HTMLPurifier_Context $context
  18512. * @return bool
  18513. */
  18514. public function filter(&$uri, $config, $context)
  18515. {
  18516. if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
  18517. return true;
  18518. }
  18519. $scheme_obj = $uri->getSchemeObj($config, $context);
  18520. if (!$scheme_obj) {
  18521. return true;
  18522. } // ignore unknown schemes, maybe another postfilter did it
  18523. if (!$scheme_obj->browsable) {
  18524. return true;
  18525. } // ignore non-browseable schemes, since we can't munge those in a reasonable way
  18526. if ($uri->isBenign($config, $context)) {
  18527. return true;
  18528. } // don't redirect if a benign URL
  18529. $this->makeReplace($uri, $config, $context);
  18530. $this->replace = array_map('rawurlencode', $this->replace);
  18531. $new_uri = strtr($this->target, $this->replace);
  18532. $new_uri = $this->parser->parse($new_uri);
  18533. // don't redirect if the target host is the same as the
  18534. // starting host
  18535. if ($uri->host === $new_uri->host) {
  18536. return true;
  18537. }
  18538. $uri = $new_uri; // overwrite
  18539. return true;
  18540. }
  18541. /**
  18542. * @param HTMLPurifier_URI $uri
  18543. * @param HTMLPurifier_Config $config
  18544. * @param HTMLPurifier_Context $context
  18545. */
  18546. protected function makeReplace($uri, $config, $context)
  18547. {
  18548. $string = $uri->toString();
  18549. // always available
  18550. $this->replace['%s'] = $string;
  18551. $this->replace['%r'] = $context->get('EmbeddedURI', true);
  18552. $token = $context->get('CurrentToken', true);
  18553. $this->replace['%n'] = $token ? $token->name : null;
  18554. $this->replace['%m'] = $context->get('CurrentAttr', true);
  18555. $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
  18556. // not always available
  18557. if ($this->secretKey) {
  18558. $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
  18559. }
  18560. }
  18561. }
  18562. /**
  18563. * Implements safety checks for safe iframes.
  18564. *
  18565. * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
  18566. * works safely.
  18567. */
  18568. class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
  18569. {
  18570. /**
  18571. * @type string
  18572. */
  18573. public $name = 'SafeIframe';
  18574. /**
  18575. * @type bool
  18576. */
  18577. public $always_load = true;
  18578. /**
  18579. * @type string
  18580. */
  18581. protected $regexp = null;
  18582. // XXX: The not so good bit about how this is all set up now is we
  18583. // can't check HTML.SafeIframe in the 'prepare' step: we have to
  18584. // defer till the actual filtering.
  18585. /**
  18586. * @param HTMLPurifier_Config $config
  18587. * @return bool
  18588. */
  18589. public function prepare($config)
  18590. {
  18591. $this->regexp = $config->get('URI.SafeIframeRegexp');
  18592. return true;
  18593. }
  18594. /**
  18595. * @param HTMLPurifier_URI $uri
  18596. * @param HTMLPurifier_Config $config
  18597. * @param HTMLPurifier_Context $context
  18598. * @return bool
  18599. */
  18600. public function filter(&$uri, $config, $context)
  18601. {
  18602. // check if filter not applicable
  18603. if (!$config->get('HTML.SafeIframe')) {
  18604. return true;
  18605. }
  18606. // check if the filter should actually trigger
  18607. if (!$context->get('EmbeddedURI', true)) {
  18608. return true;
  18609. }
  18610. $token = $context->get('CurrentToken', true);
  18611. if (!($token && $token->name == 'iframe')) {
  18612. return true;
  18613. }
  18614. // check if we actually have some whitelists enabled
  18615. if ($this->regexp === null) {
  18616. return false;
  18617. }
  18618. // actually check the whitelists
  18619. return preg_match($this->regexp, $uri->toString());
  18620. }
  18621. }
  18622. /**
  18623. * Implements data: URI for base64 encoded images supported by GD.
  18624. */
  18625. class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
  18626. {
  18627. /**
  18628. * @type bool
  18629. */
  18630. public $browsable = true;
  18631. /**
  18632. * @type array
  18633. */
  18634. public $allowed_types = array(
  18635. // you better write validation code for other types if you
  18636. // decide to allow them
  18637. 'image/jpeg' => true,
  18638. 'image/gif' => true,
  18639. 'image/png' => true,
  18640. );
  18641. // this is actually irrelevant since we only write out the path
  18642. // component
  18643. /**
  18644. * @type bool
  18645. */
  18646. public $may_omit_host = true;
  18647. /**
  18648. * @param HTMLPurifier_URI $uri
  18649. * @param HTMLPurifier_Config $config
  18650. * @param HTMLPurifier_Context $context
  18651. * @return bool
  18652. */
  18653. public function doValidate(&$uri, $config, $context)
  18654. {
  18655. $result = explode(',', $uri->path, 2);
  18656. $is_base64 = false;
  18657. $charset = null;
  18658. $content_type = null;
  18659. if (count($result) == 2) {
  18660. list($metadata, $data) = $result;
  18661. // do some legwork on the metadata
  18662. $metas = explode(';', $metadata);
  18663. while (!empty($metas)) {
  18664. $cur = array_shift($metas);
  18665. if ($cur == 'base64') {
  18666. $is_base64 = true;
  18667. break;
  18668. }
  18669. if (substr($cur, 0, 8) == 'charset=') {
  18670. // doesn't match if there are arbitrary spaces, but
  18671. // whatever dude
  18672. if ($charset !== null) {
  18673. continue;
  18674. } // garbage
  18675. $charset = substr($cur, 8); // not used
  18676. } else {
  18677. if ($content_type !== null) {
  18678. continue;
  18679. } // garbage
  18680. $content_type = $cur;
  18681. }
  18682. }
  18683. } else {
  18684. $data = $result[0];
  18685. }
  18686. if ($content_type !== null && empty($this->allowed_types[$content_type])) {
  18687. return false;
  18688. }
  18689. if ($charset !== null) {
  18690. // error; we don't allow plaintext stuff
  18691. $charset = null;
  18692. }
  18693. $data = rawurldecode($data);
  18694. if ($is_base64) {
  18695. $raw_data = base64_decode($data);
  18696. } else {
  18697. $raw_data = $data;
  18698. }
  18699. // XXX probably want to refactor this into a general mechanism
  18700. // for filtering arbitrary content types
  18701. $file = tempnam("/tmp", "");
  18702. file_put_contents($file, $raw_data);
  18703. if (function_exists('exif_imagetype')) {
  18704. $image_code = exif_imagetype($file);
  18705. unlink($file);
  18706. } elseif (function_exists('getimagesize')) {
  18707. set_error_handler(array($this, 'muteErrorHandler'));
  18708. $info = getimagesize($file);
  18709. restore_error_handler();
  18710. unlink($file);
  18711. if ($info == false) {
  18712. return false;
  18713. }
  18714. $image_code = $info[2];
  18715. } else {
  18716. trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
  18717. }
  18718. $real_content_type = image_type_to_mime_type($image_code);
  18719. if ($real_content_type != $content_type) {
  18720. // we're nice guys; if the content type is something else we
  18721. // support, change it over
  18722. if (empty($this->allowed_types[$real_content_type])) {
  18723. return false;
  18724. }
  18725. $content_type = $real_content_type;
  18726. }
  18727. // ok, it's kosher, rewrite what we need
  18728. $uri->userinfo = null;
  18729. $uri->host = null;
  18730. $uri->port = null;
  18731. $uri->fragment = null;
  18732. $uri->query = null;
  18733. $uri->path = "$content_type;base64," . base64_encode($raw_data);
  18734. return true;
  18735. }
  18736. /**
  18737. * @param int $errno
  18738. * @param string $errstr
  18739. */
  18740. public function muteErrorHandler($errno, $errstr)
  18741. {
  18742. }
  18743. }
  18744. /**
  18745. * Validates file as defined by RFC 1630 and RFC 1738.
  18746. */
  18747. class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
  18748. {
  18749. /**
  18750. * Generally file:// URLs are not accessible from most
  18751. * machines, so placing them as an img src is incorrect.
  18752. * @type bool
  18753. */
  18754. public $browsable = false;
  18755. /**
  18756. * Basically the *only* URI scheme for which this is true, since
  18757. * accessing files on the local machine is very common. In fact,
  18758. * browsers on some operating systems don't understand the
  18759. * authority, though I hear it is used on Windows to refer to
  18760. * network shares.
  18761. * @type bool
  18762. */
  18763. public $may_omit_host = true;
  18764. /**
  18765. * @param HTMLPurifier_URI $uri
  18766. * @param HTMLPurifier_Config $config
  18767. * @param HTMLPurifier_Context $context
  18768. * @return bool
  18769. */
  18770. public function doValidate(&$uri, $config, $context)
  18771. {
  18772. // Authentication method is not supported
  18773. $uri->userinfo = null;
  18774. // file:// makes no provisions for accessing the resource
  18775. $uri->port = null;
  18776. // While it seems to work on Firefox, the querystring has
  18777. // no possible effect and is thus stripped.
  18778. $uri->query = null;
  18779. return true;
  18780. }
  18781. }
  18782. /**
  18783. * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
  18784. */
  18785. class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
  18786. {
  18787. /**
  18788. * @type int
  18789. */
  18790. public $default_port = 21;
  18791. /**
  18792. * @type bool
  18793. */
  18794. public $browsable = true; // usually
  18795. /**
  18796. * @type bool
  18797. */
  18798. public $hierarchical = true;
  18799. /**
  18800. * @param HTMLPurifier_URI $uri
  18801. * @param HTMLPurifier_Config $config
  18802. * @param HTMLPurifier_Context $context
  18803. * @return bool
  18804. */
  18805. public function doValidate(&$uri, $config, $context)
  18806. {
  18807. $uri->query = null;
  18808. // typecode check
  18809. $semicolon_pos = strrpos($uri->path, ';'); // reverse
  18810. if ($semicolon_pos !== false) {
  18811. $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
  18812. $uri->path = substr($uri->path, 0, $semicolon_pos);
  18813. $type_ret = '';
  18814. if (strpos($type, '=') !== false) {
  18815. // figure out whether or not the declaration is correct
  18816. list($key, $typecode) = explode('=', $type, 2);
  18817. if ($key !== 'type') {
  18818. // invalid key, tack it back on encoded
  18819. $uri->path .= '%3B' . $type;
  18820. } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
  18821. $type_ret = ";type=$typecode";
  18822. }
  18823. } else {
  18824. $uri->path .= '%3B' . $type;
  18825. }
  18826. $uri->path = str_replace(';', '%3B', $uri->path);
  18827. $uri->path .= $type_ret;
  18828. }
  18829. return true;
  18830. }
  18831. }
  18832. /**
  18833. * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
  18834. */
  18835. class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
  18836. {
  18837. /**
  18838. * @type int
  18839. */
  18840. public $default_port = 80;
  18841. /**
  18842. * @type bool
  18843. */
  18844. public $browsable = true;
  18845. /**
  18846. * @type bool
  18847. */
  18848. public $hierarchical = true;
  18849. /**
  18850. * @param HTMLPurifier_URI $uri
  18851. * @param HTMLPurifier_Config $config
  18852. * @param HTMLPurifier_Context $context
  18853. * @return bool
  18854. */
  18855. public function doValidate(&$uri, $config, $context)
  18856. {
  18857. $uri->userinfo = null;
  18858. return true;
  18859. }
  18860. }
  18861. /**
  18862. * Validates https (Secure HTTP) according to http scheme.
  18863. */
  18864. class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
  18865. {
  18866. /**
  18867. * @type int
  18868. */
  18869. public $default_port = 443;
  18870. /**
  18871. * @type bool
  18872. */
  18873. public $secure = true;
  18874. }
  18875. // VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
  18876. // email is valid, but be careful!
  18877. /**
  18878. * Validates mailto (for E-mail) according to RFC 2368
  18879. * @todo Validate the email address
  18880. * @todo Filter allowed query parameters
  18881. */
  18882. class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
  18883. {
  18884. /**
  18885. * @type bool
  18886. */
  18887. public $browsable = false;
  18888. /**
  18889. * @type bool
  18890. */
  18891. public $may_omit_host = true;
  18892. /**
  18893. * @param HTMLPurifier_URI $uri
  18894. * @param HTMLPurifier_Config $config
  18895. * @param HTMLPurifier_Context $context
  18896. * @return bool
  18897. */
  18898. public function doValidate(&$uri, $config, $context)
  18899. {
  18900. $uri->userinfo = null;
  18901. $uri->host = null;
  18902. $uri->port = null;
  18903. // we need to validate path against RFC 2368's addr-spec
  18904. return true;
  18905. }
  18906. }
  18907. /**
  18908. * Validates news (Usenet) as defined by generic RFC 1738
  18909. */
  18910. class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
  18911. {
  18912. /**
  18913. * @type bool
  18914. */
  18915. public $browsable = false;
  18916. /**
  18917. * @type bool
  18918. */
  18919. public $may_omit_host = true;
  18920. /**
  18921. * @param HTMLPurifier_URI $uri
  18922. * @param HTMLPurifier_Config $config
  18923. * @param HTMLPurifier_Context $context
  18924. * @return bool
  18925. */
  18926. public function doValidate(&$uri, $config, $context)
  18927. {
  18928. $uri->userinfo = null;
  18929. $uri->host = null;
  18930. $uri->port = null;
  18931. $uri->query = null;
  18932. // typecode check needed on path
  18933. return true;
  18934. }
  18935. }
  18936. /**
  18937. * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
  18938. */
  18939. class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
  18940. {
  18941. /**
  18942. * @type int
  18943. */
  18944. public $default_port = 119;
  18945. /**
  18946. * @type bool
  18947. */
  18948. public $browsable = false;
  18949. /**
  18950. * @param HTMLPurifier_URI $uri
  18951. * @param HTMLPurifier_Config $config
  18952. * @param HTMLPurifier_Context $context
  18953. * @return bool
  18954. */
  18955. public function doValidate(&$uri, $config, $context)
  18956. {
  18957. $uri->userinfo = null;
  18958. $uri->query = null;
  18959. return true;
  18960. }
  18961. }
  18962. /**
  18963. * Performs safe variable parsing based on types which can be used by
  18964. * users. This may not be able to represent all possible data inputs,
  18965. * however.
  18966. */
  18967. class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
  18968. {
  18969. /**
  18970. * @param mixed $var
  18971. * @param int $type
  18972. * @param bool $allow_null
  18973. * @return array|bool|float|int|mixed|null|string
  18974. * @throws HTMLPurifier_VarParserException
  18975. */
  18976. protected function parseImplementation($var, $type, $allow_null)
  18977. {
  18978. if ($allow_null && $var === null) {
  18979. return null;
  18980. }
  18981. switch ($type) {
  18982. // Note: if code "breaks" from the switch, it triggers a generic
  18983. // exception to be thrown. Specific errors can be specifically
  18984. // done here.
  18985. case self::MIXED:
  18986. case self::ISTRING:
  18987. case self::STRING:
  18988. case self::TEXT:
  18989. case self::ITEXT:
  18990. return $var;
  18991. case self::INT:
  18992. if (is_string($var) && ctype_digit($var)) {
  18993. $var = (int)$var;
  18994. }
  18995. return $var;
  18996. case self::FLOAT:
  18997. if ((is_string($var) && is_numeric($var)) || is_int($var)) {
  18998. $var = (float)$var;
  18999. }
  19000. return $var;
  19001. case self::BOOL:
  19002. if (is_int($var) && ($var === 0 || $var === 1)) {
  19003. $var = (bool)$var;
  19004. } elseif (is_string($var)) {
  19005. if ($var == 'on' || $var == 'true' || $var == '1') {
  19006. $var = true;
  19007. } elseif ($var == 'off' || $var == 'false' || $var == '0') {
  19008. $var = false;
  19009. } else {
  19010. throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
  19011. }
  19012. }
  19013. return $var;
  19014. case self::ALIST:
  19015. case self::HASH:
  19016. case self::LOOKUP:
  19017. if (is_string($var)) {
  19018. // special case: technically, this is an array with
  19019. // a single empty string item, but having an empty
  19020. // array is more intuitive
  19021. if ($var == '') {
  19022. return array();
  19023. }
  19024. if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
  19025. // simplistic string to array method that only works
  19026. // for simple lists of tag names or alphanumeric characters
  19027. $var = explode(',', $var);
  19028. } else {
  19029. $var = preg_split('/(,|[\n\r]+)/', $var);
  19030. }
  19031. // remove spaces
  19032. foreach ($var as $i => $j) {
  19033. $var[$i] = trim($j);
  19034. }
  19035. if ($type === self::HASH) {
  19036. // key:value,key2:value2
  19037. $nvar = array();
  19038. foreach ($var as $keypair) {
  19039. $c = explode(':', $keypair, 2);
  19040. if (!isset($c[1])) {
  19041. continue;
  19042. }
  19043. $nvar[trim($c[0])] = trim($c[1]);
  19044. }
  19045. $var = $nvar;
  19046. }
  19047. }
  19048. if (!is_array($var)) {
  19049. break;
  19050. }
  19051. $keys = array_keys($var);
  19052. if ($keys === array_keys($keys)) {
  19053. if ($type == self::ALIST) {
  19054. return $var;
  19055. } elseif ($type == self::LOOKUP) {
  19056. $new = array();
  19057. foreach ($var as $key) {
  19058. $new[$key] = true;
  19059. }
  19060. return $new;
  19061. } else {
  19062. break;
  19063. }
  19064. }
  19065. if ($type === self::ALIST) {
  19066. trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
  19067. return array_values($var);
  19068. }
  19069. if ($type === self::LOOKUP) {
  19070. foreach ($var as $key => $value) {
  19071. if ($value !== true) {
  19072. trigger_error(
  19073. "Lookup array has non-true value at key '$key'; " .
  19074. "maybe your input array was not indexed numerically",
  19075. E_USER_WARNING
  19076. );
  19077. }
  19078. $var[$key] = true;
  19079. }
  19080. }
  19081. return $var;
  19082. default:
  19083. $this->errorInconsistent(__CLASS__, $type);
  19084. }
  19085. $this->errorGeneric($var, $type);
  19086. }
  19087. }
  19088. /**
  19089. * This variable parser uses PHP's internal code engine. Because it does
  19090. * this, it can represent all inputs; however, it is dangerous and cannot
  19091. * be used by users.
  19092. */
  19093. class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
  19094. {
  19095. /**
  19096. * @param mixed $var
  19097. * @param int $type
  19098. * @param bool $allow_null
  19099. * @return null|string
  19100. */
  19101. protected function parseImplementation($var, $type, $allow_null)
  19102. {
  19103. return $this->evalExpression($var);
  19104. }
  19105. /**
  19106. * @param string $expr
  19107. * @return mixed
  19108. * @throws HTMLPurifier_VarParserException
  19109. */
  19110. protected function evalExpression($expr)
  19111. {
  19112. $var = null;
  19113. $result = eval("\$var = $expr;");
  19114. if ($result === false) {
  19115. throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
  19116. }
  19117. return $var;
  19118. }
  19119. }