OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /openrisc/tags/gnu-dev/fsf-gcc-snapshot-1-mar-12/or1k-gcc/libgo/go/html
    from Rev 747 to Rev 783
    Reverse comparison

Rev 747 → Rev 783

/entity_test.go
0,0 → 1,29
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package html
 
import (
"testing"
"unicode/utf8"
)
 
func TestEntityLength(t *testing.T) {
// We verify that the length of UTF-8 encoding of each value is <= 1 + len(key).
// The +1 comes from the leading "&". This property implies that the length of
// unescaped text is <= the length of escaped text.
for k, v := range entity {
if 1+len(k) < utf8.RuneLen(v) {
t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v))
}
if len(k) > longestEntityWithoutSemicolon && k[len(k)-1] != ';' {
t.Errorf("entity name %s is %d characters, but longestEntityWithoutSemicolon=%d", k, len(k), longestEntityWithoutSemicolon)
}
}
for k, v := range entity2 {
if 1+len(k) < utf8.RuneLen(v[0])+utf8.RuneLen(v[1]) {
t.Error("escaped entity &" + k + " is shorter than its UTF-8 encoding " + string(v[0]) + string(v[1]))
}
}
}
/entity.go
0,0 → 1,2253
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package html
 
// All entities that do not end with ';' are 6 or fewer bytes long.
const longestEntityWithoutSemicolon = 6
 
