/** * @license AngularJS v1.8.2 * (c) 2010-2020 Google LLC. http://angularjs.org * License: MIT */ (function(window, angular) {'use strict'; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Any commits to this file should be reviewed with security in mind. * * Changes to this file can potentially create security vulnerabilities. * * An approval from 2 Core members with history of modifying * * this file is required. * * * * Does the change somehow allow for arbitrary javascript to be executed? * * Or allows for someone to change the prototype of built-in objects? * * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ var $sanitizeMinErr = angular.$$minErr('$sanitize'); var bind; var extend; var forEach; var isArray; var isDefined; var lowercase; var noop; var nodeContains; var htmlParser; var htmlSanitizeWriter; /** * @ngdoc module * @name ngSanitize * @description * * The `ngSanitize` module provides functionality to sanitize HTML. * * See {@link ngSanitize.$sanitize `$sanitize`} for usage. */ /** * @ngdoc service * @name $sanitize * @kind function * * @description * Sanitizes an html string by stripping all potentially dangerous tokens. * * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a trusted URI list) are * then serialized back to a properly escaped HTML string. This means that no unsafe input can make * it into the returned string. * * The trusted URIs for URL sanitization of attribute values is configured using the functions * `aHrefSanitizationTrustedUrlList` and `imgSrcSanitizationTrustedUrlList` of {@link $compileProvider}. * * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}. * * @param {string} html HTML input. * @returns {string} Sanitized HTML. * * @example
Snippet:
Directive How Source Rendered
ng-bind-html Automatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-html Bypass $sanitize by explicitly trusting the dangerous value
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
</div>
ng-bind Automatically escapes
<div ng-bind="snippet">
</div>
it('should sanitize the html snippet by default', function() { expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). toBe('

an html\nclick here\nsnippet

'); }); it('should inline raw snippet if bound to a trusted value', function() { expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')). toBe("

an html\n" + "click here\n" + "snippet

"); }); it('should escape snippet without any filter', function() { expect(element(by.css('#bind-default div')).getAttribute('innerHTML')). toBe("<p style=\"color:blue\">an html\n" + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + "snippet</p>"); }); it('should update', function() { element(by.model('snippet')).clear(); element(by.model('snippet')).sendKeys('new text'); expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')). toBe('new text'); expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe( 'new text'); expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe( "new <b onclick=\"alert(1)\">text</b>"); });
*/ /** * @ngdoc provider * @name $sanitizeProvider * @this * * @description * Creates and configures {@link $sanitize} instance. */ function $SanitizeProvider() { var hasBeenInstantiated = false; var svgEnabled = false; this.$get = ['$$sanitizeUri', function($$sanitizeUri) { hasBeenInstantiated = true; if (svgEnabled) { extend(validElements, svgElements); } return function(html) { var buf = []; htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { return !/^unsafe:/.test($$sanitizeUri(uri, isImage)); })); return buf.join(''); }; }]; /** * @ngdoc method * @name $sanitizeProvider#enableSvg * @kind function * * @description * Enables a subset of svg to be supported by the sanitizer. * *
*

By enabling this setting without taking other precautions, you might expose your * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned * outside of the containing element and be rendered over other elements on the page (e.g. a login * link). Such behavior can then result in phishing incidents.

* *

To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg * tags within the sanitized content:

* *
* *

   *   .rootOfTheIncludedContent svg {
   *     overflow: hidden !important;
   *   }
   *   
*
* * @param {boolean=} flag Enable or disable SVG support in the sanitizer. * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called * without an argument or self for chaining otherwise. */ this.enableSvg = function(enableSvg) { if (isDefined(enableSvg)) { svgEnabled = enableSvg; return this; } else { return svgEnabled; } }; /** * @ngdoc method * @name $sanitizeProvider#addValidElements * @kind function * * @description * Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe * and are not stripped off during sanitization. You can extend the following lists of elements: * * - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML * elements. HTML elements considered safe will not be removed during sanitization. All other * elements will be stripped off. * * - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as * "void elements" (similar to HTML * [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These * elements have no end tag and cannot have content. * * - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only * taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for * `$sanitize`. * *
* This method must be called during the {@link angular.Module#config config} phase. Once the * `$sanitize` service has been instantiated, this method has no effect. *
* *
* Keep in mind that extending the built-in lists of elements may expose your app to XSS or * other vulnerabilities. Be very mindful of the elements you add. *
* * @param {Array|Object} elements - A list of valid HTML elements or an object with one or * more of the following properties: * - **htmlElements** - `{Array}` - A list of elements to extend the current list of * HTML elements. * - **htmlVoidElements** - `{Array}` - A list of elements to extend the current list of * void HTML elements; i.e. elements that do not have an end tag. * - **svgElements** - `{Array}` - A list of elements to extend the current list of SVG * elements. The list of SVG elements is only taken into account if SVG is * {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`. * * Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`. * * @return {$sanitizeProvider} Returns self for chaining. */ this.addValidElements = function(elements) { if (!hasBeenInstantiated) { if (isArray(elements)) { elements = {htmlElements: elements}; } addElementsTo(svgElements, elements.svgElements); addElementsTo(voidElements, elements.htmlVoidElements); addElementsTo(validElements, elements.htmlVoidElements); addElementsTo(validElements, elements.htmlElements); } return this; }; /** * @ngdoc method * @name $sanitizeProvider#addValidAttrs * @kind function * * @description * Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are * not stripped off during sanitization. * * **Note**: * The new attributes will not be treated as URI attributes, which means their values will not be * sanitized as URIs using `$compileProvider`'s * {@link ng.$compileProvider#aHrefSanitizationTrustedUrlList aHrefSanitizationTrustedUrlList} and * {@link ng.$compileProvider#imgSrcSanitizationTrustedUrlList imgSrcSanitizationTrustedUrlList}. * *
* This method must be called during the {@link angular.Module#config config} phase. Once the * `$sanitize` service has been instantiated, this method has no effect. *
* *
* Keep in mind that extending the built-in list of attributes may expose your app to XSS or * other vulnerabilities. Be very mindful of the attributes you add. *
* * @param {Array} attrs - A list of valid attributes. * * @returns {$sanitizeProvider} Returns self for chaining. */ this.addValidAttrs = function(attrs) { if (!hasBeenInstantiated) { extend(validAttrs, arrayToMap(attrs, true)); } return this; }; ////////////////////////////////////////////////////////////////////////////////////////////////// // Private stuff ////////////////////////////////////////////////////////////////////////////////////////////////// bind = angular.bind; extend = angular.extend; forEach = angular.forEach; isArray = angular.isArray; isDefined = angular.isDefined; lowercase = angular.$$lowercase; noop = angular.noop; htmlParser = htmlParserImpl; htmlSanitizeWriter = htmlSanitizeWriterImpl; nodeContains = window.Node.prototype.contains || /** @this */ function(arg) { // eslint-disable-next-line no-bitwise return !!(this.compareDocumentPosition(arg) & 16); }; // Regular Expressions for parsing tags and attributes var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, // Match everything outside of normal chars and " (quote character) NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g; // Good source of info about elements and attributes // http://dev.w3.org/html5/spec/Overview.html#semantics // http://simon.html5.org/html-elements // Safe Void Elements - HTML5 // http://dev.w3.org/html5/spec/Overview.html#void-elements var voidElements = stringToMap('area,br,col,hr,img,wbr'); // Elements that you can, intentionally, leave open (and which close themselves) // http://dev.w3.org/html5/spec/Overview.html#optional-tags var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'), optionalEndTagInlineElements = stringToMap('rp,rt'), optionalEndTagElements = extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); // Safe Block Elements - HTML5 var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' + 'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' + 'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul')); // Inline Elements - HTML5 var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' + 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' + 'samp,small,span,strike,strong,sub,sup,time,tt,u,var')); // SVG Elements // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. // They can potentially allow for arbitrary javascript to be executed. See #11290 var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' + 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' + 'radialGradient,rect,stop,svg,switch,text,title,tspan'); // Blocked Elements (will be stripped) var blockedElements = stringToMap('script,style'); var validElements = extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); //Attributes that have href and hence need to be sanitized var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base'); var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + 'valign,value,vspace,width'); // SVG attributes (without "id" and "name" attributes) // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); var validAttrs = extend({}, uriAttrs, svgAttrs, htmlAttrs); function stringToMap(str, lowercaseKeys) { return arrayToMap(str.split(','), lowercaseKeys); } function arrayToMap(items, lowercaseKeys) { var obj = {}, i; for (i = 0; i < items.length; i++) { obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true; } return obj; } function addElementsTo(elementsMap, newElements) { if (newElements && newElements.length) { extend(elementsMap, arrayToMap(newElements)); } } /** * Create an inert document that contains the dirty HTML that needs sanitizing. * We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not * available. */ var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) { if (isDOMParserAvailable()) { return getInertBodyElement_DOMParser; } if (!document || !document.implementation) { throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document'); } var inertDocument = document.implementation.createHTMLDocument('inert'); var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body'); return getInertBodyElement_InertDocument; function isDOMParserAvailable() { try { return !!getInertBodyElement_DOMParser(''); } catch (e) { return false; } } function getInertBodyElement_DOMParser(html) { // We add this dummy element to ensure that the rest of the content is parsed as expected // e.g. leading whitespace is maintained and tags like `` do not get hoisted to the `` tag. html = '' + html; try { var body = new window.DOMParser().parseFromString(html, 'text/html').body; body.firstChild.remove(); return body; } catch (e) { return undefined; } } function getInertBodyElement_InertDocument(html) { inertBodyElement.innerHTML = html; // Support: IE 9-11 only // strip custom-namespaced attributes on IE<=11 if (document.documentMode) { stripCustomNsAttrs(inertBodyElement); } return inertBodyElement; } })(window, window.document); /** * @example * htmlParser(htmlString, { * start: function(tag, attrs) {}, * end: function(tag) {}, * chars: function(text) {}, * comment: function(text) {} * }); * * @param {string} html string * @param {object} handler */ function htmlParserImpl(html, handler) { if (html === null || html === undefined) { html = ''; } else if (typeof html !== 'string') { html = '' + html; } var inertBodyElement = getInertBodyElement(html); if (!inertBodyElement) return ''; //mXSS protection var mXSSAttempts = 5; do { if (mXSSAttempts === 0) { throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable'); } mXSSAttempts--; // trigger mXSS if it is going to happen by reading and writing the innerHTML html = inertBodyElement.innerHTML; inertBodyElement = getInertBodyElement(html); } while (html !== inertBodyElement.innerHTML); var node = inertBodyElement.firstChild; while (node) { switch (node.nodeType) { case 1: // ELEMENT_NODE handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes)); break; case 3: // TEXT NODE handler.chars(node.textContent); break; } var nextNode; if (!(nextNode = node.firstChild)) { if (node.nodeType === 1) { handler.end(node.nodeName.toLowerCase()); } nextNode = getNonDescendant('nextSibling', node); if (!nextNode) { while (nextNode == null) { node = getNonDescendant('parentNode', node); if (node === inertBodyElement) break; nextNode = getNonDescendant('nextSibling', node); if (node.nodeType === 1) { handler.end(node.nodeName.toLowerCase()); } } } } node = nextNode; } while ((node = inertBodyElement.firstChild)) { inertBodyElement.removeChild(node); } } function attrToMap(attrs) { var map = {}; for (var i = 0, ii = attrs.length; i < ii; i++) { var attr = attrs[i]; map[attr.name] = attr.value; } return map; } /** * Escapes all potentially dangerous characters, so that the * resulting string can be safely inserted into attribute or * element text. * @param value * @returns {string} escaped text */ function encodeEntities(value) { return value. replace(/&/g, '&'). replace(SURROGATE_PAIR_REGEXP, function(value) { var hi = value.charCodeAt(0); var low = value.charCodeAt(1); return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; }). replace(NON_ALPHANUMERIC_REGEXP, function(value) { return '&#' + value.charCodeAt(0) + ';'; }). replace(//g, '>'); } /** * create an HTML/XML writer which writes to buffer * @param {Array} buf use buf.join('') to get out sanitized html string * @returns {object} in the form of { * start: function(tag, attrs) {}, * end: function(tag) {}, * chars: function(text) {}, * comment: function(text) {} * } */ function htmlSanitizeWriterImpl(buf, uriValidator) { var ignoreCurrentElement = false; var out = bind(buf, buf.push); return { start: function(tag, attrs) { tag = lowercase(tag); if (!ignoreCurrentElement && blockedElements[tag]) { ignoreCurrentElement = tag; } if (!ignoreCurrentElement && validElements[tag] === true) { out('<'); out(tag); forEach(attrs, function(value, key) { var lkey = lowercase(key); var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); if (validAttrs[lkey] === true && (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { out(' '); out(key); out('="'); out(encodeEntities(value)); out('"'); } }); out('>'); } }, end: function(tag) { tag = lowercase(tag); if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) { out(''); } // eslint-disable-next-line eqeqeq if (tag == ignoreCurrentElement) { ignoreCurrentElement = false; } }, chars: function(chars) { if (!ignoreCurrentElement) { out(encodeEntities(chars)); } } }; } /** * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want * to allow any of these custom attributes. This method strips them all. * * @param node Root element to process */ function stripCustomNsAttrs(node) { while (node) { if (node.nodeType === window.Node.ELEMENT_NODE) { var attrs = node.attributes; for (var i = 0, l = attrs.length; i < l; i++) { var attrNode = attrs[i]; var attrName = attrNode.name.toLowerCase(); if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) { node.removeAttributeNode(attrNode); i--; l--; } } } var nextNode = node.firstChild; if (nextNode) { stripCustomNsAttrs(nextNode); } node = getNonDescendant('nextSibling', node); } } function getNonDescendant(propName, node) { // An element is clobbered if its `propName` property points to one of its descendants var nextNode = node[propName]; if (nextNode && nodeContains.call(node, nextNode)) { throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText); } return nextNode; } } function sanitizeText(chars) { var buf = []; var writer = htmlSanitizeWriter(buf, noop); writer.chars(chars); return buf.join(''); } // define ngSanitize module and register $sanitize service angular.module('ngSanitize', []) .provider('$sanitize', $SanitizeProvider) .info({ angularVersion: '1.8.2' }); /** * @ngdoc filter * @name linky * @kind function * * @description * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and * plain email address links. * * Requires the {@link ngSanitize `ngSanitize`} module to be installed. * * @param {string} text Input text. * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in. * @param {object|function(url)} [attributes] Add custom attributes to the link element. * * Can be one of: * * - `object`: A map of attributes * - `function`: Takes the url as a parameter and returns a map of attributes * * If the map of attributes contains a value for `target`, it overrides the value of * the target parameter. * * * @returns {string} Html-linkified and {@link $sanitize sanitized} text. * * @usage * * @example
Snippet:
Filter Source Rendered
linky filter
<div ng-bind-html="snippet | linky">
</div>
linky target
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'">
</div>
linky custom attributes
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}">
</div>
no filter
<div ng-bind="snippet">
</div>
angular.module('linkyExample', ['ngSanitize']) .controller('ExampleController', ['$scope', function($scope) { $scope.snippet = 'Pretty text with some links:\n' + 'http://angularjs.org/,\n' + 'mailto:us@somewhere.org,\n' + 'another@somewhere.org,\n' + 'and one more: ftp://127.0.0.1/.'; $scope.snippetWithSingleURL = 'http://angularjs.org/'; }]); it('should linkify the snippet with urls', function() { expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); }); it('should not linkify snippet without the linky filter', function() { expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); }); it('should update', function() { element(by.model('snippet')).clear(); element(by.model('snippet')).sendKeys('new http://link.'); expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). toBe('new http://link.'); expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) .toBe('new http://link.'); }); it('should work with the target property', function() { expect(element(by.id('linky-target')). element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()). toBe('http://angularjs.org/'); expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); }); it('should optionally add custom attributes', function() { expect(element(by.id('linky-custom-attributes')). element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()). toBe('http://angularjs.org/'); expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow'); }); */ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { var LINKY_URL_REGEXP = /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, MAILTO_REGEXP = /^mailto:/i; var linkyMinErr = angular.$$minErr('linky'); var isDefined = angular.isDefined; var isFunction = angular.isFunction; var isObject = angular.isObject; var isString = angular.isString; return function(text, target, attributes) { if (text == null || text === '') return text; if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text); var attributesFn = isFunction(attributes) ? attributes : isObject(attributes) ? function getAttributesObject() {return attributes;} : function getEmptyAttributesObject() {return {};}; var match; var raw = text; var html = []; var url; var i; while ((match = raw.match(LINKY_URL_REGEXP))) { // We can not end in these as they are sometimes found at the end of the sentence url = match[0]; // if we did not match ftp/http/www/mailto then assume mailto if (!match[2] && !match[4]) { url = (match[3] ? 'http://' : 'mailto:') + url; } i = match.index; addText(raw.substr(0, i)); addLink(url, match[0].replace(MAILTO_REGEXP, '')); raw = raw.substring(i + match[0].length); } addText(raw); return $sanitize(html.join('')); function addText(text) { if (!text) { return; } html.push(sanitizeText(text)); } function addLink(url, text) { var key, linkAttributes = attributesFn(url); html.push(''); addText(text); html.push(''); } }; }]); })(window, window.angular); NAIK128 https://naik128.net/ Mon, 08 Jun 2026 20:25:18 +0000 id hourly 1 NAIK128 Favorit | Fokus Zen Di Tengah Chaos Pertempuran https://naik128.net/naik128-favorit/ Mon, 08 Jun 2026 20:04:48 +0000 https://naik128.net/?p=130 Artikel NAIK128 Favorit | Fokus Zen Di Tengah Chaos Pertempuran pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Favorit | Fokus Zen Di Tengah Chaos Pertempuran pertama kali tampil pada NAIK128.

]]>
NAIK128 Demo | Aliansi Rahasia Pemain Profesional Berkumpul https://naik128.net/naik128-demo/ Sun, 07 Jun 2026 20:04:34 +0000 https://naik128.net/?p=125 Artikel NAIK128 Demo | Aliansi Rahasia Pemain Profesional Berkumpul pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Demo | Aliansi Rahasia Pemain Profesional Berkumpul pertama kali tampil pada NAIK128.

]]>
NAIK128 Populer | Mentalitas Baja Penakluk Jackpot Seru https://naik128.net/naik128-populer/ Sat, 06 Jun 2026 19:47:34 +0000 https://naik128.net/?p=117 Artikel NAIK128 Populer | Mentalitas Baja Penakluk Jackpot Seru pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Populer | Mentalitas Baja Penakluk Jackpot Seru pertama kali tampil pada NAIK128.

]]>
NAIK128 Wangi | Liat Nih Komunitas Paling Solid Kompak https://naik128.net/naik128-wangi/ Fri, 05 Jun 2026 20:04:21 +0000 https://naik128.net/?p=110 Artikel NAIK128 Wangi | Liat Nih Komunitas Paling Solid Kompak pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Wangi | Liat Nih Komunitas Paling Solid Kompak pertama kali tampil pada NAIK128.

]]>
NAIK128 Web Mobile | Gonta Ganti Gadget Aman Tanpa Masalah https://naik128.net/naik128-web-mobile/ Thu, 04 Jun 2026 19:05:05 +0000 https://naik128.net/?p=106 NAIK128 Web Mobile | Gonta Ganti Gadget Aman Tanpa Masalah

