diff options
author | Tianon Gravi <admwiggin@gmail.com> | 2015-01-15 11:54:00 -0700 |
---|---|---|
committer | Tianon Gravi <admwiggin@gmail.com> | 2015-01-15 11:54:00 -0700 |
commit | f154da9e12608589e8d5f0508f908a0c3e88a1bb (patch) | |
tree | f8255d51e10c6f1e0ed69702200b966c9556a431 /src/pkg/html | |
parent | 8d8329ed5dfb9622c82a9fbec6fd99a580f9c9f6 (diff) | |
download | golang-upstream/1.4.tar.gz |
Imported Upstream version 1.4upstream/1.4
Diffstat (limited to 'src/pkg/html')
23 files changed, 0 insertions, 9471 deletions
diff --git a/src/pkg/html/entity.go b/src/pkg/html/entity.go deleted file mode 100644 index af8a007ed..000000000 --- a/src/pkg/html/entity.go +++ /dev/null @@ -1,2253 +0,0 @@ -// 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'}, -} diff --git a/src/pkg/html/entity_test.go b/src/pkg/html/entity_test.go deleted file mode 100644 index b53f866fa..000000000 --- a/src/pkg/html/entity_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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])) - } - } -} diff --git a/src/pkg/html/escape.go b/src/pkg/html/escape.go deleted file mode 100644 index dd5dfa7cd..000000000 --- a/src/pkg/html/escape.go +++ /dev/null @@ -1,250 +0,0 @@ -// 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 "<" 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<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 -} - -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 = "&" - case '\'': - // "'" is shorter than "'" and apos was not in HTML until HTML5. - esc = "'" - case '<': - esc = "<" - case '>': - esc = ">" - case '"': - // """ is shorter than """. - esc = """ - 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 "<". It -// escapes only five such characters: <, >, &, ' and ". -// 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 "<" to become "<". It unescapes a -// larger range of entities than EscapeString escapes. For example, "á" -// unescapes to "á", as does "á" 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 -} diff --git a/src/pkg/html/escape_test.go b/src/pkg/html/escape_test.go deleted file mode 100644 index 2d7ad8ac2..000000000 --- a/src/pkg/html/escape_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2013 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" - -type unescapeTest struct { - // A short description of the test case. - desc string - // The HTML text. - html string - // The unescaped text. - unescaped string -} - -var unescapeTests = []unescapeTest{ - // Handle no entities. - { - "copy", - "A\ttext\nstring", - "A\ttext\nstring", - }, - // Handle simple named entities. - { - "simple", - "& > <", - "& > <", - }, - // Handle hitting the end of the string. - { - "stringEnd", - "& &", - "& &", - }, - // Handle entities with two codepoints. - { - "multiCodepoint", - "text ⋛︀ blah", - "text \u22db\ufe00 blah", - }, - // Handle decimal numeric entities. - { - "decimalEntity", - "Delta = Δ ", - "Delta = Δ ", - }, - // Handle hexadecimal numeric entities. - { - "hexadecimalEntity", - "Lambda = λ = λ ", - "Lambda = λ = λ ", - }, - // Handle numeric early termination. - { - "numericEnds", - "&# &#x €43 © = ©f = ©", - "&# &#x €43 © = ©f = ©", - }, - // Handle numeric ISO-8859-1 entity replacements. - { - "numericReplacements", - "Footnote‡", - "Footnote‡", - }, - // Handle single ampersand. - { - "copySingleAmpersand", - "&", - "&", - }, - // Handle ampersand followed by non-entity. - { - "copyAmpersandNonEntity", - "text &test", - "text &test", - }, - // Handle "&#". - { - "copyAmpersandHash", - "text &#", - "text &#", - }, -} - -func TestUnescape(t *testing.T) { - for _, tt := range unescapeTests { - unescaped := UnescapeString(tt.html) - if unescaped != tt.unescaped { - t.Errorf("TestUnescape %s: want %q, got %q", tt.desc, tt.unescaped, unescaped) - } - } -} - -func TestUnescapeEscape(t *testing.T) { - ss := []string{ - ``, - `abc def`, - `a & b`, - `a&b`, - `a & b`, - `"`, - `"`, - `"<&>"`, - `"<&>"`, - `3&5==1 && 0<1, "0<1", a+acute=á`, - `The special characters are: <, >, &, ' and "`, - } - for _, s := range ss { - if got := UnescapeString(EscapeString(s)); got != s { - t.Errorf("got %q want %q", got, s) - } - } -} diff --git a/src/pkg/html/template/attr.go b/src/pkg/html/template/attr.go deleted file mode 100644 index d65d34007..000000000 --- a/src/pkg/html/template/attr.go +++ /dev/null @@ -1,175 +0,0 @@ -// 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/webappapis.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/dom.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 -} diff --git a/src/pkg/html/template/clone_test.go b/src/pkg/html/template/clone_test.go deleted file mode 100644 index e11bff2c5..000000000 --- a/src/pkg/html/template/clone_test.go +++ /dev/null @@ -1,188 +0,0 @@ -// 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" - "errors" - "io/ioutil" - "testing" - "text/template/parse" -) - -func TestAddParseTree(t *testing.T) { - root := Must(New("root").Parse(`{{define "a"}} {{.}} {{template "b"}} {{.}} "></a>{{end}}`)) - tree, err := parse.Parse("t", `{{define "b"}}<a href="{{end}}`, "", "", nil, nil) - if err != nil { - t.Fatal(err) - } - added := Must(root.AddParseTree("b", tree["b"])) - b := new(bytes.Buffer) - err = added.ExecuteTemplate(b, "a", "1>0") - if err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` 1>0 <a href=" 1%3e0 "></a>`; got != want { - t.Errorf("got %q want %q", got, want) - } -} - -func TestClone(t *testing.T) { - // The {{.}} will be executed with data "<i>*/" in different contexts. - // In the t0 template, it will be in a text context. - // In the t1 template, it will be in a URL context. - // In the t2 template, it will be in a JavaScript context. - // In the t3 template, it will be in a CSS context. - const tmpl = `{{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}}` - b := new(bytes.Buffer) - - // Create an incomplete template t0. - t0 := Must(New("t0").Parse(tmpl)) - - // Clone t0 as t1. - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "lhs"}} <a href=" {{end}}`)) - Must(t1.Parse(`{{define "rhs"}} "></a> {{end}}`)) - - // Execute t1. - b.Reset() - if err := t1.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <a href=" %3ci%3e*/ "></a> `; got != want { - t.Errorf("t1: got %q want %q", got, want) - } - - // Clone t0 as t2. - t2 := Must(t0.Clone()) - Must(t2.Parse(`{{define "lhs"}} <p onclick="javascript: {{end}}`)) - Must(t2.Parse(`{{define "rhs"}} "></p> {{end}}`)) - - // Execute t2. - b.Reset() - if err := t2.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <p onclick="javascript: "\u003ci\u003e*/" "></p> `; got != want { - t.Errorf("t2: got %q want %q", got, want) - } - - // Clone t0 as t3, but do not execute t3 yet. - t3 := Must(t0.Clone()) - Must(t3.Parse(`{{define "lhs"}} <style> {{end}}`)) - Must(t3.Parse(`{{define "rhs"}} </style> {{end}}`)) - - // Complete t0. - Must(t0.Parse(`{{define "lhs"}} ( {{end}}`)) - Must(t0.Parse(`{{define "rhs"}} ) {{end}}`)) - - // Clone t0 as t4. Redefining the "lhs" template should fail. - t4 := Must(t0.Clone()) - if _, err := t4.Parse(`{{define "lhs"}} FAIL {{end}}`); err == nil { - t.Error(`redefine "lhs": got nil err want non-nil`) - } - - // Execute t0. - b.Reset() - if err := t0.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` ( <i>*/ ) `; got != want { - t.Errorf("t0: got %q want %q", got, want) - } - - // Clone t0. This should fail, as t0 has already executed. - if _, err := t0.Clone(); err == nil { - t.Error(`t0.Clone(): got nil err want non-nil`) - } - - // Similarly, cloning sub-templates should fail. - if _, err := t0.Lookup("a").Clone(); err == nil { - t.Error(`t0.Lookup("a").Clone(): got nil err want non-nil`) - } - if _, err := t0.Lookup("lhs").Clone(); err == nil { - t.Error(`t0.Lookup("lhs").Clone(): got nil err want non-nil`) - } - - // Execute t3. - b.Reset() - if err := t3.ExecuteTemplate(b, "a", "<i>*/"); err != nil { - t.Fatal(err) - } - if got, want := b.String(), ` <style> ZgotmplZ </style> `; got != want { - t.Errorf("t3: got %q want %q", got, want) - } -} - -func TestTemplates(t *testing.T) { - names := []string{"t0", "a", "lhs", "rhs"} - // Some template definitions borrowed from TestClone. - const tmpl = ` - {{define "a"}}{{template "lhs"}}{{.}}{{template "rhs"}}{{end}} - {{define "lhs"}} <a href=" {{end}} - {{define "rhs"}} "></a> {{end}}` - t0 := Must(New("t0").Parse(tmpl)) - templates := t0.Templates() - if len(templates) != len(names) { - t.Errorf("expected %d templates; got %d", len(names), len(templates)) - } - for _, name := range names { - found := false - for _, tmpl := range templates { - if name == tmpl.text.Name() { - found = true - break - } - } - if !found { - t.Error("could not find template", name) - } - } -} - -// This used to crash; http://golang.org/issue/3281 -func TestCloneCrash(t *testing.T) { - t1 := New("all") - Must(t1.New("t1").Parse(`{{define "foo"}}foo{{end}}`)) - t1.Clone() -} - -// Ensure that this guarantee from the docs is upheld: -// "Further calls to Parse in the copy will add templates -// to the copy but not to the original." -func TestCloneThenParse(t *testing.T) { - t0 := Must(New("t0").Parse(`{{define "a"}}{{template "embedded"}}{{end}}`)) - t1 := Must(t0.Clone()) - Must(t1.Parse(`{{define "embedded"}}t1{{end}}`)) - if len(t0.Templates())+1 != len(t1.Templates()) { - t.Error("adding a template to a clone added it to the original") - } - // double check that the embedded template isn't available in the original - err := t0.ExecuteTemplate(ioutil.Discard, "a", nil) - if err == nil { - t.Error("expected 'no such template' error") - } -} - -// https://code.google.com/p/go/issues/detail?id=5980 -func TestFuncMapWorksAfterClone(t *testing.T) { - funcs := FuncMap{"customFunc": func() (string, error) { - return "", errors.New("issue5980") - }} - - // get the expected error output (no clone) - uncloned := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - wantErr := uncloned.Execute(ioutil.Discard, nil) - - // toClone must be the same as uncloned. It has to be recreated from scratch, - // since cloning cannot occur after execution. - toClone := Must(New("").Funcs(funcs).Parse("{{customFunc}}")) - cloned := Must(toClone.Clone()) - gotErr := cloned.Execute(ioutil.Discard, nil) - - if wantErr.Error() != gotErr.Error() { - t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr) - } -} diff --git a/src/pkg/html/template/content.go b/src/pkg/html/template/content.go deleted file mode 100644 index 3715ed5c9..000000000 --- a/src/pkg/html/template/content.go +++ /dev/null @@ -1,136 +0,0 @@ -// 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/#parsing and - // https://web.archive.org/web/20090211114933/http://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, for 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 or URL substring (see RFC 3986). - // 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 a == nil { - return nil - } - 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() -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() -) - -// indirectToStringerOrError returns the value, after dereferencing as many times -// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer -// or error, -func indirectToStringerOrError(a interface{}) interface{} { - if a == nil { - return nil - } - v := reflect.ValueOf(a) - for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && 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] = indirectToStringerOrError(arg) - } - return fmt.Sprint(args...), contentTypePlain -} diff --git a/src/pkg/html/template/content_test.go b/src/pkg/html/template/content_test.go deleted file mode 100644 index 5f3ffe2d3..000000000 --- a/src/pkg/html/template/content_test.go +++ /dev/null @@ -1,280 +0,0 @@ -// 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" - "testing" -) - -func TestTypedContent(t *testing.T) { - data := []interface{}{ - `<b> "foo%" O'Reilly &bar;`, - CSS(`a[href =~ "//example.com"]#foo`), - HTML(`Hello, <b>World</b> &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 =~ "//example.com"]#foo`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `{{.}}`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Not escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\x21`, - `greeting=H%69&addressee=(World)`, - }, - }, - { - `<a{{.}}>`, - []string{ - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - // Allowed and HTML escaped. - ` dir="ltr"`, - `ZgotmplZ`, - `ZgotmplZ`, - `ZgotmplZ`, - }, - }, - { - `<a title={{.}}>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, spaces escaped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\x21`, - `greeting=H%69&addressee=(World)`, - }, - }, - { - `<a title='{{.}}'>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Tags stripped, entity not re-escaped. - `Hello, World &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\x21`, - `greeting=H%69&addressee=(World)`, - }, - }, - { - `<textarea>{{.}}</textarea>`, - []string{ - `<b> "foo%" O'Reilly &bar;`, - `a[href =~ "//example.com"]#foo`, - // Angle brackets escaped to prevent injection of close tags, entity not re-escaped. - `Hello, <b>World</b> &tc!`, - ` dir="ltr"`, - `c && alert("Hello, World!");`, - `Hello, World & O'Reilly\x21`, - `greeting=H%69&addressee=(World)`, - }, - }, - { - `<script>alert({{.}})</script>`, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\x21"`, - `"greeting=H%69\u0026addressee=(World)"`, - }, - }, - { - `<button onclick="alert({{.}})">`, - []string{ - `"\u003cb\u003e \"foo%\" O'Reilly \u0026bar;"`, - `"a[href =~ \"//example.com\"]#foo"`, - `"Hello, \u003cb\u003eWorld\u003c/b\u003e \u0026amp;tc!"`, - `" dir=\"ltr\""`, - // Not JS escaped but HTML escaped. - `c && alert("Hello, World!");`, - // Escape sequence not over-escaped. - `"Hello, World & O'Reilly\x21"`, - `"greeting=H%69\u0026addressee=(World)"`, - }, - }, - { - `<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&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 - } - } - } -} - -// Test that we print using the String method. Was issue 3073. -type stringer struct { - v int -} - -func (s *stringer) String() string { - return fmt.Sprintf("string=%d", s.v) -} - -type errorer struct { - v int -} - -func (s *errorer) Error() string { - return fmt.Sprintf("error=%d", s.v) -} - -func TestStringer(t *testing.T) { - s := &stringer{3} - b := new(bytes.Buffer) - tmpl := Must(New("x").Parse("{{.}}")) - if err := tmpl.Execute(b, s); err != nil { - t.Fatal(err) - } - var expect = "string=3" - if b.String() != expect { - t.Errorf("expected %q got %q", expect, b.String()) - } - e := &errorer{7} - b.Reset() - if err := tmpl.Execute(b, e); err != nil { - t.Fatal(err) - } - expect = "error=7" - if b.String() != expect { - t.Errorf("expected %q got %q", expect, b.String()) - } -} - -// https://code.google.com/p/go/issues/detail?id=5982 -func TestEscapingNilNonemptyInterfaces(t *testing.T) { - tmpl := Must(New("x").Parse("{{.E}}")) - - got := new(bytes.Buffer) - testData := struct{ E error }{} // any non-empty interface here will do; error is just ready at hand - tmpl.Execute(got, testData) - - // Use this data instead of just hard-coding "<nil>" to avoid - // dependencies on the html escaper and the behavior of fmt w.r.t. nil. - want := new(bytes.Buffer) - data := struct{ E string }{E: fmt.Sprint(nil)} - tmpl.Execute(want, data) - - if !bytes.Equal(want.Bytes(), got.Bytes()) { - t.Errorf("expected %q got %q", string(want.Bytes()), string(got.Bytes())) - } -} diff --git a/src/pkg/html/template/context.go b/src/pkg/html/template/context.go deleted file mode 100644 index 59e794d68..000000000 --- a/src/pkg/html/template/context.go +++ /dev/null @@ -1,339 +0,0 @@ -// 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/syntax.html#the-end -// 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 reports 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://www.w3.org/TR/html5/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)) -} diff --git a/src/pkg/html/template/css.go b/src/pkg/html/template/css.go deleted file mode 100644 index 634f183f7..000000000 --- a/src/pkg/html/template/css.go +++ /dev/null @@ -1,268 +0,0 @@ -// 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 reports 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 reports 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 reports 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 reports 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) -} diff --git a/src/pkg/html/template/css_test.go b/src/pkg/html/template/css_test.go deleted file mode 100644 index a735638b0..000000000 --- a/src/pkg/html/template/css_test.go +++ /dev/null @@ -1,281 +0,0 @@ -// 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`) - } -} diff --git a/src/pkg/html/template/doc.go b/src/pkg/html/template/doc.go deleted file mode 100644 index d422ada37..000000000 --- a/src/pkg/html/template/doc.go +++ /dev/null @@ -1,191 +0,0 @@ -// 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) implements data-driven templates for -generating HTML output safe against code injection. It provides the -same interface as package text/template and should be used instead of -text/template whenever the output is HTML. - -The documentation here focuses on the security features of the package. -For information about how to program the templates themselves, see the -documentation for text/template. - -Introduction - -This package wraps package text/template so you can share its template API -to parse and execute HTML templates safely. - - tmpl, err := template.New("name").Parse(...) - // Error checking elided - err = tmpl.Execute(out, data) - -If successful, tmpl 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 the contextual autoescaping in html/template - - 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, <script>alert('you have been pwned')</script>! - - -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 <i>you</i>? - <a title='{{.}}'> O'Reilly: How are you? - <a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e? - <a href="?q={{.}}"> O'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, <b>World<b>! - -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 diff --git a/src/pkg/html/template/error.go b/src/pkg/html/template/error.go deleted file mode 100644 index 46e49ccf8..000000000 --- a/src/pkg/html/template/error.go +++ /dev/null @@ -1,197 +0,0 @@ -// 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...)} -} diff --git a/src/pkg/html/template/escape.go b/src/pkg/html/template/escape.go deleted file mode 100644 index 4e379828d..000000000 --- a/src/pkg/html/template/escape.go +++ /dev/null @@ -1,815 +0,0 @@ -// 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" - "io" - "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 - t.Tree = nil - } - } - return err - } - } - e.commit() - for _, name := range names { - if t := tmpl.set[name]; t != nil { - t.escaped = true - t.Tree = t.text.Tree - } - } - return nil -} - -// funcMap maps command names to functions that render their inputs safe. -var funcMap = template.FuncMap{ - "html_template_attrescaper": attrEscaper, - "html_template_commentescaper": commentEscaper, - "html_template_cssescaper": cssEscaper, - "html_template_cssvaluefilter": cssValueFilter, - "html_template_htmlnamefilter": htmlNameFilter, - "html_template_htmlescaper": htmlEscaper, - "html_template_jsregexpescaper": jsRegexpEscaper, - "html_template_jsstrescaper": jsStrEscaper, - "html_template_jsvalescaper": jsValEscaper, - "html_template_nospaceescaper": htmlNospaceEscaper, - "html_template_rcdataescaper": rcdataEscaper, - "html_template_urlescaper": urlEscaper, - "html_template_urlfilter": urlFilter, - "html_template_urlnormalizer": urlNormalizer, -} - -// equivEscapers matches contextual escapers to equivalent template builtins. -var equivEscapers = map[string]string{ - "html_template_attrescaper": "html", - "html_template_htmlescaper": "html", - "html_template_nospaceescaper": "html", - "html_template_rcdataescaper": "html", - "html_template_urlescaper": "urlquery", - "html_template_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, "html_template_urlfilter") - fallthrough - case urlPartPreQuery: - switch c.state { - case stateCSSDqStr, stateCSSSqStr: - s = append(s, "html_template_cssescaper") - default: - s = append(s, "html_template_urlnormalizer") - } - case urlPartQueryOrFrag: - s = append(s, "html_template_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, "html_template_jsvalescaper") - // A slash after a value starts a div operator. - c.jsCtx = jsCtxDivOp - case stateJSDqStr, stateJSSqStr: - s = append(s, "html_template_jsstrescaper") - case stateJSRegexp: - s = append(s, "html_template_jsregexpescaper") - case stateCSS: - s = append(s, "html_template_cssvaluefilter") - case stateText: - s = append(s, "html_template_htmlescaper") - case stateRCDATA: - s = append(s, "html_template_rcdataescaper") - case stateAttr: - // Handled below in delim check. - case stateAttrName, stateTag: - c.state = stateAttrName - s = append(s, "html_template_htmlnamefilter") - default: - if isComment(c.state) { - s = append(s, "html_template_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, "html_template_nospaceescaper") - default: - s = append(s, "html_template_attrescaper") - } - e.editActionNode(n, s) - return c -} - -// allIdents returns the names of the identifiers under the Ident field of the node, -// which might be a singleton (Identifier) or a slice (Field). -func allIdents(node parse.Node) []string { - switch node := node.(type) { - case *parse.IdentifierNode: - return []string{node.Ident} - case *parse.FieldNode: - return node.Ident - } - panic("unidentified node type in allIdents") -} - -// 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 _, ok := cmd.Args[0].(*parse.IdentifierNode); ok { - continue - } - } - idents = p.Cmds[i+1:] - } - dups := 0 - for _, idNode := range idents { - for _, ident := range allIdents(idNode.Args[0]) { - if escFnsEq(s[dups], 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 _, idNode := range idents { - pos := idNode.Args[0].Position() - for _, ident := range allIdents(idNode.Args[0]) { - i := indexOfStr(ident, s, escFnsEq) - if i != -1 { - for _, name := range s[:i] { - newCmds = appendCmd(newCmds, newIdentCmd(name, pos)) - } - s = s[i+1:] - } - } - newCmds = appendCmd(newCmds, idNode) - } - // Create any remaining sanitizers. - for _, name := range s { - newCmds = appendCmd(newCmds, newIdentCmd(name, p.Position())) - } - 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{ - "html_template_commentescaper": { - "html_template_attrescaper": true, - "html_template_nospaceescaper": true, - "html_template_htmlescaper": true, - }, - "html_template_cssescaper": { - "html_template_attrescaper": true, - }, - "html_template_jsregexpescaper": { - "html_template_attrescaper": true, - }, - "html_template_jsstrescaper": { - "html_template_attrescaper": true, - }, - "html_template_urlescaper": { - "html_template_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 reports 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, pos parse.Pos) *parse.CommandNode { - return &parse.CommandNode{ - NodeType: parse.NodeCommand, - Args: []parse.Node{parse.NewIdentifier(identifier).SetPos(pos)}, - } -} - -// 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: t.Root.CopyList()} - 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(bytes.ToUpper(s[j:]), doctypeBytes) { - b.Write(s[written:j]) - b.WriteString("<") - 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/syntax.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("Hi!")"> - // 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 -} - -// Forwarding functions so that clients need only import this package -// to reach the general escaping functions of text/template. - -// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. -func HTMLEscape(w io.Writer, b []byte) { - template.HTMLEscape(w, b) -} - -// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. -func HTMLEscapeString(s string) string { - return template.HTMLEscapeString(s) -} - -// HTMLEscaper returns the escaped HTML equivalent of the textual -// representation of its arguments. -func HTMLEscaper(args ...interface{}) string { - return template.HTMLEscaper(args...) -} - -// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. -func JSEscape(w io.Writer, b []byte) { - template.JSEscape(w, b) -} - -// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. -func JSEscapeString(s string) string { - return template.JSEscapeString(s) -} - -// JSEscaper returns the escaped JavaScript equivalent of the textual -// representation of its arguments. -func JSEscaper(args ...interface{}) string { - return template.JSEscaper(args...) -} - -// URLQueryEscaper returns the escaped value of the textual representation of -// its arguments in a form suitable for embedding in a URL query. -func URLQueryEscaper(args ...interface{}) string { - return template.URLQueryEscaper(args...) -} diff --git a/src/pkg/html/template/escape_test.go b/src/pkg/html/template/escape_test.go deleted file mode 100644 index 3ccf93ece..000000000 --- a/src/pkg/html/template/escape_test.go +++ /dev/null @@ -1,1692 +0,0 @@ -// 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" - "os" - "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(`¡<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, <Cincinatti>!", - }, - { - "else", - "{{if .F}}{{.H}}{{else}}{{.G}}{{end}}!", - "<Goodbye>!", - }, - { - "overescaping1", - "Hello, {{.C | html}}!", - "Hello, <Cincinatti>!", - }, - { - "overescaping2", - "Hello, {{html .C}}!", - "Hello, <Cincinatti>!", - }, - { - "overescaping3", - "{{with .C}}{{$msg := .}}Hello, {{$msg}}!{{end}}", - "Hello, <Cincinatti>!", - }, - { - "assignment", - "{{if $x := .H}}{{$x}}{{end}}", - "<Hello>", - }, - { - "withBody", - "{{with .H}}{{.}}{{end}}", - "<Hello>", - }, - { - "withElse", - "{{with .E}}{{.}}{{else}}{{.H}}{{end}}", - "<Hello>", - }, - { - "rangeBody", - "{{range .A}}{{.}}{{end}}", - "<a><b>", - }, - { - "rangeElse", - "{{range .E}}{{.}}{{else}}{{.H}}{{end}}", - "<Hello>", - }, - { - "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=<Hello>>", - }, - { - "urlStartRel", - `<a href='{{"/foo/bar?a=b&c=d"}}'>`, - `<a href='/foo/bar?a=b&c=d'>`, - }, - { - "urlStartAbsOk", - `<a href='{{"http://example.com/foo/bar?a=b&c=d"}}'>`, - `<a href='http://example.com/foo/bar?a=b&c=d'>`, - }, - { - "protocolRelativeURLStart", - `<a href='{{"//example.com:8000/foo/bar?a=b&c=d"}}'>`, - `<a href='//example.com:8000/foo/bar?a=b&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("\u003cHello\u003e")'>`, - }, - { - "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(["\u003ca\u003e","\u003cb\u003e"])'>`, - }, - { - "jsObjValueScript", - "<script>alert({{.A}})</script>", - `<script>alert(["\u003ca\u003e","\u003cb\u003e"])</script>`, - }, - { - "jsObjValueNotOverEscaped", - "<button onclick='alert({{.A | html}})'>", - `<button onclick='alert(["\u003ca\u003e","\u003cb\u003e"])'>`, - }, - { - "jsStr", - "<button onclick='alert("{{.H}}")'>", - `<button onclick='alert("\x3cHello\x3e")'>`, - }, - { - "badMarshaler", - `<button onclick='alert(1/{{.B}}in numbers)'>`, - `<button onclick='alert(1/ /* json: error calling MarshalJSON for type *template.badMarshaler: invalid character 'f' looking for beginning of object key string */null in numbers)'>`, - }, - { - "jsMarshaler", - `<button onclick='alert({{.M}})'>`, - `<button onclick='alert({"\u003cfoo\u003e":"O'Reilly"})'>`, - }, - { - "jsStrNotUnderEscaped", - "<button onclick='alert({{.C | urlquery}})'>", - // URL escaped, then quoted for JS. - `<button onclick='alert("%3CCincinatti%3E")'>`, - }, - { - "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&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: '{{"/**/'\";:// \\"}}', "{{"/**/'\";:// \\"}}"">`, - `<a style="font-family: '\2f**\2f\27\22\3b\3a\2f\2f \\', "\2f**\2f\27\22\3b\3a\2f\2f \\"">`, - }, - { - "styleURLSpecialsEncoded", - `<a style="border-image: url({{"/**/'\";:// \\"}}), url("{{"/**/'\";:// \\"}}"), url('{{"/**/'\";:// \\"}}'), 'http://www.example.com/?q={{"/**/'\";:// \\"}}''">`, - `<a style="border-image: url(/**/%27%22;://%20%5c), url("/**/%27%22;://%20%5c"), 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, <Cincinatti></b>", - }, - { - "HTML comment not first < in text node.", - "<<!-- -->!--", - "<!--", - }, - { - "HTML normalization 1", - "a < b", - "a < b", - }, - { - "HTML normalization 2", - "a << b", - "a << b", - }, - { - "HTML normalization 3", - "a<<!-- --><!-- -->b", - "a<b", - }, - { - "HTML doctype not normalized", - "<!DOCTYPE html>Hello, World!", - "<!DOCTYPE html>Hello, World!", - }, - { - "HTML doctype not case-insensitive", - "<!doCtYPE htMl>Hello, World!", - "<!doCtYPE htMl>Hello, World!", - }, - { - "No doctype injection", - `<!{{"DOCTYPE"}}`, - "<!DOCTYPE", - }, - { - "Split HTML comment", - "<b>Hello, <!-- name of {{if .T}}city -->{{.C}}{{else}}world -->{{.W}}{{end}}</b>", - "<b>Hello, <Cincinatti></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(""); /* alert({{.H}}) */">`, - // Attribute comment tests should pass if the comments - // are successfully elided. - `<a onclick="f(""); /* 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}}`, - `¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!`, - }, - { - "typed HTML in attribute", - `<div title="{{.W}}">`, - `<div title="¡Hello, O'World!">`, - }, - { - "typed HTML in script", - `<button onclick="alert({{.W}})">`, - `<button onclick="alert("\u0026iexcl;\u003cb class=\"foo\"\u003eHello\u003c/b\u003e, \u003ctextarea\u003eO'World\u003c/textarea\u003e!")">`, - }, - { - "typed HTML in RCDATA", - `<textarea>{{.W}}</textarea>`, - `<textarea>¡<b class="foo">Hello</b>, <textarea>O'World</textarea>!</textarea>`, - }, - { - "range in textarea", - "<textarea>{{range .A}}{{.}}{{end}}</textarea>", - "<textarea><a><b></textarea>", - }, - { - "No tag injection", - `{{"10$"}}<{{"script src,evil.org/pwnd.js"}}...`, - `10$<script src,evil.org/pwnd.js...`, - }, - { - "No comment injection", - `<{{"!--"}}`, - `<!--`, - }, - { - "No RCDATA end tag injection", - `<textarea><{{"/textarea "}}...</textarea>`, - `<textarea></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="<iconId>" src="?%3ciconPath%3e"title="<title>" alt="<alt>">`, - }, - { - "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("loaded")">`, - }, - { - "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"}}>`, - `<script>doEvil()</script>`, - }, - } - - for _, test := range tests { - tmpl := New(test.name) - tmpl = Must(tmpl.Parse(test.input)) - // Check for bug 6459: Tree field was not set in Parse. - if tmpl.Tree != tmpl.text.Tree { - t.Errorf("%s: tree not set properly", test.name) - continue - } - 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 - } - if tmpl.Tree != tmpl.text.Tree { - t.Errorf("%s: tree mismatch", test.name) - 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, <World>!`, - }, - // 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 = "\u003ca\u003e"<b;'>`, - }, - // A recursive template that ends in its start context. - { - map[string]string{ - "main": `{{range .Children}}{{template "main" .}}{{else}}{{.X}} {{end}}`, - }, - `foo <bar> 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><bar></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><bar><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 <100></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=''`, - context{state: stateURL, delim: delimSingleQuote, urlPart: urlPartPreQuery}, - }, - { - `<a href=""`, - context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery}, - }, - { - `<a href=""`, - context{state: stateURL, delim: delimDoubleQuote, urlPart: urlPartPreQuery}, - }, - { - `<a href="`, - 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=""`, - context{state: stateJSDqStr, delim: delimDoubleQuote}, - }, - { - `<a onclick='"foo"`, - context{state: stateJS, delim: delimSingleQuote, jsCtx: jsCtxDivOp}, - }, - { - `<a onclick='foo'`, - context{state: stateJS, delim: delimSpaceOrTagEnd, jsCtx: jsCtxDivOp}, - }, - { - `<a onclick='foo`, - context{state: stateJSSqStr, delim: delimSpaceOrTagEnd}, - }, - { - `<a onclick=""foo'`, - context{state: stateJSDqStr, delim: delimDoubleQuote}, - }, - { - `<a onclick="'foo"`, - 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: "`, - 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("/`, - 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"}, - }, - { - "{{($).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" + - "&%22\\", - CSS(`a[href =~ "//example.com"]#foo`), - HTML(`Hello, <b>World</b> &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()) - } -} - -// This is a test for issue 3272. -func TestEmptyTemplate(t *testing.T) { - page := Must(New("page").ParseFiles(os.DevNull)) - if err := page.ExecuteTemplate(os.Stdout, "page", "nothing"); err == nil { - t.Fatal("expected error") - } -} - -type Issue7379 int - -func (Issue7379) SomeMethod(x int) string { - return fmt.Sprintf("<%d>", x) -} - -// This is a test for issue 7379: type assertion error caused panic, and then -// the code to handle the panic breaks escaping. It's hard to see the second -// problem once the first is fixed, but its fix is trivial so we let that go. See -// the discussion for issue 7379. -func TestPipeToMethodIsEscaped(t *testing.T) { - tmpl := Must(New("x").Parse("<html>{{0 | .SomeMethod}}</html>\n")) - tryExec := func() string { - defer func() { - panicValue := recover() - if panicValue != nil { - t.Errorf("panicked: %v\n", panicValue) - } - }() - var b bytes.Buffer - tmpl.Execute(&b, Issue7379(0)) - return b.String() - } - for i := 0; i < 3; i++ { - str := tryExec() - const expect = "<html><0></html>\n" - if str != expect { - t.Errorf("expected %q got %q", expect, str) - } - } -} - -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() - } -} diff --git a/src/pkg/html/template/html.go b/src/pkg/html/template/html.go deleted file mode 100644 index 9c069efd1..000000000 --- a/src/pkg/html/template/html.go +++ /dev/null @@ -1,257 +0,0 @@ -// 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/syntax.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/syntax.html#before-attribute-value-state - 0: "\uFFFD", - '"': """, - '&': "&", - '\'': "'", - '+': "+", - '<': "<", - '>': ">", -} - -// htmlNormReplacementTable is like htmlReplacementTable but without '&' to -// avoid over-encoding existing entities. -var htmlNormReplacementTable = []string{ - 0: "\uFFFD", - '"': """, - '\'': "'", - '+': "+", - '<': "<", - '>': ">", -} - -// 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: "�", - '\t': "	", - '\n': " ", - '\v': "", - '\f': "", - '\r': " ", - ' ': " ", - '"': """, - '&': "&", - '\'': "'", - '+': "+", - '<': "<", - '=': "=", - '>': ">", - // A parse error in the attribute value (unquoted) and - // before attribute value states. - // Treated as a quoting character by IE. - '`': "`", -} - -// htmlNospaceNormReplacementTable is like htmlNospaceReplacementTable but -// without '&' to avoid over-encoding existing entities. -var htmlNospaceNormReplacementTable = []string{ - 0: "�", - '\t': "	", - '\n': " ", - '\v': "", - '\f': "", - '\r': " ", - ' ': " ", - '"': """, - '\'': "'", - '+': "+", - '<': "<", - '=': "=", - '>': ">", - // A parse error in the attribute value (unquoted) and - // before attribute value states. - // Treated as a quoting character by IE. - '`': "`", -} - -// htmlReplacer returns s with runes replaced according 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>¡Hi!</b> <script>...</script>` -> `¡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 "" -} diff --git a/src/pkg/html/template/html_test.go b/src/pkg/html/template/html_test.go deleted file mode 100644 index b9b970387..000000000 --- a/src/pkg/html/template/html_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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 := ("�\x01\x02\x03\x04\x05\x06\x07" + - "\x08	  \x0E\x0F" + - "\x10\x11\x12\x13\x14\x15\x16\x17" + - "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + - ` !"#$%&'()*+,-./` + - `0123456789:;<=>?` + - `@ABCDEFGHIJKLMNO` + - `PQRSTUVWXYZ[\]^_` + - ``abcdefghijklmno` + - `pqrstuvwxyz{|}~` + "\u007f" + - "\u00A0\u0100\u2028\u2029\ufeff\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&bar", "foo&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.") - } -} diff --git a/src/pkg/html/template/js.go b/src/pkg/html/template/js.go deleted file mode 100644 index 999a61ed0..000000000 --- a/src/pkg/html/template/js.go +++ /dev/null @@ -1,362 +0,0 @@ -// 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 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 -} - -// regexpPrecederKeywords 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 reports 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 -} diff --git a/src/pkg/html/template/js_test.go b/src/pkg/html/template/js_test.go deleted file mode 100644 index 311e1d2c4..000000000 --- a/src/pkg/html/template/js_test.go +++ /dev/null @@ -1,401 +0,0 @@ -// 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. - {`&`, `\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. - {`&`, `\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>") - } -} diff --git a/src/pkg/html/template/template.go b/src/pkg/html/template/template.go deleted file mode 100644 index d38965897..000000000 --- a/src/pkg/html/template/template.go +++ /dev/null @@ -1,381 +0,0 @@ -// 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 from "text/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 - // The underlying template's parse tree, updated to be HTML-safe. - Tree *parse.Tree - *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 -} - -// Templates returns a slice of the templates associated with t, including t -// itself. -func (t *Template) Templates() []*Template { - ns := t.nameSpace - ns.mu.Lock() - defer ns.mu.Unlock() - // Return a slice so we don't expose the map. - m := make([]*Template, 0, len(ns.set)) - for _, v := range ns.set { - m = append(m, v) - } - return m -} - -// escape escapes all associated templates. -func (t *Template) escape() error { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - if !t.escaped { - if err := escapeTemplates(t, t.Name()); err != nil { - return err - } - t.escaped = true - } - return nil -} - -// Execute applies a parsed template to the specified data object, -// writing the output to wr. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel. -func (t *Template) Execute(wr io.Writer, data interface{}) error { - if err := t.escape(); err != nil { - return err - } - 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. -// If an error occurs executing the template or writing its output, -// execution stops, but partial results may already have been written to -// the output writer. -// A template may be executed safely in parallel. -func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error { - tmpl, err := t.lookupAndEscapeTemplate(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(name string) (tmpl *Template, err error) { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - tmpl = t.set[name] - if tmpl == nil { - return nil, fmt.Errorf("html/template: %q is undefined", name) - } - if tmpl.text.Tree == nil || tmpl.text.Root == nil { - return nil, fmt.Errorf("html/template: %q is an incomplete template", name) - } - if 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) - } - // Restore our record of this text/template to its unescaped original state. - tmpl.escaped = false - tmpl.text = v - tmpl.Tree = v.Tree - } - return t, nil -} - -// AddParseTree creates a new template with the name and parse tree -// and associates it with t. -// -// It returns an error if t has already been executed. -func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - if t.escaped { - return nil, fmt.Errorf("html/template: cannot AddParseTree to %q after it has executed", t.Name()) - } - text, err := t.text.AddParseTree(name, tree) - if err != nil { - return nil, err - } - ret := &Template{ - false, - text, - text.Tree, - t.nameSpace, - } - t.set[name] = ret - return ret, nil -} - -// Clone returns a duplicate of the template, including all associated -// templates. The actual representation is not copied, but the name space of -// associated templates is, so further calls to Parse in the copy will add -// templates to the copy but not to the original. Clone can be used to prepare -// common templates and use them with variant definitions for other templates -// by adding the variants after the clone is made. -// -// It returns an error if t has already been executed. -func (t *Template) Clone() (*Template, error) { - t.nameSpace.mu.Lock() - defer t.nameSpace.mu.Unlock() - if t.escaped { - return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name()) - } - textClone, err := t.text.Clone() - if err != nil { - return nil, err - } - ret := &Template{ - false, - textClone, - textClone.Tree, - &nameSpace{ - set: make(map[string]*Template), - }, - } - for _, x := range textClone.Templates() { - name := x.Name() - src := t.set[name] - if src == nil || src.escaped { - return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name()) - } - x.Tree = x.Tree.Copy() - ret.set[name] = &Template{ - false, - x, - x.Tree, - ret.nameSpace, - } - } - return ret, nil -} - -// New allocates a new HTML template with the given name. -func New(name string) *Template { - tmpl := &Template{ - false, - template.New(name), - nil, - &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), - nil, - 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 FuncMap in "text/template", 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 is a helper that wraps a call to a function returning (*Template, error) -// and panics if the error is non-nil. It is intended for use in variable initializations -// such as -// var t = template.Must(template.New("name").Parse("html")) -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("html/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("html/template: pattern matches no files: %#q", pattern) - } - return parseFiles(t, filenames...) -} diff --git a/src/pkg/html/template/transition.go b/src/pkg/html/template/transition.go deleted file mode 100644 index 7f30a7ab8..000000000 --- a/src/pkg/html/template/transition.go +++ /dev/null @@ -1,550 +0,0 @@ -// 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 - } -} - -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 - } -} - -// 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 - } -} - -// 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 reports whether c is an ASCII letter. -func asciiAlpha(c byte) bool { - return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' -} - -// asciiAlphaNum reports 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) -} diff --git a/src/pkg/html/template/url.go b/src/pkg/html/template/url.go deleted file mode 100644 index 2ca76bf38..000000000 --- a/src/pkg/html/template/url.go +++ /dev/null @@ -1,105 +0,0 @@ -// 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 '&'. -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 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() -} diff --git a/src/pkg/html/template/url_test.go b/src/pkg/html/template/url_test.go deleted file mode 100644 index 5182e9d79..000000000 --- a/src/pkg/html/template/url_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// 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") - } -} |