// entity is a map from HTML entity names to their values. The semicolon matters:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/named-character-references.html
// lists both "amp" and "amp;" as two separate entries.
//
// Note that the HTML5 list is larger than the HTML4 list at
// http://www.w3.org/TR/html4/sgml/entities.html
var entity = map[string]rune{
"AElig;": '\U000000C6',
"AMP;": '\U00000026',
"Aacute;": '\U000000C1',
"Abreve;": '\U00000102',
"Acirc;": '\U000000C2',
"Acy;": '\U00000410',
"Afr;": '\U0001D504',
"Agrave;": '\U000000C0',
"Alpha;": '\U00000391',
"Amacr;": '\U00000100',
"And;": '\U00002A53',
"Aogon;": '\U00000104',
"Aopf;": '\U0001D538',
"ApplyFunction;": '\U00002061',
"Aring;": '\U000000C5',
"Ascr;": '\U0001D49C',
"Assign;": '\U00002254',
"Atilde;": '\U000000C3',
"Auml;": '\U000000C4',
"Backslash;": '\U00002216',
"Barv;": '\U00002AE7',
"Barwed;": '\U00002306',
"Bcy;": '\U00000411',
"Because;": '\U00002235',
"Bernoullis;": '\U0000212C',
"Beta;": '\U00000392',
"Bfr;": '\U0001D505',
"Bopf;": '\U0001D539',
"Breve;": '\U000002D8',
"Bscr;": '\U0000212C',
"Bumpeq;": '\U0000224E',
"CHcy;": '\U00000427',
"COPY;": '\U000000A9',
"Cacute;": '\U00000106',
"Cap;": '\U000022D2',
"CapitalDifferentialD;": '\U00002145',
"Cayleys;": '\U0000212D',
"Ccaron;": '\U0000010C',
"Ccedil;": '\U000000C7',
"Ccirc;": '\U00000108',
"Cconint;": '\U00002230',
"Cdot;": '\U0000010A',
"Cedilla;": '\U000000B8',
"CenterDot;": '\U000000B7',
"Cfr;": '\U0000212D',
"Chi;": '\U000003A7',
"CircleDot;": '\U00002299',
"CircleMinus;": '\U00002296',
"CirclePlus;": '\U00002295',
"CircleTimes;": '\U00002297',
"ClockwiseContourIntegral;": '\U00002232',
"CloseCurlyDoubleQuote;": '\U0000201D',
"CloseCurlyQuote;": '\U00002019',
"Colon;": '\U00002237',
"Colone;": '\U00002A74',
"Congruent;": '\U00002261',
"Conint;": '\U0000222F',
"ContourIntegral;": '\U0000222E',
"Copf;": '\U00002102',
"Coproduct;": '\U00002210',
"CounterClockwiseContourIntegral;": '\U00002233',
"Cross;": '\U00002A2F',
"Cscr;": '\U0001D49E',
"Cup;": '\U000022D3',
"CupCap;": '\U0000224D',
"DD;": '\U00002145',
"DDotrahd;": '\U00002911',
"DJcy;": '\U00000402',
"DScy;": '\U00000405',
"DZcy;": '\U0000040F',
"Dagger;": '\U00002021',
"Darr;": '\U000021A1',
"Dashv;": '\U00002AE4',
"Dcaron;": '\U0000010E',
"Dcy;": '\U00000414',
"Del;": '\U00002207',
"Delta;": '\U00000394',
"Dfr;": '\U0001D507',
"DiacriticalAcute;": '\U000000B4',
"DiacriticalDot;": '\U000002D9',
"DiacriticalDoubleAcute;": '\U000002DD',
"DiacriticalGrave;": '\U00000060',
"DiacriticalTilde;": '\U000002DC',
"Diamond;": '\U000022C4',
"DifferentialD;": '\U00002146',
"Dopf;": '\U0001D53B',
"Dot;": '\U000000A8',
"DotDot;": '\U000020DC',
"DotEqual;": '\U00002250',
"DoubleContourIntegral;": '\U0000222F',
"DoubleDot;": '\U000000A8',
"DoubleDownArrow;": '\U000021D3',
"DoubleLeftArrow;": '\U000021D0',
"DoubleLeftRightArrow;": '\U000021D4',
"DoubleLeftTee;": '\U00002AE4',
"DoubleLongLeftArrow;": '\U000027F8',
"DoubleLongLeftRightArrow;": '\U000027FA',
"DoubleLongRightArrow;": '\U000027F9',
"DoubleRightArrow;": '\U000021D2',
"DoubleRightTee;": '\U000022A8',
"DoubleUpArrow;": '\U000021D1',
"DoubleUpDownArrow;": '\U000021D5',
"DoubleVerticalBar;": '\U00002225',
"DownArrow;": '\U00002193',
"DownArrowBar;": '\U00002913',
"DownArrowUpArrow;": '\U000021F5',
"DownBreve;": '\U00000311',
"DownLeftRightVector;": '\U00002950',
"DownLeftTeeVector;": '\U0000295E',
"DownLeftVector;": '\U000021BD',
"DownLeftVectorBar;": '\U00002956',
"DownRightTeeVector;": '\U0000295F',
"DownRightVector;": '\U000021C1',
"DownRightVectorBar;": '\U00002957',
"DownTee;": '\U000022A4',
"DownTeeArrow;": '\U000021A7',
"Downarrow;": '\U000021D3',
"Dscr;": '\U0001D49F',
"Dstrok;": '\U00000110',
"ENG;": '\U0000014A',
"ETH;": '\U000000D0',
"Eacute;": '\U000000C9',
"Ecaron;": '\U0000011A',
"Ecirc;": '\U000000CA',
"Ecy;": '\U0000042D',
"Edot;": '\U00000116',
"Efr;": '\U0001D508',
"Egrave;": '\U000000C8',
"Element;": '\U00002208',
"Emacr;": '\U00000112',
"EmptySmallSquare;": '\U000025FB',
"EmptyVerySmallSquare;": '\U000025AB',
"Eogon;": '\U00000118',
"Eopf;": '\U0001D53C',
"Epsilon;": '\U00000395',
"Equal;": '\U00002A75',
"EqualTilde;": '\U00002242',
"Equilibrium;": '\U000021CC',
"Escr;": '\U00002130',
"Esim;": '\U00002A73',
"Eta;": '\U00000397',
"Euml;": '\U000000CB',
"Exists;": '\U00002203',
"ExponentialE;": '\U00002147',
"Fcy;": '\U00000424',
"Ffr;": '\U0001D509',
"FilledSmallSquare;": '\U000025FC',
"FilledVerySmallSquare;": '\U000025AA',
"Fopf;": '\U0001D53D',
"ForAll;": '\U00002200',
"Fouriertrf;": '\U00002131',
"Fscr;": '\U00002131',
"GJcy;": '\U00000403',
"GT;": '\U0000003E',
"Gamma;": '\U00000393',
"Gammad;": '\U000003DC',
"Gbreve;": '\U0000011E',
"Gcedil;": '\U00000122',
"Gcirc;": '\U0000011C',
"Gcy;": '\U00000413',
"Gdot;": '\U00000120',
"Gfr;": '\U0001D50A',
"Gg;": '\U000022D9',
"Gopf;": '\U0001D53E',
"GreaterEqual;": '\U00002265',
"GreaterEqualLess;": '\U000022DB',
"GreaterFullEqual;": '\U00002267',
"GreaterGreater;": '\U00002AA2',
"GreaterLess;": '\U00002277',
"GreaterSlantEqual;": '\U00002A7E',
"GreaterTilde;": '\U00002273',
"Gscr;": '\U0001D4A2',
"Gt;": '\U0000226B',
"HARDcy;": '\U0000042A',
"Hacek;": '\U000002C7',
"Hat;": '\U0000005E',
"Hcirc;": '\U00000124',
"Hfr;": '\U0000210C',
"HilbertSpace;": '\U0000210B',
"Hopf;": '\U0000210D',
"HorizontalLine;": '\U00002500',
"Hscr;": '\U0000210B',
"Hstrok;": '\U00000126',
"HumpDownHump;": '\U0000224E',
"HumpEqual;": '\U0000224F',
"IEcy;": '\U00000415',
"IJlig;": '\U00000132',
"IOcy;": '\U00000401',
"Iacute;": '\U000000CD',
"Icirc;": '\U000000CE',
"Icy;": '\U00000418',
"Idot;": '\U00000130',
"Ifr;": '\U00002111',
"Igrave;": '\U000000CC',
"Im;": '\U00002111',
"Imacr;": '\U0000012A',
"ImaginaryI;": '\U00002148',
"Implies;": '\U000021D2',
"Int;": '\U0000222C',
"Integral;": '\U0000222B',
"Intersection;": '\U000022C2',
"InvisibleComma;": '\U00002063',
"InvisibleTimes;": '\U00002062',
"Iogon;": '\U0000012E',
"Iopf;": '\U0001D540',
"Iota;": '\U00000399',
"Iscr;": '\U00002110',
"Itilde;": '\U00000128',
"Iukcy;": '\U00000406',
"Iuml;": '\U000000CF',
"Jcirc;": '\U00000134',
"Jcy;": '\U00000419',
"Jfr;": '\U0001D50D',
"Jopf;": '\U0001D541',
"Jscr;": '\U0001D4A5',
"Jsercy;": '\U00000408',
"Jukcy;": '\U00000404',
"KHcy;": '\U00000425',
"KJcy;": '\U0000040C',
"Kappa;": '\U0000039A',
"Kcedil;": '\U00000136',
"Kcy;": '\U0000041A',
"Kfr;": '\U0001D50E',
"Kopf;": '\U0001D542',
"Kscr;": '\U0001D4A6',
"LJcy;": '\U00000409',
"LT;": '\U0000003C',
"Lacute;": '\U00000139',
"Lambda;": '\U0000039B',
"Lang;": '\U000027EA',
"Laplacetrf;": '\U00002112',
"Larr;": '\U0000219E',
"Lcaron;": '\U0000013D',
"Lcedil;": '\U0000013B',
"Lcy;": '\U0000041B',
"LeftAngleBracket;": '\U000027E8',
"LeftArrow;": '\U00002190',
"LeftArrowBar;": '\U000021E4',
"LeftArrowRightArrow;": '\U000021C6',
"LeftCeiling;": '\U00002308',
"LeftDoubleBracket;": '\U000027E6',
"LeftDownTeeVector;": '\U00002961',
"LeftDownVector;": '\U000021C3',
"LeftDownVectorBar;": '\U00002959',
"LeftFloor;": '\U0000230A',
"LeftRightArrow;": '\U00002194',
"LeftRightVector;": '\U0000294E',
"LeftTee;": '\U000022A3',
"LeftTeeArrow;": '\U000021A4',
"LeftTeeVector;": '\U0000295A',
"LeftTriangle;": '\U000022B2',
"LeftTriangleBar;": '\U000029CF',
"LeftTriangleEqual;": '\U000022B4',
"LeftUpDownVector;": '\U00002951',
"LeftUpTeeVector;": '\U00002960',
"LeftUpVector;": '\U000021BF',
"LeftUpVectorBar;": '\U00002958',
"LeftVector;": '\U000021BC',
"LeftVectorBar;": '\U00002952',
"Leftarrow;": '\U000021D0',
"Leftrightarrow;": '\U000021D4',
"LessEqualGreater;": '\U000022DA',
"LessFullEqual;": '\U00002266',
"LessGreater;": '\U00002276',
"LessLess;": '\U00002AA1',
"LessSlantEqual;": '\U00002A7D',
"LessTilde;": '\U00002272',
"Lfr;": '\U0001D50F',
"Ll;": '\U000022D8',
"Lleftarrow;": '\U000021DA',
"Lmidot;": '\U0000013F',
"LongLeftArrow;": '\U000027F5',
"LongLeftRightArrow;": '\U000027F7',
"LongRightArrow;": '\U000027F6',
"Longleftarrow;": '\U000027F8',
"Longleftrightarrow;": '\U000027FA',
"Longrightarrow;": '\U000027F9',
"Lopf;": '\U0001D543',
"LowerLeftArrow;": '\U00002199',
"LowerRightArrow;": '\U00002198',
"Lscr;": '\U00002112',
"Lsh;": '\U000021B0',
"Lstrok;": '\U00000141',
"Lt;": '\U0000226A',
"Map;": '\U00002905',
"Mcy;": '\U0000041C',
"MediumSpace;": '\U0000205F',
"Mellintrf;": '\U00002133',
"Mfr;": '\U0001D510',
"MinusPlus;": '\U00002213',
"Mopf;": '\U0001D544',
"Mscr;": '\U00002133',
"Mu;": '\U0000039C',
"NJcy;": '\U0000040A',
"Nacute;": '\U00000143',
"Ncaron;": '\U00000147',
"Ncedil;": '\U00000145',
"Ncy;": '\U0000041D',
"NegativeMediumSpace;": '\U0000200B',
"NegativeThickSpace;": '\U0000200B',
"NegativeThinSpace;": '\U0000200B',
"NegativeVeryThinSpace;": '\U0000200B',
"NestedGreaterGreater;": '\U0000226B',
"NestedLessLess;": '\U0000226A',
"NewLine;": '\U0000000A',
"Nfr;": '\U0001D511',
"NoBreak;": '\U00002060',
"NonBreakingSpace;": '\U000000A0',
"Nopf;": '\U00002115',
"Not;": '\U00002AEC',
"NotCongruent;": '\U00002262',
"NotCupCap;": '\U0000226D',
"NotDoubleVerticalBar;": '\U00002226',
"NotElement;": '\U00002209',
"NotEqual;": '\U00002260',
"NotExists;": '\U00002204',
"NotGreater;": '\U0000226F',
"NotGreaterEqual;": '\U00002271',
"NotGreaterLess;": '\U00002279',
"NotGreaterTilde;": '\U00002275',
"NotLeftTriangle;": '\U000022EA',
"NotLeftTriangleEqual;": '\U000022EC',
"NotLess;": '\U0000226E',
"NotLessEqual;": '\U00002270',
"NotLessGreater;": '\U00002278',
"NotLessTilde;": '\U00002274',
"NotPrecedes;": '\U00002280',
"NotPrecedesSlantEqual;": '\U000022E0',
"NotReverseElement;": '\U0000220C',
"NotRightTriangle;": '\U000022EB',
"NotRightTriangleEqual;": '\U000022ED',
"NotSquareSubsetEqual;": '\U000022E2',
"NotSquareSupersetEqual;": '\U000022E3',
"NotSubsetEqual;": '\U00002288',
"NotSucceeds;": '\U00002281',
"NotSucceedsSlantEqual;": '\U000022E1',
"NotSupersetEqual;": '\U00002289',
"NotTilde;": '\U00002241',
"NotTildeEqual;": '\U00002244',
"NotTildeFullEqual;": '\U00002247',
"NotTildeTilde;": '\U00002249',
"NotVerticalBar;": '\U00002224',
"Nscr;": '\U0001D4A9',
"Ntilde;": '\U000000D1',
"Nu;": '\U0000039D',
"OElig;": '\U00000152',
"Oacute;": '\U000000D3',
"Ocirc;": '\U000000D4',
"Ocy;": '\U0000041E',
"Odblac;": '\U00000150',
"Ofr;": '\U0001D512',
"Ograve;": '\U000000D2',
"Omacr;": '\U0000014C',
"Omega;": '\U000003A9',
"Omicron;": '\U0000039F',
"Oopf;": '\U0001D546',
"OpenCurlyDoubleQuote;": '\U0000201C',
"OpenCurlyQuote;": '\U00002018',
"Or;": '\U00002A54',
"Oscr;": '\U0001D4AA',
"Oslash;": '\U000000D8',
"Otilde;": '\U000000D5',
"Otimes;": '\U00002A37',
"Ouml;": '\U000000D6',
"OverBar;": '\U0000203E',
"OverBrace;": '\U000023DE',
"OverBracket;": '\U000023B4',
"OverParenthesis;": '\U000023DC',
"PartialD;": '\U00002202',
"Pcy;": '\U0000041F',
"Pfr;": '\U0001D513',
"Phi;": '\U000003A6',
"Pi;": '\U000003A0',
"PlusMinus;": '\U000000B1',
"Poincareplane;": '\U0000210C',
"Popf;": '\U00002119',
"Pr;": '\U00002ABB',
"Precedes;": '\U0000227A',
"PrecedesEqual;": '\U00002AAF',
"PrecedesSlantEqual;": '\U0000227C',
"PrecedesTilde;": '\U0000227E',
"Prime;": '\U00002033',
"Product;": '\U0000220F',
"Proportion;": '\U00002237',
"Proportional;": '\U0000221D',
"Pscr;": '\U0001D4AB',
"Psi;": '\U000003A8',
"QUOT;": '\U00000022',
"Qfr;": '\U0001D514',
"Qopf;": '\U0000211A',
"Qscr;": '\U0001D4AC',
"RBarr;": '\U00002910',
"REG;": '\U000000AE',
"Racute;": '\U00000154',
"Rang;": '\U000027EB',
"Rarr;": '\U000021A0',
"Rarrtl;": '\U00002916',
"Rcaron;": '\U00000158',
"Rcedil;": '\U00000156',
"Rcy;": '\U00000420',
"Re;": '\U0000211C',
"ReverseElement;": '\U0000220B',
"ReverseEquilibrium;": '\U000021CB',
"ReverseUpEquilibrium;": '\U0000296F',
"Rfr;": '\U0000211C',
"Rho;": '\U000003A1',
"RightAngleBracket;": '\U000027E9',
"RightArrow;": '\U00002192',
"RightArrowBar;": '\U000021E5',
"RightArrowLeftArrow;": '\U000021C4',
"RightCeiling;": '\U00002309',
"RightDoubleBracket;": '\U000027E7',
"RightDownTeeVector;": '\U0000295D',
"RightDownVector;": '\U000021C2',
"RightDownVectorBar;": '\U00002955',
"RightFloor;": '\U0000230B',
"RightTee;": '\U000022A2',
"RightTeeArrow;": '\U000021A6',
"RightTeeVector;": '\U0000295B',
"RightTriangle;": '\U000022B3',
"RightTriangleBar;": '\U000029D0',
"RightTriangleEqual;": '\U000022B5',
"RightUpDownVector;": '\U0000294F',
"RightUpTeeVector;": '\U0000295C',
"RightUpVector;": '\U000021BE',
"RightUpVectorBar;": '\U00002954',
"RightVector;": '\U000021C0',
"RightVectorBar;": '\U00002953',
"Rightarrow;": '\U000021D2',
"Ropf;": '\U0000211D',
"RoundImplies;": '\U00002970',
"Rrightarrow;": '\U000021DB',
"Rscr;": '\U0000211B',
"Rsh;": '\U000021B1',
"RuleDelayed;": '\U000029F4',
"SHCHcy;": '\U00000429',
"SHcy;": '\U00000428',
"SOFTcy;": '\U0000042C',
"Sacute;": '\U0000015A',
"Sc;": '\U00002ABC',
"Scaron;": '\U00000160',
"Scedil;": '\U0000015E',
"Scirc;": '\U0000015C',
"Scy;": '\U00000421',
"Sfr;": '\U0001D516',
"ShortDownArrow;": '\U00002193',
"ShortLeftArrow;": '\U00002190',
"ShortRightArrow;": '\U00002192',
"ShortUpArrow;": '\U00002191',
"Sigma;": '\U000003A3',
"SmallCircle;": '\U00002218',
"Sopf;": '\U0001D54A',
"Sqrt;": '\U0000221A',
"Square;": '\U000025A1',
"SquareIntersection;": '\U00002293',
"SquareSubset;": '\U0000228F',
"SquareSubsetEqual;": '\U00002291',
"SquareSuperset;": '\U00002290',
"SquareSupersetEqual;": '\U00002292',
"SquareUnion;": '\U00002294',
"Sscr;": '\U0001D4AE',
"Star;": '\U000022C6',
"Sub;": '\U000022D0',
"Subset;": '\U000022D0',
"SubsetEqual;": '\U00002286',
"Succeeds;": '\U0000227B',
"SucceedsEqual;": '\U00002AB0',
"SucceedsSlantEqual;": '\U0000227D',
"SucceedsTilde;": '\U0000227F',
"SuchThat;": '\U0000220B',
"Sum;": '\U00002211',
"Sup;": '\U000022D1',
"Superset;": '\U00002283',
"SupersetEqual;": '\U00002287',
"Supset;": '\U000022D1',
"THORN;": '\U000000DE',
"TRADE;": '\U00002122',
"TSHcy;": '\U0000040B',
"TScy;": '\U00000426',
"Tab;": '\U00000009',
"Tau;": '\U000003A4',
"Tcaron;": '\U00000164',
"Tcedil;": '\U00000162',
"Tcy;": '\U00000422',
"Tfr;": '\U0001D517',
"Therefore;": '\U00002234',
"Theta;": '\U00000398',
"ThinSpace;": '\U00002009',
"Tilde;": '\U0000223C',
"TildeEqual;": '\U00002243',
"TildeFullEqual;": '\U00002245',
"TildeTilde;": '\U00002248',
"Topf;": '\U0001D54B',
"TripleDot;": '\U000020DB',
"Tscr;": '\U0001D4AF',
"Tstrok;": '\U00000166',
"Uacute;": '\U000000DA',
"Uarr;": '\U0000219F',
"Uarrocir;": '\U00002949',
"Ubrcy;": '\U0000040E',
"Ubreve;": '\U0000016C',
"Ucirc;": '\U000000DB',
"Ucy;": '\U00000423',
"Udblac;": '\U00000170',
"Ufr;": '\U0001D518',
"Ugrave;": '\U000000D9',
"Umacr;": '\U0000016A',
"UnderBar;": '\U0000005F',
"UnderBrace;": '\U000023DF',
"UnderBracket;": '\U000023B5',
"UnderParenthesis;": '\U000023DD',
"Union;": '\U000022C3',
"UnionPlus;": '\U0000228E',
"Uogon;": '\U00000172',
"Uopf;": '\U0001D54C',
"UpArrow;": '\U00002191',
"UpArrowBar;": '\U00002912',
"UpArrowDownArrow;": '\U000021C5',
"UpDownArrow;": '\U00002195',
"UpEquilibrium;": '\U0000296E',
"UpTee;": '\U000022A5',
"UpTeeArrow;": '\U000021A5',
"Uparrow;": '\U000021D1',
"Updownarrow;": '\U000021D5',
"UpperLeftArrow;": '\U00002196',
"UpperRightArrow;": '\U00002197',
"Upsi;": '\U000003D2',
"Upsilon;": '\U000003A5',
"Uring;": '\U0000016E',
"Uscr;": '\U0001D4B0',
"Utilde;": '\U00000168',
"Uuml;": '\U000000DC',
"VDash;": '\U000022AB',
"Vbar;": '\U00002AEB',
"Vcy;": '\U00000412',
"Vdash;": '\U000022A9',
"Vdashl;": '\U00002AE6',
"Vee;": '\U000022C1',
"Verbar;": '\U00002016',
"Vert;": '\U00002016',
"VerticalBar;": '\U00002223',
"VerticalLine;": '\U0000007C',
"VerticalSeparator;": '\U00002758',
"VerticalTilde;": '\U00002240',
"VeryThinSpace;": '\U0000200A',
"Vfr;": '\U0001D519',
"Vopf;": '\U0001D54D',
"Vscr;": '\U0001D4B1',
"Vvdash;": '\U000022AA',
"Wcirc;": '\U00000174',
"Wedge;": '\U000022C0',
"Wfr;": '\U0001D51A',
"Wopf;": '\U0001D54E',
"Wscr;": '\U0001D4B2',
"Xfr;": '\U0001D51B',
"Xi;": '\U0000039E',
"Xopf;": '\U0001D54F',
"Xscr;": '\U0001D4B3',
"YAcy;": '\U0000042F',
"YIcy;": '\U00000407',
"YUcy;": '\U0000042E',
"Yacute;": '\U000000DD',
"Ycirc;": '\U00000176',
"Ycy;": '\U0000042B',
"Yfr;": '\U0001D51C',
"Yopf;": '\U0001D550',
"Yscr;": '\U0001D4B4',
"Yuml;": '\U00000178',
"ZHcy;": '\U00000416',
"Zacute;": '\U00000179',
"Zcaron;": '\U0000017D',
"Zcy;": '\U00000417',
"Zdot;": '\U0000017B',
"ZeroWidthSpace;": '\U0000200B',
"Zeta;": '\U00000396',
"Zfr;": '\U00002128',
"Zopf;": '\U00002124',
"Zscr;": '\U0001D4B5',
"aacute;": '\U000000E1',
"abreve;": '\U00000103',
"ac;": '\U0000223E',
"acd;": '\U0000223F',
"acirc;": '\U000000E2',
"acute;": '\U000000B4',
"acy;": '\U00000430',
"aelig;": '\U000000E6',
"af;": '\U00002061',
"afr;": '\U0001D51E',
"agrave;": '\U000000E0',
"alefsym;": '\U00002135',
"aleph;": '\U00002135',
"alpha;": '\U000003B1',
"amacr;": '\U00000101',
"amalg;": '\U00002A3F',
"amp;": '\U00000026',
"and;": '\U00002227',
"andand;": '\U00002A55',
"andd;": '\U00002A5C',
"andslope;": '\U00002A58',
"andv;": '\U00002A5A',
"ang;": '\U00002220',
"ange;": '\U000029A4',
"angle;": '\U00002220',
"angmsd;": '\U00002221',
"angmsdaa;": '\U000029A8',
"angmsdab;": '\U000029A9',
"angmsdac;": '\U000029AA',
"angmsdad;": '\U000029AB',
"angmsdae;": '\U000029AC',
"angmsdaf;": '\U000029AD',
"angmsdag;": '\U000029AE',
"angmsdah;": '\U000029AF',
"angrt;": '\U0000221F',
"angrtvb;": '\U000022BE',
"angrtvbd;": '\U0000299D',
"angsph;": '\U00002222',
"angst;": '\U000000C5',
"angzarr;": '\U0000237C',
"aogon;": '\U00000105',
"aopf;": '\U0001D552',
"ap;": '\U00002248',
"apE;": '\U00002A70',
"apacir;": '\U00002A6F',
"ape;": '\U0000224A',
"apid;": '\U0000224B',
"apos;": '\U00000027',
"approx;": '\U00002248',
"approxeq;": '\U0000224A',
"aring;": '\U000000E5',
"ascr;": '\U0001D4B6',
"ast;": '\U0000002A',
"asymp;": '\U00002248',
"asympeq;": '\U0000224D',
"atilde;": '\U000000E3',
"auml;": '\U000000E4',
"awconint;": '\U00002233',
"awint;": '\U00002A11',
"bNot;": '\U00002AED',
"backcong;": '\U0000224C',
"backepsilon;": '\U000003F6',
"backprime;": '\U00002035',
"backsim;": '\U0000223D',
"backsimeq;": '\U000022CD',
"barvee;": '\U000022BD',
"barwed;": '\U00002305',
"barwedge;": '\U00002305',
"bbrk;": '\U000023B5',
"bbrktbrk;": '\U000023B6',
"bcong;": '\U0000224C',
"bcy;": '\U00000431',
"bdquo;": '\U0000201E',
"becaus;": '\U00002235',
"because;": '\U00002235',
"bemptyv;": '\U000029B0',
"bepsi;": '\U000003F6',
"bernou;": '\U0000212C',
"beta;": '\U000003B2',
"beth;": '\U00002136',
"between;": '\U0000226C',
"bfr;": '\U0001D51F',
"bigcap;": '\U000022C2',
"bigcirc;": '\U000025EF',
"bigcup;": '\U000022C3',
"bigodot;": '\U00002A00',
"bigoplus;": '\U00002A01',
"bigotimes;": '\U00002A02',
"bigsqcup;": '\U00002A06',
"bigstar;": '\U00002605',
"bigtriangledown;": '\U000025BD',
"bigtriangleup;": '\U000025B3',
"biguplus;": '\U00002A04',
"bigvee;": '\U000022C1',
"bigwedge;": '\U000022C0',
"bkarow;": '\U0000290D',
"blacklozenge;": '\U000029EB',
"blacksquare;": '\U000025AA',
"blacktriangle;": '\U000025B4',
"blacktriangledown;": '\U000025BE',
"blacktriangleleft;": '\U000025C2',
"blacktriangleright;": '\U000025B8',
"blank;": '\U00002423',
"blk12;": '\U00002592',
"blk14;": '\U00002591',
"blk34;": '\U00002593',
"block;": '\U00002588',
"bnot;": '\U00002310',
"bopf;": '\U0001D553',
"bot;": '\U000022A5',
"bottom;": '\U000022A5',
"bowtie;": '\U000022C8',
"boxDL;": '\U00002557',
"boxDR;": '\U00002554',
"boxDl;": '\U00002556',
"boxDr;": '\U00002553',
"boxH;": '\U00002550',
"boxHD;": '\U00002566',
"boxHU;": '\U00002569',
"boxHd;": '\U00002564',
"boxHu;": '\U00002567',
"boxUL;": '\U0000255D',
"boxUR;": '\U0000255A',
"boxUl;": '\U0000255C',
"boxUr;": '\U00002559',
"boxV;": '\U00002551',
"boxVH;": '\U0000256C',
"boxVL;": '\U00002563',
"boxVR;": '\U00002560',
"boxVh;": '\U0000256B',
"boxVl;": '\U00002562',
"boxVr;": '\U0000255F',
"boxbox;": '\U000029C9',
"boxdL;": '\U00002555',
"boxdR;": '\U00002552',
"boxdl;": '\U00002510',
"boxdr;": '\U0000250C',
"boxh;": '\U00002500',
"boxhD;": '\U00002565',
"boxhU;": '\U00002568',
"boxhd;": '\U0000252C',
"boxhu;": '\U00002534',
"boxminus;": '\U0000229F',
"boxplus;": '\U0000229E',
"boxtimes;": '\U000022A0',
"boxuL;": '\U0000255B',
"boxuR;": '\U00002558',
"boxul;": '\U00002518',
"boxur;": '\U00002514',
"boxv;": '\U00002502',
"boxvH;": '\U0000256A',
"boxvL;": '\U00002561',
"boxvR;": '\U0000255E',
"boxvh;": '\U0000253C',
"boxvl;": '\U00002524',
"boxvr;": '\U0000251C',
"bprime;": '\U00002035',
"breve;": '\U000002D8',
"brvbar;": '\U000000A6',
"bscr;": '\U0001D4B7',
"bsemi;": '\U0000204F',
"bsim;": '\U0000223D',
"bsime;": '\U000022CD',
"bsol;": '\U0000005C',
"bsolb;": '\U000029C5',
"bsolhsub;": '\U000027C8',
"bull;": '\U00002022',
"bullet;": '\U00002022',
"bump;": '\U0000224E',
"bumpE;": '\U00002AAE',
"bumpe;": '\U0000224F',
"bumpeq;": '\U0000224F',
"cacute;": '\U00000107',
"cap;": '\U00002229',
"capand;": '\U00002A44',
"capbrcup;": '\U00002A49',
"capcap;": '\U00002A4B',
"capcup;": '\U00002A47',
"capdot;": '\U00002A40',
"caret;": '\U00002041',
"caron;": '\U000002C7',
"ccaps;": '\U00002A4D',
"ccaron;": '\U0000010D',
"ccedil;": '\U000000E7',
"ccirc;": '\U00000109',
"ccups;": '\U00002A4C',
"ccupssm;": '\U00002A50',
"cdot;": '\U0000010B',
"cedil;": '\U000000B8',
"cemptyv;": '\U000029B2',
"cent;": '\U000000A2',
"centerdot;": '\U000000B7',
"cfr;": '\U0001D520',
"chcy;": '\U00000447',
"check;": '\U00002713',
"checkmark;": '\U00002713',
"chi;": '\U000003C7',
"cir;": '\U000025CB',
"cirE;": '\U000029C3',
"circ;": '\U000002C6',
"circeq;": '\U00002257',
"circlearrowleft;": '\U000021BA',
"circlearrowright;": '\U000021BB',
"circledR;": '\U000000AE',
"circledS;": '\U000024C8',
"circledast;": '\U0000229B',
"circledcirc;": '\U0000229A',
"circleddash;": '\U0000229D',
"cire;": '\U00002257',
"cirfnint;": '\U00002A10',
"cirmid;": '\U00002AEF',
"cirscir;": '\U000029C2',
"clubs;": '\U00002663',
"clubsuit;": '\U00002663',
"colon;": '\U0000003A',
"colone;": '\U00002254',
"coloneq;": '\U00002254',
"comma;": '\U0000002C',
"commat;": '\U00000040',
"comp;": '\U00002201',
"compfn;": '\U00002218',
"complement;": '\U00002201',
"complexes;": '\U00002102',
"cong;": '\U00002245',
"congdot;": '\U00002A6D',
"conint;": '\U0000222E',
"copf;": '\U0001D554',
"coprod;": '\U00002210',
"copy;": '\U000000A9',
"copysr;": '\U00002117',
"crarr;": '\U000021B5',
"cross;": '\U00002717',
"cscr;": '\U0001D4B8',
"csub;": '\U00002ACF',
"csube;": '\U00002AD1',
"csup;": '\U00002AD0',
"csupe;": '\U00002AD2',
"ctdot;": '\U000022EF',
"cudarrl;": '\U00002938',
"cudarrr;": '\U00002935',
"cuepr;": '\U000022DE',
"cuesc;": '\U000022DF',
"cularr;": '\U000021B6',
"cularrp;": '\U0000293D',
"cup;": '\U0000222A',
"cupbrcap;": '\U00002A48',
"cupcap;": '\U00002A46',
"cupcup;": '\U00002A4A',
"cupdot;": '\U0000228D',
"cupor;": '\U00002A45',
"curarr;": '\U000021B7',
"curarrm;": '\U0000293C',
"curlyeqprec;": '\U000022DE',
"curlyeqsucc;": '\U000022DF',
"curlyvee;": '\U000022CE',
"curlywedge;": '\U000022CF',
"curren;": '\U000000A4',
"curvearrowleft;": '\U000021B6',
"curvearrowright;": '\U000021B7',
"cuvee;": '\U000022CE',
"cuwed;": '\U000022CF',
"cwconint;": '\U00002232',
"cwint;": '\U00002231',
"cylcty;": '\U0000232D',
"dArr;": '\U000021D3',
"dHar;": '\U00002965',
"dagger;": '\U00002020',
"daleth;": '\U00002138',
"darr;": '\U00002193',
"dash;": '\U00002010',
"dashv;": '\U000022A3',
"dbkarow;": '\U0000290F',
"dblac;": '\U000002DD',
"dcaron;": '\U0000010F',
"dcy;": '\U00000434',
"dd;": '\U00002146',
"ddagger;": '\U00002021',
"ddarr;": '\U000021CA',
"ddotseq;": '\U00002A77',
"deg;": '\U000000B0',
"delta;": '\U000003B4',
"demptyv;": '\U000029B1',
"dfisht;": '\U0000297F',
"dfr;": '\U0001D521',
"dharl;": '\U000021C3',
"dharr;": '\U000021C2',
"diam;": '\U000022C4',
"diamond;": '\U000022C4',
"diamondsuit;": '\U00002666',
"diams;": '\U00002666',
"die;": '\U000000A8',
"digamma;": '\U000003DD',
"disin;": '\U000022F2',
"div;": '\U000000F7',
"divide;": '\U000000F7',
"divideontimes;": '\U000022C7',
"divonx;": '\U000022C7',
"djcy;": '\U00000452',
"dlcorn;": '\U0000231E',
"dlcrop;": '\U0000230D',
"dollar;": '\U00000024',
"dopf;": '\U0001D555',
"dot;": '\U000002D9',
"doteq;": '\U00002250',
"doteqdot;": '\U00002251',
"dotminus;": '\U00002238',
"dotplus;": '\U00002214',
"dotsquare;": '\U000022A1',
"doublebarwedge;": '\U00002306',
"downarrow;": '\U00002193',
"downdownarrows;": '\U000021CA',
"downharpoonleft;": '\U000021C3',
"downharpoonright;": '\U000021C2',
"drbkarow;": '\U00002910',
"drcorn;": '\U0000231F',
"drcrop;": '\U0000230C',
"dscr;": '\U0001D4B9',
"dscy;": '\U00000455',
"dsol;": '\U000029F6',
"dstrok;": '\U00000111',
"dtdot;": '\U000022F1',
"dtri;": '\U000025BF',
"dtrif;": '\U000025BE',
"duarr;": '\U000021F5',
"duhar;": '\U0000296F',
"dwangle;": '\U000029A6',
"dzcy;": '\U0000045F',
"dzigrarr;": '\U000027FF',
"eDDot;": '\U00002A77',
"eDot;": '\U00002251',
"eacute;": '\U000000E9',
"easter;": '\U00002A6E',
"ecaron;": '\U0000011B',
"ecir;": '\U00002256',
"ecirc;": '\U000000EA',
"ecolon;": '\U00002255',
"ecy;": '\U0000044D',
"edot;": '\U00000117',
"ee;": '\U00002147',
"efDot;": '\U00002252',
"efr;": '\U0001D522',
"eg;": '\U00002A9A',
"egrave;": '\U000000E8',
"egs;": '\U00002A96',
"egsdot;": '\U00002A98',
"el;": '\U00002A99',
"elinters;": '\U000023E7',
"ell;": '\U00002113',
"els;": '\U00002A95',
"elsdot;": '\U00002A97',
"emacr;": '\U00000113',
"empty;": '\U00002205',
"emptyset;": '\U00002205',
"emptyv;": '\U00002205',
"emsp;": '\U00002003',
"emsp13;": '\U00002004',
"emsp14;": '\U00002005',
"eng;": '\U0000014B',
"ensp;": '\U00002002',
"eogon;": '\U00000119',
"eopf;": '\U0001D556',
"epar;": '\U000022D5',
"eparsl;": '\U000029E3',
"eplus;": '\U00002A71',
"epsi;": '\U000003B5',
"epsilon;": '\U000003B5',
"epsiv;": '\U000003F5',
"eqcirc;": '\U00002256',
"eqcolon;": '\U00002255',
"eqsim;": '\U00002242',
"eqslantgtr;": '\U00002A96',
"eqslantless;": '\U00002A95',
"equals;": '\U0000003D',
"equest;": '\U0000225F',
"equiv;": '\U00002261',
"equivDD;": '\U00002A78',
"eqvparsl;": '\U000029E5',
"erDot;": '\U00002253',
"erarr;": '\U00002971',
"escr;": '\U0000212F',
"esdot;": '\U00002250',
"esim;": '\U00002242',
"eta;": '\U000003B7',
"eth;": '\U000000F0',
"euml;": '\U000000EB',
"euro;": '\U000020AC',
"excl;": '\U00000021',
"exist;": '\U00002203',
"expectation;": '\U00002130',
"exponentiale;": '\U00002147',
"fallingdotseq;": '\U00002252',
"fcy;": '\U00000444',
"female;": '\U00002640',
"ffilig;": '\U0000FB03',
"fflig;": '\U0000FB00',
"ffllig;": '\U0000FB04',
"ffr;": '\U0001D523',
"filig;": '\U0000FB01',
"flat;": '\U0000266D',
"fllig;": '\U0000FB02',
"fltns;": '\U000025B1',
"fnof;": '\U00000192',
"fopf;": '\U0001D557',
"forall;": '\U00002200',
"fork;": '\U000022D4',
"forkv;": '\U00002AD9',
"fpartint;": '\U00002A0D',
"frac12;": '\U000000BD',
"frac13;": '\U00002153',
"frac14;": '\U000000BC',
"frac15;": '\U00002155',
"frac16;": '\U00002159',
"frac18;": '\U0000215B',
"frac23;": '\U00002154',
"frac25;": '\U00002156',
"frac34;": '\U000000BE',
"frac35;": '\U00002157',
"frac38;": '\U0000215C',
"frac45;": '\U00002158',
"frac56;": '\U0000215A',
"frac58;": '\U0000215D',
"frac78;": '\U0000215E',
"frasl;": '\U00002044',
"frown;": '\U00002322',
"fscr;": '\U0001D4BB',
"gE;": '\U00002267',
"gEl;": '\U00002A8C',
"gacute;": '\U000001F5',
"gamma;": '\U000003B3',
"gammad;": '\U000003DD',
"gap;": '\U00002A86',
"gbreve;": '\U0000011F',
"gcirc;": '\U0000011D',
"gcy;": '\U00000433',
"gdot;": '\U00000121',
"ge;": '\U00002265',
"gel;": '\U000022DB',
"geq;": '\U00002265',
"geqq;": '\U00002267',
"geqslant;": '\U00002A7E',
"ges;": '\U00002A7E',
"gescc;": '\U00002AA9',
"gesdot;": '\U00002A80',
"gesdoto;": '\U00002A82',
"gesdotol;": '\U00002A84',
"gesles;": '\U00002A94',
"gfr;": '\U0001D524',
"gg;": '\U0000226B',
"ggg;": '\U000022D9',
"gimel;": '\U00002137',
"gjcy;": '\U00000453',
"gl;": '\U00002277',
"glE;": '\U00002A92',
"gla;": '\U00002AA5',
"glj;": '\U00002AA4',
"gnE;": '\U00002269',
"gnap;": '\U00002A8A',
"gnapprox;": '\U00002A8A',
"gne;": '\U00002A88',
"gneq;": '\U00002A88',
"gneqq;": '\U00002269',
"gnsim;": '\U000022E7',
"gopf;": '\U0001D558',
"grave;": '\U00000060',
"gscr;": '\U0000210A',
"gsim;": '\U00002273',
"gsime;": '\U00002A8E',
"gsiml;": '\U00002A90',
"gt;": '\U0000003E',
"gtcc;": '\U00002AA7',
"gtcir;": '\U00002A7A',
"gtdot;": '\U000022D7',
"gtlPar;": '\U00002995',
"gtquest;": '\U00002A7C',
"gtrapprox;": '\U00002A86',
"gtrarr;": '\U00002978',
"gtrdot;": '\U000022D7',
"gtreqless;": '\U000022DB',
"gtreqqless;": '\U00002A8C',
"gtrless;": '\U00002277',
"gtrsim;": '\U00002273',
"hArr;": '\U000021D4',
"hairsp;": '\U0000200A',
"half;": '\U000000BD',
"hamilt;": '\U0000210B',
"hardcy;": '\U0000044A',
"harr;": '\U00002194',
"harrcir;": '\U00002948',
"harrw;": '\U000021AD',
"hbar;": '\U0000210F',
"hcirc;": '\U00000125',
"hearts;": '\U00002665',
"heartsuit;": '\U00002665',
"hellip;": '\U00002026',
"hercon;": '\U000022B9',
"hfr;": '\U0001D525',
"hksearow;": '\U00002925',
"hkswarow;": '\U00002926',
"hoarr;": '\U000021FF',
"homtht;": '\U0000223B',
"hookleftarrow;": '\U000021A9',
"hookrightarrow;": '\U000021AA',
"hopf;": '\U0001D559',
"horbar;": '\U00002015',
"hscr;": '\U0001D4BD',
"hslash;": '\U0000210F',
"hstrok;": '\U00000127',
"hybull;": '\U00002043',
"hyphen;": '\U00002010',
"iacute;": '\U000000ED',
"ic;": '\U00002063',
"icirc;": '\U000000EE',
"icy;": '\U00000438',
"iecy;": '\U00000435',
"iexcl;": '\U000000A1',
"iff;": '\U000021D4',
"ifr;": '\U0001D526',
"igrave;": '\U000000EC',
"ii;": '\U00002148',
"iiiint;": '\U00002A0C',
"iiint;": '\U0000222D',
"iinfin;": '\U000029DC',
"iiota;": '\U00002129',
"ijlig;": '\U00000133',
"imacr;": '\U0000012B',
"image;": '\U00002111',
"imagline;": '\U00002110',
"imagpart;": '\U00002111',
"imath;": '\U00000131',
"imof;": '\U000022B7',
"imped;": '\U000001B5',
"in;": '\U00002208',
"incare;": '\U00002105',
"infin;": '\U0000221E',
"infintie;": '\U000029DD',
"inodot;": '\U00000131',
"int;": '\U0000222B',
"intcal;": '\U000022BA',
"integers;": '\U00002124',
"intercal;": '\U000022BA',
"intlarhk;": '\U00002A17',
"intprod;": '\U00002A3C',
"iocy;": '\U00000451',
"iogon;": '\U0000012F',
"iopf;": '\U0001D55A',
"iota;": '\U000003B9',
"iprod;": '\U00002A3C',
"iquest;": '\U000000BF',
"iscr;": '\U0001D4BE',
"isin;": '\U00002208',
"isinE;": '\U000022F9',
"isindot;": '\U000022F5',
"isins;": '\U000022F4',
"isinsv;": '\U000022F3',
"isinv;": '\U00002208',
"it;": '\U00002062',
"itilde;": '\U00000129',
"iukcy;": '\U00000456',
"iuml;": '\U000000EF',
"jcirc;": '\U00000135',
"jcy;": '\U00000439',
"jfr;": '\U0001D527',
"jmath;": '\U00000237',
"jopf;": '\U0001D55B',
"jscr;": '\U0001D4BF',
"jsercy;": '\U00000458',
"jukcy;": '\U00000454',
"kappa;": '\U000003BA',
"kappav;": '\U000003F0',
"kcedil;": '\U00000137',
"kcy;": '\U0000043A',
"kfr;": '\U0001D528',
"kgreen;": '\U00000138',
"khcy;": '\U00000445',
"kjcy;": '\U0000045C',
"kopf;": '\U0001D55C',
"kscr;": '\U0001D4C0',
"lAarr;": '\U000021DA',
"lArr;": '\U000021D0',
"lAtail;": '\U0000291B',
"lBarr;": '\U0000290E',
"lE;": '\U00002266',
"lEg;": '\U00002A8B',
"lHar;": '\U00002962',
"lacute;": '\U0000013A',
"laemptyv;": '\U000029B4',
"lagran;": '\U00002112',
"lambda;": '\U000003BB',
"lang;": '\U000027E8',
"langd;": '\U00002991',
"langle;": '\U000027E8',
"lap;": '\U00002A85',
"laquo;": '\U000000AB',
"larr;": '\U00002190',
"larrb;": '\U000021E4',
"larrbfs;": '\U0000291F',
"larrfs;": '\U0000291D',
"larrhk;": '\U000021A9',
"larrlp;": '\U000021AB',
"larrpl;": '\U00002939',
"larrsim;": '\U00002973',
"larrtl;": '\U000021A2',
"lat;": '\U00002AAB',
"latail;": '\U00002919',
"late;": '\U00002AAD',
"lbarr;": '\U0000290C',
"lbbrk;": '\U00002772',
"lbrace;": '\U0000007B',
"lbrack;": '\U0000005B',
"lbrke;": '\U0000298B',
"lbrksld;": '\U0000298F',
"lbrkslu;": '\U0000298D',
"lcaron;": '\U0000013E',
"lcedil;": '\U0000013C',
"lceil;": '\U00002308',
"lcub;": '\U0000007B',
"lcy;": '\U0000043B',
"ldca;": '\U00002936',
"ldquo;": '\U0000201C',
"ldquor;": '\U0000201E',
"ldrdhar;": '\U00002967',
"ldrushar;": '\U0000294B',
"ldsh;": '\U000021B2',
"le;": '\U00002264',
"leftarrow;": '\U00002190',
"leftarrowtail;": '\U000021A2',
"leftharpoondown;": '\U000021BD',
"leftharpoonup;": '\U000021BC',
"leftleftarrows;": '\U000021C7',
"leftrightarrow;": '\U00002194',
"leftrightarrows;": '\U000021C6',
"leftrightharpoons;": '\U000021CB',
"leftrightsquigarrow;": '\U000021AD',
"leftthreetimes;": '\U000022CB',
"leg;": '\U000022DA',
"leq;": '\U00002264',
"leqq;": '\U00002266',
"leqslant;": '\U00002A7D',
"les;": '\U00002A7D',
"lescc;": '\U00002AA8',
"lesdot;": '\U00002A7F',
"lesdoto;": '\U00002A81',
"lesdotor;": '\U00002A83',
"lesges;": '\U00002A93',
"lessapprox;": '\U00002A85',
"lessdot;": '\U000022D6',
"lesseqgtr;": '\U000022DA',
"lesseqqgtr;": '\U00002A8B',
"lessgtr;": '\U00002276',
"lesssim;": '\U00002272',
"lfisht;": '\U0000297C',
"lfloor;": '\U0000230A',
"lfr;": '\U0001D529',
"lg;": '\U00002276',
"lgE;": '\U00002A91',
"lhard;": '\U000021BD',
"lharu;": '\U000021BC',
"lharul;": '\U0000296A',
"lhblk;": '\U00002584',
"ljcy;": '\U00000459',
"ll;": '\U0000226A',
"llarr;": '\U000021C7',
"llcorner;": '\U0000231E',
"llhard;": '\U0000296B',
"lltri;": '\U000025FA',
"lmidot;": '\U00000140',
"lmoust;": '\U000023B0',
"lmoustache;": '\U000023B0',
"lnE;": '\U00002268',
"lnap;": '\U00002A89',
"lnapprox;": '\U00002A89',
"lne;": '\U00002A87',
"lneq;": '\U00002A87',
"lneqq;": '\U00002268',
"lnsim;": '\U000022E6',
"loang;": '\U000027EC',
"loarr;": '\U000021FD',
"lobrk;": '\U000027E6',
"longleftarrow;": '\U000027F5',
"longleftrightarrow;": '\U000027F7',
"longmapsto;": '\U000027FC',
"longrightarrow;": '\U000027F6',
"looparrowleft;": '\U000021AB',
"looparrowright;": '\U000021AC',
"lopar;": '\U00002985',
"lopf;": '\U0001D55D',
"loplus;": '\U00002A2D',
"lotimes;": '\U00002A34',
"lowast;": '\U00002217',
"lowbar;": '\U0000005F',
"loz;": '\U000025CA',
"lozenge;": '\U000025CA',
"lozf;": '\U000029EB',
"lpar;": '\U00000028',
"lparlt;": '\U00002993',
"lrarr;": '\U000021C6',
"lrcorner;": '\U0000231F',
"lrhar;": '\U000021CB',
"lrhard;": '\U0000296D',
"lrm;": '\U0000200E',
"lrtri;": '\U000022BF',
"lsaquo;": '\U00002039',
"lscr;": '\U0001D4C1',
"lsh;": '\U000021B0',
"lsim;": '\U00002272',
"lsime;": '\U00002A8D',
"lsimg;": '\U00002A8F',
"lsqb;": '\U0000005B',
"lsquo;": '\U00002018',
"lsquor;": '\U0000201A',
"lstrok;": '\U00000142',
"lt;": '\U0000003C',
"ltcc;": '\U00002AA6',
"ltcir;": '\U00002A79',
"ltdot;": '\U000022D6',
"lthree;": '\U000022CB',
"ltimes;": '\U000022C9',
"ltlarr;": '\U00002976',
"ltquest;": '\U00002A7B',
"ltrPar;": '\U00002996',
"ltri;": '\U000025C3',
"ltrie;": '\U000022B4',
"ltrif;": '\U000025C2',
"lurdshar;": '\U0000294A',
"luruhar;": '\U00002966',
"mDDot;": '\U0000223A',
"macr;": '\U000000AF',
"male;": '\U00002642',
"malt;": '\U00002720',
"maltese;": '\U00002720',
"map;": '\U000021A6',
"mapsto;": '\U000021A6',
"mapstodown;": '\U000021A7',
"mapstoleft;": '\U000021A4',
"mapstoup;": '\U000021A5',
"marker;": '\U000025AE',
"mcomma;": '\U00002A29',
"mcy;": '\U0000043C',
"mdash;": '\U00002014',
"measuredangle;": '\U00002221',
"mfr;": '\U0001D52A',
"mho;": '\U00002127',
"micro;": '\U000000B5',
"mid;": '\U00002223',
"midast;": '\U0000002A',
"midcir;": '\U00002AF0',
"middot;": '\U000000B7',
"minus;": '\U00002212',
"minusb;": '\U0000229F',
"minusd;": '\U00002238',
"minusdu;": '\U00002A2A',
"mlcp;": '\U00002ADB',
"mldr;": '\U00002026',
"mnplus;": '\U00002213',
"models;": '\U000022A7',
"mopf;": '\U0001D55E',
"mp;": '\U00002213',
"mscr;": '\U0001D4C2',
"mstpos;": '\U0000223E',
"mu;": '\U000003BC',
"multimap;": '\U000022B8',
"mumap;": '\U000022B8',
"nLeftarrow;": '\U000021CD',
"nLeftrightarrow;": '\U000021CE',
"nRightarrow;": '\U000021CF',
"nVDash;": '\U000022AF',
"nVdash;": '\U000022AE',
"nabla;": '\U00002207',
"nacute;": '\U00000144',
"nap;": '\U00002249',
"napos;": '\U00000149',
"napprox;": '\U00002249',
"natur;": '\U0000266E',
"natural;": '\U0000266E',
"naturals;": '\U00002115',
"nbsp;": '\U000000A0',
"ncap;": '\U00002A43',
"ncaron;": '\U00000148',
"ncedil;": '\U00000146',
"ncong;": '\U00002247',
"ncup;": '\U00002A42',
"ncy;": '\U0000043D',
"ndash;": '\U00002013',
"ne;": '\U00002260',
"neArr;": '\U000021D7',
"nearhk;": '\U00002924',
"nearr;": '\U00002197',
"nearrow;": '\U00002197',
"nequiv;": '\U00002262',
"nesear;": '\U00002928',
"nexist;": '\U00002204',
"nexists;": '\U00002204',
"nfr;": '\U0001D52B',
"nge;": '\U00002271',
"ngeq;": '\U00002271',
"ngsim;": '\U00002275',
"ngt;": '\U0000226F',
"ngtr;": '\U0000226F',
"nhArr;": '\U000021CE',
"nharr;": '\U000021AE',
"nhpar;": '\U00002AF2',
"ni;": '\U0000220B',
"nis;": '\U000022FC',
"nisd;": '\U000022FA',
"niv;": '\U0000220B',
"njcy;": '\U0000045A',
"nlArr;": '\U000021CD',
"nlarr;": '\U0000219A',
"nldr;": '\U00002025',
"nle;": '\U00002270',
"nleftarrow;": '\U0000219A',
"nleftrightarrow;": '\U000021AE',
"nleq;": '\U00002270',
"nless;": '\U0000226E',
"nlsim;": '\U00002274',
"nlt;": '\U0000226E',
"nltri;": '\U000022EA',
"nltrie;": '\U000022EC',
"nmid;": '\U00002224',
"nopf;": '\U0001D55F',
"not;": '\U000000AC',
"notin;": '\U00002209',
"notinva;": '\U00002209',
"notinvb;": '\U000022F7',
"notinvc;": '\U000022F6',
"notni;": '\U0000220C',
"notniva;": '\U0000220C',
"notnivb;": '\U000022FE',
"notnivc;": '\U000022FD',
"npar;": '\U00002226',
"nparallel;": '\U00002226',
"npolint;": '\U00002A14',
"npr;": '\U00002280',
"nprcue;": '\U000022E0',
"nprec;": '\U00002280',
"nrArr;": '\U000021CF',
"nrarr;": '\U0000219B',
"nrightarrow;": '\U0000219B',
"nrtri;": '\U000022EB',
"nrtrie;": '\U000022ED',
"nsc;": '\U00002281',
"nsccue;": '\U000022E1',
"nscr;": '\U0001D4C3',
"nshortmid;": '\U00002224',
"nshortparallel;": '\U00002226',
"nsim;": '\U00002241',
"nsime;": '\U00002244',
"nsimeq;": '\U00002244',
"nsmid;": '\U00002224',
"nspar;": '\U00002226',
"nsqsube;": '\U000022E2',
"nsqsupe;": '\U000022E3',
"nsub;": '\U00002284',
"nsube;": '\U00002288',
"nsubseteq;": '\U00002288',
"nsucc;": '\U00002281',
"nsup;": '\U00002285',
"nsupe;": '\U00002289',
"nsupseteq;": '\U00002289',
"ntgl;": '\U00002279',
"ntilde;": '\U000000F1',
"ntlg;": '\U00002278',
"ntriangleleft;": '\U000022EA',
"ntrianglelefteq;": '\U000022EC',
"ntriangleright;": '\U000022EB',
"ntrianglerighteq;": '\U000022ED',
"nu;": '\U000003BD',
"num;": '\U00000023',
"numero;": '\U00002116',
"numsp;": '\U00002007',
"nvDash;": '\U000022AD',
"nvHarr;": '\U00002904',
"nvdash;": '\U000022AC',
"nvinfin;": '\U000029DE',
"nvlArr;": '\U00002902',
"nvrArr;": '\U00002903',
"nwArr;": '\U000021D6',
"nwarhk;": '\U00002923',
"nwarr;": '\U00002196',
"nwarrow;": '\U00002196',
"nwnear;": '\U00002927',
"oS;": '\U000024C8',
"oacute;": '\U000000F3',
"oast;": '\U0000229B',
"ocir;": '\U0000229A',
"ocirc;": '\U000000F4',
"ocy;": '\U0000043E',
"odash;": '\U0000229D',
"odblac;": '\U00000151',
"odiv;": '\U00002A38',
"odot;": '\U00002299',
"odsold;": '\U000029BC',
"oelig;": '\U00000153',
"ofcir;": '\U000029BF',
"ofr;": '\U0001D52C',
"ogon;": '\U000002DB',
"ograve;": '\U000000F2',
"ogt;": '\U000029C1',
"ohbar;": '\U000029B5',
"ohm;": '\U000003A9',
"oint;": '\U0000222E',
"olarr;": '\U000021BA',
"olcir;": '\U000029BE',
"olcross;": '\U000029BB',
"oline;": '\U0000203E',
"olt;": '\U000029C0',
"omacr;": '\U0000014D',
"omega;": '\U000003C9',
"omicron;": '\U000003BF',
"omid;": '\U000029B6',
"ominus;": '\U00002296',
"oopf;": '\U0001D560',
"opar;": '\U000029B7',
"operp;": '\U000029B9',
"oplus;": '\U00002295',
"or;": '\U00002228',
"orarr;": '\U000021BB',
"ord;": '\U00002A5D',
"order;": '\U00002134',
"orderof;": '\U00002134',
"ordf;": '\U000000AA',
"ordm;": '\U000000BA',
"origof;": '\U000022B6',
"oror;": '\U00002A56',
"orslope;": '\U00002A57',
"orv;": '\U00002A5B',
"oscr;": '\U00002134',
"oslash;": '\U000000F8',
"osol;": '\U00002298',
"otilde;": '\U000000F5',
"otimes;": '\U00002297',
"otimesas;": '\U00002A36',
"ouml;": '\U000000F6',
"ovbar;": '\U0000233D',
"par;": '\U00002225',
"para;": '\U000000B6',
"parallel;": '\U00002225',
"parsim;": '\U00002AF3',
"parsl;": '\U00002AFD',
"part;": '\U00002202',
"pcy;": '\U0000043F',
"percnt;": '\U00000025',
"period;": '\U0000002E',
"permil;": '\U00002030',
"perp;": '\U000022A5',
"pertenk;": '\U00002031',
"pfr;": '\U0001D52D',
"phi;": '\U000003C6',
"phiv;": '\U000003D5',
"phmmat;": '\U00002133',
"phone;": '\U0000260E',
"pi;": '\U000003C0',
"pitchfork;": '\U000022D4',
"piv;": '\U000003D6',
"planck;": '\U0000210F',
"planckh;": '\U0000210E',
"plankv;": '\U0000210F',
"plus;": '\U0000002B',
"plusacir;": '\U00002A23',
"plusb;": '\U0000229E',
"pluscir;": '\U00002A22',
"plusdo;": '\U00002214',
"plusdu;": '\U00002A25',
"pluse;": '\U00002A72',
"plusmn;": '\U000000B1',
"plussim;": '\U00002A26',
"plustwo;": '\U00002A27',
"pm;": '\U000000B1',
"pointint;": '\U00002A15',
"popf;": '\U0001D561',
"pound;": '\U000000A3',
"pr;": '\U0000227A',
"prE;": '\U00002AB3',
"prap;": '\U00002AB7',
"prcue;": '\U0000227C',
"pre;": '\U00002AAF',
"prec;": '\U0000227A',
"precapprox;": '\U00002AB7',
"preccurlyeq;": '\U0000227C',
"preceq;": '\U00002AAF',
"precnapprox;": '\U00002AB9',
"precneqq;": '\U00002AB5',
"precnsim;": '\U000022E8',
"precsim;": '\U0000227E',
"prime;": '\U00002032',
"primes;": '\U00002119',
"prnE;": '\U00002AB5',
"prnap;": '\U00002AB9',
"prnsim;": '\U000022E8',
"prod;": '\U0000220F',
"profalar;": '\U0000232E',
"profline;": '\U00002312',
"profsurf;": '\U00002313',
"prop;": '\U0000221D',
"propto;": '\U0000221D',
"prsim;": '\U0000227E',
"prurel;": '\U000022B0',
"pscr;": '\U0001D4C5',
"psi;": '\U000003C8',
"puncsp;": '\U00002008',
"qfr;": '\U0001D52E',
"qint;": '\U00002A0C',
"qopf;": '\U0001D562',
"qprime;": '\U00002057',
"qscr;": '\U0001D4C6',
"quaternions;": '\U0000210D',
"quatint;": '\U00002A16',
"quest;": '\U0000003F',
"questeq;": '\U0000225F',
"quot;": '\U00000022',
"rAarr;": '\U000021DB',
"rArr;": '\U000021D2',
"rAtail;": '\U0000291C',
"rBarr;": '\U0000290F',
"rHar;": '\U00002964',
"racute;": '\U00000155',
"radic;": '\U0000221A',
"raemptyv;": '\U000029B3',
"rang;": '\U000027E9',
"rangd;": '\U00002992',
"range;": '\U000029A5',
"rangle;": '\U000027E9',
"raquo;": '\U000000BB',
"rarr;": '\U00002192',
"rarrap;": '\U00002975',
"rarrb;": '\U000021E5',
"rarrbfs;": '\U00002920',
"rarrc;": '\U00002933',
"rarrfs;": '\U0000291E',
"rarrhk;": '\U000021AA',
"rarrlp;": '\U000021AC',
"rarrpl;": '\U00002945',
"rarrsim;": '\U00002974',
"rarrtl;": '\U000021A3',
"rarrw;": '\U0000219D',
"ratail;": '\U0000291A',
"ratio;": '\U00002236',
"rationals;": '\U0000211A',
"rbarr;": '\U0000290D',
"rbbrk;": '\U00002773',
"rbrace;": '\U0000007D',
"rbrack;": '\U0000005D',
"rbrke;": '\U0000298C',
"rbrksld;": '\U0000298E',
"rbrkslu;": '\U00002990',
"rcaron;": '\U00000159',
"rcedil;": '\U00000157',
"rceil;": '\U00002309',
"rcub;": '\U0000007D',
"rcy;": '\U00000440',
"rdca;": '\U00002937',
"rdldhar;": '\U00002969',
"rdquo;": '\U0000201D',
"rdquor;": '\U0000201D',
"rdsh;": '\U000021B3',
"real;": '\U0000211C',
"realine;": '\U0000211B',
"realpart;": '\U0000211C',
"reals;": '\U0000211D',
"rect;": '\U000025AD',
"reg;": '\U000000AE',
"rfisht;": '\U0000297D',
"rfloor;": '\U0000230B',
"rfr;": '\U0001D52F',
"rhard;": '\U000021C1',
"rharu;": '\U000021C0',
"rharul;": '\U0000296C',
"rho;": '\U000003C1',
"rhov;": '\U000003F1',
"rightarrow;": '\U00002192',
"rightarrowtail;": '\U000021A3',
"rightharpoondown;": '\U000021C1',
"rightharpoonup;": '\U000021C0',
"rightleftarrows;": '\U000021C4',
"rightleftharpoons;": '\U000021CC',
"rightrightarrows;": '\U000021C9',
"rightsquigarrow;": '\U0000219D',
"rightthreetimes;": '\U000022CC',
"ring;": '\U000002DA',
"risingdotseq;": '\U00002253',
"rlarr;": '\U000021C4',
"rlhar;": '\U000021CC',
"rlm;": '\U0000200F',
"rmoust;": '\U000023B1',
"rmoustache;": '\U000023B1',
"rnmid;": '\U00002AEE',
"roang;": '\U000027ED',
"roarr;": '\U000021FE',
"robrk;": '\U000027E7',
"ropar;": '\U00002986',
"ropf;": '\U0001D563',
"roplus;": '\U00002A2E',
"rotimes;": '\U00002A35',
"rpar;": '\U00000029',
"rpargt;": '\U00002994',
"rppolint;": '\U00002A12',
"rrarr;": '\U000021C9',
"rsaquo;": '\U0000203A',
"rscr;": '\U0001D4C7',
"rsh;": '\U000021B1',
"rsqb;": '\U0000005D',
"rsquo;": '\U00002019',
"rsquor;": '\U00002019',
"rthree;": '\U000022CC',
"rtimes;": '\U000022CA',
"rtri;": '\U000025B9',
"rtrie;": '\U000022B5',
"rtrif;": '\U000025B8',
"rtriltri;": '\U000029CE',
"ruluhar;": '\U00002968',
"rx;": '\U0000211E',
"sacute;": '\U0000015B',
"sbquo;": '\U0000201A',
"sc;": '\U0000227B',
"scE;": '\U00002AB4',
"scap;": '\U00002AB8',
"scaron;": '\U00000161',
"sccue;": '\U0000227D',
"sce;": '\U00002AB0',
"scedil;": '\U0000015F',
"scirc;": '\U0000015D',
"scnE;": '\U00002AB6',
"scnap;": '\U00002ABA',
"scnsim;": '\U000022E9',
"scpolint;": '\U00002A13',
"scsim;": '\U0000227F',
"scy;": '\U00000441',
"sdot;": '\U000022C5',
"sdotb;": '\U000022A1',
"sdote;": '\U00002A66',
"seArr;": '\U000021D8',
"searhk;": '\U00002925',
"searr;": '\U00002198',
"searrow;": '\U00002198',
"sect;": '\U000000A7',
"semi;": '\U0000003B',
"seswar;": '\U00002929',
"setminus;": '\U00002216',
"setmn;": '\U00002216',
"sext;": '\U00002736',
"sfr;": '\U0001D530',
"sfrown;": '\U00002322',
"sharp;": '\U0000266F',
"shchcy;": '\U00000449',
"shcy;": '\U00000448',
"shortmid;": '\U00002223',
"shortparallel;": '\U00002225',
"shy;": '\U000000AD',
"sigma;": '\U000003C3',
"sigmaf;": '\U000003C2',
"sigmav;": '\U000003C2',
"sim;": '\U0000223C',
"simdot;": '\U00002A6A',
"sime;": '\U00002243',
"simeq;": '\U00002243',
"simg;": '\U00002A9E',
"simgE;": '\U00002AA0',
"siml;": '\U00002A9D',
"simlE;": '\U00002A9F',
"simne;": '\U00002246',
"simplus;": '\U00002A24',
"simrarr;": '\U00002972',
"slarr;": '\U00002190',
"smallsetminus;": '\U00002216',
"smashp;": '\U00002A33',
"smeparsl;": '\U000029E4',
"smid;": '\U00002223',
"smile;": '\U00002323',
"smt;": '\U00002AAA',
"smte;": '\U00002AAC',
"softcy;": '\U0000044C',
"sol;": '\U0000002F',
"solb;": '\U000029C4',
"solbar;": '\U0000233F',
"sopf;": '\U0001D564',
"spades;": '\U00002660',
"spadesuit;": '\U00002660',
"spar;": '\U00002225',
"sqcap;": '\U00002293',
"sqcup;": '\U00002294',
"sqsub;": '\U0000228F',
"sqsube;": '\U00002291',
"sqsubset;": '\U0000228F',
"sqsubseteq;": '\U00002291',
"sqsup;": '\U00002290',
"sqsupe;": '\U00002292',
"sqsupset;": '\U00002290',
"sqsupseteq;": '\U00002292',
"squ;": '\U000025A1',
"square;": '\U000025A1',
"squarf;": '\U000025AA',
"squf;": '\U000025AA',
"srarr;": '\U00002192',
"sscr;": '\U0001D4C8',
"ssetmn;": '\U00002216',
"ssmile;": '\U00002323',
"sstarf;": '\U000022C6',
"star;": '\U00002606',
"starf;": '\U00002605',
"straightepsilon;": '\U000003F5',
"straightphi;": '\U000003D5',
"strns;": '\U000000AF',
"sub;": '\U00002282',
"subE;": '\U00002AC5',
"subdot;": '\U00002ABD',
"sube;": '\U00002286',
"subedot;": '\U00002AC3',
"submult;": '\U00002AC1',
"subnE;": '\U00002ACB',
"subne;": '\U0000228A',
"subplus;": '\U00002ABF',
"subrarr;": '\U00002979',
"subset;": '\U00002282',
"subseteq;": '\U00002286',
"subseteqq;": '\U00002AC5',
"subsetneq;": '\U0000228A',
"subsetneqq;": '\U00002ACB',
"subsim;": '\U00002AC7',
"subsub;": '\U00002AD5',
"subsup;": '\U00002AD3',
"succ;": '\U0000227B',
"succapprox;": '\U00002AB8',
"succcurlyeq;": '\U0000227D',
"succeq;": '\U00002AB0',
"succnapprox;": '\U00002ABA',
"succneqq;": '\U00002AB6',
"succnsim;": '\U000022E9',
"succsim;": '\U0000227F',
"sum;": '\U00002211',
"sung;": '\U0000266A',
"sup;": '\U00002283',
"sup1;": '\U000000B9',
"sup2;": '\U000000B2',
"sup3;": '\U000000B3',
"supE;": '\U00002AC6',
"supdot;": '\U00002ABE',
"supdsub;": '\U00002AD8',
"supe;": '\U00002287',
"supedot;": '\U00002AC4',
"suphsol;": '\U000027C9',
"suphsub;": '\U00002AD7',
"suplarr;": '\U0000297B',
"supmult;": '\U00002AC2',
"supnE;": '\U00002ACC',
"supne;": '\U0000228B',
"supplus;": '\U00002AC0',
"supset;": '\U00002283',
"supseteq;": '\U00002287',
"supseteqq;": '\U00002AC6',
"supsetneq;": '\U0000228B',
"supsetneqq;": '\U00002ACC',
"supsim;": '\U00002AC8',
"supsub;": '\U00002AD4',
"supsup;": '\U00002AD6',
"swArr;": '\U000021D9',
"swarhk;": '\U00002926',
"swarr;": '\U00002199',
"swarrow;": '\U00002199',
"swnwar;": '\U0000292A',
"szlig;": '\U000000DF',
"target;": '\U00002316',
"tau;": '\U000003C4',
"tbrk;": '\U000023B4',
"tcaron;": '\U00000165',
"tcedil;": '\U00000163',
"tcy;": '\U00000442',
"tdot;": '\U000020DB',
"telrec;": '\U00002315',
"tfr;": '\U0001D531',
"there4;": '\U00002234',
"therefore;": '\U00002234',
"theta;": '\U000003B8',
"thetasym;": '\U000003D1',
"thetav;": '\U000003D1',
"thickapprox;": '\U00002248',
"thicksim;": '\U0000223C',
"thinsp;": '\U00002009',
"thkap;": '\U00002248',
"thksim;": '\U0000223C',
"thorn;": '\U000000FE',
"tilde;": '\U000002DC',
"times;": '\U000000D7',
"timesb;": '\U000022A0',
"timesbar;": '\U00002A31',
"timesd;": '\U00002A30',
"tint;": '\U0000222D',
"toea;": '\U00002928',
"top;": '\U000022A4',
"topbot;": '\U00002336',
"topcir;": '\U00002AF1',
"topf;": '\U0001D565',
"topfork;": '\U00002ADA',
"tosa;": '\U00002929',
"tprime;": '\U00002034',
"trade;": '\U00002122',
"triangle;": '\U000025B5',
"triangledown;": '\U000025BF',
"triangleleft;": '\U000025C3',
"trianglelefteq;": '\U000022B4',
"triangleq;": '\U0000225C',
"triangleright;": '\U000025B9',
"trianglerighteq;": '\U000022B5',
"tridot;": '\U000025EC',
"trie;": '\U0000225C',
"triminus;": '\U00002A3A',
"triplus;": '\U00002A39',
"trisb;": '\U000029CD',
"tritime;": '\U00002A3B',
"trpezium;": '\U000023E2',
"tscr;": '\U0001D4C9',
"tscy;": '\U00000446',
"tshcy;": '\U0000045B',
"tstrok;": '\U00000167',
"twixt;": '\U0000226C',
"twoheadleftarrow;": '\U0000219E',
"twoheadrightarrow;": '\U000021A0',
"uArr;": '\U000021D1',
"uHar;": '\U00002963',
"uacute;": '\U000000FA',
"uarr;": '\U00002191',
"ubrcy;": '\U0000045E',
"ubreve;": '\U0000016D',
"ucirc;": '\U000000FB',
"ucy;": '\U00000443',
"udarr;": '\U000021C5',
"udblac;": '\U00000171',
"udhar;": '\U0000296E',
"ufisht;": '\U0000297E',
"ufr;": '\U0001D532',
"ugrave;": '\U000000F9',
"uharl;": '\U000021BF',
"uharr;": '\U000021BE',
"uhblk;": '\U00002580',
"ulcorn;": '\U0000231C',
"ulcorner;": '\U0000231C',
"ulcrop;": '\U0000230F',
"ultri;": '\U000025F8',
"umacr;": '\U0000016B',
"uml;": '\U000000A8',
"uogon;": '\U00000173',
"uopf;": '\U0001D566',
"uparrow;": '\U00002191',
"updownarrow;": '\U00002195',
"upharpoonleft;": '\U000021BF',
"upharpoonright;": '\U000021BE',
"uplus;": '\U0000228E',
"upsi;": '\U000003C5',
"upsih;": '\U000003D2',
"upsilon;": '\U000003C5',
"upuparrows;": '\U000021C8',
"urcorn;": '\U0000231D',
"urcorner;": '\U0000231D',
"urcrop;": '\U0000230E',
"uring;": '\U0000016F',
"urtri;": '\U000025F9',
"uscr;": '\U0001D4CA',
"utdot;": '\U000022F0',
"utilde;": '\U00000169',
"utri;": '\U000025B5',
"utrif;": '\U000025B4',
"uuarr;": '\U000021C8',
"uuml;": '\U000000FC',
"uwangle;": '\U000029A7',
"vArr;": '\U000021D5',
"vBar;": '\U00002AE8',
"vBarv;": '\U00002AE9',
"vDash;": '\U000022A8',
"vangrt;": '\U0000299C',
"varepsilon;": '\U000003F5',
"varkappa;": '\U000003F0',
"varnothing;": '\U00002205',
"varphi;": '\U000003D5',
"varpi;": '\U000003D6',
"varpropto;": '\U0000221D',
"varr;": '\U00002195',
"varrho;": '\U000003F1',
"varsigma;": '\U000003C2',
"vartheta;": '\U000003D1',
"vartriangleleft;": '\U000022B2',
"vartriangleright;": '\U000022B3',
"vcy;": '\U00000432',
"vdash;": '\U000022A2',
"vee;": '\U00002228',
"veebar;": '\U000022BB',
"veeeq;": '\U0000225A',
"vellip;": '\U000022EE',
"verbar;": '\U0000007C',
"vert;": '\U0000007C',
"vfr;": '\U0001D533',
"vltri;": '\U000022B2',
"vopf;": '\U0001D567',
"vprop;": '\U0000221D',
"vrtri;": '\U000022B3',
"vscr;": '\U0001D4CB',
"vzigzag;": '\U0000299A',
"wcirc;": '\U00000175',
"wedbar;": '\U00002A5F',
"wedge;": '\U00002227',
"wedgeq;": '\U00002259',
"weierp;": '\U00002118',
"wfr;": '\U0001D534',
"wopf;": '\U0001D568',
"wp;": '\U00002118',
"wr;": '\U00002240',
"wreath;": '\U00002240',
"wscr;": '\U0001D4CC',
"xcap;": '\U000022C2',
"xcirc;": '\U000025EF',
"xcup;": '\U000022C3',
"xdtri;": '\U000025BD',
"xfr;": '\U0001D535',
"xhArr;": '\U000027FA',
"xharr;": '\U000027F7',
"xi;": '\U000003BE',
"xlArr;": '\U000027F8',
"xlarr;": '\U000027F5',
"xmap;": '\U000027FC',
"xnis;": '\U000022FB',
"xodot;": '\U00002A00',
"xopf;": '\U0001D569',
"xoplus;": '\U00002A01',
"xotime;": '\U00002A02',
"xrArr;": '\U000027F9',
"xrarr;": '\U000027F6',
"xscr;": '\U0001D4CD',
"xsqcup;": '\U00002A06',
"xuplus;": '\U00002A04',
"xutri;": '\U000025B3',
"xvee;": '\U000022C1',
"xwedge;": '\U000022C0',
"yacute;": '\U000000FD',
"yacy;": '\U0000044F',
"ycirc;": '\U00000177',
"ycy;": '\U0000044B',
"yen;": '\U000000A5',
"yfr;": '\U0001D536',
"yicy;": '\U00000457',
"yopf;": '\U0001D56A',
"yscr;": '\U0001D4CE',
"yucy;": '\U0000044E',
"yuml;": '\U000000FF',
"zacute;": '\U0000017A',
"zcaron;": '\U0000017E',
"zcy;": '\U00000437',
"zdot;": '\U0000017C',
"zeetrf;": '\U00002128',
"zeta;": '\U000003B6',
"zfr;": '\U0001D537',
"zhcy;": '\U00000436',
"zigrarr;": '\U000021DD',
"zopf;": '\U0001D56B',
"zscr;": '\U0001D4CF',
"zwj;": '\U0000200D',
"zwnj;": '\U0000200C',
"AElig": '\U000000C6',
"AMP": '\U00000026',
"Aacute": '\U000000C1',
"Acirc": '\U000000C2',
"Agrave": '\U000000C0',
"Aring": '\U000000C5',
"Atilde": '\U000000C3',
"Auml": '\U000000C4',
"COPY": '\U000000A9',
"Ccedil": '\U000000C7',
"ETH": '\U000000D0',
"Eacute": '\U000000C9',
"Ecirc": '\U000000CA',
"Egrave": '\U000000C8',
"Euml": '\U000000CB',
"GT": '\U0000003E',
"Iacute": '\U000000CD',
"Icirc": '\U000000CE',
"Igrave": '\U000000CC',
"Iuml": '\U000000CF',
"LT": '\U0000003C',
"Ntilde": '\U000000D1',
"Oacute": '\U000000D3',
"Ocirc": '\U000000D4',
"Ograve": '\U000000D2',
"Oslash": '\U000000D8',
"Otilde": '\U000000D5',
"Ouml": '\U000000D6',
"QUOT": '\U00000022',
"REG": '\U000000AE',
"THORN": '\U000000DE',
"Uacute": '\U000000DA',
"Ucirc": '\U000000DB',
"Ugrave": '\U000000D9',
"Uuml": '\U000000DC',
"Yacute": '\U000000DD',
"aacute": '\U000000E1',
"acirc": '\U000000E2',
"acute": '\U000000B4',
"aelig": '\U000000E6',
"agrave": '\U000000E0',
"amp": '\U00000026',
"aring": '\U000000E5',
"atilde": '\U000000E3',
"auml": '\U000000E4',
"brvbar": '\U000000A6',
"ccedil": '\U000000E7',
"cedil": '\U000000B8',
"cent": '\U000000A2',
"copy": '\U000000A9',
"curren": '\U000000A4',
"deg": '\U000000B0',
"divide": '\U000000F7',
"eacute": '\U000000E9',
"ecirc": '\U000000EA',
"egrave": '\U000000E8',
"eth": '\U000000F0',
"euml": '\U000000EB',
"frac12": '\U000000BD',
"frac14": '\U000000BC',
"frac34": '\U000000BE',
"gt": '\U0000003E',
"iacute": '\U000000ED',
"icirc": '\U000000EE',
"iexcl": '\U000000A1',
"igrave": '\U000000EC',
"iquest": '\U000000BF',
"iuml": '\U000000EF',
"laquo": '\U000000AB',
"lt": '\U0000003C',
"macr": '\U000000AF',
"micro": '\U000000B5',
"middot": '\U000000B7',
"nbsp": '\U000000A0',
"not": '\U000000AC',
"ntilde": '\U000000F1',
"oacute": '\U000000F3',
"ocirc": '\U000000F4',
"ograve": '\U000000F2',
"ordf": '\U000000AA',
"ordm": '\U000000BA',
"oslash": '\U000000F8',
"otilde": '\U000000F5',
"ouml": '\U000000F6',
"para": '\U000000B6',
"plusmn": '\U000000B1',
"pound": '\U000000A3',
"quot": '\U00000022',
"raquo": '\U000000BB',
"reg": '\U000000AE',
"sect": '\U000000A7',
"shy": '\U000000AD',
"sup1": '\U000000B9',
"sup2": '\U000000B2',
"sup3": '\U000000B3',
"szlig": '\U000000DF',
"thorn": '\U000000FE',
"times": '\U000000D7',
"uacute": '\U000000FA',
"ucirc": '\U000000FB',
"ugrave": '\U000000F9',
"uml": '\U000000A8',
"uuml": '\U000000FC',
"yacute": '\U000000FD',
"yen": '\U000000A5',
"yuml": '\U000000FF',
}
 
