fix: bind file tree names without compiling repository content

This commit is contained in:
tdurieux committed 2026-09-06 10:46:32 +02:00
1 parent b070d7e281
commit d793d2c7c6
4 files changed
+73 -27

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
{
"core.min.js": "core.6332b3c288.min.js",
"vendor.min.js": "vendor.c40cb91530.min.js",
"vendor.min.js": "vendor.3340ba0f11.min.js",
"mermaid.min.js": "mermaid.f848a72d16.min.js",
"all.min.css": "all.99ce8f3e14.min.css"
}
}
+11 -24
View File
@@ -622,23 +622,6 @@ angular
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
// Escape a value for safe interpolation into a single-quoted
// AngularJS expression string (e.g. ng-click="openFolder('...')")
// that itself sits inside a double-quoted HTML attribute which is
// later $compile()d. Backslash/quote are escaped at the Angular
// string level; &<>" are HTML-encoded for the attribute. Without
// this a file name like `');$emit(...)//` would break out of the
// expression string and execute (DOM XSS, CWE-79).
function escapeNgString(str) {
return String(str)
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function buildSearchFilter() {
const results = $scope.searchResults;
if (!results || !results.length) return null;
@@ -721,12 +704,14 @@ angular
cssClasses.push("truncated");
}
const ngPath = escapeNgString(path);
const nodeIndex = $scope.treeNodes.length;
$scope.treeNodes.push({ name, path, href: `/r/${encodeURIComponent($scope.repoId)}${encodePathForUrl(path)}` });
const node = `treeNodes[${nodeIndex}]`;
output += `<li class="${cssClasses.join(
" "
)}" ng-class="{active: isActive('${ngPath}'), open: ${filterSet ? "opens['" + ngPath + "'] !== false" : "opens['" + ngPath + "']"}}" title="${escapeHtml(sizeTitle)}">`;
)}" ng-class="{active: isActive(${node}.path), open: opens[${node}.path]${filterSet ? ' !== false' : ''}}" title="${escapeHtml(sizeTitle)}">`;
if (dir) {
output += `<a ng-click="openFolder('${ngPath}', $event)"><span class="tree-toggle"></span><span class="tree-icon-folder"></span><span class="tree-name">${escapeHtml(name)}</span>`;
output += `<a ng-click="openFolder(${node}.path, $event)"><span class="tree-toggle"></span><span class="tree-icon-folder"></span><span class="tree-name" ng-bind="${node}.name"></span>`;
if (truncated) {
output += `<span class="truncated-warning" title="{{ 'WARNINGS.folder_truncated' | translate }}"><i class="fas fa-exclamation-triangle"></i></span>`;
}
@@ -736,9 +721,7 @@ angular
output += `</a>`;
} else {
const needsSpacer = parentPath !== "";
output += `<a href='/r/${$scope.repoId}${encodePathForUrl(
path
)}'>${needsSpacer ? '<span class="tree-spacer"></span>' : ''}<span class="tree-icon-file"></span><span class="tree-name">${escapeHtml(name)}</span></a>`;
output += `<a ng-href='{{${node}.href}}'>${needsSpacer ? '<span class="tree-spacer"></span>' : ''}<span class="tree-icon-file"></span><span class="tree-name" ng-bind="${node}.name"></span></a>`;
}
if (isOpen && collapsed.child) {
const children = collapsed.child;
@@ -757,7 +740,11 @@ angular
return output + "</ul>";
}
let renderScope = null;
function display() {
if (renderScope) renderScope.$destroy();
renderScope = $scope.$new();
$scope.treeNodes = [];
$element.html("");
const filterSet = $scope.searchQuery ? buildSearchFilter() : null;
let output;
@@ -766,7 +753,7 @@ angular
} else {
output = generate(toArray($scope.file).sort(sortFiles), "", filterSet);
}
$compile(output)($scope, (clone) => {
$compile(output)(renderScope, (clone) => {
$element.append(clone);
restoreFocus();
});
+1 -1
View File
@@ -1,2 +1,2 @@
function markedMermaid(e){return{extensions:[{name:"mermaid",level:"block",start(e){return e.match(/^```mermaid/m)?.index},tokenizer(e,t){e=/^```mermaid\n([\s\S]*?)\n```/.exec(e);if(e)return{type:"mermaid",raw:e[0],text:e[1].trim()}},renderer(e){const t="mermaid-"+Math.random().toString(36).substr(2,9);e=`<div class="mermaid" id="${t}">${e.text}</div>`;return"undefined"==typeof mermaid&&"function"==typeof window.loadMermaid&&window.loadMermaid(),setTimeout(()=>{if("undefined"!=typeof mermaid){window.mermaidInitialized||(mermaid.initialize({startOnLoad:!1,theme:"default",securityLevel:"strict"}),window.mermaidInitialized=!0);try{var e=document.getElementById(t);e&&!e.getAttribute("data-processed")&&(mermaid.init(void 0,e),e.setAttribute("data-processed","true"))}catch(e){console.error("Mermaid rendering error:",e)}}},100),e}}]}}!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=e.pdfjsLib=t():"function"==typeof define&&define.amd?define("pdfjs-dist/build/pdf",[],()=>e.pdfjsLib=t()):"object"==typeof exports?exports["pdfjs-dist/build/pdf"]=e.pdfjsLib=t():e["pdfjs-dist/build/pdf"]=e.pdfjsLib=t()}(globalThis,()=>(()=>{"use strict";var __webpack_modules__=[,(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.VerbosityLevel=t.Util=t.UnknownErrorException=t.UnexpectedResponseException=t.TextRenderingMode=t.RenderingIntentFlag=t.PromiseCapability=t.PermissionFlag=t.PasswordResponses=t.PasswordException=t.PageActionEventType=t.OPS=t.MissingPDFException=t.MAX_IMAGE_SIZE_TO_CACHE=t.LINE_FACTOR=t.LINE_DESCENT_FACTOR=t.InvalidPDFException=t.ImageKind=t.IDENTITY_MATRIX=t.FormatError=t.FeatureTest=t.FONT_IDENTITY_MATRIX=t.DocumentActionEventType=t.CMapCompressionType=t.BaseException=t.BASELINE_FACTOR=t.AnnotationType=t.AnnotationReplyType=t.AnnotationPrefix=t.AnnotationMode=t.AnnotationFlag=t.AnnotationFieldFlag=t.AnnotationEditorType=t.AnnotationEditorPrefix=t.AnnotationEditorParamsType=t.AnnotationBorderStyleType=t.AnnotationActionEventType=t.AbortException=void 0,t.assert=function(e,t){e||s(t)},t.bytesToString=h,t.createValidAbsoluteUrl=function(e){let t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(e)try{if(i&&"string"==typeof e){if(i.addDefaultProtocol&&e.startsWith("www.")){const t=e.match(/\./g);2<=t?.length&&(e="http://"+e)}if(i.tryConvertEncoding)try{e=p(e)}catch{}}var n=t?new URL(e,t):new URL(e);if(function(e){switch(e?.protocol){case"http:":case"https:":case"ftp:":case"mailto:":case"tel:":return 1;default:return}}(n))return n}catch{}return null},t.getModificationDate=function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:new Date;return[e.getUTCFullYear().toString(),(e.getUTCMonth()+1).toString().padStart(2,"0"),e.getUTCDate().toString().padStart(2,"0"),e.getUTCHours().toString().padStart(2,"0"),e.getUTCMinutes().toString().padStart(2,"0"),e.getUTCSeconds().toString().padStart(2,"0")].join("")},t.getUuid=function(){if("undefined"!=typeof crypto&&"function"==typeof crypto?.randomUUID)return crypto.randomUUID();var t=new Uint8Array(32);if("undefined"!=typeof crypto&&"function"==typeof crypto?.getRandomValues)crypto.getRandomValues(t);else for(let e=0;e<32;e++)t[e]=Math.floor(255*Math.random());return h(t)},t.getVerbosityLevel=function(){return r},t.info=function(e){r>=n.INFOS&&console.log("Info: "+e)},t.isArrayBuffer=function(e){return"object"==typeof e&&void 0!==e?.byteLength},t.isArrayEqual=function(i,n){if(i.length!==n.length)return!1;for(let e=0,t=i.length;e<t;e++)if(i[e]!==n[e])return!1;return!0},t.isNodeJS=void 0,t.normalizeUnicode=function(e){return m||(m=/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu,g=new Map([["ſt","ſt"]])),e.replaceAll(m,(e,t,i)=>t?t.normalize("NFKC"):g.get(i))},t.objectFromMap=function(e){var t,i,n=Object.create(null);for([t,i]of e)n[t]=i;return n},t.objectSize=function(e){return Object.keys(e).length},t.setVerbosityLevel=function(e){Number.isInteger(e)&&(r=e)},t.shadow=a,t.string32=function(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e)},t.stringToBytes=c,t.stringToPDFString=function(i){if("ï"<=i[0]){let e;if("þ"===i[0]&&"ÿ"===i[1]?e="utf-16be":"ÿ"===i[0]&&"þ"===i[1]?e="utf-16le":"ï"===i[0]&&"»"===i[1]&&"¿"===i[2]&&(e="utf-8"),e)try{var t=new TextDecoder(e,{fatal:!0}),n=c(i);return t.decode(n)}catch(i){o(`stringToPDFString: "${i}".`)}}var r=[];for(let e=0,t=i.length;e<t;e++){var s=u[i.charCodeAt(e)];r.push(s?String.fromCharCode(s):i.charAt(e))}return r.join("")},t.stringToUTF8String=p,t.unreachable=s,t.utf8StringToString=function(e){return unescape(encodeURIComponent(e))},t.warn=o,i(2),i(84),i(86),i(87),i(89),i(93),i(101),i(102),i(105),i(107),i(Line truncated
`}setFont(e){var t=this.current,i=this.commonObjs.get(e[0]);let n=e[1],r=(t.font=i,!this.embedFonts||i.missingFile||this.embeddedFonts[i.loadedName]||(this.addFontStyle(i),this.embeddedFonts[i.loadedName]=i),t.fontMatrix=i.fontMatrix||v.FONT_IDENTITY_MATRIX,"normal");i.black?r="900":i.bold&&(r="bold");e=i.italic?"italic":"normal";n<0?(n=-n,t.fontDirection=-1):t.fontDirection=1,t.fontSize=n,t.fontFamily=i.loadedName,t.fontWeight=r,t.fontStyle=e,t.tspan=this.svgFactory.createElement("svg:tspan"),t.tspan.setAttributeNS(null,"y",b(-t.y)),t.xcoords=[],t.ycoords=[]}endText(){var e=this.current;e.textRenderingMode&v.TextRenderingMode.ADD_TO_PATH_FLAG&&e.txtElement?.hasChildNodes()&&(e.element=e.txtElement,this.clip("nonzero"),this.endPath())}setLineWidth(e){0<e&&(this.current.lineWidth=e)}setLineCap(e){this.current.lineCap=r[e]}setLineJoin(e){this.current.lineJoin=s[e]}setMiterLimit(e){this.current.miterLimit=e}setStrokeAlpha(e){this.current.strokeAlpha=e}setStrokeRGBColor(e,t,i){this.current.strokeColor=v.Util.makeHexColor(e,t,i)}setFillAlpha(e){this.current.fillAlpha=e}setFillRGBColor(e,t,i){this.current.fillColor=v.Util.makeHexColor(e,t,i),this.current.tspan=this.svgFactory.createElement("svg:tspan"),this.current.xcoords=[],this.current.ycoords=[]}setStrokeColorN(e){this.current.strokeColor=this._makeColorN_Pattern(e)}setFillColorN(e){this.current.fillColor=this._makeColorN_Pattern(e)}shadingFill(e){var{width:t,height:i}=this.viewport,n=v.Util.inverseTransform(this.transformMatrix),[t,i,n,r]=v.Util.getAxialAlignedBoundingBox([0,0,t,i],n),s=this.svgFactory.createElement("svg:rect");s.setAttributeNS(null,"x",t),s.setAttributeNS(null,"y",i),s.setAttributeNS(null,"width",n-t),s.setAttributeNS(null,"height",r-i),s.setAttributeNS(null,"fill",this._makeShadingPattern(e)),this.current.fillAlpha<1&&s.setAttributeNS(null,"fill-opacity",this.current.fillAlpha),this._ensureTransformGroup().append(s)}_makeColorN_Pattern(e){return"TilingPattern"===e[0]?this._makeTilingPattern(e):this._makeShadingPattern(e)}_makeTilingPattern(e){var t=e[1],i=e[2],n=e[3]||v.IDENTITY_MATRIX,[r,s,o,a]=e[4],l=e[5],h=e[6],e=e[7],c="shading"+m++,[r,s,o,a]=v.Util.normalizeRect([...v.Util.applyTransform([r,s],n),...v.Util.applyTransform([o,a],n)]),[d,u]=v.Util.singularValueDecompose2dScale(n),l=l*d,d=h*u,h=this.svgFactory.createElement("svg:pattern"),u=(h.setAttributeNS(null,"id",c),h.setAttributeNS(null,"patternUnits","userSpaceOnUse"),h.setAttributeNS(null,"width",l),h.setAttributeNS(null,"height",d),h.setAttributeNS(null,"x",""+r),h.setAttributeNS(null,"y",""+s),this.svg),l=this.transformMatrix,d=this.current.fillColor,p=this.current.strokeColor,o=this.svgFactory.create(o-r,a-s);if(this.svg=o,this.transformMatrix=n,2===e){const e=v.Util.makeHexColor(...t);this.current.fillColor=e,this.current.strokeColor=e}return this.executeOpTree(this.convertOpList(i)),this.svg=u,this.transformMatrix=l,this.current.fillColor=d,this.current.strokeColor=p,h.append(o.childNodes[0]),this.defs.append(h),`url(#${c})`}_makeShadingPattern(t){switch((t="string"==typeof t?this.objs.get(t):t)[0]){case"RadialAxial":var i="shading"+m++,n=t[3];let e;switch(t[1]){case"axial":const h=t[4],v=t[5];(e=this.svgFactory.createElement("svg:linearGradient")).setAttributeNS(null,"id",i),e.setAttributeNS(null,"gradientUnits","userSpaceOnUse"),e.setAttributeNS(null,"x1",h[0]),e.setAttributeNS(null,"y1",h[1]),e.setAttributeNS(null,"x2",v[0]),e.setAttributeNS(null,"y2",v[1]);break;case"radial":var r=t[4],s=t[5],o=t[6],a=t[7];(e=this.svgFactory.createElement("svg:radialGradient")).setAttributeNS(null,"id",i),e.setAttributeNS(null,"gradientUnits","userSpaceOnUse"),e.setAttributeNS(null,"cx",s[0]),e.setAttributeNS(null,"cy",s[1]),e.setAttributeNS(null,"r",a),e.setAttributeNS(null,"fx",r[0]),e.setAttributeNS(null,"fy",r[1]),e.setAttributeNS(null,"fr",o);break;default:throw new Error("Unknown RadialAxial type: "+t[1])}for(const t of n){var l=this.svgFactory.createElement("svg:stop");l.setAttributeNS(null,"offset",t[0]),l.setAttributeNS(null,"stop-color",t[1]),e.append(l)}return this.defs.append(e),`url(#${i})`;case"Mesh":return(0,v.warn)("Unimplemented pattern Mesh"),null;case"Dummy":return"hotpink";default:throw new Error("Unknown IR type: "+t[0])}}setDash(e,t){this.current.dashArray=e,this.current.dashPhase=t}constructPath(e,t){const i=this.current;let n=i.x,r=i.y,s=[],o=0;for(const i of e)switch(0|i){case v.OPS.rectangle:n=t[o++],r=t[o++];const e=n+t[o++],i=r+t[o++];s.push("M",b(n),b(r),"L",b(e),b(r),"L",b(e),b(i),"L",b(n),b(i),"Z");break;case v.OPS.moveTo:n=t[o++],r=t[o++],s.push("M",b(n),b(r));break;case v.OPS.lineTo:n=t[o++],r=t[o++],s.push("L",b(n),b(r));break;case v.OPS.curveTo:n=t[o+4],r=t[o+5],s.push("C",b(t[o]),b(t[o+1]),b(t[o+2]),b(t[o+3]),b(n),b(r)),o+=6;break;case v.OPS.curveTo2:s.push("C",b(n),b(r),b(t[o]),b(t[o+1]),b(t[o+2]),b(t[o+3])),n=t[o+2],r=t[o+3],o+=4;break;case v.OPS.curveTo3:n=t[o+2],r=t[o+3],s.push("C",b(t[o]),b(t[o+1]),b(n),b(r),b(n),b(r)),o+=4;break;case v.OPS.closeP Line truncated
`}setFont(e){var t=this.current,i=this.commonObjs.get(e[0]);let n=e[1],r=(t.font=i,!this.embedFonts||i.missingFile||this.embeddedFonts[i.loadedName]||(this.addFontStyle(i),this.embeddedFonts[i.loadedName]=i),t.fontMatrix=i.fontMatrix||v.FONT_IDENTITY_MATRIX,"normal");i.black?r="900":i.bold&&(r="bold");e=i.italic?"italic":"normal";n<0?(n=-n,t.fontDirection=-1):t.fontDirection=1,t.fontSize=n,t.fontFamily=i.loadedName,t.fontWeight=r,t.fontStyle=e,t.tspan=this.svgFactory.createElement("svg:tspan"),t.tspan.setAttributeNS(null,"y",b(-t.y)),t.xcoords=[],t.ycoords=[]}endText(){var e=this.current;e.textRenderingMode&v.TextRenderingMode.ADD_TO_PATH_FLAG&&e.txtElement?.hasChildNodes()&&(e.element=e.txtElement,this.clip("nonzero"),this.endPath())}setLineWidth(e){0<e&&(this.current.lineWidth=e)}setLineCap(e){this.current.lineCap=r[e]}setLineJoin(e){this.current.lineJoin=s[e]}setMiterLimit(e){this.current.miterLimit=e}setStrokeAlpha(e){this.current.strokeAlpha=e}setStrokeRGBColor(e,t,i){this.current.strokeColor=v.Util.makeHexColor(e,t,i)}setFillAlpha(e){this.current.fillAlpha=e}setFillRGBColor(e,t,i){this.current.fillColor=v.Util.makeHexColor(e,t,i),this.current.tspan=this.svgFactory.createElement("svg:tspan"),this.current.xcoords=[],this.current.ycoords=[]}setStrokeColorN(e){this.current.strokeColor=this._makeColorN_Pattern(e)}setFillColorN(e){this.current.fillColor=this._makeColorN_Pattern(e)}shadingFill(e){var{width:t,height:i}=this.viewport,n=v.Util.inverseTransform(this.transformMatrix),[t,i,n,r]=v.Util.getAxialAlignedBoundingBox([0,0,t,i],n),s=this.svgFactory.createElement("svg:rect");s.setAttributeNS(null,"x",t),s.setAttributeNS(null,"y",i),s.setAttributeNS(null,"width",n-t),s.setAttributeNS(null,"height",r-i),s.setAttributeNS(null,"fill",this._makeShadingPattern(e)),this.current.fillAlpha<1&&s.setAttributeNS(null,"fill-opacity",this.current.fillAlpha),this._ensureTransformGroup().append(s)}_makeColorN_Pattern(e){return"TilingPattern"===e[0]?this._makeTilingPattern(e):this._makeShadingPattern(e)}_makeTilingPattern(e){var t=e[1],i=e[2],n=e[3]||v.IDENTITY_MATRIX,[r,s,o,a]=e[4],l=e[5],h=e[6],e=e[7],c="shading"+m++,[r,s,o,a]=v.Util.normalizeRect([...v.Util.applyTransform([r,s],n),...v.Util.applyTransform([o,a],n)]),[d,u]=v.Util.singularValueDecompose2dScale(n),l=l*d,d=h*u,h=this.svgFactory.createElement("svg:pattern"),u=(h.setAttributeNS(null,"id",c),h.setAttributeNS(null,"patternUnits","userSpaceOnUse"),h.setAttributeNS(null,"width",l),h.setAttributeNS(null,"height",d),h.setAttributeNS(null,"x",""+r),h.setAttributeNS(null,"y",""+s),this.svg),l=this.transformMatrix,d=this.current.fillColor,p=this.current.strokeColor,o=this.svgFactory.create(o-r,a-s);if(this.svg=o,this.transformMatrix=n,2===e){const e=v.Util.makeHexColor(...t);this.current.fillColor=e,this.current.strokeColor=e}return this.executeOpTree(this.convertOpList(i)),this.svg=u,this.transformMatrix=l,this.current.fillColor=d,this.current.strokeColor=p,h.append(o.childNodes[0]),this.defs.append(h),`url(#${c})`}_makeShadingPattern(t){switch((t="string"==typeof t?this.objs.get(t):t)[0]){case"RadialAxial":var i="shading"+m++,n=t[3];let e;switch(t[1]){case"axial":const h=t[4],v=t[5];(e=this.svgFactory.createElement("svg:linearGradient")).setAttributeNS(null,"id",i),e.setAttributeNS(null,"gradientUnits","userSpaceOnUse"),e.setAttributeNS(null,"x1",h[0]),e.setAttributeNS(null,"y1",h[1]),e.setAttributeNS(null,"x2",v[0]),e.setAttributeNS(null,"y2",v[1]);break;case"radial":var r=t[4],s=t[5],o=t[6],a=t[7];(e=this.svgFactory.createElement("svg:radialGradient")).setAttributeNS(null,"id",i),e.setAttributeNS(null,"gradientUnits","userSpaceOnUse"),e.setAttributeNS(null,"cx",s[0]),e.setAttributeNS(null,"cy",s[1]),e.setAttributeNS(null,"r",a),e.setAttributeNS(null,"fx",r[0]),e.setAttributeNS(null,"fy",r[1]),e.setAttributeNS(null,"fr",o);break;default:throw new Error("Unknown RadialAxial type: "+t[1])}for(const t of n){var l=this.svgFactory.createElement("svg:stop");l.setAttributeNS(null,"offset",t[0]),l.setAttributeNS(null,"stop-color",t[1]),e.append(l)}return this.defs.append(e),`url(#${i})`;case"Mesh":return(0,v.warn)("Unimplemented pattern Mesh"),null;case"Dummy":return"hotpink";default:throw new Error("Unknown IR type: "+t[0])}}setDash(e,t){this.current.dashArray=e,this.current.dashPhase=t}constructPath(e,t){const i=this.current;let n=i.x,r=i.y,s=[],o=0;for(const i of e)switch(0|i){case v.OPS.rectangle:n=t[o++],r=t[o++];const e=n+t[o++],i=r+t[o++];s.push("M",b(n),b(r),"L",b(e),b(r),"L",b(e),b(i),"L",b(n),b(i),"Z");break;case v.OPS.moveTo:n=t[o++],r=t[o++],s.push("M",b(n),b(r));break;case v.OPS.lineTo:n=t[o++],r=t[o++],s.push("L",b(n),b(r));break;case v.OPS.curveTo:n=t[o+4],r=t[o+5],s.push("C",b(t[o]),b(t[o+1]),b(t[o+2]),b(t[o+3]),b(n),b(r)),o+=6;break;case v.OPS.curveTo2:s.push("C",b(n),b(r),b(t[o]),b(t[o+1]),b(t[o+2]),b(t[o+3])),n=t[o+2],r=t[o+3],o+=4;break;case v.OPS.curveTo3:n=t[o+2],r=t[o+3],s.push("C",b(t[o]),b(t[o+1]),b(n),b(r),b(n),b(r)),o+=4;break;case v.OPS.closeP Line truncated
+59
View File
@@ -0,0 +1,59 @@
const { expect } = require("chai");
const fs = require("fs");
const vm = require("vm");
const path = require("path");
const { setImmediate } = require("timers");
const source = fs.readFileSync(path.join(__dirname, "../public/script/app.js"), "utf8");
function harness(date) {
const defs = {}, routes = {}, timers = new Map();
let timerId = 0;
const chain = new Proxy({}, { get: (_, method) => (...args) => {
if (["controller", "directive"].includes(method)) defs[args[0]] = args[1];
if (method === "config") {
const route = { when(p, opt) { routes[p] = opt; return route; }, otherwise() {} };
args[0].at(-1)(route, { html5Mode() {} }, { useStaticFilesLoader() {}, preferredLanguage() {} });
}
return chain;
} });
const context = { angular: { module: () => chain }, console, Map, Set, Date: date || Date,
navigator: { platform: "Linux" }, document: { location: { pathname: "/r/repo" }, addEventListener() {}, querySelector() {} },
window: {}, Prism: { highlightAll() {} }, encodeURIComponent,
encodePathForUrl: p => p.split("/").map(encodeURIComponent).join("/"),
humanFileSize: x => String(x), parseGithubUrl: () => ({ owner: "owner", repo: "repo" }),
$: () => ({ on() {}, tooltip() {} }),
setTimeout: fn => { timers.set(++timerId, fn); return timerId; },
clearTimeout: id => timers.delete(id), setInterval: () => 0, clearInterval() {},
};
vm.runInNewContext(source, context);
const events = {}, watches = {};
const scope = { $new() { return { $destroy() {} }; }, $on: (key, fn) => { (events[key] ||= []).push(fn); }, $watch: (key, fn) => { watches[key] = fn; }, $apply() {}, $applyAsync() {} };
const requests = [];
const http = {};
for (const method of ["get", "post"]) http[method] = (url, body) => new Promise((resolve, reject) => requests.push({ method, url, body, resolve, reject }));
const q = { resolve: () => Promise.resolve(), reject: e => Promise.reject(e), defer: () => { let resolve; const promise = new Promise(r => { resolve = r; }); return { promise, resolve }; } };
return { defs, routes, scope, http, requests, q, events, watches, timers, context,
emit: key => (events[key] || []).forEach(fn => fn()),
flush: () => new Promise(resolve => setImmediate(resolve)),
};
}
function explorer() {
const h = harness(); h.params = { repoId: "repo", path: "README.md" };
h.defs.exploreController.at(-1)(h.scope, h.http, { url: () => "/r/repo/README.md" }, h.params, { trustAsHtml: x => x }, h.q);
h.navigate = path => { h.params.path = path; h.emit("$routeUpdate"); };
return h;
}
describe("frontend production regressions", function () {
it("keeps filenames and folder paths out of compiled Angular templates", function () {
const h = harness(); let template;
const element = { html() {}, append() {}, 0: { addEventListener() {}, setAttribute() {} } };
h.scope.file = [{ name: '{{constructor.constructor("window.probe=1")()}}.txt', path: "", size: 1 }];
h.scope.$parent = {};
h.defs.tree[0]().controller.at(-1)(element, h.scope, {}, html => { template = html; return () => {}; });
h.watches.file(h.scope.file);
expect(template).not.to.include("constructor.constructor");
expect(template).to.include('ng-bind="treeNodes[0].name"');
expect(h.scope.treeNodes[0].name).to.equal(h.scope.file[0].name);
});
});