Artikel NAIK128 Web Mobile | Gonta Ganti Gadget Aman Tanpa Masalah pertama kali tampil pada NAIK128.

]]>
NAIK128 Web Mobile | Gonta Ganti Gadget Aman Tanpa Masalah

Artikel NAIK128 Web Mobile | Gonta Ganti Gadget Aman Tanpa Masalah pertama kali tampil pada NAIK128.

]]>
NAIK128 Portal | Valid No Debat Situs Paling Royal Sedunia https://naik128.net/naik128-portal/ Wed, 03 Jun 2026 19:58:05 +0000 https://naik128.net/?p=101 Artikel NAIK128 Portal | Valid No Debat Situs Paling Royal Sedunia pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Portal | Valid No Debat Situs Paling Royal Sedunia pertama kali tampil pada NAIK128.

]]>
NAIK128 Online | Analisis Kekuatan Bos Raksasa Arena https://naik128.net/naik128-online/ Sun, 15 Mar 2026 11:22:17 +0000 https://naik128.net/?p=96 Artikel NAIK128 Online | Analisis Kekuatan Bos Raksasa Arena pertama kali tampil pada NAIK128.

]]>
Artikel NAIK128 Online | Analisis Kekuatan Bos Raksasa Arena pertama kali tampil pada NAIK128.

]]>
NAIK128 Raja | Navigasi Saga Pesona Pencetak Aset Digital https://naik128.net/naik128-raja/ Fri, 27 Feb 2026 00:14:29 +0000 https://naik128.net/?p=90 NAIK128 Raja | Navigasi Saga Pesona Pencetak Aset Digital