// HTML entities that are two unicode codepoints.
var entity2 = map[string][2]rune{
// TODO(nigeltao): Handle replacements that are wider than their names.
// "nLt;": {'\u226A', '\u20D2'},
// "nGt;": {'\u226B', '\u20D2'},
"NotEqualTilde;": {'\u2242', '\u0338'},
"NotGreaterFullEqual;": {'\u2267', '\u0338'},
"NotGreaterGreater;": {'\u226B', '\u0338'},
"NotGreaterSlantEqual;": {'\u2A7E', '\u0338'},
"NotHumpDownHump;": {'\u224E', '\u0338'},
"NotHumpEqual;": {'\u224F', '\u0338'},
"NotLeftTriangleBar;": {'\u29CF', '\u0338'},
"NotLessLess;": {'\u226A', '\u0338'},
"NotLessSlantEqual;": {'\u2A7D', '\u0338'},
"NotNestedGreaterGreater;": {'\u2AA2', '\u0338'},
"NotNestedLessLess;": {'\u2AA1', '\u0338'},
"NotPrecedesEqual;": {'\u2AAF', '\u0338'},
"NotRightTriangleBar;": {'\u29D0', '\u0338'},
"NotSquareSubset;": {'\u228F', '\u0338'},
"NotSquareSuperset;": {'\u2290', '\u0338'},
"NotSubset;": {'\u2282', '\u20D2'},
"NotSucceedsEqual;": {'\u2AB0', '\u0338'},
"NotSucceedsTilde;": {'\u227F', '\u0338'},
"NotSuperset;": {'\u2283', '\u20D2'},
"ThickSpace;": {'\u205F', '\u200A'},
"acE;": {'\u223E', '\u0333'},
"bne;": {'\u003D', '\u20E5'},
"bnequiv;": {'\u2261', '\u20E5'},
"caps;": {'\u2229', '\uFE00'},
"cups;": {'\u222A', '\uFE00'},
"fjlig;": {'\u0066', '\u006A'},
"gesl;": {'\u22DB', '\uFE00'},
"gvertneqq;": {'\u2269', '\uFE00'},
"gvnE;": {'\u2269', '\uFE00'},
"lates;": {'\u2AAD', '\uFE00'},
"lesg;": {'\u22DA', '\uFE00'},
"lvertneqq;": {'\u2268', '\uFE00'},
"lvnE;": {'\u2268', '\uFE00'},
"nGg;": {'\u22D9', '\u0338'},
"nGtv;": {'\u226B', '\u0338'},
"nLl;": {'\u22D8', '\u0338'},
"nLtv;": {'\u226A', '\u0338'},
"nang;": {'\u2220', '\u20D2'},
"napE;": {'\u2A70', '\u0338'},
"napid;": {'\u224B', '\u0338'},
"nbump;": {'\u224E', '\u0338'},
"nbumpe;": {'\u224F', '\u0338'},
"ncongdot;": {'\u2A6D', '\u0338'},
"nedot;": {'\u2250', '\u0338'},
"nesim;": {'\u2242', '\u0338'},
"ngE;": {'\u2267', '\u0338'},
"ngeqq;": {'\u2267', '\u0338'},
"ngeqslant;": {'\u2A7E', '\u0338'},
"nges;": {'\u2A7E', '\u0338'},
"nlE;": {'\u2266', '\u0338'},
"nleqq;": {'\u2266', '\u0338'},
"nleqslant;": {'\u2A7D', '\u0338'},
"nles;": {'\u2A7D', '\u0338'},
"notinE;": {'\u22F9', '\u0338'},
"notindot;": {'\u22F5', '\u0338'},
"nparsl;": {'\u2AFD', '\u20E5'},
"npart;": {'\u2202', '\u0338'},
"npre;": {'\u2AAF', '\u0338'},
"npreceq;": {'\u2AAF', '\u0338'},
"nrarrc;": {'\u2933', '\u0338'},
"nrarrw;": {'\u219D', '\u0338'},
"nsce;": {'\u2AB0', '\u0338'},
"nsubE;": {'\u2AC5', '\u0338'},
"nsubset;": {'\u2282', '\u20D2'},
"nsubseteqq;": {'\u2AC5', '\u0338'},
"nsucceq;": {'\u2AB0', '\u0338'},
"nsupE;": {'\u2AC6', '\u0338'},
"nsupset;": {'\u2283', '\u20D2'},
"nsupseteqq;": {'\u2AC6', '\u0338'},
"nvap;": {'\u224D', '\u20D2'},
"nvge;": {'\u2265', '\u20D2'},
"nvgt;": {'\u003E', '\u20D2'},
"nvle;": {'\u2264', '\u20D2'},
"nvlt;": {'\u003C', '\u20D2'},
"nvltrie;": {'\u22B4', '\u20D2'},
"nvrtrie;": {'\u22B5', '\u20D2'},
"nvsim;": {'\u223C', '\u20D2'},
"race;": {'\u223D', '\u0331'},
"smtes;": {'\u2AAC', '\uFE00'},
"sqcaps;": {'\u2293', '\uFE00'},
"sqcups;": {'\u2294', '\uFE00'},
"varsubsetneq;": {'\u228A', '\uFE00'},
"varsubsetneqq;": {'\u2ACB', '\uFE00'},
"varsupsetneq;": {'\u228B', '\uFE00'},
"varsupsetneqq;": {'\u2ACC', '\uFE00'},
"vnsub;": {'\u2282', '\u20D2'},
"vnsup;": {'\u2283', '\u20D2'},
"vsubnE;": {'\u2ACB', '\uFE00'},
"vsubne;": {'\u228A', '\uFE00'},
"vsupnE;": {'\u2ACC', '\uFE00'},
"vsupne;": {'\u228B', '\uFE00'},
}
/escape.go
0,0 → 1,258
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
// Package html provides functions for escaping and unescaping HTML text.
package html
 
import (
"bytes"
"strings"
"unicode/utf8"
)
 
type writer interface {
WriteString(string) (int, error)
}
 
// These replacements permit compatibility with old numeric entities that
// assumed Windows-1252 encoding.
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
var replacementTable = [...]rune{
'\u20AC', // First entry is what 0x80 should be replaced with.
'\u0081',
'\u201A',
'\u0192',
'\u201E',
'\u2026',
'\u2020',
'\u2021',
'\u02C6',
'\u2030',
'\u0160',
'\u2039',
'\u0152',
'\u008D',
'\u017D',
'\u008F',
'\u0090',
'\u2018',
'\u2019',
'\u201C',
'\u201D',
'\u2022',
'\u2013',
'\u2014',
'\u02DC',
'\u2122',
'\u0161',
'\u203A',
'\u0153',
'\u009D',
'\u017E',
'\u0178', // Last entry is 0x9F.
// 0x00->'\uFFFD' is handled programmatically.
// 0x0D->'\u000D' is a no-op.
}
 
// unescapeEntity reads an entity like "&lt;" from b[src:] and writes the
// corresponding "<" to b[dst:], returning the incremented dst and src cursors.
// Precondition: b[src] == '&' && dst <= src.
// attribute should be true if parsing an attribute value.
func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#consume-a-character-reference
 
// i starts at 1 because we already know that s[0] == '&'.
i, s := 1, b[src:]
 
if len(s) <= 1 {
b[dst] = b[src]
return dst + 1, src + 1
}
 
if s[i] == '#' {
if len(s) <= 3 { // We need to have at least "&#.".
b[dst] = b[src]
return dst + 1, src + 1
}
i++
c := s[i]
hex := false
if c == 'x' || c == 'X' {
hex = true
i++
}
 
x := '\x00'
for i < len(s) {
c = s[i]
i++
if hex {
if '0' <= c && c <= '9' {
x = 16*x + rune(c) - '0'
continue
} else if 'a' <= c && c <= 'f' {
x = 16*x + rune(c) - 'a' + 10
continue
} else if 'A' <= c && c <= 'F' {
x = 16*x + rune(c) - 'A' + 10
continue
}
} else if '0' <= c && c <= '9' {
x = 10*x + rune(c) - '0'
continue
}
if c != ';' {
i--
}
break
}
 
if i <= 3 { // No characters matched.
b[dst] = b[src]
return dst + 1, src + 1
}
 
if 0x80 <= x && x <= 0x9F {
// Replace characters from Windows-1252 with UTF-8 equivalents.
x = replacementTable[x-0x80]
} else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF {
// Replace invalid characters with the replacement character.
x = '\uFFFD'
}
 
return dst + utf8.EncodeRune(b[dst:], x), src + i
}
 
// Consume the maximum number of characters possible, with the
// consumed characters matching one of the named references.
 
for i < len(s) {
c := s[i]
i++
// Lower-cased characters are more common in entities, so we check for them first.
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
continue
}
if c != ';' {
i--
}
break
}
 
entityName := string(s[1:i])
if entityName == "" {
// No-op.
} else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' {
// No-op.
} else if x := entity[entityName]; x != 0 {
return dst + utf8.EncodeRune(b[dst:], x), src + i
} else if x := entity2[entityName]; x[0] != 0 {
dst1 := dst + utf8.EncodeRune(b[dst:], x[0])
return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i
} else if !attribute {
maxLen := len(entityName) - 1
if maxLen > longestEntityWithoutSemicolon {
maxLen = longestEntityWithoutSemicolon
}
for j := maxLen; j > 1; j-- {
if x := entity[entityName[:j]]; x != 0 {
return dst + utf8.EncodeRune(b[dst:], x), src + j + 1
}
}
}
 
dst1, src1 = dst+i, src+i
copy(b[dst:dst1], b[src:src1])
return dst1, src1
}
 
// unescape unescapes b's entities in-place, so that "a&lt;b" becomes "a<b".
func unescape(b []byte) []byte {
for i, c := range b {
if c == '&' {
dst, src := unescapeEntity(b, i, i, false)
for src < len(b) {
c := b[src]
if c == '&' {
dst, src = unescapeEntity(b, dst, src, false)
} else {
b[dst] = c
dst, src = dst+1, src+1
}
}
return b[0:dst]
}
}
return b
}
 
// lower lower-cases the A-Z bytes in b in-place, so that "aBc" becomes "abc".
func lower(b []byte) []byte {
for i, c := range b {
if 'A' <= c && c <= 'Z' {
b[i] = c + 'a' - 'A'
}
}
return b
}
 
const escapedChars = `&'<>"`
 
func escape(w writer, s string) error {
i := strings.IndexAny(s, escapedChars)
for i != -1 {
if _, err := w.WriteString(s[:i]); err != nil {
return err
}
var esc string
switch s[i] {
case '&':
esc = "&amp;"
case '\'':
esc = "&apos;"
case '<':
esc = "&lt;"
case '>':
esc = "&gt;"
case '"':
esc = "&quot;"
default:
panic("unrecognized escape character")
}
s = s[i+1:]
if _, err := w.WriteString(esc); err != nil {
return err
}
i = strings.IndexAny(s, escapedChars)
}
_, err := w.WriteString(s)
return err
}
 
// EscapeString escapes special characters like "<" to become "&lt;". It
// escapes only five such characters: amp, apos, lt, gt and quot.
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
// always true.
func EscapeString(s string) string {
if strings.IndexAny(s, escapedChars) == -1 {
return s
}
var buf bytes.Buffer
escape(&buf, s)
return buf.String()
}
 
// UnescapeString unescapes entities like "&lt;" to become "<". It unescapes a
// larger range of entities than EscapeString escapes. For example, "&aacute;"
// unescapes to "á", as does "&#225;" and "&xE1;".
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
// always true.
func UnescapeString(s string) string {
for _, c := range s {
if c == '&' {
return string(unescape([]byte(s)))
}
}
return s
}
/template/template.go
0,0 → 1,280
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"sync"
"text/template"
"text/template/parse"
)
 
// Template is a specialized template.Template that produces a safe HTML
// document fragment.
type Template struct {
escaped bool
// We could embed the text/template field, but it's safer not to because
// we need to keep our version of the name space and the underlying
// template's in sync.
text *template.Template
*nameSpace // common to all associated templates
}
 
// nameSpace is the data structure shared by all templates in an association.
type nameSpace struct {
mu sync.Mutex
set map[string]*Template
}
 
// Execute applies a parsed template to the specified data object,
// writing the output to wr.
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
t.nameSpace.mu.Lock()
if !t.escaped {
if err = escapeTemplates(t, t.Name()); err != nil {
t.escaped = true
}
}
t.nameSpace.mu.Unlock()
if err != nil {
return
}
return t.text.Execute(wr, data)
}
 
// ExecuteTemplate applies the template associated with t that has the given
// name to the specified data object and writes the output to wr.
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
tmpl, err := t.lookupAndEscapeTemplate(wr, name)
if err != nil {
return err
}
return tmpl.text.Execute(wr, data)
}
 
// lookupAndEscapeTemplate guarantees that the template with the given name
// is escaped, or returns an error if it cannot be. It returns the named
// template.
func (t *Template) lookupAndEscapeTemplate(wr io.Writer, name string) (tmpl *Template, err error) {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
tmpl = t.set[name]
if (tmpl == nil) != (t.text.Lookup(name) == nil) {
panic("html/template internal error: template escaping out of sync")
}
if tmpl != nil && !tmpl.escaped {
err = escapeTemplates(tmpl, name)
}
return tmpl, err
}
 
// Parse parses a string into a template. Nested template definitions
// will be associated with the top-level template t. Parse may be
// called multiple times to parse definitions of templates to associate
// with t. It is an error if a resulting template is non-empty (contains
// content other than template definitions) and would replace a
// non-empty template with the same name. (In multiple calls to Parse
// with the same receiver template, only one call can contain text
// other than space, comments, and template definitions.)
func (t *Template) Parse(src string) (*Template, error) {
t.nameSpace.mu.Lock()
t.escaped = false
t.nameSpace.mu.Unlock()
ret, err := t.text.Parse(src)
if err != nil {
return nil, err
}
// In general, all the named templates might have changed underfoot.
// Regardless, some new ones may have been defined.
// The template.Template set has been updated; update ours.
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
for _, v := range ret.Templates() {
name := v.Name()
tmpl := t.set[name]
if tmpl == nil {
tmpl = t.new(name)
}
tmpl.escaped = false
tmpl.text = v
}
return t, nil
}
 
// AddParseTree is unimplemented.
func (t *Template) AddParseTree(name string, tree *parse.Tree) error {
return fmt.Errorf("html/template: AddParseTree unimplemented")
}
 
// Clone is unimplemented.
func (t *Template) Clone(name string) error {
return fmt.Errorf("html/template: Clone unimplemented")
}
 
// New allocates a new HTML template with the given name.
func New(name string) *Template {
tmpl := &Template{
false,
template.New(name),
&nameSpace{
set: make(map[string]*Template),
},
}
tmpl.set[name] = tmpl
return tmpl
}
 
// New allocates a new HTML template associated with the given one
// and with the same delimiters. The association, which is transitive,
// allows one template to invoke another with a {{template}} action.
func (t *Template) New(name string) *Template {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
return t.new(name)
}
 
// new is the implementation of New, without the lock.
func (t *Template) new(name string) *Template {
tmpl := &Template{
false,
t.text.New(name),
t.nameSpace,
}
tmpl.set[name] = tmpl
return tmpl
}
 
// Name returns the name of the template.
func (t *Template) Name() string {
return t.text.Name()
}
 
// FuncMap is the type of the map defining the mapping from names to
// functions. Each function must have either a single return value, or two
// return values of which the second has type error. In that case, if the
// second (error) argument evaluates to non-nil during execution, execution
// terminates and Execute returns that error. FuncMap has the same base type
// as template.FuncMap, copied here so clients need not import "text/template".
type FuncMap map[string]interface{}
 
// Funcs adds the elements of the argument map to the template's function map.
// It panics if a value in the map is not a function with appropriate return
// type. However, it is legal to overwrite elements of the map. The return
// value is the template, so calls can be chained.
func (t *Template) Funcs(funcMap FuncMap) *Template {
t.text.Funcs(template.FuncMap(funcMap))
return t
}
 
// Delims sets the action delimiters to the specified strings, to be used in
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
// definitions will inherit the settings. An empty delimiter stands for the
// corresponding default: {{ or }}.
// The return value is the template, so calls can be chained.
func (t *Template) Delims(left, right string) *Template {
t.text.Delims(left, right)
return t
}
 
// Lookup returns the template with the given name that is associated with t,
// or nil if there is no such template.
func (t *Template) Lookup(name string) *Template {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
return t.set[name]
}
 
// Must panics if err is non-nil in the same way as template.Must.
func Must(t *Template, err error) *Template {
if err != nil {
panic(err)
}
return t
}
 
// ParseFiles creates a new Template and parses the template definitions from
// the named files. The returned template's name will have the (base) name and
// (parsed) contents of the first file. There must be at least one file.
// If an error occurs, parsing stops and the returned *Template is nil.
func ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(nil, filenames...)
}
 
// ParseFiles parses the named files and associates the resulting templates with
// t. If an error occurs, parsing stops and the returned template is nil;
// otherwise it is t. There must be at least one file.
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
return parseFiles(t, filenames...)
}
 
// parseFiles is the helper for the method and function. If the argument
// template is nil, it is created from the first file.
func parseFiles(t *Template, filenames ...string) (*Template, error) {
if len(filenames) == 0 {
// Not really a problem, but be consistent.
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
}
for _, filename := range filenames {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
s := string(b)
name := filepath.Base(filename)
// First template becomes return value if not already defined,
// and we use that one for subsequent New calls to associate
// all the templates together. Also, if this file has the same name
// as t, this file becomes the contents of t, so
// t, err := New(name).Funcs(xxx).ParseFiles(name)
// works. Otherwise we create a new template associated with t.
var tmpl *Template
if t == nil {
t = New(name)
}
if name == t.Name() {
tmpl = t
} else {
tmpl = t.New(name)
}
_, err = tmpl.Parse(s)
if err != nil {
return nil, err
}
}
return t, nil
}
 
// ParseGlob creates a new Template and parses the template definitions from the
// files identified by the pattern, which must match at least one file. The
// returned template will have the (base) name and (parsed) contents of the
// first file matched by the pattern. ParseGlob is equivalent to calling
// ParseFiles with the list of files matched by the pattern.
func ParseGlob(pattern string) (*Template, error) {
return parseGlob(nil, pattern)
}
 
// ParseGlob parses the template definitions in the files identified by the
// pattern and associates the resulting templates with t. The pattern is
// processed by filepath.Glob and must match at least one file. ParseGlob is
// equivalent to calling t.ParseFiles with the list of files matched by the
// pattern.
func (t *Template) ParseGlob(pattern string) (*Template, error) {
return parseGlob(t, pattern)
}
 
// parseGlob is the implementation of the function and method ParseGlob.
func parseGlob(t *Template, pattern string) (*Template, error) {
filenames, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
if len(filenames) == 0 {
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
}
return parseFiles(t, filenames...)
}
/template/js_test.go
0,0 → 1,401
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"math"
"strings"
"testing"
)
 
func TestNextJsCtx(t *testing.T) {
tests := []struct {
jsCtx jsCtx
s string
}{
// Statement terminators precede regexps.
{jsCtxRegexp, ";"},
// This is not airtight.
// ({ valueOf: function () { return 1 } } / 2)
// is valid JavaScript but in practice, devs do not do this.
// A block followed by a statement starting with a RegExp is
// much more common:
// while (x) {...} /foo/.test(x) || panic()
{jsCtxRegexp, "}"},
// But member, call, grouping, and array expression terminators
// precede div ops.
{jsCtxDivOp, ")"},
{jsCtxDivOp, "]"},
// At the start of a primary expression, array, or expression
// statement, expect a regexp.
{jsCtxRegexp, "("},
{jsCtxRegexp, "["},
{jsCtxRegexp, "{"},
// Assignment operators precede regexps as do all exclusively
// prefix and binary operators.
{jsCtxRegexp, "="},
{jsCtxRegexp, "+="},
{jsCtxRegexp, "*="},
{jsCtxRegexp, "*"},
{jsCtxRegexp, "!"},
// Whether the + or - is infix or prefix, it cannot precede a
// div op.
{jsCtxRegexp, "+"},
{jsCtxRegexp, "-"},
// An incr/decr op precedes a div operator.
// This is not airtight. In (g = ++/h/i) a regexp follows a
// pre-increment operator, but in practice devs do not try to
// increment or decrement regular expressions.
// (g++/h/i) where ++ is a postfix operator on g is much more
// common.
{jsCtxDivOp, "--"},
{jsCtxDivOp, "++"},
{jsCtxDivOp, "x--"},
// When we have many dashes or pluses, then they are grouped
// left to right.
{jsCtxRegexp, "x---"}, // A postfix -- then a -.
// return followed by a slash returns the regexp literal or the
// slash starts a regexp literal in an expression statement that
// is dead code.
{jsCtxRegexp, "return"},
{jsCtxRegexp, "return "},
{jsCtxRegexp, "return\t"},
{jsCtxRegexp, "return\n"},
{jsCtxRegexp, "return\u2028"},
// Identifiers can be divided and cannot validly be preceded by
// a regular expressions. Semicolon insertion cannot happen
// between an identifier and a regular expression on a new line
// because the one token lookahead for semicolon insertion has
// to conclude that it could be a div binary op and treat it as
// such.
{jsCtxDivOp, "x"},
{jsCtxDivOp, "x "},
{jsCtxDivOp, "x\t"},
{jsCtxDivOp, "x\n"},
{jsCtxDivOp, "x\u2028"},
{jsCtxDivOp, "preturn"},
// Numbers precede div ops.
{jsCtxDivOp, "0"},
// Dots that are part of a number are div preceders.
{jsCtxDivOp, "0."},
}
 
for _, test := range tests {
if nextJSCtx([]byte(test.s), jsCtxRegexp) != test.jsCtx {
t.Errorf("want %s got %q", test.jsCtx, test.s)
}
if nextJSCtx([]byte(test.s), jsCtxDivOp) != test.jsCtx {
t.Errorf("want %s got %q", test.jsCtx, test.s)
}
}
 
if nextJSCtx([]byte(" "), jsCtxRegexp) != jsCtxRegexp {
t.Error("Blank tokens")
}
 
if nextJSCtx([]byte(" "), jsCtxDivOp) != jsCtxDivOp {
t.Error("Blank tokens")
}
}
 
