123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873 |
- <?php
- /**
- * @file
- * This file was auto-generated by generate-includes.php and includes all of
- * the core files required by HTML Purifier. Use this if performance is a
- * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
- * FILE, changes will be overwritten the next time the script is run.
- *
- * @version 4.6.0
- *
- * @warning
- * You must *not* include any other HTML Purifier files before this file,
- * because 'require' not 'require_once' is used.
- *
- * @warning
- * This file requires that the include path contains the HTML Purifier
- * library directory; this is not auto-set.
- */
- /*! @mainpage
- *
- * HTML Purifier is an HTML filter that will take an arbitrary snippet of
- * HTML and rigorously test, validate and filter it into a version that
- * is safe for output onto webpages. It achieves this by:
- *
- * -# Lexing (parsing into tokens) the document,
- * -# Executing various strategies on the tokens:
- * -# Removing all elements not in the whitelist,
- * -# Making the tokens well-formed,
- * -# Fixing the nesting of the nodes, and
- * -# Validating attributes of the nodes; and
- * -# Generating HTML from the purified tokens.
- *
- * However, most users will only need to interface with the HTMLPurifier
- * and HTMLPurifier_Config.
- */
- /*
- HTML Purifier 4.6.0 - Standards Compliant HTML Filtering
- Copyright (C) 2006-2008 Edward Z. Yang
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
- /**
- * Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
- *
- * @note There are several points in which configuration can be specified
- * for HTML Purifier. The precedence of these (from lowest to
- * highest) is as follows:
- * -# Instance: new HTMLPurifier($config)
- * -# Invocation: purify($html, $config)
- * These configurations are entirely independent of each other and
- * are *not* merged (this behavior may change in the future).
- *
- * @todo We need an easier way to inject strategies using the configuration
- * object.
- */
- class HTMLPurifier
- {
- /**
- * Version of HTML Purifier.
- * @type string
- */
- public $version = '4.6.0';
- /**
- * Constant with version of HTML Purifier.
- */
- const VERSION = '4.6.0';
- /**
- * Global configuration object.
- * @type HTMLPurifier_Config
- */
- public $config;
- /**
- * Array of extra filter objects to run on HTML,
- * for backwards compatibility.
- * @type HTMLPurifier_Filter[]
- */
- private $filters = array();
- /**
- * Single instance of HTML Purifier.
- * @type HTMLPurifier
- */
- private static $instance;
- /**
- * @type HTMLPurifier_Strategy_Core
- */
- protected $strategy;
- /**
- * @type HTMLPurifier_Generator
- */
- protected $generator;
- /**
- * Resultant context of last run purification.
- * Is an array of contexts if the last called method was purifyArray().
- * @type HTMLPurifier_Context
- */
- public $context;
- /**
- * Initializes the purifier.
- *
- * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
- * for all instances of the purifier, if omitted, a default
- * configuration is supplied (which can be overridden on a
- * per-use basis).
- * The parameter can also be any type that
- * HTMLPurifier_Config::create() supports.
- */
- public function __construct($config = null)
- {
- $this->config = HTMLPurifier_Config::create($config);
- $this->strategy = new HTMLPurifier_Strategy_Core();
- }
- /**
- * Adds a filter to process the output. First come first serve
- *
- * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
- */
- public function addFilter($filter)
- {
- trigger_error(
- 'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
- ' in the Filter namespace or Filter.Custom',
- E_USER_WARNING
- );
- $this->filters[] = $filter;
- }
- /**
- * Filters an HTML snippet/document to be XSS-free and standards-compliant.
- *
- * @param string $html String of HTML to purify
- * @param HTMLPurifier_Config $config Config object for this operation,
- * if omitted, defaults to the config object specified during this
- * object's construction. The parameter can also be any type
- * that HTMLPurifier_Config::create() supports.
- *
- * @return string Purified HTML
- */
- public function purify($html, $config = null)
- {
- // :TODO: make the config merge in, instead of replace
- $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
- // implementation is partially environment dependant, partially
- // configuration dependant
- $lexer = HTMLPurifier_Lexer::create($config);
- $context = new HTMLPurifier_Context();
- // setup HTML generator
- $this->generator = new HTMLPurifier_Generator($config, $context);
- $context->register('Generator', $this->generator);
- // set up global context variables
- if ($config->get('Core.CollectErrors')) {
- // may get moved out if other facilities use it
- $language_factory = HTMLPurifier_LanguageFactory::instance();
- $language = $language_factory->create($config, $context);
- $context->register('Locale', $language);
- $error_collector = new HTMLPurifier_ErrorCollector($context);
- $context->register('ErrorCollector', $error_collector);
- }
- // setup id_accumulator context, necessary due to the fact that
- // AttrValidator can be called from many places
- $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
- $context->register('IDAccumulator', $id_accumulator);
- $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
- // setup filters
- $filter_flags = $config->getBatch('Filter');
- $custom_filters = $filter_flags['Custom'];
- unset($filter_flags['Custom']);
- $filters = array();
- foreach ($filter_flags as $filter => $flag) {
- if (!$flag) {
- continue;
- }
- if (strpos($filter, '.') !== false) {
- continue;
- }
- $class = "HTMLPurifier_Filter_$filter";
- $filters[] = new $class;
- }
- foreach ($custom_filters as $filter) {
- // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat
- $filters[] = $filter;
- }
- $filters = array_merge($filters, $this->filters);
- // maybe prepare(), but later
- for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
- $html = $filters[$i]->preFilter($html, $config, $context);
- }
- // purified HTML
- $html =
- $this->generator->generateFromTokens(
- // list of tokens
- $this->strategy->execute(
- // list of un-purified tokens
- $lexer->tokenizeHTML(
- // un-purified HTML
- $html,
- $config,
- $context
- ),
- $config,
- $context
- )
- );
- for ($i = $filter_size - 1; $i >= 0; $i--) {
- $html = $filters[$i]->postFilter($html, $config, $context);
- }
- $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
- $this->context =& $context;
- return $html;
- }
- /**
- * Filters an array of HTML snippets
- *
- * @param string[] $array_of_html Array of html snippets
- * @param HTMLPurifier_Config $config Optional config object for this operation.
- * See HTMLPurifier::purify() for more details.
- *
- * @return string[] Array of purified HTML
- */
- public function purifyArray($array_of_html, $config = null)
- {
- $context_array = array();
- foreach ($array_of_html as $key => $html) {
- $array_of_html[$key] = $this->purify($html, $config);
- $context_array[$key] = $this->context;
- }
- $this->context = $context_array;
- return $array_of_html;
- }
- /**
- * Singleton for enforcing just one HTML Purifier in your system
- *
- * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
- * HTMLPurifier instance to overload singleton with,
- * or HTMLPurifier_Config instance to configure the
- * generated version with.
- *
- * @return HTMLPurifier
- */
- public static function instance($prototype = null)
- {
- if (!self::$instance || $prototype) {
- if ($prototype instanceof HTMLPurifier) {
- self::$instance = $prototype;
- } elseif ($prototype) {
- self::$instance = new HTMLPurifier($prototype);
- } else {
- self::$instance = new HTMLPurifier();
- }
- }
- return self::$instance;
- }
- /**
- * Singleton for enforcing just one HTML Purifier in your system
- *
- * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
- * HTMLPurifier instance to overload singleton with,
- * or HTMLPurifier_Config instance to configure the
- * generated version with.
- *
- * @return HTMLPurifier
- * @note Backwards compatibility, see instance()
- */
- public static function getInstance($prototype = null)
- {
- return HTMLPurifier::instance($prototype);
- }
- }
- /**
- * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
- * and back again.
- *
- * @note This transformation is not an equivalence. We mutate the input
- * token stream to make it so; see all [MUT] markers in code.
- */
- class HTMLPurifier_Arborize
- {
- public static function arborize($tokens, $config, $context) {
- $definition = $config->getHTMLDefinition();
- $parent = new HTMLPurifier_Token_Start($definition->info_parent);
- $stack = array($parent->toNode());
- foreach ($tokens as $token) {
- $token->skip = null; // [MUT]
- $token->carryover = null; // [MUT]
- if ($token instanceof HTMLPurifier_Token_End) {
- $token->start = null; // [MUT]
- $r = array_pop($stack);
- assert($r->name === $token->name);
- assert(empty($token->attr));
- $r->endCol = $token->col;
- $r->endLine = $token->line;
- $r->endArmor = $token->armor;
- continue;
- }
- $node = $token->toNode();
- $stack[count($stack)-1]->children[] = $node;
- if ($token instanceof HTMLPurifier_Token_Start) {
- $stack[] = $node;
- }
- }
- assert(count($stack) == 1);
- return $stack[0];
- }
- public static function flatten($node, $config, $context) {
- $level = 0;
- $nodes = array($level => new HTMLPurifier_Queue(array($node)));
- $closingTokens = array();
- $tokens = array();
- do {
- while (!$nodes[$level]->isEmpty()) {
- $node = $nodes[$level]->shift(); // FIFO
- list($start, $end) = $node->toTokenPair();
- if ($level > 0) {
- $tokens[] = $start;
- }
- if ($end !== NULL) {
- $closingTokens[$level][] = $end;
- }
- if ($node instanceof HTMLPurifier_Node_Element) {
- $level++;
- $nodes[$level] = new HTMLPurifier_Queue();
- foreach ($node->children as $childNode) {
- $nodes[$level]->push($childNode);
- }
- }
- }
- $level--;
- if ($level && isset($closingTokens[$level])) {
- while ($token = array_pop($closingTokens[$level])) {
- $tokens[] = $token;
- }
- }
- } while ($level > 0);
- return $tokens;
- }
- }
- /**
- * Defines common attribute collections that modules reference
- */
- class HTMLPurifier_AttrCollections
- {
- /**
- * Associative array of attribute collections, indexed by name.
- * @type array
- */
- public $info = array();
- /**
- * Performs all expansions on internal data for use by other inclusions
- * It also collects all attribute collection extensions from
- * modules
- * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
- * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
- */
- public function __construct($attr_types, $modules)
- {
- // load extensions from the modules
- foreach ($modules as $module) {
- foreach ($module->attr_collections as $coll_i => $coll) {
- if (!isset($this->info[$coll_i])) {
- $this->info[$coll_i] = array();
- }
- foreach ($coll as $attr_i => $attr) {
- if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
- // merge in includes
- $this->info[$coll_i][$attr_i] = array_merge(
- $this->info[$coll_i][$attr_i],
- $attr
- );
- continue;
- }
- $this->info[$coll_i][$attr_i] = $attr;
- }
- }
- }
- // perform internal expansions and inclusions
- foreach ($this->info as $name => $attr) {
- // merge attribute collections that include others
- $this->performInclusions($this->info[$name]);
- // replace string identifiers with actual attribute objects
- $this->expandIdentifiers($this->info[$name], $attr_types);
- }
- }
- /**
- * Takes a reference to an attribute associative array and performs
- * all inclusions specified by the zero index.
- * @param array &$attr Reference to attribute array
- */
- public function performInclusions(&$attr)
- {
- if (!isset($attr[0])) {
- return;
- }
- $merge = $attr[0];
- $seen = array(); // recursion guard
- // loop through all the inclusions
- for ($i = 0; isset($merge[$i]); $i++) {
- if (isset($seen[$merge[$i]])) {
- continue;
- }
- $seen[$merge[$i]] = true;
- // foreach attribute of the inclusion, copy it over
- if (!isset($this->info[$merge[$i]])) {
- continue;
- }
- foreach ($this->info[$merge[$i]] as $key => $value) {
- if (isset($attr[$key])) {
- continue;
- } // also catches more inclusions
- $attr[$key] = $value;
- }
- if (isset($this->info[$merge[$i]][0])) {
- // recursion
- $merge = array_merge($merge, $this->info[$merge[$i]][0]);
- }
- }
- unset($attr[0]);
- }
- /**
- * Expands all string identifiers in an attribute array by replacing
- * them with the appropriate values inside HTMLPurifier_AttrTypes
- * @param array &$attr Reference to attribute array
- * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
- */
- public function expandIdentifiers(&$attr, $attr_types)
- {
- // because foreach will process new elements we add, make sure we
- // skip duplicates
- $processed = array();
- foreach ($attr as $def_i => $def) {
- // skip inclusions
- if ($def_i === 0) {
- continue;
- }
- if (isset($processed[$def_i])) {
- continue;
- }
- // determine whether or not attribute is required
- if ($required = (strpos($def_i, '*') !== false)) {
- // rename the definition
- unset($attr[$def_i]);
- $def_i = trim($def_i, '*');
- $attr[$def_i] = $def;
- }
- $processed[$def_i] = true;
- // if we've already got a literal object, move on
- if (is_object($def)) {
- // preserve previous required
- $attr[$def_i]->required = ($required || $attr[$def_i]->required);
- continue;
- }
- if ($def === false) {
- unset($attr[$def_i]);
- continue;
- }
- if ($t = $attr_types->get($def)) {
- $attr[$def_i] = $t;
- $attr[$def_i]->required = $required;
- } else {
- unset($attr[$def_i]);
- }
- }
- }
- }
- /**
- * Base class for all validating attribute definitions.
- *
- * This family of classes forms the core for not only HTML attribute validation,
- * but also any sort of string that needs to be validated or cleaned (which
- * means CSS properties and composite definitions are defined here too).
- * Besides defining (through code) what precisely makes the string valid,
- * subclasses are also responsible for cleaning the code if possible.
- */
- abstract class HTMLPurifier_AttrDef
- {
- /**
- * Tells us whether or not an HTML attribute is minimized.
- * Has no meaning in other contexts.
- * @type bool
- */
- public $minimized = false;
- /**
- * Tells us whether or not an HTML attribute is required.
- * Has no meaning in other contexts
- * @type bool
- */
- public $required = false;
- /**
- * Validates and cleans passed string according to a definition.
- *
- * @param string $string String to be validated and cleaned.
- * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
- * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
- */
- abstract public function validate($string, $config, $context);
- /**
- * Convenience method that parses a string as if it were CDATA.
- *
- * This method process a string in the manner specified at
- * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
- * leading and trailing whitespace, ignoring line feeds, and replacing
- * carriage returns and tabs with spaces. While most useful for HTML
- * attributes specified as CDATA, it can also be applied to most CSS
- * values.
- *
- * @note This method is not entirely standards compliant, as trim() removes
- * more types of whitespace than specified in the spec. In practice,
- * this is rarely a problem, as those extra characters usually have
- * already been removed by HTMLPurifier_Encoder.
- *
- * @warning This processing is inconsistent with XML's whitespace handling
- * as specified by section 3.3.3 and referenced XHTML 1.0 section
- * 4.7. However, note that we are NOT necessarily
- * parsing XML, thus, this behavior may still be correct. We
- * assume that newlines have been normalized.
- */
- public function parseCDATA($string)
- {
- $string = trim($string);
- $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
- return $string;
- }
- /**
- * Factory method for creating this class from a string.
- * @param string $string String construction info
- * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
- */
- public function make($string)
- {
- // default implementation, return a flyweight of this object.
- // If $string has an effect on the returned object (i.e. you
- // need to overload this method), it is best
- // to clone or instantiate new copies. (Instantiation is safer.)
- return $this;
- }
- /**
- * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
- * properly. THIS IS A HACK!
- * @param string $string a CSS colour definition
- * @return string
- */
- protected function mungeRgb($string)
- {
- return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
- }
- /**
- * Parses a possibly escaped CSS string and returns the "pure"
- * version of it.
- */
- protected function expandCSSEscape($string)
- {
- // flexibly parse it
- $ret = '';
- for ($i = 0, $c = strlen($string); $i < $c; $i++) {
- if ($string[$i] === '\\') {
- $i++;
- if ($i >= $c) {
- $ret .= '\\';
- break;
- }
- if (ctype_xdigit($string[$i])) {
- $code = $string[$i];
- for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
- if (!ctype_xdigit($string[$i])) {
- break;
- }
- $code .= $string[$i];
- }
- // We have to be extremely careful when adding
- // new characters, to make sure we're not breaking
- // the encoding.
- $char = HTMLPurifier_Encoder::unichr(hexdec($code));
- if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
- continue;
- }
- $ret .= $char;
- if ($i < $c && trim($string[$i]) !== '') {
- $i--;
- }
- continue;
- }
- if ($string[$i] === "\n") {
- continue;
- }
- }
- $ret .= $string[$i];
- }
- return $ret;
- }
- }
- /**
- * Processes an entire attribute array for corrections needing multiple values.
- *
- * Occasionally, a certain attribute will need to be removed and popped onto
- * another value. Instead of creating a complex return syntax for
- * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
- * specialized object and have that do the special work. That is the
- * family of HTMLPurifier_AttrTransform.
- *
- * An attribute transformation can be assigned to run before or after
- * HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
- * more details.
- */
- abstract class HTMLPurifier_AttrTransform
- {
- /**
- * Abstract: makes changes to the attributes dependent on multiple values.
- *
- * @param array $attr Assoc array of attributes, usually from
- * HTMLPurifier_Token_Tag::$attr
- * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
- * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
- * @return array Processed attribute array.
- */
- abstract public function transform($attr, $config, $context);
- /**
- * Prepends CSS properties to the style attribute, creating the
- * attribute if it doesn't exist.
- * @param array &$attr Attribute array to process (passed by reference)
- * @param string $css CSS to prepend
- */
- public function prependCSS(&$attr, $css)
- {
- $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
- $attr['style'] = $css . $attr['style'];
- }
- /**
- * Retrieves and removes an attribute
- * @param array &$attr Attribute array to process (passed by reference)
- * @param mixed $key Key of attribute to confiscate
- * @return mixed
- */
- public function confiscateAttr(&$attr, $key)
- {
- if (!isset($attr[$key])) {
- return null;
- }
- $value = $attr[$key];
- unset($attr[$key]);
- return $value;
- }
- }
- /**
- * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
- */
- class HTMLPurifier_AttrTypes
- {
- /**
- * Lookup array of attribute string identifiers to concrete implementations.
- * @type HTMLPurifier_AttrDef[]
- */
- protected $info = array();
- /**
- * Constructs the info array, supplying default implementations for attribute
- * types.
- */
- public function __construct()
- {
- // XXX This is kind of poor, since we don't actually /clone/
- // instances; instead, we use the supplied make() attribute. So,
- // the underlying class must know how to deal with arguments.
- // With the old implementation of Enum, that ignored its
- // arguments when handling a make dispatch, the IAlign
- // definition wouldn't work.
- // pseudo-types, must be instantiated via shorthand
- $this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
- $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
- $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
- $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
- $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
- $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
- $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
- $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
- $this->info['Text'] = new HTMLPurifier_AttrDef_Text();
- $this->info['URI'] = new HTMLPurifier_AttrDef_URI();
- $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
- $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
- $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right');
- $this->info['LAlign'] = self::makeEnum('top,bottom,left,right');
- $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
- // unimplemented aliases
- $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
- $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
- $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
- $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
- // "proprietary" types
- $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
- // number is really a positive integer (one or more digits)
- // FIXME: ^^ not always, see start and value of list items
- $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
- }
- private static function makeEnum($in)
- {
- return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
- }
- /**
- * Retrieves a type
- * @param string $type String type name
- * @return HTMLPurifier_AttrDef Object AttrDef for type
- */
- public function get($type)
- {
- // determine if there is any extra info tacked on
- if (strpos($type, '#') !== false) {
- list($type, $string) = explode('#', $type, 2);
- } else {
- $string = '';
- }
- if (!isset($this->info[$type])) {
- trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
- return;
- }
- return $this->info[$type]->make($string);
- }
- /**
- * Sets a new implementation for a type
- * @param string $type String type name
- * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
- */
- public function set($type, $impl)
- {
- $this->info[$type] = $impl;
- }
- }
- /**
- * Validates the attributes of a token. Doesn't manage required attributes
- * very well. The only reason we factored this out was because RemoveForeignElements
- * also needed it besides ValidateAttributes.
- */
- class HTMLPurifier_AttrValidator
- {
- /**
- * Validates the attributes of a token, mutating it as necessary.
- * that has valid tokens
- * @param HTMLPurifier_Token $token Token to validate.
- * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
- * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
- */
- public function validateToken($token, $config, $context)
- {
- $definition = $config->getHTMLDefinition();
- $e =& $context->get('ErrorCollector', true);
- // initialize IDAccumulator if necessary
- $ok =& $context->get('IDAccumulator', true);
- if (!$ok) {
- $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
- $context->register('IDAccumulator', $id_accumulator);
- }
- // initialize CurrentToken if necessary
- $current_token =& $context->get('CurrentToken', true);
- if (!$current_token) {
- $context->register('CurrentToken', $token);
- }
- if (!$token instanceof HTMLPurifier_Token_Start &&
- !$token instanceof HTMLPurifier_Token_Empty
- ) {
- return;
- }
- // create alias to global definition array, see also $defs
- // DEFINITION CALL
- $d_defs = $definition->info_global_attr;
- // don't update token until the very end, to ensure an atomic update
- $attr = $token->attr;
- // do global transformations (pre)
- // nothing currently utilizes this
- foreach ($definition->info_attr_transform_pre as $transform) {
- $attr = $transform->transform($o = $attr, $config, $context);
- if ($e) {
- if ($attr != $o) {
- $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
- }
- }
- }
- // do local transformations only applicable to this element (pre)
- // ex. <p align="right"> to <p style="text-align:right;">
- foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
- $attr = $transform->transform($o = $attr, $config, $context);
- if ($e) {
- if ($attr != $o) {
- $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
- }
- }
- }
- // create alias to this element's attribute definition array, see
- // also $d_defs (global attribute definition array)
- // DEFINITION CALL
- $defs = $definition->info[$token->name]->attr;
- $attr_key = false;
- $context->register('CurrentAttr', $attr_key);
- // iterate through all the attribute keypairs
- // Watch out for name collisions: $key has previously been used
- foreach ($attr as $attr_key => $value) {
- // call the definition
- if (isset($defs[$attr_key])) {
- // there is a local definition defined
- if ($defs[$attr_key] === false) {
- // We've explicitly been told not to allow this element.
- // This is usually when there's a global definition
- // that must be overridden.
- // Theoretically speaking, we could have a
- // AttrDef_DenyAll, but this is faster!
- $result = false;
- } else {
- // validate according to the element's definition
- $result = $defs[$attr_key]->validate(
- $value,
- $config,
- $context
- );
- }
- } elseif (isset($d_defs[$attr_key])) {
- // there is a global definition defined, validate according
- // to the global definition
- $result = $d_defs[$attr_key]->validate(
- $value,
- $config,
- $context
- );
- } else {
- // system never heard of the attribute? DELETE!
- $result = false;
- }
- // put the results into effect
- if ($result === false || $result === null) {
- // this is a generic error message that should replaced
- // with more specific ones when possible
- if ($e) {
- $e->send(E_ERROR, 'AttrValidator: Attribute removed');
- }
- // remove the attribute
- unset($attr[$attr_key]);
- } elseif (is_string($result)) {
- // generally, if a substitution is happening, there
- // was some sort of implicit correction going on. We'll
- // delegate it to the attribute classes to say exactly what.
- // simple substitution
- $attr[$attr_key] = $result;
- } else {
- // nothing happens
- }
- // we'd also want slightly more complicated substitution
- // involving an array as the return value,
- // although we're not sure how colliding attributes would
- // resolve (certain ones would be completely overriden,
- // others would prepend themselves).
- }
- $context->destroy('CurrentAttr');
- // post transforms
- // global (error reporting untested)
- foreach ($definition->info_attr_transform_post as $transform) {
- $attr = $transform->transform($o = $attr, $config, $context);
- if ($e) {
- if ($attr != $o) {
- $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
- }
- }
- }
- // local (error reporting untested)
- foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
- $attr = $transform->transform($o = $attr, $config, $context);
- if ($e) {
- if ($attr != $o) {
- $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
- }
- }
- }
- $token->attr = $attr;
- // destroy CurrentToken if we made it ourselves
- if (!$current_token) {
- $context->destroy('CurrentToken');
- }
- }
- }
- // constants are slow, so we use as few as possible
- if (!defined('HTMLPURIFIER_PREFIX')) {
- define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone');
- set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());
- }
- // accomodations for versions earlier than 5.0.2
- // borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
- if (!defined('PHP_EOL')) {
- switch (strtoupper(substr(PHP_OS, 0, 3))) {
- case 'WIN':
- define('PHP_EOL', "\r\n");
- break;
- case 'DAR':
- define('PHP_EOL', "\r");
- break;
- default:
- define('PHP_EOL', "\n");
- }
- }
- /**
- * Bootstrap class that contains meta-functionality for HTML Purifier such as
- * the autoload function.
- *
- * @note
- * This class may be used without any other files from HTML Purifier.
- */
- class HTMLPurifier_Bootstrap
- {
- /**
- * Autoload function for HTML Purifier
- * @param string $class Class to load
- * @return bool
- */
- public static function autoload($class)
- {
- $file = HTMLPurifier_Bootstrap::getPath($class);
- if (!$file) {
- return false;
- }
- // Technically speaking, it should be ok and more efficient to
- // just do 'require', but Antonio Parraga reports that with
- // Zend extensions such as Zend debugger and APC, this invariant
- // may be broken. Since we have efficient alternatives, pay
- // the cost here and avoid the bug.
- require_once HTMLPURIFIER_PREFIX . '/' . $file;
- return true;
- }
- /**
- * Returns the path for a specific class.
- * @param string $class Class path to get
- * @return string
- */
- public static function getPath($class)
- {
- if (strncmp('HTMLPurifier', $class, 12) !== 0) {
- return false;
- }
- // Custom implementations
- if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
- $code = str_replace('_', '-', substr($class, 22));
- $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
- } else {
- $file = str_replace('_', '/', $class) . '.php';
- }
- if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
- return false;
- }
- return $file;
- }
- /**
- * "Pre-registers" our autoloader on the SPL stack.
- */
- public static function registerAutoload()
- {
- $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
- if (($funcs = spl_autoload_functions()) === false) {
- spl_autoload_register($autoload);
- } elseif (function_exists('spl_autoload_unregister')) {
- if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
- // prepend flag exists, no need for shenanigans
- spl_autoload_register($autoload, true, true);
- } else {
- $buggy = version_compare(PHP_VERSION, '5.2.11', '<');
- $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
- version_compare(PHP_VERSION, '5.1.0', '>=');
- foreach ($funcs as $func) {
- if ($buggy && is_array($func)) {
- // :TRICKY: There are some compatibility issues and some
- // places where we need to error out
- $reflector = new ReflectionMethod($func[0], $func[1]);
- if (!$reflector->isStatic()) {
- throw new Exception(
- 'HTML Purifier autoloader registrar is not compatible
- with non-static object methods due to PHP Bug #44144;
- Please do not use HTMLPurifier.autoload.php (or any
- file that includes this file); instead, place the code:
- spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
- after your own autoloaders.'
- );
- }
- // Suprisingly, spl_autoload_register supports the
- // Class::staticMethod callback format, although call_user_func doesn't
- if ($compat) {
- $func = implode('::', $func);
- }
- }
- spl_autoload_unregister($func);
- }
- spl_autoload_register($autoload);
- foreach ($funcs as $func) {
- spl_autoload_register($func);
- }
- }
- }
- }
- }
- /**
- * Super-class for definition datatype objects, implements serialization
- * functions for the class.
- */
- abstract class HTMLPurifier_Definition
- {
- /**
- * Has setup() been called yet?
- * @type bool
- */
- public $setup = false;
- /**
- * If true, write out the final definition object to the cache after
- * setup. This will be true only if all invocations to get a raw
- * definition object are also optimized. This does not cause file
- * system thrashing because on subsequent calls the cached object
- * is used and any writes to the raw definition object are short
- * circuited. See enduser-customize.html for the high-level
- * picture.
- * @type bool
- */
- public $optimized = null;
- /**
- * What type of definition is it?
- * @type string
- */
- public $type;
- /**
- * Sets up the definition object into the final form, something
- * not done by the constructor
- * @param HTMLPurifier_Config $config
- */
- abstract protected function doSetup($config);
- /**
- * Setup function that aborts if already setup
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- if ($this->setup) {
- return;
- }
- $this->setup = true;
- $this->doSetup($config);
- }
- }
- /**
- * Defines allowed CSS attributes and what their values are.
- * @see HTMLPurifier_HTMLDefinition
- */
- class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
- {
- public $type = 'CSS';
- /**
- * Assoc array of attribute name to definition object.
- * @type HTMLPurifier_AttrDef[]
- */
- public $info = array();
- /**
- * Constructs the info array. The meat of this class.
- * @param HTMLPurifier_Config $config
- */
- protected function doSetup($config)
- {
- $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
- array('left', 'right', 'center', 'justify'),
- false
- );
- $border_style =
- $this->info['border-bottom-style'] =
- $this->info['border-right-style'] =
- $this->info['border-left-style'] =
- $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
- array(
- 'none',
- 'hidden',
- 'dotted',
- 'dashed',
- 'solid',
- 'double',
- 'groove',
- 'ridge',
- 'inset',
- 'outset'
- ),
- false
- );
- $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
- $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
- array('none', 'left', 'right', 'both'),
- false
- );
- $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
- array('none', 'left', 'right'),
- false
- );
- $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
- array('normal', 'italic', 'oblique'),
- false
- );
- $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
- array('normal', 'small-caps'),
- false
- );
- $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('none')),
- new HTMLPurifier_AttrDef_CSS_URI()
- )
- );
- $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
- array('inside', 'outside'),
- false
- );
- $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
- array(
- 'disc',
- 'circle',
- 'square',
- 'decimal',
- 'lower-roman',
- 'upper-roman',
- 'lower-alpha',
- 'upper-alpha',
- 'none'
- ),
- false
- );
- $this->info['list-style-image'] = $uri_or_none;
- $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
- $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
- array('capitalize', 'uppercase', 'lowercase', 'none'),
- false
- );
- $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['background-image'] = $uri_or_none;
- $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
- array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
- );
- $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
- array('scroll', 'fixed')
- );
- $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
- $border_color =
- $this->info['border-top-color'] =
- $this->info['border-bottom-color'] =
- $this->info['border-left-color'] =
- $this->info['border-right-color'] =
- $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('transparent')),
- new HTMLPurifier_AttrDef_CSS_Color()
- )
- );
- $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
- $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
- $border_width =
- $this->info['border-top-width'] =
- $this->info['border-bottom-width'] =
- $this->info['border-left-width'] =
- $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
- new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
- )
- );
- $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
- $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Length()
- )
- );
- $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Length()
- )
- );
- $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(
- array(
- 'xx-small',
- 'x-small',
- 'small',
- 'medium',
- 'large',
- 'x-large',
- 'xx-large',
- 'larger',
- 'smaller'
- )
- ),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_CSS_Length()
- )
- );
- $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true)
- )
- );
- $margin =
- $this->info['margin-top'] =
- $this->info['margin-bottom'] =
- $this->info['margin-left'] =
- $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- )
- );
- $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
- // non-negative
- $padding =
- $this->info['padding-top'] =
- $this->info['padding-bottom'] =
- $this->info['padding-left'] =
- $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true)
- )
- );
- $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
- $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage()
- )
- );
- $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- )
- );
- $max = $config->get('CSS.MaxImgLength');
- $this->info['width'] =
- $this->info['height'] =
- $max === null ?
- $trusted_wh :
- new HTMLPurifier_AttrDef_Switch(
- 'img',
- // For img tags:
- new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length('0', $max),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- )
- ),
- // For everyone else:
- $trusted_wh
- );
- $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
- $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
- // this could use specialized code
- $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
- array(
- 'normal',
- 'bold',
- 'bolder',
- 'lighter',
- '100',
- '200',
- '300',
- '400',
- '500',
- '600',
- '700',
- '800',
- '900'
- ),
- false
- );
- // MUST be called after other font properties, as it references
- // a CSSDefinition object
- $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
- // same here
- $this->info['border'] =
- $this->info['border-bottom'] =
- $this->info['border-top'] =
- $this->info['border-left'] =
- $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
- $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
- array('collapse', 'separate')
- );
- $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
- array('top', 'bottom')
- );
- $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
- array('auto', 'fixed')
- );
- $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Enum(
- array(
- 'baseline',
- 'sub',
- 'super',
- 'top',
- 'text-top',
- 'middle',
- 'bottom',
- 'text-bottom'
- )
- ),
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage()
- )
- );
- $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
- // These CSS properties don't work on many browsers, but we live
- // in THE FUTURE!
- $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
- array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
- );
- if ($config->get('CSS.Proprietary')) {
- $this->doSetupProprietary($config);
- }
- if ($config->get('CSS.AllowTricky')) {
- $this->doSetupTricky($config);
- }
- if ($config->get('CSS.Trusted')) {
- $this->doSetupTrusted($config);
- }
- $allow_important = $config->get('CSS.AllowImportant');
- // wrap all attr-defs with decorator that handles !important
- foreach ($this->info as $k => $v) {
- $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
- }
- $this->setupConfigStuff($config);
- }
- /**
- * @param HTMLPurifier_Config $config
- */
- protected function doSetupProprietary($config)
- {
- // Internet Explorer only scrollbar colors
- $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- // technically not proprietary, but CSS3, and no one supports it
- $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
- $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
- $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
- // only opacity, for now
- $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
- // more CSS3
- $this->info['page-break-after'] =
- $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
- array(
- 'auto',
- 'always',
- 'avoid',
- 'left',
- 'right'
- )
- );
- $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
- }
- /**
- * @param HTMLPurifier_Config $config
- */
- protected function doSetupTricky($config)
- {
- $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
- array(
- 'inline',
- 'block',
- 'list-item',
- 'run-in',
- 'compact',
- 'marker',
- 'table',
- 'inline-block',
- 'inline-table',
- 'table-row-group',
- 'table-header-group',
- 'table-footer-group',
- 'table-row',
- 'table-column-group',
- 'table-column',
- 'table-cell',
- 'table-caption',
- 'none'
- )
- );
- $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
- array('visible', 'hidden', 'collapse')
- );
- $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
- }
- /**
- * @param HTMLPurifier_Config $config
- */
- protected function doSetupTrusted($config)
- {
- $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
- array('static', 'relative', 'absolute', 'fixed')
- );
- $this->info['top'] =
- $this->info['left'] =
- $this->info['right'] =
- $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_Enum(array('auto')),
- )
- );
- $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
- array(
- new HTMLPurifier_AttrDef_Integer(),
- new HTMLPurifier_AttrDef_Enum(array('auto')),
- )
- );
- }
- /**
- * Performs extra config-based processing. Based off of
- * HTMLPurifier_HTMLDefinition.
- * @param HTMLPurifier_Config $config
- * @todo Refactor duplicate elements into common class (probably using
- * composition, not inheritance).
- */
- protected function setupConfigStuff($config)
- {
- // setup allowed elements
- $support = "(for information on implementing this, see the " .
- "support forums) ";
- $allowed_properties = $config->get('CSS.AllowedProperties');
- if ($allowed_properties !== null) {
- foreach ($this->info as $name => $d) {
- if (!isset($allowed_properties[$name])) {
- unset($this->info[$name]);
- }
- unset($allowed_properties[$name]);
- }
- // emit errors
- foreach ($allowed_properties as $name => $d) {
- // :TODO: Is this htmlspecialchars() call really necessary?
- $name = htmlspecialchars($name);
- trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
- }
- }
- $forbidden_properties = $config->get('CSS.ForbiddenProperties');
- if ($forbidden_properties !== null) {
- foreach ($this->info as $name => $d) {
- if (isset($forbidden_properties[$name])) {
- unset($this->info[$name]);
- }
- }
- }
- }
- }
- /**
- * Defines allowed child nodes and validates nodes against it.
- */
- abstract class HTMLPurifier_ChildDef
- {
- /**
- * Type of child definition, usually right-most part of class name lowercase.
- * Used occasionally in terms of context.
- * @type string
- */
- public $type;
- /**
- * Indicates whether or not an empty array of children is okay.
- *
- * This is necessary for redundant checking when changes affecting
- * a child node may cause a parent node to now be disallowed.
- * @type bool
- */
- public $allow_empty;
- /**
- * Lookup array of all elements that this definition could possibly allow.
- * @type array
- */
- public $elements = array();
- /**
- * Get lookup of tag names that should not close this element automatically.
- * All other elements will do so.
- * @param HTMLPurifier_Config $config HTMLPurifier_Config object
- * @return array
- */
- public function getAllowedElements($config)
- {
- return $this->elements;
- }
- /**
- * Validates nodes according to definition and returns modification.
- *
- * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
- * @param HTMLPurifier_Config $config HTMLPurifier_Config object
- * @param HTMLPurifier_Context $context HTMLPurifier_Context object
- * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
- */
- abstract public function validateChildren($children, $config, $context);
- }
- /**
- * Configuration object that triggers customizable behavior.
- *
- * @warning This class is strongly defined: that means that the class
- * will fail if an undefined directive is retrieved or set.
- *
- * @note Many classes that could (although many times don't) use the
- * configuration object make it a mandatory parameter. This is
- * because a configuration object should always be forwarded,
- * otherwise, you run the risk of missing a parameter and then
- * being stumped when a configuration directive doesn't work.
- *
- * @todo Reconsider some of the public member variables
- */
- class HTMLPurifier_Config
- {
- /**
- * HTML Purifier's version
- * @type string
- */
- public $version = '4.6.0';
- /**
- * Whether or not to automatically finalize
- * the object if a read operation is done.
- * @type bool
- */
- public $autoFinalize = true;
- // protected member variables
- /**
- * Namespace indexed array of serials for specific namespaces.
- * @see getSerial() for more info.
- * @type string[]
- */
- protected $serials = array();
- /**
- * Serial for entire configuration object.
- * @type string
- */
- protected $serial;
- /**
- * Parser for variables.
- * @type HTMLPurifier_VarParser_Flexible
- */
- protected $parser = null;
- /**
- * Reference HTMLPurifier_ConfigSchema for value checking.
- * @type HTMLPurifier_ConfigSchema
- * @note This is public for introspective purposes. Please don't
- * abuse!
- */
- public $def;
- /**
- * Indexed array of definitions.
- * @type HTMLPurifier_Definition[]
- */
- protected $definitions;
- /**
- * Whether or not config is finalized.
- * @type bool
- */
- protected $finalized = false;
- /**
- * Property list containing configuration directives.
- * @type array
- */
- protected $plist;
- /**
- * Whether or not a set is taking place due to an alias lookup.
- * @type bool
- */
- private $aliasMode;
- /**
- * Set to false if you do not want line and file numbers in errors.
- * (useful when unit testing). This will also compress some errors
- * and exceptions.
- * @type bool
- */
- public $chatty = true;
- /**
- * Current lock; only gets to this namespace are allowed.
- * @type string
- */
- private $lock;
- /**
- * Constructor
- * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
- * what directives are allowed.
- * @param HTMLPurifier_PropertyList $parent
- */
- public function __construct($definition, $parent = null)
- {
- $parent = $parent ? $parent : $definition->defaultPlist;
- $this->plist = new HTMLPurifier_PropertyList($parent);
- $this->def = $definition; // keep a copy around for checking
- $this->parser = new HTMLPurifier_VarParser_Flexible();
- }
- /**
- * Convenience constructor that creates a config object based on a mixed var
- * @param mixed $config Variable that defines the state of the config
- * object. Can be: a HTMLPurifier_Config() object,
- * an array of directives based on loadArray(),
- * or a string filename of an ini file.
- * @param HTMLPurifier_ConfigSchema $schema Schema object
- * @return HTMLPurifier_Config Configured object
- */
- public static function create($config, $schema = null)
- {
- if ($config instanceof HTMLPurifier_Config) {
- // pass-through
- return $config;
- }
- if (!$schema) {
- $ret = HTMLPurifier_Config::createDefault();
- } else {
- $ret = new HTMLPurifier_Config($schema);
- }
- if (is_string($config)) {
- $ret->loadIni($config);
- } elseif (is_array($config)) $ret->loadArray($config);
- return $ret;
- }
- /**
- * Creates a new config object that inherits from a previous one.
- * @param HTMLPurifier_Config $config Configuration object to inherit from.
- * @return HTMLPurifier_Config object with $config as its parent.
- */
- public static function inherit(HTMLPurifier_Config $config)
- {
- return new HTMLPurifier_Config($config->def, $config->plist);
- }
- /**
- * Convenience constructor that creates a default configuration object.
- * @return HTMLPurifier_Config default object.
- */
- public static function createDefault()
- {
- $definition = HTMLPurifier_ConfigSchema::instance();
- $config = new HTMLPurifier_Config($definition);
- return $config;
- }
- /**
- * Retrieves a value from the configuration.
- *
- * @param string $key String key
- * @param mixed $a
- *
- * @return mixed
- */
- public function get($key, $a = null)
- {
- if ($a !== null) {
- $this->triggerError(
- "Using deprecated API: use \$config->get('$key.$a') instead",
- E_USER_WARNING
- );
- $key = "$key.$a";
- }
- if (!$this->finalized) {
- $this->autoFinalize();
- }
- if (!isset($this->def->info[$key])) {
- // can't add % due to SimpleTest bug
- $this->triggerError(
- 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
- E_USER_WARNING
- );
- return;
- }
- if (isset($this->def->info[$key]->isAlias)) {
- $d = $this->def->info[$key];
- $this->triggerError(
- 'Cannot get value from aliased directive, use real name ' . $d->key,
- E_USER_ERROR
- );
- return;
- }
- if ($this->lock) {
- list($ns) = explode('.', $key);
- if ($ns !== $this->lock) {
- $this->triggerError(
- 'Cannot get value of namespace ' . $ns . ' when lock for ' .
- $this->lock .
- ' is active, this probably indicates a Definition setup method ' .
- 'is accessing directives that are not within its namespace',
- E_USER_ERROR
- );
- return;
- }
- }
- return $this->plist->get($key);
- }
- /**
- * Retrieves an array of directives to values from a given namespace
- *
- * @param string $namespace String namespace
- *
- * @return array
- */
- public function getBatch($namespace)
- {
- if (!$this->finalized) {
- $this->autoFinalize();
- }
- $full = $this->getAll();
- if (!isset($full[$namespace])) {
- $this->triggerError(
- 'Cannot retrieve undefined namespace ' .
- htmlspecialchars($namespace),
- E_USER_WARNING
- );
- return;
- }
- return $full[$namespace];
- }
- /**
- * Returns a SHA-1 signature of a segment of the configuration object
- * that uniquely identifies that particular configuration
- *
- * @param string $namespace Namespace to get serial for
- *
- * @return string
- * @note Revision is handled specially and is removed from the batch
- * before processing!
- */
- public function getBatchSerial($namespace)
- {
- if (empty($this->serials[$namespace])) {
- $batch = $this->getBatch($namespace);
- unset($batch['DefinitionRev']);
- $this->serials[$namespace] = sha1(serialize($batch));
- }
- return $this->serials[$namespace];
- }
- /**
- * Returns a SHA-1 signature for the entire configuration object
- * that uniquely identifies that particular configuration
- *
- * @return string
- */
- public function getSerial()
- {
- if (empty($this->serial)) {
- $this->serial = sha1(serialize($this->getAll()));
- }
- return $this->serial;
- }
- /**
- * Retrieves all directives, organized by namespace
- *
- * @warning This is a pretty inefficient function, avoid if you can
- */
- public function getAll()
- {
- if (!$this->finalized) {
- $this->autoFinalize();
- }
- $ret = array();
- foreach ($this->plist->squash() as $name => $value) {
- list($ns, $key) = explode('.', $name, 2);
- $ret[$ns][$key] = $value;
- }
- return $ret;
- }
- /**
- * Sets a value to configuration.
- *
- * @param string $key key
- * @param mixed $value value
- * @param mixed $a
- */
- public function set($key, $value, $a = null)
- {
- if (strpos($key, '.') === false) {
- $namespace = $key;
- $directive = $value;
- $value = $a;
- $key = "$key.$directive";
- $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
- } else {
- list($namespace) = explode('.', $key);
- }
- if ($this->isFinalized('Cannot set directive after finalization')) {
- return;
- }
- if (!isset($this->def->info[$key])) {
- $this->triggerError(
- 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
- E_USER_WARNING
- );
- return;
- }
- $def = $this->def->info[$key];
- if (isset($def->isAlias)) {
- if ($this->aliasMode) {
- $this->triggerError(
- 'Double-aliases not allowed, please fix '.
- 'ConfigSchema bug with' . $key,
- E_USER_ERROR
- );
- return;
- }
- $this->aliasMode = true;
- $this->set($def->key, $value);
- $this->aliasMode = false;
- $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
- return;
- }
- // Raw type might be negative when using the fully optimized form
- // of stdclass, which indicates allow_null == true
- $rtype = is_int($def) ? $def : $def->type;
- if ($rtype < 0) {
- $type = -$rtype;
- $allow_null = true;
- } else {
- $type = $rtype;
- $allow_null = isset($def->allow_null);
- }
- try {
- $value = $this->parser->parse($value, $type, $allow_null);
- } catch (HTMLPurifier_VarParserException $e) {
- $this->triggerError(
- 'Value for ' . $key . ' is of invalid type, should be ' .
- HTMLPurifier_VarParser::getTypeName($type),
- E_USER_WARNING
- );
- return;
- }
- if (is_string($value) && is_object($def)) {
- // resolve value alias if defined
- if (isset($def->aliases[$value])) {
- $value = $def->aliases[$value];
- }
- // check to see if the value is allowed
- if (isset($def->allowed) && !isset($def->allowed[$value])) {
- $this->triggerError(
- 'Value not supported, valid values are: ' .
- $this->_listify($def->allowed),
- E_USER_WARNING
- );
- return;
- }
- }
- $this->plist->set($key, $value);
- // reset definitions if the directives they depend on changed
- // this is a very costly process, so it's discouraged
- // with finalization
- if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
- $this->definitions[$namespace] = null;
- }
- $this->serials[$namespace] = false;
- }
- /**
- * Convenience function for error reporting
- *
- * @param array $lookup
- *
- * @return string
- */
- private function _listify($lookup)
- {
- $list = array();
- foreach ($lookup as $name => $b) {
- $list[] = $name;
- }
- return implode(', ', $list);
- }
- /**
- * Retrieves object reference to the HTML definition.
- *
- * @param bool $raw Return a copy that has not been setup yet. Must be
- * called before it's been setup, otherwise won't work.
- * @param bool $optimized If true, this method may return null, to
- * indicate that a cached version of the modified
- * definition object is available and no further edits
- * are necessary. Consider using
- * maybeGetRawHTMLDefinition, which is more explicitly
- * named, instead.
- *
- * @return HTMLPurifier_HTMLDefinition
- */
- public function getHTMLDefinition($raw = false, $optimized = false)
- {
- return $this->getDefinition('HTML', $raw, $optimized);
- }
- /**
- * Retrieves object reference to the CSS definition
- *
- * @param bool $raw Return a copy that has not been setup yet. Must be
- * called before it's been setup, otherwise won't work.
- * @param bool $optimized If true, this method may return null, to
- * indicate that a cached version of the modified
- * definition object is available and no further edits
- * are necessary. Consider using
- * maybeGetRawCSSDefinition, which is more explicitly
- * named, instead.
- *
- * @return HTMLPurifier_CSSDefinition
- */
- public function getCSSDefinition($raw = false, $optimized = false)
- {
- return $this->getDefinition('CSS', $raw, $optimized);
- }
- /**
- * Retrieves object reference to the URI definition
- *
- * @param bool $raw Return a copy that has not been setup yet. Must be
- * called before it's been setup, otherwise won't work.
- * @param bool $optimized If true, this method may return null, to
- * indicate that a cached version of the modified
- * definition object is available and no further edits
- * are necessary. Consider using
- * maybeGetRawURIDefinition, which is more explicitly
- * named, instead.
- *
- * @return HTMLPurifier_URIDefinition
- */
- public function getURIDefinition($raw = false, $optimized = false)
- {
- return $this->getDefinition('URI', $raw, $optimized);
- }
- /**
- * Retrieves a definition
- *
- * @param string $type Type of definition: HTML, CSS, etc
- * @param bool $raw Whether or not definition should be returned raw
- * @param bool $optimized Only has an effect when $raw is true. Whether
- * or not to return null if the result is already present in
- * the cache. This is off by default for backwards
- * compatibility reasons, but you need to do things this
- * way in order to ensure that caching is done properly.
- * Check out enduser-customize.html for more details.
- * We probably won't ever change this default, as much as the
- * maybe semantics is the "right thing to do."
- *
- * @throws HTMLPurifier_Exception
- * @return HTMLPurifier_Definition
- */
- public function getDefinition($type, $raw = false, $optimized = false)
- {
- if ($optimized && !$raw) {
- throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
- }
- if (!$this->finalized) {
- $this->autoFinalize();
- }
- // temporarily suspend locks, so we can handle recursive definition calls
- $lock = $this->lock;
- $this->lock = null;
- $factory = HTMLPurifier_DefinitionCacheFactory::instance();
- $cache = $factory->create($type, $this);
- $this->lock = $lock;
- if (!$raw) {
- // full definition
- // ---------------
- // check if definition is in memory
- if (!empty($this->definitions[$type])) {
- $def = $this->definitions[$type];
- // check if the definition is setup
- if ($def->setup) {
- return $def;
- } else {
- $def->setup($this);
- if ($def->optimized) {
- $cache->add($def, $this);
- }
- return $def;
- }
- }
- // check if definition is in cache
- $def = $cache->get($this);
- if ($def) {
- // definition in cache, save to memory and return it
- $this->definitions[$type] = $def;
- return $def;
- }
- // initialize it
- $def = $this->initDefinition($type);
- // set it up
- $this->lock = $type;
- $def->setup($this);
- $this->lock = null;
- // save in cache
- $cache->add($def, $this);
- // return it
- return $def;
- } else {
- // raw definition
- // --------------
- // check preconditions
- $def = null;
- if ($optimized) {
- if (is_null($this->get($type . '.DefinitionID'))) {
- // fatally error out if definition ID not set
- throw new HTMLPurifier_Exception(
- "Cannot retrieve raw version without specifying %$type.DefinitionID"
- );
- }
- }
- if (!empty($this->definitions[$type])) {
- $def = $this->definitions[$type];
- if ($def->setup && !$optimized) {
- $extra = $this->chatty ?
- " (try moving this code block earlier in your initialization)" :
- "";
- throw new HTMLPurifier_Exception(
- "Cannot retrieve raw definition after it has already been setup" .
- $extra
- );
- }
- if ($def->optimized === null) {
- $extra = $this->chatty ? " (try flushing your cache)" : "";
- throw new HTMLPurifier_Exception(
- "Optimization status of definition is unknown" . $extra
- );
- }
- if ($def->optimized !== $optimized) {
- $msg = $optimized ? "optimized" : "unoptimized";
- $extra = $this->chatty ?
- " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
- : "";
- throw new HTMLPurifier_Exception(
- "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
- );
- }
- }
- // check if definition was in memory
- if ($def) {
- if ($def->setup) {
- // invariant: $optimized === true (checked above)
- return null;
- } else {
- return $def;
- }
- }
- // if optimized, check if definition was in cache
- // (because we do the memory check first, this formulation
- // is prone to cache slamming, but I think
- // guaranteeing that either /all/ of the raw
- // setup code or /none/ of it is run is more important.)
- if ($optimized) {
- // This code path only gets run once; once we put
- // something in $definitions (which is guaranteed by the
- // trailing code), we always short-circuit above.
- $def = $cache->get($this);
- if ($def) {
- // save the full definition for later, but don't
- // return it yet
- $this->definitions[$type] = $def;
- return null;
- }
- }
- // check invariants for creation
- if (!$optimized) {
- if (!is_null($this->get($type . '.DefinitionID'))) {
- if ($this->chatty) {
- $this->triggerError(
- 'Due to a documentation error in previous version of HTML Purifier, your ' .
- 'definitions are not being cached. If this is OK, you can remove the ' .
- '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
- 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
- 'value is null before making any edits (if it is null, that means that a ' .
- 'cached version is available, and no raw operations are necessary). See ' .
- '<a href="http://htmlpurifier.org/docs/enduser-customize.html#optimized">' .
- 'Customize</a> for more details',
- E_USER_WARNING
- );
- } else {
- $this->triggerError(
- "Useless DefinitionID declaration",
- E_USER_WARNING
- );
- }
- }
- }
- // initialize it
- $def = $this->initDefinition($type);
- $def->optimized = $optimized;
- return $def;
- }
- throw new HTMLPurifier_Exception("The impossible happened!");
- }
- /**
- * Initialise definition
- *
- * @param string $type What type of definition to create
- *
- * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
- * @throws HTMLPurifier_Exception
- */
- private function initDefinition($type)
- {
- // quick checks failed, let's create the object
- if ($type == 'HTML') {
- $def = new HTMLPurifier_HTMLDefinition();
- } elseif ($type == 'CSS') {
- $def = new HTMLPurifier_CSSDefinition();
- } elseif ($type == 'URI') {
- $def = new HTMLPurifier_URIDefinition();
- } else {
- throw new HTMLPurifier_Exception(
- "Definition of $type type not supported"
- );
- }
- $this->definitions[$type] = $def;
- return $def;
- }
- public function maybeGetRawDefinition($name)
- {
- return $this->getDefinition($name, true, true);
- }
- public function maybeGetRawHTMLDefinition()
- {
- return $this->getDefinition('HTML', true, true);
- }
- public function maybeGetRawCSSDefinition()
- {
- return $this->getDefinition('CSS', true, true);
- }
- public function maybeGetRawURIDefinition()
- {
- return $this->getDefinition('URI', true, true);
- }
- /**
- * Loads configuration values from an array with the following structure:
- * Namespace.Directive => Value
- *
- * @param array $config_array Configuration associative array
- */
- public function loadArray($config_array)
- {
- if ($this->isFinalized('Cannot load directives after finalization')) {
- return;
- }
- foreach ($config_array as $key => $value) {
- $key = str_replace('_', '.', $key);
- if (strpos($key, '.') !== false) {
- $this->set($key, $value);
- } else {
- $namespace = $key;
- $namespace_values = $value;
- foreach ($namespace_values as $directive => $value2) {
- $this->set($namespace .'.'. $directive, $value2);
- }
- }
- }
- }
- /**
- * Returns a list of array(namespace, directive) for all directives
- * that are allowed in a web-form context as per an allowed
- * namespaces/directives list.
- *
- * @param array $allowed List of allowed namespaces/directives
- * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
- *
- * @return array
- */
- public static function getAllowedDirectivesForForm($allowed, $schema = null)
- {
- if (!$schema) {
- $schema = HTMLPurifier_ConfigSchema::instance();
- }
- if ($allowed !== true) {
- if (is_string($allowed)) {
- $allowed = array($allowed);
- }
- $allowed_ns = array();
- $allowed_directives = array();
- $blacklisted_directives = array();
- foreach ($allowed as $ns_or_directive) {
- if (strpos($ns_or_directive, '.') !== false) {
- // directive
- if ($ns_or_directive[0] == '-') {
- $blacklisted_directives[substr($ns_or_directive, 1)] = true;
- } else {
- $allowed_directives[$ns_or_directive] = true;
- }
- } else {
- // namespace
- $allowed_ns[$ns_or_directive] = true;
- }
- }
- }
- $ret = array();
- foreach ($schema->info as $key => $def) {
- list($ns, $directive) = explode('.', $key, 2);
- if ($allowed !== true) {
- if (isset($blacklisted_directives["$ns.$directive"])) {
- continue;
- }
- if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
- continue;
- }
- }
- if (isset($def->isAlias)) {
- continue;
- }
- if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
- continue;
- }
- $ret[] = array($ns, $directive);
- }
- return $ret;
- }
- /**
- * Loads configuration values from $_GET/$_POST that were posted
- * via ConfigForm
- *
- * @param array $array $_GET or $_POST array to import
- * @param string|bool $index Index/name that the config variables are in
- * @param array|bool $allowed List of allowed namespaces/directives
- * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
- * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
- *
- * @return mixed
- */
- public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
- {
- $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
- $config = HTMLPurifier_Config::create($ret, $schema);
- return $config;
- }
- /**
- * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
- *
- * @param array $array $_GET or $_POST array to import
- * @param string|bool $index Index/name that the config variables are in
- * @param array|bool $allowed List of allowed namespaces/directives
- * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
- */
- public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
- {
- $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
- $this->loadArray($ret);
- }
- /**
- * Prepares an array from a form into something usable for the more
- * strict parts of HTMLPurifier_Config
- *
- * @param array $array $_GET or $_POST array to import
- * @param string|bool $index Index/name that the config variables are in
- * @param array|bool $allowed List of allowed namespaces/directives
- * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
- * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
- *
- * @return array
- */
- public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
- {
- if ($index !== false) {
- $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
- }
- $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
- $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
- $ret = array();
- foreach ($allowed as $key) {
- list($ns, $directive) = $key;
- $skey = "$ns.$directive";
- if (!empty($array["Null_$skey"])) {
- $ret[$ns][$directive] = null;
- continue;
- }
- if (!isset($array[$skey])) {
- continue;
- }
- $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
- $ret[$ns][$directive] = $value;
- }
- return $ret;
- }
- /**
- * Loads configuration values from an ini file
- *
- * @param string $filename Name of ini file
- */
- public function loadIni($filename)
- {
- if ($this->isFinalized('Cannot load directives after finalization')) {
- return;
- }
- $array = parse_ini_file($filename, true);
- $this->loadArray($array);
- }
- /**
- * Checks whether or not the configuration object is finalized.
- *
- * @param string|bool $error String error message, or false for no error
- *
- * @return bool
- */
- public function isFinalized($error = false)
- {
- if ($this->finalized && $error) {
- $this->triggerError($error, E_USER_ERROR);
- }
- return $this->finalized;
- }
- /**
- * Finalizes configuration only if auto finalize is on and not
- * already finalized
- */
- public function autoFinalize()
- {
- if ($this->autoFinalize) {
- $this->finalize();
- } else {
- $this->plist->squash(true);
- }
- }
- /**
- * Finalizes a configuration object, prohibiting further change
- */
- public function finalize()
- {
- $this->finalized = true;
- $this->parser = null;
- }
- /**
- * Produces a nicely formatted error message by supplying the
- * stack frame information OUTSIDE of HTMLPurifier_Config.
- *
- * @param string $msg An error message
- * @param int $no An error number
- */
- protected function triggerError($msg, $no)
- {
- // determine previous stack frame
- $extra = '';
- if ($this->chatty) {
- $trace = debug_backtrace();
- // zip(tail(trace), trace) -- but PHP is not Haskell har har
- for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
- // XXX this is not correct on some versions of HTML Purifier
- if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
- continue;
- }
- $frame = $trace[$i];
- $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
- break;
- }
- }
- trigger_error($msg . $extra, $no);
- }
- /**
- * Returns a serialized form of the configuration object that can
- * be reconstituted.
- *
- * @return string
- */
- public function serialize()
- {
- $this->getDefinition('HTML');
- $this->getDefinition('CSS');
- $this->getDefinition('URI');
- return serialize($this);
- }
- }
- /**
- * Configuration definition, defines directives and their defaults.
- */
- class HTMLPurifier_ConfigSchema
- {
- /**
- * Defaults of the directives and namespaces.
- * @type array
- * @note This shares the exact same structure as HTMLPurifier_Config::$conf
- */
- public $defaults = array();
- /**
- * The default property list. Do not edit this property list.
- * @type array
- */
- public $defaultPlist;
- /**
- * Definition of the directives.
- * The structure of this is:
- *
- * array(
- * 'Namespace' => array(
- * 'Directive' => new stdclass(),
- * )
- * )
- *
- * The stdclass may have the following properties:
- *
- * - If isAlias isn't set:
- * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
- * - allow_null: If set, this directive allows null values
- * - aliases: If set, an associative array of value aliases to real values
- * - allowed: If set, a lookup array of allowed (string) values
- * - If isAlias is set:
- * - namespace: Namespace this directive aliases to
- * - name: Directive name this directive aliases to
- *
- * In certain degenerate cases, stdclass will actually be an integer. In
- * that case, the value is equivalent to an stdclass with the type
- * property set to the integer. If the integer is negative, type is
- * equal to the absolute value of integer, and allow_null is true.
- *
- * This class is friendly with HTMLPurifier_Config. If you need introspection
- * about the schema, you're better of using the ConfigSchema_Interchange,
- * which uses more memory but has much richer information.
- * @type array
- */
- public $info = array();
- /**
- * Application-wide singleton
- * @type HTMLPurifier_ConfigSchema
- */
- protected static $singleton;
- public function __construct()
- {
- $this->defaultPlist = new HTMLPurifier_PropertyList();
- }
- /**
- * Unserializes the default ConfigSchema.
- * @return HTMLPurifier_ConfigSchema
- */
- public static function makeFromSerial()
- {
- $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
- $r = unserialize($contents);
- if (!$r) {
- $hash = sha1($contents);
- trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
- }
- return $r;
- }
- /**
- * Retrieves an instance of the application-wide configuration definition.
- * @param HTMLPurifier_ConfigSchema $prototype
- * @return HTMLPurifier_ConfigSchema
- */
- public static function instance($prototype = null)
- {
- if ($prototype !== null) {
- HTMLPurifier_ConfigSchema::$singleton = $prototype;
- } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
- HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
- }
- return HTMLPurifier_ConfigSchema::$singleton;
- }
- /**
- * Defines a directive for configuration
- * @warning Will fail of directive's namespace is defined.
- * @warning This method's signature is slightly different from the legacy
- * define() static method! Beware!
- * @param string $key Name of directive
- * @param mixed $default Default value of directive
- * @param string $type Allowed type of the directive. See
- * HTMLPurifier_DirectiveDef::$type for allowed values
- * @param bool $allow_null Whether or not to allow null values
- */
- public function add($key, $default, $type, $allow_null)
- {
- $obj = new stdclass();
- $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
- if ($allow_null) {
- $obj->allow_null = true;
- }
- $this->info[$key] = $obj;
- $this->defaults[$key] = $default;
- $this->defaultPlist->set($key, $default);
- }
- /**
- * Defines a directive value alias.
- *
- * Directive value aliases are convenient for developers because it lets
- * them set a directive to several values and get the same result.
- * @param string $key Name of Directive
- * @param array $aliases Hash of aliased values to the real alias
- */
- public function addValueAliases($key, $aliases)
- {
- if (!isset($this->info[$key]->aliases)) {
- $this->info[$key]->aliases = array();
- }
- foreach ($aliases as $alias => $real) {
- $this->info[$key]->aliases[$alias] = $real;
- }
- }
- /**
- * Defines a set of allowed values for a directive.
- * @warning This is slightly different from the corresponding static
- * method definition.
- * @param string $key Name of directive
- * @param array $allowed Lookup array of allowed values
- */
- public function addAllowedValues($key, $allowed)
- {
- $this->info[$key]->allowed = $allowed;
- }
- /**
- * Defines a directive alias for backwards compatibility
- * @param string $key Directive that will be aliased
- * @param string $new_key Directive that the alias will be to
- */
- public function addAlias($key, $new_key)
- {
- $obj = new stdclass;
- $obj->key = $new_key;
- $obj->isAlias = true;
- $this->info[$key] = $obj;
- }
- /**
- * Replaces any stdclass that only has the type property with type integer.
- */
- public function postProcess()
- {
- foreach ($this->info as $key => $v) {
- if (count((array) $v) == 1) {
- $this->info[$key] = $v->type;
- } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
- $this->info[$key] = -$v->type;
- }
- }
- }
- }
- /**
- * @todo Unit test
- */
- class HTMLPurifier_ContentSets
- {
- /**
- * List of content set strings (pipe separators) indexed by name.
- * @type array
- */
- public $info = array();
- /**
- * List of content set lookups (element => true) indexed by name.
- * @type array
- * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
- */
- public $lookup = array();
- /**
- * Synchronized list of defined content sets (keys of info).
- * @type array
- */
- protected $keys = array();
- /**
- * Synchronized list of defined content values (values of info).
- * @type array
- */
- protected $values = array();
- /**
- * Merges in module's content sets, expands identifiers in the content
- * sets and populates the keys, values and lookup member variables.
- * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
- */
- public function __construct($modules)
- {
- if (!is_array($modules)) {
- $modules = array($modules);
- }
- // populate content_sets based on module hints
- // sorry, no way of overloading
- foreach ($modules as $module) {
- foreach ($module->content_sets as $key => $value) {
- $temp = $this->convertToLookup($value);
- if (isset($this->lookup[$key])) {
- // add it into the existing content set
- $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
- } else {
- $this->lookup[$key] = $temp;
- }
- }
- }
- $old_lookup = false;
- while ($old_lookup !== $this->lookup) {
- $old_lookup = $this->lookup;
- foreach ($this->lookup as $i => $set) {
- $add = array();
- foreach ($set as $element => $x) {
- if (isset($this->lookup[$element])) {
- $add += $this->lookup[$element];
- unset($this->lookup[$i][$element]);
- }
- }
- $this->lookup[$i] += $add;
- }
- }
- foreach ($this->lookup as $key => $lookup) {
- $this->info[$key] = implode(' | ', array_keys($lookup));
- }
- $this->keys = array_keys($this->info);
- $this->values = array_values($this->info);
- }
- /**
- * Accepts a definition; generates and assigns a ChildDef for it
- * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
- * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
- */
- public function generateChildDef(&$def, $module)
- {
- if (!empty($def->child)) { // already done!
- return;
- }
- $content_model = $def->content_model;
- if (is_string($content_model)) {
- // Assume that $this->keys is alphanumeric
- $def->content_model = preg_replace_callback(
- '/\b(' . implode('|', $this->keys) . ')\b/',
- array($this, 'generateChildDefCallback'),
- $content_model
- );
- //$def->content_model = str_replace(
- // $this->keys, $this->values, $content_model);
- }
- $def->child = $this->getChildDef($def, $module);
- }
- public function generateChildDefCallback($matches)
- {
- return $this->info[$matches[0]];
- }
- /**
- * Instantiates a ChildDef based on content_model and content_model_type
- * member variables in HTMLPurifier_ElementDef
- * @note This will also defer to modules for custom HTMLPurifier_ChildDef
- * subclasses that need content set expansion
- * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
- * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
- * @return HTMLPurifier_ChildDef corresponding to ElementDef
- */
- public function getChildDef($def, $module)
- {
- $value = $def->content_model;
- if (is_object($value)) {
- trigger_error(
- 'Literal object child definitions should be stored in '.
- 'ElementDef->child not ElementDef->content_model',
- E_USER_NOTICE
- );
- return $value;
- }
- switch ($def->content_model_type) {
- case 'required':
- return new HTMLPurifier_ChildDef_Required($value);
- case 'optional':
- return new HTMLPurifier_ChildDef_Optional($value);
- case 'empty':
- return new HTMLPurifier_ChildDef_Empty();
- case 'custom':
- return new HTMLPurifier_ChildDef_Custom($value);
- }
- // defer to its module
- $return = false;
- if ($module->defines_child_def) { // save a func call
- $return = $module->getChildDef($def);
- }
- if ($return !== false) {
- return $return;
- }
- // error-out
- trigger_error(
- 'Could not determine which ChildDef class to instantiate',
- E_USER_ERROR
- );
- return false;
- }
- /**
- * Converts a string list of elements separated by pipes into
- * a lookup array.
- * @param string $string List of elements
- * @return array Lookup array of elements
- */
- protected function convertToLookup($string)
- {
- $array = explode('|', str_replace(' ', '', $string));
- $ret = array();
- foreach ($array as $k) {
- $ret[$k] = true;
- }
- return $ret;
- }
- }
- /**
- * Registry object that contains information about the current context.
- * @warning Is a bit buggy when variables are set to null: it thinks
- * they don't exist! So use false instead, please.
- * @note Since the variables Context deals with may not be objects,
- * references are very important here! Do not remove!
- */
- class HTMLPurifier_Context
- {
- /**
- * Private array that stores the references.
- * @type array
- */
- private $_storage = array();
- /**
- * Registers a variable into the context.
- * @param string $name String name
- * @param mixed $ref Reference to variable to be registered
- */
- public function register($name, &$ref)
- {
- if (array_key_exists($name, $this->_storage)) {
- trigger_error(
- "Name $name produces collision, cannot re-register",
- E_USER_ERROR
- );
- return;
- }
- $this->_storage[$name] =& $ref;
- }
- /**
- * Retrieves a variable reference from the context.
- * @param string $name String name
- * @param bool $ignore_error Boolean whether or not to ignore error
- * @return mixed
- */
- public function &get($name, $ignore_error = false)
- {
- if (!array_key_exists($name, $this->_storage)) {
- if (!$ignore_error) {
- trigger_error(
- "Attempted to retrieve non-existent variable $name",
- E_USER_ERROR
- );
- }
- $var = null; // so we can return by reference
- return $var;
- }
- return $this->_storage[$name];
- }
- /**
- * Destroys a variable in the context.
- * @param string $name String name
- */
- public function destroy($name)
- {
- if (!array_key_exists($name, $this->_storage)) {
- trigger_error(
- "Attempted to destroy non-existent variable $name",
- E_USER_ERROR
- );
- return;
- }
- unset($this->_storage[$name]);
- }
- /**
- * Checks whether or not the variable exists.
- * @param string $name String name
- * @return bool
- */
- public function exists($name)
- {
- return array_key_exists($name, $this->_storage);
- }
- /**
- * Loads a series of variables from an associative array
- * @param array $context_array Assoc array of variables to load
- */
- public function loadArray($context_array)
- {
- foreach ($context_array as $key => $discard) {
- $this->register($key, $context_array[$key]);
- }
- }
- }
- /**
- * Abstract class representing Definition cache managers that implements
- * useful common methods and is a factory.
- * @todo Create a separate maintenance file advanced users can use to
- * cache their custom HTMLDefinition, which can be loaded
- * via a configuration directive
- * @todo Implement memcached
- */
- abstract class HTMLPurifier_DefinitionCache
- {
- /**
- * @type string
- */
- public $type;
- /**
- * @param string $type Type of definition objects this instance of the
- * cache will handle.
- */
- public function __construct($type)
- {
- $this->type = $type;
- }
- /**
- * Generates a unique identifier for a particular configuration
- * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
- * @return string
- */
- public function generateKey($config)
- {
- return $config->version . ',' . // possibly replace with function calls
- $config->getBatchSerial($this->type) . ',' .
- $config->get($this->type . '.DefinitionRev');
- }
- /**
- * Tests whether or not a key is old with respect to the configuration's
- * version and revision number.
- * @param string $key Key to test
- * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
- * @return bool
- */
- public function isOld($key, $config)
- {
- if (substr_count($key, ',') < 2) {
- return true;
- }
- list($version, $hash, $revision) = explode(',', $key, 3);
- $compare = version_compare($version, $config->version);
- // version mismatch, is always old
- if ($compare != 0) {
- return true;
- }
- // versions match, ids match, check revision number
- if ($hash == $config->getBatchSerial($this->type) &&
- $revision < $config->get($this->type . '.DefinitionRev')) {
- return true;
- }
- return false;
- }
- /**
- * Checks if a definition's type jives with the cache's type
- * @note Throws an error on failure
- * @param HTMLPurifier_Definition $def Definition object to check
- * @return bool true if good, false if not
- */
- public function checkDefType($def)
- {
- if ($def->type !== $this->type) {
- trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
- return false;
- }
- return true;
- }
- /**
- * Adds a definition object to the cache
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- */
- abstract public function add($def, $config);
- /**
- * Unconditionally saves a definition object to the cache
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- */
- abstract public function set($def, $config);
- /**
- * Replace an object in the cache
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- */
- abstract public function replace($def, $config);
- /**
- * Retrieves a definition object from the cache
- * @param HTMLPurifier_Config $config
- */
- abstract public function get($config);
- /**
- * Removes a definition object to the cache
- * @param HTMLPurifier_Config $config
- */
- abstract public function remove($config);
- /**
- * Clears all objects from cache
- * @param HTMLPurifier_Config $config
- */
- abstract public function flush($config);
- /**
- * Clears all expired (older version or revision) objects from cache
- * @note Be carefuly implementing this method as flush. Flush must
- * not interfere with other Definition types, and cleanup()
- * should not be repeatedly called by userland code.
- * @param HTMLPurifier_Config $config
- */
- abstract public function cleanup($config);
- }
- /**
- * Responsible for creating definition caches.
- */
- class HTMLPurifier_DefinitionCacheFactory
- {
- /**
- * @type array
- */
- protected $caches = array('Serializer' => array());
- /**
- * @type array
- */
- protected $implementations = array();
- /**
- * @type HTMLPurifier_DefinitionCache_Decorator[]
- */
- protected $decorators = array();
- /**
- * Initialize default decorators
- */
- public function setup()
- {
- $this->addDecorator('Cleanup');
- }
- /**
- * Retrieves an instance of global definition cache factory.
- * @param HTMLPurifier_DefinitionCacheFactory $prototype
- * @return HTMLPurifier_DefinitionCacheFactory
- */
- public static function instance($prototype = null)
- {
- static $instance;
- if ($prototype !== null) {
- $instance = $prototype;
- } elseif ($instance === null || $prototype === true) {
- $instance = new HTMLPurifier_DefinitionCacheFactory();
- $instance->setup();
- }
- return $instance;
- }
- /**
- * Registers a new definition cache object
- * @param string $short Short name of cache object, for reference
- * @param string $long Full class name of cache object, for construction
- */
- public function register($short, $long)
- {
- $this->implementations[$short] = $long;
- }
- /**
- * Factory method that creates a cache object based on configuration
- * @param string $type Name of definitions handled by cache
- * @param HTMLPurifier_Config $config Config instance
- * @return mixed
- */
- public function create($type, $config)
- {
- $method = $config->get('Cache.DefinitionImpl');
- if ($method === null) {
- return new HTMLPurifier_DefinitionCache_Null($type);
- }
- if (!empty($this->caches[$method][$type])) {
- return $this->caches[$method][$type];
- }
- if (isset($this->implementations[$method]) &&
- class_exists($class = $this->implementations[$method], false)) {
- $cache = new $class($type);
- } else {
- if ($method != 'Serializer') {
- trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
- }
- $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
- }
- foreach ($this->decorators as $decorator) {
- $new_cache = $decorator->decorate($cache);
- // prevent infinite recursion in PHP 4
- unset($cache);
- $cache = $new_cache;
- }
- $this->caches[$method][$type] = $cache;
- return $this->caches[$method][$type];
- }
- /**
- * Registers a decorator to add to all new cache objects
- * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
- */
- public function addDecorator($decorator)
- {
- if (is_string($decorator)) {
- $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
- $decorator = new $class;
- }
- $this->decorators[$decorator->name] = $decorator;
- }
- }
- /**
- * Represents a document type, contains information on which modules
- * need to be loaded.
- * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
- * If structure changes, please update that function.
- */
- class HTMLPurifier_Doctype
- {
- /**
- * Full name of doctype
- * @type string
- */
- public $name;
- /**
- * List of standard modules (string identifiers or literal objects)
- * that this doctype uses
- * @type array
- */
- public $modules = array();
- /**
- * List of modules to use for tidying up code
- * @type array
- */
- public $tidyModules = array();
- /**
- * Is the language derived from XML (i.e. XHTML)?
- * @type bool
- */
- public $xml = true;
- /**
- * List of aliases for this doctype
- * @type array
- */
- public $aliases = array();
- /**
- * Public DTD identifier
- * @type string
- */
- public $dtdPublic;
- /**
- * System DTD identifier
- * @type string
- */
- public $dtdSystem;
- public function __construct(
- $name = null,
- $xml = true,
- $modules = array(),
- $tidyModules = array(),
- $aliases = array(),
- $dtd_public = null,
- $dtd_system = null
- ) {
- $this->name = $name;
- $this->xml = $xml;
- $this->modules = $modules;
- $this->tidyModules = $tidyModules;
- $this->aliases = $aliases;
- $this->dtdPublic = $dtd_public;
- $this->dtdSystem = $dtd_system;
- }
- }
- class HTMLPurifier_DoctypeRegistry
- {
- /**
- * Hash of doctype names to doctype objects.
- * @type array
- */
- protected $doctypes;
- /**
- * Lookup table of aliases to real doctype names.
- * @type array
- */
- protected $aliases;
- /**
- * Registers a doctype to the registry
- * @note Accepts a fully-formed doctype object, or the
- * parameters for constructing a doctype object
- * @param string $doctype Name of doctype or literal doctype object
- * @param bool $xml
- * @param array $modules Modules doctype will load
- * @param array $tidy_modules Modules doctype will load for certain modes
- * @param array $aliases Alias names for doctype
- * @param string $dtd_public
- * @param string $dtd_system
- * @return HTMLPurifier_Doctype Editable registered doctype
- */
- public function register(
- $doctype,
- $xml = true,
- $modules = array(),
- $tidy_modules = array(),
- $aliases = array(),
- $dtd_public = null,
- $dtd_system = null
- ) {
- if (!is_array($modules)) {
- $modules = array($modules);
- }
- if (!is_array($tidy_modules)) {
- $tidy_modules = array($tidy_modules);
- }
- if (!is_array($aliases)) {
- $aliases = array($aliases);
- }
- if (!is_object($doctype)) {
- $doctype = new HTMLPurifier_Doctype(
- $doctype,
- $xml,
- $modules,
- $tidy_modules,
- $aliases,
- $dtd_public,
- $dtd_system
- );
- }
- $this->doctypes[$doctype->name] = $doctype;
- $name = $doctype->name;
- // hookup aliases
- foreach ($doctype->aliases as $alias) {
- if (isset($this->doctypes[$alias])) {
- continue;
- }
- $this->aliases[$alias] = $name;
- }
- // remove old aliases
- if (isset($this->aliases[$name])) {
- unset($this->aliases[$name]);
- }
- return $doctype;
- }
- /**
- * Retrieves reference to a doctype of a certain name
- * @note This function resolves aliases
- * @note When possible, use the more fully-featured make()
- * @param string $doctype Name of doctype
- * @return HTMLPurifier_Doctype Editable doctype object
- */
- public function get($doctype)
- {
- if (isset($this->aliases[$doctype])) {
- $doctype = $this->aliases[$doctype];
- }
- if (!isset($this->doctypes[$doctype])) {
- trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
- $anon = new HTMLPurifier_Doctype($doctype);
- return $anon;
- }
- return $this->doctypes[$doctype];
- }
- /**
- * Creates a doctype based on a configuration object,
- * will perform initialization on the doctype
- * @note Use this function to get a copy of doctype that config
- * can hold on to (this is necessary in order to tell
- * Generator whether or not the current document is XML
- * based or not).
- * @param HTMLPurifier_Config $config
- * @return HTMLPurifier_Doctype
- */
- public function make($config)
- {
- return clone $this->get($this->getDoctypeFromConfig($config));
- }
- /**
- * Retrieves the doctype from the configuration object
- * @param HTMLPurifier_Config $config
- * @return string
- */
- public function getDoctypeFromConfig($config)
- {
- // recommended test
- $doctype = $config->get('HTML.Doctype');
- if (!empty($doctype)) {
- return $doctype;
- }
- $doctype = $config->get('HTML.CustomDoctype');
- if (!empty($doctype)) {
- return $doctype;
- }
- // backwards-compatibility
- if ($config->get('HTML.XHTML')) {
- $doctype = 'XHTML 1.0';
- } else {
- $doctype = 'HTML 4.01';
- }
- if ($config->get('HTML.Strict')) {
- $doctype .= ' Strict';
- } else {
- $doctype .= ' Transitional';
- }
- return $doctype;
- }
- }
- /**
- * Structure that stores an HTML element definition. Used by
- * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
- * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
- * Please update that class too.
- * @warning If you add new properties to this class, you MUST update
- * the mergeIn() method.
- */
- class HTMLPurifier_ElementDef
- {
- /**
- * Does the definition work by itself, or is it created solely
- * for the purpose of merging into another definition?
- * @type bool
- */
- public $standalone = true;
- /**
- * Associative array of attribute name to HTMLPurifier_AttrDef.
- * @type array
- * @note Before being processed by HTMLPurifier_AttrCollections
- * when modules are finalized during
- * HTMLPurifier_HTMLDefinition->setup(), this array may also
- * contain an array at index 0 that indicates which attribute
- * collections to load into the full array. It may also
- * contain string indentifiers in lieu of HTMLPurifier_AttrDef,
- * see HTMLPurifier_AttrTypes on how they are expanded during
- * HTMLPurifier_HTMLDefinition->setup() processing.
- */
- public $attr = array();
- // XXX: Design note: currently, it's not possible to override
- // previously defined AttrTransforms without messing around with
- // the final generated config. This is by design; a previous version
- // used an associated list of attr_transform, but it was extremely
- // easy to accidentally override other attribute transforms by
- // forgetting to specify an index (and just using 0.) While we
- // could check this by checking the index number and complaining,
- // there is a second problem which is that it is not at all easy to
- // tell when something is getting overridden. Combine this with a
- // codebase where this isn't really being used, and it's perfect for
- // nuking.
- /**
- * List of tags HTMLPurifier_AttrTransform to be done before validation.
- * @type array
- */
- public $attr_transform_pre = array();
- /**
- * List of tags HTMLPurifier_AttrTransform to be done after validation.
- * @type array
- */
- public $attr_transform_post = array();
- /**
- * HTMLPurifier_ChildDef of this tag.
- * @type HTMLPurifier_ChildDef
- */
- public $child;
- /**
- * Abstract string representation of internal ChildDef rules.
- * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
- * into an HTMLPurifier_ChildDef.
- * @warning This is a temporary variable that is not available after
- * being processed by HTMLDefinition
- * @type string
- */
- public $content_model;
- /**
- * Value of $child->type, used to determine which ChildDef to use,
- * used in combination with $content_model.
- * @warning This must be lowercase
- * @warning This is a temporary variable that is not available after
- * being processed by HTMLDefinition
- * @type string
- */
- public $content_model_type;
- /**
- * Does the element have a content model (#PCDATA | Inline)*? This
- * is important for chameleon ins and del processing in
- * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
- * have to worry about this one.
- * @type bool
- */
- public $descendants_are_inline = false;
- /**
- * List of the names of required attributes this element has.
- * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
- * @type array
- */
- public $required_attr = array();
- /**
- * Lookup table of tags excluded from all descendants of this tag.
- * @type array
- * @note SGML permits exclusions for all descendants, but this is
- * not possible with DTDs or XML Schemas. W3C has elected to
- * use complicated compositions of content_models to simulate
- * exclusion for children, but we go the simpler, SGML-style
- * route of flat-out exclusions, which correctly apply to
- * all descendants and not just children. Note that the XHTML
- * Modularization Abstract Modules are blithely unaware of such
- * distinctions.
- */
- public $excludes = array();
- /**
- * This tag is explicitly auto-closed by the following tags.
- * @type array
- */
- public $autoclose = array();
- /**
- * If a foreign element is found in this element, test if it is
- * allowed by this sub-element; if it is, instead of closing the
- * current element, place it inside this element.
- * @type string
- */
- public $wrap;
- /**
- * Whether or not this is a formatting element affected by the
- * "Active Formatting Elements" algorithm.
- * @type bool
- */
- public $formatting;
- /**
- * Low-level factory constructor for creating new standalone element defs
- */
- public static function create($content_model, $content_model_type, $attr)
- {
- $def = new HTMLPurifier_ElementDef();
- $def->content_model = $content_model;
- $def->content_model_type = $content_model_type;
- $def->attr = $attr;
- return $def;
- }
- /**
- * Merges the values of another element definition into this one.
- * Values from the new element def take precedence if a value is
- * not mergeable.
- * @param HTMLPurifier_ElementDef $def
- */
- public function mergeIn($def)
- {
- // later keys takes precedence
- foreach ($def->attr as $k => $v) {
- if ($k === 0) {
- // merge in the includes
- // sorry, no way to override an include
- foreach ($v as $v2) {
- $this->attr[0][] = $v2;
- }
- continue;
- }
- if ($v === false) {
- if (isset($this->attr[$k])) {
- unset($this->attr[$k]);
- }
- continue;
- }
- $this->attr[$k] = $v;
- }
- $this->_mergeAssocArray($this->excludes, $def->excludes);
- $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
- $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
- if (!empty($def->content_model)) {
- $this->content_model =
- str_replace("#SUPER", $this->content_model, $def->content_model);
- $this->child = false;
- }
- if (!empty($def->content_model_type)) {
- $this->content_model_type = $def->content_model_type;
- $this->child = false;
- }
- if (!is_null($def->child)) {
- $this->child = $def->child;
- }
- if (!is_null($def->formatting)) {
- $this->formatting = $def->formatting;
- }
- if ($def->descendants_are_inline) {
- $this->descendants_are_inline = $def->descendants_are_inline;
- }
- }
- /**
- * Merges one array into another, removes values which equal false
- * @param $a1 Array by reference that is merged into
- * @param $a2 Array that merges into $a1
- */
- private function _mergeAssocArray(&$a1, $a2)
- {
- foreach ($a2 as $k => $v) {
- if ($v === false) {
- if (isset($a1[$k])) {
- unset($a1[$k]);
- }
- continue;
- }
- $a1[$k] = $v;
- }
- }
- }
- /**
- * A UTF-8 specific character encoder that handles cleaning and transforming.
- * @note All functions in this class should be static.
- */
- class HTMLPurifier_Encoder
- {
- /**
- * Constructor throws fatal error if you attempt to instantiate class
- */
- private function __construct()
- {
- trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
- }
- /**
- * Error-handler that mutes errors, alternative to shut-up operator.
- */
- public static function muteErrorHandler()
- {
- }
- /**
- * iconv wrapper which mutes errors, but doesn't work around bugs.
- * @param string $in Input encoding
- * @param string $out Output encoding
- * @param string $text The text to convert
- * @return string
- */
- public static function unsafeIconv($in, $out, $text)
- {
- set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
- $r = iconv($in, $out, $text);
- restore_error_handler();
- return $r;
- }
- /**
- * iconv wrapper which mutes errors and works around bugs.
- * @param string $in Input encoding
- * @param string $out Output encoding
- * @param string $text The text to convert
- * @param int $max_chunk_size
- * @return string
- */
- public static function iconv($in, $out, $text, $max_chunk_size = 8000)
- {
- $code = self::testIconvTruncateBug();
- if ($code == self::ICONV_OK) {
- return self::unsafeIconv($in, $out, $text);
- } elseif ($code == self::ICONV_TRUNCATES) {
- // we can only work around this if the input character set
- // is utf-8
- if ($in == 'utf-8') {
- if ($max_chunk_size < 4) {
- trigger_error('max_chunk_size is too small', E_USER_WARNING);
- return false;
- }
- // split into 8000 byte chunks, but be careful to handle
- // multibyte boundaries properly
- if (($c = strlen($text)) <= $max_chunk_size) {
- return self::unsafeIconv($in, $out, $text);
- }
- $r = '';
- $i = 0;
- while (true) {
- if ($i + $max_chunk_size >= $c) {
- $r .= self::unsafeIconv($in, $out, substr($text, $i));
- break;
- }
- // wibble the boundary
- if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
- $chunk_size = $max_chunk_size;
- } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
- $chunk_size = $max_chunk_size - 1;
- } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
- $chunk_size = $max_chunk_size - 2;
- } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
- $chunk_size = $max_chunk_size - 3;
- } else {
- return false; // rather confusing UTF-8...
- }
- $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
- $r .= self::unsafeIconv($in, $out, $chunk);
- $i += $chunk_size;
- }
- return $r;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
- /**
- * Cleans a UTF-8 string for well-formedness and SGML validity
- *
- * It will parse according to UTF-8 and return a valid UTF8 string, with
- * non-SGML codepoints excluded.
- *
- * @param string $str The string to clean
- * @param bool $force_php
- * @return string
- *
- * @note Just for reference, the non-SGML code points are 0 to 31 and
- * 127 to 159, inclusive. However, we allow code points 9, 10
- * and 13, which are the tab, line feed and carriage return
- * respectively. 128 and above the code points map to multibyte
- * UTF-8 representations.
- *
- * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
- * hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
- * LGPL license. Notes on what changed are inside, but in general,
- * the original code transformed UTF-8 text into an array of integer
- * Unicode codepoints. Understandably, transforming that back to
- * a string would be somewhat expensive, so the function was modded to
- * directly operate on the string. However, this discourages code
- * reuse, and the logic enumerated here would be useful for any
- * function that needs to be able to understand UTF-8 characters.
- * As of right now, only smart lossless character encoding converters
- * would need that, and I'm probably not going to implement them.
- * Once again, PHP 6 should solve all our problems.
- */
- public static function cleanUTF8($str, $force_php = false)
- {
- // UTF-8 validity is checked since PHP 4.3.5
- // This is an optimization: if the string is already valid UTF-8, no
- // need to do PHP stuff. 99% of the time, this will be the case.
- // The regexp matches the XML char production, as well as well as excluding
- // non-SGML codepoints U+007F to U+009F
- if (preg_match(
- '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
- $str
- )) {
- return $str;
- }
- $mState = 0; // cached expected number of octets after the current octet
- // until the beginning of the next UTF8 character sequence
- $mUcs4 = 0; // cached Unicode character
- $mBytes = 1; // cached expected number of octets in the current sequence
- // original code involved an $out that was an array of Unicode
- // codepoints. Instead of having to convert back into UTF-8, we've
- // decided to directly append valid UTF-8 characters onto a string
- // $out once they're done. $char accumulates raw bytes, while $mUcs4
- // turns into the Unicode code point, so there's some redundancy.
- $out = '';
- $char = '';
- $len = strlen($str);
- for ($i = 0; $i < $len; $i++) {
- $in = ord($str{$i});
- $char .= $str[$i]; // append byte to char
- if (0 == $mState) {
- // When mState is zero we expect either a US-ASCII character
- // or a multi-octet sequence.
- if (0 == (0x80 & ($in))) {
- // US-ASCII, pass straight through.
- if (($in <= 31 || $in == 127) &&
- !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
- ) {
- // control characters, remove
- } else {
- $out .= $char;
- }
- // reset
- $char = '';
- $mBytes = 1;
- } elseif (0xC0 == (0xE0 & ($in))) {
- // First octet of 2 octet sequence
- $mUcs4 = ($in);
- $mUcs4 = ($mUcs4 & 0x1F) << 6;
- $mState = 1;
- $mBytes = 2;
- } elseif (0xE0 == (0xF0 & ($in))) {
- // First octet of 3 octet sequence
- $mUcs4 = ($in);
- $mUcs4 = ($mUcs4 & 0x0F) << 12;
- $mState = 2;
- $mBytes = 3;
- } elseif (0xF0 == (0xF8 & ($in))) {
- // First octet of 4 octet sequence
- $mUcs4 = ($in);
- $mUcs4 = ($mUcs4 & 0x07) << 18;
- $mState = 3;
- $mBytes = 4;
- } elseif (0xF8 == (0xFC & ($in))) {
- // First octet of 5 octet sequence.
- //
- // This is illegal because the encoded codepoint must be
- // either:
- // (a) not the shortest form or
- // (b) outside the Unicode range of 0-0x10FFFF.
- // Rather than trying to resynchronize, we will carry on
- // until the end of the sequence and let the later error
- // handling code catch it.
- $mUcs4 = ($in);
- $mUcs4 = ($mUcs4 & 0x03) << 24;
- $mState = 4;
- $mBytes = 5;
- } elseif (0xFC == (0xFE & ($in))) {
- // First octet of 6 octet sequence, see comments for 5
- // octet sequence.
- $mUcs4 = ($in);
- $mUcs4 = ($mUcs4 & 1) << 30;
- $mState = 5;
- $mBytes = 6;
- } else {
- // Current octet is neither in the US-ASCII range nor a
- // legal first octet of a multi-octet sequence.
- $mState = 0;
- $mUcs4 = 0;
- $mBytes = 1;
- $char = '';
- }
- } else {
- // When mState is non-zero, we expect a continuation of the
- // multi-octet sequence
- if (0x80 == (0xC0 & ($in))) {
- // Legal continuation.
- $shift = ($mState - 1) * 6;
- $tmp = $in;
- $tmp = ($tmp & 0x0000003F) << $shift;
- $mUcs4 |= $tmp;
- if (0 == --$mState) {
- // End of the multi-octet sequence. mUcs4 now contains
- // the final Unicode codepoint to be output
- // Check for illegal sequences and codepoints.
- // From Unicode 3.1, non-shortest form is illegal
- if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
- ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
- ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
- (4 < $mBytes) ||
- // From Unicode 3.2, surrogate characters = illegal
- (($mUcs4 & 0xFFFFF800) == 0xD800) ||
- // Codepoints outside the Unicode range are illegal
- ($mUcs4 > 0x10FFFF)
- ) {
- } elseif (0xFEFF != $mUcs4 && // omit BOM
- // check for valid Char unicode codepoints
- (
- 0x9 == $mUcs4 ||
- 0xA == $mUcs4 ||
- 0xD == $mUcs4 ||
- (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
- // 7F-9F is not strictly prohibited by XML,
- // but it is non-SGML, and thus we don't allow it
- (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
- (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
- )
- ) {
- $out .= $char;
- }
- // initialize UTF8 cache (reset)
- $mState = 0;
- $mUcs4 = 0;
- $mBytes = 1;
- $char = '';
- }
- } else {
- // ((0xC0 & (*in) != 0x80) && (mState != 0))
- // Incomplete multi-octet sequence.
- // used to result in complete fail, but we'll reset
- $mState = 0;
- $mUcs4 = 0;
- $mBytes = 1;
- $char ='';
- }
- }
- }
- return $out;
- }
- /**
- * Translates a Unicode codepoint into its corresponding UTF-8 character.
- * @note Based on Feyd's function at
- * <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
- * which is in public domain.
- * @note While we're going to do code point parsing anyway, a good
- * optimization would be to refuse to translate code points that
- * are non-SGML characters. However, this could lead to duplication.
- * @note This is very similar to the unichr function in
- * maintenance/generate-entity-file.php (although this is superior,
- * due to its sanity checks).
- */
- // +----------+----------+----------+----------+
- // | 33222222 | 22221111 | 111111 | |
- // | 10987654 | 32109876 | 54321098 | 76543210 | bit
- // +----------+----------+----------+----------+
- // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
- // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
- // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
- // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
- // +----------+----------+----------+----------+
- // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
- // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
- // +----------+----------+----------+----------+
- public static function unichr($code)
- {
- if ($code > 1114111 or $code < 0 or
- ($code >= 55296 and $code <= 57343) ) {
- // bits are set outside the "valid" range as defined
- // by UNICODE 4.1.0
- return '';
- }
- $x = $y = $z = $w = 0;
- if ($code < 128) {
- // regular ASCII character
- $x = $code;
- } else {
- // set up bits for UTF-8
- $x = ($code & 63) | 128;
- if ($code < 2048) {
- $y = (($code & 2047) >> 6) | 192;
- } else {
- $y = (($code & 4032) >> 6) | 128;
- if ($code < 65536) {
- $z = (($code >> 12) & 15) | 224;
- } else {
- $z = (($code >> 12) & 63) | 128;
- $w = (($code >> 18) & 7) | 240;
- }
- }
- }
- // set up the actual character
- $ret = '';
- if ($w) {
- $ret .= chr($w);
- }
- if ($z) {
- $ret .= chr($z);
- }
- if ($y) {
- $ret .= chr($y);
- }
- $ret .= chr($x);
- return $ret;
- }
- /**
- * @return bool
- */
- public static function iconvAvailable()
- {
- static $iconv = null;
- if ($iconv === null) {
- $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
- }
- return $iconv;
- }
- /**
- * Convert a string to UTF-8 based on configuration.
- * @param string $str The string to convert
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public static function convertToUTF8($str, $config, $context)
- {
- $encoding = $config->get('Core.Encoding');
- if ($encoding === 'utf-8') {
- return $str;
- }
- static $iconv = null;
- if ($iconv === null) {
- $iconv = self::iconvAvailable();
- }
- if ($iconv && !$config->get('Test.ForceNoIconv')) {
- // unaffected by bugs, since UTF-8 support all characters
- $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
- if ($str === false) {
- // $encoding is not a valid encoding
- trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
- return '';
- }
- // If the string is bjorked by Shift_JIS or a similar encoding
- // that doesn't support all of ASCII, convert the naughty
- // characters to their true byte-wise ASCII/UTF-8 equivalents.
- $str = strtr($str, self::testEncodingSupportsASCII($encoding));
- return $str;
- } elseif ($encoding === 'iso-8859-1') {
- $str = utf8_encode($str);
- return $str;
- }
- $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
- if ($bug == self::ICONV_OK) {
- trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
- } else {
- trigger_error(
- 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
- 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
- E_USER_ERROR
- );
- }
- }
- /**
- * Converts a string from UTF-8 based on configuration.
- * @param string $str The string to convert
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- * @note Currently, this is a lossy conversion, with unexpressable
- * characters being omitted.
- */
- public static function convertFromUTF8($str, $config, $context)
- {
- $encoding = $config->get('Core.Encoding');
- if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
- $str = self::convertToASCIIDumbLossless($str);
- }
- if ($encoding === 'utf-8') {
- return $str;
- }
- static $iconv = null;
- if ($iconv === null) {
- $iconv = self::iconvAvailable();
- }
- if ($iconv && !$config->get('Test.ForceNoIconv')) {
- // Undo our previous fix in convertToUTF8, otherwise iconv will barf
- $ascii_fix = self::testEncodingSupportsASCII($encoding);
- if (!$escape && !empty($ascii_fix)) {
- $clear_fix = array();
- foreach ($ascii_fix as $utf8 => $native) {
- $clear_fix[$utf8] = '';
- }
- $str = strtr($str, $clear_fix);
- }
- $str = strtr($str, array_flip($ascii_fix));
- // Normal stuff
- $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
- return $str;
- } elseif ($encoding === 'iso-8859-1') {
- $str = utf8_decode($str);
- return $str;
- }
- trigger_error('Encoding not supported', E_USER_ERROR);
- // You might be tempted to assume that the ASCII representation
- // might be OK, however, this is *not* universally true over all
- // encodings. So we take the conservative route here, rather
- // than forcibly turn on %Core.EscapeNonASCIICharacters
- }
- /**
- * Lossless (character-wise) conversion of HTML to ASCII
- * @param string $str UTF-8 string to be converted to ASCII
- * @return string ASCII encoded string with non-ASCII character entity-ized
- * @warning Adapted from MediaWiki, claiming fair use: this is a common
- * algorithm. If you disagree with this license fudgery,
- * implement it yourself.
- * @note Uses decimal numeric entities since they are best supported.
- * @note This is a DUMB function: it has no concept of keeping
- * character entities that the projected character encoding
- * can allow. We could possibly implement a smart version
- * but that would require it to also know which Unicode
- * codepoints the charset supported (not an easy task).
- * @note Sort of with cleanUTF8() but it assumes that $str is
- * well-formed UTF-8
- */
- public static function convertToASCIIDumbLossless($str)
- {
- $bytesleft = 0;
- $result = '';
- $working = 0;
- $len = strlen($str);
- for ($i = 0; $i < $len; $i++) {
- $bytevalue = ord($str[$i]);
- if ($bytevalue <= 0x7F) { //0xxx xxxx
- $result .= chr($bytevalue);
- $bytesleft = 0;
- } elseif ($bytevalue <= 0xBF) { //10xx xxxx
- $working = $working << 6;
- $working += ($bytevalue & 0x3F);
- $bytesleft--;
- if ($bytesleft <= 0) {
- $result .= "&#" . $working . ";";
- }
- } elseif ($bytevalue <= 0xDF) { //110x xxxx
- $working = $bytevalue & 0x1F;
- $bytesleft = 1;
- } elseif ($bytevalue <= 0xEF) { //1110 xxxx
- $working = $bytevalue & 0x0F;
- $bytesleft = 2;
- } else { //1111 0xxx
- $working = $bytevalue & 0x07;
- $bytesleft = 3;
- }
- }
- return $result;
- }
- /** No bugs detected in iconv. */
- const ICONV_OK = 0;
- /** Iconv truncates output if converting from UTF-8 to another
- * character set with //IGNORE, and a non-encodable character is found */
- const ICONV_TRUNCATES = 1;
- /** Iconv does not support //IGNORE, making it unusable for
- * transcoding purposes */
- const ICONV_UNUSABLE = 2;
- /**
- * glibc iconv has a known bug where it doesn't handle the magic
- * //IGNORE stanza correctly. In particular, rather than ignore
- * characters, it will return an EILSEQ after consuming some number
- * of characters, and expect you to restart iconv as if it were
- * an E2BIG. Old versions of PHP did not respect the errno, and
- * returned the fragment, so as a result you would see iconv
- * mysteriously truncating output. We can work around this by
- * manually chopping our input into segments of about 8000
- * characters, as long as PHP ignores the error code. If PHP starts
- * paying attention to the error code, iconv becomes unusable.
- *
- * @return int Error code indicating severity of bug.
- */
- public static function testIconvTruncateBug()
- {
- static $code = null;
- if ($code === null) {
- // better not use iconv, otherwise infinite loop!
- $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
- if ($r === false) {
- $code = self::ICONV_UNUSABLE;
- } elseif (($c = strlen($r)) < 9000) {
- $code = self::ICONV_TRUNCATES;
- } elseif ($c > 9000) {
- trigger_error(
- 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
- 'include your iconv version as per phpversion()',
- E_USER_ERROR
- );
- } else {
- $code = self::ICONV_OK;
- }
- }
- return $code;
- }
- /**
- * This expensive function tests whether or not a given character
- * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
- * fail this test, and require special processing. Variable width
- * encodings shouldn't ever fail.
- *
- * @param string $encoding Encoding name to test, as per iconv format
- * @param bool $bypass Whether or not to bypass the precompiled arrays.
- * @return Array of UTF-8 characters to their corresponding ASCII,
- * which can be used to "undo" any overzealous iconv action.
- */
- public static function testEncodingSupportsASCII($encoding, $bypass = false)
- {
- // All calls to iconv here are unsafe, proof by case analysis:
- // If ICONV_OK, no difference.
- // If ICONV_TRUNCATE, all calls involve one character inputs,
- // so bug is not triggered.
- // If ICONV_UNUSABLE, this call is irrelevant
- static $encodings = array();
- if (!$bypass) {
- if (isset($encodings[$encoding])) {
- return $encodings[$encoding];
- }
- $lenc = strtolower($encoding);
- switch ($lenc) {
- case 'shift_jis':
- return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
- case 'johab':
- return array("\xE2\x82\xA9" => '\\');
- }
- if (strpos($lenc, 'iso-8859-') === 0) {
- return array();
- }
- }
- $ret = array();
- if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
- return false;
- }
- for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
- $c = chr($i); // UTF-8 char
- $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
- if ($r === '' ||
- // This line is needed for iconv implementations that do not
- // omit characters that do not exist in the target character set
- ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
- ) {
- // Reverse engineer: what's the UTF-8 equiv of this byte
- // sequence? This assumes that there's no variable width
- // encoding that doesn't support ASCII.
- $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
- }
- }
- $encodings[$encoding] = $ret;
- return $ret;
- }
- }
- /**
- * Object that provides entity lookup table from entity name to character
- */
- class HTMLPurifier_EntityLookup
- {
- /**
- * Assoc array of entity name to character represented.
- * @type array
- */
- public $table;
- /**
- * Sets up the entity lookup table from the serialized file contents.
- * @param bool $file
- * @note The serialized contents are versioned, but were generated
- * using the maintenance script generate_entity_file.php
- * @warning This is not in constructor to help enforce the Singleton
- */
- public function setup($file = false)
- {
- if (!$file) {
- $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
- }
- $this->table = unserialize(file_get_contents($file));
- }
- /**
- * Retrieves sole instance of the object.
- * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
- * @return HTMLPurifier_EntityLookup
- */
- public static function instance($prototype = false)
- {
- // no references, since PHP doesn't copy unless modified
- static $instance = null;
- if ($prototype) {
- $instance = $prototype;
- } elseif (!$instance) {
- $instance = new HTMLPurifier_EntityLookup();
- $instance->setup();
- }
- return $instance;
- }
- }
- // if want to implement error collecting here, we'll need to use some sort
- // of global data (probably trigger_error) because it's impossible to pass
- // $config or $context to the callback functions.
- /**
- * Handles referencing and derefencing character entities
- */
- class HTMLPurifier_EntityParser
- {
- /**
- * Reference to entity lookup table.
- * @type HTMLPurifier_EntityLookup
- */
- protected $_entity_lookup;
- /**
- * Callback regex string for parsing entities.
- * @type string
- */
- protected $_substituteEntitiesRegex =
- '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
- // 1. hex 2. dec 3. string (XML style)
- /**
- * Decimal to parsed string conversion table for special entities.
- * @type array
- */
- protected $_special_dec2str =
- array(
- 34 => '"',
- 38 => '&',
- 39 => "'",
- 60 => '<',
- 62 => '>'
- );
- /**
- * Stripped entity names to decimal conversion table for special entities.
- * @type array
- */
- protected $_special_ent2dec =
- array(
- 'quot' => 34,
- 'amp' => 38,
- 'lt' => 60,
- 'gt' => 62
- );
- /**
- * Substitutes non-special entities with their parsed equivalents. Since
- * running this whenever you have parsed character is t3h 5uck, we run
- * it before everything else.
- *
- * @param string $string String to have non-special entities parsed.
- * @return string Parsed string.
- */
- public function substituteNonSpecialEntities($string)
- {
- // it will try to detect missing semicolons, but don't rely on it
- return preg_replace_callback(
- $this->_substituteEntitiesRegex,
- array($this, 'nonSpecialEntityCallback'),
- $string
- );
- }
- /**
- * Callback function for substituteNonSpecialEntities() that does the work.
- *
- * @param array $matches PCRE matches array, with 0 the entire match, and
- * either index 1, 2 or 3 set with a hex value, dec value,
- * or string (respectively).
- * @return string Replacement string.
- */
- protected function nonSpecialEntityCallback($matches)
- {
- // replaces all but big five
- $entity = $matches[0];
- $is_num = (@$matches[0][1] === '#');
- if ($is_num) {
- $is_hex = (@$entity[2] === 'x');
- $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
- // abort for special characters
- if (isset($this->_special_dec2str[$code])) {
- return $entity;
- }
- return HTMLPurifier_Encoder::unichr($code);
- } else {
- if (isset($this->_special_ent2dec[$matches[3]])) {
- return $entity;
- }
- if (!$this->_entity_lookup) {
- $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
- }
- if (isset($this->_entity_lookup->table[$matches[3]])) {
- return $this->_entity_lookup->table[$matches[3]];
- } else {
- return $entity;
- }
- }
- }
- /**
- * Substitutes only special entities with their parsed equivalents.
- *
- * @notice We try to avoid calling this function because otherwise, it
- * would have to be called a lot (for every parsed section).
- *
- * @param string $string String to have non-special entities parsed.
- * @return string Parsed string.
- */
- public function substituteSpecialEntities($string)
- {
- return preg_replace_callback(
- $this->_substituteEntitiesRegex,
- array($this, 'specialEntityCallback'),
- $string
- );
- }
- /**
- * Callback function for substituteSpecialEntities() that does the work.
- *
- * This callback has same syntax as nonSpecialEntityCallback().
- *
- * @param array $matches PCRE-style matches array, with 0 the entire match, and
- * either index 1, 2 or 3 set with a hex value, dec value,
- * or string (respectively).
- * @return string Replacement string.
- */
- protected function specialEntityCallback($matches)
- {
- $entity = $matches[0];
- $is_num = (@$matches[0][1] === '#');
- if ($is_num) {
- $is_hex = (@$entity[2] === 'x');
- $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
- return isset($this->_special_dec2str[$int]) ?
- $this->_special_dec2str[$int] :
- $entity;
- } else {
- return isset($this->_special_ent2dec[$matches[3]]) ?
- $this->_special_ent2dec[$matches[3]] :
- $entity;
- }
- }
- }
- /**
- * Error collection class that enables HTML Purifier to report HTML
- * problems back to the user
- */
- class HTMLPurifier_ErrorCollector
- {
- /**
- * Identifiers for the returned error array. These are purposely numeric
- * so list() can be used.
- */
- const LINENO = 0;
- const SEVERITY = 1;
- const MESSAGE = 2;
- const CHILDREN = 3;
- /**
- * @type array
- */
- protected $errors;
- /**
- * @type array
- */
- protected $_current;
- /**
- * @type array
- */
- protected $_stacks = array(array());
- /**
- * @type HTMLPurifier_Language
- */
- protected $locale;
- /**
- * @type HTMLPurifier_Generator
- */
- protected $generator;
- /**
- * @type HTMLPurifier_Context
- */
- protected $context;
- /**
- * @type array
- */
- protected $lines = array();
- /**
- * @param HTMLPurifier_Context $context
- */
- public function __construct($context)
- {
- $this->locale =& $context->get('Locale');
- $this->context = $context;
- $this->_current =& $this->_stacks[0];
- $this->errors =& $this->_stacks[0];
- }
- /**
- * Sends an error message to the collector for later use
- * @param int $severity Error severity, PHP error style (don't use E_USER_)
- * @param string $msg Error message text
- */
- public function send($severity, $msg)
- {
- $args = array();
- if (func_num_args() > 2) {
- $args = func_get_args();
- array_shift($args);
- unset($args[0]);
- }
- $token = $this->context->get('CurrentToken', true);
- $line = $token ? $token->line : $this->context->get('CurrentLine', true);
- $col = $token ? $token->col : $this->context->get('CurrentCol', true);
- $attr = $this->context->get('CurrentAttr', true);
- // perform special substitutions, also add custom parameters
- $subst = array();
- if (!is_null($token)) {
- $args['CurrentToken'] = $token;
- }
- if (!is_null($attr)) {
- $subst['$CurrentAttr.Name'] = $attr;
- if (isset($token->attr[$attr])) {
- $subst['$CurrentAttr.Value'] = $token->attr[$attr];
- }
- }
- if (empty($args)) {
- $msg = $this->locale->getMessage($msg);
- } else {
- $msg = $this->locale->formatMessage($msg, $args);
- }
- if (!empty($subst)) {
- $msg = strtr($msg, $subst);
- }
- // (numerically indexed)
- $error = array(
- self::LINENO => $line,
- self::SEVERITY => $severity,
- self::MESSAGE => $msg,
- self::CHILDREN => array()
- );
- $this->_current[] = $error;
- // NEW CODE BELOW ...
- // Top-level errors are either:
- // TOKEN type, if $value is set appropriately, or
- // "syntax" type, if $value is null
- $new_struct = new HTMLPurifier_ErrorStruct();
- $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
- if ($token) {
- $new_struct->value = clone $token;
- }
- if (is_int($line) && is_int($col)) {
- if (isset($this->lines[$line][$col])) {
- $struct = $this->lines[$line][$col];
- } else {
- $struct = $this->lines[$line][$col] = $new_struct;
- }
- // These ksorts may present a performance problem
- ksort($this->lines[$line], SORT_NUMERIC);
- } else {
- if (isset($this->lines[-1])) {
- $struct = $this->lines[-1];
- } else {
- $struct = $this->lines[-1] = $new_struct;
- }
- }
- ksort($this->lines, SORT_NUMERIC);
- // Now, check if we need to operate on a lower structure
- if (!empty($attr)) {
- $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
- if (!$struct->value) {
- $struct->value = array($attr, 'PUT VALUE HERE');
- }
- }
- if (!empty($cssprop)) {
- $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
- if (!$struct->value) {
- // if we tokenize CSS this might be a little more difficult to do
- $struct->value = array($cssprop, 'PUT VALUE HERE');
- }
- }
- // Ok, structs are all setup, now time to register the error
- $struct->addError($severity, $msg);
- }
- /**
- * Retrieves raw error data for custom formatter to use
- */
- public function getRaw()
- {
- return $this->errors;
- }
- /**
- * Default HTML formatting implementation for error messages
- * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
- * @param array $errors Errors array to display; used for recursion.
- * @return string
- */
- public function getHTMLFormatted($config, $errors = null)
- {
- $ret = array();
- $this->generator = new HTMLPurifier_Generator($config, $this->context);
- if ($errors === null) {
- $errors = $this->errors;
- }
- // 'At line' message needs to be removed
- // generation code for new structure goes here. It needs to be recursive.
- foreach ($this->lines as $line => $col_array) {
- if ($line == -1) {
- continue;
- }
- foreach ($col_array as $col => $struct) {
- $this->_renderStruct($ret, $struct, $line, $col);
- }
- }
- if (isset($this->lines[-1])) {
- $this->_renderStruct($ret, $this->lines[-1]);
- }
- if (empty($errors)) {
- return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
- } else {
- return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
- }
- }
- private function _renderStruct(&$ret, $struct, $line = null, $col = null)
- {
- $stack = array($struct);
- $context_stack = array(array());
- while ($current = array_pop($stack)) {
- $context = array_pop($context_stack);
- foreach ($current->errors as $error) {
- list($severity, $msg) = $error;
- $string = '';
- $string .= '<div>';
- // W3C uses an icon to indicate the severity of the error.
- $error = $this->locale->getErrorName($severity);
- $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
- if (!is_null($line) && !is_null($col)) {
- $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
- } else {
- $string .= '<em class="location">End of Document: </em> ';
- }
- $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
- $string .= '</div>';
- // Here, have a marker for the character on the column appropriate.
- // Be sure to clip extremely long lines.
- //$string .= '<pre>';
- //$string .= '';
- //$string .= '</pre>';
- $ret[] = $string;
- }
- foreach ($current->children as $array) {
- $context[] = $current;
- $stack = array_merge($stack, array_reverse($array, true));
- for ($i = count($array); $i > 0; $i--) {
- $context_stack[] = $context;
- }
- }
- }
- }
- }
- /**
- * Records errors for particular segments of an HTML document such as tokens,
- * attributes or CSS properties. They can contain error structs (which apply
- * to components of what they represent), but their main purpose is to hold
- * errors applying to whatever struct is being used.
- */
- class HTMLPurifier_ErrorStruct
- {
- /**
- * Possible values for $children first-key. Note that top-level structures
- * are automatically token-level.
- */
- const TOKEN = 0;
- const ATTR = 1;
- const CSSPROP = 2;
- /**
- * Type of this struct.
- * @type string
- */
- public $type;
- /**
- * Value of the struct we are recording errors for. There are various
- * values for this:
- * - TOKEN: Instance of HTMLPurifier_Token
- * - ATTR: array('attr-name', 'value')
- * - CSSPROP: array('prop-name', 'value')
- * @type mixed
- */
- public $value;
- /**
- * Errors registered for this structure.
- * @type array
- */
- public $errors = array();
- /**
- * Child ErrorStructs that are from this structure. For example, a TOKEN
- * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
- * array in structure: [TYPE]['identifier']
- * @type array
- */
- public $children = array();
- /**
- * @param string $type
- * @param string $id
- * @return mixed
- */
- public function getChild($type, $id)
- {
- if (!isset($this->children[$type][$id])) {
- $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
- $this->children[$type][$id]->type = $type;
- }
- return $this->children[$type][$id];
- }
- /**
- * @param int $severity
- * @param string $message
- */
- public function addError($severity, $message)
- {
- $this->errors[] = array($severity, $message);
- }
- }
- /**
- * Global exception class for HTML Purifier; any exceptions we throw
- * are from here.
- */
- class HTMLPurifier_Exception extends Exception
- {
- }
- /**
- * Represents a pre or post processing filter on HTML Purifier's output
- *
- * Sometimes, a little ad-hoc fixing of HTML has to be done before
- * it gets sent through HTML Purifier: you can use filters to acheive
- * this effect. For instance, YouTube videos can be preserved using
- * this manner. You could have used a decorator for this task, but
- * PHP's support for them is not terribly robust, so we're going
- * to just loop through the filters.
- *
- * Filters should be exited first in, last out. If there are three filters,
- * named 1, 2 and 3, the order of execution should go 1->preFilter,
- * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
- * 1->postFilter.
- *
- * @note Methods are not declared abstract as it is perfectly legitimate
- * for an implementation not to want anything to happen on a step
- */
- class HTMLPurifier_Filter
- {
- /**
- * Name of the filter for identification purposes.
- * @type string
- */
- public $name;
- /**
- * Pre-processor function, handles HTML before HTML Purifier
- * @param string $html
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public function preFilter($html, $config, $context)
- {
- return $html;
- }
- /**
- * Post-processor function, handles HTML after HTML Purifier
- * @param string $html
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public function postFilter($html, $config, $context)
- {
- return $html;
- }
- }
- /**
- * Generates HTML from tokens.
- * @todo Refactor interface so that configuration/context is determined
- * upon instantiation, no need for messy generateFromTokens() calls
- * @todo Make some of the more internal functions protected, and have
- * unit tests work around that
- */
- class HTMLPurifier_Generator
- {
- /**
- * Whether or not generator should produce XML output.
- * @type bool
- */
- private $_xhtml = true;
- /**
- * :HACK: Whether or not generator should comment the insides of <script> tags.
- * @type bool
- */
- private $_scriptFix = false;
- /**
- * Cache of HTMLDefinition during HTML output to determine whether or
- * not attributes should be minimized.
- * @type HTMLPurifier_HTMLDefinition
- */
- private $_def;
- /**
- * Cache of %Output.SortAttr.
- * @type bool
- */
- private $_sortAttr;
- /**
- * Cache of %Output.FlashCompat.
- * @type bool
- */
- private $_flashCompat;
- /**
- * Cache of %Output.FixInnerHTML.
- * @type bool
- */
- private $_innerHTMLFix;
- /**
- * Stack for keeping track of object information when outputting IE
- * compatibility code.
- * @type array
- */
- private $_flashStack = array();
- /**
- * Configuration for the generator
- * @type HTMLPurifier_Config
- */
- protected $config;
- /**
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- */
- public function __construct($config, $context)
- {
- $this->config = $config;
- $this->_scriptFix = $config->get('Output.CommentScriptContents');
- $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
- $this->_sortAttr = $config->get('Output.SortAttr');
- $this->_flashCompat = $config->get('Output.FlashCompat');
- $this->_def = $config->getHTMLDefinition();
- $this->_xhtml = $this->_def->doctype->xml;
- }
- /**
- * Generates HTML from an array of tokens.
- * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
- * @return string Generated HTML
- */
- public function generateFromTokens($tokens)
- {
- if (!$tokens) {
- return '';
- }
- // Basic algorithm
- $html = '';
- for ($i = 0, $size = count($tokens); $i < $size; $i++) {
- if ($this->_scriptFix && $tokens[$i]->name === 'script'
- && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
- // script special case
- // the contents of the script block must be ONE token
- // for this to work.
- $html .= $this->generateFromToken($tokens[$i++]);
- $html .= $this->generateScriptFromToken($tokens[$i++]);
- }
- $html .= $this->generateFromToken($tokens[$i]);
- }
- // Tidy cleanup
- if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
- $tidy = new Tidy;
- $tidy->parseString(
- $html,
- array(
- 'indent'=> true,
- 'output-xhtml' => $this->_xhtml,
- 'show-body-only' => true,
- 'indent-spaces' => 2,
- 'wrap' => 68,
- ),
- 'utf8'
- );
- $tidy->cleanRepair();
- $html = (string) $tidy; // explicit cast necessary
- }
- // Normalize newlines to system defined value
- if ($this->config->get('Core.NormalizeNewlines')) {
- $nl = $this->config->get('Output.Newline');
- if ($nl === null) {
- $nl = PHP_EOL;
- }
- if ($nl !== "\n") {
- $html = str_replace("\n", $nl, $html);
- }
- }
- return $html;
- }
- /**
- * Generates HTML from a single token.
- * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
- * @return string Generated HTML
- */
- public function generateFromToken($token)
- {
- if (!$token instanceof HTMLPurifier_Token) {
- trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
- return '';
- } elseif ($token instanceof HTMLPurifier_Token_Start) {
- $attr = $this->generateAttributes($token->attr, $token->name);
- if ($this->_flashCompat) {
- if ($token->name == "object") {
- $flash = new stdclass();
- $flash->attr = $token->attr;
- $flash->param = array();
- $this->_flashStack[] = $flash;
- }
- }
- return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
- } elseif ($token instanceof HTMLPurifier_Token_End) {
- $_extra = '';
- if ($this->_flashCompat) {
- if ($token->name == "object" && !empty($this->_flashStack)) {
- // doesn't do anything for now
- }
- }
- return $_extra . '</' . $token->name . '>';
- } elseif ($token instanceof HTMLPurifier_Token_Empty) {
- if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
- $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
- }
- $attr = $this->generateAttributes($token->attr, $token->name);
- return '<' . $token->name . ($attr ? ' ' : '') . $attr .
- ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
- . '>';
- } elseif ($token instanceof HTMLPurifier_Token_Text) {
- return $this->escape($token->data, ENT_NOQUOTES);
- } elseif ($token instanceof HTMLPurifier_Token_Comment) {
- return '<!--' . $token->data . '-->';
- } else {
- return '';
- }
- }
- /**
- * Special case processor for the contents of script tags
- * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
- * @return string
- * @warning This runs into problems if there's already a literal
- * --> somewhere inside the script contents.
- */
- public function generateScriptFromToken($token)
- {
- if (!$token instanceof HTMLPurifier_Token_Text) {
- return $this->generateFromToken($token);
- }
- // Thanks <http://lachy.id.au/log/2005/05/script-comments>
- $data = preg_replace('#//\s*$#', '', $token->data);
- return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
- }
- /**
- * Generates attribute declarations from attribute array.
- * @note This does not include the leading or trailing space.
- * @param array $assoc_array_of_attributes Attribute array
- * @param string $element Name of element attributes are for, used to check
- * attribute minimization.
- * @return string Generated HTML fragment for insertion.
- */
- public function generateAttributes($assoc_array_of_attributes, $element = '')
- {
- $html = '';
- if ($this->_sortAttr) {
- ksort($assoc_array_of_attributes);
- }
- foreach ($assoc_array_of_attributes as $key => $value) {
- if (!$this->_xhtml) {
- // Remove namespaced attributes
- if (strpos($key, ':') !== false) {
- continue;
- }
- // Check if we should minimize the attribute: val="val" -> val
- if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
- $html .= $key . ' ';
- continue;
- }
- }
- // Workaround for Internet Explorer innerHTML bug.
- // Essentially, Internet Explorer, when calculating
- // innerHTML, omits quotes if there are no instances of
- // angled brackets, quotes or spaces. However, when parsing
- // HTML (for example, when you assign to innerHTML), it
- // treats backticks as quotes. Thus,
- // <img alt="``" />
- // becomes
- // <img alt=`` />
- // becomes
- // <img alt='' />
- // Fortunately, all we need to do is trigger an appropriate
- // quoting style, which we do by adding an extra space.
- // This also is consistent with the W3C spec, which states
- // that user agents may ignore leading or trailing
- // whitespace (in fact, most don't, at least for attributes
- // like alt, but an extra space at the end is barely
- // noticeable). Still, we have a configuration knob for
- // this, since this transformation is not necesary if you
- // don't process user input with innerHTML or you don't plan
- // on supporting Internet Explorer.
- if ($this->_innerHTMLFix) {
- if (strpos($value, '`') !== false) {
- // check if correct quoting style would not already be
- // triggered
- if (strcspn($value, '"\' <>') === strlen($value)) {
- // protect!
- $value .= ' ';
- }
- }
- }
- $html .= $key.'="'.$this->escape($value).'" ';
- }
- return rtrim($html);
- }
- /**
- * Escapes raw text data.
- * @todo This really ought to be protected, but until we have a facility
- * for properly generating HTML here w/o using tokens, it stays
- * public.
- * @param string $string String data to escape for HTML.
- * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
- * permissible for non-attribute output.
- * @return string escaped data.
- */
- public function escape($string, $quote = null)
- {
- // Workaround for APC bug on Mac Leopard reported by sidepodcast
- // http://htmlpurifier.org/phorum/read.php?3,4823,4846
- if ($quote === null) {
- $quote = ENT_COMPAT;
- }
- return htmlspecialchars($string, $quote, 'UTF-8');
- }
- }
- /**
- * Definition of the purified HTML that describes allowed children,
- * attributes, and many other things.
- *
- * Conventions:
- *
- * All member variables that are prefixed with info
- * (including the main $info array) are used by HTML Purifier internals
- * and should not be directly edited when customizing the HTMLDefinition.
- * They can usually be set via configuration directives or custom
- * modules.
- *
- * On the other hand, member variables without the info prefix are used
- * internally by the HTMLDefinition and MUST NOT be used by other HTML
- * Purifier internals. Many of them, however, are public, and may be
- * edited by userspace code to tweak the behavior of HTMLDefinition.
- *
- * @note This class is inspected by Printer_HTMLDefinition; please
- * update that class if things here change.
- *
- * @warning Directives that change this object's structure must be in
- * the HTML or Attr namespace!
- */
- class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
- {
- // FULLY-PUBLIC VARIABLES ---------------------------------------------
- /**
- * Associative array of element names to HTMLPurifier_ElementDef.
- * @type HTMLPurifier_ElementDef[]
- */
- public $info = array();
- /**
- * Associative array of global attribute name to attribute definition.
- * @type array
- */
- public $info_global_attr = array();
- /**
- * String name of parent element HTML will be going into.
- * @type string
- */
- public $info_parent = 'div';
- /**
- * Definition for parent element, allows parent element to be a
- * tag that's not allowed inside the HTML fragment.
- * @type HTMLPurifier_ElementDef
- */
- public $info_parent_def;
- /**
- * String name of element used to wrap inline elements in block context.
- * @type string
- * @note This is rarely used except for BLOCKQUOTEs in strict mode
- */
- public $info_block_wrapper = 'p';
- /**
- * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
- * @type array
- */
- public $info_tag_transform = array();
- /**
- * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
- * @type HTMLPurifier_AttrTransform[]
- */
- public $info_attr_transform_pre = array();
- /**
- * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
- * @type HTMLPurifier_AttrTransform[]
- */
- public $info_attr_transform_post = array();
- /**
- * Nested lookup array of content set name (Block, Inline) to
- * element name to whether or not it belongs in that content set.
- * @type array
- */
- public $info_content_sets = array();
- /**
- * Indexed list of HTMLPurifier_Injector to be used.
- * @type HTMLPurifier_Injector[]
- */
- public $info_injector = array();
- /**
- * Doctype object
- * @type HTMLPurifier_Doctype
- */
- public $doctype;
- // RAW CUSTOMIZATION STUFF --------------------------------------------
- /**
- * Adds a custom attribute to a pre-existing element
- * @note This is strictly convenience, and does not have a corresponding
- * method in HTMLPurifier_HTMLModule
- * @param string $element_name Element name to add attribute to
- * @param string $attr_name Name of attribute
- * @param mixed $def Attribute definition, can be string or object, see
- * HTMLPurifier_AttrTypes for details
- */
- public function addAttribute($element_name, $attr_name, $def)
- {
- $module = $this->getAnonymousModule();
- if (!isset($module->info[$element_name])) {
- $element = $module->addBlankElement($element_name);
- } else {
- $element = $module->info[$element_name];
- }
- $element->attr[$attr_name] = $def;
- }
- /**
- * Adds a custom element to your HTML definition
- * @see HTMLPurifier_HTMLModule::addElement() for detailed
- * parameter and return value descriptions.
- */
- public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
- {
- $module = $this->getAnonymousModule();
- // assume that if the user is calling this, the element
- // is safe. This may not be a good idea
- $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
- return $element;
- }
- /**
- * Adds a blank element to your HTML definition, for overriding
- * existing behavior
- * @param string $element_name
- * @return HTMLPurifier_ElementDef
- * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
- * parameter and return value descriptions.
- */
- public function addBlankElement($element_name)
- {
- $module = $this->getAnonymousModule();
- $element = $module->addBlankElement($element_name);
- return $element;
- }
- /**
- * Retrieves a reference to the anonymous module, so you can
- * bust out advanced features without having to make your own
- * module.
- * @return HTMLPurifier_HTMLModule
- */
- public function getAnonymousModule()
- {
- if (!$this->_anonModule) {
- $this->_anonModule = new HTMLPurifier_HTMLModule();
- $this->_anonModule->name = 'Anonymous';
- }
- return $this->_anonModule;
- }
- private $_anonModule = null;
- // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
- /**
- * @type string
- */
- public $type = 'HTML';
- /**
- * @type HTMLPurifier_HTMLModuleManager
- */
- public $manager;
- /**
- * Performs low-cost, preliminary initialization.
- */
- public function __construct()
- {
- $this->manager = new HTMLPurifier_HTMLModuleManager();
- }
- /**
- * @param HTMLPurifier_Config $config
- */
- protected function doSetup($config)
- {
- $this->processModules($config);
- $this->setupConfigStuff($config);
- unset($this->manager);
- // cleanup some of the element definitions
- foreach ($this->info as $k => $v) {
- unset($this->info[$k]->content_model);
- unset($this->info[$k]->content_model_type);
- }
- }
- /**
- * Extract out the information from the manager
- * @param HTMLPurifier_Config $config
- */
- protected function processModules($config)
- {
- if ($this->_anonModule) {
- // for user specific changes
- // this is late-loaded so we don't have to deal with PHP4
- // reference wonky-ness
- $this->manager->addModule($this->_anonModule);
- unset($this->_anonModule);
- }
- $this->manager->setup($config);
- $this->doctype = $this->manager->doctype;
- foreach ($this->manager->modules as $module) {
- foreach ($module->info_tag_transform as $k => $v) {
- if ($v === false) {
- unset($this->info_tag_transform[$k]);
- } else {
- $this->info_tag_transform[$k] = $v;
- }
- }
- foreach ($module->info_attr_transform_pre as $k => $v) {
- if ($v === false) {
- unset($this->info_attr_transform_pre[$k]);
- } else {
- $this->info_attr_transform_pre[$k] = $v;
- }
- }
- foreach ($module->info_attr_transform_post as $k => $v) {
- if ($v === false) {
- unset($this->info_attr_transform_post[$k]);
- } else {
- $this->info_attr_transform_post[$k] = $v;
- }
- }
- foreach ($module->info_injector as $k => $v) {
- if ($v === false) {
- unset($this->info_injector[$k]);
- } else {
- $this->info_injector[$k] = $v;
- }
- }
- }
- $this->info = $this->manager->getElements();
- $this->info_content_sets = $this->manager->contentSets->lookup;
- }
- /**
- * Sets up stuff based on config. We need a better way of doing this.
- * @param HTMLPurifier_Config $config
- */
- protected function setupConfigStuff($config)
- {
- $block_wrapper = $config->get('HTML.BlockWrapper');
- if (isset($this->info_content_sets['Block'][$block_wrapper])) {
- $this->info_block_wrapper = $block_wrapper;
- } else {
- trigger_error(
- 'Cannot use non-block element as block wrapper',
- E_USER_ERROR
- );
- }
- $parent = $config->get('HTML.Parent');
- $def = $this->manager->getElement($parent, true);
- if ($def) {
- $this->info_parent = $parent;
- $this->info_parent_def = $def;
- } else {
- trigger_error(
- 'Cannot use unrecognized element as parent',
- E_USER_ERROR
- );
- $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
- }
- // support template text
- $support = "(for information on implementing this, see the support forums) ";
- // setup allowed elements -----------------------------------------
- $allowed_elements = $config->get('HTML.AllowedElements');
- $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
- if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
- $allowed = $config->get('HTML.Allowed');
- if (is_string($allowed)) {
- list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
- }
- }
- if (is_array($allowed_elements)) {
- foreach ($this->info as $name => $d) {
- if (!isset($allowed_elements[$name])) {
- unset($this->info[$name]);
- }
- unset($allowed_elements[$name]);
- }
- // emit errors
- foreach ($allowed_elements as $element => $d) {
- $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
- trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
- }
- }
- // setup allowed attributes ---------------------------------------
- $allowed_attributes_mutable = $allowed_attributes; // by copy!
- if (is_array($allowed_attributes)) {
- // This actually doesn't do anything, since we went away from
- // global attributes. It's possible that userland code uses
- // it, but HTMLModuleManager doesn't!
- foreach ($this->info_global_attr as $attr => $x) {
- $keys = array($attr, "*@$attr", "*.$attr");
- $delete = true;
- foreach ($keys as $key) {
- if ($delete && isset($allowed_attributes[$key])) {
- $delete = false;
- }
- if (isset($allowed_attributes_mutable[$key])) {
- unset($allowed_attributes_mutable[$key]);
- }
- }
- if ($delete) {
- unset($this->info_global_attr[$attr]);
- }
- }
- foreach ($this->info as $tag => $info) {
- foreach ($info->attr as $attr => $x) {
- $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
- $delete = true;
- foreach ($keys as $key) {
- if ($delete && isset($allowed_attributes[$key])) {
- $delete = false;
- }
- if (isset($allowed_attributes_mutable[$key])) {
- unset($allowed_attributes_mutable[$key]);
- }
- }
- if ($delete) {
- if ($this->info[$tag]->attr[$attr]->required) {
- trigger_error(
- "Required attribute '$attr' in element '$tag' " .
- "was not allowed, which means '$tag' will not be allowed either",
- E_USER_WARNING
- );
- }
- unset($this->info[$tag]->attr[$attr]);
- }
- }
- }
- // emit errors
- foreach ($allowed_attributes_mutable as $elattr => $d) {
- $bits = preg_split('/[.@]/', $elattr, 2);
- $c = count($bits);
- switch ($c) {
- case 2:
- if ($bits[0] !== '*') {
- $element = htmlspecialchars($bits[0]);
- $attribute = htmlspecialchars($bits[1]);
- if (!isset($this->info[$element])) {
- trigger_error(
- "Cannot allow attribute '$attribute' if element " .
- "'$element' is not allowed/supported $support"
- );
- } else {
- trigger_error(
- "Attribute '$attribute' in element '$element' not supported $support",
- E_USER_WARNING
- );
- }
- break;
- }
- // otherwise fall through
- case 1:
- $attribute = htmlspecialchars($bits[0]);
- trigger_error(
- "Global attribute '$attribute' is not ".
- "supported in any elements $support",
- E_USER_WARNING
- );
- break;
- }
- }
- }
- // setup forbidden elements ---------------------------------------
- $forbidden_elements = $config->get('HTML.ForbiddenElements');
- $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
- foreach ($this->info as $tag => $info) {
- if (isset($forbidden_elements[$tag])) {
- unset($this->info[$tag]);
- continue;
- }
- foreach ($info->attr as $attr => $x) {
- if (isset($forbidden_attributes["$tag@$attr"]) ||
- isset($forbidden_attributes["*@$attr"]) ||
- isset($forbidden_attributes[$attr])
- ) {
- unset($this->info[$tag]->attr[$attr]);
- continue;
- } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
- // $tag.$attr are not user supplied, so no worries!
- trigger_error(
- "Error with $tag.$attr: tag.attr syntax not supported for " .
- "HTML.ForbiddenAttributes; use tag@attr instead",
- E_USER_WARNING
- );
- }
- }
- }
- foreach ($forbidden_attributes as $key => $v) {
- if (strlen($key) < 2) {
- continue;
- }
- if ($key[0] != '*') {
- continue;
- }
- if ($key[1] == '.') {
- trigger_error(
- "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
- E_USER_WARNING
- );
- }
- }
- // setup injectors -----------------------------------------------------
- foreach ($this->info_injector as $i => $injector) {
- if ($injector->checkNeeded($config) !== false) {
- // remove injector that does not have it's required
- // elements/attributes present, and is thus not needed.
- unset($this->info_injector[$i]);
- }
- }
- }
- /**
- * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
- * separate lists for processing. Format is element[attr1|attr2],element2...
- * @warning Although it's largely drawn from TinyMCE's implementation,
- * it is different, and you'll probably have to modify your lists
- * @param array $list String list to parse
- * @return array
- * @todo Give this its own class, probably static interface
- */
- public function parseTinyMCEAllowedList($list)
- {
- $list = str_replace(array(' ', "\t"), '', $list);
- $elements = array();
- $attributes = array();
- $chunks = preg_split('/(,|[\n\r]+)/', $list);
- foreach ($chunks as $chunk) {
- if (empty($chunk)) {
- continue;
- }
- // remove TinyMCE element control characters
- if (!strpos($chunk, '[')) {
- $element = $chunk;
- $attr = false;
- } else {
- list($element, $attr) = explode('[', $chunk);
- }
- if ($element !== '*') {
- $elements[$element] = true;
- }
- if (!$attr) {
- continue;
- }
- $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
- $attr = explode('|', $attr);
- foreach ($attr as $key) {
- $attributes["$element.$key"] = true;
- }
- }
- return array($elements, $attributes);
- }
- }
- /**
- * Represents an XHTML 1.1 module, with information on elements, tags
- * and attributes.
- * @note Even though this is technically XHTML 1.1, it is also used for
- * regular HTML parsing. We are using modulization as a convenient
- * way to represent the internals of HTMLDefinition, and our
- * implementation is by no means conforming and does not directly
- * use the normative DTDs or XML schemas.
- * @note The public variables in a module should almost directly
- * correspond to the variables in HTMLPurifier_HTMLDefinition.
- * However, the prefix info carries no special meaning in these
- * objects (include it anyway if that's the correspondence though).
- * @todo Consider making some member functions protected
- */
- class HTMLPurifier_HTMLModule
- {
- // -- Overloadable ----------------------------------------------------
- /**
- * Short unique string identifier of the module.
- * @type string
- */
- public $name;
- /**
- * Informally, a list of elements this module changes.
- * Not used in any significant way.
- * @type array
- */
- public $elements = array();
- /**
- * Associative array of element names to element definitions.
- * Some definitions may be incomplete, to be merged in later
- * with the full definition.
- * @type array
- */
- public $info = array();
- /**
- * Associative array of content set names to content set additions.
- * This is commonly used to, say, add an A element to the Inline
- * content set. This corresponds to an internal variable $content_sets
- * and NOT info_content_sets member variable of HTMLDefinition.
- * @type array
- */
- public $content_sets = array();
- /**
- * Associative array of attribute collection names to attribute
- * collection additions. More rarely used for adding attributes to
- * the global collections. Example is the StyleAttribute module adding
- * the style attribute to the Core. Corresponds to HTMLDefinition's
- * attr_collections->info, since the object's data is only info,
- * with extra behavior associated with it.
- * @type array
- */
- public $attr_collections = array();
- /**
- * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
- * @type array
- */
- public $info_tag_transform = array();
- /**
- * List of HTMLPurifier_AttrTransform to be performed before validation.
- * @type array
- */
- public $info_attr_transform_pre = array();
- /**
- * List of HTMLPurifier_AttrTransform to be performed after validation.
- * @type array
- */
- public $info_attr_transform_post = array();
- /**
- * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
- * An injector will only be invoked if all of it's pre-requisites are met;
- * if an injector fails setup, there will be no error; it will simply be
- * silently disabled.
- * @type array
- */
- public $info_injector = array();
- /**
- * Boolean flag that indicates whether or not getChildDef is implemented.
- * For optimization reasons: may save a call to a function. Be sure
- * to set it if you do implement getChildDef(), otherwise it will have
- * no effect!
- * @type bool
- */
- public $defines_child_def = false;
- /**
- * Boolean flag whether or not this module is safe. If it is not safe, all
- * of its members are unsafe. Modules are safe by default (this might be
- * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
- * which is based off of safe HTML, to explicitly say, "This is safe," even
- * though there are modules which are "unsafe")
- *
- * @type bool
- * @note Previously, safety could be applied at an element level granularity.
- * We've removed this ability, so in order to add "unsafe" elements
- * or attributes, a dedicated module with this property set to false
- * must be used.
- */
- public $safe = true;
- /**
- * Retrieves a proper HTMLPurifier_ChildDef subclass based on
- * content_model and content_model_type member variables of
- * the HTMLPurifier_ElementDef class. There is a similar function
- * in HTMLPurifier_HTMLDefinition.
- * @param HTMLPurifier_ElementDef $def
- * @return HTMLPurifier_ChildDef subclass
- */
- public function getChildDef($def)
- {
- return false;
- }
- // -- Convenience -----------------------------------------------------
- /**
- * Convenience function that sets up a new element
- * @param string $element Name of element to add
- * @param string|bool $type What content set should element be registered to?
- * Set as false to skip this step.
- * @param string $contents Allowed children in form of:
- * "$content_model_type: $content_model"
- * @param array $attr_includes What attribute collections to register to
- * element?
- * @param array $attr What unique attributes does the element define?
- * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
- * @return HTMLPurifier_ElementDef Created element definition object, so you
- * can set advanced parameters
- */
- public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
- {
- $this->elements[] = $element;
- // parse content_model
- list($content_model_type, $content_model) = $this->parseContents($contents);
- // merge in attribute inclusions
- $this->mergeInAttrIncludes($attr, $attr_includes);
- // add element to content sets
- if ($type) {
- $this->addElementToContentSet($element, $type);
- }
- // create element
- $this->info[$element] = HTMLPurifier_ElementDef::create(
- $content_model,
- $content_model_type,
- $attr
- );
- // literal object $contents means direct child manipulation
- if (!is_string($contents)) {
- $this->info[$element]->child = $contents;
- }
- return $this->info[$element];
- }
- /**
- * Convenience function that creates a totally blank, non-standalone
- * element.
- * @param string $element Name of element to create
- * @return HTMLPurifier_ElementDef Created element
- */
- public function addBlankElement($element)
- {
- if (!isset($this->info[$element])) {
- $this->elements[] = $element;
- $this->info[$element] = new HTMLPurifier_ElementDef();
- $this->info[$element]->standalone = false;
- } else {
- trigger_error("Definition for $element already exists in module, cannot redefine");
- }
- return $this->info[$element];
- }
- /**
- * Convenience function that registers an element to a content set
- * @param string $element Element to register
- * @param string $type Name content set (warning: case sensitive, usually upper-case
- * first letter)
- */
- public function addElementToContentSet($element, $type)
- {
- if (!isset($this->content_sets[$type])) {
- $this->content_sets[$type] = '';
- } else {
- $this->content_sets[$type] .= ' | ';
- }
- $this->content_sets[$type] .= $element;
- }
- /**
- * Convenience function that transforms single-string contents
- * into separate content model and content model type
- * @param string $contents Allowed children in form of:
- * "$content_model_type: $content_model"
- * @return array
- * @note If contents is an object, an array of two nulls will be
- * returned, and the callee needs to take the original $contents
- * and use it directly.
- */
- public function parseContents($contents)
- {
- if (!is_string($contents)) {
- return array(null, null);
- } // defer
- switch ($contents) {
- // check for shorthand content model forms
- case 'Empty':
- return array('empty', '');
- case 'Inline':
- return array('optional', 'Inline | #PCDATA');
- case 'Flow':
- return array('optional', 'Flow | #PCDATA');
- }
- list($content_model_type, $content_model) = explode(':', $contents);
- $content_model_type = strtolower(trim($content_model_type));
- $content_model = trim($content_model);
- return array($content_model_type, $content_model);
- }
- /**
- * Convenience function that merges a list of attribute includes into
- * an attribute array.
- * @param array $attr Reference to attr array to modify
- * @param array $attr_includes Array of includes / string include to merge in
- */
- public function mergeInAttrIncludes(&$attr, $attr_includes)
- {
- if (!is_array($attr_includes)) {
- if (empty($attr_includes)) {
- $attr_includes = array();
- } else {
- $attr_includes = array($attr_includes);
- }
- }
- $attr[0] = $attr_includes;
- }
- /**
- * Convenience function that generates a lookup table with boolean
- * true as value.
- * @param string $list List of values to turn into a lookup
- * @note You can also pass an arbitrary number of arguments in
- * place of the regular argument
- * @return array array equivalent of list
- */
- public function makeLookup($list)
- {
- if (is_string($list)) {
- $list = func_get_args();
- }
- $ret = array();
- foreach ($list as $value) {
- if (is_null($value)) {
- continue;
- }
- $ret[$value] = true;
- }
- return $ret;
- }
- /**
- * Lazy load construction of the module after determining whether
- * or not it's needed, and also when a finalized configuration object
- * is available.
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- }
- }
- class HTMLPurifier_HTMLModuleManager
- {
- /**
- * @type HTMLPurifier_DoctypeRegistry
- */
- public $doctypes;
- /**
- * Instance of current doctype.
- * @type string
- */
- public $doctype;
- /**
- * @type HTMLPurifier_AttrTypes
- */
- public $attrTypes;
- /**
- * Active instances of modules for the specified doctype are
- * indexed, by name, in this array.
- * @type HTMLPurifier_HTMLModule[]
- */
- public $modules = array();
- /**
- * Array of recognized HTMLPurifier_HTMLModule instances,
- * indexed by module's class name. This array is usually lazy loaded, but a
- * user can overload a module by pre-emptively registering it.
- * @type HTMLPurifier_HTMLModule[]
- */
- public $registeredModules = array();
- /**
- * List of extra modules that were added by the user
- * using addModule(). These get unconditionally merged into the current doctype, whatever
- * it may be.
- * @type HTMLPurifier_HTMLModule[]
- */
- public $userModules = array();
- /**
- * Associative array of element name to list of modules that have
- * definitions for the element; this array is dynamically filled.
- * @type array
- */
- public $elementLookup = array();
- /**
- * List of prefixes we should use for registering small names.
- * @type array
- */
- public $prefixes = array('HTMLPurifier_HTMLModule_');
- /**
- * @type HTMLPurifier_ContentSets
- */
- public $contentSets;
- /**
- * @type HTMLPurifier_AttrCollections
- */
- public $attrCollections;
- /**
- * If set to true, unsafe elements and attributes will be allowed.
- * @type bool
- */
- public $trusted = false;
- public function __construct()
- {
- // editable internal objects
- $this->attrTypes = new HTMLPurifier_AttrTypes();
- $this->doctypes = new HTMLPurifier_DoctypeRegistry();
- // setup basic modules
- $common = array(
- 'CommonAttributes', 'Text', 'Hypertext', 'List',
- 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
- 'StyleAttribute',
- // Unsafe:
- 'Scripting', 'Object', 'Forms',
- // Sorta legacy, but present in strict:
- 'Name',
- );
- $transitional = array('Legacy', 'Target', 'Iframe');
- $xml = array('XMLCommonAttributes');
- $non_xml = array('NonXMLCommonAttributes');
- // setup basic doctypes
- $this->doctypes->register(
- 'HTML 4.01 Transitional',
- false,
- array_merge($common, $transitional, $non_xml),
- array('Tidy_Transitional', 'Tidy_Proprietary'),
- array(),
- '-//W3C//DTD HTML 4.01 Transitional//EN',
- 'http://www.w3.org/TR/html4/loose.dtd'
- );
- $this->doctypes->register(
- 'HTML 4.01 Strict',
- false,
- array_merge($common, $non_xml),
- array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
- array(),
- '-//W3C//DTD HTML 4.01//EN',
- 'http://www.w3.org/TR/html4/strict.dtd'
- );
- $this->doctypes->register(
- 'XHTML 1.0 Transitional',
- true,
- array_merge($common, $transitional, $xml, $non_xml),
- array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
- array(),
- '-//W3C//DTD XHTML 1.0 Transitional//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
- );
- $this->doctypes->register(
- 'XHTML 1.0 Strict',
- true,
- array_merge($common, $xml, $non_xml),
- array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
- array(),
- '-//W3C//DTD XHTML 1.0 Strict//EN',
- 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
- );
- $this->doctypes->register(
- 'XHTML 1.1',
- true,
- // Iframe is a real XHTML 1.1 module, despite being
- // "transitional"!
- array_merge($common, $xml, array('Ruby', 'Iframe')),
- array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
- array(),
- '-//W3C//DTD XHTML 1.1//EN',
- 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
- );
- }
- /**
- * Registers a module to the recognized module list, useful for
- * overloading pre-existing modules.
- * @param $module Mixed: string module name, with or without
- * HTMLPurifier_HTMLModule prefix, or instance of
- * subclass of HTMLPurifier_HTMLModule.
- * @param $overload Boolean whether or not to overload previous modules.
- * If this is not set, and you do overload a module,
- * HTML Purifier will complain with a warning.
- * @note This function will not call autoload, you must instantiate
- * (and thus invoke) autoload outside the method.
- * @note If a string is passed as a module name, different variants
- * will be tested in this order:
- * - Check for HTMLPurifier_HTMLModule_$name
- * - Check all prefixes with $name in order they were added
- * - Check for literal object name
- * - Throw fatal error
- * If your object name collides with an internal class, specify
- * your module manually. All modules must have been included
- * externally: registerModule will not perform inclusions for you!
- */
- public function registerModule($module, $overload = false)
- {
- if (is_string($module)) {
- // attempt to load the module
- $original_module = $module;
- $ok = false;
- foreach ($this->prefixes as $prefix) {
- $module = $prefix . $original_module;
- if (class_exists($module)) {
- $ok = true;
- break;
- }
- }
- if (!$ok) {
- $module = $original_module;
- if (!class_exists($module)) {
- trigger_error(
- $original_module . ' module does not exist',
- E_USER_ERROR
- );
- return;
- }
- }
- $module = new $module();
- }
- if (empty($module->name)) {
- trigger_error('Module instance of ' . get_class($module) . ' must have name');
- return;
- }
- if (!$overload && isset($this->registeredModules[$module->name])) {
- trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
- }
- $this->registeredModules[$module->name] = $module;
- }
- /**
- * Adds a module to the current doctype by first registering it,
- * and then tacking it on to the active doctype
- */
- public function addModule($module)
- {
- $this->registerModule($module);
- if (is_object($module)) {
- $module = $module->name;
- }
- $this->userModules[] = $module;
- }
- /**
- * Adds a class prefix that registerModule() will use to resolve a
- * string name to a concrete class
- */
- public function addPrefix($prefix)
- {
- $this->prefixes[] = $prefix;
- }
- /**
- * Performs processing on modules, after being called you may
- * use getElement() and getElements()
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->trusted = $config->get('HTML.Trusted');
- // generate
- $this->doctype = $this->doctypes->make($config);
- $modules = $this->doctype->modules;
- // take out the default modules that aren't allowed
- $lookup = $config->get('HTML.AllowedModules');
- $special_cases = $config->get('HTML.CoreModules');
- if (is_array($lookup)) {
- foreach ($modules as $k => $m) {
- if (isset($special_cases[$m])) {
- continue;
- }
- if (!isset($lookup[$m])) {
- unset($modules[$k]);
- }
- }
- }
- // custom modules
- if ($config->get('HTML.Proprietary')) {
- $modules[] = 'Proprietary';
- }
- if ($config->get('HTML.SafeObject')) {
- $modules[] = 'SafeObject';
- }
- if ($config->get('HTML.SafeEmbed')) {
- $modules[] = 'SafeEmbed';
- }
- if ($config->get('HTML.SafeScripting') !== array()) {
- $modules[] = 'SafeScripting';
- }
- if ($config->get('HTML.Nofollow')) {
- $modules[] = 'Nofollow';
- }
- if ($config->get('HTML.TargetBlank')) {
- $modules[] = 'TargetBlank';
- }
- // merge in custom modules
- $modules = array_merge($modules, $this->userModules);
- foreach ($modules as $module) {
- $this->processModule($module);
- $this->modules[$module]->setup($config);
- }
- foreach ($this->doctype->tidyModules as $module) {
- $this->processModule($module);
- $this->modules[$module]->setup($config);
- }
- // prepare any injectors
- foreach ($this->modules as $module) {
- $n = array();
- foreach ($module->info_injector as $injector) {
- if (!is_object($injector)) {
- $class = "HTMLPurifier_Injector_$injector";
- $injector = new $class;
- }
- $n[$injector->name] = $injector;
- }
- $module->info_injector = $n;
- }
- // setup lookup table based on all valid modules
- foreach ($this->modules as $module) {
- foreach ($module->info as $name => $def) {
- if (!isset($this->elementLookup[$name])) {
- $this->elementLookup[$name] = array();
- }
- $this->elementLookup[$name][] = $module->name;
- }
- }
- // note the different choice
- $this->contentSets = new HTMLPurifier_ContentSets(
- // content set assembly deals with all possible modules,
- // not just ones deemed to be "safe"
- $this->modules
- );
- $this->attrCollections = new HTMLPurifier_AttrCollections(
- $this->attrTypes,
- // there is no way to directly disable a global attribute,
- // but using AllowedAttributes or simply not including
- // the module in your custom doctype should be sufficient
- $this->modules
- );
- }
- /**
- * Takes a module and adds it to the active module collection,
- * registering it if necessary.
- */
- public function processModule($module)
- {
- if (!isset($this->registeredModules[$module]) || is_object($module)) {
- $this->registerModule($module);
- }
- $this->modules[$module] = $this->registeredModules[$module];
- }
- /**
- * Retrieves merged element definitions.
- * @return Array of HTMLPurifier_ElementDef
- */
- public function getElements()
- {
- $elements = array();
- foreach ($this->modules as $module) {
- if (!$this->trusted && !$module->safe) {
- continue;
- }
- foreach ($module->info as $name => $v) {
- if (isset($elements[$name])) {
- continue;
- }
- $elements[$name] = $this->getElement($name);
- }
- }
- // remove dud elements, this happens when an element that
- // appeared to be safe actually wasn't
- foreach ($elements as $n => $v) {
- if ($v === false) {
- unset($elements[$n]);
- }
- }
- return $elements;
- }
- /**
- * Retrieves a single merged element definition
- * @param string $name Name of element
- * @param bool $trusted Boolean trusted overriding parameter: set to true
- * if you want the full version of an element
- * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef
- * @note You may notice that modules are getting iterated over twice (once
- * in getElements() and once here). This
- * is because
- */
- public function getElement($name, $trusted = null)
- {
- if (!isset($this->elementLookup[$name])) {
- return false;
- }
- // setup global state variables
- $def = false;
- if ($trusted === null) {
- $trusted = $this->trusted;
- }
- // iterate through each module that has registered itself to this
- // element
- foreach ($this->elementLookup[$name] as $module_name) {
- $module = $this->modules[$module_name];
- // refuse to create/merge from a module that is deemed unsafe--
- // pretend the module doesn't exist--when trusted mode is not on.
- if (!$trusted && !$module->safe) {
- continue;
- }
- // clone is used because, ideally speaking, the original
- // definition should not be modified. Usually, this will
- // make no difference, but for consistency's sake
- $new_def = clone $module->info[$name];
- if (!$def && $new_def->standalone) {
- $def = $new_def;
- } elseif ($def) {
- // This will occur even if $new_def is standalone. In practice,
- // this will usually result in a full replacement.
- $def->mergeIn($new_def);
- } else {
- // :TODO:
- // non-standalone definitions that don't have a standalone
- // to merge into could be deferred to the end
- // HOWEVER, it is perfectly valid for a non-standalone
- // definition to lack a standalone definition, even
- // after all processing: this allows us to safely
- // specify extra attributes for elements that may not be
- // enabled all in one place. In particular, this might
- // be the case for trusted elements. WARNING: care must
- // be taken that the /extra/ definitions are all safe.
- continue;
- }
- // attribute value expansions
- $this->attrCollections->performInclusions($def->attr);
- $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
- // descendants_are_inline, for ChildDef_Chameleon
- if (is_string($def->content_model) &&
- strpos($def->content_model, 'Inline') !== false) {
- if ($name != 'del' && $name != 'ins') {
- // this is for you, ins/del
- $def->descendants_are_inline = true;
- }
- }
- $this->contentSets->generateChildDef($def, $module);
- }
- // This can occur if there is a blank definition, but no base to
- // mix it in with
- if (!$def) {
- return false;
- }
- // add information on required attributes
- foreach ($def->attr as $attr_name => $attr_def) {
- if ($attr_def->required) {
- $def->required_attr[] = $attr_name;
- }
- }
- return $def;
- }
- }
- /**
- * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
- * @note In Slashdot-speak, dupe means duplicate.
- * @note The default constructor does not accept $config or $context objects:
- * use must use the static build() factory method to perform initialization.
- */
- class HTMLPurifier_IDAccumulator
- {
- /**
- * Lookup table of IDs we've accumulated.
- * @public
- */
- public $ids = array();
- /**
- * Builds an IDAccumulator, also initializing the default blacklist
- * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
- * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
- * @return HTMLPurifier_IDAccumulator Fully initialized HTMLPurifier_IDAccumulator
- */
- public static function build($config, $context)
- {
- $id_accumulator = new HTMLPurifier_IDAccumulator();
- $id_accumulator->load($config->get('Attr.IDBlacklist'));
- return $id_accumulator;
- }
- /**
- * Add an ID to the lookup table.
- * @param string $id ID to be added.
- * @return bool status, true if success, false if there's a dupe
- */
- public function add($id)
- {
- if (isset($this->ids[$id])) {
- return false;
- }
- return $this->ids[$id] = true;
- }
- /**
- * Load a list of IDs into the lookup table
- * @param $array_of_ids Array of IDs to load
- * @note This function doesn't care about duplicates
- */
- public function load($array_of_ids)
- {
- foreach ($array_of_ids as $id) {
- $this->ids[$id] = true;
- }
- }
- }
- /**
- * Injects tokens into the document while parsing for well-formedness.
- * This enables "formatter-like" functionality such as auto-paragraphing,
- * smiley-ification and linkification to take place.
- *
- * A note on how handlers create changes; this is done by assigning a new
- * value to the $token reference. These values can take a variety of forms and
- * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
- * documentation.
- *
- * @todo Allow injectors to request a re-run on their output. This
- * would help if an operation is recursive.
- */
- abstract class HTMLPurifier_Injector
- {
- /**
- * Advisory name of injector, this is for friendly error messages.
- * @type string
- */
- public $name;
- /**
- * @type HTMLPurifier_HTMLDefinition
- */
- protected $htmlDefinition;
- /**
- * Reference to CurrentNesting variable in Context. This is an array
- * list of tokens that we are currently "inside"
- * @type array
- */
- protected $currentNesting;
- /**
- * Reference to current token.
- * @type HTMLPurifier_Token
- */
- protected $currentToken;
- /**
- * Reference to InputZipper variable in Context.
- * @type HTMLPurifier_Zipper
- */
- protected $inputZipper;
- /**
- * Array of elements and attributes this injector creates and therefore
- * need to be allowed by the definition. Takes form of
- * array('element' => array('attr', 'attr2'), 'element2')
- * @type array
- */
- public $needed = array();
- /**
- * Number of elements to rewind backwards (relative).
- * @type bool|int
- */
- protected $rewindOffset = false;
- /**
- * Rewind to a spot to re-perform processing. This is useful if you
- * deleted a node, and now need to see if this change affected any
- * earlier nodes. Rewinding does not affect other injectors, and can
- * result in infinite loops if not used carefully.
- * @param bool|int $offset
- * @warning HTML Purifier will prevent you from fast-forwarding with this
- * function.
- */
- public function rewindOffset($offset)
- {
- $this->rewindOffset = $offset;
- }
- /**
- * Retrieves rewind offset, and then unsets it.
- * @return bool|int
- */
- public function getRewindOffset()
- {
- $r = $this->rewindOffset;
- $this->rewindOffset = false;
- return $r;
- }
- /**
- * Prepares the injector by giving it the config and context objects:
- * this allows references to important variables to be made within
- * the injector. This function also checks if the HTML environment
- * will work with the Injector (see checkNeeded()).
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
- */
- public function prepare($config, $context)
- {
- $this->htmlDefinition = $config->getHTMLDefinition();
- // Even though this might fail, some unit tests ignore this and
- // still test checkNeeded, so be careful. Maybe get rid of that
- // dependency.
- $result = $this->checkNeeded($config);
- if ($result !== false) {
- return $result;
- }
- $this->currentNesting =& $context->get('CurrentNesting');
- $this->currentToken =& $context->get('CurrentToken');
- $this->inputZipper =& $context->get('InputZipper');
- return false;
- }
- /**
- * This function checks if the HTML environment
- * will work with the Injector: if p tags are not allowed, the
- * Auto-Paragraphing injector should not be enabled.
- * @param HTMLPurifier_Config $config
- * @return bool|string Boolean false if success, string of missing needed element/attribute if failure
- */
- public function checkNeeded($config)
- {
- $def = $config->getHTMLDefinition();
- foreach ($this->needed as $element => $attributes) {
- if (is_int($element)) {
- $element = $attributes;
- }
- if (!isset($def->info[$element])) {
- return $element;
- }
- if (!is_array($attributes)) {
- continue;
- }
- foreach ($attributes as $name) {
- if (!isset($def->info[$element]->attr[$name])) {
- return "$element.$name";
- }
- }
- }
- return false;
- }
- /**
- * Tests if the context node allows a certain element
- * @param string $name Name of element to test for
- * @return bool True if element is allowed, false if it is not
- */
- public function allowsElement($name)
- {
- if (!empty($this->currentNesting)) {
- $parent_token = array_pop($this->currentNesting);
- $this->currentNesting[] = $parent_token;
- $parent = $this->htmlDefinition->info[$parent_token->name];
- } else {
- $parent = $this->htmlDefinition->info_parent_def;
- }
- if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
- return false;
- }
- // check for exclusion
- for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
- $node = $this->currentNesting[$i];
- $def = $this->htmlDefinition->info[$node->name];
- if (isset($def->excludes[$name])) {
- return false;
- }
- }
- return true;
- }
- /**
- * Iterator function, which starts with the next token and continues until
- * you reach the end of the input tokens.
- * @warning Please prevent previous references from interfering with this
- * functions by setting $i = null beforehand!
- * @param int $i Current integer index variable for inputTokens
- * @param HTMLPurifier_Token $current Current token variable.
- * Do NOT use $token, as that variable is also a reference
- * @return bool
- */
- protected function forward(&$i, &$current)
- {
- if ($i === null) {
- $i = count($this->inputZipper->back) - 1;
- } else {
- $i--;
- }
- if ($i < 0) {
- return false;
- }
- $current = $this->inputZipper->back[$i];
- return true;
- }
- /**
- * Similar to _forward, but accepts a third parameter $nesting (which
- * should be initialized at 0) and stops when we hit the end tag
- * for the node $this->inputIndex starts in.
- * @param int $i Current integer index variable for inputTokens
- * @param HTMLPurifier_Token $current Current token variable.
- * Do NOT use $token, as that variable is also a reference
- * @param int $nesting
- * @return bool
- */
- protected function forwardUntilEndToken(&$i, &$current, &$nesting)
- {
- $result = $this->forward($i, $current);
- if (!$result) {
- return false;
- }
- if ($nesting === null) {
- $nesting = 0;
- }
- if ($current instanceof HTMLPurifier_Token_Start) {
- $nesting++;
- } elseif ($current instanceof HTMLPurifier_Token_End) {
- if ($nesting <= 0) {
- return false;
- }
- $nesting--;
- }
- return true;
- }
- /**
- * Iterator function, starts with the previous token and continues until
- * you reach the beginning of input tokens.
- * @warning Please prevent previous references from interfering with this
- * functions by setting $i = null beforehand!
- * @param int $i Current integer index variable for inputTokens
- * @param HTMLPurifier_Token $current Current token variable.
- * Do NOT use $token, as that variable is also a reference
- * @return bool
- */
- protected function backward(&$i, &$current)
- {
- if ($i === null) {
- $i = count($this->inputZipper->front) - 1;
- } else {
- $i--;
- }
- if ($i < 0) {
- return false;
- }
- $current = $this->inputZipper->front[$i];
- return true;
- }
- /**
- * Handler that is called when a text token is processed
- */
- public function handleText(&$token)
- {
- }
- /**
- * Handler that is called when a start or empty token is processed
- */
- public function handleElement(&$token)
- {
- }
- /**
- * Handler that is called when an end token is processed
- */
- public function handleEnd(&$token)
- {
- $this->notifyEnd($token);
- }
- /**
- * Notifier that is called when an end token is processed
- * @param HTMLPurifier_Token $token Current token variable.
- * @note This differs from handlers in that the token is read-only
- * @deprecated
- */
- public function notifyEnd($token)
- {
- }
- }
- /**
- * Represents a language and defines localizable string formatting and
- * other functions, as well as the localized messages for HTML Purifier.
- */
- class HTMLPurifier_Language
- {
- /**
- * ISO 639 language code of language. Prefers shortest possible version.
- * @type string
- */
- public $code = 'en';
- /**
- * Fallback language code.
- * @type bool|string
- */
- public $fallback = false;
- /**
- * Array of localizable messages.
- * @type array
- */
- public $messages = array();
- /**
- * Array of localizable error codes.
- * @type array
- */
- public $errorNames = array();
- /**
- * True if no message file was found for this language, so English
- * is being used instead. Check this if you'd like to notify the
- * user that they've used a non-supported language.
- * @type bool
- */
- public $error = false;
- /**
- * Has the language object been loaded yet?
- * @type bool
- * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
- */
- public $_loaded = false;
- /**
- * @type HTMLPurifier_Config
- */
- protected $config;
- /**
- * @type HTMLPurifier_Context
- */
- protected $context;
- /**
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- */
- public function __construct($config, $context)
- {
- $this->config = $config;
- $this->context = $context;
- }
- /**
- * Loads language object with necessary info from factory cache
- * @note This is a lazy loader
- */
- public function load()
- {
- if ($this->_loaded) {
- return;
- }
- $factory = HTMLPurifier_LanguageFactory::instance();
- $factory->loadLanguage($this->code);
- foreach ($factory->keys as $key) {
- $this->$key = $factory->cache[$this->code][$key];
- }
- $this->_loaded = true;
- }
- /**
- * Retrieves a localised message.
- * @param string $key string identifier of message
- * @return string localised message
- */
- public function getMessage($key)
- {
- if (!$this->_loaded) {
- $this->load();
- }
- if (!isset($this->messages[$key])) {
- return "[$key]";
- }
- return $this->messages[$key];
- }
- /**
- * Retrieves a localised error name.
- * @param int $int error number, corresponding to PHP's error reporting
- * @return string localised message
- */
- public function getErrorName($int)
- {
- if (!$this->_loaded) {
- $this->load();
- }
- if (!isset($this->errorNames[$int])) {
- return "[Error: $int]";
- }
- return $this->errorNames[$int];
- }
- /**
- * Converts an array list into a string readable representation
- * @param array $array
- * @return string
- */
- public function listify($array)
- {
- $sep = $this->getMessage('Item separator');
- $sep_last = $this->getMessage('Item separator last');
- $ret = '';
- for ($i = 0, $c = count($array); $i < $c; $i++) {
- if ($i == 0) {
- } elseif ($i + 1 < $c) {
- $ret .= $sep;
- } else {
- $ret .= $sep_last;
- }
- $ret .= $array[$i];
- }
- return $ret;
- }
- /**
- * Formats a localised message with passed parameters
- * @param string $key string identifier of message
- * @param array $args Parameters to substitute in
- * @return string localised message
- * @todo Implement conditionals? Right now, some messages make
- * reference to line numbers, but those aren't always available
- */
- public function formatMessage($key, $args = array())
- {
- if (!$this->_loaded) {
- $this->load();
- }
- if (!isset($this->messages[$key])) {
- return "[$key]";
- }
- $raw = $this->messages[$key];
- $subst = array();
- $generator = false;
- foreach ($args as $i => $value) {
- if (is_object($value)) {
- if ($value instanceof HTMLPurifier_Token) {
- // factor this out some time
- if (!$generator) {
- $generator = $this->context->get('Generator');
- }
- if (isset($value->name)) {
- $subst['$'.$i.'.Name'] = $value->name;
- }
- if (isset($value->data)) {
- $subst['$'.$i.'.Data'] = $value->data;
- }
- $subst['$'.$i.'.Compact'] =
- $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
- // a more complex algorithm for compact representation
- // could be introduced for all types of tokens. This
- // may need to be factored out into a dedicated class
- if (!empty($value->attr)) {
- $stripped_token = clone $value;
- $stripped_token->attr = array();
- $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
- }
- $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
- }
- continue;
- } elseif (is_array($value)) {
- $keys = array_keys($value);
- if (array_keys($keys) === $keys) {
- // list
- $subst['$'.$i] = $this->listify($value);
- } else {
- // associative array
- // no $i implementation yet, sorry
- $subst['$'.$i.'.Keys'] = $this->listify($keys);
- $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
- }
- continue;
- }
- $subst['$' . $i] = $value;
- }
- return strtr($raw, $subst);
- }
- }
- /**
- * Class responsible for generating HTMLPurifier_Language objects, managing
- * caching and fallbacks.
- * @note Thanks to MediaWiki for the general logic, although this version
- * has been entirely rewritten
- * @todo Serialized cache for languages
- */
- class HTMLPurifier_LanguageFactory
- {
- /**
- * Cache of language code information used to load HTMLPurifier_Language objects.
- * Structure is: $factory->cache[$language_code][$key] = $value
- * @type array
- */
- public $cache;
- /**
- * Valid keys in the HTMLPurifier_Language object. Designates which
- * variables to slurp out of a message file.
- * @type array
- */
- public $keys = array('fallback', 'messages', 'errorNames');
- /**
- * Instance to validate language codes.
- * @type HTMLPurifier_AttrDef_Lang
- *
- */
- protected $validator;
- /**
- * Cached copy of dirname(__FILE__), directory of current file without
- * trailing slash.
- * @type string
- */
- protected $dir;
- /**
- * Keys whose contents are a hash map and can be merged.
- * @type array
- */
- protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
- /**
- * Keys whose contents are a list and can be merged.
- * @value array lookup
- */
- protected $mergeable_keys_list = array();
- /**
- * Retrieve sole instance of the factory.
- * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
- * or bool true to reset to default factory.
- * @return HTMLPurifier_LanguageFactory
- */
- public static function instance($prototype = null)
- {
- static $instance = null;
- if ($prototype !== null) {
- $instance = $prototype;
- } elseif ($instance === null || $prototype == true) {
- $instance = new HTMLPurifier_LanguageFactory();
- $instance->setup();
- }
- return $instance;
- }
- /**
- * Sets up the singleton, much like a constructor
- * @note Prevents people from getting this outside of the singleton
- */
- public function setup()
- {
- $this->validator = new HTMLPurifier_AttrDef_Lang();
- $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
- }
- /**
- * Creates a language object, handles class fallbacks
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @param bool|string $code Code to override configuration with. Private parameter.
- * @return HTMLPurifier_Language
- */
- public function create($config, $context, $code = false)
- {
- // validate language code
- if ($code === false) {
- $code = $this->validator->validate(
- $config->get('Core.Language'),
- $config,
- $context
- );
- } else {
- $code = $this->validator->validate($code, $config, $context);
- }
- if ($code === false) {
- $code = 'en'; // malformed code becomes English
- }
- $pcode = str_replace('-', '_', $code); // make valid PHP classname
- static $depth = 0; // recursion protection
- if ($code == 'en') {
- $lang = new HTMLPurifier_Language($config, $context);
- } else {
- $class = 'HTMLPurifier_Language_' . $pcode;
- $file = $this->dir . '/Language/classes/' . $code . '.php';
- if (file_exists($file) || class_exists($class, false)) {
- $lang = new $class($config, $context);
- } else {
- // Go fallback
- $raw_fallback = $this->getFallbackFor($code);
- $fallback = $raw_fallback ? $raw_fallback : 'en';
- $depth++;
- $lang = $this->create($config, $context, $fallback);
- if (!$raw_fallback) {
- $lang->error = true;
- }
- $depth--;
- }
- }
- $lang->code = $code;
- return $lang;
- }
- /**
- * Returns the fallback language for language
- * @note Loads the original language into cache
- * @param string $code language code
- * @return string|bool
- */
- public function getFallbackFor($code)
- {
- $this->loadLanguage($code);
- return $this->cache[$code]['fallback'];
- }
- /**
- * Loads language into the cache, handles message file and fallbacks
- * @param string $code language code
- */
- public function loadLanguage($code)
- {
- static $languages_seen = array(); // recursion guard
- // abort if we've already loaded it
- if (isset($this->cache[$code])) {
- return;
- }
- // generate filename
- $filename = $this->dir . '/Language/messages/' . $code . '.php';
- // default fallback : may be overwritten by the ensuing include
- $fallback = ($code != 'en') ? 'en' : false;
- // load primary localisation
- if (!file_exists($filename)) {
- // skip the include: will rely solely on fallback
- $filename = $this->dir . '/Language/messages/en.php';
- $cache = array();
- } else {
- include $filename;
- $cache = compact($this->keys);
- }
- // load fallback localisation
- if (!empty($fallback)) {
- // infinite recursion guard
- if (isset($languages_seen[$code])) {
- trigger_error(
- 'Circular fallback reference in language ' .
- $code,
- E_USER_ERROR
- );
- $fallback = 'en';
- }
- $language_seen[$code] = true;
- // load the fallback recursively
- $this->loadLanguage($fallback);
- $fallback_cache = $this->cache[$fallback];
- // merge fallback with current language
- foreach ($this->keys as $key) {
- if (isset($cache[$key]) && isset($fallback_cache[$key])) {
- if (isset($this->mergeable_keys_map[$key])) {
- $cache[$key] = $cache[$key] + $fallback_cache[$key];
- } elseif (isset($this->mergeable_keys_list[$key])) {
- $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
- }
- } else {
- $cache[$key] = $fallback_cache[$key];
- }
- }
- }
- // save to cache for later retrieval
- $this->cache[$code] = $cache;
- return;
- }
- }
- /**
- * Represents a measurable length, with a string numeric magnitude
- * and a unit. This object is immutable.
- */
- class HTMLPurifier_Length
- {
- /**
- * String numeric magnitude.
- * @type string
- */
- protected $n;
- /**
- * String unit. False is permitted if $n = 0.
- * @type string|bool
- */
- protected $unit;
- /**
- * Whether or not this length is valid. Null if not calculated yet.
- * @type bool
- */
- protected $isValid;
- /**
- * Array Lookup array of units recognized by CSS 2.1
- * @type array
- */
- protected static $allowedUnits = array(
- 'em' => true, 'ex' => true, 'px' => true, 'in' => true,
- 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
- );
- /**
- * @param string $n Magnitude
- * @param bool|string $u Unit
- */
- public function __construct($n = '0', $u = false)
- {
- $this->n = (string) $n;
- $this->unit = $u !== false ? (string) $u : false;
- }
- /**
- * @param string $s Unit string, like '2em' or '3.4in'
- * @return HTMLPurifier_Length
- * @warning Does not perform validation.
- */
- public static function make($s)
- {
- if ($s instanceof HTMLPurifier_Length) {
- return $s;
- }
- $n_length = strspn($s, '1234567890.+-');
- $n = substr($s, 0, $n_length);
- $unit = substr($s, $n_length);
- if ($unit === '') {
- $unit = false;
- }
- return new HTMLPurifier_Length($n, $unit);
- }
- /**
- * Validates the number and unit.
- * @return bool
- */
- protected function validate()
- {
- // Special case:
- if ($this->n === '+0' || $this->n === '-0') {
- $this->n = '0';
- }
- if ($this->n === '0' && $this->unit === false) {
- return true;
- }
- if (!ctype_lower($this->unit)) {
- $this->unit = strtolower($this->unit);
- }
- if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
- return false;
- }
- // Hack:
- $def = new HTMLPurifier_AttrDef_CSS_Number();
- $result = $def->validate($this->n, false, false);
- if ($result === false) {
- return false;
- }
- $this->n = $result;
- return true;
- }
- /**
- * Returns string representation of number.
- * @return string
- */
- public function toString()
- {
- if (!$this->isValid()) {
- return false;
- }
- return $this->n . $this->unit;
- }
- /**
- * Retrieves string numeric magnitude.
- * @return string
- */
- public function getN()
- {
- return $this->n;
- }
- /**
- * Retrieves string unit.
- * @return string
- */
- public function getUnit()
- {
- return $this->unit;
- }
- /**
- * Returns true if this length unit is valid.
- * @return bool
- */
- public function isValid()
- {
- if ($this->isValid === null) {
- $this->isValid = $this->validate();
- }
- return $this->isValid;
- }
- /**
- * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
- * @param HTMLPurifier_Length $l
- * @return int
- * @warning If both values are too large or small, this calculation will
- * not work properly
- */
- public function compareTo($l)
- {
- if ($l === false) {
- return false;
- }
- if ($l->unit !== $this->unit) {
- $converter = new HTMLPurifier_UnitConverter();
- $l = $converter->convert($l, $this->unit);
- if ($l === false) {
- return false;
- }
- }
- return $this->n - $l->n;
- }
- }
- /**
- * Forgivingly lexes HTML (SGML-style) markup into tokens.
- *
- * A lexer parses a string of SGML-style markup and converts them into
- * corresponding tokens. It doesn't check for well-formedness, although its
- * internal mechanism may make this automatic (such as the case of
- * HTMLPurifier_Lexer_DOMLex). There are several implementations to choose
- * from.
- *
- * A lexer is HTML-oriented: it might work with XML, but it's not
- * recommended, as we adhere to a subset of the specification for optimization
- * reasons. This might change in the future. Also, most tokenizers are not
- * expected to handle DTDs or PIs.
- *
- * This class should not be directly instantiated, but you may use create() to
- * retrieve a default copy of the lexer. Being a supertype, this class
- * does not actually define any implementation, but offers commonly used
- * convenience functions for subclasses.
- *
- * @note The unit tests will instantiate this class for testing purposes, as
- * many of the utility functions require a class to be instantiated.
- * This means that, even though this class is not runnable, it will
- * not be declared abstract.
- *
- * @par
- *
- * @note
- * We use tokens rather than create a DOM representation because DOM would:
- *
- * @par
- * -# Require more processing and memory to create,
- * -# Is not streamable, and
- * -# Has the entire document structure (html and body not needed).
- *
- * @par
- * However, DOM is helpful in that it makes it easy to move around nodes
- * without a lot of lookaheads to see when a tag is closed. This is a
- * limitation of the token system and some workarounds would be nice.
- */
- class HTMLPurifier_Lexer
- {
- /**
- * Whether or not this lexer implements line-number/column-number tracking.
- * If it does, set to true.
- */
- public $tracksLineNumbers = false;
- // -- STATIC ----------------------------------------------------------
- /**
- * Retrieves or sets the default Lexer as a Prototype Factory.
- *
- * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
- * a few exceptions involving special features that only DirectLex
- * implements.
- *
- * @note The behavior of this class has changed, rather than accepting
- * a prototype object, it now accepts a configuration object.
- * To specify your own prototype, set %Core.LexerImpl to it.
- * This change in behavior de-singletonizes the lexer object.
- *
- * @param HTMLPurifier_Config $config
- * @return HTMLPurifier_Lexer
- * @throws HTMLPurifier_Exception
- */
- public static function create($config)
- {
- if (!($config instanceof HTMLPurifier_Config)) {
- $lexer = $config;
- trigger_error(
- "Passing a prototype to
- HTMLPurifier_Lexer::create() is deprecated, please instead
- use %Core.LexerImpl",
- E_USER_WARNING
- );
- } else {
- $lexer = $config->get('Core.LexerImpl');
- }
- $needs_tracking =
- $config->get('Core.MaintainLineNumbers') ||
- $config->get('Core.CollectErrors');
- $inst = null;
- if (is_object($lexer)) {
- $inst = $lexer;
- } else {
- if (is_null($lexer)) {
- do {
- // auto-detection algorithm
- if ($needs_tracking) {
- $lexer = 'DirectLex';
- break;
- }
- if (class_exists('DOMDocument') &&
- method_exists('DOMDocument', 'loadHTML') &&
- !extension_loaded('domxml')
- ) {
- // check for DOM support, because while it's part of the
- // core, it can be disabled compile time. Also, the PECL
- // domxml extension overrides the default DOM, and is evil
- // and nasty and we shan't bother to support it
- $lexer = 'DOMLex';
- } else {
- $lexer = 'DirectLex';
- }
- } while (0);
- } // do..while so we can break
- // instantiate recognized string names
- switch ($lexer) {
- case 'DOMLex':
- $inst = new HTMLPurifier_Lexer_DOMLex();
- break;
- case 'DirectLex':
- $inst = new HTMLPurifier_Lexer_DirectLex();
- break;
- case 'PH5P':
- $inst = new HTMLPurifier_Lexer_PH5P();
- break;
- default:
- throw new HTMLPurifier_Exception(
- "Cannot instantiate unrecognized Lexer type " .
- htmlspecialchars($lexer)
- );
- }
- }
- if (!$inst) {
- throw new HTMLPurifier_Exception('No lexer was instantiated');
- }
- // once PHP DOM implements native line numbers, or we
- // hack out something using XSLT, remove this stipulation
- if ($needs_tracking && !$inst->tracksLineNumbers) {
- throw new HTMLPurifier_Exception(
- 'Cannot use lexer that does not support line numbers with ' .
- 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)'
- );
- }
- return $inst;
- }
- // -- CONVENIENCE MEMBERS ---------------------------------------------
- public function __construct()
- {
- $this->_entity_parser = new HTMLPurifier_EntityParser();
- }
- /**
- * Most common entity to raw value conversion table for special entities.
- * @type array
- */
- protected $_special_entity2str =
- array(
- '"' => '"',
- '&' => '&',
- '<' => '<',
- '>' => '>',
- ''' => "'",
- ''' => "'",
- ''' => "'"
- );
- /**
- * Parses special entities into the proper characters.
- *
- * This string will translate escaped versions of the special characters
- * into the correct ones.
- *
- * @warning
- * You should be able to treat the output of this function as
- * completely parsed, but that's only because all other entities should
- * have been handled previously in substituteNonSpecialEntities()
- *
- * @param string $string String character data to be parsed.
- * @return string Parsed character data.
- */
- public function parseData($string)
- {
- // following functions require at least one character
- if ($string === '') {
- return '';
- }
- // subtracts amps that cannot possibly be escaped
- $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
- ($string[strlen($string) - 1] === '&' ? 1 : 0);
- if (!$num_amp) {
- return $string;
- } // abort if no entities
- $num_esc_amp = substr_count($string, '&');
- $string = strtr($string, $this->_special_entity2str);
- // code duplication for sake of optimization, see above
- $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
- ($string[strlen($string) - 1] === '&' ? 1 : 0);
- if ($num_amp_2 <= $num_esc_amp) {
- return $string;
- }
- // hmm... now we have some uncommon entities. Use the callback.
- $string = $this->_entity_parser->substituteSpecialEntities($string);
- return $string;
- }
- /**
- * Lexes an HTML string into tokens.
- * @param $string String HTML.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[] array representation of HTML.
- */
- public function tokenizeHTML($string, $config, $context)
- {
- trigger_error('Call to abstract class', E_USER_ERROR);
- }
- /**
- * Translates CDATA sections into regular sections (through escaping).
- * @param string $string HTML string to process.
- * @return string HTML with CDATA sections escaped.
- */
- protected static function escapeCDATA($string)
- {
- return preg_replace_callback(
- '/<!\[CDATA\[(.+?)\]\]>/s',
- array('HTMLPurifier_Lexer', 'CDATACallback'),
- $string
- );
- }
- /**
- * Special CDATA case that is especially convoluted for <script>
- * @param string $string HTML string to process.
- * @return string HTML with CDATA sections escaped.
- */
- protected static function escapeCommentedCDATA($string)
- {
- return preg_replace_callback(
- '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
- array('HTMLPurifier_Lexer', 'CDATACallback'),
- $string
- );
- }
- /**
- * Special Internet Explorer conditional comments should be removed.
- * @param string $string HTML string to process.
- * @return string HTML with conditional comments removed.
- */
- protected static function removeIEConditional($string)
- {
- return preg_replace(
- '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
- '',
- $string
- );
- }
- /**
- * Callback function for escapeCDATA() that does the work.
- *
- * @warning Though this is public in order to let the callback happen,
- * calling it directly is not recommended.
- * @param array $matches PCRE matches array, with index 0 the entire match
- * and 1 the inside of the CDATA section.
- * @return string Escaped internals of the CDATA section.
- */
- protected static function CDATACallback($matches)
- {
- // not exactly sure why the character set is needed, but whatever
- return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
- }
- /**
- * Takes a piece of HTML and normalizes it by converting entities, fixing
- * encoding, extracting bits, and other good stuff.
- * @param string $html HTML.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- * @todo Consider making protected
- */
- public function normalize($html, $config, $context)
- {
- // normalize newlines to \n
- if ($config->get('Core.NormalizeNewlines')) {
- $html = str_replace("\r\n", "\n", $html);
- $html = str_replace("\r", "\n", $html);
- }
- if ($config->get('HTML.Trusted')) {
- // escape convoluted CDATA
- $html = $this->escapeCommentedCDATA($html);
- }
- // escape CDATA
- $html = $this->escapeCDATA($html);
- $html = $this->removeIEConditional($html);
- // extract body from document if applicable
- if ($config->get('Core.ConvertDocumentToFragment')) {
- $e = false;
- if ($config->get('Core.CollectErrors')) {
- $e =& $context->get('ErrorCollector');
- }
- $new_html = $this->extractBody($html);
- if ($e && $new_html != $html) {
- $e->send(E_WARNING, 'Lexer: Extracted body');
- }
- $html = $new_html;
- }
- // expand entities that aren't the big five
- $html = $this->_entity_parser->substituteNonSpecialEntities($html);
- // clean into wellformed UTF-8 string for an SGML context: this has
- // to be done after entity expansion because the entities sometimes
- // represent non-SGML characters (horror, horror!)
- $html = HTMLPurifier_Encoder::cleanUTF8($html);
- // if processing instructions are to removed, remove them now
- if ($config->get('Core.RemoveProcessingInstructions')) {
- $html = preg_replace('#<\?.+?\?>#s', '', $html);
- }
- return $html;
- }
- /**
- * Takes a string of HTML (fragment or document) and returns the content
- * @todo Consider making protected
- */
- public function extractBody($html)
- {
- $matches = array();
- $result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches);
- if ($result) {
- return $matches[1];
- } else {
- return $html;
- }
- }
- }
- /**
- * Abstract base node class that all others inherit from.
- *
- * Why do we not use the DOM extension? (1) It is not always available,
- * (2) it has funny constraints on the data it can represent,
- * whereas we want a maximally flexible representation, and (3) its
- * interface is a bit cumbersome.
- */
- abstract class HTMLPurifier_Node
- {
- /**
- * Line number of the start token in the source document
- * @type int
- */
- public $line;
- /**
- * Column number of the start token in the source document. Null if unknown.
- * @type int
- */
- public $col;
- /**
- * Lookup array of processing that this token is exempt from.
- * Currently, valid values are "ValidateAttributes".
- * @type array
- */
- public $armor = array();
- /**
- * When true, this node should be ignored as non-existent.
- *
- * Who is responsible for ignoring dead nodes? FixNesting is
- * responsible for removing them before passing on to child
- * validators.
- */
- public $dead = false;
- /**
- * Returns a pair of start and end tokens, where the end token
- * is null if it is not necessary. Does not include children.
- * @type array
- */
- abstract public function toTokenPair();
- }
- /**
- * Class that handles operations involving percent-encoding in URIs.
- *
- * @warning
- * Be careful when reusing instances of PercentEncoder. The object
- * you use for normalize() SHOULD NOT be used for encode(), or
- * vice-versa.
- */
- class HTMLPurifier_PercentEncoder
- {
- /**
- * Reserved characters to preserve when using encode().
- * @type array
- */
- protected $preserve = array();
- /**
- * String of characters that should be preserved while using encode().
- * @param bool $preserve
- */
- public function __construct($preserve = false)
- {
- // unreserved letters, ought to const-ify
- for ($i = 48; $i <= 57; $i++) { // digits
- $this->preserve[$i] = true;
- }
- for ($i = 65; $i <= 90; $i++) { // upper-case
- $this->preserve[$i] = true;
- }
- for ($i = 97; $i <= 122; $i++) { // lower-case
- $this->preserve[$i] = true;
- }
- $this->preserve[45] = true; // Dash -
- $this->preserve[46] = true; // Period .
- $this->preserve[95] = true; // Underscore _
- $this->preserve[126]= true; // Tilde ~
- // extra letters not to escape
- if ($preserve !== false) {
- for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
- $this->preserve[ord($preserve[$i])] = true;
- }
- }
- }
- /**
- * Our replacement for urlencode, it encodes all non-reserved characters,
- * as well as any extra characters that were instructed to be preserved.
- * @note
- * Assumes that the string has already been normalized, making any
- * and all percent escape sequences valid. Percents will not be
- * re-escaped, regardless of their status in $preserve
- * @param string $string String to be encoded
- * @return string Encoded string.
- */
- public function encode($string)
- {
- $ret = '';
- for ($i = 0, $c = strlen($string); $i < $c; $i++) {
- if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) {
- $ret .= '%' . sprintf('%02X', $int);
- } else {
- $ret .= $string[$i];
- }
- }
- return $ret;
- }
- /**
- * Fix up percent-encoding by decoding unreserved characters and normalizing.
- * @warning This function is affected by $preserve, even though the
- * usual desired behavior is for this not to preserve those
- * characters. Be careful when reusing instances of PercentEncoder!
- * @param string $string String to normalize
- * @return string
- */
- public function normalize($string)
- {
- if ($string == '') {
- return '';
- }
- $parts = explode('%', $string);
- $ret = array_shift($parts);
- foreach ($parts as $part) {
- $length = strlen($part);
- if ($length < 2) {
- $ret .= '%25' . $part;
- continue;
- }
- $encoding = substr($part, 0, 2);
- $text = substr($part, 2);
- if (!ctype_xdigit($encoding)) {
- $ret .= '%25' . $part;
- continue;
- }
- $int = hexdec($encoding);
- if (isset($this->preserve[$int])) {
- $ret .= chr($int) . $text;
- continue;
- }
- $encoding = strtoupper($encoding);
- $ret .= '%' . $encoding . $text;
- }
- return $ret;
- }
- }
- /**
- * Generic property list implementation
- */
- class HTMLPurifier_PropertyList
- {
- /**
- * Internal data-structure for properties.
- * @type array
- */
- protected $data = array();
- /**
- * Parent plist.
- * @type HTMLPurifier_PropertyList
- */
- protected $parent;
- /**
- * Cache.
- * @type array
- */
- protected $cache;
- /**
- * @param HTMLPurifier_PropertyList $parent Parent plist
- */
- public function __construct($parent = null)
- {
- $this->parent = $parent;
- }
- /**
- * Recursively retrieves the value for a key
- * @param string $name
- * @throws HTMLPurifier_Exception
- */
- public function get($name)
- {
- if ($this->has($name)) {
- return $this->data[$name];
- }
- // possible performance bottleneck, convert to iterative if necessary
- if ($this->parent) {
- return $this->parent->get($name);
- }
- throw new HTMLPurifier_Exception("Key '$name' not found");
- }
- /**
- * Sets the value of a key, for this plist
- * @param string $name
- * @param mixed $value
- */
- public function set($name, $value)
- {
- $this->data[$name] = $value;
- }
- /**
- * Returns true if a given key exists
- * @param string $name
- * @return bool
- */
- public function has($name)
- {
- return array_key_exists($name, $this->data);
- }
- /**
- * Resets a value to the value of it's parent, usually the default. If
- * no value is specified, the entire plist is reset.
- * @param string $name
- */
- public function reset($name = null)
- {
- if ($name == null) {
- $this->data = array();
- } else {
- unset($this->data[$name]);
- }
- }
- /**
- * Squashes this property list and all of its property lists into a single
- * array, and returns the array. This value is cached by default.
- * @param bool $force If true, ignores the cache and regenerates the array.
- * @return array
- */
- public function squash($force = false)
- {
- if ($this->cache !== null && !$force) {
- return $this->cache;
- }
- if ($this->parent) {
- return $this->cache = array_merge($this->parent->squash($force), $this->data);
- } else {
- return $this->cache = $this->data;
- }
- }
- /**
- * Returns the parent plist.
- * @return HTMLPurifier_PropertyList
- */
- public function getParent()
- {
- return $this->parent;
- }
- /**
- * Sets the parent plist.
- * @param HTMLPurifier_PropertyList $plist Parent plist
- */
- public function setParent($plist)
- {
- $this->parent = $plist;
- }
- }
- /**
- * Property list iterator. Do not instantiate this class directly.
- */
- class HTMLPurifier_PropertyListIterator extends FilterIterator
- {
- /**
- * @type int
- */
- protected $l;
- /**
- * @type string
- */
- protected $filter;
- /**
- * @param Iterator $iterator Array of data to iterate over
- * @param string $filter Optional prefix to only allow values of
- */
- public function __construct(Iterator $iterator, $filter = null)
- {
- parent::__construct($iterator);
- $this->l = strlen($filter);
- $this->filter = $filter;
- }
- /**
- * @return bool
- */
- public function accept()
- {
- $key = $this->getInnerIterator()->key();
- if (strncmp($key, $this->filter, $this->l) !== 0) {
- return false;
- }
- return true;
- }
- }
- /**
- * A simple array-backed queue, based off of the classic Okasaki
- * persistent amortized queue. The basic idea is to maintain two
- * stacks: an input stack and an output stack. When the output
- * stack runs out, reverse the input stack and use it as the output
- * stack.
- *
- * We don't use the SPL implementation because it's only supported
- * on PHP 5.3 and later.
- *
- * Exercise: Prove that push/pop on this queue take amortized O(1) time.
- *
- * Exercise: Extend this queue to be a deque, while preserving amortized
- * O(1) time. Some care must be taken on rebalancing to avoid quadratic
- * behaviour caused by repeatedly shuffling data from the input stack
- * to the output stack and back.
- */
- class HTMLPurifier_Queue {
- private $input;
- private $output;
- public function __construct($input = array()) {
- $this->input = $input;
- $this->output = array();
- }
- /**
- * Shifts an element off the front of the queue.
- */
- public function shift() {
- if (empty($this->output)) {
- $this->output = array_reverse($this->input);
- $this->input = array();
- }
- if (empty($this->output)) {
- return NULL;
- }
- return array_pop($this->output);
- }
- /**
- * Pushes an element onto the front of the queue.
- */
- public function push($x) {
- array_push($this->input, $x);
- }
- /**
- * Checks if it's empty.
- */
- public function isEmpty() {
- return empty($this->input) && empty($this->output);
- }
- }
- /**
- * Supertype for classes that define a strategy for modifying/purifying tokens.
- *
- * While HTMLPurifier's core purpose is fixing HTML into something proper,
- * strategies provide plug points for extra configuration or even extra
- * features, such as custom tags, custom parsing of text, etc.
- */
- abstract class HTMLPurifier_Strategy
- {
- /**
- * Executes the strategy on the tokens.
- *
- * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token objects to be operated on.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[] Processed array of token objects.
- */
- abstract public function execute($tokens, $config, $context);
- }
- /**
- * This is in almost every respect equivalent to an array except
- * that it keeps track of which keys were accessed.
- *
- * @warning For the sake of backwards compatibility with early versions
- * of PHP 5, you must not use the $hash[$key] syntax; if you do
- * our version of offsetGet is never called.
- */
- class HTMLPurifier_StringHash extends ArrayObject
- {
- /**
- * @type array
- */
- protected $accessed = array();
- /**
- * Retrieves a value, and logs the access.
- * @param mixed $index
- * @return mixed
- */
- public function offsetGet($index)
- {
- $this->accessed[$index] = true;
- return parent::offsetGet($index);
- }
- /**
- * Returns a lookup array of all array indexes that have been accessed.
- * @return array in form array($index => true).
- */
- public function getAccessed()
- {
- return $this->accessed;
- }
- /**
- * Resets the access array.
- */
- public function resetAccessed()
- {
- $this->accessed = array();
- }
- }
- /**
- * Parses string hash files. File format is as such:
- *
- * DefaultKeyValue
- * KEY: Value
- * KEY2: Value2
- * --MULTILINE-KEY--
- * Multiline
- * value.
- *
- * Which would output something similar to:
- *
- * array(
- * 'ID' => 'DefaultKeyValue',
- * 'KEY' => 'Value',
- * 'KEY2' => 'Value2',
- * 'MULTILINE-KEY' => "Multiline\nvalue.\n",
- * )
- *
- * We use this as an easy to use file-format for configuration schema
- * files, but the class itself is usage agnostic.
- *
- * You can use ---- to forcibly terminate parsing of a single string-hash;
- * this marker is used in multi string-hashes to delimit boundaries.
- */
- class HTMLPurifier_StringHashParser
- {
- /**
- * @type string
- */
- public $default = 'ID';
- /**
- * Parses a file that contains a single string-hash.
- * @param string $file
- * @return array
- */
- public function parseFile($file)
- {
- if (!file_exists($file)) {
- return false;
- }
- $fh = fopen($file, 'r');
- if (!$fh) {
- return false;
- }
- $ret = $this->parseHandle($fh);
- fclose($fh);
- return $ret;
- }
- /**
- * Parses a file that contains multiple string-hashes delimited by '----'
- * @param string $file
- * @return array
- */
- public function parseMultiFile($file)
- {
- if (!file_exists($file)) {
- return false;
- }
- $ret = array();
- $fh = fopen($file, 'r');
- if (!$fh) {
- return false;
- }
- while (!feof($fh)) {
- $ret[] = $this->parseHandle($fh);
- }
- fclose($fh);
- return $ret;
- }
- /**
- * Internal parser that acepts a file handle.
- * @note While it's possible to simulate in-memory parsing by using
- * custom stream wrappers, if such a use-case arises we should
- * factor out the file handle into its own class.
- * @param resource $fh File handle with pointer at start of valid string-hash
- * block.
- * @return array
- */
- protected function parseHandle($fh)
- {
- $state = false;
- $single = false;
- $ret = array();
- do {
- $line = fgets($fh);
- if ($line === false) {
- break;
- }
- $line = rtrim($line, "\n\r");
- if (!$state && $line === '') {
- continue;
- }
- if ($line === '----') {
- break;
- }
- if (strncmp('--#', $line, 3) === 0) {
- // Comment
- continue;
- } elseif (strncmp('--', $line, 2) === 0) {
- // Multiline declaration
- $state = trim($line, '- ');
- if (!isset($ret[$state])) {
- $ret[$state] = '';
- }
- continue;
- } elseif (!$state) {
- $single = true;
- if (strpos($line, ':') !== false) {
- // Single-line declaration
- list($state, $line) = explode(':', $line, 2);
- $line = trim($line);
- } else {
- // Use default declaration
- $state = $this->default;
- }
- }
- if ($single) {
- $ret[$state] = $line;
- $single = false;
- $state = false;
- } else {
- $ret[$state] .= "$line\n";
- }
- } while (!feof($fh));
- return $ret;
- }
- }
- /**
- * Defines a mutation of an obsolete tag into a valid tag.
- */
- abstract class HTMLPurifier_TagTransform
- {
- /**
- * Tag name to transform the tag to.
- * @type string
- */
- public $transform_to;
- /**
- * Transforms the obsolete tag into the valid tag.
- * @param HTMLPurifier_Token_Tag $tag Tag to be transformed.
- * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object
- * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
- */
- abstract public function transform($tag, $config, $context);
- /**
- * Prepends CSS properties to the style attribute, creating the
- * attribute if it doesn't exist.
- * @warning Copied over from AttrTransform, be sure to keep in sync
- * @param array $attr Attribute array to process (passed by reference)
- * @param string $css CSS to prepend
- */
- protected function prependCSS(&$attr, $css)
- {
- $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
- $attr['style'] = $css . $attr['style'];
- }
- }
- /**
- * Abstract base token class that all others inherit from.
- */
- abstract class HTMLPurifier_Token
- {
- /**
- * Line number node was on in source document. Null if unknown.
- * @type int
- */
- public $line;
- /**
- * Column of line node was on in source document. Null if unknown.
- * @type int
- */
- public $col;
- /**
- * Lookup array of processing that this token is exempt from.
- * Currently, valid values are "ValidateAttributes" and
- * "MakeWellFormed_TagClosedError"
- * @type array
- */
- public $armor = array();
- /**
- * Used during MakeWellFormed.
- * @type
- */
- public $skip;
- /**
- * @type
- */
- public $rewind;
- /**
- * @type
- */
- public $carryover;
- /**
- * @param string $n
- * @return null|string
- */
- public function __get($n)
- {
- if ($n === 'type') {
- trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
- switch (get_class($this)) {
- case 'HTMLPurifier_Token_Start':
- return 'start';
- case 'HTMLPurifier_Token_Empty':
- return 'empty';
- case 'HTMLPurifier_Token_End':
- return 'end';
- case 'HTMLPurifier_Token_Text':
- return 'text';
- case 'HTMLPurifier_Token_Comment':
- return 'comment';
- default:
- return null;
- }
- }
- }
- /**
- * Sets the position of the token in the source document.
- * @param int $l
- * @param int $c
- */
- public function position($l = null, $c = null)
- {
- $this->line = $l;
- $this->col = $c;
- }
- /**
- * Convenience function for DirectLex settings line/col position.
- * @param int $l
- * @param int $c
- */
- public function rawPosition($l, $c)
- {
- if ($c === -1) {
- $l++;
- }
- $this->line = $l;
- $this->col = $c;
- }
- /**
- * Converts a token into its corresponding node.
- */
- abstract public function toNode();
- }
- /**
- * Factory for token generation.
- *
- * @note Doing some benchmarking indicates that the new operator is much
- * slower than the clone operator (even discounting the cost of the
- * constructor). This class is for that optimization.
- * Other then that, there's not much point as we don't
- * maintain parallel HTMLPurifier_Token hierarchies (the main reason why
- * you'd want to use an abstract factory).
- * @todo Port DirectLex to use this
- */
- class HTMLPurifier_TokenFactory
- {
- // p stands for prototype
- /**
- * @type HTMLPurifier_Token_Start
- */
- private $p_start;
- /**
- * @type HTMLPurifier_Token_End
- */
- private $p_end;
- /**
- * @type HTMLPurifier_Token_Empty
- */
- private $p_empty;
- /**
- * @type HTMLPurifier_Token_Text
- */
- private $p_text;
- /**
- * @type HTMLPurifier_Token_Comment
- */
- private $p_comment;
- /**
- * Generates blank prototypes for cloning.
- */
- public function __construct()
- {
- $this->p_start = new HTMLPurifier_Token_Start('', array());
- $this->p_end = new HTMLPurifier_Token_End('');
- $this->p_empty = new HTMLPurifier_Token_Empty('', array());
- $this->p_text = new HTMLPurifier_Token_Text('');
- $this->p_comment = new HTMLPurifier_Token_Comment('');
- }
- /**
- * Creates a HTMLPurifier_Token_Start.
- * @param string $name Tag name
- * @param array $attr Associative array of attributes
- * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
- */
- public function createStart($name, $attr = array())
- {
- $p = clone $this->p_start;
- $p->__construct($name, $attr);
- return $p;
- }
- /**
- * Creates a HTMLPurifier_Token_End.
- * @param string $name Tag name
- * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
- */
- public function createEnd($name)
- {
- $p = clone $this->p_end;
- $p->__construct($name);
- return $p;
- }
- /**
- * Creates a HTMLPurifier_Token_Empty.
- * @param string $name Tag name
- * @param array $attr Associative array of attributes
- * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
- */
- public function createEmpty($name, $attr = array())
- {
- $p = clone $this->p_empty;
- $p->__construct($name, $attr);
- return $p;
- }
- /**
- * Creates a HTMLPurifier_Token_Text.
- * @param string $data Data of text token
- * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
- */
- public function createText($data)
- {
- $p = clone $this->p_text;
- $p->__construct($data);
- return $p;
- }
- /**
- * Creates a HTMLPurifier_Token_Comment.
- * @param string $data Data of comment token
- * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
- */
- public function createComment($data)
- {
- $p = clone $this->p_comment;
- $p->__construct($data);
- return $p;
- }
- }
- /**
- * HTML Purifier's internal representation of a URI.
- * @note
- * Internal data-structures are completely escaped. If the data needs
- * to be used in a non-URI context (which is very unlikely), be sure
- * to decode it first. The URI may not necessarily be well-formed until
- * validate() is called.
- */
- class HTMLPurifier_URI
- {
- /**
- * @type string
- */
- public $scheme;
- /**
- * @type string
- */
- public $userinfo;
- /**
- * @type string
- */
- public $host;
- /**
- * @type int
- */
- public $port;
- /**
- * @type string
- */
- public $path;
- /**
- * @type string
- */
- public $query;
- /**
- * @type string
- */
- public $fragment;
- /**
- * @param string $scheme
- * @param string $userinfo
- * @param string $host
- * @param int $port
- * @param string $path
- * @param string $query
- * @param string $fragment
- * @note Automatically normalizes scheme and port
- */
- public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
- {
- $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
- $this->userinfo = $userinfo;
- $this->host = $host;
- $this->port = is_null($port) ? $port : (int)$port;
- $this->path = $path;
- $this->query = $query;
- $this->fragment = $fragment;
- }
- /**
- * Retrieves a scheme object corresponding to the URI's scheme/default
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
- */
- public function getSchemeObj($config, $context)
- {
- $registry = HTMLPurifier_URISchemeRegistry::instance();
- if ($this->scheme !== null) {
- $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
- if (!$scheme_obj) {
- return false;
- } // invalid scheme, clean it out
- } else {
- // no scheme: retrieve the default one
- $def = $config->getDefinition('URI');
- $scheme_obj = $def->getDefaultScheme($config, $context);
- if (!$scheme_obj) {
- // something funky happened to the default scheme object
- trigger_error(
- 'Default scheme object "' . $def->defaultScheme . '" was not readable',
- E_USER_WARNING
- );
- return false;
- }
- }
- return $scheme_obj;
- }
- /**
- * Generic validation method applicable for all schemes. May modify
- * this URI in order to get it into a compliant form.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool True if validation/filtering succeeds, false if failure
- */
- public function validate($config, $context)
- {
- // ABNF definitions from RFC 3986
- $chars_sub_delims = '!$&\'()*+,;=';
- $chars_gen_delims = ':/?#[]@';
- $chars_pchar = $chars_sub_delims . ':@';
- // validate host
- if (!is_null($this->host)) {
- $host_def = new HTMLPurifier_AttrDef_URI_Host();
- $this->host = $host_def->validate($this->host, $config, $context);
- if ($this->host === false) {
- $this->host = null;
- }
- }
- // validate scheme
- // NOTE: It's not appropriate to check whether or not this
- // scheme is in our registry, since a URIFilter may convert a
- // URI that we don't allow into one we do. So instead, we just
- // check if the scheme can be dropped because there is no host
- // and it is our default scheme.
- if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
- // support for relative paths is pretty abysmal when the
- // scheme is present, so axe it when possible
- $def = $config->getDefinition('URI');
- if ($def->defaultScheme === $this->scheme) {
- $this->scheme = null;
- }
- }
- // validate username
- if (!is_null($this->userinfo)) {
- $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
- $this->userinfo = $encoder->encode($this->userinfo);
- }
- // validate port
- if (!is_null($this->port)) {
- if ($this->port < 1 || $this->port > 65535) {
- $this->port = null;
- }
- }
- // validate path
- $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
- if (!is_null($this->host)) { // this catches $this->host === ''
- // path-abempty (hier and relative)
- // http://www.example.com/my/path
- // //www.example.com/my/path (looks odd, but works, and
- // recognized by most browsers)
- // (this set is valid or invalid on a scheme by scheme
- // basis, so we'll deal with it later)
- // file:///my/path
- // ///my/path
- $this->path = $segments_encoder->encode($this->path);
- } elseif ($this->path !== '') {
- if ($this->path[0] === '/') {
- // path-absolute (hier and relative)
- // http:/my/path
- // /my/path
- if (strlen($this->path) >= 2 && $this->path[1] === '/') {
- // This could happen if both the host gets stripped
- // out
- // http://my/path
- // //my/path
- $this->path = '';
- } else {
- $this->path = $segments_encoder->encode($this->path);
- }
- } elseif (!is_null($this->scheme)) {
- // path-rootless (hier)
- // http:my/path
- // Short circuit evaluation means we don't need to check nz
- $this->path = $segments_encoder->encode($this->path);
- } else {
- // path-noscheme (relative)
- // my/path
- // (once again, not checking nz)
- $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
- $c = strpos($this->path, '/');
- if ($c !== false) {
- $this->path =
- $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
- $segments_encoder->encode(substr($this->path, $c));
- } else {
- $this->path = $segment_nc_encoder->encode($this->path);
- }
- }
- } else {
- // path-empty (hier and relative)
- $this->path = ''; // just to be safe
- }
- // qf = query and fragment
- $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
- if (!is_null($this->query)) {
- $this->query = $qf_encoder->encode($this->query);
- }
- if (!is_null($this->fragment)) {
- $this->fragment = $qf_encoder->encode($this->fragment);
- }
- return true;
- }
- /**
- * Convert URI back to string
- * @return string URI appropriate for output
- */
- public function toString()
- {
- // reconstruct authority
- $authority = null;
- // there is a rendering difference between a null authority
- // (http:foo-bar) and an empty string authority
- // (http:///foo-bar).
- if (!is_null($this->host)) {
- $authority = '';
- if (!is_null($this->userinfo)) {
- $authority .= $this->userinfo . '@';
- }
- $authority .= $this->host;
- if (!is_null($this->port)) {
- $authority .= ':' . $this->port;
- }
- }
- // Reconstruct the result
- // One might wonder about parsing quirks from browsers after
- // this reconstruction. Unfortunately, parsing behavior depends
- // on what *scheme* was employed (file:///foo is handled *very*
- // differently than http:///foo), so unfortunately we have to
- // defer to the schemes to do the right thing.
- $result = '';
- if (!is_null($this->scheme)) {
- $result .= $this->scheme . ':';
- }
- if (!is_null($authority)) {
- $result .= '//' . $authority;
- }
- $result .= $this->path;
- if (!is_null($this->query)) {
- $result .= '?' . $this->query;
- }
- if (!is_null($this->fragment)) {
- $result .= '#' . $this->fragment;
- }
- return $result;
- }
- /**
- * Returns true if this URL might be considered a 'local' URL given
- * the current context. This is true when the host is null, or
- * when it matches the host supplied to the configuration.
- *
- * Note that this does not do any scheme checking, so it is mostly
- * only appropriate for metadata that doesn't care about protocol
- * security. isBenign is probably what you actually want.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function isLocal($config, $context)
- {
- if ($this->host === null) {
- return true;
- }
- $uri_def = $config->getDefinition('URI');
- if ($uri_def->host === $this->host) {
- return true;
- }
- return false;
- }
- /**
- * Returns true if this URL should be considered a 'benign' URL,
- * that is:
- *
- * - It is a local URL (isLocal), and
- * - It has a equal or better level of security
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function isBenign($config, $context)
- {
- if (!$this->isLocal($config, $context)) {
- return false;
- }
- $scheme_obj = $this->getSchemeObj($config, $context);
- if (!$scheme_obj) {
- return false;
- } // conservative approach
- $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
- if ($current_scheme_obj->secure) {
- if (!$scheme_obj->secure) {
- return false;
- }
- }
- return true;
- }
- }
- class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
- {
- public $type = 'URI';
- protected $filters = array();
- protected $postFilters = array();
- protected $registeredFilters = array();
- /**
- * HTMLPurifier_URI object of the base specified at %URI.Base
- */
- public $base;
- /**
- * String host to consider "home" base, derived off of $base
- */
- public $host;
- /**
- * Name of default scheme based on %URI.DefaultScheme and %URI.Base
- */
- public $defaultScheme;
- public function __construct()
- {
- $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
- $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
- $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
- $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
- $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
- $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
- $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
- }
- public function registerFilter($filter)
- {
- $this->registeredFilters[$filter->name] = $filter;
- }
- public function addFilter($filter, $config)
- {
- $r = $filter->prepare($config);
- if ($r === false) return; // null is ok, for backwards compat
- if ($filter->post) {
- $this->postFilters[$filter->name] = $filter;
- } else {
- $this->filters[$filter->name] = $filter;
- }
- }
- protected function doSetup($config)
- {
- $this->setupMemberVariables($config);
- $this->setupFilters($config);
- }
- protected function setupFilters($config)
- {
- foreach ($this->registeredFilters as $name => $filter) {
- if ($filter->always_load) {
- $this->addFilter($filter, $config);
- } else {
- $conf = $config->get('URI.' . $name);
- if ($conf !== false && $conf !== null) {
- $this->addFilter($filter, $config);
- }
- }
- }
- unset($this->registeredFilters);
- }
- protected function setupMemberVariables($config)
- {
- $this->host = $config->get('URI.Host');
- $base_uri = $config->get('URI.Base');
- if (!is_null($base_uri)) {
- $parser = new HTMLPurifier_URIParser();
- $this->base = $parser->parse($base_uri);
- $this->defaultScheme = $this->base->scheme;
- if (is_null($this->host)) $this->host = $this->base->host;
- }
- if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
- }
- public function getDefaultScheme($config, $context)
- {
- return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
- }
- public function filter(&$uri, $config, $context)
- {
- foreach ($this->filters as $name => $f) {
- $result = $f->filter($uri, $config, $context);
- if (!$result) return false;
- }
- return true;
- }
- public function postFilter(&$uri, $config, $context)
- {
- foreach ($this->postFilters as $name => $f) {
- $result = $f->filter($uri, $config, $context);
- if (!$result) return false;
- }
- return true;
- }
- }
- /**
- * Chainable filters for custom URI processing.
- *
- * These filters can perform custom actions on a URI filter object,
- * including transformation or blacklisting. A filter named Foo
- * must have a corresponding configuration directive %URI.Foo,
- * unless always_load is specified to be true.
- *
- * The following contexts may be available while URIFilters are being
- * processed:
- *
- * - EmbeddedURI: true if URI is an embedded resource that will
- * be loaded automatically on page load
- * - CurrentToken: a reference to the token that is currently
- * being processed
- * - CurrentAttr: the name of the attribute that is currently being
- * processed
- * - CurrentCSSProperty: the name of the CSS property that is
- * currently being processed (if applicable)
- *
- * @warning This filter is called before scheme object validation occurs.
- * Make sure, if you require a specific scheme object, you
- * you check that it exists. This allows filters to convert
- * proprietary URI schemes into regular ones.
- */
- abstract class HTMLPurifier_URIFilter
- {
- /**
- * Unique identifier of filter.
- * @type string
- */
- public $name;
- /**
- * True if this filter should be run after scheme validation.
- * @type bool
- */
- public $post = false;
- /**
- * True if this filter should always be loaded.
- * This permits a filter to be named Foo without the corresponding
- * %URI.Foo directive existing.
- * @type bool
- */
- public $always_load = false;
- /**
- * Performs initialization for the filter. If the filter returns
- * false, this means that it shouldn't be considered active.
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function prepare($config)
- {
- return true;
- }
- /**
- * Filter a URI object
- * @param HTMLPurifier_URI $uri Reference to URI object variable
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool Whether or not to continue processing: false indicates
- * URL is no good, true indicates continue processing. Note that
- * all changes are committed directly on the URI object
- */
- abstract public function filter(&$uri, $config, $context);
- }
- /**
- * Parses a URI into the components and fragment identifier as specified
- * by RFC 3986.
- */
- class HTMLPurifier_URIParser
- {
- /**
- * Instance of HTMLPurifier_PercentEncoder to do normalization with.
- */
- protected $percentEncoder;
- public function __construct()
- {
- $this->percentEncoder = new HTMLPurifier_PercentEncoder();
- }
- /**
- * Parses a URI.
- * @param $uri string URI to parse
- * @return HTMLPurifier_URI representation of URI. This representation has
- * not been validated yet and may not conform to RFC.
- */
- public function parse($uri)
- {
- $uri = $this->percentEncoder->normalize($uri);
- // Regexp is as per Appendix B.
- // Note that ["<>] are an addition to the RFC's recommended
- // characters, because they represent external delimeters.
- $r_URI = '!'.
- '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
- '(//([^/?#"<>]*))?'. // 4. Authority
- '([^?#"<>]*)'. // 5. Path
- '(\?([^#"<>]*))?'. // 7. Query
- '(#([^"<>]*))?'. // 8. Fragment
- '!';
- $matches = array();
- $result = preg_match($r_URI, $uri, $matches);
- if (!$result) return false; // *really* invalid URI
- // seperate out parts
- $scheme = !empty($matches[1]) ? $matches[2] : null;
- $authority = !empty($matches[3]) ? $matches[4] : null;
- $path = $matches[5]; // always present, can be empty
- $query = !empty($matches[6]) ? $matches[7] : null;
- $fragment = !empty($matches[8]) ? $matches[9] : null;
- // further parse authority
- if ($authority !== null) {
- $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
- $matches = array();
- preg_match($r_authority, $authority, $matches);
- $userinfo = !empty($matches[1]) ? $matches[2] : null;
- $host = !empty($matches[3]) ? $matches[3] : '';
- $port = !empty($matches[4]) ? (int) $matches[5] : null;
- } else {
- $port = $host = $userinfo = null;
- }
- return new HTMLPurifier_URI(
- $scheme, $userinfo, $host, $port, $path, $query, $fragment);
- }
- }
- /**
- * Validator for the components of a URI for a specific scheme
- */
- abstract class HTMLPurifier_URIScheme
- {
- /**
- * Scheme's default port (integer). If an explicit port number is
- * specified that coincides with the default port, it will be
- * elided.
- * @type int
- */
- public $default_port = null;
- /**
- * Whether or not URIs of this scheme are locatable by a browser
- * http and ftp are accessible, while mailto and news are not.
- * @type bool
- */
- public $browsable = false;
- /**
- * Whether or not data transmitted over this scheme is encrypted.
- * https is secure, http is not.
- * @type bool
- */
- public $secure = false;
- /**
- * Whether or not the URI always uses <hier_part>, resolves edge cases
- * with making relative URIs absolute
- * @type bool
- */
- public $hierarchical = false;
- /**
- * Whether or not the URI may omit a hostname when the scheme is
- * explicitly specified, ala file:///path/to/file. As of writing,
- * 'file' is the only scheme that browsers support his properly.
- * @type bool
- */
- public $may_omit_host = false;
- /**
- * Validates the components of a URI for a specific scheme.
- * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool success or failure
- */
- abstract public function doValidate(&$uri, $config, $context);
- /**
- * Public interface for validating components of a URI. Performs a
- * bunch of default actions. Don't overload this method.
- * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool success or failure
- */
- public function validate(&$uri, $config, $context)
- {
- if ($this->default_port == $uri->port) {
- $uri->port = null;
- }
- // kludge: browsers do funny things when the scheme but not the
- // authority is set
- if (!$this->may_omit_host &&
- // if the scheme is present, a missing host is always in error
- (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
- // if the scheme is not present, a *blank* host is in error,
- // since this translates into '///path' which most browsers
- // interpret as being 'http://path'.
- (is_null($uri->scheme) && $uri->host === '')
- ) {
- do {
- if (is_null($uri->scheme)) {
- if (substr($uri->path, 0, 2) != '//') {
- $uri->host = null;
- break;
- }
- // URI is '////path', so we cannot nullify the
- // host to preserve semantics. Try expanding the
- // hostname instead (fall through)
- }
- // first see if we can manually insert a hostname
- $host = $config->get('URI.Host');
- if (!is_null($host)) {
- $uri->host = $host;
- } else {
- // we can't do anything sensible, reject the URL.
- return false;
- }
- } while (false);
- }
- return $this->doValidate($uri, $config, $context);
- }
- }
- /**
- * Registry for retrieving specific URI scheme validator objects.
- */
- class HTMLPurifier_URISchemeRegistry
- {
- /**
- * Retrieve sole instance of the registry.
- * @param HTMLPurifier_URISchemeRegistry $prototype Optional prototype to overload sole instance with,
- * or bool true to reset to default registry.
- * @return HTMLPurifier_URISchemeRegistry
- * @note Pass a registry object $prototype with a compatible interface and
- * the function will copy it and return it all further times.
- */
- public static function instance($prototype = null)
- {
- static $instance = null;
- if ($prototype !== null) {
- $instance = $prototype;
- } elseif ($instance === null || $prototype == true) {
- $instance = new HTMLPurifier_URISchemeRegistry();
- }
- return $instance;
- }
- /**
- * Cache of retrieved schemes.
- * @type HTMLPurifier_URIScheme[]
- */
- protected $schemes = array();
- /**
- * Retrieves a scheme validator object
- * @param string $scheme String scheme name like http or mailto
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_URIScheme
- */
- public function getScheme($scheme, $config, $context)
- {
- if (!$config) {
- $config = HTMLPurifier_Config::createDefault();
- }
- // important, otherwise attacker could include arbitrary file
- $allowed_schemes = $config->get('URI.AllowedSchemes');
- if (!$config->get('URI.OverrideAllowedSchemes') &&
- !isset($allowed_schemes[$scheme])
- ) {
- return;
- }
- if (isset($this->schemes[$scheme])) {
- return $this->schemes[$scheme];
- }
- if (!isset($allowed_schemes[$scheme])) {
- return;
- }
- $class = 'HTMLPurifier_URIScheme_' . $scheme;
- if (!class_exists($class)) {
- return;
- }
- $this->schemes[$scheme] = new $class();
- return $this->schemes[$scheme];
- }
- /**
- * Registers a custom scheme to the cache, bypassing reflection.
- * @param string $scheme Scheme name
- * @param HTMLPurifier_URIScheme $scheme_obj
- */
- public function register($scheme, $scheme_obj)
- {
- $this->schemes[$scheme] = $scheme_obj;
- }
- }
- /**
- * Class for converting between different unit-lengths as specified by
- * CSS.
- */
- class HTMLPurifier_UnitConverter
- {
- const ENGLISH = 1;
- const METRIC = 2;
- const DIGITAL = 3;
- /**
- * Units information array. Units are grouped into measuring systems
- * (English, Metric), and are assigned an integer representing
- * the conversion factor between that unit and the smallest unit in
- * the system. Numeric indexes are actually magical constants that
- * encode conversion data from one system to the next, with a O(n^2)
- * constraint on memory (this is generally not a problem, since
- * the number of measuring systems is small.)
- */
- protected static $units = array(
- self::ENGLISH => array(
- 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
- 'pt' => 4,
- 'pc' => 48,
- 'in' => 288,
- self::METRIC => array('pt', '0.352777778', 'mm'),
- ),
- self::METRIC => array(
- 'mm' => 1,
- 'cm' => 10,
- self::ENGLISH => array('mm', '2.83464567', 'pt'),
- ),
- );
- /**
- * Minimum bcmath precision for output.
- * @type int
- */
- protected $outputPrecision;
- /**
- * Bcmath precision for internal calculations.
- * @type int
- */
- protected $internalPrecision;
- /**
- * Whether or not BCMath is available.
- * @type bool
- */
- private $bcmath;
- public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
- {
- $this->outputPrecision = $output_precision;
- $this->internalPrecision = $internal_precision;
- $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
- }
- /**
- * Converts a length object of one unit into another unit.
- * @param HTMLPurifier_Length $length
- * Instance of HTMLPurifier_Length to convert. You must validate()
- * it before passing it here!
- * @param string $to_unit
- * Unit to convert to.
- * @return HTMLPurifier_Length|bool
- * @note
- * About precision: This conversion function pays very special
- * attention to the incoming precision of values and attempts
- * to maintain a number of significant figure. Results are
- * fairly accurate up to nine digits. Some caveats:
- * - If a number is zero-padded as a result of this significant
- * figure tracking, the zeroes will be eliminated.
- * - If a number contains less than four sigfigs ($outputPrecision)
- * and this causes some decimals to be excluded, those
- * decimals will be added on.
- */
- public function convert($length, $to_unit)
- {
- if (!$length->isValid()) {
- return false;
- }
- $n = $length->getN();
- $unit = $length->getUnit();
- if ($n === '0' || $unit === false) {
- return new HTMLPurifier_Length('0', false);
- }
- $state = $dest_state = false;
- foreach (self::$units as $k => $x) {
- if (isset($x[$unit])) {
- $state = $k;
- }
- if (isset($x[$to_unit])) {
- $dest_state = $k;
- }
- }
- if (!$state || !$dest_state) {
- return false;
- }
- // Some calculations about the initial precision of the number;
- // this will be useful when we need to do final rounding.
- $sigfigs = $this->getSigFigs($n);
- if ($sigfigs < $this->outputPrecision) {
- $sigfigs = $this->outputPrecision;
- }
- // BCMath's internal precision deals only with decimals. Use
- // our default if the initial number has no decimals, or increase
- // it by how ever many decimals, thus, the number of guard digits
- // will always be greater than or equal to internalPrecision.
- $log = (int)floor(log(abs($n), 10));
- $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
- for ($i = 0; $i < 2; $i++) {
- // Determine what unit IN THIS SYSTEM we need to convert to
- if ($dest_state === $state) {
- // Simple conversion
- $dest_unit = $to_unit;
- } else {
- // Convert to the smallest unit, pending a system shift
- $dest_unit = self::$units[$state][$dest_state][0];
- }
- // Do the conversion if necessary
- if ($dest_unit !== $unit) {
- $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
- $n = $this->mul($n, $factor, $cp);
- $unit = $dest_unit;
- }
- // Output was zero, so bail out early. Shouldn't ever happen.
- if ($n === '') {
- $n = '0';
- $unit = $to_unit;
- break;
- }
- // It was a simple conversion, so bail out
- if ($dest_state === $state) {
- break;
- }
- if ($i !== 0) {
- // Conversion failed! Apparently, the system we forwarded
- // to didn't have this unit. This should never happen!
- return false;
- }
- // Pre-condition: $i == 0
- // Perform conversion to next system of units
- $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
- $unit = self::$units[$state][$dest_state][2];
- $state = $dest_state;
- // One more loop around to convert the unit in the new system.
- }
- // Post-condition: $unit == $to_unit
- if ($unit !== $to_unit) {
- return false;
- }
- // Useful for debugging:
- //echo "<pre>n";
- //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
- $n = $this->round($n, $sigfigs);
- if (strpos($n, '.') !== false) {
- $n = rtrim($n, '0');
- }
- $n = rtrim($n, '.');
- return new HTMLPurifier_Length($n, $unit);
- }
- /**
- * Returns the number of significant figures in a string number.
- * @param string $n Decimal number
- * @return int number of sigfigs
- */
- public function getSigFigs($n)
- {
- $n = ltrim($n, '0+-');
- $dp = strpos($n, '.'); // decimal position
- if ($dp === false) {
- $sigfigs = strlen(rtrim($n, '0'));
- } else {
- $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
- if ($dp !== 0) {
- $sigfigs--;
- }
- }
- return $sigfigs;
- }
- /**
- * Adds two numbers, using arbitrary precision when available.
- * @param string $s1
- * @param string $s2
- * @param int $scale
- * @return string
- */
- private function add($s1, $s2, $scale)
- {
- if ($this->bcmath) {
- return bcadd($s1, $s2, $scale);
- } else {
- return $this->scale((float)$s1 + (float)$s2, $scale);
- }
- }
- /**
- * Multiples two numbers, using arbitrary precision when available.
- * @param string $s1
- * @param string $s2
- * @param int $scale
- * @return string
- */
- private function mul($s1, $s2, $scale)
- {
- if ($this->bcmath) {
- return bcmul($s1, $s2, $scale);
- } else {
- return $this->scale((float)$s1 * (float)$s2, $scale);
- }
- }
- /**
- * Divides two numbers, using arbitrary precision when available.
- * @param string $s1
- * @param string $s2
- * @param int $scale
- * @return string
- */
- private function div($s1, $s2, $scale)
- {
- if ($this->bcmath) {
- return bcdiv($s1, $s2, $scale);
- } else {
- return $this->scale((float)$s1 / (float)$s2, $scale);
- }
- }
- /**
- * Rounds a number according to the number of sigfigs it should have,
- * using arbitrary precision when available.
- * @param float $n
- * @param int $sigfigs
- * @return string
- */
- private function round($n, $sigfigs)
- {
- $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1
- $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
- $neg = $n < 0 ? '-' : ''; // Negative sign
- if ($this->bcmath) {
- if ($rp >= 0) {
- $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
- $n = bcdiv($n, '1', $rp);
- } else {
- // This algorithm partially depends on the standardized
- // form of numbers that comes out of bcmath.
- $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
- $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
- }
- return $n;
- } else {
- return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
- }
- }
- /**
- * Scales a float to $scale digits right of decimal point, like BCMath.
- * @param float $r
- * @param int $scale
- * @return string
- */
- private function scale($r, $scale)
- {
- if ($scale < 0) {
- // The f sprintf type doesn't support negative numbers, so we
- // need to cludge things manually. First get the string.
- $r = sprintf('%.0f', (float)$r);
- // Due to floating point precision loss, $r will more than likely
- // look something like 4652999999999.9234. We grab one more digit
- // than we need to precise from $r and then use that to round
- // appropriately.
- $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
- // Now we return it, truncating the zero that was rounded off.
- return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
- }
- return sprintf('%.' . $scale . 'f', (float)$r);
- }
- }
- /**
- * Parses string representations into their corresponding native PHP
- * variable type. The base implementation does a simple type-check.
- */
- class HTMLPurifier_VarParser
- {
- const STRING = 1;
- const ISTRING = 2;
- const TEXT = 3;
- const ITEXT = 4;
- const INT = 5;
- const FLOAT = 6;
- const BOOL = 7;
- const LOOKUP = 8;
- const ALIST = 9;
- const HASH = 10;
- const MIXED = 11;
- /**
- * Lookup table of allowed types. Mainly for backwards compatibility, but
- * also convenient for transforming string type names to the integer constants.
- */
- public static $types = array(
- 'string' => self::STRING,
- 'istring' => self::ISTRING,
- 'text' => self::TEXT,
- 'itext' => self::ITEXT,
- 'int' => self::INT,
- 'float' => self::FLOAT,
- 'bool' => self::BOOL,
- 'lookup' => self::LOOKUP,
- 'list' => self::ALIST,
- 'hash' => self::HASH,
- 'mixed' => self::MIXED
- );
- /**
- * Lookup table of types that are string, and can have aliases or
- * allowed value lists.
- */
- public static $stringTypes = array(
- self::STRING => true,
- self::ISTRING => true,
- self::TEXT => true,
- self::ITEXT => true,
- );
- /**
- * Validate a variable according to type.
- * It may return NULL as a valid type if $allow_null is true.
- *
- * @param mixed $var Variable to validate
- * @param int $type Type of variable, see HTMLPurifier_VarParser->types
- * @param bool $allow_null Whether or not to permit null as a value
- * @return string Validated and type-coerced variable
- * @throws HTMLPurifier_VarParserException
- */
- final public function parse($var, $type, $allow_null = false)
- {
- if (is_string($type)) {
- if (!isset(HTMLPurifier_VarParser::$types[$type])) {
- throw new HTMLPurifier_VarParserException("Invalid type '$type'");
- } else {
- $type = HTMLPurifier_VarParser::$types[$type];
- }
- }
- $var = $this->parseImplementation($var, $type, $allow_null);
- if ($allow_null && $var === null) {
- return null;
- }
- // These are basic checks, to make sure nothing horribly wrong
- // happened in our implementations.
- switch ($type) {
- case (self::STRING):
- case (self::ISTRING):
- case (self::TEXT):
- case (self::ITEXT):
- if (!is_string($var)) {
- break;
- }
- if ($type == self::ISTRING || $type == self::ITEXT) {
- $var = strtolower($var);
- }
- return $var;
- case (self::INT):
- if (!is_int($var)) {
- break;
- }
- return $var;
- case (self::FLOAT):
- if (!is_float($var)) {
- break;
- }
- return $var;
- case (self::BOOL):
- if (!is_bool($var)) {
- break;
- }
- return $var;
- case (self::LOOKUP):
- case (self::ALIST):
- case (self::HASH):
- if (!is_array($var)) {
- break;
- }
- if ($type === self::LOOKUP) {
- foreach ($var as $k) {
- if ($k !== true) {
- $this->error('Lookup table contains value other than true');
- }
- }
- } elseif ($type === self::ALIST) {
- $keys = array_keys($var);
- if (array_keys($keys) !== $keys) {
- $this->error('Indices for list are not uniform');
- }
- }
- return $var;
- case (self::MIXED):
- return $var;
- default:
- $this->errorInconsistent(get_class($this), $type);
- }
- $this->errorGeneric($var, $type);
- }
- /**
- * Actually implements the parsing. Base implementation does not
- * do anything to $var. Subclasses should overload this!
- * @param mixed $var
- * @param int $type
- * @param bool $allow_null
- * @return string
- */
- protected function parseImplementation($var, $type, $allow_null)
- {
- return $var;
- }
- /**
- * Throws an exception.
- * @throws HTMLPurifier_VarParserException
- */
- protected function error($msg)
- {
- throw new HTMLPurifier_VarParserException($msg);
- }
- /**
- * Throws an inconsistency exception.
- * @note This should not ever be called. It would be called if we
- * extend the allowed values of HTMLPurifier_VarParser without
- * updating subclasses.
- * @param string $class
- * @param int $type
- * @throws HTMLPurifier_Exception
- */
- protected function errorInconsistent($class, $type)
- {
- throw new HTMLPurifier_Exception(
- "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
- " not implemented"
- );
- }
- /**
- * Generic error for if a type didn't work.
- * @param mixed $var
- * @param int $type
- */
- protected function errorGeneric($var, $type)
- {
- $vtype = gettype($var);
- $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
- }
- /**
- * @param int $type
- * @return string
- */
- public static function getTypeName($type)
- {
- static $lookup;
- if (!$lookup) {
- // Lazy load the alternative lookup table
- $lookup = array_flip(HTMLPurifier_VarParser::$types);
- }
- if (!isset($lookup[$type])) {
- return 'unknown';
- }
- return $lookup[$type];
- }
- }
- /**
- * Exception type for HTMLPurifier_VarParser
- */
- class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
- {
- }
- /**
- * A zipper is a purely-functional data structure which contains
- * a focus that can be efficiently manipulated. It is known as
- * a "one-hole context". This mutable variant implements a zipper
- * for a list as a pair of two arrays, laid out as follows:
- *
- * Base list: 1 2 3 4 [ ] 6 7 8 9
- * Front list: 1 2 3 4
- * Back list: 9 8 7 6
- *
- * User is expected to keep track of the "current element" and properly
- * fill it back in as necessary. (ToDo: Maybe it's more user friendly
- * to implicitly track the current element?)
- *
- * Nota bene: the current class gets confused if you try to store NULLs
- * in the list.
- */
- class HTMLPurifier_Zipper
- {
- public $front, $back;
- public function __construct($front, $back) {
- $this->front = $front;
- $this->back = $back;
- }
- /**
- * Creates a zipper from an array, with a hole in the
- * 0-index position.
- * @param Array to zipper-ify.
- * @return Tuple of zipper and element of first position.
- */
- static public function fromArray($array) {
- $z = new self(array(), array_reverse($array));
- $t = $z->delete(); // delete the "dummy hole"
- return array($z, $t);
- }
- /**
- * Convert zipper back into a normal array, optionally filling in
- * the hole with a value. (Usually you should supply a $t, unless you
- * are at the end of the array.)
- */
- public function toArray($t = NULL) {
- $a = $this->front;
- if ($t !== NULL) $a[] = $t;
- for ($i = count($this->back)-1; $i >= 0; $i--) {
- $a[] = $this->back[$i];
- }
- return $a;
- }
- /**
- * Move hole to the next element.
- * @param $t Element to fill hole with
- * @return Original contents of new hole.
- */
- public function next($t) {
- if ($t !== NULL) array_push($this->front, $t);
- return empty($this->back) ? NULL : array_pop($this->back);
- }
- /**
- * Iterated hole advancement.
- * @param $t Element to fill hole with
- * @param $i How many forward to advance hole
- * @return Original contents of new hole, i away
- */
- public function advance($t, $n) {
- for ($i = 0; $i < $n; $i++) {
- $t = $this->next($t);
- }
- return $t;
- }
- /**
- * Move hole to the previous element
- * @param $t Element to fill hole with
- * @return Original contents of new hole.
- */
- public function prev($t) {
- if ($t !== NULL) array_push($this->back, $t);
- return empty($this->front) ? NULL : array_pop($this->front);
- }
- /**
- * Delete contents of current hole, shifting hole to
- * next element.
- * @return Original contents of new hole.
- */
- public function delete() {
- return empty($this->back) ? NULL : array_pop($this->back);
- }
- /**
- * Returns true if we are at the end of the list.
- * @return bool
- */
- public function done() {
- return empty($this->back);
- }
- /**
- * Insert element before hole.
- * @param Element to insert
- */
- public function insertBefore($t) {
- if ($t !== NULL) array_push($this->front, $t);
- }
- /**
- * Insert element after hole.
- * @param Element to insert
- */
- public function insertAfter($t) {
- if ($t !== NULL) array_push($this->back, $t);
- }
- /**
- * Splice in multiple elements at hole. Functional specification
- * in terms of array_splice:
- *
- * $arr1 = $arr;
- * $old1 = array_splice($arr1, $i, $delete, $replacement);
- *
- * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
- * $t = $z->advance($t, $i);
- * list($old2, $t) = $z->splice($t, $delete, $replacement);
- * $arr2 = $z->toArray($t);
- *
- * assert($old1 === $old2);
- * assert($arr1 === $arr2);
- *
- * NB: the absolute index location after this operation is
- * *unchanged!*
- *
- * @param Current contents of hole.
- */
- public function splice($t, $delete, $replacement) {
- // delete
- $old = array();
- $r = $t;
- for ($i = $delete; $i > 0; $i--) {
- $old[] = $r;
- $r = $this->delete();
- }
- // insert
- for ($i = count($replacement)-1; $i >= 0; $i--) {
- $this->insertAfter($r);
- $r = $replacement[$i];
- }
- return array($old, $r);
- }
- }
- /**
- * Validates the HTML attribute style, otherwise known as CSS.
- * @note We don't implement the whole CSS specification, so it might be
- * difficult to reuse this component in the context of validating
- * actual stylesheet declarations.
- * @note If we were really serious about validating the CSS, we would
- * tokenize the styles and then parse the tokens. Obviously, we
- * are not doing that. Doing that could seriously harm performance,
- * but would make these components a lot more viable for a CSS
- * filtering solution.
- */
- class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $css
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($css, $config, $context)
- {
- $css = $this->parseCDATA($css);
- $definition = $config->getCSSDefinition();
- // we're going to break the spec and explode by semicolons.
- // This is because semicolon rarely appears in escaped form
- // Doing this is generally flaky but fast
- // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
- // for details
- $declarations = explode(';', $css);
- $propvalues = array();
- /**
- * Name of the current CSS property being validated.
- */
- $property = false;
- $context->register('CurrentCSSProperty', $property);
- foreach ($declarations as $declaration) {
- if (!$declaration) {
- continue;
- }
- if (!strpos($declaration, ':')) {
- continue;
- }
- list($property, $value) = explode(':', $declaration, 2);
- $property = trim($property);
- $value = trim($value);
- $ok = false;
- do {
- if (isset($definition->info[$property])) {
- $ok = true;
- break;
- }
- if (ctype_lower($property)) {
- break;
- }
- $property = strtolower($property);
- if (isset($definition->info[$property])) {
- $ok = true;
- break;
- }
- } while (0);
- if (!$ok) {
- continue;
- }
- // inefficient call, since the validator will do this again
- if (strtolower(trim($value)) !== 'inherit') {
- // inherit works for everything (but only on the base property)
- $result = $definition->info[$property]->validate(
- $value,
- $config,
- $context
- );
- } else {
- $result = 'inherit';
- }
- if ($result === false) {
- continue;
- }
- $propvalues[$property] = $result;
- }
- $context->destroy('CurrentCSSProperty');
- // procedure does not write the new CSS simultaneously, so it's
- // slightly inefficient, but it's the only way of getting rid of
- // duplicates. Perhaps config to optimize it, but not now.
- $new_declarations = '';
- foreach ($propvalues as $prop => $value) {
- $new_declarations .= "$prop:$value;";
- }
- return $new_declarations ? $new_declarations : false;
- }
- }
- /**
- * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
- * with make.
- */
- class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
- {
- /**
- * What we're cloning.
- * @type HTMLPurifier_AttrDef
- */
- protected $clone;
- /**
- * @param HTMLPurifier_AttrDef $clone
- */
- public function __construct($clone)
- {
- $this->clone = $clone;
- }
- /**
- * @param string $v
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($v, $config, $context)
- {
- return $this->clone->validate($v, $config, $context);
- }
- /**
- * @param string $string
- * @return HTMLPurifier_AttrDef
- */
- public function make($string)
- {
- return clone $this->clone;
- }
- }
- // Enum = Enumerated
- /**
- * Validates a keyword against a list of valid values.
- * @warning The case-insensitive compare of this function uses PHP's
- * built-in strtolower and ctype_lower functions, which may
- * cause problems with international comparisons
- */
- class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
- {
- /**
- * Lookup table of valid values.
- * @type array
- * @todo Make protected
- */
- public $valid_values = array();
- /**
- * Bool indicating whether or not enumeration is case sensitive.
- * @note In general this is always case insensitive.
- */
- protected $case_sensitive = false; // values according to W3C spec
- /**
- * @param array $valid_values List of valid values
- * @param bool $case_sensitive Whether or not case sensitive
- */
- public function __construct($valid_values = array(), $case_sensitive = false)
- {
- $this->valid_values = array_flip($valid_values);
- $this->case_sensitive = $case_sensitive;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- if (!$this->case_sensitive) {
- // we may want to do full case-insensitive libraries
- $string = ctype_lower($string) ? $string : strtolower($string);
- }
- $result = isset($this->valid_values[$string]);
- return $result ? $string : false;
- }
- /**
- * @param string $string In form of comma-delimited list of case-insensitive
- * valid values. Example: "foo,bar,baz". Prepend "s:" to make
- * case sensitive
- * @return HTMLPurifier_AttrDef_Enum
- */
- public function make($string)
- {
- if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
- $string = substr($string, 2);
- $sensitive = true;
- } else {
- $sensitive = false;
- }
- $values = explode(',', $string);
- return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
- }
- }
- /**
- * Validates an integer.
- * @note While this class was modeled off the CSS definition, no currently
- * allowed CSS uses this type. The properties that do are: widows,
- * orphans, z-index, counter-increment, counter-reset. Some of the
- * HTML attributes, however, find use for a non-negative version of this.
- */
- class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
- {
- /**
- * Whether or not negative values are allowed.
- * @type bool
- */
- protected $negative = true;
- /**
- * Whether or not zero is allowed.
- * @type bool
- */
- protected $zero = true;
- /**
- * Whether or not positive values are allowed.
- * @type bool
- */
- protected $positive = true;
- /**
- * @param $negative Bool indicating whether or not negative values are allowed
- * @param $zero Bool indicating whether or not zero is allowed
- * @param $positive Bool indicating whether or not positive values are allowed
- */
- public function __construct($negative = true, $zero = true, $positive = true)
- {
- $this->negative = $negative;
- $this->zero = $zero;
- $this->positive = $positive;
- }
- /**
- * @param string $integer
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($integer, $config, $context)
- {
- $integer = $this->parseCDATA($integer);
- if ($integer === '') {
- return false;
- }
- // we could possibly simply typecast it to integer, but there are
- // certain fringe cases that must not return an integer.
- // clip leading sign
- if ($this->negative && $integer[0] === '-') {
- $digits = substr($integer, 1);
- if ($digits === '0') {
- $integer = '0';
- } // rm minus sign for zero
- } elseif ($this->positive && $integer[0] === '+') {
- $digits = $integer = substr($integer, 1); // rm unnecessary plus
- } else {
- $digits = $integer;
- }
- // test if it's numeric
- if (!ctype_digit($digits)) {
- return false;
- }
- // perform scope tests
- if (!$this->zero && $integer == 0) {
- return false;
- }
- if (!$this->positive && $integer > 0) {
- return false;
- }
- if (!$this->negative && $integer < 0) {
- return false;
- }
- return $integer;
- }
- }
- /**
- * Validates the HTML attribute lang, effectively a language code.
- * @note Built according to RFC 3066, which obsoleted RFC 1766
- */
- class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- if (!$string) {
- return false;
- }
- $subtags = explode('-', $string);
- $num_subtags = count($subtags);
- if ($num_subtags == 0) { // sanity check
- return false;
- }
- // process primary subtag : $subtags[0]
- $length = strlen($subtags[0]);
- switch ($length) {
- case 0:
- return false;
- case 1:
- if (!($subtags[0] == 'x' || $subtags[0] == 'i')) {
- return false;
- }
- break;
- case 2:
- case 3:
- if (!ctype_alpha($subtags[0])) {
- return false;
- } elseif (!ctype_lower($subtags[0])) {
- $subtags[0] = strtolower($subtags[0]);
- }
- break;
- default:
- return false;
- }
- $new_string = $subtags[0];
- if ($num_subtags == 1) {
- return $new_string;
- }
- // process second subtag : $subtags[1]
- $length = strlen($subtags[1]);
- if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
- return $new_string;
- }
- if (!ctype_lower($subtags[1])) {
- $subtags[1] = strtolower($subtags[1]);
- }
- $new_string .= '-' . $subtags[1];
- if ($num_subtags == 2) {
- return $new_string;
- }
- // process all other subtags, index 2 and up
- for ($i = 2; $i < $num_subtags; $i++) {
- $length = strlen($subtags[$i]);
- if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
- return $new_string;
- }
- if (!ctype_lower($subtags[$i])) {
- $subtags[$i] = strtolower($subtags[$i]);
- }
- $new_string .= '-' . $subtags[$i];
- }
- return $new_string;
- }
- }
- /**
- * Decorator that, depending on a token, switches between two definitions.
- */
- class HTMLPurifier_AttrDef_Switch
- {
- /**
- * @type string
- */
- protected $tag;
- /**
- * @type HTMLPurifier_AttrDef
- */
- protected $withTag;
- /**
- * @type HTMLPurifier_AttrDef
- */
- protected $withoutTag;
- /**
- * @param string $tag Tag name to switch upon
- * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
- * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
- */
- public function __construct($tag, $with_tag, $without_tag)
- {
- $this->tag = $tag;
- $this->withTag = $with_tag;
- $this->withoutTag = $without_tag;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $token = $context->get('CurrentToken', true);
- if (!$token || $token->name !== $this->tag) {
- return $this->withoutTag->validate($string, $config, $context);
- } else {
- return $this->withTag->validate($string, $config, $context);
- }
- }
- }
- /**
- * Validates arbitrary text according to the HTML spec.
- */
- class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- return $this->parseCDATA($string);
- }
- }
- /**
- * Validates a URI as defined by RFC 3986.
- * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
- */
- class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_URIParser
- */
- protected $parser;
- /**
- * @type bool
- */
- protected $embedsResource;
- /**
- * @param bool $embeds_resource Does the URI here result in an extra HTTP request?
- */
- public function __construct($embeds_resource = false)
- {
- $this->parser = new HTMLPurifier_URIParser();
- $this->embedsResource = (bool)$embeds_resource;
- }
- /**
- * @param string $string
- * @return HTMLPurifier_AttrDef_URI
- */
- public function make($string)
- {
- $embeds = ($string === 'embedded');
- return new HTMLPurifier_AttrDef_URI($embeds);
- }
- /**
- * @param string $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($uri, $config, $context)
- {
- if ($config->get('URI.Disable')) {
- return false;
- }
- $uri = $this->parseCDATA($uri);
- // parse the URI
- $uri = $this->parser->parse($uri);
- if ($uri === false) {
- return false;
- }
- // add embedded flag to context for validators
- $context->register('EmbeddedURI', $this->embedsResource);
- $ok = false;
- do {
- // generic validation
- $result = $uri->validate($config, $context);
- if (!$result) {
- break;
- }
- // chained filtering
- $uri_def = $config->getDefinition('URI');
- $result = $uri_def->filter($uri, $config, $context);
- if (!$result) {
- break;
- }
- // scheme-specific validation
- $scheme_obj = $uri->getSchemeObj($config, $context);
- if (!$scheme_obj) {
- break;
- }
- if ($this->embedsResource && !$scheme_obj->browsable) {
- break;
- }
- $result = $scheme_obj->validate($uri, $config, $context);
- if (!$result) {
- break;
- }
- // Post chained filtering
- $result = $uri_def->postFilter($uri, $config, $context);
- if (!$result) {
- break;
- }
- // survived gauntlet
- $ok = true;
- } while (false);
- $context->destroy('EmbeddedURI');
- if (!$ok) {
- return false;
- }
- // back to string
- return $uri->toString();
- }
- }
- /**
- * Validates a number as defined by the CSS spec.
- */
- class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
- {
- /**
- * Indicates whether or not only positive values are allowed.
- * @type bool
- */
- protected $non_negative = false;
- /**
- * @param bool $non_negative indicates whether negatives are forbidden
- */
- public function __construct($non_negative = false)
- {
- $this->non_negative = $non_negative;
- }
- /**
- * @param string $number
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string|bool
- * @warning Some contexts do not pass $config, $context. These
- * variables should not be used without checking HTMLPurifier_Length
- */
- public function validate($number, $config, $context)
- {
- $number = $this->parseCDATA($number);
- if ($number === '') {
- return false;
- }
- if ($number === '0') {
- return '0';
- }
- $sign = '';
- switch ($number[0]) {
- case '-':
- if ($this->non_negative) {
- return false;
- }
- $sign = '-';
- case '+':
- $number = substr($number, 1);
- }
- if (ctype_digit($number)) {
- $number = ltrim($number, '0');
- return $number ? $sign . $number : '0';
- }
- // Period is the only non-numeric character allowed
- if (strpos($number, '.') === false) {
- return false;
- }
- list($left, $right) = explode('.', $number, 2);
- if ($left === '' && $right === '') {
- return false;
- }
- if ($left !== '' && !ctype_digit($left)) {
- return false;
- }
- $left = ltrim($left, '0');
- $right = rtrim($right, '0');
- if ($right === '') {
- return $left ? $sign . $left : '0';
- } elseif (!ctype_digit($right)) {
- return false;
- }
- return $sign . $left . '.' . $right;
- }
- }
- class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
- {
- public function __construct()
- {
- parent::__construct(false); // opacity is non-negative, but we will clamp it
- }
- /**
- * @param string $number
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public function validate($number, $config, $context)
- {
- $result = parent::validate($number, $config, $context);
- if ($result === false) {
- return $result;
- }
- $float = (float)$result;
- if ($float < 0.0) {
- $result = '0';
- }
- if ($float > 1.0) {
- $result = '1';
- }
- return $result;
- }
- }
- /**
- * Validates shorthand CSS property background.
- * @warning Does not support url tokens that have internal spaces.
- */
- class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
- {
- /**
- * Local copy of component validators.
- * @type HTMLPurifier_AttrDef[]
- * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
- */
- protected $info;
- /**
- * @param HTMLPurifier_Config $config
- */
- public function __construct($config)
- {
- $def = $config->getCSSDefinition();
- $this->info['background-color'] = $def->info['background-color'];
- $this->info['background-image'] = $def->info['background-image'];
- $this->info['background-repeat'] = $def->info['background-repeat'];
- $this->info['background-attachment'] = $def->info['background-attachment'];
- $this->info['background-position'] = $def->info['background-position'];
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- // regular pre-processing
- $string = $this->parseCDATA($string);
- if ($string === '') {
- return false;
- }
- // munge rgb() decl if necessary
- $string = $this->mungeRgb($string);
- // assumes URI doesn't have spaces in it
- $bits = explode(' ', $string); // bits to process
- $caught = array();
- $caught['color'] = false;
- $caught['image'] = false;
- $caught['repeat'] = false;
- $caught['attachment'] = false;
- $caught['position'] = false;
- $i = 0; // number of catches
- foreach ($bits as $bit) {
- if ($bit === '') {
- continue;
- }
- foreach ($caught as $key => $status) {
- if ($key != 'position') {
- if ($status !== false) {
- continue;
- }
- $r = $this->info['background-' . $key]->validate($bit, $config, $context);
- } else {
- $r = $bit;
- }
- if ($r === false) {
- continue;
- }
- if ($key == 'position') {
- if ($caught[$key] === false) {
- $caught[$key] = '';
- }
- $caught[$key] .= $r . ' ';
- } else {
- $caught[$key] = $r;
- }
- $i++;
- break;
- }
- }
- if (!$i) {
- return false;
- }
- if ($caught['position'] !== false) {
- $caught['position'] = $this->info['background-position']->
- validate($caught['position'], $config, $context);
- }
- $ret = array();
- foreach ($caught as $value) {
- if ($value === false) {
- continue;
- }
- $ret[] = $value;
- }
- if (empty($ret)) {
- return false;
- }
- return implode(' ', $ret);
- }
- }
- /* W3C says:
- [ // adjective and number must be in correct order, even if
- // you could switch them without introducing ambiguity.
- // some browsers support that syntax
- [
- <percentage> | <length> | left | center | right
- ]
- [
- <percentage> | <length> | top | center | bottom
- ]?
- ] |
- [ // this signifies that the vertical and horizontal adjectives
- // can be arbitrarily ordered, however, there can only be two,
- // one of each, or none at all
- [
- left | center | right
- ] ||
- [
- top | center | bottom
- ]
- ]
- top, left = 0%
- center, (none) = 50%
- bottom, right = 100%
- */
- /* QuirksMode says:
- keyword + length/percentage must be ordered correctly, as per W3C
- Internet Explorer and Opera, however, support arbitrary ordering. We
- should fix it up.
- Minor issue though, not strictly necessary.
- */
- // control freaks may appreciate the ability to convert these to
- // percentages or something, but it's not necessary
- /**
- * Validates the value of background-position.
- */
- class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_AttrDef_CSS_Length
- */
- protected $length;
- /**
- * @type HTMLPurifier_AttrDef_CSS_Percentage
- */
- protected $percentage;
- public function __construct()
- {
- $this->length = new HTMLPurifier_AttrDef_CSS_Length();
- $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = $this->parseCDATA($string);
- $bits = explode(' ', $string);
- $keywords = array();
- $keywords['h'] = false; // left, right
- $keywords['v'] = false; // top, bottom
- $keywords['ch'] = false; // center (first word)
- $keywords['cv'] = false; // center (second word)
- $measures = array();
- $i = 0;
- $lookup = array(
- 'top' => 'v',
- 'bottom' => 'v',
- 'left' => 'h',
- 'right' => 'h',
- 'center' => 'c'
- );
- foreach ($bits as $bit) {
- if ($bit === '') {
- continue;
- }
- // test for keyword
- $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
- if (isset($lookup[$lbit])) {
- $status = $lookup[$lbit];
- if ($status == 'c') {
- if ($i == 0) {
- $status = 'ch';
- } else {
- $status = 'cv';
- }
- }
- $keywords[$status] = $lbit;
- $i++;
- }
- // test for length
- $r = $this->length->validate($bit, $config, $context);
- if ($r !== false) {
- $measures[] = $r;
- $i++;
- }
- // test for percentage
- $r = $this->percentage->validate($bit, $config, $context);
- if ($r !== false) {
- $measures[] = $r;
- $i++;
- }
- }
- if (!$i) {
- return false;
- } // no valid values were caught
- $ret = array();
- // first keyword
- if ($keywords['h']) {
- $ret[] = $keywords['h'];
- } elseif ($keywords['ch']) {
- $ret[] = $keywords['ch'];
- $keywords['cv'] = false; // prevent re-use: center = center center
- } elseif (count($measures)) {
- $ret[] = array_shift($measures);
- }
- if ($keywords['v']) {
- $ret[] = $keywords['v'];
- } elseif ($keywords['cv']) {
- $ret[] = $keywords['cv'];
- } elseif (count($measures)) {
- $ret[] = array_shift($measures);
- }
- if (empty($ret)) {
- return false;
- }
- return implode(' ', $ret);
- }
- }
- /**
- * Validates the border property as defined by CSS.
- */
- class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
- {
- /**
- * Local copy of properties this property is shorthand for.
- * @type HTMLPurifier_AttrDef[]
- */
- protected $info = array();
- /**
- * @param HTMLPurifier_Config $config
- */
- public function __construct($config)
- {
- $def = $config->getCSSDefinition();
- $this->info['border-width'] = $def->info['border-width'];
- $this->info['border-style'] = $def->info['border-style'];
- $this->info['border-top-color'] = $def->info['border-top-color'];
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = $this->parseCDATA($string);
- $string = $this->mungeRgb($string);
- $bits = explode(' ', $string);
- $done = array(); // segments we've finished
- $ret = ''; // return value
- foreach ($bits as $bit) {
- foreach ($this->info as $propname => $validator) {
- if (isset($done[$propname])) {
- continue;
- }
- $r = $validator->validate($bit, $config, $context);
- if ($r !== false) {
- $ret .= $r . ' ';
- $done[$propname] = true;
- break;
- }
- }
- }
- return rtrim($ret);
- }
- }
- /**
- * Validates Color as defined by CSS.
- */
- class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $color
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($color, $config, $context)
- {
- static $colors = null;
- if ($colors === null) {
- $colors = $config->get('Core.ColorKeywords');
- }
- $color = trim($color);
- if ($color === '') {
- return false;
- }
- $lower = strtolower($color);
- if (isset($colors[$lower])) {
- return $colors[$lower];
- }
- if (strpos($color, 'rgb(') !== false) {
- // rgb literal handling
- $length = strlen($color);
- if (strpos($color, ')') !== $length - 1) {
- return false;
- }
- $triad = substr($color, 4, $length - 4 - 1);
- $parts = explode(',', $triad);
- if (count($parts) !== 3) {
- return false;
- }
- $type = false; // to ensure that they're all the same type
- $new_parts = array();
- foreach ($parts as $part) {
- $part = trim($part);
- if ($part === '') {
- return false;
- }
- $length = strlen($part);
- if ($part[$length - 1] === '%') {
- // handle percents
- if (!$type) {
- $type = 'percentage';
- } elseif ($type !== 'percentage') {
- return false;
- }
- $num = (float)substr($part, 0, $length - 1);
- if ($num < 0) {
- $num = 0;
- }
- if ($num > 100) {
- $num = 100;
- }
- $new_parts[] = "$num%";
- } else {
- // handle integers
- if (!$type) {
- $type = 'integer';
- } elseif ($type !== 'integer') {
- return false;
- }
- $num = (int)$part;
- if ($num < 0) {
- $num = 0;
- }
- if ($num > 255) {
- $num = 255;
- }
- $new_parts[] = (string)$num;
- }
- }
- $new_triad = implode(',', $new_parts);
- $color = "rgb($new_triad)";
- } else {
- // hexadecimal handling
- if ($color[0] === '#') {
- $hex = substr($color, 1);
- } else {
- $hex = $color;
- $color = '#' . $color;
- }
- $length = strlen($hex);
- if ($length !== 3 && $length !== 6) {
- return false;
- }
- if (!ctype_xdigit($hex)) {
- return false;
- }
- }
- return $color;
- }
- }
- /**
- * Allows multiple validators to attempt to validate attribute.
- *
- * Composite is just what it sounds like: a composite of many validators.
- * This means that multiple HTMLPurifier_AttrDef objects will have a whack
- * at the string. If one of them passes, that's what is returned. This is
- * especially useful for CSS values, which often are a choice between
- * an enumerated set of predefined values or a flexible data type.
- */
- class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
- {
- /**
- * List of objects that may process strings.
- * @type HTMLPurifier_AttrDef[]
- * @todo Make protected
- */
- public $defs;
- /**
- * @param HTMLPurifier_AttrDef[] $defs List of HTMLPurifier_AttrDef objects
- */
- public function __construct($defs)
- {
- $this->defs = $defs;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- foreach ($this->defs as $i => $def) {
- $result = $this->defs[$i]->validate($string, $config, $context);
- if ($result !== false) {
- return $result;
- }
- }
- return false;
- }
- }
- /**
- * Decorator which enables CSS properties to be disabled for specific elements.
- */
- class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_AttrDef
- */
- public $def;
- /**
- * @type string
- */
- public $element;
- /**
- * @param HTMLPurifier_AttrDef $def Definition to wrap
- * @param string $element Element to deny
- */
- public function __construct($def, $element)
- {
- $this->def = $def;
- $this->element = $element;
- }
- /**
- * Checks if CurrentToken is set and equal to $this->element
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $token = $context->get('CurrentToken', true);
- if ($token && $token->name == $this->element) {
- return false;
- }
- return $this->def->validate($string, $config, $context);
- }
- }
- /**
- * Microsoft's proprietary filter: CSS property
- * @note Currently supports the alpha filter. In the future, this will
- * probably need an extensible framework
- */
- class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_AttrDef_Integer
- */
- protected $intValidator;
- public function __construct()
- {
- $this->intValidator = new HTMLPurifier_AttrDef_Integer();
- }
- /**
- * @param string $value
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($value, $config, $context)
- {
- $value = $this->parseCDATA($value);
- if ($value === 'none') {
- return $value;
- }
- // if we looped this we could support multiple filters
- $function_length = strcspn($value, '(');
- $function = trim(substr($value, 0, $function_length));
- if ($function !== 'alpha' &&
- $function !== 'Alpha' &&
- $function !== 'progid:DXImageTransform.Microsoft.Alpha'
- ) {
- return false;
- }
- $cursor = $function_length + 1;
- $parameters_length = strcspn($value, ')', $cursor);
- $parameters = substr($value, $cursor, $parameters_length);
- $params = explode(',', $parameters);
- $ret_params = array();
- $lookup = array();
- foreach ($params as $param) {
- list($key, $value) = explode('=', $param);
- $key = trim($key);
- $value = trim($value);
- if (isset($lookup[$key])) {
- continue;
- }
- if ($key !== 'opacity') {
- continue;
- }
- $value = $this->intValidator->validate($value, $config, $context);
- if ($value === false) {
- continue;
- }
- $int = (int)$value;
- if ($int > 100) {
- $value = '100';
- }
- if ($int < 0) {
- $value = '0';
- }
- $ret_params[] = "$key=$value";
- $lookup[$key] = true;
- }
- $ret_parameters = implode(',', $ret_params);
- $ret_function = "$function($ret_parameters)";
- return $ret_function;
- }
- }
- /**
- * Validates shorthand CSS property font.
- */
- class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
- {
- /**
- * Local copy of validators
- * @type HTMLPurifier_AttrDef[]
- * @note If we moved specific CSS property definitions to their own
- * classes instead of having them be assembled at run time by
- * CSSDefinition, this wouldn't be necessary. We'd instantiate
- * our own copies.
- */
- protected $info = array();
- /**
- * @param HTMLPurifier_Config $config
- */
- public function __construct($config)
- {
- $def = $config->getCSSDefinition();
- $this->info['font-style'] = $def->info['font-style'];
- $this->info['font-variant'] = $def->info['font-variant'];
- $this->info['font-weight'] = $def->info['font-weight'];
- $this->info['font-size'] = $def->info['font-size'];
- $this->info['line-height'] = $def->info['line-height'];
- $this->info['font-family'] = $def->info['font-family'];
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- static $system_fonts = array(
- 'caption' => true,
- 'icon' => true,
- 'menu' => true,
- 'message-box' => true,
- 'small-caption' => true,
- 'status-bar' => true
- );
- // regular pre-processing
- $string = $this->parseCDATA($string);
- if ($string === '') {
- return false;
- }
- // check if it's one of the keywords
- $lowercase_string = strtolower($string);
- if (isset($system_fonts[$lowercase_string])) {
- return $lowercase_string;
- }
- $bits = explode(' ', $string); // bits to process
- $stage = 0; // this indicates what we're looking for
- $caught = array(); // which stage 0 properties have we caught?
- $stage_1 = array('font-style', 'font-variant', 'font-weight');
- $final = ''; // output
- for ($i = 0, $size = count($bits); $i < $size; $i++) {
- if ($bits[$i] === '') {
- continue;
- }
- switch ($stage) {
- case 0: // attempting to catch font-style, font-variant or font-weight
- foreach ($stage_1 as $validator_name) {
- if (isset($caught[$validator_name])) {
- continue;
- }
- $r = $this->info[$validator_name]->validate(
- $bits[$i],
- $config,
- $context
- );
- if ($r !== false) {
- $final .= $r . ' ';
- $caught[$validator_name] = true;
- break;
- }
- }
- // all three caught, continue on
- if (count($caught) >= 3) {
- $stage = 1;
- }
- if ($r !== false) {
- break;
- }
- case 1: // attempting to catch font-size and perhaps line-height
- $found_slash = false;
- if (strpos($bits[$i], '/') !== false) {
- list($font_size, $line_height) =
- explode('/', $bits[$i]);
- if ($line_height === '') {
- // ooh, there's a space after the slash!
- $line_height = false;
- $found_slash = true;
- }
- } else {
- $font_size = $bits[$i];
- $line_height = false;
- }
- $r = $this->info['font-size']->validate(
- $font_size,
- $config,
- $context
- );
- if ($r !== false) {
- $final .= $r;
- // attempt to catch line-height
- if ($line_height === false) {
- // we need to scroll forward
- for ($j = $i + 1; $j < $size; $j++) {
- if ($bits[$j] === '') {
- continue;
- }
- if ($bits[$j] === '/') {
- if ($found_slash) {
- return false;
- } else {
- $found_slash = true;
- continue;
- }
- }
- $line_height = $bits[$j];
- break;
- }
- } else {
- // slash already found
- $found_slash = true;
- $j = $i;
- }
- if ($found_slash) {
- $i = $j;
- $r = $this->info['line-height']->validate(
- $line_height,
- $config,
- $context
- );
- if ($r !== false) {
- $final .= '/' . $r;
- }
- }
- $final .= ' ';
- $stage = 2;
- break;
- }
- return false;
- case 2: // attempting to catch font-family
- $font_family =
- implode(' ', array_slice($bits, $i, $size - $i));
- $r = $this->info['font-family']->validate(
- $font_family,
- $config,
- $context
- );
- if ($r !== false) {
- $final .= $r . ' ';
- // processing completed successfully
- return rtrim($final);
- }
- return false;
- }
- }
- return false;
- }
- }
- /**
- * Validates a font family list according to CSS spec
- */
- class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
- {
- protected $mask = null;
- public function __construct()
- {
- $this->mask = '_- ';
- for ($c = 'a'; $c <= 'z'; $c++) {
- $this->mask .= $c;
- }
- for ($c = 'A'; $c <= 'Z'; $c++) {
- $this->mask .= $c;
- }
- for ($c = '0'; $c <= '9'; $c++) {
- $this->mask .= $c;
- } // cast-y, but should be fine
- // special bytes used by UTF-8
- for ($i = 0x80; $i <= 0xFF; $i++) {
- // We don't bother excluding invalid bytes in this range,
- // because the our restriction of well-formed UTF-8 will
- // prevent these from ever occurring.
- $this->mask .= chr($i);
- }
- /*
- PHP's internal strcspn implementation is
- O(length of string * length of mask), making it inefficient
- for large masks. However, it's still faster than
- preg_match 8)
- for (p = s1;;) {
- spanp = s2;
- do {
- if (*spanp == c || p == s1_end) {
- return p - s1;
- }
- } while (spanp++ < (s2_end - 1));
- c = *++p;
- }
- */
- // possible optimization: invert the mask.
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- static $generic_names = array(
- 'serif' => true,
- 'sans-serif' => true,
- 'monospace' => true,
- 'fantasy' => true,
- 'cursive' => true
- );
- $allowed_fonts = $config->get('CSS.AllowedFonts');
- // assume that no font names contain commas in them
- $fonts = explode(',', $string);
- $final = '';
- foreach ($fonts as $font) {
- $font = trim($font);
- if ($font === '') {
- continue;
- }
- // match a generic name
- if (isset($generic_names[$font])) {
- if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
- $final .= $font . ', ';
- }
- continue;
- }
- // match a quoted name
- if ($font[0] === '"' || $font[0] === "'") {
- $length = strlen($font);
- if ($length <= 2) {
- continue;
- }
- $quote = $font[0];
- if ($font[$length - 1] !== $quote) {
- continue;
- }
- $font = substr($font, 1, $length - 2);
- }
- $font = $this->expandCSSEscape($font);
- // $font is a pure representation of the font name
- if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
- continue;
- }
- if (ctype_alnum($font) && $font !== '') {
- // very simple font, allow it in unharmed
- $final .= $font . ', ';
- continue;
- }
- // bugger out on whitespace. form feed (0C) really
- // shouldn't show up regardless
- $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
- // Here, there are various classes of characters which need
- // to be treated differently:
- // - Alphanumeric characters are essentially safe. We
- // handled these above.
- // - Spaces require quoting, though most parsers will do
- // the right thing if there aren't any characters that
- // can be misinterpreted
- // - Dashes rarely occur, but they fairly unproblematic
- // for parsing/rendering purposes.
- // The above characters cover the majority of Western font
- // names.
- // - Arbitrary Unicode characters not in ASCII. Because
- // most parsers give little thought to Unicode, treatment
- // of these codepoints is basically uniform, even for
- // punctuation-like codepoints. These characters can
- // show up in non-Western pages and are supported by most
- // major browsers, for example: "MS 明朝" is a
- // legitimate font-name
- // <http://ja.wikipedia.org/wiki/MS_明朝>. See
- // the CSS3 spec for more examples:
- // <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
- // You can see live samples of these on the Internet:
- // <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
- // However, most of these fonts have ASCII equivalents:
- // for example, 'MS Mincho', and it's considered
- // professional to use ASCII font names instead of
- // Unicode font names. Thanks Takeshi Terada for
- // providing this information.
- // The following characters, to my knowledge, have not been
- // used to name font names.
- // - Single quote. While theoretically you might find a
- // font name that has a single quote in its name (serving
- // as an apostrophe, e.g. Dave's Scribble), I haven't
- // been able to find any actual examples of this.
- // Internet Explorer's cssText translation (which I
- // believe is invoked by innerHTML) normalizes any
- // quoting to single quotes, and fails to escape single
- // quotes. (Note that this is not IE's behavior for all
- // CSS properties, just some sort of special casing for
- // font-family). So a single quote *cannot* be used
- // safely in the font-family context if there will be an
- // innerHTML/cssText translation. Note that Firefox 3.x
- // does this too.
- // - Double quote. In IE, these get normalized to
- // single-quotes, no matter what the encoding. (Fun
- // fact, in IE8, the 'content' CSS property gained
- // support, where they special cased to preserve encoded
- // double quotes, but still translate unadorned double
- // quotes into single quotes.) So, because their
- // fixpoint behavior is identical to single quotes, they
- // cannot be allowed either. Firefox 3.x displays
- // single-quote style behavior.
- // - Backslashes are reduced by one (so \\ -> \) every
- // iteration, so they cannot be used safely. This shows
- // up in IE7, IE8 and FF3
- // - Semicolons, commas and backticks are handled properly.
- // - The rest of the ASCII punctuation is handled properly.
- // We haven't checked what browsers do to unadorned
- // versions, but this is not important as long as the
- // browser doesn't /remove/ surrounding quotes (as IE does
- // for HTML).
- //
- // With these results in hand, we conclude that there are
- // various levels of safety:
- // - Paranoid: alphanumeric, spaces and dashes(?)
- // - International: Paranoid + non-ASCII Unicode
- // - Edgy: Everything except quotes, backslashes
- // - NoJS: Standards compliance, e.g. sod IE. Note that
- // with some judicious character escaping (since certain
- // types of escaping doesn't work) this is theoretically
- // OK as long as innerHTML/cssText is not called.
- // We believe that international is a reasonable default
- // (that we will implement now), and once we do more
- // extensive research, we may feel comfortable with dropping
- // it down to edgy.
- // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of
- // str(c)spn assumes that the string was already well formed
- // Unicode (which of course it is).
- if (strspn($font, $this->mask) !== strlen($font)) {
- continue;
- }
- // Historical:
- // In the absence of innerHTML/cssText, these ugly
- // transforms don't pose a security risk (as \\ and \"
- // might--these escapes are not supported by most browsers).
- // We could try to be clever and use single-quote wrapping
- // when there is a double quote present, but I have choosen
- // not to implement that. (NOTE: you can reduce the amount
- // of escapes by one depending on what quoting style you use)
- // $font = str_replace('\\', '\\5C ', $font);
- // $font = str_replace('"', '\\22 ', $font);
- // $font = str_replace("'", '\\27 ', $font);
- // font possibly with spaces, requires quoting
- $final .= "'$font', ";
- }
- $final = rtrim($final, ', ');
- if ($final === '') {
- return false;
- }
- return $final;
- }
- }
- /**
- * Validates based on {ident} CSS grammar production
- */
- class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- // early abort: '' and '0' (strings that convert to false) are invalid
- if (!$string) {
- return false;
- }
- $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
- if (!preg_match($pattern, $string)) {
- return false;
- }
- return $string;
- }
- }
- /**
- * Decorator which enables !important to be used in CSS values.
- */
- class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_AttrDef
- */
- public $def;
- /**
- * @type bool
- */
- public $allow;
- /**
- * @param HTMLPurifier_AttrDef $def Definition to wrap
- * @param bool $allow Whether or not to allow !important
- */
- public function __construct($def, $allow = false)
- {
- $this->def = $def;
- $this->allow = $allow;
- }
- /**
- * Intercepts and removes !important if necessary
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- // test for ! and important tokens
- $string = trim($string);
- $is_important = false;
- // :TODO: optimization: test directly for !important and ! important
- if (strlen($string) >= 9 && substr($string, -9) === 'important') {
- $temp = rtrim(substr($string, 0, -9));
- // use a temp, because we might want to restore important
- if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
- $string = rtrim(substr($temp, 0, -1));
- $is_important = true;
- }
- }
- $string = $this->def->validate($string, $config, $context);
- if ($this->allow && $is_important) {
- $string .= ' !important';
- }
- return $string;
- }
- }
- /**
- * Represents a Length as defined by CSS.
- */
- class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
- {
- /**
- * @type HTMLPurifier_Length|string
- */
- protected $min;
- /**
- * @type HTMLPurifier_Length|string
- */
- protected $max;
- /**
- * @param HTMLPurifier_Length|string $min Minimum length, or null for no bound. String is also acceptable.
- * @param HTMLPurifier_Length|string $max Maximum length, or null for no bound. String is also acceptable.
- */
- public function __construct($min = null, $max = null)
- {
- $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
- $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = $this->parseCDATA($string);
- // Optimizations
- if ($string === '') {
- return false;
- }
- if ($string === '0') {
- return '0';
- }
- if (strlen($string) === 1) {
- return false;
- }
- $length = HTMLPurifier_Length::make($string);
- if (!$length->isValid()) {
- return false;
- }
- if ($this->min) {
- $c = $length->compareTo($this->min);
- if ($c === false) {
- return false;
- }
- if ($c < 0) {
- return false;
- }
- }
- if ($this->max) {
- $c = $length->compareTo($this->max);
- if ($c === false) {
- return false;
- }
- if ($c > 0) {
- return false;
- }
- }
- return $length->toString();
- }
- }
- /**
- * Validates shorthand CSS property list-style.
- * @warning Does not support url tokens that have internal spaces.
- */
- class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
- {
- /**
- * Local copy of validators.
- * @type HTMLPurifier_AttrDef[]
- * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
- */
- protected $info;
- /**
- * @param HTMLPurifier_Config $config
- */
- public function __construct($config)
- {
- $def = $config->getCSSDefinition();
- $this->info['list-style-type'] = $def->info['list-style-type'];
- $this->info['list-style-position'] = $def->info['list-style-position'];
- $this->info['list-style-image'] = $def->info['list-style-image'];
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- // regular pre-processing
- $string = $this->parseCDATA($string);
- if ($string === '') {
- return false;
- }
- // assumes URI doesn't have spaces in it
- $bits = explode(' ', strtolower($string)); // bits to process
- $caught = array();
- $caught['type'] = false;
- $caught['position'] = false;
- $caught['image'] = false;
- $i = 0; // number of catches
- $none = false;
- foreach ($bits as $bit) {
- if ($i >= 3) {
- return;
- } // optimization bit
- if ($bit === '') {
- continue;
- }
- foreach ($caught as $key => $status) {
- if ($status !== false) {
- continue;
- }
- $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
- if ($r === false) {
- continue;
- }
- if ($r === 'none') {
- if ($none) {
- continue;
- } else {
- $none = true;
- }
- if ($key == 'image') {
- continue;
- }
- }
- $caught[$key] = $r;
- $i++;
- break;
- }
- }
- if (!$i) {
- return false;
- }
- $ret = array();
- // construct type
- if ($caught['type']) {
- $ret[] = $caught['type'];
- }
- // construct image
- if ($caught['image']) {
- $ret[] = $caught['image'];
- }
- // construct position
- if ($caught['position']) {
- $ret[] = $caught['position'];
- }
- if (empty($ret)) {
- return false;
- }
- return implode(' ', $ret);
- }
- }
- /**
- * Framework class for strings that involve multiple values.
- *
- * Certain CSS properties such as border-width and margin allow multiple
- * lengths to be specified. This class can take a vanilla border-width
- * definition and multiply it, usually into a max of four.
- *
- * @note Even though the CSS specification isn't clear about it, inherit
- * can only be used alone: it will never manifest as part of a multi
- * shorthand declaration. Thus, this class does not allow inherit.
- */
- class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
- {
- /**
- * Instance of component definition to defer validation to.
- * @type HTMLPurifier_AttrDef
- * @todo Make protected
- */
- public $single;
- /**
- * Max number of values allowed.
- * @todo Make protected
- */
- public $max;
- /**
- * @param HTMLPurifier_AttrDef $single HTMLPurifier_AttrDef to multiply
- * @param int $max Max number of values allowed (usually four)
- */
- public function __construct($single, $max = 4)
- {
- $this->single = $single;
- $this->max = $max;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = $this->parseCDATA($string);
- if ($string === '') {
- return false;
- }
- $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
- $length = count($parts);
- $final = '';
- for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
- if (ctype_space($parts[$i])) {
- continue;
- }
- $result = $this->single->validate($parts[$i], $config, $context);
- if ($result !== false) {
- $final .= $result . ' ';
- $num++;
- }
- }
- if ($final === '') {
- return false;
- }
- return rtrim($final);
- }
- }
- /**
- * Validates a Percentage as defined by the CSS spec.
- */
- class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
- {
- /**
- * Instance to defer number validation to.
- * @type HTMLPurifier_AttrDef_CSS_Number
- */
- protected $number_def;
- /**
- * @param bool $non_negative Whether to forbid negative values
- */
- public function __construct($non_negative = false)
- {
- $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = $this->parseCDATA($string);
- if ($string === '') {
- return false;
- }
- $length = strlen($string);
- if ($length === 1) {
- return false;
- }
- if ($string[$length - 1] !== '%') {
- return false;
- }
- $number = substr($string, 0, $length - 1);
- $number = $this->number_def->validate($number, $config, $context);
- if ($number === false) {
- return false;
- }
- return "$number%";
- }
- }
- /**
- * Validates the value for the CSS property text-decoration
- * @note This class could be generalized into a version that acts sort of
- * like Enum except you can compound the allowed values.
- */
- class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- static $allowed_values = array(
- 'line-through' => true,
- 'overline' => true,
- 'underline' => true,
- );
- $string = strtolower($this->parseCDATA($string));
- if ($string === 'none') {
- return $string;
- }
- $parts = explode(' ', $string);
- $final = '';
- foreach ($parts as $part) {
- if (isset($allowed_values[$part])) {
- $final .= $part . ' ';
- }
- }
- $final = rtrim($final);
- if ($final === '') {
- return false;
- }
- return $final;
- }
- }
- /**
- * Validates a URI in CSS syntax, which uses url('http://example.com')
- * @note While theoretically speaking a URI in a CSS document could
- * be non-embedded, as of CSS2 there is no such usage so we're
- * generalizing it. This may need to be changed in the future.
- * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
- * the separator, you cannot put a literal semicolon in
- * in the URI. Try percent encoding it, in that case.
- */
- class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
- {
- public function __construct()
- {
- parent::__construct(true); // always embedded
- }
- /**
- * @param string $uri_string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($uri_string, $config, $context)
- {
- // parse the URI out of the string and then pass it onto
- // the parent object
- $uri_string = $this->parseCDATA($uri_string);
- if (strpos($uri_string, 'url(') !== 0) {
- return false;
- }
- $uri_string = substr($uri_string, 4);
- $new_length = strlen($uri_string) - 1;
- if ($uri_string[$new_length] != ')') {
- return false;
- }
- $uri = trim(substr($uri_string, 0, $new_length));
- if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
- $quote = $uri[0];
- $new_length = strlen($uri) - 1;
- if ($uri[$new_length] !== $quote) {
- return false;
- }
- $uri = substr($uri, 1, $new_length - 1);
- }
- $uri = $this->expandCSSEscape($uri);
- $result = parent::validate($uri, $config, $context);
- if ($result === false) {
- return false;
- }
- // extra sanity check; should have been done by URI
- $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
- // suspicious characters are ()'; we're going to percent encode
- // them for safety.
- $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
- // there's an extra bug where ampersands lose their escaping on
- // an innerHTML cycle, so a very unlucky query parameter could
- // then change the meaning of the URL. Unfortunately, there's
- // not much we can do about that...
- return "url(\"$result\")";
- }
- }
- /**
- * Validates a boolean attribute
- */
- class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
- {
- /**
- * @type bool
- */
- protected $name;
- /**
- * @type bool
- */
- public $minimized = true;
- /**
- * @param bool $name
- */
- public function __construct($name = false)
- {
- $this->name = $name;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- if (empty($string)) {
- return false;
- }
- return $this->name;
- }
- /**
- * @param string $string Name of attribute
- * @return HTMLPurifier_AttrDef_HTML_Bool
- */
- public function make($string)
- {
- return new HTMLPurifier_AttrDef_HTML_Bool($string);
- }
- }
- /**
- * Validates contents based on NMTOKENS attribute type.
- */
- class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- // early abort: '' and '0' (strings that convert to false) are invalid
- if (!$string) {
- return false;
- }
- $tokens = $this->split($string, $config, $context);
- $tokens = $this->filter($tokens, $config, $context);
- if (empty($tokens)) {
- return false;
- }
- return implode(' ', $tokens);
- }
- /**
- * Splits a space separated list of tokens into its constituent parts.
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- protected function split($string, $config, $context)
- {
- // OPTIMIZABLE!
- // do the preg_match, capture all subpatterns for reformulation
- // we don't support U+00A1 and up codepoints or
- // escaping because I don't know how to do that with regexps
- // and plus it would complicate optimization efforts (you never
- // see that anyway).
- $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start
- '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' .
- '(?:(?=\s)|\z)/'; // look ahead for space or string end
- preg_match_all($pattern, $string, $matches);
- return $matches[1];
- }
- /**
- * Template method for removing certain tokens based on arbitrary criteria.
- * @note If we wanted to be really functional, we'd do an array_filter
- * with a callback. But... we're not.
- * @param array $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- protected function filter($tokens, $config, $context)
- {
- return $tokens;
- }
- }
- /**
- * Implements special behavior for class attribute (normally NMTOKENS)
- */
- class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- protected function split($string, $config, $context)
- {
- // really, this twiddle should be lazy loaded
- $name = $config->getDefinition('HTML')->doctype->name;
- if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
- return parent::split($string, $config, $context);
- } else {
- return preg_split('/\s+/', $string);
- }
- }
- /**
- * @param array $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- protected function filter($tokens, $config, $context)
- {
- $allowed = $config->get('Attr.AllowedClasses');
- $forbidden = $config->get('Attr.ForbiddenClasses');
- $ret = array();
- foreach ($tokens as $token) {
- if (($allowed === null || isset($allowed[$token])) &&
- !isset($forbidden[$token]) &&
- // We need this O(n) check because of PHP's array
- // implementation that casts -0 to 0.
- !in_array($token, $ret, true)
- ) {
- $ret[] = $token;
- }
- }
- return $ret;
- }
- }
- /**
- * Validates a color according to the HTML spec.
- */
- class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- static $colors = null;
- if ($colors === null) {
- $colors = $config->get('Core.ColorKeywords');
- }
- $string = trim($string);
- if (empty($string)) {
- return false;
- }
- $lower = strtolower($string);
- if (isset($colors[$lower])) {
- return $colors[$lower];
- }
- if ($string[0] === '#') {
- $hex = substr($string, 1);
- } else {
- $hex = $string;
- }
- $length = strlen($hex);
- if ($length !== 3 && $length !== 6) {
- return false;
- }
- if (!ctype_xdigit($hex)) {
- return false;
- }
- if ($length === 3) {
- $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
- }
- return "#$hex";
- }
- }
- /**
- * Special-case enum attribute definition that lazy loads allowed frame targets
- */
- class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
- {
- /**
- * @type array
- */
- public $valid_values = false; // uninitialized value
- /**
- * @type bool
- */
- protected $case_sensitive = false;
- public function __construct()
- {
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- if ($this->valid_values === false) {
- $this->valid_values = $config->get('Attr.AllowedFrameTargets');
- }
- return parent::validate($string, $config, $context);
- }
- }
- /**
- * Validates the HTML attribute ID.
- * @warning Even though this is the id processor, it
- * will ignore the directive Attr:IDBlacklist, since it will only
- * go according to the ID accumulator. Since the accumulator is
- * automatically generated, it will have already absorbed the
- * blacklist. If you're hacking around, make sure you use load()!
- */
- class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
- {
- // selector is NOT a valid thing to use for IDREFs, because IDREFs
- // *must* target IDs that exist, whereas selector #ids do not.
- /**
- * Determines whether or not we're validating an ID in a CSS
- * selector context.
- * @type bool
- */
- protected $selector;
- /**
- * @param bool $selector
- */
- public function __construct($selector = false)
- {
- $this->selector = $selector;
- }
- /**
- * @param string $id
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($id, $config, $context)
- {
- if (!$this->selector && !$config->get('Attr.EnableID')) {
- return false;
- }
- $id = trim($id); // trim it first
- if ($id === '') {
- return false;
- }
- $prefix = $config->get('Attr.IDPrefix');
- if ($prefix !== '') {
- $prefix .= $config->get('Attr.IDPrefixLocal');
- // prevent re-appending the prefix
- if (strpos($id, $prefix) !== 0) {
- $id = $prefix . $id;
- }
- } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
- trigger_error(
- '%Attr.IDPrefixLocal cannot be used unless ' .
- '%Attr.IDPrefix is set',
- E_USER_WARNING
- );
- }
- if (!$this->selector) {
- $id_accumulator =& $context->get('IDAccumulator');
- if (isset($id_accumulator->ids[$id])) {
- return false;
- }
- }
- // we purposely avoid using regex, hopefully this is faster
- if (ctype_alpha($id)) {
- $result = true;
- } else {
- if (!ctype_alpha(@$id[0])) {
- return false;
- }
- // primitive style of regexps, I suppose
- $trim = trim(
- $id,
- 'A..Za..z0..9:-._'
- );
- $result = ($trim === '');
- }
- $regexp = $config->get('Attr.IDBlacklistRegexp');
- if ($regexp && preg_match($regexp, $id)) {
- return false;
- }
- if (!$this->selector && $result) {
- $id_accumulator->add($id);
- }
- // if no change was made to the ID, return the result
- // else, return the new id if stripping whitespace made it
- // valid, or return false.
- return $result ? $id : false;
- }
- }
- /**
- * Validates an integer representation of pixels according to the HTML spec.
- */
- class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
- {
- /**
- * @type int
- */
- protected $max;
- /**
- * @param int $max
- */
- public function __construct($max = null)
- {
- $this->max = $max;
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- if ($string === '0') {
- return $string;
- }
- if ($string === '') {
- return false;
- }
- $length = strlen($string);
- if (substr($string, $length - 2) == 'px') {
- $string = substr($string, 0, $length - 2);
- }
- if (!is_numeric($string)) {
- return false;
- }
- $int = (int)$string;
- if ($int < 0) {
- return '0';
- }
- // upper-bound value, extremely high values can
- // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
- // WARNING, above link WILL crash you if you're using Windows
- if ($this->max !== null && $int > $this->max) {
- return (string)$this->max;
- }
- return (string)$int;
- }
- /**
- * @param string $string
- * @return HTMLPurifier_AttrDef
- */
- public function make($string)
- {
- if ($string === '') {
- $max = null;
- } else {
- $max = (int)$string;
- }
- $class = get_class($this);
- return new $class($max);
- }
- }
- /**
- * Validates the HTML type length (not to be confused with CSS's length).
- *
- * This accepts integer pixels or percentages as lengths for certain
- * HTML attributes.
- */
- class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- if ($string === '') {
- return false;
- }
- $parent_result = parent::validate($string, $config, $context);
- if ($parent_result !== false) {
- return $parent_result;
- }
- $length = strlen($string);
- $last_char = $string[$length - 1];
- if ($last_char !== '%') {
- return false;
- }
- $points = substr($string, 0, $length - 1);
- if (!is_numeric($points)) {
- return false;
- }
- $points = (int)$points;
- if ($points < 0) {
- return '0%';
- }
- if ($points > 100) {
- return '100%';
- }
- return ((string)$points) . '%';
- }
- }
- /**
- * Validates a rel/rev link attribute against a directive of allowed values
- * @note We cannot use Enum because link types allow multiple
- * values.
- * @note Assumes link types are ASCII text
- */
- class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
- {
- /**
- * Name config attribute to pull.
- * @type string
- */
- protected $name;
- /**
- * @param string $name
- */
- public function __construct($name)
- {
- $configLookup = array(
- 'rel' => 'AllowedRel',
- 'rev' => 'AllowedRev'
- );
- if (!isset($configLookup[$name])) {
- trigger_error(
- 'Unrecognized attribute name for link ' .
- 'relationship.',
- E_USER_ERROR
- );
- return;
- }
- $this->name = $configLookup[$name];
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $allowed = $config->get('Attr.' . $this->name);
- if (empty($allowed)) {
- return false;
- }
- $string = $this->parseCDATA($string);
- $parts = explode(' ', $string);
- // lookup to prevent duplicates
- $ret_lookup = array();
- foreach ($parts as $part) {
- $part = strtolower(trim($part));
- if (!isset($allowed[$part])) {
- continue;
- }
- $ret_lookup[$part] = true;
- }
- if (empty($ret_lookup)) {
- return false;
- }
- $string = implode(' ', array_keys($ret_lookup));
- return $string;
- }
- }
- /**
- * Validates a MultiLength as defined by the HTML spec.
- *
- * A multilength is either a integer (pixel count), a percentage, or
- * a relative number.
- */
- class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $string = trim($string);
- if ($string === '') {
- return false;
- }
- $parent_result = parent::validate($string, $config, $context);
- if ($parent_result !== false) {
- return $parent_result;
- }
- $length = strlen($string);
- $last_char = $string[$length - 1];
- if ($last_char !== '*') {
- return false;
- }
- $int = substr($string, 0, $length - 1);
- if ($int == '') {
- return '*';
- }
- if (!is_numeric($int)) {
- return false;
- }
- $int = (int)$int;
- if ($int < 0) {
- return false;
- }
- if ($int == 0) {
- return '0';
- }
- if ($int == 1) {
- return '*';
- }
- return ((string)$int) . '*';
- }
- }
- abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
- {
- /**
- * Unpacks a mailbox into its display-name and address
- * @param string $string
- * @return mixed
- */
- public function unpack($string)
- {
- // needs to be implemented
- }
- }
- // sub-implementations
- /**
- * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
- */
- class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
- {
- /**
- * IPv4 sub-validator.
- * @type HTMLPurifier_AttrDef_URI_IPv4
- */
- protected $ipv4;
- /**
- * IPv6 sub-validator.
- * @type HTMLPurifier_AttrDef_URI_IPv6
- */
- protected $ipv6;
- public function __construct()
- {
- $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
- $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
- }
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- $length = strlen($string);
- // empty hostname is OK; it's usually semantically equivalent:
- // the default host as defined by a URI scheme is used:
- //
- // If the URI scheme defines a default for host, then that
- // default applies when the host subcomponent is undefined
- // or when the registered name is empty (zero length).
- if ($string === '') {
- return '';
- }
- if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') {
- //IPv6
- $ip = substr($string, 1, $length - 2);
- $valid = $this->ipv6->validate($ip, $config, $context);
- if ($valid === false) {
- return false;
- }
- return '[' . $valid . ']';
- }
- // need to do checks on unusual encodings too
- $ipv4 = $this->ipv4->validate($string, $config, $context);
- if ($ipv4 !== false) {
- return $ipv4;
- }
- // A regular domain name.
- // This doesn't match I18N domain names, but we don't have proper IRI support,
- // so force users to insert Punycode.
- // There is not a good sense in which underscores should be
- // allowed, since it's technically not! (And if you go as
- // far to allow everything as specified by the DNS spec...
- // well, that's literally everything, modulo some space limits
- // for the components and the overall name (which, by the way,
- // we are NOT checking!). So we (arbitrarily) decide this:
- // let's allow underscores wherever we would have allowed
- // hyphens, if they are enabled. This is a pretty good match
- // for browser behavior, for example, a large number of browsers
- // cannot handle foo_.example.com, but foo_bar.example.com is
- // fairly well supported.
- $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : '';
- // The productions describing this are:
- $a = '[a-z]'; // alpha
- $an = '[a-z0-9]'; // alphanum
- $and = "[a-z0-9-$underscore]"; // alphanum | "-"
- // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
- $domainlabel = "$an($and*$an)?";
- // toplabel = alpha | alpha *( alphanum | "-" ) alphanum
- $toplabel = "$a($and*$an)?";
- // hostname = *( domainlabel "." ) toplabel [ "." ]
- if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
- return $string;
- }
- // If we have Net_IDNA2 support, we can support IRIs by
- // punycoding them. (This is the most portable thing to do,
- // since otherwise we have to assume browsers support
- if ($config->get('Core.EnableIDNA')) {
- $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
- // we need to encode each period separately
- $parts = explode('.', $string);
- try {
- $new_parts = array();
- foreach ($parts as $part) {
- $encodable = false;
- for ($i = 0, $c = strlen($part); $i < $c; $i++) {
- if (ord($part[$i]) > 0x7a) {
- $encodable = true;
- break;
- }
- }
- if (!$encodable) {
- $new_parts[] = $part;
- } else {
- $new_parts[] = $idna->encode($part);
- }
- }
- $string = implode('.', $new_parts);
- if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
- return $string;
- }
- } catch (Exception $e) {
- // XXX error reporting
- }
- }
- return false;
- }
- }
- /**
- * Validates an IPv4 address
- * @author Feyd @ forums.devnetwork.net (public domain)
- */
- class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
- {
- /**
- * IPv4 regex, protected so that IPv6 can reuse it.
- * @type string
- */
- protected $ip4;
- /**
- * @param string $aIP
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($aIP, $config, $context)
- {
- if (!$this->ip4) {
- $this->_loadRegex();
- }
- if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) {
- return $aIP;
- }
- return false;
- }
- /**
- * Lazy load function to prevent regex from being stuffed in
- * cache.
- */
- protected function _loadRegex()
- {
- $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
- $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
- }
- }
- /**
- * Validates an IPv6 address.
- * @author Feyd @ forums.devnetwork.net (public domain)
- * @note This function requires brackets to have been removed from address
- * in URI.
- */
- class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
- {
- /**
- * @param string $aIP
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($aIP, $config, $context)
- {
- if (!$this->ip4) {
- $this->_loadRegex();
- }
- $original = $aIP;
- $hex = '[0-9a-fA-F]';
- $blk = '(?:' . $hex . '{1,4})';
- $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
- // prefix check
- if (strpos($aIP, '/') !== false) {
- if (preg_match('#' . $pre . '$#s', $aIP, $find)) {
- $aIP = substr($aIP, 0, 0 - strlen($find[0]));
- unset($find);
- } else {
- return false;
- }
- }
- // IPv4-compatiblity check
- if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) {
- $aIP = substr($aIP, 0, 0 - strlen($find[0]));
- $ip = explode('.', $find[0]);
- $ip = array_map('dechex', $ip);
- $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
- unset($find, $ip);
- }
- // compression check
- $aIP = explode('::', $aIP);
- $c = count($aIP);
- if ($c > 2) {
- return false;
- } elseif ($c == 2) {
- list($first, $second) = $aIP;
- $first = explode(':', $first);
- $second = explode(':', $second);
- if (count($first) + count($second) > 8) {
- return false;
- }
- while (count($first) < 8) {
- array_push($first, '0');
- }
- array_splice($first, 8 - count($second), 8, $second);
- $aIP = $first;
- unset($first, $second);
- } else {
- $aIP = explode(':', $aIP[0]);
- }
- $c = count($aIP);
- if ($c != 8) {
- return false;
- }
- // All the pieces should be 16-bit hex strings. Are they?
- foreach ($aIP as $piece) {
- if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) {
- return false;
- }
- }
- return $original;
- }
- }
- /**
- * Primitive email validation class based on the regexp found at
- * http://www.regular-expressions.info/email.html
- */
- class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
- {
- /**
- * @param string $string
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool|string
- */
- public function validate($string, $config, $context)
- {
- // no support for named mailboxes i.e. "Bob <bob@example.com>"
- // that needs more percent encoding to be done
- if ($string == '') {
- return false;
- }
- $string = trim($string);
- $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
- return $result ? $string : false;
- }
- }
- /**
- * Pre-transform that changes proprietary background attribute to CSS.
- */
- class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['background'])) {
- return $attr;
- }
- $background = $this->confiscateAttr($attr, 'background');
- // some validation should happen here
- $this->prependCSS($attr, "background-image:url($background);");
- return $attr;
- }
- }
- // this MUST be placed in post, as it assumes that any value in dir is valid
- /**
- * Post-trasnform that ensures that bdo tags have the dir attribute set.
- */
- class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (isset($attr['dir'])) {
- return $attr;
- }
- $attr['dir'] = $config->get('Attr.DefaultTextDir');
- return $attr;
- }
- }
- /**
- * Pre-transform that changes deprecated bgcolor attribute to CSS.
- */
- class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['bgcolor'])) {
- return $attr;
- }
- $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
- // some validation should happen here
- $this->prependCSS($attr, "background-color:$bgcolor;");
- return $attr;
- }
- }
- /**
- * Pre-transform that changes converts a boolean attribute to fixed CSS
- */
- class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform
- {
- /**
- * Name of boolean attribute that is trigger.
- * @type string
- */
- protected $attr;
- /**
- * CSS declarations to add to style, needs trailing semicolon.
- * @type string
- */
- protected $css;
- /**
- * @param string $attr attribute name to convert from
- * @param string $css CSS declarations to add to style (needs semicolon)
- */
- public function __construct($attr, $css)
- {
- $this->attr = $attr;
- $this->css = $css;
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr[$this->attr])) {
- return $attr;
- }
- unset($attr[$this->attr]);
- $this->prependCSS($attr, $this->css);
- return $attr;
- }
- }
- /**
- * Pre-transform that changes deprecated border attribute to CSS.
- */
- class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['border'])) {
- return $attr;
- }
- $border_width = $this->confiscateAttr($attr, 'border');
- // some validation should happen here
- $this->prependCSS($attr, "border:{$border_width}px solid;");
- return $attr;
- }
- }
- /**
- * Generic pre-transform that converts an attribute with a fixed number of
- * values (enumerated) to CSS.
- */
- class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform
- {
- /**
- * Name of attribute to transform from.
- * @type string
- */
- protected $attr;
- /**
- * Lookup array of attribute values to CSS.
- * @type array
- */
- protected $enumToCSS = array();
- /**
- * Case sensitivity of the matching.
- * @type bool
- * @warning Currently can only be guaranteed to work with ASCII
- * values.
- */
- protected $caseSensitive = false;
- /**
- * @param string $attr Attribute name to transform from
- * @param array $enum_to_css Lookup array of attribute values to CSS
- * @param bool $case_sensitive Case sensitivity indicator, default false
- */
- public function __construct($attr, $enum_to_css, $case_sensitive = false)
- {
- $this->attr = $attr;
- $this->enumToCSS = $enum_to_css;
- $this->caseSensitive = (bool)$case_sensitive;
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr[$this->attr])) {
- return $attr;
- }
- $value = trim($attr[$this->attr]);
- unset($attr[$this->attr]);
- if (!$this->caseSensitive) {
- $value = strtolower($value);
- }
- if (!isset($this->enumToCSS[$value])) {
- return $attr;
- }
- $this->prependCSS($attr, $this->enumToCSS[$value]);
- return $attr;
- }
- }
- // must be called POST validation
- /**
- * Transform that supplies default values for the src and alt attributes
- * in img tags, as well as prevents the img tag from being removed
- * because of a missing alt tag. This needs to be registered as both
- * a pre and post attribute transform.
- */
- class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- $src = true;
- if (!isset($attr['src'])) {
- if ($config->get('Core.RemoveInvalidImg')) {
- return $attr;
- }
- $attr['src'] = $config->get('Attr.DefaultInvalidImage');
- $src = false;
- }
- if (!isset($attr['alt'])) {
- if ($src) {
- $alt = $config->get('Attr.DefaultImageAlt');
- if ($alt === null) {
- // truncate if the alt is too long
- $attr['alt'] = substr(basename($attr['src']), 0, 40);
- } else {
- $attr['alt'] = $alt;
- }
- } else {
- $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
- }
- }
- return $attr;
- }
- }
- /**
- * Pre-transform that changes deprecated hspace and vspace attributes to CSS
- */
- class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform
- {
- /**
- * @type string
- */
- protected $attr;
- /**
- * @type array
- */
- protected $css = array(
- 'hspace' => array('left', 'right'),
- 'vspace' => array('top', 'bottom')
- );
- /**
- * @param string $attr
- */
- public function __construct($attr)
- {
- $this->attr = $attr;
- if (!isset($this->css[$attr])) {
- trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
- }
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr[$this->attr])) {
- return $attr;
- }
- $width = $this->confiscateAttr($attr, $this->attr);
- // some validation could happen here
- if (!isset($this->css[$this->attr])) {
- return $attr;
- }
- $style = '';
- foreach ($this->css[$this->attr] as $suffix) {
- $property = "margin-$suffix";
- $style .= "$property:{$width}px;";
- }
- $this->prependCSS($attr, $style);
- return $attr;
- }
- }
- /**
- * Performs miscellaneous cross attribute validation and filtering for
- * input elements. This is meant to be a post-transform.
- */
- class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform
- {
- /**
- * @type HTMLPurifier_AttrDef_HTML_Pixels
- */
- protected $pixels;
- public function __construct()
- {
- $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['type'])) {
- $t = 'text';
- } else {
- $t = strtolower($attr['type']);
- }
- if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
- unset($attr['checked']);
- }
- if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
- unset($attr['maxlength']);
- }
- if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
- $result = $this->pixels->validate($attr['size'], $config, $context);
- if ($result === false) {
- unset($attr['size']);
- } else {
- $attr['size'] = $result;
- }
- }
- if (isset($attr['src']) && $t !== 'image') {
- unset($attr['src']);
- }
- if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
- $attr['value'] = '';
- }
- return $attr;
- }
- }
- /**
- * Post-transform that copies lang's value to xml:lang (and vice-versa)
- * @note Theoretically speaking, this could be a pre-transform, but putting
- * post is more efficient.
- */
- class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- $lang = isset($attr['lang']) ? $attr['lang'] : false;
- $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
- if ($lang !== false && $xml_lang === false) {
- $attr['xml:lang'] = $lang;
- } elseif ($xml_lang !== false) {
- $attr['lang'] = $xml_lang;
- }
- return $attr;
- }
- }
- /**
- * Class for handling width/height length attribute transformations to CSS
- */
- class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
- {
- /**
- * @type string
- */
- protected $name;
- /**
- * @type string
- */
- protected $cssName;
- public function __construct($name, $css_name = null)
- {
- $this->name = $name;
- $this->cssName = $css_name ? $css_name : $name;
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr[$this->name])) {
- return $attr;
- }
- $length = $this->confiscateAttr($attr, $this->name);
- if (ctype_digit($length)) {
- $length .= 'px';
- }
- $this->prependCSS($attr, $this->cssName . ":$length;");
- return $attr;
- }
- }
- /**
- * Pre-transform that changes deprecated name attribute to ID if necessary
- */
- class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- // Abort early if we're using relaxed definition of name
- if ($config->get('HTML.Attr.Name.UseCDATA')) {
- return $attr;
- }
- if (!isset($attr['name'])) {
- return $attr;
- }
- $id = $this->confiscateAttr($attr, 'name');
- if (isset($attr['id'])) {
- return $attr;
- }
- $attr['id'] = $id;
- return $attr;
- }
- }
- /**
- * Post-transform that performs validation to the name attribute; if
- * it is present with an equivalent id attribute, it is passed through;
- * otherwise validation is performed.
- */
- class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
- {
- public function __construct()
- {
- $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['name'])) {
- return $attr;
- }
- $name = $attr['name'];
- if (isset($attr['id']) && $attr['id'] === $name) {
- return $attr;
- }
- $result = $this->idDef->validate($name, $config, $context);
- if ($result === false) {
- unset($attr['name']);
- } else {
- $attr['name'] = $result;
- }
- return $attr;
- }
- }
- // must be called POST validation
- /**
- * Adds rel="nofollow" to all outbound links. This transform is
- * only attached if Attr.Nofollow is TRUE.
- */
- class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
- {
- /**
- * @type HTMLPurifier_URIParser
- */
- private $parser;
- public function __construct()
- {
- $this->parser = new HTMLPurifier_URIParser();
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['href'])) {
- return $attr;
- }
- // XXX Kind of inefficient
- $url = $this->parser->parse($attr['href']);
- $scheme = $url->getSchemeObj($config, $context);
- if ($scheme->browsable && !$url->isLocal($config, $context)) {
- if (isset($attr['rel'])) {
- $rels = explode(' ', $attr['rel']);
- if (!in_array('nofollow', $rels)) {
- $rels[] = 'nofollow';
- }
- $attr['rel'] = implode(' ', $rels);
- } else {
- $attr['rel'] = 'nofollow';
- }
- }
- return $attr;
- }
- }
- class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
- {
- /**
- * @type string
- */
- public $name = "SafeEmbed";
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- $attr['allowscriptaccess'] = 'never';
- $attr['allownetworking'] = 'internal';
- $attr['type'] = 'application/x-shockwave-flash';
- return $attr;
- }
- }
- /**
- * Writes default type for all objects. Currently only supports flash.
- */
- class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
- {
- /**
- * @type string
- */
- public $name = "SafeObject";
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['type'])) {
- $attr['type'] = 'application/x-shockwave-flash';
- }
- return $attr;
- }
- }
- /**
- * Validates name/value pairs in param tags to be used in safe objects. This
- * will only allow name values it recognizes, and pre-fill certain attributes
- * with required values.
- *
- * @note
- * This class only supports Flash. In the future, Quicktime support
- * may be added.
- *
- * @warning
- * This class expects an injector to add the necessary parameters tags.
- */
- class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
- {
- /**
- * @type string
- */
- public $name = "SafeParam";
- /**
- * @type HTMLPurifier_AttrDef_URI
- */
- private $uri;
- public function __construct()
- {
- $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
- $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- // If we add support for other objects, we'll need to alter the
- // transforms.
- switch ($attr['name']) {
- // application/x-shockwave-flash
- // Keep this synchronized with Injector/SafeObject.php
- case 'allowScriptAccess':
- $attr['value'] = 'never';
- break;
- case 'allowNetworking':
- $attr['value'] = 'internal';
- break;
- case 'allowFullScreen':
- if ($config->get('HTML.FlashAllowFullScreen')) {
- $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
- } else {
- $attr['value'] = 'false';
- }
- break;
- case 'wmode':
- $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
- break;
- case 'movie':
- case 'src':
- $attr['name'] = "movie";
- $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
- break;
- case 'flashvars':
- // we're going to allow arbitrary inputs to the SWF, on
- // the reasoning that it could only hack the SWF, not us.
- break;
- // add other cases to support other param name/value pairs
- default:
- $attr['name'] = $attr['value'] = null;
- }
- return $attr;
- }
- }
- /**
- * Implements required attribute stipulation for <script>
- */
- class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['type'])) {
- $attr['type'] = 'text/javascript';
- }
- return $attr;
- }
- }
- // must be called POST validation
- /**
- * Adds target="blank" to all outbound links. This transform is
- * only attached if Attr.TargetBlank is TRUE. This works regardless
- * of whether or not Attr.AllowedFrameTargets
- */
- class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
- {
- /**
- * @type HTMLPurifier_URIParser
- */
- private $parser;
- public function __construct()
- {
- $this->parser = new HTMLPurifier_URIParser();
- }
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- if (!isset($attr['href'])) {
- return $attr;
- }
- // XXX Kind of inefficient
- $url = $this->parser->parse($attr['href']);
- $scheme = $url->getSchemeObj($config, $context);
- if ($scheme->browsable && !$url->isBenign($config, $context)) {
- $attr['target'] = '_blank';
- }
- return $attr;
- }
- }
- /**
- * Sets height/width defaults for <textarea>
- */
- class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
- {
- /**
- * @param array $attr
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function transform($attr, $config, $context)
- {
- // Calculated from Firefox
- if (!isset($attr['cols'])) {
- $attr['cols'] = '22';
- }
- if (!isset($attr['rows'])) {
- $attr['rows'] = '3';
- }
- return $attr;
- }
- }
- /**
- * Definition that uses different definitions depending on context.
- *
- * The del and ins tags are notable because they allow different types of
- * elements depending on whether or not they're in a block or inline context.
- * Chameleon allows this behavior to happen by using two different
- * definitions depending on context. While this somewhat generalized,
- * it is specifically intended for those two tags.
- */
- class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
- {
- /**
- * Instance of the definition object to use when inline. Usually stricter.
- * @type HTMLPurifier_ChildDef_Optional
- */
- public $inline;
- /**
- * Instance of the definition object to use when block.
- * @type HTMLPurifier_ChildDef_Optional
- */
- public $block;
- /**
- * @type string
- */
- public $type = 'chameleon';
- /**
- * @param array $inline List of elements to allow when inline.
- * @param array $block List of elements to allow when block.
- */
- public function __construct($inline, $block)
- {
- $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
- $this->block = new HTMLPurifier_ChildDef_Optional($block);
- $this->elements = $this->block->elements;
- }
- /**
- * @param HTMLPurifier_Node[] $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function validateChildren($children, $config, $context)
- {
- if ($context->get('IsInline') === false) {
- return $this->block->validateChildren(
- $children,
- $config,
- $context
- );
- } else {
- return $this->inline->validateChildren(
- $children,
- $config,
- $context
- );
- }
- }
- }
- /**
- * Custom validation class, accepts DTD child definitions
- *
- * @warning Currently this class is an all or nothing proposition, that is,
- * it will only give a bool return value.
- */
- class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
- {
- /**
- * @type string
- */
- public $type = 'custom';
- /**
- * @type bool
- */
- public $allow_empty = false;
- /**
- * Allowed child pattern as defined by the DTD.
- * @type string
- */
- public $dtd_regex;
- /**
- * PCRE regex derived from $dtd_regex.
- * @type string
- */
- private $_pcre_regex;
- /**
- * @param $dtd_regex Allowed child pattern from the DTD
- */
- public function __construct($dtd_regex)
- {
- $this->dtd_regex = $dtd_regex;
- $this->_compileRegex();
- }
- /**
- * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
- */
- protected function _compileRegex()
- {
- $raw = str_replace(' ', '', $this->dtd_regex);
- if ($raw{0} != '(') {
- $raw = "($raw)";
- }
- $el = '[#a-zA-Z0-9_.-]+';
- $reg = $raw;
- // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
- // DOING! Seriously: if there's problems, please report them.
- // collect all elements into the $elements array
- preg_match_all("/$el/", $reg, $matches);
- foreach ($matches[0] as $match) {
- $this->elements[$match] = true;
- }
- // setup all elements as parentheticals with leading commas
- $reg = preg_replace("/$el/", '(,\\0)', $reg);
- // remove commas when they were not solicited
- $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
- // remove all non-paranthetical commas: they are handled by first regex
- $reg = preg_replace("/,\(/", '(', $reg);
- $this->_pcre_regex = $reg;
- }
- /**
- * @param HTMLPurifier_Node[] $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function validateChildren($children, $config, $context)
- {
- $list_of_children = '';
- $nesting = 0; // depth into the nest
- foreach ($children as $node) {
- if (!empty($node->is_whitespace)) {
- continue;
- }
- $list_of_children .= $node->name . ',';
- }
- // add leading comma to deal with stray comma declarations
- $list_of_children = ',' . rtrim($list_of_children, ',');
- $okay =
- preg_match(
- '/^,?' . $this->_pcre_regex . '$/',
- $list_of_children
- );
- return (bool)$okay;
- }
- }
- /**
- * Definition that disallows all elements.
- * @warning validateChildren() in this class is actually never called, because
- * empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
- * before child definitions are parsed in earnest by
- * HTMLPurifier_Strategy_FixNesting.
- */
- class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
- {
- /**
- * @type bool
- */
- public $allow_empty = true;
- /**
- * @type string
- */
- public $type = 'empty';
- public function __construct()
- {
- }
- /**
- * @param HTMLPurifier_Node[] $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- return array();
- }
- }
- /**
- * Definition for list containers ul and ol.
- *
- * What does this do? The big thing is to handle ol/ul at the top
- * level of list nodes, which should be handled specially by /folding/
- * them into the previous list node. We generally shouldn't ever
- * see other disallowed elements, because the autoclose behavior
- * in MakeWellFormed handles it.
- */
- class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
- {
- /**
- * @type string
- */
- public $type = 'list';
- /**
- * @type array
- */
- // lying a little bit, so that we can handle ul and ol ourselves
- // XXX: This whole business with 'wrap' is all a bit unsatisfactory
- public $elements = array('li' => true, 'ul' => true, 'ol' => true);
- /**
- * @param array $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- // Flag for subclasses
- $this->whitespace = false;
- // if there are no tokens, delete parent node
- if (empty($children)) {
- return false;
- }
- // the new set of children
- $result = array();
- // a little sanity check to make sure it's not ALL whitespace
- $all_whitespace = true;
- $current_li = false;
- foreach ($children as $node) {
- if (!empty($node->is_whitespace)) {
- $result[] = $node;
- continue;
- }
- $all_whitespace = false; // phew, we're not talking about whitespace
- if ($node->name === 'li') {
- // good
- $current_li = $node;
- $result[] = $node;
- } else {
- // we want to tuck this into the previous li
- // Invariant: we expect the node to be ol/ul
- // ToDo: Make this more robust in the case of not ol/ul
- // by distinguishing between existing li and li created
- // to handle non-list elements; non-list elements should
- // not be appended to an existing li; only li created
- // for non-list. This distinction is not currently made.
- if ($current_li === false) {
- $current_li = new HTMLPurifier_Node_Element('li');
- $result[] = $current_li;
- }
- $current_li->children[] = $node;
- $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
- }
- }
- if (empty($result)) {
- return false;
- }
- if ($all_whitespace) {
- return false;
- }
- return $result;
- }
- }
- /**
- * Definition that allows a set of elements, but disallows empty children.
- */
- class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
- {
- /**
- * Lookup table of allowed elements.
- * @type array
- */
- public $elements = array();
- /**
- * Whether or not the last passed node was all whitespace.
- * @type bool
- */
- protected $whitespace = false;
- /**
- * @param array|string $elements List of allowed element names (lowercase).
- */
- public function __construct($elements)
- {
- if (is_string($elements)) {
- $elements = str_replace(' ', '', $elements);
- $elements = explode('|', $elements);
- }
- $keys = array_keys($elements);
- if ($keys == array_keys($keys)) {
- $elements = array_flip($elements);
- foreach ($elements as $i => $x) {
- $elements[$i] = true;
- if (empty($i)) {
- unset($elements[$i]);
- } // remove blank
- }
- }
- $this->elements = $elements;
- }
- /**
- * @type bool
- */
- public $allow_empty = false;
- /**
- * @type string
- */
- public $type = 'required';
- /**
- * @param array $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- // Flag for subclasses
- $this->whitespace = false;
- // if there are no tokens, delete parent node
- if (empty($children)) {
- return false;
- }
- // the new set of children
- $result = array();
- // whether or not parsed character data is allowed
- // this controls whether or not we silently drop a tag
- // or generate escaped HTML from it
- $pcdata_allowed = isset($this->elements['#PCDATA']);
- // a little sanity check to make sure it's not ALL whitespace
- $all_whitespace = true;
- $stack = array_reverse($children);
- while (!empty($stack)) {
- $node = array_pop($stack);
- if (!empty($node->is_whitespace)) {
- $result[] = $node;
- continue;
- }
- $all_whitespace = false; // phew, we're not talking about whitespace
- if (!isset($this->elements[$node->name])) {
- // special case text
- // XXX One of these ought to be redundant or something
- if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
- $result[] = $node;
- continue;
- }
- // spill the child contents in
- // ToDo: Make configurable
- if ($node instanceof HTMLPurifier_Node_Element) {
- for ($i = count($node->children) - 1; $i >= 0; $i--) {
- $stack[] = $node->children[$i];
- }
- continue;
- }
- continue;
- }
- $result[] = $node;
- }
- if (empty($result)) {
- return false;
- }
- if ($all_whitespace) {
- $this->whitespace = true;
- return false;
- }
- return $result;
- }
- }
- /**
- * Definition that allows a set of elements, and allows no children.
- * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
- * really, one shouldn't inherit from the other. Only altered behavior
- * is to overload a returned false with an array. Thus, it will never
- * return false.
- */
- class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
- {
- /**
- * @type bool
- */
- public $allow_empty = true;
- /**
- * @type string
- */
- public $type = 'optional';
- /**
- * @param array $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- $result = parent::validateChildren($children, $config, $context);
- // we assume that $children is not modified
- if ($result === false) {
- if (empty($children)) {
- return true;
- } elseif ($this->whitespace) {
- return $children;
- } else {
- return array();
- }
- }
- return $result;
- }
- }
- /**
- * Takes the contents of blockquote when in strict and reformats for validation.
- */
- class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
- {
- /**
- * @type array
- */
- protected $real_elements;
- /**
- * @type array
- */
- protected $fake_elements;
- /**
- * @type bool
- */
- public $allow_empty = true;
- /**
- * @type string
- */
- public $type = 'strictblockquote';
- /**
- * @type bool
- */
- protected $init = false;
- /**
- * @param HTMLPurifier_Config $config
- * @return array
- * @note We don't want MakeWellFormed to auto-close inline elements since
- * they might be allowed.
- */
- public function getAllowedElements($config)
- {
- $this->init($config);
- return $this->fake_elements;
- }
- /**
- * @param array $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- $this->init($config);
- // trick the parent class into thinking it allows more
- $this->elements = $this->fake_elements;
- $result = parent::validateChildren($children, $config, $context);
- $this->elements = $this->real_elements;
- if ($result === false) {
- return array();
- }
- if ($result === true) {
- $result = $children;
- }
- $def = $config->getHTMLDefinition();
- $block_wrap_name = $def->info_block_wrapper;
- $block_wrap = false;
- $ret = array();
- foreach ($result as $node) {
- if ($block_wrap === false) {
- if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
- ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
- $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
- $ret[] = $block_wrap;
- }
- } else {
- if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
- $block_wrap = false;
- }
- }
- if ($block_wrap) {
- $block_wrap->children[] = $node;
- } else {
- $ret[] = $node;
- }
- }
- return $ret;
- }
- /**
- * @param HTMLPurifier_Config $config
- */
- private function init($config)
- {
- if (!$this->init) {
- $def = $config->getHTMLDefinition();
- // allow all inline elements
- $this->real_elements = $this->elements;
- $this->fake_elements = $def->info_content_sets['Flow'];
- $this->fake_elements['#PCDATA'] = true;
- $this->init = true;
- }
- }
- }
- /**
- * Definition for tables. The general idea is to extract out all of the
- * essential bits, and then reconstruct it later.
- *
- * This is a bit confusing, because the DTDs and the W3C
- * validators seem to disagree on the appropriate definition. The
- * DTD claims:
- *
- * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
- *
- * But actually, the HTML4 spec then has this to say:
- *
- * The TBODY start tag is always required except when the table
- * contains only one table body and no table head or foot sections.
- * The TBODY end tag may always be safely omitted.
- *
- * So the DTD is kind of wrong. The validator is, unfortunately, kind
- * of on crack.
- *
- * The definition changed again in XHTML1.1; and in my opinion, this
- * formulation makes the most sense.
- *
- * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
- *
- * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
- * If we encounter a thead, tfoot or tbody, we are placed in the former
- * mode, and we *must* wrap any stray tr segments with a tbody. But if
- * we don't run into any of them, just have tr tags is OK.
- */
- class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
- {
- /**
- * @type bool
- */
- public $allow_empty = false;
- /**
- * @type string
- */
- public $type = 'table';
- /**
- * @type array
- */
- public $elements = array(
- 'tr' => true,
- 'tbody' => true,
- 'thead' => true,
- 'tfoot' => true,
- 'caption' => true,
- 'colgroup' => true,
- 'col' => true
- );
- public function __construct()
- {
- }
- /**
- * @param array $children
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array
- */
- public function validateChildren($children, $config, $context)
- {
- if (empty($children)) {
- return false;
- }
- // only one of these elements is allowed in a table
- $caption = false;
- $thead = false;
- $tfoot = false;
- // whitespace
- $initial_ws = array();
- $after_caption_ws = array();
- $after_thead_ws = array();
- $after_tfoot_ws = array();
- // as many of these as you want
- $cols = array();
- $content = array();
- $tbody_mode = false; // if true, then we need to wrap any stray
- // <tr>s with a <tbody>.
- $ws_accum =& $initial_ws;
- foreach ($children as $node) {
- if ($node instanceof HTMLPurifier_Node_Comment) {
- $ws_accum[] = $node;
- continue;
- }
- switch ($node->name) {
- case 'tbody':
- $tbody_mode = true;
- // fall through
- case 'tr':
- $content[] = $node;
- $ws_accum =& $content;
- break;
- case 'caption':
- // there can only be one caption!
- if ($caption !== false) break;
- $caption = $node;
- $ws_accum =& $after_caption_ws;
- break;
- case 'thead':
- $tbody_mode = true;
- // XXX This breaks rendering properties with
- // Firefox, which never floats a <thead> to
- // the top. Ever. (Our scheme will float the
- // first <thead> to the top.) So maybe
- // <thead>s that are not first should be
- // turned into <tbody>? Very tricky, indeed.
- if ($thead === false) {
- $thead = $node;
- $ws_accum =& $after_thead_ws;
- } else {
- // Oops, there's a second one! What
- // should we do? Current behavior is to
- // transmutate the first and last entries into
- // tbody tags, and then put into content.
- // Maybe a better idea is to *attach
- // it* to the existing thead or tfoot?
- // We don't do this, because Firefox
- // doesn't float an extra tfoot to the
- // bottom like it does for the first one.
- $node->name = 'tbody';
- $content[] = $node;
- $ws_accum =& $content;
- }
- break;
- case 'tfoot':
- // see above for some aveats
- $tbody_mode = true;
- if ($tfoot === false) {
- $tfoot = $node;
- $ws_accum =& $after_tfoot_ws;
- } else {
- $node->name = 'tbody';
- $content[] = $node;
- $ws_accum =& $content;
- }
- break;
- case 'colgroup':
- case 'col':
- $cols[] = $node;
- $ws_accum =& $cols;
- break;
- case '#PCDATA':
- // How is whitespace handled? We treat is as sticky to
- // the *end* of the previous element. So all of the
- // nonsense we have worked on is to keep things
- // together.
- if (!empty($node->is_whitespace)) {
- $ws_accum[] = $node;
- }
- break;
- }
- }
- if (empty($content)) {
- return false;
- }
- $ret = $initial_ws;
- if ($caption !== false) {
- $ret[] = $caption;
- $ret = array_merge($ret, $after_caption_ws);
- }
- if ($cols !== false) {
- $ret = array_merge($ret, $cols);
- }
- if ($thead !== false) {
- $ret[] = $thead;
- $ret = array_merge($ret, $after_thead_ws);
- }
- if ($tfoot !== false) {
- $ret[] = $tfoot;
- $ret = array_merge($ret, $after_tfoot_ws);
- }
- if ($tbody_mode) {
- // we have to shuffle tr into tbody
- $current_tr_tbody = null;
- foreach($content as $node) {
- switch ($node->name) {
- case 'tbody':
- $current_tr_tbody = null;
- $ret[] = $node;
- break;
- case 'tr':
- if ($current_tr_tbody === null) {
- $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
- $ret[] = $current_tr_tbody;
- }
- $current_tr_tbody->children[] = $node;
- break;
- case '#PCDATA':
- assert($node->is_whitespace);
- if ($current_tr_tbody === null) {
- $ret[] = $node;
- } else {
- $current_tr_tbody->children[] = $node;
- }
- break;
- }
- }
- } else {
- $ret = array_merge($ret, $content);
- }
- return $ret;
- }
- }
- class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
- {
- /**
- * Cache object we are decorating
- * @type HTMLPurifier_DefinitionCache
- */
- public $cache;
- /**
- * The name of the decorator
- * @var string
- */
- public $name;
- public function __construct()
- {
- }
- /**
- * Lazy decorator function
- * @param HTMLPurifier_DefinitionCache $cache Reference to cache object to decorate
- * @return HTMLPurifier_DefinitionCache_Decorator
- */
- public function decorate(&$cache)
- {
- $decorator = $this->copy();
- // reference is necessary for mocks in PHP 4
- $decorator->cache =& $cache;
- $decorator->type = $cache->type;
- return $decorator;
- }
- /**
- * Cross-compatible clone substitute
- * @return HTMLPurifier_DefinitionCache_Decorator
- */
- public function copy()
- {
- return new HTMLPurifier_DefinitionCache_Decorator();
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function add($def, $config)
- {
- return $this->cache->add($def, $config);
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function set($def, $config)
- {
- return $this->cache->set($def, $config);
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function replace($def, $config)
- {
- return $this->cache->replace($def, $config);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function get($config)
- {
- return $this->cache->get($config);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function remove($config)
- {
- return $this->cache->remove($config);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function flush($config)
- {
- return $this->cache->flush($config);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function cleanup($config)
- {
- return $this->cache->cleanup($config);
- }
- }
- /**
- * Null cache object to use when no caching is on.
- */
- class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
- {
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function add($def, $config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function set($def, $config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function replace($def, $config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function remove($config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function get($config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function flush($config)
- {
- return false;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function cleanup($config)
- {
- return false;
- }
- }
- class HTMLPurifier_DefinitionCache_Serializer extends HTMLPurifier_DefinitionCache
- {
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return int|bool
- */
- public function add($def, $config)
- {
- if (!$this->checkDefType($def)) {
- return;
- }
- $file = $this->generateFilePath($config);
- if (file_exists($file)) {
- return false;
- }
- if (!$this->_prepareDir($config)) {
- return false;
- }
- return $this->_write($file, serialize($def), $config);
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return int|bool
- */
- public function set($def, $config)
- {
- if (!$this->checkDefType($def)) {
- return;
- }
- $file = $this->generateFilePath($config);
- if (!$this->_prepareDir($config)) {
- return false;
- }
- return $this->_write($file, serialize($def), $config);
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return int|bool
- */
- public function replace($def, $config)
- {
- if (!$this->checkDefType($def)) {
- return;
- }
- $file = $this->generateFilePath($config);
- if (!file_exists($file)) {
- return false;
- }
- if (!$this->_prepareDir($config)) {
- return false;
- }
- return $this->_write($file, serialize($def), $config);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool|HTMLPurifier_Config
- */
- public function get($config)
- {
- $file = $this->generateFilePath($config);
- if (!file_exists($file)) {
- return false;
- }
- return unserialize(file_get_contents($file));
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function remove($config)
- {
- $file = $this->generateFilePath($config);
- if (!file_exists($file)) {
- return false;
- }
- return unlink($file);
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function flush($config)
- {
- if (!$this->_prepareDir($config)) {
- return false;
- }
- $dir = $this->generateDirectoryPath($config);
- $dh = opendir($dir);
- while (false !== ($filename = readdir($dh))) {
- if (empty($filename)) {
- continue;
- }
- if ($filename[0] === '.') {
- continue;
- }
- unlink($dir . '/' . $filename);
- }
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function cleanup($config)
- {
- if (!$this->_prepareDir($config)) {
- return false;
- }
- $dir = $this->generateDirectoryPath($config);
- $dh = opendir($dir);
- while (false !== ($filename = readdir($dh))) {
- if (empty($filename)) {
- continue;
- }
- if ($filename[0] === '.') {
- continue;
- }
- $key = substr($filename, 0, strlen($filename) - 4);
- if ($this->isOld($key, $config)) {
- unlink($dir . '/' . $filename);
- }
- }
- }
- /**
- * Generates the file path to the serial file corresponding to
- * the configuration and definition name
- * @param HTMLPurifier_Config $config
- * @return string
- * @todo Make protected
- */
- public function generateFilePath($config)
- {
- $key = $this->generateKey($config);
- return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
- }
- /**
- * Generates the path to the directory contain this cache's serial files
- * @param HTMLPurifier_Config $config
- * @return string
- * @note No trailing slash
- * @todo Make protected
- */
- public function generateDirectoryPath($config)
- {
- $base = $this->generateBaseDirectoryPath($config);
- return $base . '/' . $this->type;
- }
- /**
- * Generates path to base directory that contains all definition type
- * serials
- * @param HTMLPurifier_Config $config
- * @return mixed|string
- * @todo Make protected
- */
- public function generateBaseDirectoryPath($config)
- {
- $base = $config->get('Cache.SerializerPath');
- $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
- return $base;
- }
- /**
- * Convenience wrapper function for file_put_contents
- * @param string $file File name to write to
- * @param string $data Data to write into file
- * @param HTMLPurifier_Config $config
- * @return int|bool Number of bytes written if success, or false if failure.
- */
- private function _write($file, $data, $config)
- {
- $result = file_put_contents($file, $data);
- if ($result !== false) {
- // set permissions of the new file (no execute)
- $chmod = $config->get('Cache.SerializerPermissions');
- if (!$chmod) {
- $chmod = 0644; // invalid config or simpletest
- }
- $chmod = $chmod & 0666;
- chmod($file, $chmod);
- }
- return $result;
- }
- /**
- * Prepares the directory that this type stores the serials in
- * @param HTMLPurifier_Config $config
- * @return bool True if successful
- */
- private function _prepareDir($config)
- {
- $directory = $this->generateDirectoryPath($config);
- $chmod = $config->get('Cache.SerializerPermissions');
- if (!$chmod) {
- $chmod = 0755; // invalid config or simpletest
- }
- if (!is_dir($directory)) {
- $base = $this->generateBaseDirectoryPath($config);
- if (!is_dir($base)) {
- trigger_error(
- 'Base directory ' . $base . ' does not exist,
- please create or change using %Cache.SerializerPath',
- E_USER_WARNING
- );
- return false;
- } elseif (!$this->_testPermissions($base, $chmod)) {
- return false;
- }
- $old = umask(0000);
- mkdir($directory, $chmod);
- umask($old);
- } elseif (!$this->_testPermissions($directory, $chmod)) {
- return false;
- }
- return true;
- }
- /**
- * Tests permissions on a directory and throws out friendly
- * error messages and attempts to chmod it itself if possible
- * @param string $dir Directory path
- * @param int $chmod Permissions
- * @return bool True if directory is writable
- */
- private function _testPermissions($dir, $chmod)
- {
- // early abort, if it is writable, everything is hunky-dory
- if (is_writable($dir)) {
- return true;
- }
- if (!is_dir($dir)) {
- // generally, you'll want to handle this beforehand
- // so a more specific error message can be given
- trigger_error(
- 'Directory ' . $dir . ' does not exist',
- E_USER_WARNING
- );
- return false;
- }
- if (function_exists('posix_getuid')) {
- // POSIX system, we can give more specific advice
- if (fileowner($dir) === posix_getuid()) {
- // we can chmod it ourselves
- $chmod = $chmod | 0700;
- if (chmod($dir, $chmod)) {
- return true;
- }
- } elseif (filegroup($dir) === posix_getgid()) {
- $chmod = $chmod | 0070;
- } else {
- // PHP's probably running as nobody, so we'll
- // need to give global permissions
- $chmod = $chmod | 0777;
- }
- trigger_error(
- 'Directory ' . $dir . ' not writable, ' .
- 'please chmod to ' . decoct($chmod),
- E_USER_WARNING
- );
- } else {
- // generic error message
- trigger_error(
- 'Directory ' . $dir . ' not writable, ' .
- 'please alter file permissions',
- E_USER_WARNING
- );
- }
- return false;
- }
- }
- /**
- * Definition cache decorator class that cleans up the cache
- * whenever there is a cache miss.
- */
- class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends HTMLPurifier_DefinitionCache_Decorator
- {
- /**
- * @type string
- */
- public $name = 'Cleanup';
- /**
- * @return HTMLPurifier_DefinitionCache_Decorator_Cleanup
- */
- public function copy()
- {
- return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function add($def, $config)
- {
- $status = parent::add($def, $config);
- if (!$status) {
- parent::cleanup($config);
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function set($def, $config)
- {
- $status = parent::set($def, $config);
- if (!$status) {
- parent::cleanup($config);
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function replace($def, $config)
- {
- $status = parent::replace($def, $config);
- if (!$status) {
- parent::cleanup($config);
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function get($config)
- {
- $ret = parent::get($config);
- if (!$ret) {
- parent::cleanup($config);
- }
- return $ret;
- }
- }
- /**
- * Definition cache decorator class that saves all cache retrievals
- * to PHP's memory; good for unit tests or circumstances where
- * there are lots of configuration objects floating around.
- */
- class HTMLPurifier_DefinitionCache_Decorator_Memory extends HTMLPurifier_DefinitionCache_Decorator
- {
- /**
- * @type array
- */
- protected $definitions;
- /**
- * @type string
- */
- public $name = 'Memory';
- /**
- * @return HTMLPurifier_DefinitionCache_Decorator_Memory
- */
- public function copy()
- {
- return new HTMLPurifier_DefinitionCache_Decorator_Memory();
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function add($def, $config)
- {
- $status = parent::add($def, $config);
- if ($status) {
- $this->definitions[$this->generateKey($config)] = $def;
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function set($def, $config)
- {
- $status = parent::set($def, $config);
- if ($status) {
- $this->definitions[$this->generateKey($config)] = $def;
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Definition $def
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function replace($def, $config)
- {
- $status = parent::replace($def, $config);
- if ($status) {
- $this->definitions[$this->generateKey($config)] = $def;
- }
- return $status;
- }
- /**
- * @param HTMLPurifier_Config $config
- * @return mixed
- */
- public function get($config)
- {
- $key = $this->generateKey($config);
- if (isset($this->definitions[$key])) {
- return $this->definitions[$key];
- }
- $this->definitions[$key] = parent::get($config);
- return $this->definitions[$key];
- }
- }
- /**
- * XHTML 1.1 Bi-directional Text Module, defines elements that
- * declare directionality of content. Text Extension Module.
- */
- class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Bdo';
- /**
- * @type array
- */
- public $attr_collections = array(
- 'I18N' => array('dir' => false)
- );
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $bdo = $this->addElement(
- 'bdo',
- 'Inline',
- 'Inline',
- array('Core', 'Lang'),
- array(
- 'dir' => 'Enum#ltr,rtl', // required
- // The Abstract Module specification has the attribute
- // inclusions wrong for bdo: bdo allows Lang
- )
- );
- $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
- $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
- }
- }
- class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'CommonAttributes';
- /**
- * @type array
- */
- public $attr_collections = array(
- 'Core' => array(
- 0 => array('Style'),
- // 'xml:space' => false,
- 'class' => 'Class',
- 'id' => 'ID',
- 'title' => 'CDATA',
- ),
- 'Lang' => array(),
- 'I18N' => array(
- 0 => array('Lang'), // proprietary, for xml:lang/lang
- ),
- 'Common' => array(
- 0 => array('Core', 'I18N')
- )
- );
- }
- /**
- * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
- * Module.
- */
- class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Edit';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
- $attr = array(
- 'cite' => 'URI',
- // 'datetime' => 'Datetime', // not implemented
- );
- $this->addElement('del', 'Inline', $contents, 'Common', $attr);
- $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
- }
- // HTML 4.01 specifies that ins/del must not contain block
- // elements when used in an inline context, chameleon is
- // a complicated workaround to acheive this effect
- // Inline context ! Block context (exclamation mark is
- // separator, see getChildDef for parsing)
- /**
- * @type bool
- */
- public $defines_child_def = true;
- /**
- * @param HTMLPurifier_ElementDef $def
- * @return HTMLPurifier_ChildDef_Chameleon
- */
- public function getChildDef($def)
- {
- if ($def->content_model_type != 'chameleon') {
- return false;
- }
- $value = explode('!', $def->content_model);
- return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
- }
- }
- /**
- * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
- */
- class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Forms';
- /**
- * @type bool
- */
- public $safe = false;
- /**
- * @type array
- */
- public $content_sets = array(
- 'Block' => 'Form',
- 'Inline' => 'Formctrl',
- );
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $form = $this->addElement(
- 'form',
- 'Form',
- 'Required: Heading | List | Block | fieldset',
- 'Common',
- array(
- 'accept' => 'ContentTypes',
- 'accept-charset' => 'Charsets',
- 'action*' => 'URI',
- 'method' => 'Enum#get,post',
- // really ContentType, but these two are the only ones used today
- 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
- )
- );
- $form->excludes = array('form' => true);
- $input = $this->addElement(
- 'input',
- 'Formctrl',
- 'Empty',
- 'Common',
- array(
- 'accept' => 'ContentTypes',
- 'accesskey' => 'Character',
- 'alt' => 'Text',
- 'checked' => 'Bool#checked',
- 'disabled' => 'Bool#disabled',
- 'maxlength' => 'Number',
- 'name' => 'CDATA',
- 'readonly' => 'Bool#readonly',
- 'size' => 'Number',
- 'src' => 'URI#embedded',
- 'tabindex' => 'Number',
- 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
- 'value' => 'CDATA',
- )
- );
- $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
- $this->addElement(
- 'select',
- 'Formctrl',
- 'Required: optgroup | option',
- 'Common',
- array(
- 'disabled' => 'Bool#disabled',
- 'multiple' => 'Bool#multiple',
- 'name' => 'CDATA',
- 'size' => 'Number',
- 'tabindex' => 'Number',
- )
- );
- $this->addElement(
- 'option',
- false,
- 'Optional: #PCDATA',
- 'Common',
- array(
- 'disabled' => 'Bool#disabled',
- 'label' => 'Text',
- 'selected' => 'Bool#selected',
- 'value' => 'CDATA',
- )
- );
- // It's illegal for there to be more than one selected, but not
- // be multiple. Also, no selected means undefined behavior. This might
- // be difficult to implement; perhaps an injector, or a context variable.
- $textarea = $this->addElement(
- 'textarea',
- 'Formctrl',
- 'Optional: #PCDATA',
- 'Common',
- array(
- 'accesskey' => 'Character',
- 'cols*' => 'Number',
- 'disabled' => 'Bool#disabled',
- 'name' => 'CDATA',
- 'readonly' => 'Bool#readonly',
- 'rows*' => 'Number',
- 'tabindex' => 'Number',
- )
- );
- $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
- $button = $this->addElement(
- 'button',
- 'Formctrl',
- 'Optional: #PCDATA | Heading | List | Block | Inline',
- 'Common',
- array(
- 'accesskey' => 'Character',
- 'disabled' => 'Bool#disabled',
- 'name' => 'CDATA',
- 'tabindex' => 'Number',
- 'type' => 'Enum#button,submit,reset',
- 'value' => 'CDATA',
- )
- );
- // For exclusions, ideally we'd specify content sets, not literal elements
- $button->excludes = $this->makeLookup(
- 'form',
- 'fieldset', // Form
- 'input',
- 'select',
- 'textarea',
- 'label',
- 'button', // Formctrl
- 'a', // as per HTML 4.01 spec, this is omitted by modularization
- 'isindex',
- 'iframe' // legacy items
- );
- // Extra exclusion: img usemap="" is not permitted within this element.
- // We'll omit this for now, since we don't have any good way of
- // indicating it yet.
- // This is HIGHLY user-unfriendly; we need a custom child-def for this
- $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
- $label = $this->addElement(
- 'label',
- 'Formctrl',
- 'Optional: #PCDATA | Inline',
- 'Common',
- array(
- 'accesskey' => 'Character',
- // 'for' => 'IDREF', // IDREF not implemented, cannot allow
- )
- );
- $label->excludes = array('label' => true);
- $this->addElement(
- 'legend',
- false,
- 'Optional: #PCDATA | Inline',
- 'Common',
- array(
- 'accesskey' => 'Character',
- )
- );
- $this->addElement(
- 'optgroup',
- false,
- 'Required: option',
- 'Common',
- array(
- 'disabled' => 'Bool#disabled',
- 'label*' => 'Text',
- )
- );
- // Don't forget an injector for <isindex>. This one's a little complex
- // because it maps to multiple elements.
- }
- }
- /**
- * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
- */
- class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Hypertext';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $a = $this->addElement(
- 'a',
- 'Inline',
- 'Inline',
- 'Common',
- array(
- // 'accesskey' => 'Character',
- // 'charset' => 'Charset',
- 'href' => 'URI',
- // 'hreflang' => 'LanguageCode',
- 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
- 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
- // 'tabindex' => 'Number',
- // 'type' => 'ContentType',
- )
- );
- $a->formatting = true;
- $a->excludes = array('a' => true);
- }
- }
- /**
- * XHTML 1.1 Iframe Module provides inline frames.
- *
- * @note This module is not considered safe unless an Iframe
- * whitelisting mechanism is specified. Currently, the only
- * such mechanism is %URL.SafeIframeRegexp
- */
- class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Iframe';
- /**
- * @type bool
- */
- public $safe = false;
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- if ($config->get('HTML.SafeIframe')) {
- $this->safe = true;
- }
- $this->addElement(
- 'iframe',
- 'Inline',
- 'Flow',
- 'Common',
- array(
- 'src' => 'URI#embedded',
- 'width' => 'Length',
- 'height' => 'Length',
- 'name' => 'ID',
- 'scrolling' => 'Enum#yes,no,auto',
- 'frameborder' => 'Enum#0,1',
- 'longdesc' => 'URI',
- 'marginheight' => 'Pixels',
- 'marginwidth' => 'Pixels',
- )
- );
- }
- }
- /**
- * XHTML 1.1 Image Module provides basic image embedding.
- * @note There is specialized code for removing empty images in
- * HTMLPurifier_Strategy_RemoveForeignElements
- */
- class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Image';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $max = $config->get('HTML.MaxImgLength');
- $img = $this->addElement(
- 'img',
- 'Inline',
- 'Empty',
- 'Common',
- array(
- 'alt*' => 'Text',
- // According to the spec, it's Length, but percents can
- // be abused, so we allow only Pixels.
- 'height' => 'Pixels#' . $max,
- 'width' => 'Pixels#' . $max,
- 'longdesc' => 'URI',
- 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
- )
- );
- if ($max === null || $config->get('HTML.Trusted')) {
- $img->attr['height'] =
- $img->attr['width'] = 'Length';
- }
- // kind of strange, but splitting things up would be inefficient
- $img->attr_transform_pre[] =
- $img->attr_transform_post[] =
- new HTMLPurifier_AttrTransform_ImgRequired();
- }
- }
- /**
- * XHTML 1.1 Legacy module defines elements that were previously
- * deprecated.
- *
- * @note Not all legacy elements have been implemented yet, which
- * is a bit of a reverse problem as compared to browsers! In
- * addition, this legacy module may implement a bit more than
- * mandated by XHTML 1.1.
- *
- * This module can be used in combination with TransformToStrict in order
- * to transform as many deprecated elements as possible, but retain
- * questionably deprecated elements that do not have good alternatives
- * as well as transform elements that don't have an implementation.
- * See docs/ref-strictness.txt for more details.
- */
- class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Legacy';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement(
- 'basefont',
- 'Inline',
- 'Empty',
- null,
- array(
- 'color' => 'Color',
- 'face' => 'Text', // extremely broad, we should
- 'size' => 'Text', // tighten it
- 'id' => 'ID'
- )
- );
- $this->addElement('center', 'Block', 'Flow', 'Common');
- $this->addElement(
- 'dir',
- 'Block',
- 'Required: li',
- 'Common',
- array(
- 'compact' => 'Bool#compact'
- )
- );
- $this->addElement(
- 'font',
- 'Inline',
- 'Inline',
- array('Core', 'I18N'),
- array(
- 'color' => 'Color',
- 'face' => 'Text', // extremely broad, we should
- 'size' => 'Text', // tighten it
- )
- );
- $this->addElement(
- 'menu',
- 'Block',
- 'Required: li',
- 'Common',
- array(
- 'compact' => 'Bool#compact'
- )
- );
- $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
- $s->formatting = true;
- $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
- $strike->formatting = true;
- $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
- $u->formatting = true;
- // setup modifications to old elements
- $align = 'Enum#left,right,center,justify';
- $address = $this->addBlankElement('address');
- $address->content_model = 'Inline | #PCDATA | p';
- $address->content_model_type = 'optional';
- $address->child = false;
- $blockquote = $this->addBlankElement('blockquote');
- $blockquote->content_model = 'Flow | #PCDATA';
- $blockquote->content_model_type = 'optional';
- $blockquote->child = false;
- $br = $this->addBlankElement('br');
- $br->attr['clear'] = 'Enum#left,all,right,none';
- $caption = $this->addBlankElement('caption');
- $caption->attr['align'] = 'Enum#top,bottom,left,right';
- $div = $this->addBlankElement('div');
- $div->attr['align'] = $align;
- $dl = $this->addBlankElement('dl');
- $dl->attr['compact'] = 'Bool#compact';
- for ($i = 1; $i <= 6; $i++) {
- $h = $this->addBlankElement("h$i");
- $h->attr['align'] = $align;
- }
- $hr = $this->addBlankElement('hr');
- $hr->attr['align'] = $align;
- $hr->attr['noshade'] = 'Bool#noshade';
- $hr->attr['size'] = 'Pixels';
- $hr->attr['width'] = 'Length';
- $img = $this->addBlankElement('img');
- $img->attr['align'] = 'IAlign';
- $img->attr['border'] = 'Pixels';
- $img->attr['hspace'] = 'Pixels';
- $img->attr['vspace'] = 'Pixels';
- // figure out this integer business
- $li = $this->addBlankElement('li');
- $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
- $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
- $ol = $this->addBlankElement('ol');
- $ol->attr['compact'] = 'Bool#compact';
- $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
- $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
- $p = $this->addBlankElement('p');
- $p->attr['align'] = $align;
- $pre = $this->addBlankElement('pre');
- $pre->attr['width'] = 'Number';
- // script omitted
- $table = $this->addBlankElement('table');
- $table->attr['align'] = 'Enum#left,center,right';
- $table->attr['bgcolor'] = 'Color';
- $tr = $this->addBlankElement('tr');
- $tr->attr['bgcolor'] = 'Color';
- $th = $this->addBlankElement('th');
- $th->attr['bgcolor'] = 'Color';
- $th->attr['height'] = 'Length';
- $th->attr['nowrap'] = 'Bool#nowrap';
- $th->attr['width'] = 'Length';
- $td = $this->addBlankElement('td');
- $td->attr['bgcolor'] = 'Color';
- $td->attr['height'] = 'Length';
- $td->attr['nowrap'] = 'Bool#nowrap';
- $td->attr['width'] = 'Length';
- $ul = $this->addBlankElement('ul');
- $ul->attr['compact'] = 'Bool#compact';
- $ul->attr['type'] = 'Enum#square,disc,circle';
- // "safe" modifications to "unsafe" elements
- // WARNING: If you want to add support for an unsafe, legacy
- // attribute, make a new TrustedLegacy module with the trusted
- // bit set appropriately
- $form = $this->addBlankElement('form');
- $form->content_model = 'Flow | #PCDATA';
- $form->content_model_type = 'optional';
- $form->attr['target'] = 'FrameTarget';
- $input = $this->addBlankElement('input');
- $input->attr['align'] = 'IAlign';
- $legend = $this->addBlankElement('legend');
- $legend->attr['align'] = 'LAlign';
- }
- }
- /**
- * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
- */
- class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'List';
- // According to the abstract schema, the List content set is a fully formed
- // one or more expr, but it invariably occurs in an optional declaration
- // so we're not going to do that subtlety. It might cause trouble
- // if a user defines "List" and expects that multiple lists are
- // allowed to be specified, but then again, that's not very intuitive.
- // Furthermore, the actual XML Schema may disagree. Regardless,
- // we don't have support for such nested expressions without using
- // the incredibly inefficient and draconic Custom ChildDef.
- /**
- * @type array
- */
- public $content_sets = array('Flow' => 'List');
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
- $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
- // XXX The wrap attribute is handled by MakeWellFormed. This is all
- // quite unsatisfactory, because we generated this
- // *specifically* for lists, and now a big chunk of the handling
- // is done properly by the List ChildDef. So actually, we just
- // want enough information to make autoclosing work properly,
- // and then hand off the tricky stuff to the ChildDef.
- $ol->wrap = 'li';
- $ul->wrap = 'li';
- $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
- $this->addElement('li', false, 'Flow', 'Common');
- $this->addElement('dd', false, 'Flow', 'Common');
- $this->addElement('dt', false, 'Inline', 'Common');
- }
- }
- class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Name';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
- foreach ($elements as $name) {
- $element = $this->addBlankElement($name);
- $element->attr['name'] = 'CDATA';
- if (!$config->get('HTML.Attr.Name.UseCDATA')) {
- $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
- }
- }
- }
- }
- /**
- * Module adds the nofollow attribute transformation to a tags. It
- * is enabled by HTML.Nofollow
- */
- class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Nofollow';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $a = $this->addBlankElement('a');
- $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
- }
- }
- class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'NonXMLCommonAttributes';
- /**
- * @type array
- */
- public $attr_collections = array(
- 'Lang' => array(
- 'lang' => 'LanguageCode',
- )
- );
- }
- /**
- * XHTML 1.1 Object Module, defines elements for generic object inclusion
- * @warning Users will commonly use <embed> to cater to legacy browsers: this
- * module does not allow this sort of behavior
- */
- class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Object';
- /**
- * @type bool
- */
- public $safe = false;
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement(
- 'object',
- 'Inline',
- 'Optional: #PCDATA | Flow | param',
- 'Common',
- array(
- 'archive' => 'URI',
- 'classid' => 'URI',
- 'codebase' => 'URI',
- 'codetype' => 'Text',
- 'data' => 'URI',
- 'declare' => 'Bool#declare',
- 'height' => 'Length',
- 'name' => 'CDATA',
- 'standby' => 'Text',
- 'tabindex' => 'Number',
- 'type' => 'ContentType',
- 'width' => 'Length'
- )
- );
- $this->addElement(
- 'param',
- false,
- 'Empty',
- null,
- array(
- 'id' => 'ID',
- 'name*' => 'Text',
- 'type' => 'Text',
- 'value' => 'Text',
- 'valuetype' => 'Enum#data,ref,object'
- )
- );
- }
- }
- /**
- * XHTML 1.1 Presentation Module, defines simple presentation-related
- * markup. Text Extension Module.
- * @note The official XML Schema and DTD specs further divide this into
- * two modules:
- * - Block Presentation (hr)
- * - Inline Presentation (b, big, i, small, sub, sup, tt)
- * We have chosen not to heed this distinction, as content_sets
- * provides satisfactory disambiguation.
- */
- class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Presentation';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement('hr', 'Block', 'Empty', 'Common');
- $this->addElement('sub', 'Inline', 'Inline', 'Common');
- $this->addElement('sup', 'Inline', 'Inline', 'Common');
- $b = $this->addElement('b', 'Inline', 'Inline', 'Common');
- $b->formatting = true;
- $big = $this->addElement('big', 'Inline', 'Inline', 'Common');
- $big->formatting = true;
- $i = $this->addElement('i', 'Inline', 'Inline', 'Common');
- $i->formatting = true;
- $small = $this->addElement('small', 'Inline', 'Inline', 'Common');
- $small->formatting = true;
- $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common');
- $tt->formatting = true;
- }
- }
- /**
- * Module defines proprietary tags and attributes in HTML.
- * @warning If this module is enabled, standards-compliance is off!
- */
- class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Proprietary';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement(
- 'marquee',
- 'Inline',
- 'Flow',
- 'Common',
- array(
- 'direction' => 'Enum#left,right,up,down',
- 'behavior' => 'Enum#alternate',
- 'width' => 'Length',
- 'height' => 'Length',
- 'scrolldelay' => 'Number',
- 'scrollamount' => 'Number',
- 'loop' => 'Number',
- 'bgcolor' => 'Color',
- 'hspace' => 'Pixels',
- 'vspace' => 'Pixels',
- )
- );
- }
- }
- /**
- * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
- * short runs of text alongside base text for annotation or pronounciation.
- */
- class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Ruby';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement(
- 'ruby',
- 'Inline',
- 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
- 'Common'
- );
- $this->addElement('rbc', false, 'Required: rb', 'Common');
- $this->addElement('rtc', false, 'Required: rt', 'Common');
- $rb = $this->addElement('rb', false, 'Inline', 'Common');
- $rb->excludes = array('ruby' => true);
- $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
- $rt->excludes = array('ruby' => true);
- $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
- }
- }
- /**
- * A "safe" embed module. See SafeObject. This is a proprietary element.
- */
- class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'SafeEmbed';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $max = $config->get('HTML.MaxImgLength');
- $embed = $this->addElement(
- 'embed',
- 'Inline',
- 'Empty',
- 'Common',
- array(
- 'src*' => 'URI#embedded',
- 'type' => 'Enum#application/x-shockwave-flash',
- 'width' => 'Pixels#' . $max,
- 'height' => 'Pixels#' . $max,
- 'allowscriptaccess' => 'Enum#never',
- 'allownetworking' => 'Enum#internal',
- 'flashvars' => 'Text',
- 'wmode' => 'Enum#window,transparent,opaque',
- 'name' => 'ID',
- )
- );
- $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
- }
- }
- /**
- * A "safe" object module. In theory, objects permitted by this module will
- * be safe, and untrusted users can be allowed to embed arbitrary flash objects
- * (maybe other types too, but only Flash is supported as of right now).
- * Highly experimental.
- */
- class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'SafeObject';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- // These definitions are not intrinsically safe: the attribute transforms
- // are a vital part of ensuring safety.
- $max = $config->get('HTML.MaxImgLength');
- $object = $this->addElement(
- 'object',
- 'Inline',
- 'Optional: param | Flow | #PCDATA',
- 'Common',
- array(
- // While technically not required by the spec, we're forcing
- // it to this value.
- 'type' => 'Enum#application/x-shockwave-flash',
- 'width' => 'Pixels#' . $max,
- 'height' => 'Pixels#' . $max,
- 'data' => 'URI#embedded',
- 'codebase' => new HTMLPurifier_AttrDef_Enum(
- array(
- 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
- )
- ),
- )
- );
- $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
- $param = $this->addElement(
- 'param',
- false,
- 'Empty',
- false,
- array(
- 'id' => 'ID',
- 'name*' => 'Text',
- 'value' => 'Text'
- )
- );
- $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
- $this->info_injector[] = 'SafeObject';
- }
- }
- /**
- * A "safe" script module. No inline JS is allowed, and pointed to JS
- * files must match whitelist.
- */
- class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'SafeScripting';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- // These definitions are not intrinsically safe: the attribute transforms
- // are a vital part of ensuring safety.
- $allowed = $config->get('HTML.SafeScripting');
- $script = $this->addElement(
- 'script',
- 'Inline',
- 'Empty',
- null,
- array(
- // While technically not required by the spec, we're forcing
- // it to this value.
- 'type' => 'Enum#text/javascript',
- 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
- )
- );
- $script->attr_transform_pre[] =
- $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
- }
- }
- /*
- WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
- INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
- */
- /**
- * XHTML 1.1 Scripting module, defines elements that are used to contain
- * information pertaining to executable scripts or the lack of support
- * for executable scripts.
- * @note This module does not contain inline scripting elements
- */
- class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Scripting';
- /**
- * @type array
- */
- public $elements = array('script', 'noscript');
- /**
- * @type array
- */
- public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
- /**
- * @type bool
- */
- public $safe = false;
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- // TODO: create custom child-definition for noscript that
- // auto-wraps stray #PCDATA in a similar manner to
- // blockquote's custom definition (we would use it but
- // blockquote's contents are optional while noscript's contents
- // are required)
- // TODO: convert this to new syntax, main problem is getting
- // both content sets working
- // In theory, this could be safe, but I don't see any reason to
- // allow it.
- $this->info['noscript'] = new HTMLPurifier_ElementDef();
- $this->info['noscript']->attr = array(0 => array('Common'));
- $this->info['noscript']->content_model = 'Heading | List | Block';
- $this->info['noscript']->content_model_type = 'required';
- $this->info['script'] = new HTMLPurifier_ElementDef();
- $this->info['script']->attr = array(
- 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
- 'src' => new HTMLPurifier_AttrDef_URI(true),
- 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
- );
- $this->info['script']->content_model = '#PCDATA';
- $this->info['script']->content_model_type = 'optional';
- $this->info['script']->attr_transform_pre[] =
- $this->info['script']->attr_transform_post[] =
- new HTMLPurifier_AttrTransform_ScriptRequired();
- }
- }
- /**
- * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
- * Module.
- */
- class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'StyleAttribute';
- /**
- * @type array
- */
- public $attr_collections = array(
- // The inclusion routine differs from the Abstract Modules but
- // is in line with the DTD and XML Schemas.
- 'Style' => array('style' => false), // see constructor
- 'Core' => array(0 => array('Style'))
- );
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
- }
- }
- /**
- * XHTML 1.1 Tables Module, fully defines accessible table elements.
- */
- class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Tables';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $this->addElement('caption', false, 'Inline', 'Common');
- $this->addElement(
- 'table',
- 'Block',
- new HTMLPurifier_ChildDef_Table(),
- 'Common',
- array(
- 'border' => 'Pixels',
- 'cellpadding' => 'Length',
- 'cellspacing' => 'Length',
- 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
- 'rules' => 'Enum#none,groups,rows,cols,all',
- 'summary' => 'Text',
- 'width' => 'Length'
- )
- );
- // common attributes
- $cell_align = array(
- 'align' => 'Enum#left,center,right,justify,char',
- 'charoff' => 'Length',
- 'valign' => 'Enum#top,middle,bottom,baseline',
- );
- $cell_t = array_merge(
- array(
- 'abbr' => 'Text',
- 'colspan' => 'Number',
- 'rowspan' => 'Number',
- // Apparently, as of HTML5 this attribute only applies
- // to 'th' elements.
- 'scope' => 'Enum#row,col,rowgroup,colgroup',
- ),
- $cell_align
- );
- $this->addElement('td', false, 'Flow', 'Common', $cell_t);
- $this->addElement('th', false, 'Flow', 'Common', $cell_t);
- $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
- $cell_col = array_merge(
- array(
- 'span' => 'Number',
- 'width' => 'MultiLength',
- ),
- $cell_align
- );
- $this->addElement('col', false, 'Empty', 'Common', $cell_col);
- $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
- $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
- $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
- $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
- }
- }
- /**
- * XHTML 1.1 Target Module, defines target attribute in link elements.
- */
- class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Target';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $elements = array('a');
- foreach ($elements as $name) {
- $e = $this->addBlankElement($name);
- $e->attr = array(
- 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
- );
- }
- }
- }
- /**
- * Module adds the target=blank attribute transformation to a tags. It
- * is enabled by HTML.TargetBlank
- */
- class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'TargetBlank';
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- $a = $this->addBlankElement('a');
- $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
- }
- }
- /**
- * XHTML 1.1 Text Module, defines basic text containers. Core Module.
- * @note In the normative XML Schema specification, this module
- * is further abstracted into the following modules:
- * - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
- * - Block Structural (div, p)
- * - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
- * - Inline Structural (br, span)
- * This module, functionally, does not distinguish between these
- * sub-modules, but the code is internally structured to reflect
- * these distinctions.
- */
- class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'Text';
- /**
- * @type array
- */
- public $content_sets = array(
- 'Flow' => 'Heading | Block | Inline'
- );
- /**
- * @param HTMLPurifier_Config $config
- */
- public function setup($config)
- {
- // Inline Phrasal -------------------------------------------------
- $this->addElement('abbr', 'Inline', 'Inline', 'Common');
- $this->addElement('acronym', 'Inline', 'Inline', 'Common');
- $this->addElement('cite', 'Inline', 'Inline', 'Common');
- $this->addElement('dfn', 'Inline', 'Inline', 'Common');
- $this->addElement('kbd', 'Inline', 'Inline', 'Common');
- $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI'));
- $this->addElement('samp', 'Inline', 'Inline', 'Common');
- $this->addElement('var', 'Inline', 'Inline', 'Common');
- $em = $this->addElement('em', 'Inline', 'Inline', 'Common');
- $em->formatting = true;
- $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common');
- $strong->formatting = true;
- $code = $this->addElement('code', 'Inline', 'Inline', 'Common');
- $code->formatting = true;
- // Inline Structural ----------------------------------------------
- $this->addElement('span', 'Inline', 'Inline', 'Common');
- $this->addElement('br', 'Inline', 'Empty', 'Core');
- // Block Phrasal --------------------------------------------------
- $this->addElement('address', 'Block', 'Inline', 'Common');
- $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI'));
- $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
- $pre->excludes = $this->makeLookup(
- 'img',
- 'big',
- 'small',
- 'object',
- 'applet',
- 'font',
- 'basefont'
- );
- $this->addElement('h1', 'Heading', 'Inline', 'Common');
- $this->addElement('h2', 'Heading', 'Inline', 'Common');
- $this->addElement('h3', 'Heading', 'Inline', 'Common');
- $this->addElement('h4', 'Heading', 'Inline', 'Common');
- $this->addElement('h5', 'Heading', 'Inline', 'Common');
- $this->addElement('h6', 'Heading', 'Inline', 'Common');
- // Block Structural -----------------------------------------------
- $p = $this->addElement('p', 'Block', 'Inline', 'Common');
- $p->autoclose = array_flip(
- array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul")
- );
- $this->addElement('div', 'Block', 'Flow', 'Common');
- }
- }
- /**
- * Abstract class for a set of proprietary modules that clean up (tidy)
- * poorly written HTML.
- * @todo Figure out how to protect some of these methods/properties
- */
- class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
- {
- /**
- * List of supported levels.
- * Index zero is a special case "no fixes" level.
- * @type array
- */
- public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
- /**
- * Default level to place all fixes in.
- * Disabled by default.
- * @type string
- */
- public $defaultLevel = null;
- /**
- * Lists of fixes used by getFixesForLevel().
- * Format is:
- * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
- * @type array
- */
- public $fixesForLevel = array(
- 'light' => array(),
- 'medium' => array(),
- 'heavy' => array()
- );
- /**
- * Lazy load constructs the module by determining the necessary
- * fixes to create and then delegating to the populate() function.
- * @param HTMLPurifier_Config $config
- * @todo Wildcard matching and error reporting when an added or
- * subtracted fix has no effect.
- */
- public function setup($config)
- {
- // create fixes, initialize fixesForLevel
- $fixes = $this->makeFixes();
- $this->makeFixesForLevel($fixes);
- // figure out which fixes to use
- $level = $config->get('HTML.TidyLevel');
- $fixes_lookup = $this->getFixesForLevel($level);
- // get custom fix declarations: these need namespace processing
- $add_fixes = $config->get('HTML.TidyAdd');
- $remove_fixes = $config->get('HTML.TidyRemove');
- foreach ($fixes as $name => $fix) {
- // needs to be refactored a little to implement globbing
- if (isset($remove_fixes[$name]) ||
- (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) {
- unset($fixes[$name]);
- }
- }
- // populate this module with necessary fixes
- $this->populate($fixes);
- }
- /**
- * Retrieves all fixes per a level, returning fixes for that specific
- * level as well as all levels below it.
- * @param string $level level identifier, see $levels for valid values
- * @return array Lookup up table of fixes
- */
- public function getFixesForLevel($level)
- {
- if ($level == $this->levels[0]) {
- return array();
- }
- $activated_levels = array();
- for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
- $activated_levels[] = $this->levels[$i];
- if ($this->levels[$i] == $level) {
- break;
- }
- }
- if ($i == $c) {
- trigger_error(
- 'Tidy level ' . htmlspecialchars($level) . ' not recognized',
- E_USER_WARNING
- );
- return array();
- }
- $ret = array();
- foreach ($activated_levels as $level) {
- foreach ($this->fixesForLevel[$level] as $fix) {
- $ret[$fix] = true;
- }
- }
- return $ret;
- }
- /**
- * Dynamically populates the $fixesForLevel member variable using
- * the fixes array. It may be custom overloaded, used in conjunction
- * with $defaultLevel, or not used at all.
- * @param array $fixes
- */
- public function makeFixesForLevel($fixes)
- {
- if (!isset($this->defaultLevel)) {
- return;
- }
- if (!isset($this->fixesForLevel[$this->defaultLevel])) {
- trigger_error(
- 'Default level ' . $this->defaultLevel . ' does not exist',
- E_USER_ERROR
- );
- return;
- }
- $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
- }
- /**
- * Populates the module with transforms and other special-case code
- * based on a list of fixes passed to it
- * @param array $fixes Lookup table of fixes to activate
- */
- public function populate($fixes)
- {
- foreach ($fixes as $name => $fix) {
- // determine what the fix is for
- list($type, $params) = $this->getFixType($name);
- switch ($type) {
- case 'attr_transform_pre':
- case 'attr_transform_post':
- $attr = $params['attr'];
- if (isset($params['element'])) {
- $element = $params['element'];
- if (empty($this->info[$element])) {
- $e = $this->addBlankElement($element);
- } else {
- $e = $this->info[$element];
- }
- } else {
- $type = "info_$type";
- $e = $this;
- }
- // PHP does some weird parsing when I do
- // $e->$type[$attr], so I have to assign a ref.
- $f =& $e->$type;
- $f[$attr] = $fix;
- break;
- case 'tag_transform':
- $this->info_tag_transform[$params['element']] = $fix;
- break;
- case 'child':
- case 'content_model_type':
- $element = $params['element'];
- if (empty($this->info[$element])) {
- $e = $this->addBlankElement($element);
- } else {
- $e = $this->info[$element];
- }
- $e->$type = $fix;
- break;
- default:
- trigger_error("Fix type $type not supported", E_USER_ERROR);
- break;
- }
- }
- }
- /**
- * Parses a fix name and determines what kind of fix it is, as well
- * as other information defined by the fix
- * @param $name String name of fix
- * @return array(string $fix_type, array $fix_parameters)
- * @note $fix_parameters is type dependant, see populate() for usage
- * of these parameters
- */
- public function getFixType($name)
- {
- // parse it
- $property = $attr = null;
- if (strpos($name, '#') !== false) {
- list($name, $property) = explode('#', $name);
- }
- if (strpos($name, '@') !== false) {
- list($name, $attr) = explode('@', $name);
- }
- // figure out the parameters
- $params = array();
- if ($name !== '') {
- $params['element'] = $name;
- }
- if (!is_null($attr)) {
- $params['attr'] = $attr;
- }
- // special case: attribute transform
- if (!is_null($attr)) {
- if (is_null($property)) {
- $property = 'pre';
- }
- $type = 'attr_transform_' . $property;
- return array($type, $params);
- }
- // special case: tag transform
- if (is_null($property)) {
- return array('tag_transform', $params);
- }
- return array($property, $params);
- }
- /**
- * Defines all fixes the module will perform in a compact
- * associative array of fix name to fix implementation.
- * @return array
- */
- public function makeFixes()
- {
- }
- }
- class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
- {
- /**
- * @type string
- */
- public $name = 'XMLCommonAttributes';
- /**
- * @type array
- */
- public $attr_collections = array(
- 'Lang' => array(
- 'xml:lang' => 'LanguageCode',
- )
- );
- }
- /**
- * Name is deprecated, but allowed in strict doctypes, so onl
- */
- class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
- {
- /**
- * @type string
- */
- public $name = 'Tidy_Name';
- /**
- * @type string
- */
- public $defaultLevel = 'heavy';
- /**
- * @return array
- */
- public function makeFixes()
- {
- $r = array();
- // @name for img, a -----------------------------------------------
- // Technically, it's allowed even on strict, so we allow authors to use
- // it. However, it's deprecated in future versions of XHTML.
- $r['img@name'] =
- $r['a@name'] = new HTMLPurifier_AttrTransform_Name();
- return $r;
- }
- }
- class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
- {
- /**
- * @type string
- */
- public $name = 'Tidy_Proprietary';
- /**
- * @type string
- */
- public $defaultLevel = 'light';
- /**
- * @return array
- */
- public function makeFixes()
- {
- $r = array();
- $r['table@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['td@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['th@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['tr@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['thead@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['tfoot@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['tbody@background'] = new HTMLPurifier_AttrTransform_Background();
- $r['table@height'] = new HTMLPurifier_AttrTransform_Length('height');
- return $r;
- }
- }
- class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
- {
- /**
- * @return array
- */
- public function makeFixes()
- {
- $r = array();
- // == deprecated tag transforms ===================================
- $r['font'] = new HTMLPurifier_TagTransform_Font();
- $r['menu'] = new HTMLPurifier_TagTransform_Simple('ul');
- $r['dir'] = new HTMLPurifier_TagTransform_Simple('ul');
- $r['center'] = new HTMLPurifier_TagTransform_Simple('div', 'text-align:center;');
- $r['u'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
- $r['s'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
- $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
- // == deprecated attribute transforms =============================
- $r['caption@align'] =
- new HTMLPurifier_AttrTransform_EnumToCSS(
- 'align',
- array(
- // we're following IE's behavior, not Firefox's, due
- // to the fact that no one supports caption-side:right,
- // W3C included (with CSS 2.1). This is a slightly
- // unreasonable attribute!
- 'left' => 'text-align:left;',
- 'right' => 'text-align:right;',
- 'top' => 'caption-side:top;',
- 'bottom' => 'caption-side:bottom;' // not supported by IE
- )
- );
- // @align for img -------------------------------------------------
- $r['img@align'] =
- new HTMLPurifier_AttrTransform_EnumToCSS(
- 'align',
- array(
- 'left' => 'float:left;',
- 'right' => 'float:right;',
- 'top' => 'vertical-align:top;',
- 'middle' => 'vertical-align:middle;',
- 'bottom' => 'vertical-align:baseline;',
- )
- );
- // @align for table -----------------------------------------------
- $r['table@align'] =
- new HTMLPurifier_AttrTransform_EnumToCSS(
- 'align',
- array(
- 'left' => 'float:left;',
- 'center' => 'margin-left:auto;margin-right:auto;',
- 'right' => 'float:right;'
- )
- );
- // @align for hr -----------------------------------------------
- $r['hr@align'] =
- new HTMLPurifier_AttrTransform_EnumToCSS(
- 'align',
- array(
- // we use both text-align and margin because these work
- // for different browsers (IE and Firefox, respectively)
- // and the melange makes for a pretty cross-compatible
- // solution
- 'left' => 'margin-left:0;margin-right:auto;text-align:left;',
- 'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
- 'right' => 'margin-left:auto;margin-right:0;text-align:right;'
- )
- );
- // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
- // {{{
- $align_lookup = array();
- $align_values = array('left', 'right', 'center', 'justify');
- foreach ($align_values as $v) {
- $align_lookup[$v] = "text-align:$v;";
- }
- // }}}
- $r['h1@align'] =
- $r['h2@align'] =
- $r['h3@align'] =
- $r['h4@align'] =
- $r['h5@align'] =
- $r['h6@align'] =
- $r['p@align'] =
- $r['div@align'] =
- new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
- // @bgcolor for table, tr, td, th ---------------------------------
- $r['table@bgcolor'] =
- $r['td@bgcolor'] =
- $r['th@bgcolor'] =
- new HTMLPurifier_AttrTransform_BgColor();
- // @border for img ------------------------------------------------
- $r['img@border'] = new HTMLPurifier_AttrTransform_Border();
- // @clear for br --------------------------------------------------
- $r['br@clear'] =
- new HTMLPurifier_AttrTransform_EnumToCSS(
- 'clear',
- array(
- 'left' => 'clear:left;',
- 'right' => 'clear:right;',
- 'all' => 'clear:both;',
- 'none' => 'clear:none;',
- )
- );
- // @height for td, th ---------------------------------------------
- $r['td@height'] =
- $r['th@height'] =
- new HTMLPurifier_AttrTransform_Length('height');
- // @hspace for img ------------------------------------------------
- $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
- // @noshade for hr ------------------------------------------------
- // this transformation is not precise but often good enough.
- // different browsers use different styles to designate noshade
- $r['hr@noshade'] =
- new HTMLPurifier_AttrTransform_BoolToCSS(
- 'noshade',
- 'color:#808080;background-color:#808080;border:0;'
- );
- // @nowrap for td, th ---------------------------------------------
- $r['td@nowrap'] =
- $r['th@nowrap'] =
- new HTMLPurifier_AttrTransform_BoolToCSS(
- 'nowrap',
- 'white-space:nowrap;'
- );
- // @size for hr --------------------------------------------------
- $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
- // @type for li, ol, ul -------------------------------------------
- // {{{
- $ul_types = array(
- 'disc' => 'list-style-type:disc;',
- 'square' => 'list-style-type:square;',
- 'circle' => 'list-style-type:circle;'
- );
- $ol_types = array(
- '1' => 'list-style-type:decimal;',
- 'i' => 'list-style-type:lower-roman;',
- 'I' => 'list-style-type:upper-roman;',
- 'a' => 'list-style-type:lower-alpha;',
- 'A' => 'list-style-type:upper-alpha;'
- );
- $li_types = $ul_types + $ol_types;
- // }}}
- $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
- $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
- $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
- // @vspace for img ------------------------------------------------
- $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
- // @width for hr, td, th ------------------------------------------
- $r['td@width'] =
- $r['th@width'] =
- $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
- return $r;
- }
- }
- class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
- {
- /**
- * @type string
- */
- public $name = 'Tidy_Strict';
- /**
- * @type string
- */
- public $defaultLevel = 'light';
- /**
- * @return array
- */
- public function makeFixes()
- {
- $r = parent::makeFixes();
- $r['blockquote#content_model_type'] = 'strictblockquote';
- return $r;
- }
- /**
- * @type bool
- */
- public $defines_child_def = true;
- /**
- * @param HTMLPurifier_ElementDef $def
- * @return HTMLPurifier_ChildDef_StrictBlockquote
- */
- public function getChildDef($def)
- {
- if ($def->content_model_type != 'strictblockquote') {
- return parent::getChildDef($def);
- }
- return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
- }
- }
- class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
- {
- /**
- * @type string
- */
- public $name = 'Tidy_Transitional';
- /**
- * @type string
- */
- public $defaultLevel = 'heavy';
- }
- class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
- {
- /**
- * @type string
- */
- public $name = 'Tidy_XHTML';
- /**
- * @type string
- */
- public $defaultLevel = 'medium';
- /**
- * @return array
- */
- public function makeFixes()
- {
- $r = array();
- $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
- return $r;
- }
- }
- /**
- * Injector that auto paragraphs text in the root node based on
- * double-spacing.
- * @todo Ensure all states are unit tested, including variations as well.
- * @todo Make a graph of the flow control for this Injector.
- */
- class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'AutoParagraph';
- /**
- * @type array
- */
- public $needed = array('p');
- /**
- * @return HTMLPurifier_Token_Start
- */
- private function _pStart()
- {
- $par = new HTMLPurifier_Token_Start('p');
- $par->armor['MakeWellFormed_TagClosedError'] = true;
- return $par;
- }
- /**
- * @param HTMLPurifier_Token_Text $token
- */
- public function handleText(&$token)
- {
- $text = $token->data;
- // Does the current parent allow <p> tags?
- if ($this->allowsElement('p')) {
- if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
- // Note that we have differing behavior when dealing with text
- // in the anonymous root node, or a node inside the document.
- // If the text as a double-newline, the treatment is the same;
- // if it doesn't, see the next if-block if you're in the document.
- $i = $nesting = null;
- if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
- // State 1.1: ... ^ (whitespace, then document end)
- // ----
- // This is a degenerate case
- } else {
- if (!$token->is_whitespace || $this->_isInline($current)) {
- // State 1.2: PAR1
- // ----
- // State 1.3: PAR1\n\nPAR2
- // ------------
- // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
- // ------------
- $token = array($this->_pStart());
- $this->_splitText($text, $token);
- } else {
- // State 1.5: \n<hr />
- // --
- }
- }
- } else {
- // State 2: <div>PAR1... (similar to 1.4)
- // ----
- // We're in an element that allows paragraph tags, but we're not
- // sure if we're going to need them.
- if ($this->_pLookAhead()) {
- // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
- // ----
- // Note: This will always be the first child, since any
- // previous inline element would have triggered this very
- // same routine, and found the double newline. One possible
- // exception would be a comment.
- $token = array($this->_pStart(), $token);
- } else {
- // State 2.2.1: <div>PAR1<div>
- // ----
- // State 2.2.2: <div>PAR1<b>PAR1</b></div>
- // ----
- }
- }
- // Is the current parent a <p> tag?
- } elseif (!empty($this->currentNesting) &&
- $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
- // State 3.1: ...<p>PAR1
- // ----
- // State 3.2: ...<p>PAR1\n\nPAR2
- // ------------
- $token = array();
- $this->_splitText($text, $token);
- // Abort!
- } else {
- // State 4.1: ...<b>PAR1
- // ----
- // State 4.2: ...<b>PAR1\n\nPAR2
- // ------------
- }
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleElement(&$token)
- {
- // We don't have to check if we're already in a <p> tag for block
- // tokens, because the tag would have been autoclosed by MakeWellFormed.
- if ($this->allowsElement('p')) {
- if (!empty($this->currentNesting)) {
- if ($this->_isInline($token)) {
- // State 1: <div>...<b>
- // ---
- // Check if this token is adjacent to the parent token
- // (seek backwards until token isn't whitespace)
- $i = null;
- $this->backward($i, $prev);
- if (!$prev instanceof HTMLPurifier_Token_Start) {
- // Token wasn't adjacent
- if ($prev instanceof HTMLPurifier_Token_Text &&
- substr($prev->data, -2) === "\n\n"
- ) {
- // State 1.1.4: <div><p>PAR1</p>\n\n<b>
- // ---
- // Quite frankly, this should be handled by splitText
- $token = array($this->_pStart(), $token);
- } else {
- // State 1.1.1: <div><p>PAR1</p><b>
- // ---
- // State 1.1.2: <div><br /><b>
- // ---
- // State 1.1.3: <div>PAR<b>
- // ---
- }
- } else {
- // State 1.2.1: <div><b>
- // ---
- // Lookahead to see if <p> is needed.
- if ($this->_pLookAhead()) {
- // State 1.3.1: <div><b>PAR1\n\nPAR2
- // ---
- $token = array($this->_pStart(), $token);
- } else {
- // State 1.3.2: <div><b>PAR1</b></div>
- // ---
- // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
- // ---
- }
- }
- } else {
- // State 2.3: ...<div>
- // -----
- }
- } else {
- if ($this->_isInline($token)) {
- // State 3.1: <b>
- // ---
- // This is where the {p} tag is inserted, not reflected in
- // inputTokens yet, however.
- $token = array($this->_pStart(), $token);
- } else {
- // State 3.2: <div>
- // -----
- }
- $i = null;
- if ($this->backward($i, $prev)) {
- if (!$prev instanceof HTMLPurifier_Token_Text) {
- // State 3.1.1: ...</p>{p}<b>
- // ---
- // State 3.2.1: ...</p><div>
- // -----
- if (!is_array($token)) {
- $token = array($token);
- }
- array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
- } else {
- // State 3.1.2: ...</p>\n\n{p}<b>
- // ---
- // State 3.2.2: ...</p>\n\n<div>
- // -----
- // Note: PAR<ELEM> cannot occur because PAR would have been
- // wrapped in <p> tags.
- }
- }
- }
- } else {
- // State 2.2: <ul><li>
- // ----
- // State 2.4: <p><b>
- // ---
- }
- }
- /**
- * Splits up a text in paragraph tokens and appends them
- * to the result stream that will replace the original
- * @param string $data String text data that will be processed
- * into paragraphs
- * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
- * tags will be appended onto
- */
- private function _splitText($data, &$result)
- {
- $raw_paragraphs = explode("\n\n", $data);
- $paragraphs = array(); // without empty paragraphs
- $needs_start = false;
- $needs_end = false;
- $c = count($raw_paragraphs);
- if ($c == 1) {
- // There were no double-newlines, abort quickly. In theory this
- // should never happen.
- $result[] = new HTMLPurifier_Token_Text($data);
- return;
- }
- for ($i = 0; $i < $c; $i++) {
- $par = $raw_paragraphs[$i];
- if (trim($par) !== '') {
- $paragraphs[] = $par;
- } else {
- if ($i == 0) {
- // Double newline at the front
- if (empty($result)) {
- // The empty result indicates that the AutoParagraph
- // injector did not add any start paragraph tokens.
- // This means that we have been in a paragraph for
- // a while, and the newline means we should start a new one.
- $result[] = new HTMLPurifier_Token_End('p');
- $result[] = new HTMLPurifier_Token_Text("\n\n");
- // However, the start token should only be added if
- // there is more processing to be done (i.e. there are
- // real paragraphs in here). If there are none, the
- // next start paragraph tag will be handled by the
- // next call to the injector
- $needs_start = true;
- } else {
- // We just started a new paragraph!
- // Reinstate a double-newline for presentation's sake, since
- // it was in the source code.
- array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
- }
- } elseif ($i + 1 == $c) {
- // Double newline at the end
- // There should be a trailing </p> when we're finally done.
- $needs_end = true;
- }
- }
- }
- // Check if this was just a giant blob of whitespace. Move this earlier,
- // perhaps?
- if (empty($paragraphs)) {
- return;
- }
- // Add the start tag indicated by \n\n at the beginning of $data
- if ($needs_start) {
- $result[] = $this->_pStart();
- }
- // Append the paragraphs onto the result
- foreach ($paragraphs as $par) {
- $result[] = new HTMLPurifier_Token_Text($par);
- $result[] = new HTMLPurifier_Token_End('p');
- $result[] = new HTMLPurifier_Token_Text("\n\n");
- $result[] = $this->_pStart();
- }
- // Remove trailing start token; Injector will handle this later if
- // it was indeed needed. This prevents from needing to do a lookahead,
- // at the cost of a lookbehind later.
- array_pop($result);
- // If there is no need for an end tag, remove all of it and let
- // MakeWellFormed close it later.
- if (!$needs_end) {
- array_pop($result); // removes \n\n
- array_pop($result); // removes </p>
- }
- }
- /**
- * Returns true if passed token is inline (and, ergo, allowed in
- * paragraph tags)
- * @param HTMLPurifier_Token $token
- * @return bool
- */
- private function _isInline($token)
- {
- return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
- }
- /**
- * Looks ahead in the token list and determines whether or not we need
- * to insert a <p> tag.
- * @return bool
- */
- private function _pLookAhead()
- {
- if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
- $nesting = 1;
- } else {
- $nesting = 0;
- }
- $ok = false;
- $i = null;
- while ($this->forwardUntilEndToken($i, $current, $nesting)) {
- $result = $this->_checkNeedsP($current);
- if ($result !== null) {
- $ok = $result;
- break;
- }
- }
- return $ok;
- }
- /**
- * Determines if a particular token requires an earlier inline token
- * to get a paragraph. This should be used with _forwardUntilEndToken
- * @param HTMLPurifier_Token $current
- * @return bool
- */
- private function _checkNeedsP($current)
- {
- if ($current instanceof HTMLPurifier_Token_Start) {
- if (!$this->_isInline($current)) {
- // <div>PAR1<div>
- // ----
- // Terminate early, since we hit a block element
- return false;
- }
- } elseif ($current instanceof HTMLPurifier_Token_Text) {
- if (strpos($current->data, "\n\n") !== false) {
- // <div>PAR1<b>PAR1\n\nPAR2
- // ----
- return true;
- } else {
- // <div>PAR1<b>PAR1...
- // ----
- }
- }
- return null;
- }
- }
- /**
- * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
- */
- class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'DisplayLinkURI';
- /**
- * @type array
- */
- public $needed = array('a');
- /**
- * @param $token
- */
- public function handleElement(&$token)
- {
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleEnd(&$token)
- {
- if (isset($token->start->attr['href'])) {
- $url = $token->start->attr['href'];
- unset($token->start->attr['href']);
- $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
- } else {
- // nothing to display
- }
- }
- }
- /**
- * Injector that converts http, https and ftp text URLs to actual links.
- */
- class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'Linkify';
- /**
- * @type array
- */
- public $needed = array('a' => array('href'));
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleText(&$token)
- {
- if (!$this->allowsElement('a')) {
- return;
- }
- if (strpos($token->data, '://') === false) {
- // our really quick heuristic failed, abort
- // this may not work so well if we want to match things like
- // "google.com", but then again, most people don't
- return;
- }
- // there is/are URL(s). Let's split the string:
- // Note: this regex is extremely permissive
- $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
- $token = array();
- // $i = index
- // $c = count
- // $l = is link
- for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
- if (!$l) {
- if ($bits[$i] === '') {
- continue;
- }
- $token[] = new HTMLPurifier_Token_Text($bits[$i]);
- } else {
- $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
- $token[] = new HTMLPurifier_Token_Text($bits[$i]);
- $token[] = new HTMLPurifier_Token_End('a');
- }
- }
- }
- }
- /**
- * Injector that converts configuration directive syntax %Namespace.Directive
- * to links
- */
- class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'PurifierLinkify';
- /**
- * @type string
- */
- public $docURL;
- /**
- * @type array
- */
- public $needed = array('a' => array('href'));
- /**
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public function prepare($config, $context)
- {
- $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
- return parent::prepare($config, $context);
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleText(&$token)
- {
- if (!$this->allowsElement('a')) {
- return;
- }
- if (strpos($token->data, '%') === false) {
- return;
- }
- $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
- $token = array();
- // $i = index
- // $c = count
- // $l = is link
- for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
- if (!$l) {
- if ($bits[$i] === '') {
- continue;
- }
- $token[] = new HTMLPurifier_Token_Text($bits[$i]);
- } else {
- $token[] = new HTMLPurifier_Token_Start(
- 'a',
- array('href' => str_replace('%s', $bits[$i], $this->docURL))
- );
- $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
- $token[] = new HTMLPurifier_Token_End('a');
- }
- }
- }
- }
- class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
- {
- /**
- * @type HTMLPurifier_Context
- */
- private $context;
- /**
- * @type HTMLPurifier_Config
- */
- private $config;
- /**
- * @type HTMLPurifier_AttrValidator
- */
- private $attrValidator;
- /**
- * @type bool
- */
- private $removeNbsp;
- /**
- * @type bool
- */
- private $removeNbspExceptions;
- /**
- * @type array
- * TODO: make me configurable
- */
- private $_exclude = array('colgroup' => 1, 'th' => 1, 'td' => 1, 'iframe' => 1);
- /**
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return void
- */
- public function prepare($config, $context)
- {
- parent::prepare($config, $context);
- $this->config = $config;
- $this->context = $context;
- $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
- $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
- $this->attrValidator = new HTMLPurifier_AttrValidator();
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleElement(&$token)
- {
- if (!$token instanceof HTMLPurifier_Token_Start) {
- return;
- }
- $next = false;
- $deleted = 1; // the current tag
- for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
- $next = $this->inputZipper->back[$i];
- if ($next instanceof HTMLPurifier_Token_Text) {
- if ($next->is_whitespace) {
- continue;
- }
- if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
- $plain = str_replace("\xC2\xA0", "", $next->data);
- $isWsOrNbsp = $plain === '' || ctype_space($plain);
- if ($isWsOrNbsp) {
- continue;
- }
- }
- }
- break;
- }
- if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
- if (isset($this->_exclude[$token->name])) {
- return;
- }
- $this->attrValidator->validateToken($token, $this->config, $this->context);
- $token->armor['ValidateAttributes'] = true;
- if (isset($token->attr['id']) || isset($token->attr['name'])) {
- return;
- }
- $token = $deleted + 1;
- for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
- $prev = $this->inputZipper->front[$b];
- if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
- continue;
- }
- break;
- }
- // This is safe because we removed the token that triggered this.
- $this->rewindOffset($b+$deleted);
- return;
- }
- }
- }
- /**
- * Injector that removes spans with no attributes
- */
- class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'RemoveSpansWithoutAttributes';
- /**
- * @type array
- */
- public $needed = array('span');
- /**
- * @type HTMLPurifier_AttrValidator
- */
- private $attrValidator;
- /**
- * Used by AttrValidator.
- * @type HTMLPurifier_Config
- */
- private $config;
- /**
- * @type HTMLPurifier_Context
- */
- private $context;
- public function prepare($config, $context)
- {
- $this->attrValidator = new HTMLPurifier_AttrValidator();
- $this->config = $config;
- $this->context = $context;
- return parent::prepare($config, $context);
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleElement(&$token)
- {
- if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
- return;
- }
- // We need to validate the attributes now since this doesn't normally
- // happen until after MakeWellFormed. If all the attributes are removed
- // the span needs to be removed too.
- $this->attrValidator->validateToken($token, $this->config, $this->context);
- $token->armor['ValidateAttributes'] = true;
- if (!empty($token->attr)) {
- return;
- }
- $nesting = 0;
- while ($this->forwardUntilEndToken($i, $current, $nesting)) {
- }
- if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
- // Mark closing span tag for deletion
- $current->markForDeletion = true;
- // Delete open span tag
- $token = false;
- }
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleEnd(&$token)
- {
- if ($token->markForDeletion) {
- $token = false;
- }
- }
- }
- /**
- * Adds important param elements to inside of object in order to make
- * things safe.
- */
- class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
- {
- /**
- * @type string
- */
- public $name = 'SafeObject';
- /**
- * @type array
- */
- public $needed = array('object', 'param');
- /**
- * @type array
- */
- protected $objectStack = array();
- /**
- * @type array
- */
- protected $paramStack = array();
- /**
- * Keep this synchronized with AttrTransform/SafeParam.php.
- * @type array
- */
- protected $addParam = array(
- 'allowScriptAccess' => 'never',
- 'allowNetworking' => 'internal',
- );
- /**
- * @type array
- */
- protected $allowedParam = array(
- 'wmode' => true,
- 'movie' => true,
- 'flashvars' => true,
- 'src' => true,
- 'allowFullScreen' => true, // if omitted, assume to be 'false'
- );
- /**
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return void
- */
- public function prepare($config, $context)
- {
- parent::prepare($config, $context);
- }
- /**
- * @param HTMLPurifier_Token $token
- */
- public function handleElement(&$token)
- {
- if ($token->name == 'object') {
- $this->objectStack[] = $token;
- $this->paramStack[] = array();
- $new = array($token);
- foreach ($this->addParam as $name => $value) {
- $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
- }
- $token = $new;
- } elseif ($token->name == 'param') {
- $nest = count($this->currentNesting) - 1;
- if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
- $i = count($this->objectStack) - 1;
- if (!isset($token->attr['name'])) {
- $token = false;
- return;
- }
- $n = $token->attr['name'];
- // We need this fix because YouTube doesn't supply a data
- // attribute, which we need if a type is specified. This is
- // *very* Flash specific.
- if (!isset($this->objectStack[$i]->attr['data']) &&
- ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')
- ) {
- $this->objectStack[$i]->attr['data'] = $token->attr['value'];
- }
- // Check if the parameter is the correct value but has not
- // already been added
- if (!isset($this->paramStack[$i][$n]) &&
- isset($this->addParam[$n]) &&
- $token->attr['name'] === $this->addParam[$n]) {
- // keep token, and add to param stack
- $this->paramStack[$i][$n] = true;
- } elseif (isset($this->allowedParam[$n])) {
- // keep token, don't do anything to it
- // (could possibly check for duplicates here)
- } else {
- $token = false;
- }
- } else {
- // not directly inside an object, DENY!
- $token = false;
- }
- }
- }
- public function handleEnd(&$token)
- {
- // This is the WRONG way of handling the object and param stacks;
- // we should be inserting them directly on the relevant object tokens
- // so that the global stack handling handles it.
- if ($token->name == 'object') {
- array_pop($this->objectStack);
- array_pop($this->paramStack);
- }
- }
- }
- /**
- * Parser that uses PHP 5's DOM extension (part of the core).
- *
- * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
- * It gives us a forgiving HTML parser, which we use to transform the HTML
- * into a DOM, and then into the tokens. It is blazingly fast (for large
- * documents, it performs twenty times faster than
- * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
- *
- * @note Any empty elements will have empty tokens associated with them, even if
- * this is prohibited by the spec. This is cannot be fixed until the spec
- * comes into play.
- *
- * @note PHP's DOM extension does not actually parse any entities, we use
- * our own function to do that.
- *
- * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
- * If this is a huge problem, due to the fact that HTML is hand
- * edited and you are unable to get a parser cache that caches the
- * the output of HTML Purifier while keeping the original HTML lying
- * around, you may want to run Tidy on the resulting output or use
- * HTMLPurifier_DirectLex
- */
- class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
- {
- /**
- * @type HTMLPurifier_TokenFactory
- */
- private $factory;
- public function __construct()
- {
- // setup the factory
- parent::__construct();
- $this->factory = new HTMLPurifier_TokenFactory();
- }
- /**
- * @param string $html
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[]
- */
- public function tokenizeHTML($html, $config, $context)
- {
- $html = $this->normalize($html, $config, $context);
- // attempt to armor stray angled brackets that cannot possibly
- // form tags and thus are probably being used as emoticons
- if ($config->get('Core.AggressivelyFixLt')) {
- $char = '[^a-z!\/]';
- $comment = "/<!--(.*?)(-->|\z)/is";
- $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
- do {
- $old = $html;
- $html = preg_replace("/<($char)/i", '<\\1', $html);
- } while ($html !== $old);
- $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
- }
- // preprocess html, essential for UTF-8
- $html = $this->wrapHTML($html, $config, $context);
- $doc = new DOMDocument();
- $doc->encoding = 'UTF-8'; // theoretically, the above has this covered
- set_error_handler(array($this, 'muteErrorHandler'));
- $doc->loadHTML($html);
- restore_error_handler();
- $tokens = array();
- $this->tokenizeDOM(
- $doc->getElementsByTagName('html')->item(0)-> // <html>
- getElementsByTagName('body')->item(0)-> // <body>
- getElementsByTagName('div')->item(0), // <div>
- $tokens
- );
- return $tokens;
- }
- /**
- * Iterative function that tokenizes a node, putting it into an accumulator.
- * To iterate is human, to recurse divine - L. Peter Deutsch
- * @param DOMNode $node DOMNode to be tokenized.
- * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
- * @return HTMLPurifier_Token of node appended to previously passed tokens.
- */
- protected function tokenizeDOM($node, &$tokens)
- {
- $level = 0;
- $nodes = array($level => new HTMLPurifier_Queue(array($node)));
- $closingNodes = array();
- do {
- while (!$nodes[$level]->isEmpty()) {
- $node = $nodes[$level]->shift(); // FIFO
- $collect = $level > 0 ? true : false;
- $needEndingTag = $this->createStartNode($node, $tokens, $collect);
- if ($needEndingTag) {
- $closingNodes[$level][] = $node;
- }
- if ($node->childNodes && $node->childNodes->length) {
- $level++;
- $nodes[$level] = new HTMLPurifier_Queue();
- foreach ($node->childNodes as $childNode) {
- $nodes[$level]->push($childNode);
- }
- }
- }
- $level--;
- if ($level && isset($closingNodes[$level])) {
- while ($node = array_pop($closingNodes[$level])) {
- $this->createEndNode($node, $tokens);
- }
- }
- } while ($level > 0);
- }
- /**
- * @param DOMNode $node DOMNode to be tokenized.
- * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens.
- * @param bool $collect Says whether or start and close are collected, set to
- * false at first recursion because it's the implicit DIV
- * tag you're dealing with.
- * @return bool if the token needs an endtoken
- * @todo data and tagName properties don't seem to exist in DOMNode?
- */
- protected function createStartNode($node, &$tokens, $collect)
- {
- // intercept non element nodes. WE MUST catch all of them,
- // but we're not getting the character reference nodes because
- // those should have been preprocessed
- if ($node->nodeType === XML_TEXT_NODE) {
- $tokens[] = $this->factory->createText($node->data);
- return false;
- } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
- // undo libxml's special treatment of <script> and <style> tags
- $last = end($tokens);
- $data = $node->data;
- // (note $node->tagname is already normalized)
- if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
- $new_data = trim($data);
- if (substr($new_data, 0, 4) === '<!--') {
- $data = substr($new_data, 4);
- if (substr($data, -3) === '-->') {
- $data = substr($data, 0, -3);
- } else {
- // Highly suspicious! Not sure what to do...
- }
- }
- }
- $tokens[] = $this->factory->createText($this->parseData($data));
- return false;
- } elseif ($node->nodeType === XML_COMMENT_NODE) {
- // this is code is only invoked for comments in script/style in versions
- // of libxml pre-2.6.28 (regular comments, of course, are still
- // handled regularly)
- $tokens[] = $this->factory->createComment($node->data);
- return false;
- } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
- // not-well tested: there may be other nodes we have to grab
- return false;
- }
- $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
- // We still have to make sure that the element actually IS empty
- if (!$node->childNodes->length) {
- if ($collect) {
- $tokens[] = $this->factory->createEmpty($node->tagName, $attr);
- }
- return false;
- } else {
- if ($collect) {
- $tokens[] = $this->factory->createStart(
- $tag_name = $node->tagName, // somehow, it get's dropped
- $attr
- );
- }
- return true;
- }
- }
- /**
- * @param DOMNode $node
- * @param HTMLPurifier_Token[] $tokens
- */
- protected function createEndNode($node, &$tokens)
- {
- $tokens[] = $this->factory->createEnd($node->tagName);
- }
- /**
- * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
- *
- * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
- * @return array Associative array of attributes.
- */
- protected function transformAttrToAssoc($node_map)
- {
- // NamedNodeMap is documented very well, so we're using undocumented
- // features, namely, the fact that it implements Iterator and
- // has a ->length attribute
- if ($node_map->length === 0) {
- return array();
- }
- $array = array();
- foreach ($node_map as $attr) {
- $array[$attr->name] = $attr->value;
- }
- return $array;
- }
- /**
- * An error handler that mutes all errors
- * @param int $errno
- * @param string $errstr
- */
- public function muteErrorHandler($errno, $errstr)
- {
- }
- /**
- * Callback function for undoing escaping of stray angled brackets
- * in comments
- * @param array $matches
- * @return string
- */
- public function callbackUndoCommentSubst($matches)
- {
- return '<!--' . strtr($matches[1], array('&' => '&', '<' => '<')) . $matches[2];
- }
- /**
- * Callback function that entity-izes ampersands in comments so that
- * callbackUndoCommentSubst doesn't clobber them
- * @param array $matches
- * @return string
- */
- public function callbackArmorCommentEntities($matches)
- {
- return '<!--' . str_replace('&', '&', $matches[1]) . $matches[2];
- }
- /**
- * Wraps an HTML fragment in the necessary HTML
- * @param string $html
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- protected function wrapHTML($html, $config, $context)
- {
- $def = $config->getDefinition('HTML');
- $ret = '';
- if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
- $ret .= '<!DOCTYPE html ';
- if (!empty($def->doctype->dtdPublic)) {
- $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
- }
- if (!empty($def->doctype->dtdSystem)) {
- $ret .= '"' . $def->doctype->dtdSystem . '" ';
- }
- $ret .= '>';
- }
- $ret .= '<html><head>';
- $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
- // No protection if $html contains a stray </div>!
- $ret .= '</head><body><div>' . $html . '</div></body></html>';
- return $ret;
- }
- }
- /**
- * Our in-house implementation of a parser.
- *
- * A pure PHP parser, DirectLex has absolutely no dependencies, making
- * it a reasonably good default for PHP4. Written with efficiency in mind,
- * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
- * pales in comparison to HTMLPurifier_Lexer_DOMLex.
- *
- * @todo Reread XML spec and document differences.
- */
- class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
- {
- /**
- * @type bool
- */
- public $tracksLineNumbers = true;
- /**
- * Whitespace characters for str(c)spn.
- * @type string
- */
- protected $_whitespace = "\x20\x09\x0D\x0A";
- /**
- * Callback function for script CDATA fudge
- * @param array $matches, in form of array(opening tag, contents, closing tag)
- * @return string
- */
- protected function scriptCallback($matches)
- {
- return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
- }
- /**
- * @param String $html
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array|HTMLPurifier_Token[]
- */
- public function tokenizeHTML($html, $config, $context)
- {
- // special normalization for script tags without any armor
- // our "armor" heurstic is a < sign any number of whitespaces after
- // the first script tag
- if ($config->get('HTML.Trusted')) {
- $html = preg_replace_callback(
- '#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
- array($this, 'scriptCallback'),
- $html
- );
- }
- $html = $this->normalize($html, $config, $context);
- $cursor = 0; // our location in the text
- $inside_tag = false; // whether or not we're parsing the inside of a tag
- $array = array(); // result array
- // This is also treated to mean maintain *column* numbers too
- $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
- if ($maintain_line_numbers === null) {
- // automatically determine line numbering by checking
- // if error collection is on
- $maintain_line_numbers = $config->get('Core.CollectErrors');
- }
- if ($maintain_line_numbers) {
- $current_line = 1;
- $current_col = 0;
- $length = strlen($html);
- } else {
- $current_line = false;
- $current_col = false;
- $length = false;
- }
- $context->register('CurrentLine', $current_line);
- $context->register('CurrentCol', $current_col);
- $nl = "\n";
- // how often to manually recalculate. This will ALWAYS be right,
- // but it's pretty wasteful. Set to 0 to turn off
- $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
- $e = false;
- if ($config->get('Core.CollectErrors')) {
- $e =& $context->get('ErrorCollector');
- }
- // for testing synchronization
- $loops = 0;
- while (++$loops) {
- // $cursor is either at the start of a token, or inside of
- // a tag (i.e. there was a < immediately before it), as indicated
- // by $inside_tag
- if ($maintain_line_numbers) {
- // $rcursor, however, is always at the start of a token.
- $rcursor = $cursor - (int)$inside_tag;
- // Column number is cheap, so we calculate it every round.
- // We're interested at the *end* of the newline string, so
- // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
- // from our "rcursor" position.
- $nl_pos = strrpos($html, $nl, $rcursor - $length);
- $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
- // recalculate lines
- if ($synchronize_interval && // synchronization is on
- $cursor > 0 && // cursor is further than zero
- $loops % $synchronize_interval === 0) { // time to synchronize!
- $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
- }
- }
- $position_next_lt = strpos($html, '<', $cursor);
- $position_next_gt = strpos($html, '>', $cursor);
- // triggers on "<b>asdf</b>" but not "asdf <b></b>"
- // special case to set up context
- if ($position_next_lt === $cursor) {
- $inside_tag = true;
- $cursor++;
- }
- if (!$inside_tag && $position_next_lt !== false) {
- // We are not inside tag and there still is another tag to parse
- $token = new
- HTMLPurifier_Token_Text(
- $this->parseData(
- substr(
- $html,
- $cursor,
- $position_next_lt - $cursor
- )
- )
- );
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
- }
- $array[] = $token;
- $cursor = $position_next_lt + 1;
- $inside_tag = true;
- continue;
- } elseif (!$inside_tag) {
- // We are not inside tag but there are no more tags
- // If we're already at the end, break
- if ($cursor === strlen($html)) {
- break;
- }
- // Create Text of rest of string
- $token = new
- HTMLPurifier_Token_Text(
- $this->parseData(
- substr(
- $html,
- $cursor
- )
- )
- );
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- }
- $array[] = $token;
- break;
- } elseif ($inside_tag && $position_next_gt !== false) {
- // We are in tag and it is well formed
- // Grab the internals of the tag
- $strlen_segment = $position_next_gt - $cursor;
- if ($strlen_segment < 1) {
- // there's nothing to process!
- $token = new HTMLPurifier_Token_Text('<');
- $cursor++;
- continue;
- }
- $segment = substr($html, $cursor, $strlen_segment);
- if ($segment === false) {
- // somehow, we attempted to access beyond the end of
- // the string, defense-in-depth, reported by Nate Abele
- break;
- }
- // Check if it's a comment
- if (substr($segment, 0, 3) === '!--') {
- // re-determine segment length, looking for -->
- $position_comment_end = strpos($html, '-->', $cursor);
- if ($position_comment_end === false) {
- // uh oh, we have a comment that extends to
- // infinity. Can't be helped: set comment
- // end position to end of string
- if ($e) {
- $e->send(E_WARNING, 'Lexer: Unclosed comment');
- }
- $position_comment_end = strlen($html);
- $end = true;
- } else {
- $end = false;
- }
- $strlen_segment = $position_comment_end - $cursor;
- $segment = substr($html, $cursor, $strlen_segment);
- $token = new
- HTMLPurifier_Token_Comment(
- substr(
- $segment,
- 3,
- $strlen_segment - 3
- )
- );
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
- }
- $array[] = $token;
- $cursor = $end ? $position_comment_end : $position_comment_end + 3;
- $inside_tag = false;
- continue;
- }
- // Check if it's an end tag
- $is_end_tag = (strpos($segment, '/') === 0);
- if ($is_end_tag) {
- $type = substr($segment, 1);
- $token = new HTMLPurifier_Token_End($type);
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
- }
- $array[] = $token;
- $inside_tag = false;
- $cursor = $position_next_gt + 1;
- continue;
- }
- // Check leading character is alnum, if not, we may
- // have accidently grabbed an emoticon. Translate into
- // text and go our merry way
- if (!ctype_alpha($segment[0])) {
- // XML: $segment[0] !== '_' && $segment[0] !== ':'
- if ($e) {
- $e->send(E_NOTICE, 'Lexer: Unescaped lt');
- }
- $token = new HTMLPurifier_Token_Text('<');
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
- }
- $array[] = $token;
- $inside_tag = false;
- continue;
- }
- // Check if it is explicitly self closing, if so, remove
- // trailing slash. Remember, we could have a tag like <br>, so
- // any later token processing scripts must convert improperly
- // classified EmptyTags from StartTags.
- $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
- if ($is_self_closing) {
- $strlen_segment--;
- $segment = substr($segment, 0, $strlen_segment);
- }
- // Check if there are any attributes
- $position_first_space = strcspn($segment, $this->_whitespace);
- if ($position_first_space >= $strlen_segment) {
- if ($is_self_closing) {
- $token = new HTMLPurifier_Token_Empty($segment);
- } else {
- $token = new HTMLPurifier_Token_Start($segment);
- }
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
- }
- $array[] = $token;
- $inside_tag = false;
- $cursor = $position_next_gt + 1;
- continue;
- }
- // Grab out all the data
- $type = substr($segment, 0, $position_first_space);
- $attribute_string =
- trim(
- substr(
- $segment,
- $position_first_space
- )
- );
- if ($attribute_string) {
- $attr = $this->parseAttributeString(
- $attribute_string,
- $config,
- $context
- );
- } else {
- $attr = array();
- }
- if ($is_self_closing) {
- $token = new HTMLPurifier_Token_Empty($type, $attr);
- } else {
- $token = new HTMLPurifier_Token_Start($type, $attr);
- }
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
- }
- $array[] = $token;
- $cursor = $position_next_gt + 1;
- $inside_tag = false;
- continue;
- } else {
- // inside tag, but there's no ending > sign
- if ($e) {
- $e->send(E_WARNING, 'Lexer: Missing gt');
- }
- $token = new
- HTMLPurifier_Token_Text(
- '<' .
- $this->parseData(
- substr($html, $cursor)
- )
- );
- if ($maintain_line_numbers) {
- $token->rawPosition($current_line, $current_col);
- }
- // no cursor scroll? Hmm...
- $array[] = $token;
- break;
- }
- break;
- }
- $context->destroy('CurrentLine');
- $context->destroy('CurrentCol');
- return $array;
- }
- /**
- * PHP 5.0.x compatible substr_count that implements offset and length
- * @param string $haystack
- * @param string $needle
- * @param int $offset
- * @param int $length
- * @return int
- */
- protected function substrCount($haystack, $needle, $offset, $length)
- {
- static $oldVersion;
- if ($oldVersion === null) {
- $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
- }
- if ($oldVersion) {
- $haystack = substr($haystack, $offset, $length);
- return substr_count($haystack, $needle);
- } else {
- return substr_count($haystack, $needle, $offset, $length);
- }
- }
- /**
- * Takes the inside of an HTML tag and makes an assoc array of attributes.
- *
- * @param string $string Inside of tag excluding name.
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array Assoc array of attributes.
- */
- public function parseAttributeString($string, $config, $context)
- {
- $string = (string)$string; // quick typecast
- if ($string == '') {
- return array();
- } // no attributes
- $e = false;
- if ($config->get('Core.CollectErrors')) {
- $e =& $context->get('ErrorCollector');
- }
- // let's see if we can abort as quickly as possible
- // one equal sign, no spaces => one attribute
- $num_equal = substr_count($string, '=');
- $has_space = strpos($string, ' ');
- if ($num_equal === 0 && !$has_space) {
- // bool attribute
- return array($string => $string);
- } elseif ($num_equal === 1 && !$has_space) {
- // only one attribute
- list($key, $quoted_value) = explode('=', $string);
- $quoted_value = trim($quoted_value);
- if (!$key) {
- if ($e) {
- $e->send(E_ERROR, 'Lexer: Missing attribute key');
- }
- return array();
- }
- if (!$quoted_value) {
- return array($key => '');
- }
- $first_char = @$quoted_value[0];
- $last_char = @$quoted_value[strlen($quoted_value) - 1];
- $same_quote = ($first_char == $last_char);
- $open_quote = ($first_char == '"' || $first_char == "'");
- if ($same_quote && $open_quote) {
- // well behaved
- $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
- } else {
- // not well behaved
- if ($open_quote) {
- if ($e) {
- $e->send(E_ERROR, 'Lexer: Missing end quote');
- }
- $value = substr($quoted_value, 1);
- } else {
- $value = $quoted_value;
- }
- }
- if ($value === false) {
- $value = '';
- }
- return array($key => $this->parseData($value));
- }
- // setup loop environment
- $array = array(); // return assoc array of attributes
- $cursor = 0; // current position in string (moves forward)
- $size = strlen($string); // size of the string (stays the same)
- // if we have unquoted attributes, the parser expects a terminating
- // space, so let's guarantee that there's always a terminating space.
- $string .= ' ';
- $old_cursor = -1;
- while ($cursor < $size) {
- if ($old_cursor >= $cursor) {
- throw new Exception("Infinite loop detected");
- }
- $old_cursor = $cursor;
- $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
- // grab the key
- $key_begin = $cursor; //we're currently at the start of the key
- // scroll past all characters that are the key (not whitespace or =)
- $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
- $key_end = $cursor; // now at the end of the key
- $key = substr($string, $key_begin, $key_end - $key_begin);
- if (!$key) {
- if ($e) {
- $e->send(E_ERROR, 'Lexer: Missing attribute key');
- }
- $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
- continue; // empty key
- }
- // scroll past all whitespace
- $cursor += strspn($string, $this->_whitespace, $cursor);
- if ($cursor >= $size) {
- $array[$key] = $key;
- break;
- }
- // if the next character is an equal sign, we've got a regular
- // pair, otherwise, it's a bool attribute
- $first_char = @$string[$cursor];
- if ($first_char == '=') {
- // key="value"
- $cursor++;
- $cursor += strspn($string, $this->_whitespace, $cursor);
- if ($cursor === false) {
- $array[$key] = '';
- break;
- }
- // we might be in front of a quote right now
- $char = @$string[$cursor];
- if ($char == '"' || $char == "'") {
- // it's quoted, end bound is $char
- $cursor++;
- $value_begin = $cursor;
- $cursor = strpos($string, $char, $cursor);
- $value_end = $cursor;
- } else {
- // it's not quoted, end bound is whitespace
- $value_begin = $cursor;
- $cursor += strcspn($string, $this->_whitespace, $cursor);
- $value_end = $cursor;
- }
- // we reached a premature end
- if ($cursor === false) {
- $cursor = $size;
- $value_end = $cursor;
- }
- $value = substr($string, $value_begin, $value_end - $value_begin);
- if ($value === false) {
- $value = '';
- }
- $array[$key] = $this->parseData($value);
- $cursor++;
- } else {
- // boolattr
- if ($key !== '') {
- $array[$key] = $key;
- } else {
- // purely theoretical
- if ($e) {
- $e->send(E_ERROR, 'Lexer: Missing attribute key');
- }
- }
- }
- }
- return $array;
- }
- }
- /**
- * Concrete comment node class.
- */
- class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
- {
- /**
- * Character data within comment.
- * @type string
- */
- public $data;
- /**
- * @type bool
- */
- public $is_whitespace = true;
- /**
- * Transparent constructor.
- *
- * @param string $data String comment data.
- * @param int $line
- * @param int $col
- */
- public function __construct($data, $line = null, $col = null)
- {
- $this->data = $data;
- $this->line = $line;
- $this->col = $col;
- }
- public function toTokenPair() {
- return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
- }
- }
- /**
- * Concrete element node class.
- */
- class HTMLPurifier_Node_Element extends HTMLPurifier_Node
- {
- /**
- * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
- *
- * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
- * be lower-casing them, but these tokens cater to HTML tags, which are
- * insensitive.
- * @type string
- */
- public $name;
- /**
- * Associative array of the node's attributes.
- * @type array
- */
- public $attr = array();
- /**
- * List of child elements.
- * @type array
- */
- public $children = array();
- /**
- * Does this use the <a></a> form or the </a> form, i.e.
- * is it a pair of start/end tokens or an empty token.
- * @bool
- */
- public $empty = false;
- public $endCol = null, $endLine = null, $endArmor = array();
- public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
- $this->name = $name;
- $this->attr = $attr;
- $this->line = $line;
- $this->col = $col;
- $this->armor = $armor;
- }
- public function toTokenPair() {
- // XXX inefficiency here, normalization is not necessary
- if ($this->empty) {
- return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
- } else {
- $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
- $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
- //$end->start = $start;
- return array($start, $end);
- }
- }
- }
- /**
- * Concrete text token class.
- *
- * Text tokens comprise of regular parsed character data (PCDATA) and raw
- * character data (from the CDATA sections). Internally, their
- * data is parsed with all entities expanded. Surprisingly, the text token
- * does have a "tag name" called #PCDATA, which is how the DTD represents it
- * in permissible child nodes.
- */
- class HTMLPurifier_Node_Text extends HTMLPurifier_Node
- {
- /**
- * PCDATA tag name compatible with DTD, see
- * HTMLPurifier_ChildDef_Custom for details.
- * @type string
- */
- public $name = '#PCDATA';
- /**
- * @type string
- */
- public $data;
- /**< Parsed character data of text. */
- /**
- * @type bool
- */
- public $is_whitespace;
- /**< Bool indicating if node is whitespace. */
- /**
- * Constructor, accepts data and determines if it is whitespace.
- * @param string $data String parsed character data.
- * @param int $line
- * @param int $col
- */
- public function __construct($data, $is_whitespace, $line = null, $col = null)
- {
- $this->data = $data;
- $this->is_whitespace = $is_whitespace;
- $this->line = $line;
- $this->col = $col;
- }
- public function toTokenPair() {
- return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
- }
- }
- /**
- * Composite strategy that runs multiple strategies on tokens.
- */
- abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
- {
- /**
- * List of strategies to run tokens through.
- * @type HTMLPurifier_Strategy[]
- */
- protected $strategies = array();
- /**
- * @param HTMLPurifier_Token[] $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[]
- */
- public function execute($tokens, $config, $context)
- {
- foreach ($this->strategies as $strategy) {
- $tokens = $strategy->execute($tokens, $config, $context);
- }
- return $tokens;
- }
- }
- /**
- * Core strategy composed of the big four strategies.
- */
- class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
- {
- public function __construct()
- {
- $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
- $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
- $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
- $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
- }
- }
- /**
- * Takes a well formed list of tokens and fixes their nesting.
- *
- * HTML elements dictate which elements are allowed to be their children,
- * for example, you can't have a p tag in a span tag. Other elements have
- * much more rigorous definitions: tables, for instance, require a specific
- * order for their elements. There are also constraints not expressible by
- * document type definitions, such as the chameleon nature of ins/del
- * tags and global child exclusions.
- *
- * The first major objective of this strategy is to iterate through all
- * the nodes and determine whether or not their children conform to the
- * element's definition. If they do not, the child definition may
- * optionally supply an amended list of elements that is valid or
- * require that the entire node be deleted (and the previous node
- * rescanned).
- *
- * The second objective is to ensure that explicitly excluded elements of
- * an element do not appear in its children. Code that accomplishes this
- * task is pervasive through the strategy, though the two are distinct tasks
- * and could, theoretically, be seperated (although it's not recommended).
- *
- * @note Whether or not unrecognized children are silently dropped or
- * translated into text depends on the child definitions.
- *
- * @todo Enable nodes to be bubbled out of the structure. This is
- * easier with our new algorithm.
- */
- class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
- {
- /**
- * @param HTMLPurifier_Token[] $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array|HTMLPurifier_Token[]
- */
- public function execute($tokens, $config, $context)
- {
- //####################################################################//
- // Pre-processing
- // O(n) pass to convert to a tree, so that we can efficiently
- // refer to substrings
- $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
- // get a copy of the HTML definition
- $definition = $config->getHTMLDefinition();
- $excludes_enabled = !$config->get('Core.DisableExcludes');
- // setup the context variable 'IsInline', for chameleon processing
- // is 'false' when we are not inline, 'true' when it must always
- // be inline, and an integer when it is inline for a certain
- // branch of the document tree
- $is_inline = $definition->info_parent_def->descendants_are_inline;
- $context->register('IsInline', $is_inline);
- // setup error collector
- $e =& $context->get('ErrorCollector', true);
- //####################################################################//
- // Loop initialization
- // stack that contains all elements that are excluded
- // it is organized by parent elements, similar to $stack,
- // but it is only populated when an element with exclusions is
- // processed, i.e. there won't be empty exclusions.
- $exclude_stack = array($definition->info_parent_def->excludes);
- // variable that contains the start token while we are processing
- // nodes. This enables error reporting to do its job
- $node = $top_node;
- // dummy token
- list($token, $d) = $node->toTokenPair();
- $context->register('CurrentNode', $node);
- $context->register('CurrentToken', $token);
- //####################################################################//
- // Loop
- // We need to implement a post-order traversal iteratively, to
- // avoid running into stack space limits. This is pretty tricky
- // to reason about, so we just manually stack-ify the recursive
- // variant:
- //
- // function f($node) {
- // foreach ($node->children as $child) {
- // f($child);
- // }
- // validate($node);
- // }
- //
- // Thus, we will represent a stack frame as array($node,
- // $is_inline, stack of children)
- // e.g. array_reverse($node->children) - already processed
- // children.
- $parent_def = $definition->info_parent_def;
- $stack = array(
- array($top_node,
- $parent_def->descendants_are_inline,
- $parent_def->excludes, // exclusions
- 0)
- );
- while (!empty($stack)) {
- list($node, $is_inline, $excludes, $ix) = array_pop($stack);
- // recursive call
- $go = false;
- $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
- while (isset($node->children[$ix])) {
- $child = $node->children[$ix++];
- if ($child instanceof HTMLPurifier_Node_Element) {
- $go = true;
- $stack[] = array($node, $is_inline, $excludes, $ix);
- $stack[] = array($child,
- // ToDo: I don't think it matters if it's def or
- // child_def, but double check this...
- $is_inline || $def->descendants_are_inline,
- empty($def->excludes) ? $excludes
- : array_merge($excludes, $def->excludes),
- 0);
- break;
- }
- };
- if ($go) continue;
- list($token, $d) = $node->toTokenPair();
- // base case
- if ($excludes_enabled && isset($excludes[$node->name])) {
- $node->dead = true;
- if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
- } else {
- // XXX I suppose it would be slightly more efficient to
- // avoid the allocation here and have children
- // strategies handle it
- $children = array();
- foreach ($node->children as $child) {
- if (!$child->dead) $children[] = $child;
- }
- $result = $def->child->validateChildren($children, $config, $context);
- if ($result === true) {
- // nop
- $node->children = $children;
- } elseif ($result === false) {
- $node->dead = true;
- if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
- } else {
- $node->children = $result;
- if ($e) {
- // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
- if (empty($result) && !empty($children)) {
- $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
- } else if ($result != $children) {
- $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
- }
- }
- }
- }
- }
- //####################################################################//
- // Post-processing
- // remove context variables
- $context->destroy('IsInline');
- $context->destroy('CurrentNode');
- $context->destroy('CurrentToken');
- //####################################################################//
- // Return
- return HTMLPurifier_Arborize::flatten($node, $config, $context);
- }
- }
- /**
- * Takes tokens makes them well-formed (balance end tags, etc.)
- *
- * Specification of the armor attributes this strategy uses:
- *
- * - MakeWellFormed_TagClosedError: This armor field is used to
- * suppress tag closed errors for certain tokens [TagClosedSuppress],
- * in particular, if a tag was generated automatically by HTML
- * Purifier, we may rely on our infrastructure to close it for us
- * and shouldn't report an error to the user [TagClosedAuto].
- */
- class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
- {
- /**
- * Array stream of tokens being processed.
- * @type HTMLPurifier_Token[]
- */
- protected $tokens;
- /**
- * Current token.
- * @type HTMLPurifier_Token
- */
- protected $token;
- /**
- * Zipper managing the true state.
- * @type HTMLPurifier_Zipper
- */
- protected $zipper;
- /**
- * Current nesting of elements.
- * @type array
- */
- protected $stack;
- /**
- * Injectors active in this stream processing.
- * @type HTMLPurifier_Injector[]
- */
- protected $injectors;
- /**
- * Current instance of HTMLPurifier_Config.
- * @type HTMLPurifier_Config
- */
- protected $config;
- /**
- * Current instance of HTMLPurifier_Context.
- * @type HTMLPurifier_Context
- */
- protected $context;
- /**
- * @param HTMLPurifier_Token[] $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[]
- * @throws HTMLPurifier_Exception
- */
- public function execute($tokens, $config, $context)
- {
- $definition = $config->getHTMLDefinition();
- // local variables
- $generator = new HTMLPurifier_Generator($config, $context);
- $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
- // used for autoclose early abortion
- $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
- $e = $context->get('ErrorCollector', true);
- $i = false; // injector index
- list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
- if ($token === NULL) {
- return array();
- }
- $reprocess = false; // whether or not to reprocess the same token
- $stack = array();
- // member variables
- $this->stack =& $stack;
- $this->tokens =& $tokens;
- $this->token =& $token;
- $this->zipper =& $zipper;
- $this->config = $config;
- $this->context = $context;
- // context variables
- $context->register('CurrentNesting', $stack);
- $context->register('InputZipper', $zipper);
- $context->register('CurrentToken', $token);
- // -- begin INJECTOR --
- $this->injectors = array();
- $injectors = $config->getBatch('AutoFormat');
- $def_injectors = $definition->info_injector;
- $custom_injectors = $injectors['Custom'];
- unset($injectors['Custom']); // special case
- foreach ($injectors as $injector => $b) {
- // XXX: Fix with a legitimate lookup table of enabled filters
- if (strpos($injector, '.') !== false) {
- continue;
- }
- $injector = "HTMLPurifier_Injector_$injector";
- if (!$b) {
- continue;
- }
- $this->injectors[] = new $injector;
- }
- foreach ($def_injectors as $injector) {
- // assumed to be objects
- $this->injectors[] = $injector;
- }
- foreach ($custom_injectors as $injector) {
- if (!$injector) {
- continue;
- }
- if (is_string($injector)) {
- $injector = "HTMLPurifier_Injector_$injector";
- $injector = new $injector;
- }
- $this->injectors[] = $injector;
- }
- // give the injectors references to the definition and context
- // variables for performance reasons
- foreach ($this->injectors as $ix => $injector) {
- $error = $injector->prepare($config, $context);
- if (!$error) {
- continue;
- }
- array_splice($this->injectors, $ix, 1); // rm the injector
- trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
- }
- // -- end INJECTOR --
- // a note on reprocessing:
- // In order to reduce code duplication, whenever some code needs
- // to make HTML changes in order to make things "correct", the
- // new HTML gets sent through the purifier, regardless of its
- // status. This means that if we add a start token, because it
- // was totally necessary, we don't have to update nesting; we just
- // punt ($reprocess = true; continue;) and it does that for us.
- // isset is in loop because $tokens size changes during loop exec
- for (;;
- // only increment if we don't need to reprocess
- $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
- // check for a rewind
- if (is_int($i)) {
- // possibility: disable rewinding if the current token has a
- // rewind set on it already. This would offer protection from
- // infinite loop, but might hinder some advanced rewinding.
- $rewind_offset = $this->injectors[$i]->getRewindOffset();
- if (is_int($rewind_offset)) {
- for ($j = 0; $j < $rewind_offset; $j++) {
- if (empty($zipper->front)) break;
- $token = $zipper->prev($token);
- // indicate that other injectors should not process this token,
- // but we need to reprocess it
- unset($token->skip[$i]);
- $token->rewind = $i;
- if ($token instanceof HTMLPurifier_Token_Start) {
- array_pop($this->stack);
- } elseif ($token instanceof HTMLPurifier_Token_End) {
- $this->stack[] = $token->start;
- }
- }
- }
- $i = false;
- }
- // handle case of document end
- if ($token === NULL) {
- // kill processing if stack is empty
- if (empty($this->stack)) {
- break;
- }
- // peek
- $top_nesting = array_pop($this->stack);
- $this->stack[] = $top_nesting;
- // send error [TagClosedSuppress]
- if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
- $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
- }
- // append, don't splice, since this is the end
- $token = new HTMLPurifier_Token_End($top_nesting->name);
- // punt!
- $reprocess = true;
- continue;
- }
- //echo '<br>'; printZipper($zipper, $token);//printTokens($this->stack);
- //flush();
- // quick-check: if it's not a tag, no need to process
- if (empty($token->is_tag)) {
- if ($token instanceof HTMLPurifier_Token_Text) {
- foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) {
- continue;
- }
- if ($token->rewind !== null && $token->rewind !== $i) {
- continue;
- }
- // XXX fuckup
- $r = $token;
- $injector->handleText($r);
- $token = $this->processToken($r, $i);
- $reprocess = true;
- break;
- }
- }
- // another possibility is a comment
- continue;
- }
- if (isset($definition->info[$token->name])) {
- $type = $definition->info[$token->name]->child->type;
- } else {
- $type = false; // Type is unknown, treat accordingly
- }
- // quick tag checks: anything that's *not* an end tag
- $ok = false;
- if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
- // claims to be a start tag but is empty
- $token = new HTMLPurifier_Token_Empty(
- $token->name,
- $token->attr,
- $token->line,
- $token->col,
- $token->armor
- );
- $ok = true;
- } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
- // claims to be empty but really is a start tag
- // NB: this assignment is required
- $old_token = $token;
- $token = new HTMLPurifier_Token_End($token->name);
- $token = $this->insertBefore(
- new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
- );
- // punt (since we had to modify the input stream in a non-trivial way)
- $reprocess = true;
- continue;
- } elseif ($token instanceof HTMLPurifier_Token_Empty) {
- // real empty token
- $ok = true;
- } elseif ($token instanceof HTMLPurifier_Token_Start) {
- // start tag
- // ...unless they also have to close their parent
- if (!empty($this->stack)) {
- // Performance note: you might think that it's rather
- // inefficient, recalculating the autoclose information
- // for every tag that a token closes (since when we
- // do an autoclose, we push a new token into the
- // stream and then /process/ that, before
- // re-processing this token.) But this is
- // necessary, because an injector can make an
- // arbitrary transformations to the autoclosing
- // tokens we introduce, so things may have changed
- // in the meantime. Also, doing the inefficient thing is
- // "easy" to reason about (for certain perverse definitions
- // of "easy")
- $parent = array_pop($this->stack);
- $this->stack[] = $parent;
- $parent_def = null;
- $parent_elements = null;
- $autoclose = false;
- if (isset($definition->info[$parent->name])) {
- $parent_def = $definition->info[$parent->name];
- $parent_elements = $parent_def->child->getAllowedElements($config);
- $autoclose = !isset($parent_elements[$token->name]);
- }
- if ($autoclose && $definition->info[$token->name]->wrap) {
- // Check if an element can be wrapped by another
- // element to make it valid in a context (for
- // example, <ul><ul> needs a <li> in between)
- $wrapname = $definition->info[$token->name]->wrap;
- $wrapdef = $definition->info[$wrapname];
- $elements = $wrapdef->child->getAllowedElements($config);
- if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
- $newtoken = new HTMLPurifier_Token_Start($wrapname);
- $token = $this->insertBefore($newtoken);
- $reprocess = true;
- continue;
- }
- }
- $carryover = false;
- if ($autoclose && $parent_def->formatting) {
- $carryover = true;
- }
- if ($autoclose) {
- // check if this autoclose is doomed to fail
- // (this rechecks $parent, which his harmless)
- $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
- if (!$autoclose_ok) {
- foreach ($this->stack as $ancestor) {
- $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
- if (isset($elements[$token->name])) {
- $autoclose_ok = true;
- break;
- }
- if ($definition->info[$token->name]->wrap) {
- $wrapname = $definition->info[$token->name]->wrap;
- $wrapdef = $definition->info[$wrapname];
- $wrap_elements = $wrapdef->child->getAllowedElements($config);
- if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
- $autoclose_ok = true;
- break;
- }
- }
- }
- }
- if ($autoclose_ok) {
- // errors need to be updated
- $new_token = new HTMLPurifier_Token_End($parent->name);
- $new_token->start = $parent;
- // [TagClosedSuppress]
- if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
- if (!$carryover) {
- $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
- } else {
- $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
- }
- }
- if ($carryover) {
- $element = clone $parent;
- // [TagClosedAuto]
- $element->armor['MakeWellFormed_TagClosedError'] = true;
- $element->carryover = true;
- $token = $this->processToken(array($new_token, $token, $element));
- } else {
- $token = $this->insertBefore($new_token);
- }
- } else {
- $token = $this->remove();
- }
- $reprocess = true;
- continue;
- }
- }
- $ok = true;
- }
- if ($ok) {
- foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) {
- continue;
- }
- if ($token->rewind !== null && $token->rewind !== $i) {
- continue;
- }
- $r = $token;
- $injector->handleElement($r);
- $token = $this->processToken($r, $i);
- $reprocess = true;
- break;
- }
- if (!$reprocess) {
- // ah, nothing interesting happened; do normal processing
- if ($token instanceof HTMLPurifier_Token_Start) {
- $this->stack[] = $token;
- } elseif ($token instanceof HTMLPurifier_Token_End) {
- throw new HTMLPurifier_Exception(
- 'Improper handling of end tag in start code; possible error in MakeWellFormed'
- );
- }
- }
- continue;
- }
- // sanity check: we should be dealing with a closing tag
- if (!$token instanceof HTMLPurifier_Token_End) {
- throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
- }
- // make sure that we have something open
- if (empty($this->stack)) {
- if ($escape_invalid_tags) {
- if ($e) {
- $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
- }
- $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
- } else {
- if ($e) {
- $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
- }
- $token = $this->remove();
- }
- $reprocess = true;
- continue;
- }
- // first, check for the simplest case: everything closes neatly.
- // Eventually, everything passes through here; if there are problems
- // we modify the input stream accordingly and then punt, so that
- // the tokens get processed again.
- $current_parent = array_pop($this->stack);
- if ($current_parent->name == $token->name) {
- $token->start = $current_parent;
- foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) {
- continue;
- }
- if ($token->rewind !== null && $token->rewind !== $i) {
- continue;
- }
- $r = $token;
- $injector->handleEnd($r);
- $token = $this->processToken($r, $i);
- $this->stack[] = $current_parent;
- $reprocess = true;
- break;
- }
- continue;
- }
- // okay, so we're trying to close the wrong tag
- // undo the pop previous pop
- $this->stack[] = $current_parent;
- // scroll back the entire nest, trying to find our tag.
- // (feature could be to specify how far you'd like to go)
- $size = count($this->stack);
- // -2 because -1 is the last element, but we already checked that
- $skipped_tags = false;
- for ($j = $size - 2; $j >= 0; $j--) {
- if ($this->stack[$j]->name == $token->name) {
- $skipped_tags = array_slice($this->stack, $j);
- break;
- }
- }
- // we didn't find the tag, so remove
- if ($skipped_tags === false) {
- if ($escape_invalid_tags) {
- if ($e) {
- $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
- }
- $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
- } else {
- if ($e) {
- $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
- }
- $token = $this->remove();
- }
- $reprocess = true;
- continue;
- }
- // do errors, in REVERSE $j order: a,b,c with </a></b></c>
- $c = count($skipped_tags);
- if ($e) {
- for ($j = $c - 1; $j > 0; $j--) {
- // notice we exclude $j == 0, i.e. the current ending tag, from
- // the errors... [TagClosedSuppress]
- if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
- $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
- }
- }
- }
- // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
- $replace = array($token);
- for ($j = 1; $j < $c; $j++) {
- // ...as well as from the insertions
- $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
- $new_token->start = $skipped_tags[$j];
- array_unshift($replace, $new_token);
- if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
- // [TagClosedAuto]
- $element = clone $skipped_tags[$j];
- $element->carryover = true;
- $element->armor['MakeWellFormed_TagClosedError'] = true;
- $replace[] = $element;
- }
- }
- $token = $this->processToken($replace);
- $reprocess = true;
- continue;
- }
- $context->destroy('CurrentToken');
- $context->destroy('CurrentNesting');
- $context->destroy('InputZipper');
- unset($this->injectors, $this->stack, $this->tokens);
- return $zipper->toArray($token);
- }
- /**
- * Processes arbitrary token values for complicated substitution patterns.
- * In general:
- *
- * If $token is an array, it is a list of tokens to substitute for the
- * current token. These tokens then get individually processed. If there
- * is a leading integer in the list, that integer determines how many
- * tokens from the stream should be removed.
- *
- * If $token is a regular token, it is swapped with the current token.
- *
- * If $token is false, the current token is deleted.
- *
- * If $token is an integer, that number of tokens (with the first token
- * being the current one) will be deleted.
- *
- * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
- * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
- * this is not an injector related operation.
- * @throws HTMLPurifier_Exception
- */
- protected function processToken($token, $injector = -1)
- {
- // normalize forms of token
- if (is_object($token)) {
- $token = array(1, $token);
- }
- if (is_int($token)) {
- $token = array($token);
- }
- if ($token === false) {
- $token = array(1);
- }
- if (!is_array($token)) {
- throw new HTMLPurifier_Exception('Invalid token type from injector');
- }
- if (!is_int($token[0])) {
- array_unshift($token, 1);
- }
- if ($token[0] === 0) {
- throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
- }
- // $token is now an array with the following form:
- // array(number nodes to delete, new node 1, new node 2, ...)
- $delete = array_shift($token);
- list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
- if ($injector > -1) {
- // determine appropriate skips
- $oldskip = isset($old[0]) ? $old[0]->skip : array();
- foreach ($token as $object) {
- $object->skip = $oldskip;
- $object->skip[$injector] = true;
- }
- }
- return $r;
- }
- /**
- * Inserts a token before the current token. Cursor now points to
- * this token. You must reprocess after this.
- * @param HTMLPurifier_Token $token
- */
- private function insertBefore($token)
- {
- // NB not $this->zipper->insertBefore(), due to positioning
- // differences
- $splice = $this->zipper->splice($this->token, 0, array($token));
- return $splice[1];
- }
- /**
- * Removes current token. Cursor now points to new token occupying previously
- * occupied space. You must reprocess after this.
- */
- private function remove()
- {
- return $this->zipper->delete();
- }
- }
- /**
- * Removes all unrecognized tags from the list of tokens.
- *
- * This strategy iterates through all the tokens and removes unrecognized
- * tokens. If a token is not recognized but a TagTransform is defined for
- * that element, the element will be transformed accordingly.
- */
- class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
- {
- /**
- * @param HTMLPurifier_Token[] $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return array|HTMLPurifier_Token[]
- */
- public function execute($tokens, $config, $context)
- {
- $definition = $config->getHTMLDefinition();
- $generator = new HTMLPurifier_Generator($config, $context);
- $result = array();
- $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
- $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
- // currently only used to determine if comments should be kept
- $trusted = $config->get('HTML.Trusted');
- $comment_lookup = $config->get('HTML.AllowedComments');
- $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
- $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
- $remove_script_contents = $config->get('Core.RemoveScriptContents');
- $hidden_elements = $config->get('Core.HiddenElements');
- // remove script contents compatibility
- if ($remove_script_contents === true) {
- $hidden_elements['script'] = true;
- } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
- unset($hidden_elements['script']);
- }
- $attr_validator = new HTMLPurifier_AttrValidator();
- // removes tokens until it reaches a closing tag with its value
- $remove_until = false;
- // converts comments into text tokens when this is equal to a tag name
- $textify_comments = false;
- $token = false;
- $context->register('CurrentToken', $token);
- $e = false;
- if ($config->get('Core.CollectErrors')) {
- $e =& $context->get('ErrorCollector');
- }
- foreach ($tokens as $token) {
- if ($remove_until) {
- if (empty($token->is_tag) || $token->name !== $remove_until) {
- continue;
- }
- }
- if (!empty($token->is_tag)) {
- // DEFINITION CALL
- // before any processing, try to transform the element
- if (isset($definition->info_tag_transform[$token->name])) {
- $original_name = $token->name;
- // there is a transformation for this tag
- // DEFINITION CALL
- $token = $definition->
- info_tag_transform[$token->name]->transform($token, $config, $context);
- if ($e) {
- $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
- }
- }
- if (isset($definition->info[$token->name])) {
- // mostly everything's good, but
- // we need to make sure required attributes are in order
- if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
- $definition->info[$token->name]->required_attr &&
- ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
- ) {
- $attr_validator->validateToken($token, $config, $context);
- $ok = true;
- foreach ($definition->info[$token->name]->required_attr as $name) {
- if (!isset($token->attr[$name])) {
- $ok = false;
- break;
- }
- }
- if (!$ok) {
- if ($e) {
- $e->send(
- E_ERROR,
- 'Strategy_RemoveForeignElements: Missing required attribute',
- $name
- );
- }
- continue;
- }
- $token->armor['ValidateAttributes'] = true;
- }
- if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
- $textify_comments = $token->name;
- } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
- $textify_comments = false;
- }
- } elseif ($escape_invalid_tags) {
- // invalid tag, generate HTML representation and insert in
- if ($e) {
- $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
- }
- $token = new HTMLPurifier_Token_Text(
- $generator->generateFromToken($token)
- );
- } else {
- // check if we need to destroy all of the tag's children
- // CAN BE GENERICIZED
- if (isset($hidden_elements[$token->name])) {
- if ($token instanceof HTMLPurifier_Token_Start) {
- $remove_until = $token->name;
- } elseif ($token instanceof HTMLPurifier_Token_Empty) {
- // do nothing: we're still looking
- } else {
- $remove_until = false;
- }
- if ($e) {
- $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
- }
- } else {
- if ($e) {
- $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
- }
- }
- continue;
- }
- } elseif ($token instanceof HTMLPurifier_Token_Comment) {
- // textify comments in script tags when they are allowed
- if ($textify_comments !== false) {
- $data = $token->data;
- $token = new HTMLPurifier_Token_Text($data);
- } elseif ($trusted || $check_comments) {
- // always cleanup comments
- $trailing_hyphen = false;
- if ($e) {
- // perform check whether or not there's a trailing hyphen
- if (substr($token->data, -1) == '-') {
- $trailing_hyphen = true;
- }
- }
- $token->data = rtrim($token->data, '-');
- $found_double_hyphen = false;
- while (strpos($token->data, '--') !== false) {
- $found_double_hyphen = true;
- $token->data = str_replace('--', '-', $token->data);
- }
- if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
- ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
- // OK good
- if ($e) {
- if ($trailing_hyphen) {
- $e->send(
- E_NOTICE,
- 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
- );
- }
- if ($found_double_hyphen) {
- $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
- }
- }
- } else {
- if ($e) {
- $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
- }
- continue;
- }
- } else {
- // strip comments
- if ($e) {
- $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
- }
- continue;
- }
- } elseif ($token instanceof HTMLPurifier_Token_Text) {
- } else {
- continue;
- }
- $result[] = $token;
- }
- if ($remove_until && $e) {
- // we removed tokens until the end, throw error
- $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
- }
- $context->destroy('CurrentToken');
- return $result;
- }
- }
- /**
- * Validate all attributes in the tokens.
- */
- class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
- {
- /**
- * @param HTMLPurifier_Token[] $tokens
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token[]
- */
- public function execute($tokens, $config, $context)
- {
- // setup validator
- $validator = new HTMLPurifier_AttrValidator();
- $token = false;
- $context->register('CurrentToken', $token);
- foreach ($tokens as $key => $token) {
- // only process tokens that have attributes,
- // namely start and empty tags
- if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
- continue;
- }
- // skip tokens that are armored
- if (!empty($token->armor['ValidateAttributes'])) {
- continue;
- }
- // note that we have no facilities here for removing tokens
- $validator->validateToken($token, $config, $context);
- }
- $context->destroy('CurrentToken');
- return $tokens;
- }
- }
- /**
- * Transforms FONT tags to the proper form (SPAN with CSS styling)
- *
- * This transformation takes the three proprietary attributes of FONT and
- * transforms them into their corresponding CSS attributes. These are color,
- * face, and size.
- *
- * @note Size is an interesting case because it doesn't map cleanly to CSS.
- * Thanks to
- * http://style.cleverchimp.com/font_size_intervals/altintervals.html
- * for reasonable mappings.
- * @warning This doesn't work completely correctly; specifically, this
- * TagTransform operates before well-formedness is enforced, so
- * the "active formatting elements" algorithm doesn't get applied.
- */
- class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
- {
- /**
- * @type string
- */
- public $transform_to = 'span';
- /**
- * @type array
- */
- protected $_size_lookup = array(
- '0' => 'xx-small',
- '1' => 'xx-small',
- '2' => 'small',
- '3' => 'medium',
- '4' => 'large',
- '5' => 'x-large',
- '6' => 'xx-large',
- '7' => '300%',
- '-1' => 'smaller',
- '-2' => '60%',
- '+1' => 'larger',
- '+2' => '150%',
- '+3' => '200%',
- '+4' => '300%'
- );
- /**
- * @param HTMLPurifier_Token_Tag $tag
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return HTMLPurifier_Token_End|string
- */
- public function transform($tag, $config, $context)
- {
- if ($tag instanceof HTMLPurifier_Token_End) {
- $new_tag = clone $tag;
- $new_tag->name = $this->transform_to;
- return $new_tag;
- }
- $attr = $tag->attr;
- $prepend_style = '';
- // handle color transform
- if (isset($attr['color'])) {
- $prepend_style .= 'color:' . $attr['color'] . ';';
- unset($attr['color']);
- }
- // handle face transform
- if (isset($attr['face'])) {
- $prepend_style .= 'font-family:' . $attr['face'] . ';';
- unset($attr['face']);
- }
- // handle size transform
- if (isset($attr['size'])) {
- // normalize large numbers
- if ($attr['size'] !== '') {
- if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
- $size = (int)$attr['size'];
- if ($size < -2) {
- $attr['size'] = '-2';
- }
- if ($size > 4) {
- $attr['size'] = '+4';
- }
- } else {
- $size = (int)$attr['size'];
- if ($size > 7) {
- $attr['size'] = '7';
- }
- }
- }
- if (isset($this->_size_lookup[$attr['size']])) {
- $prepend_style .= 'font-size:' .
- $this->_size_lookup[$attr['size']] . ';';
- }
- unset($attr['size']);
- }
- if ($prepend_style) {
- $attr['style'] = isset($attr['style']) ?
- $prepend_style . $attr['style'] :
- $prepend_style;
- }
- $new_tag = clone $tag;
- $new_tag->name = $this->transform_to;
- $new_tag->attr = $attr;
- return $new_tag;
- }
- }
- /**
- * Simple transformation, just change tag name to something else,
- * and possibly add some styling. This will cover most of the deprecated
- * tag cases.
- */
- class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
- {
- /**
- * @type string
- */
- protected $style;
- /**
- * @param string $transform_to Tag name to transform to.
- * @param string $style CSS style to add to the tag
- */
- public function __construct($transform_to, $style = null)
- {
- $this->transform_to = $transform_to;
- $this->style = $style;
- }
- /**
- * @param HTMLPurifier_Token_Tag $tag
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return string
- */
- public function transform($tag, $config, $context)
- {
- $new_tag = clone $tag;
- $new_tag->name = $this->transform_to;
- if (!is_null($this->style) &&
- ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
- ) {
- $this->prependCSS($new_tag->attr, $this->style);
- }
- return $new_tag;
- }
- }
- /**
- * Concrete comment token class. Generally will be ignored.
- */
- class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
- {
- /**
- * Character data within comment.
- * @type string
- */
- public $data;
- /**
- * @type bool
- */
- public $is_whitespace = true;
- /**
- * Transparent constructor.
- *
- * @param string $data String comment data.
- * @param int $line
- * @param int $col
- */
- public function __construct($data, $line = null, $col = null)
- {
- $this->data = $data;
- $this->line = $line;
- $this->col = $col;
- }
- public function toNode() {
- return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
- }
- }
- /**
- * Abstract class of a tag token (start, end or empty), and its behavior.
- */
- abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
- {
- /**
- * Static bool marker that indicates the class is a tag.
- *
- * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
- * without having to use a function call <tt>is_a()</tt>.
- * @type bool
- */
- public $is_tag = true;
- /**
- * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
- *
- * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
- * be lower-casing them, but these tokens cater to HTML tags, which are
- * insensitive.
- * @type string
- */
- public $name;
- /**
- * Associative array of the tag's attributes.
- * @type array
- */
- public $attr = array();
- /**
- * Non-overloaded constructor, which lower-cases passed tag name.
- *
- * @param string $name String name.
- * @param array $attr Associative array of attributes.
- * @param int $line
- * @param int $col
- * @param array $armor
- */
- public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
- {
- $this->name = ctype_lower($name) ? $name : strtolower($name);
- foreach ($attr as $key => $value) {
- // normalization only necessary when key is not lowercase
- if (!ctype_lower($key)) {
- $new_key = strtolower($key);
- if (!isset($attr[$new_key])) {
- $attr[$new_key] = $attr[$key];
- }
- if ($new_key !== $key) {
- unset($attr[$key]);
- }
- }
- }
- $this->attr = $attr;
- $this->line = $line;
- $this->col = $col;
- $this->armor = $armor;
- }
- public function toNode() {
- return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
- }
- }
- /**
- * Concrete empty token class.
- */
- class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
- {
- public function toNode() {
- $n = parent::toNode();
- $n->empty = true;
- return $n;
- }
- }
- /**
- * Concrete end token class.
- *
- * @warning This class accepts attributes even though end tags cannot. This
- * is for optimization reasons, as under normal circumstances, the Lexers
- * do not pass attributes.
- */
- class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
- {
- /**
- * Token that started this node.
- * Added by MakeWellFormed. Please do not edit this!
- * @type HTMLPurifier_Token
- */
- public $start;
- public function toNode() {
- throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
- }
- }
- /**
- * Concrete start token class.
- */
- class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
- {
- }
- /**
- * Concrete text token class.
- *
- * Text tokens comprise of regular parsed character data (PCDATA) and raw
- * character data (from the CDATA sections). Internally, their
- * data is parsed with all entities expanded. Surprisingly, the text token
- * does have a "tag name" called #PCDATA, which is how the DTD represents it
- * in permissible child nodes.
- */
- class HTMLPurifier_Token_Text extends HTMLPurifier_Token
- {
- /**
- * @type string
- */
- public $name = '#PCDATA';
- /**< PCDATA tag name compatible with DTD. */
- /**
- * @type string
- */
- public $data;
- /**< Parsed character data of text. */
- /**
- * @type bool
- */
- public $is_whitespace;
- /**< Bool indicating if node is whitespace. */
- /**
- * Constructor, accepts data and determines if it is whitespace.
- * @param string $data String parsed character data.
- * @param int $line
- * @param int $col
- */
- public function __construct($data, $line = null, $col = null)
- {
- $this->data = $data;
- $this->is_whitespace = ctype_space($data);
- $this->line = $line;
- $this->col = $col;
- }
- public function toNode() {
- return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
- }
- }
- class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'DisableExternal';
- /**
- * @type array
- */
- protected $ourHostParts = false;
- /**
- * @param HTMLPurifier_Config $config
- * @return void
- */
- public function prepare($config)
- {
- $our_host = $config->getDefinition('URI')->host;
- if ($our_host !== null) {
- $this->ourHostParts = array_reverse(explode('.', $our_host));
- }
- }
- /**
- * @param HTMLPurifier_URI $uri Reference
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- if (is_null($uri->host)) {
- return true;
- }
- if ($this->ourHostParts === false) {
- return false;
- }
- $host_parts = array_reverse(explode('.', $uri->host));
- foreach ($this->ourHostParts as $i => $x) {
- if (!isset($host_parts[$i])) {
- return false;
- }
- if ($host_parts[$i] != $this->ourHostParts[$i]) {
- return false;
- }
- }
- return true;
- }
- }
- class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
- {
- /**
- * @type string
- */
- public $name = 'DisableExternalResources';
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- if (!$context->get('EmbeddedURI', true)) {
- return true;
- }
- return parent::filter($uri, $config, $context);
- }
- }
- class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'DisableResources';
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- return !$context->get('EmbeddedURI', true);
- }
- }
- // It's not clear to me whether or not Punycode means that hostnames
- // do not have canonical forms anymore. As far as I can tell, it's
- // not a problem (punycoding should be identity when no Unicode
- // points are involved), but I'm not 100% sure
- class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'HostBlacklist';
- /**
- * @type array
- */
- protected $blacklist = array();
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function prepare($config)
- {
- $this->blacklist = $config->get('URI.HostBlacklist');
- return true;
- }
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- foreach ($this->blacklist as $blacklisted_host_fragment) {
- if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
- return false;
- }
- }
- return true;
- }
- }
- // does not support network paths
- class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'MakeAbsolute';
- /**
- * @type
- */
- protected $base;
- /**
- * @type array
- */
- protected $basePathStack = array();
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function prepare($config)
- {
- $def = $config->getDefinition('URI');
- $this->base = $def->base;
- if (is_null($this->base)) {
- trigger_error(
- 'URI.MakeAbsolute is being ignored due to lack of ' .
- 'value for URI.Base configuration',
- E_USER_WARNING
- );
- return false;
- }
- $this->base->fragment = null; // fragment is invalid for base URI
- $stack = explode('/', $this->base->path);
- array_pop($stack); // discard last segment
- $stack = $this->_collapseStack($stack); // do pre-parsing
- $this->basePathStack = $stack;
- return true;
- }
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- if (is_null($this->base)) {
- return true;
- } // abort early
- if ($uri->path === '' && is_null($uri->scheme) &&
- is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
- // reference to current document
- $uri = clone $this->base;
- return true;
- }
- if (!is_null($uri->scheme)) {
- // absolute URI already: don't change
- if (!is_null($uri->host)) {
- return true;
- }
- $scheme_obj = $uri->getSchemeObj($config, $context);
- if (!$scheme_obj) {
- // scheme not recognized
- return false;
- }
- if (!$scheme_obj->hierarchical) {
- // non-hierarchal URI with explicit scheme, don't change
- return true;
- }
- // special case: had a scheme but always is hierarchical and had no authority
- }
- if (!is_null($uri->host)) {
- // network path, don't bother
- return true;
- }
- if ($uri->path === '') {
- $uri->path = $this->base->path;
- } elseif ($uri->path[0] !== '/') {
- // relative path, needs more complicated processing
- $stack = explode('/', $uri->path);
- $new_stack = array_merge($this->basePathStack, $stack);
- if ($new_stack[0] !== '' && !is_null($this->base->host)) {
- array_unshift($new_stack, '');
- }
- $new_stack = $this->_collapseStack($new_stack);
- $uri->path = implode('/', $new_stack);
- } else {
- // absolute path, but still we should collapse
- $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
- }
- // re-combine
- $uri->scheme = $this->base->scheme;
- if (is_null($uri->userinfo)) {
- $uri->userinfo = $this->base->userinfo;
- }
- if (is_null($uri->host)) {
- $uri->host = $this->base->host;
- }
- if (is_null($uri->port)) {
- $uri->port = $this->base->port;
- }
- return true;
- }
- /**
- * Resolve dots and double-dots in a path stack
- * @param array $stack
- * @return array
- */
- private function _collapseStack($stack)
- {
- $result = array();
- $is_folder = false;
- for ($i = 0; isset($stack[$i]); $i++) {
- $is_folder = false;
- // absorb an internally duplicated slash
- if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
- continue;
- }
- if ($stack[$i] == '..') {
- if (!empty($result)) {
- $segment = array_pop($result);
- if ($segment === '' && empty($result)) {
- // error case: attempted to back out too far:
- // restore the leading slash
- $result[] = '';
- } elseif ($segment === '..') {
- $result[] = '..'; // cannot remove .. with ..
- }
- } else {
- // relative path, preserve the double-dots
- $result[] = '..';
- }
- $is_folder = true;
- continue;
- }
- if ($stack[$i] == '.') {
- // silently absorb
- $is_folder = true;
- continue;
- }
- $result[] = $stack[$i];
- }
- if ($is_folder) {
- $result[] = '';
- }
- return $result;
- }
- }
- class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'Munge';
- /**
- * @type bool
- */
- public $post = true;
- /**
- * @type string
- */
- private $target;
- /**
- * @type HTMLPurifier_URIParser
- */
- private $parser;
- /**
- * @type bool
- */
- private $doEmbed;
- /**
- * @type string
- */
- private $secretKey;
- /**
- * @type array
- */
- protected $replace = array();
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function prepare($config)
- {
- $this->target = $config->get('URI.' . $this->name);
- $this->parser = new HTMLPurifier_URIParser();
- $this->doEmbed = $config->get('URI.MungeResources');
- $this->secretKey = $config->get('URI.MungeSecretKey');
- if ($this->secretKey && !function_exists('hash_hmac')) {
- throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
- }
- return true;
- }
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
- return true;
- }
- $scheme_obj = $uri->getSchemeObj($config, $context);
- if (!$scheme_obj) {
- return true;
- } // ignore unknown schemes, maybe another postfilter did it
- if (!$scheme_obj->browsable) {
- return true;
- } // ignore non-browseable schemes, since we can't munge those in a reasonable way
- if ($uri->isBenign($config, $context)) {
- return true;
- } // don't redirect if a benign URL
- $this->makeReplace($uri, $config, $context);
- $this->replace = array_map('rawurlencode', $this->replace);
- $new_uri = strtr($this->target, $this->replace);
- $new_uri = $this->parser->parse($new_uri);
- // don't redirect if the target host is the same as the
- // starting host
- if ($uri->host === $new_uri->host) {
- return true;
- }
- $uri = $new_uri; // overwrite
- return true;
- }
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- */
- protected function makeReplace($uri, $config, $context)
- {
- $string = $uri->toString();
- // always available
- $this->replace['%s'] = $string;
- $this->replace['%r'] = $context->get('EmbeddedURI', true);
- $token = $context->get('CurrentToken', true);
- $this->replace['%n'] = $token ? $token->name : null;
- $this->replace['%m'] = $context->get('CurrentAttr', true);
- $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
- // not always available
- if ($this->secretKey) {
- $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
- }
- }
- }
- /**
- * Implements safety checks for safe iframes.
- *
- * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
- * works safely.
- */
- class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
- {
- /**
- * @type string
- */
- public $name = 'SafeIframe';
- /**
- * @type bool
- */
- public $always_load = true;
- /**
- * @type string
- */
- protected $regexp = null;
- // XXX: The not so good bit about how this is all set up now is we
- // can't check HTML.SafeIframe in the 'prepare' step: we have to
- // defer till the actual filtering.
- /**
- * @param HTMLPurifier_Config $config
- * @return bool
- */
- public function prepare($config)
- {
- $this->regexp = $config->get('URI.SafeIframeRegexp');
- return true;
- }
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function filter(&$uri, $config, $context)
- {
- // check if filter not applicable
- if (!$config->get('HTML.SafeIframe')) {
- return true;
- }
- // check if the filter should actually trigger
- if (!$context->get('EmbeddedURI', true)) {
- return true;
- }
- $token = $context->get('CurrentToken', true);
- if (!($token && $token->name == 'iframe')) {
- return true;
- }
- // check if we actually have some whitelists enabled
- if ($this->regexp === null) {
- return false;
- }
- // actually check the whitelists
- return preg_match($this->regexp, $uri->toString());
- }
- }
- /**
- * Implements data: URI for base64 encoded images supported by GD.
- */
- class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
- {
- /**
- * @type bool
- */
- public $browsable = true;
- /**
- * @type array
- */
- public $allowed_types = array(
- // you better write validation code for other types if you
- // decide to allow them
- 'image/jpeg' => true,
- 'image/gif' => true,
- 'image/png' => true,
- );
- // this is actually irrelevant since we only write out the path
- // component
- /**
- * @type bool
- */
- public $may_omit_host = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $result = explode(',', $uri->path, 2);
- $is_base64 = false;
- $charset = null;
- $content_type = null;
- if (count($result) == 2) {
- list($metadata, $data) = $result;
- // do some legwork on the metadata
- $metas = explode(';', $metadata);
- while (!empty($metas)) {
- $cur = array_shift($metas);
- if ($cur == 'base64') {
- $is_base64 = true;
- break;
- }
- if (substr($cur, 0, 8) == 'charset=') {
- // doesn't match if there are arbitrary spaces, but
- // whatever dude
- if ($charset !== null) {
- continue;
- } // garbage
- $charset = substr($cur, 8); // not used
- } else {
- if ($content_type !== null) {
- continue;
- } // garbage
- $content_type = $cur;
- }
- }
- } else {
- $data = $result[0];
- }
- if ($content_type !== null && empty($this->allowed_types[$content_type])) {
- return false;
- }
- if ($charset !== null) {
- // error; we don't allow plaintext stuff
- $charset = null;
- }
- $data = rawurldecode($data);
- if ($is_base64) {
- $raw_data = base64_decode($data);
- } else {
- $raw_data = $data;
- }
- // XXX probably want to refactor this into a general mechanism
- // for filtering arbitrary content types
- $file = tempnam("/tmp", "");
- file_put_contents($file, $raw_data);
- if (function_exists('exif_imagetype')) {
- $image_code = exif_imagetype($file);
- unlink($file);
- } elseif (function_exists('getimagesize')) {
- set_error_handler(array($this, 'muteErrorHandler'));
- $info = getimagesize($file);
- restore_error_handler();
- unlink($file);
- if ($info == false) {
- return false;
- }
- $image_code = $info[2];
- } else {
- trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
- }
- $real_content_type = image_type_to_mime_type($image_code);
- if ($real_content_type != $content_type) {
- // we're nice guys; if the content type is something else we
- // support, change it over
- if (empty($this->allowed_types[$real_content_type])) {
- return false;
- }
- $content_type = $real_content_type;
- }
- // ok, it's kosher, rewrite what we need
- $uri->userinfo = null;
- $uri->host = null;
- $uri->port = null;
- $uri->fragment = null;
- $uri->query = null;
- $uri->path = "$content_type;base64," . base64_encode($raw_data);
- return true;
- }
- /**
- * @param int $errno
- * @param string $errstr
- */
- public function muteErrorHandler($errno, $errstr)
- {
- }
- }
- /**
- * Validates file as defined by RFC 1630 and RFC 1738.
- */
- class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
- {
- /**
- * Generally file:// URLs are not accessible from most
- * machines, so placing them as an img src is incorrect.
- * @type bool
- */
- public $browsable = false;
- /**
- * Basically the *only* URI scheme for which this is true, since
- * accessing files on the local machine is very common. In fact,
- * browsers on some operating systems don't understand the
- * authority, though I hear it is used on Windows to refer to
- * network shares.
- * @type bool
- */
- public $may_omit_host = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- // Authentication method is not supported
- $uri->userinfo = null;
- // file:// makes no provisions for accessing the resource
- $uri->port = null;
- // While it seems to work on Firefox, the querystring has
- // no possible effect and is thus stripped.
- $uri->query = null;
- return true;
- }
- }
- /**
- * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
- */
- class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
- {
- /**
- * @type int
- */
- public $default_port = 21;
- /**
- * @type bool
- */
- public $browsable = true; // usually
- /**
- * @type bool
- */
- public $hierarchical = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $uri->query = null;
- // typecode check
- $semicolon_pos = strrpos($uri->path, ';'); // reverse
- if ($semicolon_pos !== false) {
- $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
- $uri->path = substr($uri->path, 0, $semicolon_pos);
- $type_ret = '';
- if (strpos($type, '=') !== false) {
- // figure out whether or not the declaration is correct
- list($key, $typecode) = explode('=', $type, 2);
- if ($key !== 'type') {
- // invalid key, tack it back on encoded
- $uri->path .= '%3B' . $type;
- } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
- $type_ret = ";type=$typecode";
- }
- } else {
- $uri->path .= '%3B' . $type;
- }
- $uri->path = str_replace(';', '%3B', $uri->path);
- $uri->path .= $type_ret;
- }
- return true;
- }
- }
- /**
- * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
- */
- class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
- {
- /**
- * @type int
- */
- public $default_port = 80;
- /**
- * @type bool
- */
- public $browsable = true;
- /**
- * @type bool
- */
- public $hierarchical = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $uri->userinfo = null;
- return true;
- }
- }
- /**
- * Validates https (Secure HTTP) according to http scheme.
- */
- class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
- {
- /**
- * @type int
- */
- public $default_port = 443;
- /**
- * @type bool
- */
- public $secure = true;
- }
- // VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
- // email is valid, but be careful!
- /**
- * Validates mailto (for E-mail) according to RFC 2368
- * @todo Validate the email address
- * @todo Filter allowed query parameters
- */
- class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
- {
- /**
- * @type bool
- */
- public $browsable = false;
- /**
- * @type bool
- */
- public $may_omit_host = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $uri->userinfo = null;
- $uri->host = null;
- $uri->port = null;
- // we need to validate path against RFC 2368's addr-spec
- return true;
- }
- }
- /**
- * Validates news (Usenet) as defined by generic RFC 1738
- */
- class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
- {
- /**
- * @type bool
- */
- public $browsable = false;
- /**
- * @type bool
- */
- public $may_omit_host = true;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $uri->userinfo = null;
- $uri->host = null;
- $uri->port = null;
- $uri->query = null;
- // typecode check needed on path
- return true;
- }
- }
- /**
- * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
- */
- class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
- {
- /**
- * @type int
- */
- public $default_port = 119;
- /**
- * @type bool
- */
- public $browsable = false;
- /**
- * @param HTMLPurifier_URI $uri
- * @param HTMLPurifier_Config $config
- * @param HTMLPurifier_Context $context
- * @return bool
- */
- public function doValidate(&$uri, $config, $context)
- {
- $uri->userinfo = null;
- $uri->query = null;
- return true;
- }
- }
- /**
- * Performs safe variable parsing based on types which can be used by
- * users. This may not be able to represent all possible data inputs,
- * however.
- */
- class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
- {
- /**
- * @param mixed $var
- * @param int $type
- * @param bool $allow_null
- * @return array|bool|float|int|mixed|null|string
- * @throws HTMLPurifier_VarParserException
- */
- protected function parseImplementation($var, $type, $allow_null)
- {
- if ($allow_null && $var === null) {
- return null;
- }
- switch ($type) {
- // Note: if code "breaks" from the switch, it triggers a generic
- // exception to be thrown. Specific errors can be specifically
- // done here.
- case self::MIXED:
- case self::ISTRING:
- case self::STRING:
- case self::TEXT:
- case self::ITEXT:
- return $var;
- case self::INT:
- if (is_string($var) && ctype_digit($var)) {
- $var = (int)$var;
- }
- return $var;
- case self::FLOAT:
- if ((is_string($var) && is_numeric($var)) || is_int($var)) {
- $var = (float)$var;
- }
- return $var;
- case self::BOOL:
- if (is_int($var) && ($var === 0 || $var === 1)) {
- $var = (bool)$var;
- } elseif (is_string($var)) {
- if ($var == 'on' || $var == 'true' || $var == '1') {
- $var = true;
- } elseif ($var == 'off' || $var == 'false' || $var == '0') {
- $var = false;
- } else {
- throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
- }
- }
- return $var;
- case self::ALIST:
- case self::HASH:
- case self::LOOKUP:
- if (is_string($var)) {
- // special case: technically, this is an array with
- // a single empty string item, but having an empty
- // array is more intuitive
- if ($var == '') {
- return array();
- }
- if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
- // simplistic string to array method that only works
- // for simple lists of tag names or alphanumeric characters
- $var = explode(',', $var);
- } else {
- $var = preg_split('/(,|[\n\r]+)/', $var);
- }
- // remove spaces
- foreach ($var as $i => $j) {
- $var[$i] = trim($j);
- }
- if ($type === self::HASH) {
- // key:value,key2:value2
- $nvar = array();
- foreach ($var as $keypair) {
- $c = explode(':', $keypair, 2);
- if (!isset($c[1])) {
- continue;
- }
- $nvar[trim($c[0])] = trim($c[1]);
- }
- $var = $nvar;
- }
- }
- if (!is_array($var)) {
- break;
- }
- $keys = array_keys($var);
- if ($keys === array_keys($keys)) {
- if ($type == self::ALIST) {
- return $var;
- } elseif ($type == self::LOOKUP) {
- $new = array();
- foreach ($var as $key) {
- $new[$key] = true;
- }
- return $new;
- } else {
- break;
- }
- }
- if ($type === self::ALIST) {
- trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
- return array_values($var);
- }
- if ($type === self::LOOKUP) {
- foreach ($var as $key => $value) {
- if ($value !== true) {
- trigger_error(
- "Lookup array has non-true value at key '$key'; " .
- "maybe your input array was not indexed numerically",
- E_USER_WARNING
- );
- }
- $var[$key] = true;
- }
- }
- return $var;
- default:
- $this->errorInconsistent(__CLASS__, $type);
- }
- $this->errorGeneric($var, $type);
- }
- }
- /**
- * This variable parser uses PHP's internal code engine. Because it does
- * this, it can represent all inputs; however, it is dangerous and cannot
- * be used by users.
- */
- class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
- {
- /**
- * @param mixed $var
- * @param int $type
- * @param bool $allow_null
- * @return null|string
- */
- protected function parseImplementation($var, $type, $allow_null)
- {
- return $this->evalExpression($var);
- }
- /**
- * @param string $expr
- * @return mixed
- * @throws HTMLPurifier_VarParserException
- */
- protected function evalExpression($expr)
- {
- $var = null;
- $result = eval("\$var = $expr;");
- if ($result === false) {
- throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
- }
- return $var;
- }
- }
|