Artikel NAIK128 Raja | Navigasi Saga Pesona Pencetak Aset Digital pertama kali tampil pada NAIK128.

]]>
NAIK128 Raja | Navigasi Saga Pesona Pencetak Aset Digital

Artikel NAIK128 Raja | Navigasi Saga Pesona Pencetak Aset Digital pertama kali tampil pada NAIK128.

]]>
NAIK128 Top | Konfigurasi Cloud Gaming Tanpa Kendala Delay https://naik128.net/naik128-top/ Sun, 22 Feb 2026 00:07:00 +0000 https://naik128.net/?p=83 NAIK128 Top | Konfigurasi Cloud Gaming Tanpa Kendala Delay

Artikel NAIK128 Top | Konfigurasi Cloud Gaming Tanpa Kendala Delay pertama kali tampil pada NAIK128.

]]>
NAIK128 Top | Konfigurasi Cloud Gaming Tanpa Kendala Delay

Artikel NAIK128 Top | Konfigurasi Cloud Gaming Tanpa Kendala Delay pertama kali tampil pada NAIK128.

]]>
NAIK128 Link | Fitur Komunitas Game Eksklusif Penuh Manfaat https://naik128.net/naik128-link/ Mon, 16 Feb 2026 01:06:28 +0000 https://naik128.net/?p=78 NAIK128 Link Kemajuan tehnologi mobile sudah mengganti trik rakyat nikmati kesenangan digital. Bila dahulu game cuma dapat dimainkan lewat fitur tersendiri, saat ini cukup hanya dengan mobile-phone, beragam tipe permainan bisa dijangkau ringan. APK mobile jadi jalan keluar penting sebab menjajakan kepraktisan, kecepatan, serta elastisitas buat banyak pemakai. Pada dunia permainan online, NAIK128 Link memakai …