func TestJSValEscaper(t *testing.T) {
tests := []struct {
x interface{}
js string
}{
{int(42), " 42 "},
{uint(42), " 42 "},
{int16(42), " 42 "},
{uint16(42), " 42 "},
{int32(-42), " -42 "},
{uint32(42), " 42 "},
{int16(-42), " -42 "},
{uint16(42), " 42 "},
{int64(-42), " -42 "},
{uint64(42), " 42 "},
{uint64(1) << 53, " 9007199254740992 "},
// ulp(1 << 53) > 1 so this loses precision in JS
// but it is still a representable integer literal.
{uint64(1)<<53 + 1, " 9007199254740993 "},
{float32(1.0), " 1 "},
{float32(-1.0), " -1 "},
{float32(0.5), " 0.5 "},
{float32(-0.5), " -0.5 "},
{float32(1.0) / float32(256), " 0.00390625 "},
{float32(0), " 0 "},
{math.Copysign(0, -1), " -0 "},
{float64(1.0), " 1 "},
{float64(-1.0), " -1 "},
{float64(0.5), " 0.5 "},
{float64(-0.5), " -0.5 "},
{float64(0), " 0 "},
{math.Copysign(0, -1), " -0 "},
{"", `""`},
{"foo", `"foo"`},
// Newlines.
{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
// "\v" == "v" on IE 6 so use "\x0b" instead.
{"\t\x0b", `"\u0009\u000b"`},
{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
{[]interface{}{}, "[]"},
{[]interface{}{42, "foo", nil}, `[42,"foo",null]`},
{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
{"<!--", `"\u003c!--"`},
{"-->", `"--\u003e"`},
{"<![CDATA[", `"\u003c![CDATA["`},
{"]]>", `"]]\u003e"`},
{"</script", `"\u003c/script"`},
{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
}
 
for _, test := range tests {
if js := jsValEscaper(test.x); js != test.js {
t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
}
// Make sure that escaping corner cases are not broken
// by nesting.
a := []interface{}{test.x}
want := "[" + strings.TrimSpace(test.js) + "]"
if js := jsValEscaper(a); js != want {
t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
}
}
}
 
func TestJSStrEscaper(t *testing.T) {
tests := []struct {
x interface{}
esc string
}{
{"", ``},
{"foo", `foo`},
{"\u0000", `\0`},
{"\t", `\t`},
{"\n", `\n`},
{"\r", `\r`},
{"\u2028", `\u2028`},
{"\u2029", `\u2029`},
{"\\", `\\`},
{"\\n", `\\n`},
{"foo\r\nbar", `foo\r\nbar`},
// Preserve attribute boundaries.
{`"`, `\x22`},
{`'`, `\x27`},
// Allow embedding in HTML without further escaping.
{`&amp;`, `\x26amp;`},
// Prevent breaking out of text node and element boundaries.
{"</script>", `\x3c\/script\x3e`},
{"<![CDATA[", `\x3c![CDATA[`},
{"]]>", `]]\x3e`},
// http://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
// "The text in style, script, title, and textarea elements
// must not have an escaping text span start that is not
// followed by an escaping text span end."
// Furthermore, spoofing an escaping text span end could lead
// to different interpretation of a </script> sequence otherwise
// masked by the escaping text span, and spoofing a start could
// allow regular text content to be interpreted as script
// allowing script execution via a combination of a JS string
// injection followed by an HTML text injection.
{"<!--", `\x3c!--`},
{"-->", `--\x3e`},
// From http://code.google.com/p/doctype/wiki/ArticleUtf7
{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
`\x2bADw-script\x2bAD4-alert(1)\x2bADw-\/script\x2bAD4-`,
},
// Invalid UTF-8 sequence
{"foo\xA0bar", "foo\xA0bar"},
// Invalid unicode scalar value.
{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
}
 
for _, test := range tests {
esc := jsStrEscaper(test.x)
if esc != test.esc {
t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
}
}
}
 
func TestJSRegexpEscaper(t *testing.T) {
tests := []struct {
x interface{}
esc string
}{
{"", `(?:)`},
{"foo", `foo`},
{"\u0000", `\0`},
{"\t", `\t`},
{"\n", `\n`},
{"\r", `\r`},
{"\u2028", `\u2028`},
{"\u2029", `\u2029`},
{"\\", `\\`},
{"\\n", `\\n`},
{"foo\r\nbar", `foo\r\nbar`},
// Preserve attribute boundaries.
{`"`, `\x22`},
{`'`, `\x27`},
// Allow embedding in HTML without further escaping.
{`&amp;`, `\x26amp;`},
// Prevent breaking out of text node and element boundaries.
{"</script>", `\x3c\/script\x3e`},
{"<![CDATA[", `\x3c!\[CDATA\[`},
{"]]>", `\]\]\x3e`},
// Escaping text spans.
{"<!--", `\x3c!\-\-`},
{"-->", `\-\-\x3e`},
{"*", `\*`},
{"+", `\x2b`},
{"?", `\?`},
{"[](){}", `\[\]\(\)\{\}`},
{"$foo|x.y", `\$foo\|x\.y`},
{"x^y", `x\^y`},
}
 
for _, test := range tests {
esc := jsRegexpEscaper(test.x)
if esc != test.esc {
t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
}
}
}
 
func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./` +
`0123456789:;<=>?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
 
tests := []struct {
name string
escaper func(...interface{}) string
escaped string
}{
{
"jsStrEscaper",
jsStrEscaper,
"\\0\x01\x02\x03\x04\x05\x06\x07" +
"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
"\x10\x11\x12\x13\x14\x15\x16\x17" +
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !\x22#$%\x26\x27()*\x2b,-.\/` +
`0123456789:;\x3c=\x3e?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
},
{
"jsRegexpEscaper",
jsRegexpEscaper,
"\\0\x01\x02\x03\x04\x05\x06\x07" +
"\x08\\t\\n\\x0b\\f\\r\x0E\x0F" +
"\x10\x11\x12\x13\x14\x15\x16\x17" +
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !\x22#\$%\x26\x27\(\)\*\x2b,\-\.\/` +
`0123456789:;\x3c=\x3e\?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ\[\\\]\^_` +
"`abcdefghijklmno" +
`pqrstuvwxyz\{\|\}~` + "\u007f" +
"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
},
}
 
for _, test := range tests {
if s := test.escaper(input); s != test.escaped {
t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
continue
}
 
// Escape it rune by rune to make sure that any
// fast-path checking does not break escaping.
var buf bytes.Buffer
for _, c := range input {
buf.WriteString(test.escaper(string(c)))
}
 
if s := buf.String(); s != test.escaped {
t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
continue
}
}
}
 
func BenchmarkJSValEscaperWithNum(b *testing.B) {
for i := 0; i < b.N; i++ {
jsValEscaper(3.141592654)
}
}
 
func BenchmarkJSValEscaperWithStr(b *testing.B) {
for i := 0; i < b.N; i++ {
jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
 
func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
jsValEscaper("The quick, brown fox jumps over the lazy dog")
}
}
 
func BenchmarkJSValEscaperWithObj(b *testing.B) {
o := struct {
S string
N int
}{
"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
42,
}
for i := 0; i < b.N; i++ {
jsValEscaper(o)
}
}
 
func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
o := struct {
S string
N int
}{
"The quick, brown fox jumps over the lazy dog",
42,
}
for i := 0; i < b.N; i++ {
jsValEscaper(o)
}
}
 
func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
}
}
 
func BenchmarkJSStrEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
 
func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
}
}
 
func BenchmarkJSRegexpEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
/template/js.go
0,0 → 1,362
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"unicode/utf8"
)
 
// nextJSCtx returns the context that determines whether a slash after the
// given run of tokens tokens starts a regular expression instead of a division
// operator: / or /=.
//
// This assumes that the token run does not include any string tokens, comment
// tokens, regular expression literal tokens, or division operators.
//
// This fails on some valid but nonsensical JavaScript programs like
// "x = ++/foo/i" which is quite different than "x++/foo/i", but is not known to
// fail on any known useful programs. It is based on the draft
// JavaScript 2.0 lexical grammar and requires one token of lookbehind:
// http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html
func nextJSCtx(s []byte, preceding jsCtx) jsCtx {
s = bytes.TrimRight(s, "\t\n\f\r \u2028\u2029")
if len(s) == 0 {
return preceding
}
 
// All cases below are in the single-byte UTF-8 group.
switch c, n := s[len(s)-1], len(s); c {
case '+', '-':
// ++ and -- are not regexp preceders, but + and - are whether
// they are used as infix or prefix operators.
start := n - 1
// Count the number of adjacent dashes or pluses.
for start > 0 && s[start-1] == c {
start--
}
if (n-start)&1 == 1 {
// Reached for trailing minus signs since "---" is the
// same as "-- -".
return jsCtxRegexp
}
return jsCtxDivOp
case '.':
// Handle "42."
if n != 1 && '0' <= s[n-2] && s[n-2] <= '9' {
return jsCtxDivOp
}
return jsCtxRegexp
// Suffixes for all punctuators from section 7.7 of the language spec
// that only end binary operators not handled above.
case ',', '<', '>', '=', '*', '%', '&', '|', '^', '?':
return jsCtxRegexp
// Suffixes for all punctuators from section 7.7 of the language spec
// that are prefix operators not handled above.
case '!', '~':
return jsCtxRegexp
// Matches all the punctuators from section 7.7 of the language spec
// that are open brackets not handled above.
case '(', '[':
return jsCtxRegexp
// Matches all the punctuators from section 7.7 of the language spec
// that precede expression starts.
case ':', ';', '{':
return jsCtxRegexp
// CAVEAT: the close punctuators ('}', ']', ')') precede div ops and
// are handled in the default except for '}' which can precede a
// division op as in
// ({ valueOf: function () { return 42 } } / 2
// which is valid, but, in practice, developers don't divide object
// literals, so our heuristic works well for code like
// function () { ... } /foo/.test(x) && sideEffect();
// The ')' punctuator can precede a regular expression as in
// if (b) /foo/.test(x) && ...
// but this is much less likely than
// (a + b) / c
case '}':
return jsCtxRegexp
default:
// Look for an IdentifierName and see if it is a keyword that
// can precede a regular expression.
j := n
for j > 0 && isJSIdentPart(rune(s[j-1])) {
j--
}
if regexpPrecederKeywords[string(s[j:])] {
return jsCtxRegexp
}
}
// Otherwise is a punctuator not listed above, or
// a string which precedes a div op, or an identifier
// which precedes a div op.
return jsCtxDivOp
}
 
// regexPrecederKeywords is a set of reserved JS keywords that can precede a
// regular expression in JS source.
var regexpPrecederKeywords = map[string]bool{
"break": true,
"case": true,
"continue": true,
"delete": true,
"do": true,
"else": true,
"finally": true,
"in": true,
"instanceof": true,
"return": true,
"throw": true,
"try": true,
"typeof": true,
"void": true,
}
 
var jsonMarshalType = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
 
// indirectToJSONMarshaler returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil) or an implementation of json.Marshal.
func indirectToJSONMarshaler(a interface{}) interface{} {
v := reflect.ValueOf(a)
for !v.Type().Implements(jsonMarshalType) && v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
 
// jsValEscaper escapes its inputs to a JS Expression (section 11.14) that has
// neither side-effects nor free variables outside (NaN, Infinity).
func jsValEscaper(args ...interface{}) string {
var a interface{}
if len(args) == 1 {
a = indirectToJSONMarshaler(args[0])
switch t := a.(type) {
case JS:
return string(t)
case JSStr:
// TODO: normalize quotes.
return `"` + string(t) + `"`
case json.Marshaler:
// Do not treat as a Stringer.
case fmt.Stringer:
a = t.String()
}
} else {
for i, arg := range args {
args[i] = indirectToJSONMarshaler(arg)
}
a = fmt.Sprint(args...)
}
// TODO: detect cycles before calling Marshal which loops infinitely on
// cyclic data. This may be an unacceptable DoS risk.
 
b, err := json.Marshal(a)
if err != nil {
// Put a space before comment so that if it is flush against
// a division operator it is not turned into a line comment:
// x/{{y}}
// turning into
// x//* error marshalling y:
// second line of error message */null
return fmt.Sprintf(" /* %s */null ", strings.Replace(err.Error(), "*/", "* /", -1))
}
 
// TODO: maybe post-process output to prevent it from containing
// "<!--", "-->", "<![CDATA[", "]]>", or "</script"
// in case custom marshallers produce output containing those.
 
// TODO: Maybe abbreviate \u00ab to \xab to produce more compact output.
if len(b) == 0 {
// In, `x=y/{{.}}*z` a json.Marshaler that produces "" should
// not cause the output `x=y/*z`.
return " null "
}
first, _ := utf8.DecodeRune(b)
last, _ := utf8.DecodeLastRune(b)
var buf bytes.Buffer
// Prevent IdentifierNames and NumericLiterals from running into
// keywords: in, instanceof, typeof, void
pad := isJSIdentPart(first) || isJSIdentPart(last)
if pad {
buf.WriteByte(' ')
}
written := 0
// Make sure that json.Marshal escapes codepoints U+2028 & U+2029
// so it falls within the subset of JSON which is valid JS.
for i := 0; i < len(b); {
rune, n := utf8.DecodeRune(b[i:])
repl := ""
if rune == 0x2028 {
repl = `\u2028`
} else if rune == 0x2029 {
repl = `\u2029`
}
if repl != "" {
buf.Write(b[written:i])
buf.WriteString(repl)
written = i + n
}
i += n
}
if buf.Len() != 0 {
buf.Write(b[written:])
if pad {
buf.WriteByte(' ')
}
b = buf.Bytes()
}
return string(b)
}
 
// jsStrEscaper produces a string that can be included between quotes in
// JavaScript source, in JavaScript embedded in an HTML5 <script> element,
// or in an HTML5 event handler attribute such as onclick.
func jsStrEscaper(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeJSStr {
return replace(s, jsStrNormReplacementTable)
}
return replace(s, jsStrReplacementTable)
}
 
// jsRegexpEscaper behaves like jsStrEscaper but escapes regular expression
// specials so the result is treated literally when included in a regular
// expression literal. /foo{{.X}}bar/ matches the string "foo" followed by
// the literal text of {{.X}} followed by the string "bar".
func jsRegexpEscaper(args ...interface{}) string {
s, _ := stringify(args...)
s = replace(s, jsRegexpReplacementTable)
if s == "" {
// /{{.X}}/ should not produce a line comment when .X == "".
return "(?:)"
}
return s
}
 
// replace replaces each rune r of s with replacementTable[r], provided that
// r < len(replacementTable). If replacementTable[r] is the empty string then
// no replacement is made.
// It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and
// `\u2029`.
func replace(s string, replacementTable []string) string {
var b bytes.Buffer
written := 0
for i, r := range s {
var repl string
switch {
case int(r) < len(replacementTable) && replacementTable[r] != "":
repl = replacementTable[r]
case r == '\u2028':
repl = `\u2028`
case r == '\u2029':
repl = `\u2029`
default:
continue
}
b.WriteString(s[written:i])
b.WriteString(repl)
written = i + utf8.RuneLen(r)
}
if written == 0 {
return s
}
b.WriteString(s[written:])
return b.String()
}
 
var jsStrReplacementTable = []string{
0: `\0`,
'\t': `\t`,
'\n': `\n`,
'\v': `\x0b`, // "\v" == "v" on IE 6.
'\f': `\f`,
'\r': `\r`,
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
'"': `\x22`,
'&': `\x26`,
'\'': `\x27`,
'+': `\x2b`,
'/': `\/`,
'<': `\x3c`,
'>': `\x3e`,
'\\': `\\`,
}
 
// jsStrNormReplacementTable is like jsStrReplacementTable but does not
// overencode existing escapes since this table has no entry for `\`.
var jsStrNormReplacementTable = []string{
0: `\0`,
'\t': `\t`,
'\n': `\n`,
'\v': `\x0b`, // "\v" == "v" on IE 6.
'\f': `\f`,
'\r': `\r`,
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
'"': `\x22`,
'&': `\x26`,
'\'': `\x27`,
'+': `\x2b`,
'/': `\/`,
'<': `\x3c`,
'>': `\x3e`,
}
 
var jsRegexpReplacementTable = []string{
0: `\0`,
'\t': `\t`,
'\n': `\n`,
'\v': `\x0b`, // "\v" == "v" on IE 6.
'\f': `\f`,
'\r': `\r`,
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
'"': `\x22`,
'$': `\$`,
'&': `\x26`,
'\'': `\x27`,
'(': `\(`,
')': `\)`,
'*': `\*`,
'+': `\x2b`,
'-': `\-`,
'.': `\.`,
'/': `\/`,
'<': `\x3c`,
'>': `\x3e`,
'?': `\?`,
'[': `\[`,
'\\': `\\`,
']': `\]`,
'^': `\^`,
'{': `\{`,
'|': `\|`,
'}': `\}`,
}
 
// isJSIdentPart returns whether the given rune is a JS identifier part.
// It does not handle all the non-Latin letters, joiners, and combining marks,
// but it does handle every codepoint that can occur in a numeric literal or
// a keyword.
func isJSIdentPart(r rune) bool {
switch {
case r == '$':
return true
case '0' <= r && r <= '9':
return true
case 'A' <= r && r <= 'Z':
return true
case r == '_':
return true
case 'a' <= r && r <= 'z':
return true
}
return false
}
/template/context.go
0,0 → 1,339
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"fmt"
)
 
// context describes the state an HTML parser must be in when it reaches the
// portion of HTML produced by evaluating a particular template node.
//
// The zero value of type context is the start context for a template that
// produces an HTML fragment as defined at
// http://www.w3.org/TR/html5/the-end.html#parsing-html-fragments
// where the context element is null.
type context struct {
state state
delim delim
urlPart urlPart
jsCtx jsCtx
attr attr
element element
err *Error
}
 
func (c context) String() string {
return fmt.Sprintf("{%v %v %v %v %v %v %v}", c.state, c.delim, c.urlPart, c.jsCtx, c.attr, c.element, c.err)
}
 
// eq returns whether two contexts are equal.
func (c context) eq(d context) bool {
return c.state == d.state &&
c.delim == d.delim &&
c.urlPart == d.urlPart &&
c.jsCtx == d.jsCtx &&
c.attr == d.attr &&
c.element == d.element &&
c.err == d.err
}
 
// mangle produces an identifier that includes a suffix that distinguishes it
// from template names mangled with different contexts.
func (c context) mangle(templateName string) string {
// The mangled name for the default context is the input templateName.
if c.state == stateText {
return templateName
}
s := templateName + "$htmltemplate_" + c.state.String()
if c.delim != 0 {
s += "_" + c.delim.String()
}
if c.urlPart != 0 {
s += "_" + c.urlPart.String()
}
if c.jsCtx != 0 {
s += "_" + c.jsCtx.String()
}
if c.attr != 0 {
s += "_" + c.attr.String()
}
if c.element != 0 {
s += "_" + c.element.String()
}
return s
}
 
// state describes a high-level HTML parser state.
//
// It bounds the top of the element stack, and by extension the HTML insertion
// mode, but also contains state that does not correspond to anything in the
// HTML5 parsing algorithm because a single token production in the HTML
// grammar may contain embedded actions in a template. For instance, the quoted
// HTML attribute produced by
// <div title="Hello {{.World}}">
// is a single token in HTML's grammar but in a template spans several nodes.
type state uint8
 
const (
// stateText is parsed character data. An HTML parser is in
// this state when its parse position is outside an HTML tag,
// directive, comment, and special element body.
stateText state = iota
// stateTag occurs before an HTML attribute or the end of a tag.
stateTag
// stateAttrName occurs inside an attribute name.
// It occurs between the ^'s in ` ^name^ = value`.
stateAttrName
// stateAfterName occurs after an attr name has ended but before any
// equals sign. It occurs between the ^'s in ` name^ ^= value`.
stateAfterName
// stateBeforeValue occurs after the equals sign but before the value.
// It occurs between the ^'s in ` name =^ ^value`.
stateBeforeValue
// stateHTMLCmt occurs inside an <!-- HTML comment -->.
stateHTMLCmt
// stateRCDATA occurs inside an RCDATA element (<textarea> or <title>)
// as described at http://dev.w3.org/html5/spec/syntax.html#elements-0
stateRCDATA
// stateAttr occurs inside an HTML attribute whose content is text.
stateAttr
// stateURL occurs inside an HTML attribute whose content is a URL.
stateURL
// stateJS occurs inside an event handler or script element.
stateJS
// stateJSDqStr occurs inside a JavaScript double quoted string.
stateJSDqStr
// stateJSSqStr occurs inside a JavaScript single quoted string.
stateJSSqStr
// stateJSRegexp occurs inside a JavaScript regexp literal.
stateJSRegexp
// stateJSBlockCmt occurs inside a JavaScript /* block comment */.
stateJSBlockCmt
// stateJSLineCmt occurs inside a JavaScript // line comment.
stateJSLineCmt
// stateCSS occurs inside a <style> element or style attribute.
stateCSS
// stateCSSDqStr occurs inside a CSS double quoted string.
stateCSSDqStr
// stateCSSSqStr occurs inside a CSS single quoted string.
stateCSSSqStr
// stateCSSDqURL occurs inside a CSS double quoted url("...").
stateCSSDqURL
// stateCSSSqURL occurs inside a CSS single quoted url('...').
stateCSSSqURL
// stateCSSURL occurs inside a CSS unquoted url(...).
stateCSSURL
// stateCSSBlockCmt occurs inside a CSS /* block comment */.
stateCSSBlockCmt
// stateCSSLineCmt occurs inside a CSS // line comment.
stateCSSLineCmt
// stateError is an infectious error state outside any valid
// HTML/CSS/JS construct.
stateError
)
 
var stateNames = [...]string{
stateText: "stateText",
stateTag: "stateTag",
stateAttrName: "stateAttrName",
stateAfterName: "stateAfterName",
stateBeforeValue: "stateBeforeValue",
stateHTMLCmt: "stateHTMLCmt",
stateRCDATA: "stateRCDATA",
stateAttr: "stateAttr",
stateURL: "stateURL",
stateJS: "stateJS",
stateJSDqStr: "stateJSDqStr",
stateJSSqStr: "stateJSSqStr",
stateJSRegexp: "stateJSRegexp",
stateJSBlockCmt: "stateJSBlockCmt",
stateJSLineCmt: "stateJSLineCmt",
stateCSS: "stateCSS",
stateCSSDqStr: "stateCSSDqStr",
stateCSSSqStr: "stateCSSSqStr",
stateCSSDqURL: "stateCSSDqURL",
stateCSSSqURL: "stateCSSSqURL",
stateCSSURL: "stateCSSURL",
stateCSSBlockCmt: "stateCSSBlockCmt",
stateCSSLineCmt: "stateCSSLineCmt",
stateError: "stateError",
}
 
func (s state) String() string {
if int(s) < len(stateNames) {
return stateNames[s]
}
return fmt.Sprintf("illegal state %d", int(s))
}
 
// isComment is true for any state that contains content meant for template
// authors & maintainers, not for end-users or machines.
func isComment(s state) bool {
switch s {
case stateHTMLCmt, stateJSBlockCmt, stateJSLineCmt, stateCSSBlockCmt, stateCSSLineCmt:
return true
}
return false
}
 
// isInTag return whether s occurs solely inside an HTML tag.
func isInTag(s state) bool {
switch s {
case stateTag, stateAttrName, stateAfterName, stateBeforeValue, stateAttr:
return true
}
return false
}
 
// delim is the delimiter that will end the current HTML attribute.
type delim uint8
 
const (
// delimNone occurs outside any attribute.
delimNone delim = iota
// delimDoubleQuote occurs when a double quote (") closes the attribute.
delimDoubleQuote
// delimSingleQuote occurs when a single quote (') closes the attribute.
delimSingleQuote
// delimSpaceOrTagEnd occurs when a space or right angle bracket (>)
// closes the attribute.
delimSpaceOrTagEnd
)
 
var delimNames = [...]string{
delimNone: "delimNone",
delimDoubleQuote: "delimDoubleQuote",
delimSingleQuote: "delimSingleQuote",
delimSpaceOrTagEnd: "delimSpaceOrTagEnd",
}
 
func (d delim) String() string {
if int(d) < len(delimNames) {
return delimNames[d]
}
return fmt.Sprintf("illegal delim %d", int(d))
}
 
// urlPart identifies a part in an RFC 3986 hierarchical URL to allow different
// encoding strategies.
type urlPart uint8
 
const (
// urlPartNone occurs when not in a URL, or possibly at the start:
// ^ in "^http://auth/path?k=v#frag".
urlPartNone urlPart = iota
// urlPartPreQuery occurs in the scheme, authority, or path; between the
// ^s in "h^ttp://auth/path^?k=v#frag".
urlPartPreQuery
// urlPartQueryOrFrag occurs in the query portion between the ^s in
// "http://auth/path?^k=v#frag^".
urlPartQueryOrFrag
// urlPartUnknown occurs due to joining of contexts both before and
// after the query separator.
urlPartUnknown
)
 
var urlPartNames = [...]string{
urlPartNone: "urlPartNone",
urlPartPreQuery: "urlPartPreQuery",
urlPartQueryOrFrag: "urlPartQueryOrFrag",
urlPartUnknown: "urlPartUnknown",
}
 
func (u urlPart) String() string {
if int(u) < len(urlPartNames) {
return urlPartNames[u]
}
return fmt.Sprintf("illegal urlPart %d", int(u))
}
 
// jsCtx determines whether a '/' starts a regular expression literal or a
// division operator.
type jsCtx uint8
 
const (
// jsCtxRegexp occurs where a '/' would start a regexp literal.
jsCtxRegexp jsCtx = iota
// jsCtxDivOp occurs where a '/' would start a division operator.
jsCtxDivOp
// jsCtxUnknown occurs where a '/' is ambiguous due to context joining.
jsCtxUnknown
)
 
func (c jsCtx) String() string {
switch c {
case jsCtxRegexp:
return "jsCtxRegexp"
case jsCtxDivOp:
return "jsCtxDivOp"
case jsCtxUnknown:
return "jsCtxUnknown"
}
return fmt.Sprintf("illegal jsCtx %d", int(c))
}
 
// element identifies the HTML element when inside a start tag or special body.
// Certain HTML element (for example <script> and <style>) have bodies that are
// treated differently from stateText so the element type is necessary to
// transition into the correct context at the end of a tag and to identify the
// end delimiter for the body.
type element uint8
 
const (
// elementNone occurs outside a special tag or special element body.
elementNone element = iota
// elementScript corresponds to the raw text <script> element.
elementScript
// elementStyle corresponds to the raw text <style> element.
elementStyle
// elementTextarea corresponds to the RCDATA <textarea> element.
elementTextarea
// elementTitle corresponds to the RCDATA <title> element.
elementTitle
)
 
var elementNames = [...]string{
elementNone: "elementNone",
elementScript: "elementScript",
elementStyle: "elementStyle",
elementTextarea: "elementTextarea",
elementTitle: "elementTitle",
}
 
func (e element) String() string {
if int(e) < len(elementNames) {
return elementNames[e]
}
return fmt.Sprintf("illegal element %d", int(e))
}
 
// attr identifies the most recent HTML attribute when inside a start tag.
type attr uint8
 
const (
// attrNone corresponds to a normal attribute or no attribute.
attrNone attr = iota
// attrScript corresponds to an event handler attribute.
attrScript
// attrStyle corresponds to the style attribute whose value is CSS.
attrStyle
// attrURL corresponds to an attribute whose value is a URL.
attrURL
)
 
var attrNames = [...]string{
attrNone: "attrNone",
attrScript: "attrScript",
attrStyle: "attrStyle",
attrURL: "attrURL",
}
 
func (a attr) String() string {
if int(a) < len(attrNames) {
return attrNames[a]
}
return fmt.Sprintf("illegal attr %d", int(a))
}
/template/css_test.go
0,0 → 1,281
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"strconv"
"strings"
"testing"
)
 
func TestEndsWithCSSKeyword(t *testing.T) {
tests := []struct {
css, kw string
want bool
}{
{"", "url", false},
{"url", "url", true},
{"URL", "url", true},
{"Url", "url", true},
{"url", "important", false},
{"important", "important", true},
{"image-url", "url", false},
{"imageurl", "url", false},
{"image url", "url", true},
}
for _, test := range tests {
got := endsWithCSSKeyword([]byte(test.css), test.kw)
if got != test.want {
t.Errorf("want %t but got %t for css=%v, kw=%v", test.want, got, test.css, test.kw)
}
}
}
 
func TestIsCSSNmchar(t *testing.T) {
tests := []struct {
rune rune
want bool
}{
{0, false},
{'0', true},
{'9', true},
{'A', true},
{'Z', true},
{'a', true},
{'z', true},
{'_', true},
{'-', true},
{':', false},
{';', false},
{' ', false},
{0x7f, false},
{0x80, true},
{0x1234, true},
{0xd800, false},
{0xdc00, false},
{0xfffe, false},
{0x10000, true},
{0x110000, false},
}
for _, test := range tests {
got := isCSSNmchar(test.rune)
if got != test.want {
t.Errorf("%q: want %t but got %t", string(test.rune), test.want, got)
}
}
}
 
func TestDecodeCSS(t *testing.T) {
tests := []struct {
css, want string
}{
{``, ``},
{`foo`, `foo`},
{`foo\`, `foo`},
{`foo\\`, `foo\`},
{`\`, ``},
{`\A`, "\n"},
{`\a`, "\n"},
{`\0a`, "\n"},
{`\00000a`, "\n"},
{`\000000a`, "\u0000a"},
{`\1234 5`, "\u1234" + "5"},
{`\1234\20 5`, "\u1234" + " 5"},
{`\1234\A 5`, "\u1234" + "\n5"},
{"\\1234\t5", "\u1234" + "5"},
{"\\1234\n5", "\u1234" + "5"},
{"\\1234\r\n5", "\u1234" + "5"},
{`\12345`, "\U00012345"},
{`\\`, `\`},
{`\\ `, `\ `},
{`\"`, `"`},
{`\'`, `'`},
{`\.`, `.`},
{`\. .`, `. .`},
{
`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3e dog\3c/canine\3e`,
"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>",
},
}
for _, test := range tests {
got1 := string(decodeCSS([]byte(test.css)))
if got1 != test.want {
t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.css, test.want, got1)
}
recoded := cssEscaper(got1)
if got2 := string(decodeCSS([]byte(recoded))); got2 != test.want {
t.Errorf("%q: escape & decode not dual for %q", test.css, recoded)
}
}
}
 
func TestHexDecode(t *testing.T) {
for i := 0; i < 0x200000; i += 101 /* coprime with 16 */ {
s := strconv.FormatInt(int64(i), 16)
if got := int(hexDecode([]byte(s))); got != i {
t.Errorf("%s: want %d but got %d", s, i, got)
}
s = strings.ToUpper(s)
if got := int(hexDecode([]byte(s))); got != i {
t.Errorf("%s: want %d but got %d", s, i, got)
}
}
}
 
func TestSkipCSSSpace(t *testing.T) {
tests := []struct {
css, want string
}{
{"", ""},
{"foo", "foo"},
{"\n", ""},
{"\r\n", ""},
{"\r", ""},
{"\t", ""},
{" ", ""},
{"\f", ""},
{" foo", "foo"},
{" foo", " foo"},
{`\20`, `\20`},
}
for _, test := range tests {
got := string(skipCSSSpace([]byte(test.css)))
if got != test.want {
t.Errorf("%q: want %q but got %q", test.css, test.want, got)
}
}
}
 
func TestCSSEscaper(t *testing.T) {
input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./` +
`0123456789:;<=>?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
 
want := ("\\0\x01\x02\x03\x04\x05\x06\x07" +
"\x08\\9 \\a\x0b\\c \\d\x0E\x0F" +
"\x10\x11\x12\x13\x14\x15\x16\x17" +
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !\22#$%\26\27\28\29*\2b,-.\2f ` +
`0123456789\3a\3b\3c=\3e?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\\]^_` +
"`abcdefghijklmno" +
`pqrstuvwxyz\7b|\7d~` + "\u007f" +
"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
 
got := cssEscaper(input)
if got != want {
t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got)
}
 
got = string(decodeCSS([]byte(got)))
if input != got {
t.Errorf("decode: want\n\t%q\nbut got\n\t%q", input, got)
}
}
 
func TestCSSValueFilter(t *testing.T) {
tests := []struct {
css, want string
}{
{"", ""},
{"foo", "foo"},
{"0", "0"},
{"0px", "0px"},
{"-5px", "-5px"},
{"1.25in", "1.25in"},
{"+.33em", "+.33em"},
{"100%", "100%"},
{"12.5%", "12.5%"},
{".foo", ".foo"},
{"#bar", "#bar"},
{"corner-radius", "corner-radius"},
{"-moz-corner-radius", "-moz-corner-radius"},
{"#000", "#000"},
{"#48f", "#48f"},
{"#123456", "#123456"},
{"U+00-FF, U+980-9FF", "U+00-FF, U+980-9FF"},
{"color: red", "color: red"},
{"<!--", "ZgotmplZ"},
{"-->", "ZgotmplZ"},
{"<![CDATA[", "ZgotmplZ"},
{"]]>", "ZgotmplZ"},
{"</style", "ZgotmplZ"},
{`"`, "ZgotmplZ"},
{`'`, "ZgotmplZ"},
{"`", "ZgotmplZ"},
{"\x00", "ZgotmplZ"},
{"/* foo */", "ZgotmplZ"},
{"//", "ZgotmplZ"},
{"[href=~", "ZgotmplZ"},
{"expression(alert(1337))", "ZgotmplZ"},
{"-expression(alert(1337))", "ZgotmplZ"},
{"expression", "ZgotmplZ"},
{"Expression", "ZgotmplZ"},
{"EXPRESSION", "ZgotmplZ"},
{"-moz-binding", "ZgotmplZ"},
{"-expr\x00ession(alert(1337))", "ZgotmplZ"},
{`-expr\0ession(alert(1337))`, "ZgotmplZ"},
{`-express\69on(alert(1337))`, "ZgotmplZ"},
{`-express\69 on(alert(1337))`, "ZgotmplZ"},
{`-exp\72 ession(alert(1337))`, "ZgotmplZ"},
{`-exp\52 ession(alert(1337))`, "ZgotmplZ"},
{`-exp\000052 ession(alert(1337))`, "ZgotmplZ"},
{`-expre\0000073sion`, "-expre\x073sion"},
{`@import url evil.css`, "ZgotmplZ"},
}
for _, test := range tests {
got := cssValueFilter(test.css)
if got != test.want {
t.Errorf("%q: want %q but got %q", test.css, test.want, got)
}
}
}
 
func BenchmarkCSSEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
cssEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
 
func BenchmarkCSSEscaperNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
cssEscaper("The quick, brown fox jumps over the lazy dog.")
}
}
 
func BenchmarkDecodeCSS(b *testing.B) {
s := []byte(`The \3c i\3equick\3c/i\3e,\d\A\3cspan style=\27 color:brown\27\3e brown\3c/span\3e fox jumps\2028over the \3c canine class=\22lazy\22 \3edog\3c/canine\3e`)
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeCSS(s)
}
}
 
func BenchmarkDecodeCSSNoSpecials(b *testing.B) {
s := []byte("The quick, brown fox jumps over the lazy dog.")
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeCSS(s)
}
}
 
func BenchmarkCSSValueFilter(b *testing.B) {
for i := 0; i < b.N; i++ {
cssValueFilter(` e\78preS\0Sio/**/n(alert(1337))`)
}
}
 
func BenchmarkCSSValueFilterOk(b *testing.B) {
for i := 0; i < b.N; i++ {
cssValueFilter(`Times New Roman`)
}
}
/template/css.go
0,0 → 1,268
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"fmt"
"unicode"
"unicode/utf8"
)
 
// endsWithCSSKeyword returns whether b ends with an ident that
// case-insensitively matches the lower-case kw.
func endsWithCSSKeyword(b []byte, kw string) bool {
i := len(b) - len(kw)
if i < 0 {
// Too short.
return false
}
if i != 0 {
r, _ := utf8.DecodeLastRune(b[:i])
if isCSSNmchar(r) {
// Too long.
return false
}
}
// Many CSS keywords, such as "!important" can have characters encoded,
// but the URI production does not allow that according to
// http://www.w3.org/TR/css3-syntax/#TOK-URI
// This does not attempt to recognize encoded keywords. For example,
// given "\75\72\6c" and "url" this return false.
return string(bytes.ToLower(b[i:])) == kw
}
 
// isCSSNmchar returns whether rune is allowed anywhere in a CSS identifier.
func isCSSNmchar(r rune) bool {
// Based on the CSS3 nmchar production but ignores multi-rune escape
// sequences.
// http://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar
return 'a' <= r && r <= 'z' ||
'A' <= r && r <= 'Z' ||
'0' <= r && r <= '9' ||
r == '-' ||
r == '_' ||
// Non-ASCII cases below.
0x80 <= r && r <= 0xd7ff ||
0xe000 <= r && r <= 0xfffd ||
0x10000 <= r && r <= 0x10ffff
}
 
// decodeCSS decodes CSS3 escapes given a sequence of stringchars.
// If there is no change, it returns the input, otherwise it returns a slice
// backed by a new array.
// http://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar.
func decodeCSS(s []byte) []byte {
i := bytes.IndexByte(s, '\\')
if i == -1 {
return s
}
// The UTF-8 sequence for a codepoint is never longer than 1 + the
// number hex digits need to represent that codepoint, so len(s) is an
// upper bound on the output length.
b := make([]byte, 0, len(s))
for len(s) != 0 {
i := bytes.IndexByte(s, '\\')
if i == -1 {
i = len(s)
}
b, s = append(b, s[:i]...), s[i:]
if len(s) < 2 {
break
}
// http://www.w3.org/TR/css3-syntax/#SUBTOK-escape
// escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
if isHex(s[1]) {
// http://www.w3.org/TR/css3-syntax/#SUBTOK-unicode
// unicode ::= '\' [0-9a-fA-F]{1,6} wc?
j := 2
for j < len(s) && j < 7 && isHex(s[j]) {
j++
}
r := hexDecode(s[1:j])
if r > unicode.MaxRune {
r, j = r/16, j-1
}
n := utf8.EncodeRune(b[len(b):cap(b)], r)
// The optional space at the end allows a hex
// sequence to be followed by a literal hex.
// string(decodeCSS([]byte(`\A B`))) == "\nB"
b, s = b[:len(b)+n], skipCSSSpace(s[j:])
} else {
// `\\` decodes to `\` and `\"` to `"`.
_, n := utf8.DecodeRune(s[1:])
b, s = append(b, s[1:1+n]...), s[1+n:]
}
}
return b
}
 
// isHex returns whether the given character is a hex digit.
func isHex(c byte) bool {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
}
 
// hexDecode decodes a short hex digit sequence: "10" -> 16.
func hexDecode(s []byte) rune {
n := '\x00'
for _, c := range s {
n <<= 4
switch {
case '0' <= c && c <= '9':
n |= rune(c - '0')
case 'a' <= c && c <= 'f':
n |= rune(c-'a') + 10
case 'A' <= c && c <= 'F':
n |= rune(c-'A') + 10
default:
panic(fmt.Sprintf("Bad hex digit in %q", s))
}
}
return n
}
 
// skipCSSSpace returns a suffix of c, skipping over a single space.
func skipCSSSpace(c []byte) []byte {
if len(c) == 0 {
return c
}
// wc ::= #x9 | #xA | #xC | #xD | #x20
switch c[0] {
case '\t', '\n', '\f', ' ':
return c[1:]
case '\r':
// This differs from CSS3's wc production because it contains a
// probable spec error whereby wc contains all the single byte
// sequences in nl (newline) but not CRLF.
if len(c) >= 2 && c[1] == '\n' {
return c[2:]
}
return c[1:]
}
return c
}
 
// isCSSSpace returns whether b is a CSS space char as defined in wc.
func isCSSSpace(b byte) bool {
switch b {
case '\t', '\n', '\f', '\r', ' ':
return true
}
return false
}
 
// cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
func cssEscaper(args ...interface{}) string {
s, _ := stringify(args...)
var b bytes.Buffer
written := 0
for i, r := range s {
var repl string
switch r {
case 0:
repl = `\0`
case '\t':
repl = `\9`
case '\n':
repl = `\a`
case '\f':
repl = `\c`
case '\r':
repl = `\d`
// Encode HTML specials as hex so the output can be embedded
// in HTML attributes without further encoding.
case '"':
repl = `\22`
case '&':
repl = `\26`
case '\'':
repl = `\27`
case '(':
repl = `\28`
case ')':
repl = `\29`
case '+':
repl = `\2b`
case '/':
repl = `\2f`
case ':':
repl = `\3a`
case ';':
repl = `\3b`
case '<':
repl = `\3c`
case '>':
repl = `\3e`
case '\\':
repl = `\\`
case '{':
repl = `\7b`
case '}':
repl = `\7d`
default:
continue
}
b.WriteString(s[written:i])
b.WriteString(repl)
written = i + utf8.RuneLen(r)
if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
b.WriteByte(' ')
}
}
if written == 0 {
return s
}
b.WriteString(s[written:])
return b.String()
}
 
var expressionBytes = []byte("expression")
var mozBindingBytes = []byte("mozbinding")
 
// cssValueFilter allows innocuous CSS values in the output including CSS
// quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values
// (inherit, blue), and colors (#888).
// It filters out unsafe values, such as those that affect token boundaries,
// and anything that might execute scripts.
func cssValueFilter(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeCSS {
return s
}
b, id := decodeCSS([]byte(s)), make([]byte, 0, 64)
 
// CSS3 error handling is specified as honoring string boundaries per
// http://www.w3.org/TR/css3-syntax/#error-handling :
// Malformed declarations. User agents must handle unexpected
// tokens encountered while parsing a declaration by reading until
// the end of the declaration, while observing the rules for
// matching pairs of (), [], {}, "", and '', and correctly handling
// escapes. For example, a malformed declaration may be missing a
// property, colon (:) or value.
// So we need to make sure that values do not have mismatched bracket
// or quote characters to prevent the browser from restarting parsing
// inside a string that might embed JavaScript source.
for i, c := range b {
switch c {
case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}':
return filterFailsafe
case '-':
// Disallow <!-- or -->.
// -- should not appear in valid identifiers.
if i != 0 && b[i-1] == '-' {
return filterFailsafe
}
default:
if c < 0x80 && isCSSNmchar(rune(c)) {
id = append(id, c)
}
}
}
id = bytes.ToLower(id)
if bytes.Index(id, expressionBytes) != -1 || bytes.Index(id, mozBindingBytes) != -1 {
return filterFailsafe
}
return string(b)
}
/template/error.go
0,0 → 1,197
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"fmt"
)
 
// Error describes a problem encountered during template Escaping.
type Error struct {
// ErrorCode describes the kind of error.
ErrorCode ErrorCode
// Name is the name of the template in which the error was encountered.
Name string
// Line is the line number of the error in the template source or 0.
Line int
// Description is a human-readable description of the problem.
Description string
}
 
// ErrorCode is a code for a kind of error.
type ErrorCode int
 
// We define codes for each error that manifests while escaping templates, but
// escaped templates may also fail at runtime.
//
// Output: "ZgotmplZ"
// Example:
// <img src="{{.X}}">
// where {{.X}} evaluates to `javascript:...`
// Discussion:
// "ZgotmplZ" is a special value that indicates that unsafe content reached a
// CSS or URL context at runtime. The output of the example will be
// <img src="#ZgotmplZ">
// If the data comes from a trusted source, use content types to exempt it
// from filtering: URL(`javascript:...`).
const (
// OK indicates the lack of an error.
OK ErrorCode = iota
 
// ErrAmbigContext: "... appears in an ambiguous URL context"
// Example:
// <a href="
// {{if .C}}
// /path/
// {{else}}
// /search?q=
// {{end}}
// {{.X}}
// ">
// Discussion:
// {{.X}} is in an ambiguous URL context since, depending on {{.C}},
// it may be either a URL suffix or a query parameter.
// Moving {{.X}} into the condition removes the ambiguity:
// <a href="{{if .C}}/path/{{.X}}{{else}}/search?q={{.X}}">
ErrAmbigContext
 
// ErrBadHTML: "expected space, attr name, or end of tag, but got ...",
// "... in unquoted attr", "... in attribute name"
// Example:
// <a href = /search?q=foo>
// <href=foo>
// <form na<e=...>
// <option selected<
// Discussion:
// This is often due to a typo in an HTML element, but some runes
// are banned in tag names, attribute names, and unquoted attribute
// values because they can tickle parser ambiguities.
// Quoting all attributes is the best policy.
ErrBadHTML
 
// ErrBranchEnd: "{{if}} branches end in different contexts"
// Example:
// {{if .C}}<a href="{{end}}{{.X}}
// Discussion:
// Package html/template statically examines each path through an
// {{if}}, {{range}}, or {{with}} to escape any following pipelines.
// The example is ambiguous since {{.X}} might be an HTML text node,
// or a URL prefix in an HTML attribute. The context of {{.X}} is
// used to figure out how to escape it, but that context depends on
// the run-time value of {{.C}} which is not statically known.
//
// The problem is usually something like missing quotes or angle
// brackets, or can be avoided by refactoring to put the two contexts
// into different branches of an if, range or with. If the problem
// is in a {{range}} over a collection that should never be empty,
// adding a dummy {{else}} can help.
ErrBranchEnd
 
// ErrEndContext: "... ends in a non-text context: ..."
// Examples:
// <div
// <div title="no close quote>
// <script>f()
// Discussion:
// Executed templates should produce a DocumentFragment of HTML.
// Templates that end without closing tags will trigger this error.
// Templates that should not be used in an HTML context or that
// produce incomplete Fragments should not be executed directly.
//
// {{define "main"}} <script>{{template "helper"}}</script> {{end}}
// {{define "helper"}} document.write(' <div title=" ') {{end}}
//
// "helper" does not produce a valid document fragment, so should
// not be Executed directly.
ErrEndContext
 
// ErrNoSuchTemplate: "no such template ..."
// Examples:
// {{define "main"}}<div {{template "attrs"}}>{{end}}
// {{define "attrs"}}href="{{.URL}}"{{end}}
// Discussion:
// Package html/template looks through template calls to compute the
// context.
// Here the {{.URL}} in "attrs" must be treated as a URL when called
// from "main", but you will get this error if "attrs" is not defined
// when "main" is parsed.
ErrNoSuchTemplate
 
// ErrOutputContext: "cannot compute output context for template ..."
// Examples:
// {{define "t"}}{{if .T}}{{template "t" .T}}{{end}}{{.H}}",{{end}}
// Discussion:
// A recursive template does not end in the same context in which it
// starts, and a reliable output context cannot be computed.
// Look for typos in the named template.
// If the template should not be called in the named start context,
// look for calls to that template in unexpected contexts.
// Maybe refactor recursive templates to not be recursive.
ErrOutputContext
 
// ErrPartialCharset: "unfinished JS regexp charset in ..."
// Example:
// <script>var pattern = /foo[{{.Chars}}]/</script>
// Discussion:
// Package html/template does not support interpolation into regular
// expression literal character sets.
ErrPartialCharset
 
// ErrPartialEscape: "unfinished escape sequence in ..."
// Example:
// <script>alert("\{{.X}}")</script>
// Discussion:
// Package html/template does not support actions following a
// backslash.
// This is usually an error and there are better solutions; for
// example
// <script>alert("{{.X}}")</script>
// should work, and if {{.X}} is a partial escape sequence such as
// "xA0", mark the whole sequence as safe content: JSStr(`\xA0`)
ErrPartialEscape
 
// ErrRangeLoopReentry: "on range loop re-entry: ..."
// Example:
// <script>var x = [{{range .}}'{{.}},{{end}}]</script>
// Discussion:
// If an iteration through a range would cause it to end in a
// different context than an earlier pass, there is no single context.
// In the example, there is missing a quote, so it is not clear
// whether {{.}} is meant to be inside a JS string or in a JS value
// context. The second iteration would produce something like
//
// <script>var x = ['firstValue,'secondValue]</script>
ErrRangeLoopReentry
 
// ErrSlashAmbig: '/' could start a division or regexp.
// Example:
// <script>
// {{if .C}}var x = 1{{end}}
// /-{{.N}}/i.test(x) ? doThis : doThat();
// </script>
// Discussion:
// The example above could produce `var x = 1/-2/i.test(s)...`
// in which the first '/' is a mathematical division operator or it
// could produce `/-2/i.test(s)` in which the first '/' starts a
// regexp literal.
// Look for missing semicolons inside branches, and maybe add
// parentheses to make it clear which interpretation you intend.
ErrSlashAmbig
)
 
func (e *Error) Error() string {
if e.Line != 0 {
return fmt.Sprintf("html/template:%s:%d: %s", e.Name, e.Line, e.Description)
} else if e.Name != "" {
return fmt.Sprintf("html/template:%s: %s", e.Name, e.Description)
}
return "html/template: " + e.Description
}
 
// errorf creates an error given a format string f and args.
// The template Name still needs to be supplied.
func errorf(k ErrorCode, line int, f string, args ...interface{}) *Error {
return &Error{k, "", line, fmt.Sprintf(f, args...)}
}
/template/transition.go
0,0 → 1,553
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"strings"
)
 
// transitionFunc is the array of context transition functions for text nodes.
// A transition function takes a context and template text input, and returns
// the updated context and the number of bytes consumed from the front of the
// input.
var transitionFunc = [...]func(context, []byte) (context, int){
stateText: tText,
stateTag: tTag,
stateAttrName: tAttrName,
stateAfterName: tAfterName,
stateBeforeValue: tBeforeValue,
stateHTMLCmt: tHTMLCmt,
stateRCDATA: tSpecialTagEnd,
stateAttr: tAttr,
stateURL: tURL,
stateJS: tJS,
stateJSDqStr: tJSDelimited,
stateJSSqStr: tJSDelimited,
stateJSRegexp: tJSDelimited,
stateJSBlockCmt: tBlockCmt,
stateJSLineCmt: tLineCmt,
stateCSS: tCSS,
stateCSSDqStr: tCSSStr,
stateCSSSqStr: tCSSStr,
stateCSSDqURL: tCSSStr,
stateCSSSqURL: tCSSStr,
stateCSSURL: tCSSStr,
stateCSSBlockCmt: tBlockCmt,
stateCSSLineCmt: tLineCmt,
stateError: tError,
}
 
var commentStart = []byte("<!--")
var commentEnd = []byte("-->")
 
// tText is the context transition function for the text state.
func tText(c context, s []byte) (context, int) {
k := 0
for {
i := k + bytes.IndexByte(s[k:], '<')
if i < k || i+1 == len(s) {
return c, len(s)
} else if i+4 <= len(s) && bytes.Equal(commentStart, s[i:i+4]) {
return context{state: stateHTMLCmt}, i + 4
}
i++
end := false
if s[i] == '/' {
if i+1 == len(s) {
return c, len(s)
}
end, i = true, i+1
}
j, e := eatTagName(s, i)
if j != i {
if end {
e = elementNone
}
// We've found an HTML tag.
return context{state: stateTag, element: e}, j
}
k = j
}
panic("unreachable")
}
 
var elementContentType = [...]state{
elementNone: stateText,
elementScript: stateJS,
elementStyle: stateCSS,
elementTextarea: stateRCDATA,
elementTitle: stateRCDATA,
}
 
// tTag is the context transition function for the tag state.
func tTag(c context, s []byte) (context, int) {
// Find the attribute name.
i := eatWhiteSpace(s, 0)
if i == len(s) {
return c, len(s)
}
if s[i] == '>' {
return context{
state: elementContentType[c.element],
element: c.element,
}, i + 1
}
j, err := eatAttrName(s, i)
if err != nil {
return context{state: stateError, err: err}, len(s)
}
state, attr := stateTag, attrNone
if i == j {
return context{
state: stateError,
err: errorf(ErrBadHTML, 0, "expected space, attr name, or end of tag, but got %q", s[i:]),
}, len(s)
}
switch attrType(string(s[i:j])) {
case contentTypeURL:
attr = attrURL
case contentTypeCSS:
attr = attrStyle
case contentTypeJS:
attr = attrScript
}
if j == len(s) {
state = stateAttrName
} else {
state = stateAfterName
}
return context{state: state, element: c.element, attr: attr}, j
}
 
// tAttrName is the context transition function for stateAttrName.
func tAttrName(c context, s []byte) (context, int) {
i, err := eatAttrName(s, 0)
if err != nil {
return context{state: stateError, err: err}, len(s)
} else if i != len(s) {
c.state = stateAfterName
}
return c, i
}
 
// tAfterName is the context transition function for stateAfterName.
func tAfterName(c context, s []byte) (context, int) {
// Look for the start of the value.
i := eatWhiteSpace(s, 0)
if i == len(s) {
return c, len(s)
} else if s[i] != '=' {
// Occurs due to tag ending '>', and valueless attribute.
c.state = stateTag
return c, i
}
c.state = stateBeforeValue
// Consume the "=".
return c, i + 1
}
 
var attrStartStates = [...]state{
attrNone: stateAttr,
attrScript: stateJS,
attrStyle: stateCSS,
attrURL: stateURL,
}
 
// tBeforeValue is the context transition function for stateBeforeValue.
func tBeforeValue(c context, s []byte) (context, int) {
i := eatWhiteSpace(s, 0)
if i == len(s) {
return c, len(s)
}
// Find the attribute delimiter.
delim := delimSpaceOrTagEnd
switch s[i] {
case '\'':
delim, i = delimSingleQuote, i+1
case '"':
delim, i = delimDoubleQuote, i+1
}
c.state, c.delim, c.attr = attrStartStates[c.attr], delim, attrNone
return c, i
}
 
// tHTMLCmt is the context transition function for stateHTMLCmt.
func tHTMLCmt(c context, s []byte) (context, int) {
if i := bytes.Index(s, commentEnd); i != -1 {
return context{}, i + 3
}
return c, len(s)
}
 
// specialTagEndMarkers maps element types to the character sequence that
// case-insensitively signals the end of the special tag body.
var specialTagEndMarkers = [...]string{
elementScript: "</script",
elementStyle: "</style",
elementTextarea: "</textarea",
elementTitle: "</title",
}
 
// tSpecialTagEnd is the context transition function for raw text and RCDATA
// element states.
func tSpecialTagEnd(c context, s []byte) (context, int) {
if c.element != elementNone {
if i := strings.Index(strings.ToLower(string(s)), specialTagEndMarkers[c.element]); i != -1 {
return context{}, i
}
}
return c, len(s)
}
 
// tAttr is the context transition function for the attribute state.
func tAttr(c context, s []byte) (context, int) {
return c, len(s)
}
 
// tURL is the context transition function for the URL state.
func tURL(c context, s []byte) (context, int) {
if bytes.IndexAny(s, "#?") >= 0 {
c.urlPart = urlPartQueryOrFrag
} else if len(s) != eatWhiteSpace(s, 0) && c.urlPart == urlPartNone {
// HTML5 uses "Valid URL potentially surrounded by spaces" for
// attrs: http://www.w3.org/TR/html5/index.html#attributes-1
c.urlPart = urlPartPreQuery
}
return c, len(s)
}
 
// tJS is the context transition function for the JS state.
func tJS(c context, s []byte) (context, int) {
i := bytes.IndexAny(s, `"'/`)
if i == -1 {
// Entire input is non string, comment, regexp tokens.
c.jsCtx = nextJSCtx(s, c.jsCtx)
return c, len(s)
}
c.jsCtx = nextJSCtx(s[:i], c.jsCtx)
switch s[i] {
case '"':
c.state, c.jsCtx = stateJSDqStr, jsCtxRegexp
case '\'':
c.state, c.jsCtx = stateJSSqStr, jsCtxRegexp
case '/':
switch {
case i+1 < len(s) && s[i+1] == '/':
c.state, i = stateJSLineCmt, i+1
case i+1 < len(s) && s[i+1] == '*':
c.state, i = stateJSBlockCmt, i+1
case c.jsCtx == jsCtxRegexp:
c.state = stateJSRegexp
case c.jsCtx == jsCtxDivOp:
c.jsCtx = jsCtxRegexp
default:
return context{
state: stateError,
err: errorf(ErrSlashAmbig, 0, "'/' could start a division or regexp: %.32q", s[i:]),
}, len(s)
}
default:
panic("unreachable")
}
return c, i + 1
}
 
// tJSDelimited is the context transition function for the JS string and regexp
// states.
func tJSDelimited(c context, s []byte) (context, int) {
specials := `\"`
switch c.state {
case stateJSSqStr:
specials = `\'`
case stateJSRegexp:
specials = `\/[]`
}
 
k, inCharset := 0, false
for {
i := k + bytes.IndexAny(s[k:], specials)
if i < k {
break
}
switch s[i] {
case '\\':
i++
if i == len(s) {
return context{
state: stateError,
err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in JS string: %q", s),
}, len(s)
}
case '[':
inCharset = true
case ']':
inCharset = false
default:
// end delimiter
if !inCharset {
c.state, c.jsCtx = stateJS, jsCtxDivOp
return c, i + 1
}
}
k = i + 1
}
 
if inCharset {
// This can be fixed by making context richer if interpolation
// into charsets is desired.
return context{
state: stateError,
err: errorf(ErrPartialCharset, 0, "unfinished JS regexp charset: %q", s),
}, len(s)
}
 
return c, len(s)
}
 
var blockCommentEnd = []byte("*/")
 
// tBlockCmt is the context transition function for /*comment*/ states.
func tBlockCmt(c context, s []byte) (context, int) {
i := bytes.Index(s, blockCommentEnd)
if i == -1 {
return c, len(s)
}
switch c.state {
case stateJSBlockCmt:
c.state = stateJS
case stateCSSBlockCmt:
c.state = stateCSS
default:
panic(c.state.String())
}
return c, i + 2
}
 
// tLineCmt is the context transition function for //comment states.
func tLineCmt(c context, s []byte) (context, int) {
var lineTerminators string
var endState state
switch c.state {
case stateJSLineCmt:
lineTerminators, endState = "\n\r\u2028\u2029", stateJS
case stateCSSLineCmt:
lineTerminators, endState = "\n\f\r", stateCSS
// Line comments are not part of any published CSS standard but
// are supported by the 4 major browsers.
// This defines line comments as
// LINECOMMENT ::= "//" [^\n\f\d]*
// since http://www.w3.org/TR/css3-syntax/#SUBTOK-nl defines
// newlines:
// nl ::= #xA | #xD #xA | #xD | #xC
default:
panic(c.state.String())
}
 
i := bytes.IndexAny(s, lineTerminators)
if i == -1 {
return c, len(s)
}
c.state = endState
// Per section 7.4 of EcmaScript 5 : http://es5.github.com/#x7.4
// "However, the LineTerminator at the end of the line is not
// considered to be part of the single-line comment; it is
// recognized separately by the lexical grammar and becomes part
// of the stream of input elements for the syntactic grammar."
return c, i
}
 
// tCSS is the context transition function for the CSS state.
func tCSS(c context, s []byte) (context, int) {
// CSS quoted strings are almost never used except for:
// (1) URLs as in background: "/foo.png"
// (2) Multiword font-names as in font-family: "Times New Roman"
// (3) List separators in content values as in inline-lists:
// <style>
// ul.inlineList { list-style: none; padding:0 }
// ul.inlineList > li { display: inline }
// ul.inlineList > li:before { content: ", " }
// ul.inlineList > li:first-child:before { content: "" }
// </style>
// <ul class=inlineList><li>One<li>Two<li>Three</ul>
// (4) Attribute value selectors as in a[href="http://example.com/"]
//
// We conservatively treat all strings as URLs, but make some
// allowances to avoid confusion.
//
// In (1), our conservative assumption is justified.
// In (2), valid font names do not contain ':', '?', or '#', so our
// conservative assumption is fine since we will never transition past
// urlPartPreQuery.
// In (3), our protocol heuristic should not be tripped, and there
// should not be non-space content after a '?' or '#', so as long as
// we only %-encode RFC 3986 reserved characters we are ok.
// In (4), we should URL escape for URL attributes, and for others we
// have the attribute name available if our conservative assumption
// proves problematic for real code.
 
k := 0
for {
i := k + bytes.IndexAny(s[k:], `("'/`)
if i < k {
return c, len(s)
}
switch s[i] {
case '(':
// Look for url to the left.
p := bytes.TrimRight(s[:i], "\t\n\f\r ")
if endsWithCSSKeyword(p, "url") {
j := len(s) - len(bytes.TrimLeft(s[i+1:], "\t\n\f\r "))
switch {
case j != len(s) && s[j] == '"':
c.state, j = stateCSSDqURL, j+1
case j != len(s) && s[j] == '\'':
c.state, j = stateCSSSqURL, j+1
default:
c.state = stateCSSURL
}
return c, j
}
case '/':
if i+1 < len(s) {
switch s[i+1] {
case '/':
c.state = stateCSSLineCmt
return c, i + 2
case '*':
c.state = stateCSSBlockCmt
return c, i + 2
}
}
case '"':
c.state = stateCSSDqStr
return c, i + 1
case '\'':
c.state = stateCSSSqStr
return c, i + 1
}
k = i + 1
}
panic("unreachable")
}
 
// tCSSStr is the context transition function for the CSS string and URL states.
func tCSSStr(c context, s []byte) (context, int) {
var endAndEsc string
switch c.state {
case stateCSSDqStr, stateCSSDqURL:
endAndEsc = `\"`
case stateCSSSqStr, stateCSSSqURL:
endAndEsc = `\'`
case stateCSSURL:
// Unquoted URLs end with a newline or close parenthesis.
// The below includes the wc (whitespace character) and nl.
endAndEsc = "\\\t\n\f\r )"
default:
panic(c.state.String())
}
 
k := 0
for {
i := k + bytes.IndexAny(s[k:], endAndEsc)
if i < k {
c, nread := tURL(c, decodeCSS(s[k:]))
return c, k + nread
}
if s[i] == '\\' {
i++
if i == len(s) {
return context{
state: stateError,
err: errorf(ErrPartialEscape, 0, "unfinished escape sequence in CSS string: %q", s),
}, len(s)
}
} else {
c.state = stateCSS
return c, i + 1
}
c, _ = tURL(c, decodeCSS(s[:i+1]))
k = i + 1
}
panic("unreachable")
}
 
// tError is the context transition function for the error state.
func tError(c context, s []byte) (context, int) {
return c, len(s)
}
 
// eatAttrName returns the largest j such that s[i:j] is an attribute name.
// It returns an error if s[i:] does not look like it begins with an
// attribute name, such as encountering a quote mark without a preceding
// equals sign.
func eatAttrName(s []byte, i int) (int, *Error) {
for j := i; j < len(s); j++ {
switch s[j] {
case ' ', '\t', '\n', '\f', '\r', '=', '>':
return j, nil
case '\'', '"', '<':
// These result in a parse warning in HTML5 and are
// indicative of serious problems if seen in an attr
// name in a template.
return -1, errorf(ErrBadHTML, 0, "%q in attribute name: %.32q", s[j:j+1], s)
default:
// No-op.
}
}
return len(s), nil
}
 
var elementNameMap = map[string]element{
"script": elementScript,
"style": elementStyle,
"textarea": elementTextarea,
"title": elementTitle,
}
 
// asciiAlpha returns whether c is an ASCII letter.
func asciiAlpha(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z'
}
 
// asciiAlphaNum returns whether c is an ASCII letter or digit.
func asciiAlphaNum(c byte) bool {
return asciiAlpha(c) || '0' <= c && c <= '9'
}
 
// eatTagName returns the largest j such that s[i:j] is a tag name and the tag type.
func eatTagName(s []byte, i int) (int, element) {
if i == len(s) || !asciiAlpha(s[i]) {
return i, elementNone
}
j := i + 1
for j < len(s) {
x := s[j]
if asciiAlphaNum(x) {
j++
continue
}
// Allow "x-y" or "x:y" but not "x-", "-y", or "x--y".
if (x == ':' || x == '-') && j+1 < len(s) && asciiAlphaNum(s[j+1]) {
j += 2
continue
}
break
}
return j, elementNameMap[strings.ToLower(string(s[i:j]))]
}
 
// eatWhiteSpace returns the largest j such that s[i:j] is white space.
func eatWhiteSpace(s []byte, i int) int {
for j := i; j < len(s); j++ {
switch s[j] {
case ' ', '\t', '\n', '\f', '\r':
// No-op.
default:
return j
}
}
return len(s)
}
/template/escape_test.go
0,0 → 1,1643
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"text/template"
"text/template/parse"
)
 
type badMarshaler struct{}
 
func (x *badMarshaler) MarshalJSON() ([]byte, error) {
// Keys in valid JSON must be double quoted as must all strings.
return []byte("{ foo: 'not quite valid JSON' }"), nil
}
 
type goodMarshaler struct{}
 
func (x *goodMarshaler) MarshalJSON() ([]byte, error) {
return []byte(`{ "<foo>": "O'Reilly" }`), nil
}
 