NAIK128 Link | Fitur Komunitas Game Eksklusif Penuh Manfaat Read More »

Artikel NAIK128 Link | Fitur Komunitas Game Eksklusif Penuh Manfaat pertama kali tampil pada NAIK128.

]]>
NAIK128 Link Kemajuan tehnologi mobile sudah mengganti trik rakyat nikmati kesenangan digital. Bila dahulu game cuma dapat dimainkan lewat fitur tersendiri, saat ini cukup hanya dengan mobile-phone, beragam tipe permainan bisa dijangkau ringan. APK mobile jadi jalan keluar penting sebab menjajakan kepraktisan, kecepatan, serta elastisitas buat banyak pemakai.

Pada dunia permainan online, NAIK128 Link memakai kemajuan ini dengan mendatangkan basis berbasiskan APK mobile. Hadirnya APK NAIK128 Link bikin pemain bisa langsung tersambung ke dunia permainan tanpa ada proses yang susah, menjadikan gerbang khusus kesenangan digital saat ini.

APK Mobile Jadi Gerbang Penting Game NAIK128 Link

APK mobile memegang peranan jadi pintu masuk penting untuk pemain NAIK128 Link. Dengan 1 terapan, pemakai bisa terhubung beberapa spesifikasi dan permainan tanpa berubah basis. Perihal ini bikin pengalaman main bertambah singkat serta terpadu.