func TestEscape(t *testing.T) {
data := struct {
F, T bool
C, G, H string
A, E []string
B, M json.Marshaler
N int
Z *int
W HTML
}{
F: false,
T: true,
C: "<Cincinatti>",
G: "<Goodbye>",
H: "<Hello>",
A: []string{"<a>", "<b>"},
E: []string{},
N: 42,
B: &badMarshaler{},
M: &goodMarshaler{},
Z: nil,
W: HTML(`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`),
}
pdata := &data
 
tests := []struct {
name string
input string
output string
}{
{
"if",
"{{if .T}}Hello{{end}}, {{.C}}!",
"Hello, &lt;Cincinatti&gt;!",
},
{
"else",
"{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!",
"&lt;Goodbye&gt;!",
},
{
"overescaping1",
"Hello, {{.C | html}}!",
"Hello, &lt;Cincinatti&gt;!",
},
{
"overescaping2",
"Hello, {{html .C}}!",
"Hello, &lt;Cincinatti&gt;!",
},
{
"overescaping3",
"{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}",
"Hello, &lt;Cincinatti&gt;!",
},
{
"assignment",
"{{if $x := .H}}{{$x}}{{end}}",
"&lt;Hello&gt;",
},
{
"withBody",
"{{with .H}}{{.}}{{end}}",
"&lt;Hello&gt;",
},
{
"withElse",
"{{with .E}}{{.}}{{else}}{{.H}}{{end}}",
"&lt;Hello&gt;",
},
{
"rangeBody",
"{{range .A}}{{.}}{{end}}",
"&lt;a&gt;&lt;b&gt;",
},
{
"rangeElse",
"{{range .E}}{{.}}{{else}}{{.H}}{{end}}",
"&lt;Hello&gt;",
},
{
"nonStringValue",
"{{.T}}",
"true",
},
{
"constant",
`<a href="/search?q={{"'a<b'"}}">`,
`<a href="/search?q=%27a%3cb%27">`,
},
{
"multipleAttrs",
"<a b=1 c={{.H}}>",
"<a b=1 c=&lt;Hello&gt;>",
},
{
"urlStartRel",
`<a href='{{"/foo/bar?a=b&c=d"}}'>`,
`<a href='/foo/bar?a=b&amp;c=d'>`,
},
{
"urlStartAbsOk",
`<a href='{{"http://example.com/foo/bar?a=b&c=d"}}'>`,
`<a href='http://example.com/foo/bar?a=b&amp;c=d'>`,
},
{
"protocolRelativeURLStart",
`<a href='{{"//example.com:8000/foo/bar?a=b&c=d"}}'>`,
`<a href='//example.com:8000/foo/bar?a=b&amp;c=d'>`,
},
{
"pathRelativeURLStart",
`<a href="{{"/javascript:80/foo/bar"}}">`,
`<a href="/javascript:80/foo/bar">`,
},
{
"dangerousURLStart",
`<a href='{{"javascript:alert(%22pwned%22)"}}'>`,
`<a href='#ZgotmplZ'>`,
},
{
"dangerousURLStart2",
`<a href=' {{"javascript:alert(%22pwned%22)"}}'>`,
`<a href=' #ZgotmplZ'>`,
},
{
"nonHierURL",
`<a href={{"mailto:Muhammed \"The Greatest\" Ali <m.ali@example.com>"}}>`,
`<a href=mailto:Muhammed%20%22The%20Greatest%22%20Ali%20%3cm.ali@example.com%3e>`,
},
{
"urlPath",
`<a href='http://{{"javascript:80"}}/foo'>`,
`<a href='http://javascript:80/foo'>`,
},
{
"urlQuery",
`<a href='/search?q={{.H}}'>`,
`<a href='/search?q=%3cHello%3e'>`,
},
{
"urlFragment",
`<a href='/faq#{{.H}}'>`,
`<a href='/faq#%3cHello%3e'>`,
},
{
"urlBranch",
`<a href="{{if .F}}/foo?a=b{{else}}/bar{{end}}">`,
`<a href="/bar">`,
},
{
"urlBranchConflictMoot",
`<a href="{{if .T}}/foo?a={{else}}/bar#{{end}}{{.C}}">`,
`<a href="/foo?a=%3cCincinatti%3e">`,
},
{
"jsStrValue",
"<button onclick='alert({{.H}})'>",
`<button onclick='alert(&#34;\u003cHello\u003e&#34;)'>`,
},
{
"jsNumericValue",
"<button onclick='alert({{.N}})'>",
`<button onclick='alert( 42 )'>`,
},
{
"jsBoolValue",
"<button onclick='alert({{.T}})'>",
`<button onclick='alert( true )'>`,
},
{
"jsNilValue",
"<button onclick='alert(typeof{{.Z}})'>",
`<button onclick='alert(typeof null )'>`,
},
{
"jsObjValue",
"<button onclick='alert({{.A}})'>",
`<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
},
{
"jsObjValueScript",
"<script>alert({{.A}})</script>",
`<script>alert(["\u003ca\u003e","\u003cb\u003e"])</script>`,
},
{
"jsObjValueNotOverEscaped",
"<button onclick='alert({{.A | html}})'>",
`<button onclick='alert([&#34;\u003ca\u003e&#34;,&#34;\u003cb\u003e&#34;])'>`,
},
{
"jsStr",
"<button onclick='alert(&quot;{{.H}}&quot;)'>",
`<button onclick='alert(&quot;\x3cHello\x3e&quot;)'>`,
},
{
"badMarshaller",
`<button onclick='alert(1/{{.B}}in numbers)'>`,
`<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character &#39;f&#39; looking for beginning of object key string */null in numbers)'>`,
},
{
"jsMarshaller",
`<button onclick='alert({{.M}})'>`,
`<button onclick='alert({&#34;&lt;foo&gt;&#34;:&#34;O&#39;Reilly&#34;})'>`,
},
{
"jsStrNotUnderEscaped",
"<button onclick='alert({{.C | urlquery}})'>",
// URL escaped, then quoted for JS.
`<button onclick='alert(&#34;%3CCincinatti%3E&#34;)'>`,
},
{
"jsRe",
`<button onclick='alert(/{{"foo+bar"}}/.test(""))'>`,
`<button onclick='alert(/foo\x2bbar/.test(""))'>`,
},
{
"jsReBlank",
`<script>alert(/{{""}}/.test(""));</script>`,
`<script>alert(/(?:)/.test(""));</script>`,
},
{
"jsReAmbigOk",
`<script>{{if true}}var x = 1{{end}}</script>`,
// The {if} ends in an ambiguous jsCtx but there is
// no slash following so we shouldn't care.
`<script>var x = 1</script>`,
},
{
"styleBidiKeywordPassed",
`<p style="dir: {{"ltr"}}">`,
`<p style="dir: ltr">`,
},
{
"styleBidiPropNamePassed",
`<p style="border-{{"left"}}: 0; border-{{"right"}}: 1in">`,
`<p style="border-left: 0; border-right: 1in">`,
},
{
"styleExpressionBlocked",
`<p style="width: {{"expression(alert(1337))"}}">`,
`<p style="width: ZgotmplZ">`,
},
{
"styleTagSelectorPassed",
`<style>{{"p"}} { color: pink }</style>`,
`<style>p { color: pink }</style>`,
},
{
"styleIDPassed",
`<style>p{{"#my-ID"}} { font: Arial }</style>`,
`<style>p#my-ID { font: Arial }</style>`,
},
{
"styleClassPassed",
`<style>p{{".my_class"}} { font: Arial }</style>`,
`<style>p.my_class { font: Arial }</style>`,
},
{
"styleQuantityPassed",
`<a style="left: {{"2em"}}; top: {{0}}">`,
`<a style="left: 2em; top: 0">`,
},
{
"stylePctPassed",
`<table style=width:{{"100%"}}>`,
`<table style=width:100%>`,
},
{
"styleColorPassed",
`<p style="color: {{"#8ff"}}; background: {{"#000"}}">`,
`<p style="color: #8ff; background: #000">`,
},
{
"styleObfuscatedExpressionBlocked",
`<p style="width: {{" e\\78preS\x00Sio/**/n(alert(1337))"}}">`,
`<p style="width: ZgotmplZ">`,
},
{
"styleMozBindingBlocked",
`<p style="{{"-moz-binding(alert(1337))"}}: ...">`,
`<p style="ZgotmplZ: ...">`,
},
{
"styleObfuscatedMozBindingBlocked",
`<p style="{{" -mo\\7a-B\x00I/**/nding(alert(1337))"}}: ...">`,
`<p style="ZgotmplZ: ...">`,
},
{
"styleFontNameString",
`<p style='font-family: "{{"Times New Roman"}}"'>`,
`<p style='font-family: "Times New Roman"'>`,
},
{
"styleFontNameString",
`<p style='font-family: "{{"Times New Roman"}}", "{{"sans-serif"}}"'>`,
`<p style='font-family: "Times New Roman", "sans-serif"'>`,
},
{
"styleFontNameUnquoted",
`<p style='font-family: {{"Times New Roman"}}'>`,
`<p style='font-family: Times New Roman'>`,
},
{
"styleURLQueryEncoded",
`<p style="background: url(/img?name={{"O'Reilly Animal(1)<2>.png"}})">`,
`<p style="background: url(/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png)">`,
},
{
"styleQuotedURLQueryEncoded",
`<p style="background: url('/img?name={{"O'Reilly Animal(1)<2>.png"}}')">`,
`<p style="background: url('/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png')">`,
},
{
"styleStrQueryEncoded",
`<p style="background: '/img?name={{"O'Reilly Animal(1)<2>.png"}}'">`,
`<p style="background: '/img?name=O%27Reilly%20Animal%281%29%3c2%3e.png'">`,
},
{
"styleURLBadProtocolBlocked",
`<a style="background: url('{{"javascript:alert(1337)"}}')">`,
`<a style="background: url('#ZgotmplZ')">`,
},
{
"styleStrBadProtocolBlocked",
`<a style="background: '{{"vbscript:alert(1337)"}}'">`,
`<a style="background: '#ZgotmplZ'">`,
},
{
"styleStrEncodedProtocolEncoded",
`<a style="background: '{{"javascript\\3a alert(1337)"}}'">`,
// The CSS string 'javascript\\3a alert(1337)' does not contains a colon.
`<a style="background: 'javascript\\3a alert\28 1337\29 '">`,
},
{
"styleURLGoodProtocolPassed",
`<a style="background: url('{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}')">`,
`<a style="background: url('http://oreilly.com/O%27Reilly%20Animals%281%29%3c2%3e;%7b%7d.html')">`,
},
{
"styleStrGoodProtocolPassed",
`<a style="background: '{{"http://oreilly.com/O'Reilly Animals(1)<2>;{}.html"}}'">`,
`<a style="background: 'http\3a\2f\2foreilly.com\2fO\27Reilly Animals\28 1\29\3c 2\3e\3b\7b\7d.html'">`,
},
{
"styleURLEncodedForHTMLInAttr",
`<a style="background: url('{{"/search?img=foo&size=icon"}}')">`,
`<a style="background: url('/search?img=foo&amp;size=icon')">`,
},
{
"styleURLNotEncodedForHTMLInCdata",
`<style>body { background: url('{{"/search?img=foo&size=icon"}}') }</style>`,
`<style>body { background: url('/search?img=foo&size=icon') }</style>`,
},
{
"styleURLMixedCase",
`<p style="background: URL(#{{.H}})">`,
`<p style="background: URL(#%3cHello%3e)">`,
},
{
"stylePropertyPairPassed",
`<a style='{{"color: red"}}'>`,
`<a style='color: red'>`,
},
{
"styleStrSpecialsEncoded",
`<a style="font-family: '{{"/**/'\";:// \\"}}', &quot;{{"/**/'\";:// \\"}}&quot;">`,
`<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f \\', &quot;\2f**\2f\27\22\3b\3a\2f\2f \\&quot;">`,
},
{
"styleURLSpecialsEncoded",
`<a style="border-image: url({{"/**/'\";:// \\"}}), url(&quot;{{"/**/'\";:// \\"}}&quot;), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`,
`<a style="border-image: url(/**/%27%22;://%20%5c), url(&quot;/**/%27%22;://%20%5c&quot;), url('/**/%27%22;://%20%5c'), 'http://www.example.com/?q=%2f%2a%2a%2f%27%22%3b%3a%2f%2f%20%5c''">`,
},
{
"HTML comment",
"<b>Hello, <!-- name of world -->{{.C}}</b>",
"<b>Hello, &lt;Cincinatti&gt;</b>",
},
{
"HTML comment not first < in text node.",
"<<!-- -->!--",
"&lt;!--",
},
{
"HTML normalization 1",
"a < b",
"a &lt; b",
},
{
"HTML normalization 2",
"a << b",
"a &lt;&lt; b",
},
{
"HTML normalization 3",
"a<<!-- --><!-- -->b",
"a&lt;b",
},
{
"HTML doctype not normalized",
"<!DOCTYPE html>Hello, World!",
"<!DOCTYPE html>Hello, World!",
},
{
"No doctype injection",
`<!{{"DOCTYPE"}}`,
"&lt;!DOCTYPE",
},
{
"Split HTML comment",
"<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>",
"<b>Hello, &lt;Cincinatti&gt;</b>",
},
{
"JS line comment",
"<script>for (;;) { if (c()) break// foo not a label\n" +
"foo({{.T}});}</script>",
"<script>for (;;) { if (c()) break\n" +
"foo( true );}</script>",
},
{
"JS multiline block comment",
"<script>for (;;) { if (c()) break/* foo not a label\n" +
" */foo({{.T}});}</script>",
// Newline separates break from call. If newline
// removed, then break will consume label leaving
// code invalid.
"<script>for (;;) { if (c()) break\n" +
"foo( true );}</script>",
},
{
"JS single-line block comment",
"<script>for (;;) {\n" +
"if (c()) break/* foo a label */foo;" +
"x({{.T}});}</script>",
// Newline separates break from call. If newline
// removed, then break will consume label leaving
// code invalid.
"<script>for (;;) {\n" +
"if (c()) break foo;" +
"x( true );}</script>",
},
{
"JS block comment flush with mathematical division",
"<script>var a/*b*//c\nd</script>",
"<script>var a /c\nd</script>",
},
{
"JS mixed comments",
"<script>var a/*b*///c\nd</script>",
"<script>var a \nd</script>",
},
{
"CSS comments",
"<style>p// paragraph\n" +
`{border: 1px/* color */{{"#00f"}}}</style>`,
"<style>p\n" +
"{border: 1px #00f}</style>",
},
{
"JS attr block comment",
`<a onclick="f(&quot;&quot;); /* alert({{.H}}) */">`,
// Attribute comment tests should pass if the comments
// are successfully elided.
`<a onclick="f(&quot;&quot;); /* alert() */">`,
},
{
"JS attr line comment",
`<a onclick="// alert({{.G}})">`,
`<a onclick="// alert()">`,
},
{
"CSS attr block comment",
`<a style="/* color: {{.H}} */">`,
`<a style="/* color: */">`,
},
{
"CSS attr line comment",
`<a style="// color: {{.G}}">`,
`<a style="// color: ">`,
},
{
"HTML substitution commented out",
"<p><!-- {{.H}} --></p>",
"<p></p>",
},
{
"Comment ends flush with start",
"<!--{{.}}--><script>/*{{.}}*///{{.}}\n</script><style>/*{{.}}*///{{.}}\n</style><a onclick='/*{{.}}*///{{.}}' style='/*{{.}}*///{{.}}'>",
"<script> \n</script><style> \n</style><a onclick='/**///' style='/**///'>",
},
{
"typed HTML in text",
`{{.W}}`,
`&iexcl;<b class="foo">Hello</b>, <textarea>O'World</textarea>!`,
},
{
"typed HTML in attribute",
`<div title="{{.W}}">`,
`<div title="&iexcl;Hello, O&#39;World!">`,
},
{
"typed HTML in script",
`<button onclick="alert({{.W}})">`,
`<button onclick="alert(&#34;&amp;iexcl;\u003cb class=\&#34;foo\&#34;\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO&#39;World\u003c/textarea\u003e!&#34;)">`,
},
{
"typed HTML in RCDATA",
`<textarea>{{.W}}</textarea>`,
`<textarea>&iexcl;&lt;b class=&#34;foo&#34;&gt;Hello&lt;/b&gt;, &lt;textarea&gt;O&#39;World&lt;/textarea&gt;!</textarea>`,
},
{
"range in textarea",
"<textarea>{{range .A}}{{.}}{{end}}</textarea>",
"<textarea>&lt;a&gt;&lt;b&gt;</textarea>",
},
{
"auditable exemption from escaping",
"{{range .A}}{{. | noescape}}{{end}}",
"<a><b>",
},
{
"No tag injection",
`{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`,
`10$&lt;script src,evil.org/pwnd.js...`,
},
{
"No comment injection",
`<{{"!--"}}`,
`&lt;!--`,
},
{
"No RCDATA end tag injection",
`<textarea><{{"/textarea "}}...</textarea>`,
`<textarea>&lt;/textarea ...</textarea>`,
},
{
"optional attrs",
`<img class="{{"iconClass"}}"` +
`{{if .T}} id="{{"<iconId>"}}"{{end}}` +
// Double quotes inside if/else.
` src=` +
`{{if .T}}"?{{"<iconPath>"}}"` +
`{{else}}"images/cleardot.gif"{{end}}` +
// Missing space before title, but it is not a
// part of the src attribute.
`{{if .T}}title="{{"<title>"}}"{{end}}` +
// Quotes outside if/else.
` alt="` +
`{{if .T}}{{"<alt>"}}` +
`{{else}}{{if .F}}{{"<title>"}}{{end}}` +
`{{end}}"` +
`>`,
`<img class="iconClass" id="&lt;iconId&gt;" src="?%3ciconPath%3e"title="&lt;title&gt;" alt="&lt;alt&gt;">`,
},
{
"conditional valueless attr name",
`<input{{if .T}} checked{{end}} name=n>`,
`<input checked name=n>`,
},
{
"conditional dynamic valueless attr name 1",
`<input{{if .T}} {{"checked"}}{{end}} name=n>`,
`<input checked name=n>`,
},
{
"conditional dynamic valueless attr name 2",
`<input {{if .T}}{{"checked"}} {{end}}name=n>`,
`<input checked name=n>`,
},
{
"dynamic attribute name",
`<img on{{"load"}}="alert({{"loaded"}})">`,
// Treated as JS since quotes are inserted.
`<img onload="alert(&#34;loaded&#34;)">`,
},
{
"bad dynamic attribute name 1",
// Allow checked, selected, disabled, but not JS or
// CSS attributes.
`<input {{"onchange"}}="{{"doEvil()"}}">`,
`<input ZgotmplZ="doEvil()">`,
},
{
"bad dynamic attribute name 2",
`<div {{"sTyle"}}="{{"color: expression(alert(1337))"}}">`,
`<div ZgotmplZ="color: expression(alert(1337))">`,
},
{
"bad dynamic attribute name 3",
// Allow title or alt, but not a URL.
`<img {{"src"}}="{{"javascript:doEvil()"}}">`,
`<img ZgotmplZ="javascript:doEvil()">`,
},
{
"bad dynamic attribute name 4",
// Structure preservation requires values to associate
// with a consistent attribute.
`<input checked {{""}}="Whose value am I?">`,
`<input checked ZgotmplZ="Whose value am I?">`,
},
{
"dynamic element name",
`<h{{3}}><table><t{{"head"}}>...</h{{3}}>`,
`<h3><table><thead>...</h3>`,
},
{
"bad dynamic element name",
// Dynamic element names are typically used to switch
// between (thead, tfoot, tbody), (ul, ol), (th, td),
// and other replaceable sets.
// We do not currently easily support (ul, ol).
// If we do change to support that, this test should
// catch failures to filter out special tag names which
// would violate the structure preservation property --
// if any special tag name could be substituted, then
// the content could be raw text/RCDATA for some inputs
// and regular HTML content for others.
`<{{"script"}}>{{"doEvil()"}}</{{"script"}}>`,
`&lt;script>doEvil()&lt;/script>`,
},
}
 
for _, test := range tests {
tmpl := New(test.name)
// TODO: Move noescape into template/func.go
tmpl.Funcs(FuncMap{
"noescape": func(a ...interface{}) string {
return fmt.Sprint(a...)
},
})
tmpl = Must(tmpl.Parse(test.input))
b := new(bytes.Buffer)
if err := tmpl.Execute(b, data); err != nil {
t.Errorf("%s: template execution failed: %s", test.name, err)
continue
}
if w, g := test.output, b.String(); w != g {
t.Errorf("%s: escaped output: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue
}
b.Reset()
if err := tmpl.Execute(b, pdata); err != nil {
t.Errorf("%s: template execution failed for pointer: %s", test.name, err)
continue
}
if w, g := test.output, b.String(); w != g {
t.Errorf("%s: escaped output for pointer: want\n\t%q\ngot\n\t%q", test.name, w, g)
continue
}
}
}
 
func TestEscapeSet(t *testing.T) {
type dataItem struct {
Children []*dataItem
X string
}
 
data := dataItem{
Children: []*dataItem{
{X: "foo"},
{X: "<bar>"},
{
Children: []*dataItem{
{X: "baz"},
},
},
},
}
 
tests := []struct {
inputs map[string]string
want string
}{
// The trivial set.
{
map[string]string{
"main": ``,
},
``,
},
// A template called in the start context.
{
map[string]string{
"main": `Hello, {{template "helper"}}!`,
// Not a valid top level HTML template.
// "<b" is not a full tag.
"helper": `{{"<World>"}}`,
},
`Hello, &lt;World&gt;!`,
},
// A template called in a context other than the start.
{
map[string]string{
"main": `<a onclick='a = {{template "helper"}};'>`,
// Not a valid top level HTML template.
// "<b" is not a full tag.
"helper": `{{"<a>"}}<b`,
},
`<a onclick='a = &#34;\u003ca\u003e&#34;<b;'>`,
},
// A recursive template that ends in its start context.
{
map[string]string{
"main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`,
},
`foo &lt;bar&gt; baz `,
},
// A recursive helper template that ends in its start context.
{
map[string]string{
"main": `{{template "helper" .}}`,
"helper": `{{if .Children}}<ul>{{range .Children}}<li>{{template "main" .}}</li>{{end}}</ul>{{else}}{{.X}}{{end}}`,
},
`<ul><li>foo</li><li>&lt;bar&gt;</li><li><ul><li>baz</li></ul></li></ul>`,
},
// Co-recursive templates that end in its start context.
{
map[string]string{
"main": `<blockquote>{{range .Children}}{{template "helper" .}}{{end}}</blockquote>`,
"helper": `{{if .Children}}{{template "main" .}}{{else}}{{.X}}<br>{{end}}`,
},
`<blockquote>foo<br>&lt;bar&gt;<br><blockquote>baz<br></blockquote></blockquote>`,
},
// A template that is called in two different contexts.
{
map[string]string{
"main": `<button onclick="title='{{template "helper"}}'; ...">{{template "helper"}}</button>`,
"helper": `{{11}} of {{"<100>"}}`,
},
`<button onclick="title='11 of \x3c100\x3e'; ...">11 of &lt;100&gt;</button>`,
},
// A non-recursive template that ends in a different context.
// helper starts in jsCtxRegexp and ends in jsCtxDivOp.
{
map[string]string{
"main": `<script>var x={{template "helper"}}/{{"42"}};</script>`,
"helper": "{{126}}",
},
`<script>var x= 126 /"42";</script>`,
},
// A recursive template that ends in a similar context.
{
map[string]string{
"main": `<script>var x=[{{template "countdown" 4}}];</script>`,
"countdown": `{{.}}{{if .}},{{template "countdown" . | pred}}{{end}}`,
},
`<script>var x=[ 4 , 3 , 2 , 1 , 0 ];</script>`,
},
// A recursive template that ends in a different context.
/*
{
map[string]string{
"main": `<a href="/foo{{template "helper" .}}">`,
"helper": `{{if .Children}}{{range .Children}}{{template "helper" .}}{{end}}{{else}}?x={{.X}}{{end}}`,
},
`<a href="/foo?x=foo?x=%3cbar%3e?x=baz">`,
},
*/
}
 
// pred is a template function that returns the predecessor of a
// natural number for testing recursive templates.
fns := FuncMap{"pred": func(a ...interface{}) (interface{}, error) {
if len(a) == 1 {
if i, _ := a[0].(int); i > 0 {
return i - 1, nil
}
}
return nil, fmt.Errorf("undefined pred(%v)", a)
}}
 
for _, test := range tests {
source := ""
for name, body := range test.inputs {
source += fmt.Sprintf("{{define %q}}%s{{end}} ", name, body)
}
tmpl, err := New("root").Funcs(fns).Parse(source)
if err != nil {
t.Errorf("error parsing %q: %v", source, err)
continue
}
var b bytes.Buffer
 
if err := tmpl.ExecuteTemplate(&b, "main", data); err != nil {
t.Errorf("%q executing %v", err.Error(), tmpl.Lookup("main"))
continue
}
if got := b.String(); test.want != got {
t.Errorf("want\n\t%q\ngot\n\t%q", test.want, got)
}
}
 
}
 
func TestErrors(t *testing.T) {
tests := []struct {
input string
err string
}{
// Non-error cases.
{
"{{if .Cond}}<a>{{else}}<b>{{end}}",
"",
},
{
"{{if .Cond}}<a>{{end}}",
"",
},
{
"{{if .Cond}}{{else}}<b>{{end}}",
"",
},
{
"{{with .Cond}}<div>{{end}}",
"",
},
{
"{{range .Items}}<a>{{end}}",
"",
},
{
"<a href='/foo?{{range .Items}}&{{.K}}={{.V}}{{end}}'>",
"",
},
// Error cases.
{
"{{if .Cond}}<a{{end}}",
"z:1: {{if}} branches",
},
{
"{{if .Cond}}\n{{else}}\n<a{{end}}",
"z:1: {{if}} branches",
},
{
// Missing quote in the else branch.
`{{if .Cond}}<a href="foo">{{else}}<a href="bar>{{end}}`,
"z:1: {{if}} branches",
},
{
// Different kind of attribute: href implies a URL.
"<a {{if .Cond}}href='{{else}}title='{{end}}{{.X}}'>",
"z:1: {{if}} branches",
},
{
"\n{{with .X}}<a{{end}}",
"z:2: {{with}} branches",
},
{
"\n{{with .X}}<a>{{else}}<a{{end}}",
"z:2: {{with}} branches",
},
{
"{{range .Items}}<a{{end}}",
`z:1: on range loop re-entry: "<" in attribute name: "<a"`,
},
{
"\n{{range .Items}} x='<a{{end}}",
"z:2: on range loop re-entry: {{range}} branches",
},
{
"<a b=1 c={{.H}}",
"z: ends in a non-text context: {stateAttr delimSpaceOrTagEnd",
},
{
"<script>foo();",
"z: ends in a non-text context: {stateJS",
},
{
`<a href="{{if .F}}/foo?a={{else}}/bar/{{end}}{{.H}}">`,
"z:1: {{.H}} appears in an ambiguous URL context",
},
{
`<a onclick="alert('Hello \`,
`unfinished escape sequence in JS string: "Hello \\"`,
},
{
`<a onclick='alert("Hello\, World\`,
`unfinished escape sequence in JS string: "Hello\\, World\\"`,
},
{
`<a onclick='alert(/x+\`,
`unfinished escape sequence in JS string: "x+\\"`,
},
{
`<a onclick="/foo[\]/`,
`unfinished JS regexp charset: "foo[\\]/"`,
},
{
// It is ambiguous whether 1.5 should be 1\.5 or 1.5.
// Either `var x = 1/- 1.5 /i.test(x)`
// where `i.test(x)` is a method call of reference i,
// or `/-1\.5/i.test(x)` which is a method call on a
// case insensitive regular expression.
`<script>{{if false}}var x = 1{{end}}/-{{"1.5"}}/i.test(x)</script>`,
`'/' could start a division or regexp: "/-"`,
},
{
`{{template "foo"}}`,
"z:1: no such template \"foo\"",
},
{
`<div{{template "y"}}>` +
// Illegal starting in stateTag but not in stateText.
`{{define "y"}} foo<b{{end}}`,
`"<" in attribute name: " foo<b"`,
},
{
`<script>reverseList = [{{template "t"}}]</script>` +
// Missing " after recursive call.
`{{define "t"}}{{if .Tail}}{{template "t" .Tail}}{{end}}{{.Head}}",{{end}}`,
`: cannot compute output context for template t$htmltemplate_stateJS_elementScript`,
},
{
`<input type=button value=onclick=>`,
`html/template:z: "=" in unquoted attr: "onclick="`,
},
{
`<input type=button value= onclick=>`,
`html/template:z: "=" in unquoted attr: "onclick="`,
},
{
`<input type=button value= 1+1=2>`,
`html/template:z: "=" in unquoted attr: "1+1=2"`,
},
{
"<a class=`foo>",
"html/template:z: \"`\" in unquoted attr: \"`foo\"",
},
{
`<a style=font:'Arial'>`,
`html/template:z: "'" in unquoted attr: "font:'Arial'"`,
},
{
`<a=foo>`,
`: expected space, attr name, or end of tag, but got "=foo>"`,
},
}
 
for _, test := range tests {
buf := new(bytes.Buffer)
tmpl, err := New("z").Parse(test.input)
if err != nil {
t.Errorf("input=%q: unexpected parse error %s\n", test.input, err)
continue
}
err = tmpl.Execute(buf, nil)
var got string
if err != nil {
got = err.Error()
}
if test.err == "" {
if got != "" {
t.Errorf("input=%q: unexpected error %q", test.input, got)
}
continue
}
if strings.Index(got, test.err) == -1 {
t.Errorf("input=%q: error\n\t%q\ndoes not contain expected string\n\t%q", test.input, got, test.err)
continue
}
}
}
 
func TestEscapeText(t *testing.T) {
tests := []struct {
input string
output context
}{
{
``,
context{},
},
{
`Hello, World!`,
context{},
},
{
// An orphaned "<" is OK.
`I <3 Ponies!`,
context{},
},
{
`<a`,
context{state: stateTag},
},
{
`<a `,
context{state: stateTag},
},
{
`<a>`,
context{state: stateText},
},
{
`<a href`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a on`,
context{state: stateAttrName, attr: attrScript},
},
{
`<a href `,
context{state: stateAfterName, attr: attrURL},
},
{
`<a style = `,
context{state: stateBeforeValue, attr: attrStyle},
},
{
`<a href=`,
context{state: stateBeforeValue, attr: attrURL},
},
{
`<a href=x`,
context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery},
},
{
`<a href=x `,
context{state: stateTag},
},
{
`<a href=>`,
context{state: stateText},
},
{
`<a href=x>`,
context{state: stateText},
},
{
`<a href ='`,
context{state: stateURL, delim: delimSingleQuote},
},
{
`<a href=''`,
context{state: stateTag},
},
{
`<a href= "`,
context{state: stateURL, delim: delimDoubleQuote},
},
{
`<a href=""`,
context{state: stateTag},
},
{
`<a title="`,
context{state: stateAttr, delim: delimDoubleQuote},
},
{
`<a HREF='http:`,
context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery},
},
{
`<a Href='/`,
context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery},
},
{
`<a href='"`,
context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery},
},
{
`<a href="'`,
context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a href='&apos;`,
context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery},
},
{
`<a href="&quot;`,
context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a href="&#34;`,
context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a href=&quot;`,
context{state: stateURL, delim: delimSpaceOrTagEnd, urlPart: urlPartPreQuery},
},
{
`<img alt="1">`,
context{state: stateText},
},
{
`<img alt="1>"`,
context{state: stateTag},
},
{
`<img alt="1>">`,
context{state: stateText},
},
{
`<input checked type="checkbox"`,
context{state: stateTag},
},
{
`<a onclick="`,
context{state: stateJS, delim: delimDoubleQuote},
},
{
`<a onclick="//foo`,
context{state: stateJSLineCmt, delim: delimDoubleQuote},
},
{
"<a onclick='//\n",
context{state: stateJS, delim: delimSingleQuote},
},
{
"<a onclick='//\r\n",
context{state: stateJS, delim: delimSingleQuote},
},
{
"<a onclick='//\u2028",
context{state: stateJS, delim: delimSingleQuote},
},
{
`<a onclick="/*`,
context{state: stateJSBlockCmt, delim: delimDoubleQuote},
},
{
`<a onclick="/*/`,
context{state: stateJSBlockCmt, delim: delimDoubleQuote},
},
{
`<a onclick="/**/`,
context{state: stateJS, delim: delimDoubleQuote},
},
{
`<a onkeypress="&quot;`,
context{state: stateJSDqStr, delim: delimDoubleQuote},
},
{
`<a onclick='&quot;foo&quot;`,
context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp},
},
{
`<a onclick=&#39;foo&#39;`,
context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp},
},
{
`<a onclick=&#39;foo`,
context{state: stateJSSqStr, delim: delimSpaceOrTagEnd},
},
{
`<a onclick="&quot;foo'`,
context{state: stateJSDqStr, delim: delimDoubleQuote},
},
{
`<a onclick="'foo&quot;`,
context{state: stateJSSqStr, delim: delimDoubleQuote},
},
{
`<A ONCLICK="'`,
context{state: stateJSSqStr, delim: delimDoubleQuote},
},
{
`<a onclick="/`,
context{state: stateJSRegexp, delim: delimDoubleQuote},
},
{
`<a onclick="'foo'`,
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
},
{
`<a onclick="'foo\'`,
context{state: stateJSSqStr, delim: delimDoubleQuote},
},
{
`<a onclick="'foo\'`,
context{state: stateJSSqStr, delim: delimDoubleQuote},
},
{
`<a onclick="/foo/`,
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
},
{
`<script>/foo/ /=`,
context{state: stateJS, element: elementScript},
},
{
`<a onclick="1 /foo`,
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
},
{
`<a onclick="1 /*c*/ /foo`,
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
},
{
`<a onclick="/foo[/]`,
context{state: stateJSRegexp, delim: delimDoubleQuote},
},
{
`<a onclick="/foo\/`,
context{state: stateJSRegexp, delim: delimDoubleQuote},
},
{
`<a onclick="/foo/`,
context{state: stateJS, delim: delimDoubleQuote, jsCtx: jsCtxDivOp},
},
{
`<input checked style="`,
context{state: stateCSS, delim: delimDoubleQuote},
},
{
`<a style="//`,
context{state: stateCSSLineCmt, delim: delimDoubleQuote},
},
{
`<a style="//</script>`,
context{state: stateCSSLineCmt, delim: delimDoubleQuote},
},
{
"<a style='//\n",
context{state: stateCSS, delim: delimSingleQuote},
},
{
"<a style='//\r",
context{state: stateCSS, delim: delimSingleQuote},
},
{
`<a style="/*`,
context{state: stateCSSBlockCmt, delim: delimDoubleQuote},
},
{
`<a style="/*/`,
context{state: stateCSSBlockCmt, delim: delimDoubleQuote},
},
{
`<a style="/**/`,
context{state: stateCSS, delim: delimDoubleQuote},
},
{
`<a style="background: '`,
context{state: stateCSSSqStr, delim: delimDoubleQuote},
},
{
`<a style="background: &quot;`,
context{state: stateCSSDqStr, delim: delimDoubleQuote},
},
{
`<a style="background: '/foo?img=`,
context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag},
},
{
`<a style="background: '/`,
context{state: stateCSSSqStr, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url(&#x22;/`,
context{state: stateCSSDqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url('/`,
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url('/)`,
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url('/ `,
context{state: stateCSSSqURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url(/`,
context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery},
},
{
`<a style="background: url( `,
context{state: stateCSSURL, delim: delimDoubleQuote},
},
{
`<a style="background: url( /image?name=`,
context{state: stateCSSURL, delim: delimDoubleQuote, urlPart: urlPartQueryOrFrag},
},
{
`<a style="background: url(x)`,
context{state: stateCSS, delim: delimDoubleQuote},
},
{
`<a style="background: url('x'`,
context{state: stateCSS, delim: delimDoubleQuote},
},
{
`<a style="background: url( x `,
context{state: stateCSS, delim: delimDoubleQuote},
},
{
`<!-- foo`,
context{state: stateHTMLCmt},
},
{
`<!-->`,
context{state: stateHTMLCmt},
},
{
`<!--->`,
context{state: stateHTMLCmt},
},
{
`<!-- foo -->`,
context{state: stateText},
},
{
`<script`,
context{state: stateTag, element: elementScript},
},
{
`<script `,
context{state: stateTag, element: elementScript},
},
{
`<script src="foo.js" `,
context{state: stateTag, element: elementScript},
},
{
`<script src='foo.js' `,
context{state: stateTag, element: elementScript},
},
{
`<script type=text/javascript `,
context{state: stateTag, element: elementScript},
},
{
`<script>foo`,
context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
},
{
`<script>foo</script>`,
context{state: stateText},
},
{
`<script>foo</script><!--`,
context{state: stateHTMLCmt},
},
{
`<script>document.write("<p>foo</p>");`,
context{state: stateJS, element: elementScript},
},
{
`<script>document.write("<p>foo<\/script>");`,
context{state: stateJS, element: elementScript},
},
{
`<script>document.write("<script>alert(1)</script>");`,
context{state: stateText},
},
{
`<Script>`,
context{state: stateJS, element: elementScript},
},
{
`<SCRIPT>foo`,
context{state: stateJS, jsCtx: jsCtxDivOp, element: elementScript},
},
{
`<textarea>value`,
context{state: stateRCDATA, element: elementTextarea},
},
{
`<textarea>value</TEXTAREA>`,
context{state: stateText},
},
{
`<textarea name=html><b`,
context{state: stateRCDATA, element: elementTextarea},
},
{
`<title>value`,
context{state: stateRCDATA, element: elementTitle},
},
{
`<style>value`,
context{state: stateCSS, element: elementStyle},
},
{
`<a xlink:href`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a xmlns`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a xmlns:foo`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a xmlnsxyz`,
context{state: stateAttrName},
},
{
`<a data-url`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a data-iconUri`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a data-urlItem`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a g:`,
context{state: stateAttrName},
},
{
`<a g:url`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a g:iconUri`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a g:urlItem`,
context{state: stateAttrName, attr: attrURL},
},
{
`<a g:value`,
context{state: stateAttrName},
},
{
`<a svg:style='`,
context{state: stateCSS, delim: delimSingleQuote},
},
{
`<svg:font-face`,
context{state: stateTag},
},
{
`<svg:a svg:onclick="`,
context{state: stateJS, delim: delimDoubleQuote},
},
}
 
for _, test := range tests {
b, e := []byte(test.input), newEscaper(nil)
c := e.escapeText(context{}, &parse.TextNode{NodeType: parse.NodeText, Text: b})
if !test.output.eq(c) {
t.Errorf("input %q: want context\n\t%v\ngot\n\t%v", test.input, test.output, c)
continue
}
if test.input != string(b) {
t.Errorf("input %q: text node was modified: want %q got %q", test.input, test.input, b)
continue
}
}
}
 
func TestEnsurePipelineContains(t *testing.T) {
tests := []struct {
input, output string
ids []string
}{
{
"{{.X}}",
".X",
[]string{},
},
{
"{{.X | html}}",
".X | html",
[]string{},
},
{
"{{.X}}",
".X | html",
[]string{"html"},
},
{
"{{.X | html}}",
".X | html | urlquery",
[]string{"urlquery"},
},
{
"{{.X | html | urlquery}}",
".X | html | urlquery",
[]string{"urlquery"},
},
{
"{{.X | html | urlquery}}",
".X | html | urlquery",
[]string{"html", "urlquery"},
},
{
"{{.X | html | urlquery}}",
".X | html | urlquery",
[]string{"html"},
},
{
"{{.X | urlquery}}",
".X | html | urlquery",
[]string{"html", "urlquery"},
},
{
"{{.X | html | print}}",
".X | urlquery | html | print",
[]string{"urlquery", "html"},
},
}
for i, test := range tests {
tmpl := template.Must(template.New("test").Parse(test.input))
action, ok := (tmpl.Tree.Root.Nodes[0].(*parse.ActionNode))
if !ok {
t.Errorf("#%d: First node is not an action: %s", i, test.input)
continue
}
pipe := action.Pipe
ensurePipelineContains(pipe, test.ids)
got := pipe.String()
if got != test.output {
t.Errorf("#%d: %s, %v: want\n\t%s\ngot\n\t%s", i, test.input, test.ids, test.output, got)
}
}
}
 
func TestEscapeErrorsNotIgnorable(t *testing.T) {
var b bytes.Buffer
tmpl, _ := New("dangerous").Parse("<a")
err := tmpl.Execute(&b, nil)
if err == nil {
t.Errorf("Expected error")
} else if b.Len() != 0 {
t.Errorf("Emitted output despite escaping failure")
}
}
 
func TestEscapeSetErrorsNotIgnorable(t *testing.T) {
var b bytes.Buffer
tmpl, err := New("root").Parse(`{{define "t"}}<a{{end}}`)
if err != nil {
t.Errorf("failed to parse set: %q", err)
}
err = tmpl.ExecuteTemplate(&b, "t", nil)
if err == nil {
t.Errorf("Expected error")
} else if b.Len() != 0 {
t.Errorf("Emitted output despite escaping failure")
}
}
 
func TestRedundantFuncs(t *testing.T) {
inputs := []interface{}{
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./` +
`0123456789:;<=>?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\u2028\u2029\ufeff\ufdec\ufffd\uffff\U0001D11E" +
"&amp;%22\\",
CSS(`a[href =~ "//example.com"]#foo`),
HTML(`Hello, <b>World</b> &amp;tc!`),
HTMLAttr(` dir="ltr"`),
JS(`c && alert("Hello, World!");`),
JSStr(`Hello, World & O'Reilly\x21`),
URL(`greeting=H%69&addressee=(World)`),
}
 
for n0, m := range redundantFuncs {
f0 := funcMap[n0].(func(...interface{}) string)
for n1 := range m {
f1 := funcMap[n1].(func(...interface{}) string)
for _, input := range inputs {
want := f0(input)
if got := f1(want); want != got {
t.Errorf("%s %s with %T %q: want\n\t%q,\ngot\n\t%q", n0, n1, input, input, want, got)
}
}
}
}
}
 
func TestIndirectPrint(t *testing.T) {
a := 3
ap := &a
b := "hello"
bp := &b
bpp := &bp
tmpl := Must(New("t").Parse(`{{.}}`))
var buf bytes.Buffer
err := tmpl.Execute(&buf, ap)
if err != nil {
t.Errorf("Unexpected error: %s", err)
} else if buf.String() != "3" {
t.Errorf(`Expected "3"; got %q`, buf.String())
}
buf.Reset()
err = tmpl.Execute(&buf, bpp)
if err != nil {
t.Errorf("Unexpected error: %s", err)
} else if buf.String() != "hello" {
t.Errorf(`Expected "hello"; got %q`, buf.String())
}
}
 
func BenchmarkEscapedExecute(b *testing.B) {
tmpl := Must(New("t").Parse(`<a onclick="alert('{{.}}')">{{.}}</a>`))
var buf bytes.Buffer
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmpl.Execute(&buf, "foo & 'bar' & baz")
buf.Reset()
}
}
/template/clone_test.go
0,0 → 1,92
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"testing"
)
 
func TestClone(t *testing.T) {
tests := []struct {
input, want, wantClone string
}{
{
`Hello, {{if true}}{{"<World>"}}{{end}}!`,
"Hello, <World>!",
"Hello, &lt;World&gt;!",
},
{
`Hello, {{if false}}{{.X}}{{else}}{{"<World>"}}{{end}}!`,
"Hello, <World>!",
"Hello, &lt;World&gt;!",
},
{
`Hello, {{with "<World>"}}{{.}}{{end}}!`,
"Hello, <World>!",
"Hello, &lt;World&gt;!",
},
{
`{{range .}}<p>{{.}}</p>{{end}}`,
"<p>foo</p><p><bar></p><p>baz</p>",
"<p>foo</p><p>&lt;bar&gt;</p><p>baz</p>",
},
{
`Hello, {{"<World>" | html}}!`,
"Hello, &lt;World&gt;!",
"Hello, &lt;World&gt;!",
},
{
`Hello{{if 1}}, World{{else}}{{template "d"}}{{end}}!`,
"Hello, World!",
"Hello, World!",
},
}
 
for _, test := range tests {
s, err := New("s").Parse(test.input)
if err != nil {
t.Errorf("input=%q: unexpected parse error %v", test.input, err)
}
 
d, _ := New("d").Parse(test.input)
// Hack: just replace the root of the tree.
d.text.Root = cloneList(s.text.Root)
 
if want, got := s.text.Root.String(), d.text.Root.String(); want != got {
t.Errorf("want %q, got %q", want, got)
}
 
err = escapeTemplates(d, "d")
if err != nil {
t.Errorf("%q: failed to escape: %s", test.input, err)
continue
}
 
if want, got := "s", s.Name(); want != got {
t.Errorf("want %q, got %q", want, got)
continue
}
if want, got := "d", d.Name(); want != got {
t.Errorf("want %q, got %q", want, got)
continue
}
 
data := []string{"foo", "<bar>", "baz"}
 
var b bytes.Buffer
d.Execute(&b, data)
if got := b.String(); got != test.wantClone {
t.Errorf("input=%q: want %q, got %q", test.input, test.wantClone, got)
}
 
// Make sure escaping d did not affect s.
b.Reset()
s.text.Execute(&b, data)
if got := b.String(); got != test.want {
t.Errorf("input=%q: want %q, got %q", test.input, test.want, got)
}
}
}
/template/escape.go
0,0 → 1,753
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"fmt"
"html"
"text/template"
"text/template/parse"
)
 
// escapeTemplates rewrites the named templates, which must be
// associated with t, to guarantee that the output of any of the named
// templates is properly escaped. Names should include the names of
// all templates that might be Executed but need not include helper
// templates. If no error is returned, then the named templates have
// been modified. Otherwise the named templates have been rendered
// unusable.
func escapeTemplates(tmpl *Template, names ...string) error {
e := newEscaper(tmpl)
for _, name := range names {
c, _ := e.escapeTree(context{}, name, 0)
var err error
if c.err != nil {
err, c.err.Name = c.err, name
} else if c.state != stateText {
err = &Error{ErrEndContext, name, 0, fmt.Sprintf("ends in a non-text context: %v", c)}
}
if err != nil {
// Prevent execution of unsafe templates.
for _, name := range names {
if t := tmpl.set[name]; t != nil {
t.text.Tree = nil
}
}
return err
}
tmpl.escaped = true
}
e.commit()
return nil
}
 
// funcMap maps command names to functions that render their inputs safe.
var funcMap = template.FuncMap{
"exp_template_html_attrescaper": attrEscaper,
"exp_template_html_commentescaper": commentEscaper,
"exp_template_html_cssescaper": cssEscaper,
"exp_template_html_cssvaluefilter": cssValueFilter,
"exp_template_html_htmlnamefilter": htmlNameFilter,
"exp_template_html_htmlescaper": htmlEscaper,
"exp_template_html_jsregexpescaper": jsRegexpEscaper,
"exp_template_html_jsstrescaper": jsStrEscaper,
"exp_template_html_jsvalescaper": jsValEscaper,
"exp_template_html_nospaceescaper": htmlNospaceEscaper,
"exp_template_html_rcdataescaper": rcdataEscaper,
"exp_template_html_urlescaper": urlEscaper,
"exp_template_html_urlfilter": urlFilter,
"exp_template_html_urlnormalizer": urlNormalizer,
}
 
// equivEscapers matches contextual escapers to equivalent template builtins.
var equivEscapers = map[string]string{
"exp_template_html_attrescaper": "html",
"exp_template_html_htmlescaper": "html",
"exp_template_html_nospaceescaper": "html",
"exp_template_html_rcdataescaper": "html",
"exp_template_html_urlescaper": "urlquery",
"exp_template_html_urlnormalizer": "urlquery",
}
 
// escaper collects type inferences about templates and changes needed to make
// templates injection safe.
type escaper struct {
tmpl *Template
// output[templateName] is the output context for a templateName that
// has been mangled to include its input context.
output map[string]context
// derived[c.mangle(name)] maps to a template derived from the template
// named name templateName for the start context c.
derived map[string]*template.Template
// called[templateName] is a set of called mangled template names.
called map[string]bool
// xxxNodeEdits are the accumulated edits to apply during commit.
// Such edits are not applied immediately in case a template set
// executes a given template in different escaping contexts.
actionNodeEdits map[*parse.ActionNode][]string
templateNodeEdits map[*parse.TemplateNode]string
textNodeEdits map[*parse.TextNode][]byte
}
 
// newEscaper creates a blank escaper for the given set.
func newEscaper(t *Template) *escaper {
return &escaper{
t,
map[string]context{},
map[string]*template.Template{},
map[string]bool{},
map[*parse.ActionNode][]string{},
map[*parse.TemplateNode]string{},
map[*parse.TextNode][]byte{},
}
}
 
// filterFailsafe is an innocuous word that is emitted in place of unsafe values
// by sanitizer functions. It is not a keyword in any programming language,
// contains no special characters, is not empty, and when it appears in output
// it is distinct enough that a developer can find the source of the problem
// via a search engine.
const filterFailsafe = "ZgotmplZ"
 
// escape escapes a template node.
func (e *escaper) escape(c context, n parse.Node) context {
switch n := n.(type) {
case *parse.ActionNode:
return e.escapeAction(c, n)
case *parse.IfNode:
return e.escapeBranch(c, &n.BranchNode, "if")
case *parse.ListNode:
return e.escapeList(c, n)
case *parse.RangeNode:
return e.escapeBranch(c, &n.BranchNode, "range")
case *parse.TemplateNode:
return e.escapeTemplate(c, n)
case *parse.TextNode:
return e.escapeText(c, n)
case *parse.WithNode:
return e.escapeBranch(c, &n.BranchNode, "with")
}
panic("escaping " + n.String() + " is unimplemented")
}
 
// escapeAction escapes an action template node.
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
if len(n.Pipe.Decl) != 0 {
// A local variable assignment, not an interpolation.
return c
}
c = nudge(c)
s := make([]string, 0, 3)
switch c.state {
case stateError:
return c
case stateURL, stateCSSDqStr, stateCSSSqStr, stateCSSDqURL, stateCSSSqURL, stateCSSURL:
switch c.urlPart {
case urlPartNone:
s = append(s, "exp_template_html_urlfilter")
fallthrough
case urlPartPreQuery:
switch c.state {
case stateCSSDqStr, stateCSSSqStr:
s = append(s, "exp_template_html_cssescaper")
default:
s = append(s, "exp_template_html_urlnormalizer")
}
case urlPartQueryOrFrag:
s = append(s, "exp_template_html_urlescaper")
case urlPartUnknown:
return context{
state: stateError,
err: errorf(ErrAmbigContext, n.Line, "%s appears in an ambiguous URL context", n),
}
default:
panic(c.urlPart.String())
}
case stateJS:
s = append(s, "exp_template_html_jsvalescaper")
// A slash after a value starts a div operator.
c.jsCtx = jsCtxDivOp
case stateJSDqStr, stateJSSqStr:
s = append(s, "exp_template_html_jsstrescaper")
case stateJSRegexp:
s = append(s, "exp_template_html_jsregexpescaper")
case stateCSS:
s = append(s, "exp_template_html_cssvaluefilter")
case stateText:
s = append(s, "exp_template_html_htmlescaper")
case stateRCDATA:
s = append(s, "exp_template_html_rcdataescaper")
case stateAttr:
// Handled below in delim check.
case stateAttrName, stateTag:
c.state = stateAttrName
s = append(s, "exp_template_html_htmlnamefilter")
default:
if isComment(c.state) {
s = append(s, "exp_template_html_commentescaper")
} else {
panic("unexpected state " + c.state.String())
}
}
switch c.delim {
case delimNone:
// No extra-escaping needed for raw text content.
case delimSpaceOrTagEnd:
s = append(s, "exp_template_html_nospaceescaper")
default:
s = append(s, "exp_template_html_attrescaper")
}
e.editActionNode(n, s)
return c
}
 
// ensurePipelineContains ensures that the pipeline has commands with
// the identifiers in s in order.
// If the pipeline already has some of the sanitizers, do not interfere.
// For example, if p is (.X | html) and s is ["escapeJSVal", "html"] then it
// has one matching, "html", and one to insert, "escapeJSVal", to produce
// (.X | escapeJSVal | html).
func ensurePipelineContains(p *parse.PipeNode, s []string) {
if len(s) == 0 {
return
}
n := len(p.Cmds)
// Find the identifiers at the end of the command chain.
idents := p.Cmds
for i := n - 1; i >= 0; i-- {
if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
if id, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
if id.Ident == "noescape" {
return
}
continue
}
}
idents = p.Cmds[i+1:]
}
dups := 0
for _, id := range idents {
if escFnsEq(s[dups], (id.Args[0].(*parse.IdentifierNode)).Ident) {
dups++
if dups == len(s) {
return
}
}
}
newCmds := make([]*parse.CommandNode, n-len(idents), n+len(s)-dups)
copy(newCmds, p.Cmds)
// Merge existing identifier commands with the sanitizers needed.
for _, id := range idents {
i := indexOfStr((id.Args[0].(*parse.IdentifierNode)).Ident, s, escFnsEq)
if i != -1 {
for _, name := range s[:i] {
newCmds = appendCmd(newCmds, newIdentCmd(name))
}
s = s[i+1:]
}
newCmds = appendCmd(newCmds, id)
}
// Create any remaining sanitizers.
for _, name := range s {
newCmds = appendCmd(newCmds, newIdentCmd(name))
}
p.Cmds = newCmds
}
 
// redundantFuncs[a][b] implies that funcMap[b](funcMap[a](x)) == funcMap[a](x)
// for all x.
var redundantFuncs = map[string]map[string]bool{
"exp_template_html_commentescaper": {
"exp_template_html_attrescaper": true,
"exp_template_html_nospaceescaper": true,
"exp_template_html_htmlescaper": true,
},
"exp_template_html_cssescaper": {
"exp_template_html_attrescaper": true,
},
"exp_template_html_jsregexpescaper": {
"exp_template_html_attrescaper": true,
},
"exp_template_html_jsstrescaper": {
"exp_template_html_attrescaper": true,
},
"exp_template_html_urlescaper": {
"exp_template_html_urlnormalizer": true,
},
}
 
// appendCmd appends the given command to the end of the command pipeline
// unless it is redundant with the last command.
func appendCmd(cmds []*parse.CommandNode, cmd *parse.CommandNode) []*parse.CommandNode {
if n := len(cmds); n != 0 {
last, ok := cmds[n-1].Args[0].(*parse.IdentifierNode)
next, _ := cmd.Args[0].(*parse.IdentifierNode)
if ok && redundantFuncs[last.Ident][next.Ident] {
return cmds
}
}
return append(cmds, cmd)
}
 
// indexOfStr is the first i such that eq(s, strs[i]) or -1 if s was not found.
func indexOfStr(s string, strs []string, eq func(a, b string) bool) int {
for i, t := range strs {
if eq(s, t) {
return i
}
}
return -1
}
 
// escFnsEq returns whether the two escaping functions are equivalent.
func escFnsEq(a, b string) bool {
if e := equivEscapers[a]; e != "" {
a = e
}
if e := equivEscapers[b]; e != "" {
b = e
}
return a == b
}
 
// newIdentCmd produces a command containing a single identifier node.
func newIdentCmd(identifier string) *parse.CommandNode {
return &parse.CommandNode{
NodeType: parse.NodeCommand,
Args: []parse.Node{parse.NewIdentifier(identifier)},
}
}
 
// nudge returns the context that would result from following empty string
// transitions from the input context.
// For example, parsing:
// `<a href=`
// will end in context{stateBeforeValue, attrURL}, but parsing one extra rune:
// `<a href=x`
// will end in context{stateURL, delimSpaceOrTagEnd, ...}.
// There are two transitions that happen when the 'x' is seen:
// (1) Transition from a before-value state to a start-of-value state without
// consuming any character.
// (2) Consume 'x' and transition past the first value character.
// In this case, nudging produces the context after (1) happens.
func nudge(c context) context {
switch c.state {
case stateTag:
// In `<foo {{.}}`, the action should emit an attribute.
c.state = stateAttrName
case stateBeforeValue:
// In `<foo bar={{.}}`, the action is an undelimited value.
c.state, c.delim, c.attr = attrStartStates[c.attr], delimSpaceOrTagEnd, attrNone
case stateAfterName:
// In `<foo bar {{.}}`, the action is an attribute name.
c.state, c.attr = stateAttrName, attrNone
}
return c
}
 
// join joins the two contexts of a branch template node. The result is an
// error context if either of the input contexts are error contexts, or if the
// the input contexts differ.
func join(a, b context, line int, nodeName string) context {
if a.state == stateError {
return a
}
if b.state == stateError {
return b
}
if a.eq(b) {
return a
}
 
c := a
c.urlPart = b.urlPart
if c.eq(b) {
// The contexts differ only by urlPart.
c.urlPart = urlPartUnknown
return c
}
 
c = a
c.jsCtx = b.jsCtx
if c.eq(b) {
// The contexts differ only by jsCtx.
c.jsCtx = jsCtxUnknown
return c
}
 
// Allow a nudged context to join with an unnudged one.
// This means that
// <p title={{if .C}}{{.}}{{end}}
// ends in an unquoted value state even though the else branch
// ends in stateBeforeValue.
if c, d := nudge(a), nudge(b); !(c.eq(a) && d.eq(b)) {
if e := join(c, d, line, nodeName); e.state != stateError {
return e
}
}
 
return context{
state: stateError,
err: errorf(ErrBranchEnd, line, "{{%s}} branches end in different contexts: %v, %v", nodeName, a, b),
}
}
 
// escapeBranch escapes a branch template node: "if", "range" and "with".
func (e *escaper) escapeBranch(c context, n *parse.BranchNode, nodeName string) context {
c0 := e.escapeList(c, n.List)
if nodeName == "range" && c0.state != stateError {
// The "true" branch of a "range" node can execute multiple times.
// We check that executing n.List once results in the same context
// as executing n.List twice.
c1, _ := e.escapeListConditionally(c0, n.List, nil)
c0 = join(c0, c1, n.Line, nodeName)
if c0.state == stateError {
// Make clear that this is a problem on loop re-entry
// since developers tend to overlook that branch when
// debugging templates.
c0.err.Line = n.Line
c0.err.Description = "on range loop re-entry: " + c0.err.Description
return c0
}
}
c1 := e.escapeList(c, n.ElseList)
return join(c0, c1, n.Line, nodeName)
}
 
// escapeList escapes a list template node.
func (e *escaper) escapeList(c context, n *parse.ListNode) context {
if n == nil {
return c
}
for _, m := range n.Nodes {
c = e.escape(c, m)
}
return c
}
 
// escapeListConditionally escapes a list node but only preserves edits and
// inferences in e if the inferences and output context satisfy filter.
// It returns the best guess at an output context, and the result of the filter
// which is the same as whether e was updated.
func (e *escaper) escapeListConditionally(c context, n *parse.ListNode, filter func(*escaper, context) bool) (context, bool) {
e1 := newEscaper(e.tmpl)
// Make type inferences available to f.
for k, v := range e.output {
e1.output[k] = v
}
c = e1.escapeList(c, n)
ok := filter != nil && filter(e1, c)
if ok {
// Copy inferences and edits from e1 back into e.
for k, v := range e1.output {
e.output[k] = v
}
for k, v := range e1.derived {
e.derived[k] = v
}
for k, v := range e1.called {
e.called[k] = v
}
for k, v := range e1.actionNodeEdits {
e.editActionNode(k, v)
}
for k, v := range e1.templateNodeEdits {
e.editTemplateNode(k, v)
}
for k, v := range e1.textNodeEdits {
e.editTextNode(k, v)
}
}
return c, ok
}
 
// escapeTemplate escapes a {{template}} call node.
func (e *escaper) escapeTemplate(c context, n *parse.TemplateNode) context {
c, name := e.escapeTree(c, n.Name, n.Line)
if name != n.Name {
e.editTemplateNode(n, name)
}
return c
}
 
// escapeTree escapes the named template starting in the given context as
// necessary and returns its output context.
func (e *escaper) escapeTree(c context, name string, line int) (context, string) {
// Mangle the template name with the input context to produce a reliable
// identifier.
dname := c.mangle(name)
e.called[dname] = true
if out, ok := e.output[dname]; ok {
// Already escaped.
return out, dname
}
t := e.template(name)
if t == nil {
// Two cases: The template exists but is empty, or has never been mentioned at
// all. Distinguish the cases in the error messages.
if e.tmpl.set[name] != nil {
return context{
state: stateError,
err: errorf(ErrNoSuchTemplate, line, "%q is an incomplete or empty template", name),
}, dname
}
return context{
state: stateError,
err: errorf(ErrNoSuchTemplate, line, "no such template %q", name),
}, dname
}
if dname != name {
// Use any template derived during an earlier call to escapeTemplate
// with different top level templates, or clone if necessary.
dt := e.template(dname)
if dt == nil {
dt = template.New(dname)
dt.Tree = &parse.Tree{Name: dname, Root: cloneList(t.Root)}
e.derived[dname] = dt
}
t = dt
}
return e.computeOutCtx(c, t), dname
}
 
// computeOutCtx takes a template and its start context and computes the output
// context while storing any inferences in e.
func (e *escaper) computeOutCtx(c context, t *template.Template) context {
// Propagate context over the body.
c1, ok := e.escapeTemplateBody(c, t)
if !ok {
// Look for a fixed point by assuming c1 as the output context.
if c2, ok2 := e.escapeTemplateBody(c1, t); ok2 {
c1, ok = c2, true
}
// Use c1 as the error context if neither assumption worked.
}
if !ok && c1.state != stateError {
return context{
state: stateError,
// TODO: Find the first node with a line in t.text.Tree.Root
err: errorf(ErrOutputContext, 0, "cannot compute output context for template %s", t.Name()),
}
}
return c1
}
 
// escapeTemplateBody escapes the given template assuming the given output
// context, and returns the best guess at the output context and whether the
// assumption was correct.
func (e *escaper) escapeTemplateBody(c context, t *template.Template) (context, bool) {
filter := func(e1 *escaper, c1 context) bool {
if c1.state == stateError {
// Do not update the input escaper, e.
return false
}
if !e1.called[t.Name()] {
// If t is not recursively called, then c1 is an
// accurate output context.
return true
}
// c1 is accurate if it matches our assumed output context.
return c.eq(c1)
}
// We need to assume an output context so that recursive template calls
// take the fast path out of escapeTree instead of infinitely recursing.
// Naively assuming that the input context is the same as the output
// works >90% of the time.
e.output[t.Name()] = c
return e.escapeListConditionally(c, t.Tree.Root, filter)
}
 
// delimEnds maps each delim to a string of characters that terminate it.
var delimEnds = [...]string{
delimDoubleQuote: `"`,
delimSingleQuote: "'",
// Determined empirically by running the below in various browsers.
// var div = document.createElement("DIV");
// for (var i = 0; i < 0x10000; ++i) {
// div.innerHTML = "<span title=x" + String.fromCharCode(i) + "-bar>";
// if (div.getElementsByTagName("SPAN")[0].title.indexOf("bar") < 0)
// document.write("<p>U+" + i.toString(16));
// }
delimSpaceOrTagEnd: " \t\n\f\r>",
}
 
var doctypeBytes = []byte("<!DOCTYPE")
 
// escapeText escapes a text template node.
func (e *escaper) escapeText(c context, n *parse.TextNode) context {
s, written, i, b := n.Text, 0, 0, new(bytes.Buffer)
for i != len(s) {
c1, nread := contextAfterText(c, s[i:])
i1 := i + nread
if c.state == stateText || c.state == stateRCDATA {
end := i1
if c1.state != c.state {
for j := end - 1; j >= i; j-- {
if s[j] == '<' {
end = j
break
}
}
}
for j := i; j < end; j++ {
if s[j] == '<' && !bytes.HasPrefix(s[j:], doctypeBytes) {
b.Write(s[written:j])
b.WriteString("&lt;")
written = j + 1
}
}
} else if isComment(c.state) && c.delim == delimNone {
switch c.state {
case stateJSBlockCmt:
// http://es5.github.com/#x7.4:
// "Comments behave like white space and are
// discarded except that, if a MultiLineComment
// contains a line terminator character, then
// the entire comment is considered to be a
// LineTerminator for purposes of parsing by
// the syntactic grammar."
if bytes.IndexAny(s[written:i1], "\n\r\u2028\u2029") != -1 {
b.WriteByte('\n')
} else {
b.WriteByte(' ')
}
case stateCSSBlockCmt:
b.WriteByte(' ')
}
written = i1
}
if c.state != c1.state && isComment(c1.state) && c1.delim == delimNone {
// Preserve the portion between written and the comment start.
cs := i1 - 2
if c1.state == stateHTMLCmt {
// "<!--" instead of "/*" or "//"
cs -= 2
}
b.Write(s[written:cs])
written = i1
}
if i == i1 && c.state == c1.state {
panic(fmt.Sprintf("infinite loop from %v to %v on %q..%q", c, c1, s[:i], s[i:]))
}
c, i = c1, i1
}
 
if written != 0 && c.state != stateError {
if !isComment(c.state) || c.delim != delimNone {
b.Write(n.Text[written:])
}
e.editTextNode(n, b.Bytes())
}
return c
}
 
// contextAfterText starts in context c, consumes some tokens from the front of
// s, then returns the context after those tokens and the unprocessed suffix.
func contextAfterText(c context, s []byte) (context, int) {
if c.delim == delimNone {
c1, i := tSpecialTagEnd(c, s)
if i == 0 {
// A special end tag (`</script>`) has been seen and
// all content preceding it has been consumed.
return c1, 0
}
// Consider all content up to any end tag.
return transitionFunc[c.state](c, s[:i])
}
 
i := bytes.IndexAny(s, delimEnds[c.delim])
if i == -1 {
i = len(s)
}
if c.delim == delimSpaceOrTagEnd {
// http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state
// lists the runes below as error characters.
// Error out because HTML parsers may differ on whether
// "<a id= onclick=f(" ends inside id's or onclick's value,
// "<a class=`foo " ends inside a value,
// "<a style=font:'Arial'" needs open-quote fixup.
// IE treats '`' as a quotation character.
if j := bytes.IndexAny(s[:i], "\"'<=`"); j >= 0 {
return context{
state: stateError,
err: errorf(ErrBadHTML, 0, "%q in unquoted attr: %q", s[j:j+1], s[:i]),
}, len(s)
}
}
if i == len(s) {
// Remain inside the attribute.
// Decode the value so non-HTML rules can easily handle
// <button onclick="alert(&quot;Hi!&quot;)">
// without having to entity decode token boundaries.
for u := []byte(html.UnescapeString(string(s))); len(u) != 0; {
c1, i1 := transitionFunc[c.state](c, u)
c, u = c1, u[i1:]
}
return c, len(s)
}
if c.delim != delimSpaceOrTagEnd {
// Consume any quote.
i++
}
// On exiting an attribute, we discard all state information
// except the state and element.
return context{state: stateTag, element: c.element}, i
}
 
// editActionNode records a change to an action pipeline for later commit.
func (e *escaper) editActionNode(n *parse.ActionNode, cmds []string) {
if _, ok := e.actionNodeEdits[n]; ok {
panic(fmt.Sprintf("node %s shared between templates", n))
}
e.actionNodeEdits[n] = cmds
}
 
// editTemplateNode records a change to a {{template}} callee for later commit.
func (e *escaper) editTemplateNode(n *parse.TemplateNode, callee string) {
if _, ok := e.templateNodeEdits[n]; ok {
panic(fmt.Sprintf("node %s shared between templates", n))
}
e.templateNodeEdits[n] = callee
}
 
// editTextNode records a change to a text node for later commit.
func (e *escaper) editTextNode(n *parse.TextNode, text []byte) {
if _, ok := e.textNodeEdits[n]; ok {
panic(fmt.Sprintf("node %s shared between templates", n))
}
e.textNodeEdits[n] = text
}
 
// commit applies changes to actions and template calls needed to contextually
// autoescape content and adds any derived templates to the set.
func (e *escaper) commit() {
for name := range e.output {
e.template(name).Funcs(funcMap)
}
for _, t := range e.derived {
if _, err := e.tmpl.text.AddParseTree(t.Name(), t.Tree); err != nil {
panic("error adding derived template")
}
}
for n, s := range e.actionNodeEdits {
ensurePipelineContains(n.Pipe, s)
}
for n, name := range e.templateNodeEdits {
n.Name = name
}
for n, s := range e.textNodeEdits {
n.Text = s
}
}
 
// template returns the named template given a mangled template name.
func (e *escaper) template(name string) *template.Template {
t := e.tmpl.text.Lookup(name)
if t == nil {
t = e.derived[name]
}
return t
}
/template/clone.go
0,0 → 1,90
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"text/template/parse"
)
 
// clone clones a template Node.
func clone(n parse.Node) parse.Node {
switch t := n.(type) {
case *parse.ActionNode:
return cloneAction(t)
case *parse.IfNode:
b := new(parse.IfNode)
copyBranch(&b.BranchNode, &t.BranchNode)
return b
case *parse.ListNode:
return cloneList(t)
case *parse.RangeNode:
b := new(parse.RangeNode)
copyBranch(&b.BranchNode, &t.BranchNode)
return b
case *parse.TemplateNode:
return cloneTemplate(t)
case *parse.TextNode:
return cloneText(t)
case *parse.WithNode:
b := new(parse.WithNode)
copyBranch(&b.BranchNode, &t.BranchNode)
return b
}
panic("cloning " + n.String() + " is unimplemented")
}
 
// cloneAction returns a deep clone of n.
func cloneAction(n *parse.ActionNode) *parse.ActionNode {
// We use keyless fields because they won't compile if a field is added.
return &parse.ActionNode{n.NodeType, n.Line, clonePipe(n.Pipe)}
}
 
// cloneList returns a deep clone of n.
func cloneList(n *parse.ListNode) *parse.ListNode {
if n == nil {
return nil
}
// We use keyless fields because they won't compile if a field is added.
c := parse.ListNode{n.NodeType, make([]parse.Node, len(n.Nodes))}
for i, child := range n.Nodes {
c.Nodes[i] = clone(child)
}
return &c
}
 
// clonePipe returns a shallow clone of n.
// The escaper does not modify pipe descendants in place so there's no need to
// clone deeply.
func clonePipe(n *parse.PipeNode) *parse.PipeNode {
if n == nil {
return nil
}
// We use keyless fields because they won't compile if a field is added.
return &parse.PipeNode{n.NodeType, n.Line, n.Decl, n.Cmds}
}
 
// cloneTemplate returns a deep clone of n.
func cloneTemplate(n *parse.TemplateNode) *parse.TemplateNode {
// We use keyless fields because they won't compile if a field is added.
return &parse.TemplateNode{n.NodeType, n.Line, n.Name, clonePipe(n.Pipe)}
}
 
// cloneText clones the given node sharing its []byte.
func cloneText(n *parse.TextNode) *parse.TextNode {
// We use keyless fields because they won't compile if a field is added.
return &parse.TextNode{n.NodeType, n.Text}
}
 
// copyBranch clones src into dst.
func copyBranch(dst, src *parse.BranchNode) {
// We use keyless fields because they won't compile if a field is added.
*dst = parse.BranchNode{
src.NodeType,
src.Line,
clonePipe(src.Pipe),
cloneList(src.List),
cloneList(src.ElseList),
}
}
/template/url_test.go
0,0 → 1,112
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"testing"
)
 
func TestURLNormalizer(t *testing.T) {
tests := []struct {
url, want string
}{
{"", ""},
{
"http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag",
"http://example.com:80/foo/bar?q=foo%20&bar=x+y#frag",
},
{" ", "%20"},
{"%7c", "%7c"},
{"%7C", "%7C"},
{"%2", "%252"},
{"%", "%25"},
{"%z", "%25z"},
{"/foo|bar/%5c\u1234", "/foo%7cbar/%5c%e1%88%b4"},
}
for _, test := range tests {
if got := urlNormalizer(test.url); test.want != got {
t.Errorf("%q: want\n\t%q\nbut got\n\t%q", test.url, test.want, got)
}
if test.want != urlNormalizer(test.want) {
t.Errorf("not idempotent: %q", test.want)
}
}
}
 