Keringanan akses lewat APK bikin NAIK128 Link lebih inklusif. Siapa pun yang mempunyai gadget bisa nikmati game tiada butuh feature tambahan. Rancangan ini amat sesuai sama life-style kekinian yang serba praktis dan cepat.

Disamping itu, APK mobile memungkinnya NAIK128 Link mendatangkan perbaikan content secara periodik. Pemain bisa langsung nikmati feature teranyar tak mesti melaksanakan instalasi lagi, agar pengalaman main masih fresh dan menarik.

Kelebihan Bermain NAIK128 Link Melalui APK Game

Bermain NAIK128 Link lewat APK game memberi beragam kelebihan diperbandingkan akses yang lain. Satu diantaranya keunggulan khusus yakni perform yang tambah lebih konstan karena program direncanakan teristimewa guna fitur mobile.

Dari segi penampakan, APK NAIK128 Link punyai antar-muka yang peka dan simpel dimengerti. Navigasi yang simpel mempermudah pemain baru buat secepatnya beradaptasi tanpa kepanikan saat kali pertama main.

Diluar itu, APK game memungkinnya kontrol yang tambah lebih maksimum. Tiap sentuhan dan gerakan berasa lebih responsive, agar pengalaman bermain jadi lebih menyenangkan dan nyaman dalam periode waktu lama.

Tehnologi APK Mobile Membikin NAIK128 Link Lebih Ringkas

Technologi APK mobile jadi hal khusus yang membuat NAIK128 Link kian efektif dipakai. Program ini didesain biar enteng serta sesuai dengan bermacam macam mobile-phone, hingga tidak memberatkan piranti.

Kepraktisan pula tampak dari keluwesan waktu bermain. Pemain bisa terhubung NAIK128 Link setiap saat dan dimana-mana, baik saat kosong atau guna sebatas melepaskan lelah seusai bekerja.

Dengan support technologi mobile yang selalu berkembang, NAIK128 Link dapat mendatangkan pengalaman main yang efisien. Semuanya feature bisa dicapai dalam satu terapan tanpa proses yang berbelit.

Download APK NAIK128 Link Sah serta Mulai Bermain Hari Ini

Mengambil APK NAIK128 Link sah yaitu langkah pertama guna nikmati seluruhnya feature yang siap. Proses instalasi yang simpel memungkinnya pemain langsung mengawali tanpa hambatan tekhnis yang bermakna.

Sehabis program dipasang, pemain bisa menelusuri pelbagai opsi game dengan penampilan dengan rapi serta terancang. Tiap-tiap menu direncanakan untuk meringankan pemakai dalam menunjuk permainan pujaan.