func TestURLFilters(t *testing.T) {
input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./` +
`0123456789:;<=>?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")
 
tests := []struct {
name string
escaper func(...interface{}) string
escaped string
}{
{
"urlEscaper",
urlEscaper,
"%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" +
"%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" +
"%20%21%22%23%24%25%26%27%28%29%2a%2b%2c-.%2f" +
"0123456789%3a%3b%3c%3d%3e%3f" +
"%40ABCDEFGHIJKLMNO" +
"PQRSTUVWXYZ%5b%5c%5d%5e_" +
"%60abcdefghijklmno" +
"pqrstuvwxyz%7b%7c%7d~%7f" +
"%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e",
},
{
"urlNormalizer",
urlNormalizer,
"%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f" +
"%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f" +
"%20!%22#$%25&%27%28%29*+,-./" +
"0123456789:;%3c=%3e?" +
"@ABCDEFGHIJKLMNO" +
"PQRSTUVWXYZ[%5c]%5e_" +
"%60abcdefghijklmno" +
"pqrstuvwxyz%7b%7c%7d~%7f" +
"%c2%a0%c4%80%e2%80%a8%e2%80%a9%ef%bb%bf%f0%9d%84%9e",
},
}
 
for _, test := range tests {
if s := test.escaper(input); s != test.escaped {
t.Errorf("%s: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
continue
}
}
}
 
func BenchmarkURLEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
urlEscaper("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
}
}
 
func BenchmarkURLEscaperNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
urlEscaper("TheQuickBrownFoxJumpsOverTheLazyDog.")
}
}
 
func BenchmarkURLNormalizer(b *testing.B) {
for i := 0; i < b.N; i++ {
urlNormalizer("The quick brown fox jumps over the lazy dog.\n")
}
}
 
func BenchmarkURLNormalizerNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
urlNormalizer("http://example.com:80/foo?q=bar%20&baz=x+y#frag")
}
}
/template/url.go
0,0 → 1,105
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"fmt"
"strings"
)
 
// urlFilter returns its input unless it contains an unsafe protocol in which
// case it defangs the entire URL.
func urlFilter(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeURL {
return s
}
if i := strings.IndexRune(s, ':'); i >= 0 && strings.IndexRune(s[:i], '/') < 0 {
protocol := strings.ToLower(s[:i])
if protocol != "http" && protocol != "https" && protocol != "mailto" {
return "#" + filterFailsafe
}
}
return s
}
 
// urlEscaper produces an output that can be embedded in a URL query.
// The output can be embedded in an HTML attribute without further escaping.
func urlEscaper(args ...interface{}) string {
return urlProcessor(false, args...)
}
 
// urlEscaper normalizes URL content so it can be embedded in a quote-delimited
// string or parenthesis delimited url(...).
// The normalizer does not encode all HTML specials. Specifically, it does not
// encode '&' so correct embedding in an HTML attribute requires escaping of
// '&' to '&amp;'.
func urlNormalizer(args ...interface{}) string {
return urlProcessor(true, args...)
}
 
// urlProcessor normalizes (when norm is true) or escapes its input to produce
// a valid hierarchical or opaque URL part.
func urlProcessor(norm bool, args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeURL {
norm = true
}
var b bytes.Buffer
written := 0
// The byte loop below assumes that all URLs use UTF-8 as the
// content-encoding. This is similar to the URI to IRI encoding scheme
// defined in section 3.1 of RFC 3987, and behaves the same as the
// EcmaScript builtin encodeURIComponent.
// It should not cause any misencoding of URLs in pages with
// Content-type: text/html;charset=UTF-8.
for i, n := 0, len(s); i < n; i++ {
c := s[i]
switch c {
// Single quote and parens are sub-delims in RFC 3986, but we
// escape them so the output can be embedded in in single
// quoted attributes and unquoted CSS url(...) constructs.
// Single quotes are reserved in URLs, but are only used in
// the obsolete "mark" rule in an appendix in RFC 3986
// so can be safely encoded.
case '!', '#', '$', '&', '*', '+', ',', '/', ':', ';', '=', '?', '@', '[', ']':
if norm {
continue
}
// Unreserved according to RFC 3986 sec 2.3
// "For consistency, percent-encoded octets in the ranges of
// ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D),
// period (%2E), underscore (%5F), or tilde (%7E) should not be
// created by URI producers
case '-', '.', '_', '~':
continue
case '%':
// When normalizing do not re-encode valid escapes.
if norm && i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
continue
}
default:
// Unreserved according to RFC 3986 sec 2.3
if 'a' <= c && c <= 'z' {
continue
}
if 'A' <= c && c <= 'Z' {
continue
}
if '0' <= c && c <= '9' {
continue
}
}
b.WriteString(s[written:i])
fmt.Fprintf(&b, "%%%02x", c)
written = i + 1
}
if written == 0 {
return s
}
b.WriteString(s[written:])
return b.String()
}
/template/html_test.go
0,0 → 1,94
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"html"
"strings"
"testing"
)
 
func TestHTMLNospaceEscaper(t *testing.T) {
input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
` !"#$%&'()*+,-./` +
`0123456789:;<=>?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
"`abcdefghijklmno" +
"pqrstuvwxyz{|}~\x7f" +
"\u00A0\u0100\u2028\u2029\ufeff\ufdec\U0001D11E")
 
want := ("&#xfffd;\x01\x02\x03\x04\x05\x06\x07" +
"\x08&#9;&#10;&#11;&#12;&#13;\x0E\x0F" +
"\x10\x11\x12\x13\x14\x15\x16\x17" +
"\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
`&#32;!&#34;#$%&amp;&#39;()*&#43;,-./` +
`0123456789:;&lt;&#61;&gt;?` +
`@ABCDEFGHIJKLMNO` +
`PQRSTUVWXYZ[\]^_` +
`&#96;abcdefghijklmno` +
`pqrstuvwxyz{|}~` + "\u007f" +
"\u00A0\u0100\u2028\u2029\ufeff&#xfdec;\U0001D11E")
 
got := htmlNospaceEscaper(input)
if got != want {
t.Errorf("encode: want\n\t%q\nbut got\n\t%q", want, got)
}
 
got, want = html.UnescapeString(got), strings.Replace(input, "\x00", "\ufffd", 1)
if want != got {
t.Errorf("decode: want\n\t%q\nbut got\n\t%q", want, got)
}
}
 
func TestStripTags(t *testing.T) {
tests := []struct {
input, want string
}{
{"", ""},
{"Hello, World!", "Hello, World!"},
{"foo&amp;bar", "foo&amp;bar"},
{`Hello <a href="www.example.com/">World</a>!`, "Hello World!"},
{"Foo <textarea>Bar</textarea> Baz", "Foo Bar Baz"},
{"Foo <!-- Bar --> Baz", "Foo Baz"},
{"<", "<"},
{"foo < bar", "foo < bar"},
{`Foo<script type="text/javascript">alert(1337)</script>Bar`, "FooBar"},
{`Foo<div title="1>2">Bar`, "FooBar"},
{`I <3 Ponies!`, `I <3 Ponies!`},
{`<script>foo()</script>`, ``},
}
 
for _, test := range tests {
if got := stripTags(test.input); got != test.want {
t.Errorf("%q: want %q, got %q", test.input, test.want, got)
}
}
}
 
func BenchmarkHTMLNospaceEscaper(b *testing.B) {
for i := 0; i < b.N; i++ {
htmlNospaceEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
 
func BenchmarkHTMLNospaceEscaperNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
htmlNospaceEscaper("The_quick,_brown_fox_jumps_over_the_lazy_dog.")
}
}
 
func BenchmarkStripTags(b *testing.B) {
for i := 0; i < b.N; i++ {
stripTags("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
}
}
 
func BenchmarkStripTagsNoSpecials(b *testing.B) {
for i := 0; i < b.N; i++ {
stripTags("The quick, brown fox jumps over the lazy dog.")
}
}
/template/html.go
0,0 → 1,257
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"fmt"
"strings"
"unicode/utf8"
)
 
// htmlNospaceEscaper escapes for inclusion in unquoted attribute values.
func htmlNospaceEscaper(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeHTML {
return htmlReplacer(stripTags(s), htmlNospaceNormReplacementTable, false)
}
return htmlReplacer(s, htmlNospaceReplacementTable, false)
}
 
// attrEscaper escapes for inclusion in quoted attribute values.
func attrEscaper(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeHTML {
return htmlReplacer(stripTags(s), htmlNormReplacementTable, true)
}
return htmlReplacer(s, htmlReplacementTable, true)
}
 
// rcdataEscaper escapes for inclusion in an RCDATA element body.
func rcdataEscaper(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeHTML {
return htmlReplacer(s, htmlNormReplacementTable, true)
}
return htmlReplacer(s, htmlReplacementTable, true)
}
 
// htmlEscaper escapes for inclusion in HTML text.
func htmlEscaper(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeHTML {
return s
}
return htmlReplacer(s, htmlReplacementTable, true)
}
 
// htmlReplacementTable contains the runes that need to be escaped
// inside a quoted attribute value or in a text node.
var htmlReplacementTable = []string{
// http://www.w3.org/TR/html5/tokenization.html#attribute-value-unquoted-state: "
// U+0000 NULL Parse error. Append a U+FFFD REPLACEMENT
// CHARACTER character to the current attribute's value.
// "
// and similarly
// http://www.w3.org/TR/html5/tokenization.html#before-attribute-value-state
0: "\uFFFD",
'"': "&#34;",
'&': "&amp;",
'\'': "&#39;",
'+': "&#43;",
'<': "&lt;",
'>': "&gt;",
}
 
// htmlNormReplacementTable is like htmlReplacementTable but without '&' to
// avoid over-encoding existing entities.
var htmlNormReplacementTable = []string{
0: "\uFFFD",
'"': "&#34;",
'\'': "&#39;",
'+': "&#43;",
'<': "&lt;",
'>': "&gt;",
}
 
// htmlNospaceReplacementTable contains the runes that need to be escaped
// inside an unquoted attribute value.
// The set of runes escaped is the union of the HTML specials and
// those determined by running the JS below in browsers:
// <div id=d></div>
// <script>(function () {
// var a = [], d = document.getElementById("d"), i, c, s;
// for (i = 0; i < 0x10000; ++i) {
// c = String.fromCharCode(i);
// d.innerHTML = "<span title=" + c + "lt" + c + "></span>"
// s = d.getElementsByTagName("SPAN")[0];
// if (!s || s.title !== c + "lt" + c) { a.push(i.toString(16)); }
// }
// document.write(a.join(", "));
// })()</script>
var htmlNospaceReplacementTable = []string{
0: "&#xfffd;",
'\t': "&#9;",
'\n': "&#10;",
'\v': "&#11;",
'\f': "&#12;",
'\r': "&#13;",
' ': "&#32;",
'"': "&#34;",
'&': "&amp;",
'\'': "&#39;",
'+': "&#43;",
'<': "&lt;",
'=': "&#61;",
'>': "&gt;",
// A parse error in the attribute value (unquoted) and
// before attribute value states.
// Treated as a quoting character by IE.
'`': "&#96;",
}
 
// htmlNospaceNormReplacementTable is like htmlNospaceReplacementTable but
// without '&' to avoid over-encoding existing entities.
var htmlNospaceNormReplacementTable = []string{
0: "&#xfffd;",
'\t': "&#9;",
'\n': "&#10;",
'\v': "&#11;",
'\f': "&#12;",
'\r': "&#13;",
' ': "&#32;",
'"': "&#34;",
'\'': "&#39;",
'+': "&#43;",
'<': "&lt;",
'=': "&#61;",
'>': "&gt;",
// A parse error in the attribute value (unquoted) and
// before attribute value states.
// Treated as a quoting character by IE.
'`': "&#96;",
}
 
// htmlReplacer returns s with runes replaced acccording to replacementTable
// and when badRunes is true, certain bad runes are allowed through unescaped.
func htmlReplacer(s string, replacementTable []string, badRunes bool) string {
written, b := 0, new(bytes.Buffer)
for i, r := range s {
if int(r) < len(replacementTable) {
if repl := replacementTable[r]; len(repl) != 0 {
b.WriteString(s[written:i])
b.WriteString(repl)
// Valid as long as replacementTable doesn't
// include anything above 0x7f.
written = i + utf8.RuneLen(r)
}
} else if badRunes {
// No-op.
// IE does not allow these ranges in unquoted attrs.
} else if 0xfdd0 <= r && r <= 0xfdef || 0xfff0 <= r && r <= 0xffff {
fmt.Fprintf(b, "%s&#x%x;", s[written:i], r)
written = i + utf8.RuneLen(r)
}
}
if written == 0 {
return s
}
b.WriteString(s[written:])
return b.String()
}
 
// stripTags takes a snippet of HTML and returns only the text content.
// For example, `<b>&iexcl;Hi!</b> <script>...</script>` -> `&iexcl;Hi! `.
func stripTags(html string) string {
var b bytes.Buffer
s, c, i, allText := []byte(html), context{}, 0, true
// Using the transition funcs helps us avoid mangling
// `<div title="1>2">` or `I <3 Ponies!`.
for i != len(s) {
if c.delim == delimNone {
st := c.state
// Use RCDATA instead of parsing into JS or CSS styles.
if c.element != elementNone && !isInTag(st) {
st = stateRCDATA
}
d, nread := transitionFunc[st](c, s[i:])
i1 := i + nread
if c.state == stateText || c.state == stateRCDATA {
// Emit text up to the start of the tag or comment.
j := i1
if d.state != c.state {
for j1 := j - 1; j1 >= i; j1-- {
if s[j1] == '<' {
j = j1
break
}
}
}
b.Write(s[i:j])
} else {
allText = false
}
c, i = d, i1
continue
}
i1 := i + bytes.IndexAny(s[i:], delimEnds[c.delim])
if i1 < i {
break
}
if c.delim != delimSpaceOrTagEnd {
// Consume any quote.
i1++
}
c, i = context{state: stateTag, element: c.element}, i1
}
if allText {
return html
} else if c.state == stateText || c.state == stateRCDATA {
b.Write(s[i:])
}
return b.String()
}
 
// htmlNameFilter accepts valid parts of an HTML attribute or tag name or
// a known-safe HTML attribute.
func htmlNameFilter(args ...interface{}) string {
s, t := stringify(args...)
if t == contentTypeHTMLAttr {
return s
}
if len(s) == 0 {
// Avoid violation of structure preservation.
// <input checked {{.K}}={{.V}}>.
// Without this, if .K is empty then .V is the value of
// checked, but otherwise .V is the value of the attribute
// named .K.
return filterFailsafe
}
s = strings.ToLower(s)
if t := attrType(s); t != contentTypePlain {
// TODO: Split attr and element name part filters so we can whitelist
// attributes.
return filterFailsafe
}
for _, r := range s {
switch {
case '0' <= r && r <= '9':
case 'a' <= r && r <= 'z':
default:
return filterFailsafe
}
}
return s
}
 
// commentEscaper returns the empty string regardless of input.
// Comment content does not correspond to any parsed structure or
// human-readable content, so the simplest and most secure policy is to drop
// content interpolated into comments.
// This approach is equally valid whether or not static comment content is
// removed from the template.
func commentEscaper(args ...interface{}) string {
return ""
}
/template/doc.go
0,0 → 1,186
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
/*
Package template (html/template) is a specialization of package text/template
that automates the construction of HTML output that is safe against code
injection.
 
 
Introduction
 
This package wraps package template so you can use the standard template API
to parse and execute templates.
 
set, err := new(template.Set).Parse(...)
// Error checking elided
err = set.Execute(out, "Foo", data)
 
If successful, set will now be injection-safe. Otherwise, err is an error
defined in the docs for ErrorCode.
 
HTML templates treat data values as plain text which should be encoded so they
can be safely embedded in an HTML document. The escaping is contextual, so
actions can appear within JavaScript, CSS, and URI contexts.
 
The security model used by this package assumes that template authors are
trusted, while Execute's data parameter is not. More details are provided below.
 
Example
 
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
 
produces
 
Hello, <script>alert('you have been pwned')</script>!
 
but with contextual autoescaping,
 
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
 
produces safe, escaped HTML output
 
Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
 
 
Contexts
 
This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing
functions to each simple action pipeline, so given the excerpt
 
<a href="/search?q={{.}}">{{.}}</a>
 
At parse time each {{.}} is overwritten to add escaping functions as necessary.
In this case it becomes
 
<a href="/search?q={{. | urlquery}}">{{. | html}}</a>
 
 
Errors
 
See the documentation of ErrorCode for details.
 
 
A fuller picture
 
The rest of this package comment may be skipped on first reading; it includes
details necessary to understand escaping contexts and error messages. Most users
will not need to understand these details.
 
 
Contexts
 
Assuming {{.}} is `O'Reilly: How are <i>you</i>?`, the table below shows
how {{.}} appears when used in the context to the left.
 
Context {{.}} After
{{.}} O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'> O&#39;Reilly: How are you?
<a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f
 
If used in an unsafe context, then the value might be filtered out:
 
Context {{.}} After
<a href="{{.}}"> #ZgotmplZ
 
since "O'Reilly:" is not an allowed protocol like "http:".
 
 
If {{.}} is the innocuous word, `left`, then it can appear more widely,
 
Context {{.}} After
{{.}} left
<a title='{{.}}'> left
<a href='{{.}}'> left
<a href='/{{.}}'> left
<a href='?dir={{.}}'> left
<a style="border-{{.}}: 4px"> left
<a style="align: {{.}}"> left
<a style="background: '{{.}}'> left
<a style="background: url('{{.}}')> left
<style>p.{{.}} {color:red}</style> left
 
Non-string values can be used in JavaScript contexts.
If {{.}} is
 
[]struct{A,B string}{ "foo", "bar" }
 
in the escaped template
 
<script>var pair = {{.}};</script>
 
then the template output is
 
<script>var pair = {"A": "foo", "B": "bar"};</script>
 
See package json to understand how non-string content is marshalled for
embedding in JavaScript contexts.
 
 
Typed Strings
 
By default, this package assumes that all pipelines produce a plain text string.
It adds escaping pipeline stages necessary to correctly and safely embed that
plain text string in the appropriate context.
 
When a data value is not plain text, you can make sure it is not over-escaped
by marking it with its type.
 
Types HTML, JS, URL, and others from content.go can carry safe content that is
exempted from escaping.
 
The template
 
Hello, {{.}}!
 
can be invoked with
 
tmpl.Execute(out, HTML(`<b>World</b>`))
 
to produce
 
Hello, <b>World</b>!
 
instead of the
 
Hello, &lt;b&gt;World&lt;b&gt;!
 
that would have been produced if {{.}} was a regular string.
 
 
Security Model
 
http://js-quasis-libraries-and-repl.googlecode.com/svn/trunk/safetemplate.html#problem_definition defines "safe" as used by this package.
 
This package assumes that template authors are trusted, that Execute's data
parameter is not, and seeks to preserve the properties below in the face
of untrusted data:
 
Structure Preservation Property
"... when a template author writes an HTML tag in a safe templating language,
the browser will interpret the corresponding portion of the output as a tag
regardless of the values of untrusted data, and similarly for other structures
such as attribute boundaries and JS and CSS string boundaries."
 
Code Effect Property
"... only code specified by the template author should run as a result of
injecting the template output into a page and all code specified by the
template author should run as a result of the same."
 
Least Surprise Property
"A developer (or code reviewer) familiar with HTML, CSS, and JavaScript, who
knows that contextual autoescaping happens should be able to look at a {{.}}
and correctly infer what sanitization happens."
*/
package template
/template/content_test.go
0,0 → 1,221
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"bytes"
"strings"
"testing"
)
 
func TestTypedContent(t *testing.T) {
data := []interface{}{
`<b> "foo%" O'Reilly &bar;`,
CSS(`a[href =~ "//example.com"]#foo`),
HTML(`Hello, <b>World</b> &amp;tc!`),
HTMLAttr(` dir="ltr"`),
JS(`c && alert("Hello, World!");`),
JSStr(`Hello, World & O'Reilly\x21`),
URL(`greeting=H%69&addressee=(World)`),
}
 
// For each content sensitive escaper, see how it does on
// each of the typed strings above.
tests := []struct {
// A template containing a single {{.}}.
input string
want []string
}{
{
`<style>{{.}} { color: blue }</style>`,
[]string{
`ZgotmplZ`,
// Allowed but not escaped.
`a[href =~ "//example.com"]#foo`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
},
},
{
`<div style="{{.}}">`,
[]string{
`ZgotmplZ`,
// Allowed and HTML escaped.
`a[href =~ &#34;//example.com&#34;]#foo`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
},
},
{
`{{.}}`,
[]string{
`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
`a[href =~ &#34;//example.com&#34;]#foo`,
// Not escaped.
`Hello, <b>World</b> &amp;tc!`,
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
`greeting=H%69&amp;addressee=(World)`,
},
},
{
`<a{{.}}>`,
[]string{
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
// Allowed and HTML escaped.
` dir="ltr"`,
`ZgotmplZ`,
`ZgotmplZ`,
`ZgotmplZ`,
},
},
{
`<a title={{.}}>`,
[]string{
`&lt;b&gt;&#32;&#34;foo%&#34;&#32;O&#39;Reilly&#32;&amp;bar;`,
`a[href&#32;&#61;~&#32;&#34;//example.com&#34;]#foo`,
// Tags stripped, spaces escaped, entity not re-escaped.
`Hello,&#32;World&#32;&amp;tc!`,
`&#32;dir&#61;&#34;ltr&#34;`,
`c&#32;&amp;&amp;&#32;alert(&#34;Hello,&#32;World!&#34;);`,
`Hello,&#32;World&#32;&amp;&#32;O&#39;Reilly\x21`,
`greeting&#61;H%69&amp;addressee&#61;(World)`,
},
},
{
`<a title='{{.}}'>`,
[]string{
`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
`a[href =~ &#34;//example.com&#34;]#foo`,
// Tags stripped, entity not re-escaped.
`Hello, World &amp;tc!`,
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
`greeting=H%69&amp;addressee=(World)`,
},
},
{
`<textarea>{{.}}</textarea>`,
[]string{
`&lt;b&gt; &#34;foo%&#34; O&#39;Reilly &amp;bar;`,
`a[href =~ &#34;//example.com&#34;]#foo`,
// Angle brackets escaped to prevent injection of close tags, entity not re-escaped.
`Hello, &lt;b&gt;World&lt;/b&gt; &amp;tc!`,
` dir=&#34;ltr&#34;`,
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
`Hello, World &amp; O&#39;Reilly\x21`,
`greeting=H%69&amp;addressee=(World)`,
},
},
{
`<script>alert({{.}})</script>`,
[]string{
`"\u003cb\u003e \"foo%\" O'Reilly &bar;"`,
`"a[href =~ \"//example.com\"]#foo"`,
`"Hello, \u003cb\u003eWorld\u003c/b\u003e &amp;tc!"`,
`" dir=\"ltr\""`,
// Not escaped.
`c && alert("Hello, World!");`,
// Escape sequence not over-escaped.
`"Hello, World & O'Reilly\x21"`,
`"greeting=H%69&addressee=(World)"`,
},
},
{
`<button onclick="alert({{.}})">`,
[]string{
`&#34;\u003cb\u003e \&#34;foo%\&#34; O&#39;Reilly &amp;bar;&#34;`,
`&#34;a[href =~ \&#34;//example.com\&#34;]#foo&#34;`,
`&#34;Hello, \u003cb\u003eWorld\u003c/b\u003e &amp;amp;tc!&#34;`,
`&#34; dir=\&#34;ltr\&#34;&#34;`,
// Not JS escaped but HTML escaped.
`c &amp;&amp; alert(&#34;Hello, World!&#34;);`,
// Escape sequence not over-escaped.
`&#34;Hello, World &amp; O&#39;Reilly\x21&#34;`,
`&#34;greeting=H%69&amp;addressee=(World)&#34;`,
},
},
{
`<script>alert("{{.}}")</script>`,
[]string{
`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
`a[href =~ \x22\/\/example.com\x22]#foo`,
`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
` dir=\x22ltr\x22`,
`c \x26\x26 alert(\x22Hello, World!\x22);`,
// Escape sequence not over-escaped.
`Hello, World \x26 O\x27Reilly\x21`,
`greeting=H%69\x26addressee=(World)`,
},
},
{
`<button onclick='alert("{{.}}")'>`,
[]string{
`\x3cb\x3e \x22foo%\x22 O\x27Reilly \x26bar;`,
`a[href =~ \x22\/\/example.com\x22]#foo`,
`Hello, \x3cb\x3eWorld\x3c\/b\x3e \x26amp;tc!`,
` dir=\x22ltr\x22`,
`c \x26\x26 alert(\x22Hello, World!\x22);`,
// Escape sequence not over-escaped.
`Hello, World \x26 O\x27Reilly\x21`,
`greeting=H%69\x26addressee=(World)`,
},
},
{
`<a href="?q={{.}}">`,
[]string{
`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
`%20dir%3d%22ltr%22`,
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is done.
`greeting=H%69&amp;addressee=%28World%29`,
},
},
{
`<style>body { background: url('?img={{.}}') }</style>`,
[]string{
`%3cb%3e%20%22foo%25%22%20O%27Reilly%20%26bar%3b`,
`a%5bhref%20%3d~%20%22%2f%2fexample.com%22%5d%23foo`,
`Hello%2c%20%3cb%3eWorld%3c%2fb%3e%20%26amp%3btc%21`,
`%20dir%3d%22ltr%22`,
`c%20%26%26%20alert%28%22Hello%2c%20World%21%22%29%3b`,
`Hello%2c%20World%20%26%20O%27Reilly%5cx21`,
// Quotes and parens are escaped but %69 is not over-escaped. HTML escaping is not done.
`greeting=H%69&addressee=%28World%29`,
},
},
}
 
for _, test := range tests {
tmpl := Must(New("x").Parse(test.input))
pre := strings.Index(test.input, "{{.}}")
post := len(test.input) - (pre + 5)
var b bytes.Buffer
for i, x := range data {
b.Reset()
if err := tmpl.Execute(&b, x); err != nil {
t.Errorf("%q with %v: %s", test.input, x, err)
continue
}
if want, got := test.want[i], b.String()[pre:b.Len()-post]; want != got {
t.Errorf("%q with %v:\nwant\n\t%q,\ngot\n\t%q\n", test.input, x, want, got)
continue
}
}
}
}
/template/content.go
0,0 → 1,113
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"fmt"
"reflect"
)
 
// Strings of content from a trusted source.
type (
// CSS encapsulates known safe content that matches any of:
// 1. The CSS3 stylesheet production, such as `p { color: purple }`.
// 2. The CSS3 rule production, such as `a[href=~"https:"].foo#bar`.
// 3. CSS3 declaration productions, such as `color: red; margin: 2px`.
// 4. The CSS3 value production, such as `rgba(0, 0, 255, 127)`.
// See http://www.w3.org/TR/css3-syntax/#style
CSS string
 
// HTML encapsulates a known safe HTML document fragment.
// It should not be used for HTML from a third-party, or HTML with
// unclosed tags or comments. The outputs of a sound HTML sanitizer
// and a template escaped by this package are fine for use with HTML.
HTML string
 
// HTMLAttr encapsulates an HTML attribute from a trusted source,
// for example: ` dir="ltr"`.
HTMLAttr string
 
// JS encapsulates a known safe EcmaScript5 Expression, or example,
// `(x + y * z())`.
// Template authors are responsible for ensuring that typed expressions
// do not break the intended precedence and that there is no
// statement/expression ambiguity as when passing an expression like
// "{ foo: bar() }\n['foo']()", which is both a valid Expression and a
// valid Program with a very different meaning.
JS string
 
// JSStr encapsulates a sequence of characters meant to be embedded
// between quotes in a JavaScript expression.
// The string must match a series of StringCharacters:
// StringCharacter :: SourceCharacter but not `\` or LineTerminator
// | EscapeSequence
// Note that LineContinuations are not allowed.
// JSStr("foo\\nbar") is fine, but JSStr("foo\\\nbar") is not.
JSStr string
 
// URL encapsulates a known safe URL as defined in RFC 3896.
// A URL like `javascript:checkThatFormNotEditedBeforeLeavingPage()`
// from a trusted source should go in the page, but by default dynamic
// `javascript:` URLs are filtered out since they are a frequently
// exploited injection vector.
URL string
)
 
type contentType uint8
 
const (
contentTypePlain contentType = iota
contentTypeCSS
contentTypeHTML
contentTypeHTMLAttr
contentTypeJS
contentTypeJSStr
contentTypeURL
// contentTypeUnsafe is used in attr.go for values that affect how
// embedded content and network messages are formed, vetted,
// or interpreted; or which credentials network messages carry.
contentTypeUnsafe
)
 
// indirect returns the value, after dereferencing as many times
// as necessary to reach the base type (or nil).
func indirect(a interface{}) interface{} {
if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr {
// Avoid creating a reflect.Value if it's not a pointer.
return a
}
v := reflect.ValueOf(a)
for v.Kind() == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return v.Interface()
}
 
// stringify converts its arguments to a string and the type of the content.
// All pointers are dereferenced, as in the text/template package.
func stringify(args ...interface{}) (string, contentType) {
if len(args) == 1 {
switch s := indirect(args[0]).(type) {
case string:
return s, contentTypePlain
case CSS:
return string(s), contentTypeCSS
case HTML:
return string(s), contentTypeHTML
case HTMLAttr:
return string(s), contentTypeHTMLAttr
case JS:
return string(s), contentTypeJS
case JSStr:
return string(s), contentTypeJSStr
case URL:
return string(s), contentTypeURL
}
}
for i, arg := range args {
args[i] = indirect(arg)
}
return fmt.Sprint(args...), contentTypePlain
}
/template/attr.go
0,0 → 1,175
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package template
 
import (
"strings"
)
 
// attrTypeMap[n] describes the value of the given attribute.
// If an attribute affects (or can mask) the encoding or interpretation of
// other content, or affects the contents, idempotency, or credentials of a
// network message, then the value in this map is contentTypeUnsafe.
// This map is derived from HTML5, specifically
// http://www.w3.org/TR/html5/Overview.html#attributes-1
// as well as "%URI"-typed attributes from
// http://www.w3.org/TR/html4/index/attributes.html
var attrTypeMap = map[string]contentType{
"accept": contentTypePlain,
"accept-charset": contentTypeUnsafe,
"action": contentTypeURL,
"alt": contentTypePlain,
"archive": contentTypeURL,
"async": contentTypeUnsafe,
"autocomplete": contentTypePlain,
"autofocus": contentTypePlain,
"autoplay": contentTypePlain,
"background": contentTypeURL,
"border": contentTypePlain,
"checked": contentTypePlain,
"cite": contentTypeURL,
"challenge": contentTypeUnsafe,
"charset": contentTypeUnsafe,
"class": contentTypePlain,
"classid": contentTypeURL,
"codebase": contentTypeURL,
"cols": contentTypePlain,
"colspan": contentTypePlain,
"content": contentTypeUnsafe,
"contenteditable": contentTypePlain,
"contextmenu": contentTypePlain,
"controls": contentTypePlain,
"coords": contentTypePlain,
"crossorigin": contentTypeUnsafe,
"data": contentTypeURL,
"datetime": contentTypePlain,
"default": contentTypePlain,
"defer": contentTypeUnsafe,
"dir": contentTypePlain,
"dirname": contentTypePlain,
"disabled": contentTypePlain,
"draggable": contentTypePlain,
"dropzone": contentTypePlain,
"enctype": contentTypeUnsafe,
"for": contentTypePlain,
"form": contentTypeUnsafe,
"formaction": contentTypeURL,
"formenctype": contentTypeUnsafe,
"formmethod": contentTypeUnsafe,
"formnovalidate": contentTypeUnsafe,
"formtarget": contentTypePlain,
"headers": contentTypePlain,
"height": contentTypePlain,
"hidden": contentTypePlain,
"high": contentTypePlain,
"href": contentTypeURL,
"hreflang": contentTypePlain,
"http-equiv": contentTypeUnsafe,
"icon": contentTypeURL,
"id": contentTypePlain,
"ismap": contentTypePlain,
"keytype": contentTypeUnsafe,
"kind": contentTypePlain,
"label": contentTypePlain,
"lang": contentTypePlain,
"language": contentTypeUnsafe,
"list": contentTypePlain,
"longdesc": contentTypeURL,
"loop": contentTypePlain,
"low": contentTypePlain,
"manifest": contentTypeURL,
"max": contentTypePlain,
"maxlength": contentTypePlain,
"media": contentTypePlain,
"mediagroup": contentTypePlain,
"method": contentTypeUnsafe,
"min": contentTypePlain,
"multiple": contentTypePlain,
"name": contentTypePlain,
"novalidate": contentTypeUnsafe,
// Skip handler names from
// http://www.w3.org/TR/html5/Overview.html#event-handlers-on-elements-document-objects-and-window-objects
// since we have special handling in attrType.
"open": contentTypePlain,
"optimum": contentTypePlain,
"pattern": contentTypeUnsafe,
"placeholder": contentTypePlain,
"poster": contentTypeURL,
"profile": contentTypeURL,
"preload": contentTypePlain,
"pubdate": contentTypePlain,
"radiogroup": contentTypePlain,
"readonly": contentTypePlain,
"rel": contentTypeUnsafe,
"required": contentTypePlain,
"reversed": contentTypePlain,
"rows": contentTypePlain,
"rowspan": contentTypePlain,
"sandbox": contentTypeUnsafe,
"spellcheck": contentTypePlain,
"scope": contentTypePlain,
"scoped": contentTypePlain,
"seamless": contentTypePlain,
"selected": contentTypePlain,
"shape": contentTypePlain,
"size": contentTypePlain,
"sizes": contentTypePlain,
"span": contentTypePlain,
"src": contentTypeURL,
"srcdoc": contentTypeHTML,
"srclang": contentTypePlain,
"start": contentTypePlain,
"step": contentTypePlain,
"style": contentTypeCSS,
"tabindex": contentTypePlain,
"target": contentTypePlain,
"title": contentTypePlain,
"type": contentTypeUnsafe,
"usemap": contentTypeURL,
"value": contentTypeUnsafe,
"width": contentTypePlain,
"wrap": contentTypePlain,
"xmlns": contentTypeURL,
}
 
// attrType returns a conservative (upper-bound on authority) guess at the
// type of the named attribute.
func attrType(name string) contentType {
name = strings.ToLower(name)
if strings.HasPrefix(name, "data-") {
// Strip data- so that custom attribute heuristics below are
// widely applied.
// Treat data-action as URL below.
name = name[5:]
} else if colon := strings.IndexRune(name, ':'); colon != -1 {
if name[:colon] == "xmlns" {
return contentTypeURL
}
// Treat svg:href and xlink:href as href below.
name = name[colon+1:]
}
if t, ok := attrTypeMap[name]; ok {
return t
}
// Treat partial event handler names as script.
if strings.HasPrefix(name, "on") {
return contentTypeJS
}
 
// Heuristics to prevent "javascript:..." injection in custom
// data attributes and custom attributes like g:tweetUrl.
// http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible-data-with-the-data-attributes:
// "Custom data attributes are intended to store custom data
// private to the page or application, for which there are no
// more appropriate attributes or elements."
// Developers seem to store URL content in data URLs that start
// or end with "URI" or "URL".
if strings.Contains(name, "src") ||
strings.Contains(name, "uri") ||
strings.Contains(name, "url") {
return contentTypeURL
}
return contentTypePlain
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.