Mempunyai performa yang maksimum serta prosedur yang konstan, APK NAIK128 Link siap berikan pengalaman bermain yang maksimum. Ini menjadi alternatif benar buat penggemar game mobile yang memprioritaskan ketenteraman.

Kupasan

Keseluruhannya, APK NAIK128 Link tawarkan pengalaman game mobile yang padu. Dari segi rancangan, terapan ini tampak kekinian dan ramah pemakai.

Perform terapan layak dipuji karena berjalan manis di bermacam piranti. Waktu tanggapan yang lebih cepat membuat kegiatan main berasa lebih membahagiakan.

Ragam permainan yang siap menjadi nilai lebih khusus. Pemain miliki banyak terdapat pilihan hingga tidak gampang terasa bosan.

Penutup

APK mobile sudah jadi jalan keluar khusus pada dunia permainan online, dan NAIK128 Link menggunakannya secara baik. Kepraktisan dan keluasaan akses menjadi keunggulan yang mencolok.

Dengan support tehnologi yang tetap berkembang, NAIK128 Link mempunyai potensi lagi menaikkan kwalitas pengalaman main. Perubahan akan jadi kunci kesinambungan basis ini.

Untuk pemakai handphone yang cari selingan digital, APK NAIK128 Link bisa jadi alternatif memikat untuk temani waktu senggang secara positif dan membahagiakan.

Artikel NAIK128 Link | Fitur Komunitas Game Eksklusif Penuh Manfaat pertama kali tampil pada NAIK128.

]]>