[VID-6] Initial rebase push
[vid.git] / epsdk-app-onap / src / main / webapp / app / fusion / external / ebz / angular_js / angular-sanitize.js
1 /**\r
2  * @license AngularJS v1.2.25\r
3  * (c) 2010-2014 Google, Inc. http://angularjs.org\r
4  * License: MIT\r
5  */\r
6 (function(window, angular, undefined) {'use strict';\r
7 \r
8 var $sanitizeMinErr = angular.$$minErr('$sanitize');\r
9 \r
10 /**\r
11  * @ngdoc module\r
12  * @name ngSanitize\r
13  * @description\r
14  *\r
15  * # ngSanitize\r
16  *\r
17  * The `ngSanitize` module provides functionality to sanitize HTML.\r
18  *\r
19  *\r
20  * <div doc-module-components="ngSanitize"></div>\r
21  *\r
22  * See {@link ngSanitize.$sanitize `$sanitize`} for usage.\r
23  */\r
24 \r
25 /*\r
26  * HTML Parser By Misko Hevery (misko@hevery.com)\r
27  * based on:  HTML Parser By John Resig (ejohn.org)\r
28  * Original code by Erik Arvidsson, Mozilla Public License\r
29  * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js\r
30  *\r
31  * // Use like so:\r
32  * htmlParser(htmlString, {\r
33  *     start: function(tag, attrs, unary) {},\r
34  *     end: function(tag) {},\r
35  *     chars: function(text) {},\r
36  *     comment: function(text) {}\r
37  * });\r
38  *\r
39  */\r
40 \r
41 \r
42 /**\r
43  * @ngdoc service\r
44  * @name $sanitize\r
45  * @kind function\r
46  *\r
47  * @description\r
48  *   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are\r
49  *   then serialized back to properly escaped html string. This means that no unsafe input can make\r
50  *   it into the returned string, however, since our parser is more strict than a typical browser\r
51  *   parser, it's possible that some obscure input, which would be recognized as valid HTML by a\r
52  *   browser, won't make it through the sanitizer.\r
53  *   The whitelist is configured using the functions `aHrefSanitizationWhitelist` and\r
54  *   `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.\r
55  *\r
56  * @param {string} html Html input.\r
57  * @returns {string} Sanitized html.\r
58  *\r
59  * @example\r
60    <example module="sanitizeExample" deps="angular-sanitize.js">\r
61    <file name="index.html">\r
62      <script>\r
63          angular.module('sanitizeExample', ['ngSanitize'])\r
64            .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {\r
65              $scope.snippet =\r
66                '<p style="color:blue">an html\n' +\r
67                '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +\r
68                'snippet</p>';\r
69              $scope.deliberatelyTrustDangerousSnippet = function() {\r
70                return $sce.trustAsHtml($scope.snippet);\r
71              };\r
72            }]);\r
73      </script>\r
74      <div ng-controller="ExampleController">\r
75         Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>\r
76        <table>\r
77          <tr>\r
78            <td>Directive</td>\r
79            <td>How</td>\r
80            <td>Source</td>\r
81            <td>Rendered</td>\r
82          </tr>\r
83          <tr id="bind-html-with-sanitize">\r
84            <td>ng-bind-html</td>\r
85            <td>Automatically uses $sanitize</td>\r
86            <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>\r
87            <td><div ng-bind-html="snippet"></div></td>\r
88          </tr>\r
89          <tr id="bind-html-with-trust">\r
90            <td>ng-bind-html</td>\r
91            <td>Bypass $sanitize by explicitly trusting the dangerous value</td>\r
92            <td>\r
93            <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;\r
94 &lt;/div&gt;</pre>\r
95            </td>\r
96            <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>\r
97          </tr>\r
98          <tr id="bind-default">\r
99            <td>ng-bind</td>\r
100            <td>Automatically escapes</td>\r
101            <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>\r
102            <td><div ng-bind="snippet"></div></td>\r
103          </tr>\r
104        </table>\r
105        </div>\r
106    </file>\r
107    <file name="protractor.js" type="protractor">\r
108      it('should sanitize the html snippet by default', function() {\r
109        expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).\r
110          toBe('<p>an html\n<em>click here</em>\nsnippet</p>');\r
111      });\r
112 \r
113      it('should inline raw snippet if bound to a trusted value', function() {\r
114        expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).\r
115          toBe("<p style=\"color:blue\">an html\n" +\r
116               "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +\r
117               "snippet</p>");\r
118      });\r
119 \r
120      it('should escape snippet without any filter', function() {\r
121        expect(element(by.css('#bind-default div')).getInnerHtml()).\r
122          toBe("&lt;p style=\"color:blue\"&gt;an html\n" +\r
123               "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +\r
124               "snippet&lt;/p&gt;");\r
125      });\r
126 \r
127      it('should update', function() {\r
128        element(by.model('snippet')).clear();\r
129        element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');\r
130        expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).\r
131          toBe('new <b>text</b>');\r
132        expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(\r
133          'new <b onclick="alert(1)">text</b>');\r
134        expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(\r
135          "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");\r
136      });\r
137    </file>\r
138    </example>\r
139  */\r
140 function $SanitizeProvider() {\r
141   this.$get = ['$$sanitizeUri', function($$sanitizeUri) {\r
142     return function(html) {\r
143       var buf = [];\r
144       htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {\r
145         return !/^unsafe/.test($$sanitizeUri(uri, isImage));\r
146       }));\r
147       return buf.join('');\r
148     };\r
149   }];\r
150 }\r
151 \r
152 function sanitizeText(chars) {\r
153   var buf = [];\r
154   var writer = htmlSanitizeWriter(buf, angular.noop);\r
155   writer.chars(chars);\r
156   return buf.join('');\r
157 }\r
158 \r
159 \r
160 // Regular Expressions for parsing tags and attributes\r
161 var START_TAG_REGEXP =\r
162        /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,\r
163   END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,\r
164   ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,\r
165   BEGIN_TAG_REGEXP = /^</,\r
166   BEGING_END_TAGE_REGEXP = /^<\//,\r
167   COMMENT_REGEXP = /<!--(.*?)-->/g,\r
168   DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,\r
169   CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,\r
170   SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
171   // Match everything outside of normal chars and " (quote character)\r
172   NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;\r
173 \r
174 \r
175 // Good source of info about elements and attributes\r
176 // http://dev.w3.org/html5/spec/Overview.html#semantics\r
177 // http://simon.html5.org/html-elements\r
178 \r
179 // Safe Void Elements - HTML5\r
180 // http://dev.w3.org/html5/spec/Overview.html#void-elements\r
181 var voidElements = makeMap("area,br,col,hr,img,wbr");\r
182 \r
183 // Elements that you can, intentionally, leave open (and which close themselves)\r
184 // http://dev.w3.org/html5/spec/Overview.html#optional-tags\r
185 var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),\r
186     optionalEndTagInlineElements = makeMap("rp,rt"),\r
187     optionalEndTagElements = angular.extend({},\r
188                                             optionalEndTagInlineElements,\r
189                                             optionalEndTagBlockElements);\r
190 \r
191 // Safe Block Elements - HTML5\r
192 var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +\r
193         "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +\r
194         "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));\r
195 \r
196 // Inline Elements - HTML5\r
197 var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +\r
198         "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +\r
199         "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));\r
200 \r
201 \r
202 // Special Elements (can contain anything)\r
203 var specialElements = makeMap("script,style");\r
204 \r
205 var validElements = angular.extend({},\r
206                                    voidElements,\r
207                                    blockElements,\r
208                                    inlineElements,\r
209                                    optionalEndTagElements);\r
210 \r
211 //Attributes that have href and hence need to be sanitized\r
212 var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");\r
213 var validAttrs = angular.extend({}, uriAttrs, makeMap(\r
214     'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+\r
215     'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+\r
216     'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+\r
217     'scope,scrolling,shape,size,span,start,summary,target,title,type,'+\r
218     'valign,value,vspace,width'));\r
219 \r
220 function makeMap(str) {\r
221   var obj = {}, items = str.split(','), i;\r
222   for (i = 0; i < items.length; i++) obj[items[i]] = true;\r
223   return obj;\r
224 }\r
225 \r
226 \r
227 /**\r
228  * @example\r
229  * htmlParser(htmlString, {\r
230  *     start: function(tag, attrs, unary) {},\r
231  *     end: function(tag) {},\r
232  *     chars: function(text) {},\r
233  *     comment: function(text) {}\r
234  * });\r
235  *\r
236  * @param {string} html string\r
237  * @param {object} handler\r
238  */\r
239 function htmlParser( html, handler ) {\r
240   if (typeof html !== 'string') {\r
241     if (html === null || typeof html === 'undefined') {\r
242       html = '';\r
243     } else {\r
244       html = '' + html;\r
245     }\r
246   }\r
247   var index, chars, match, stack = [], last = html, text;\r
248   stack.last = function() { return stack[ stack.length - 1 ]; };\r
249 \r
250   while ( html ) {\r
251     text = '';\r
252     chars = true;\r
253 \r
254     // Make sure we're not in a script or style element\r
255     if ( !stack.last() || !specialElements[ stack.last() ] ) {\r
256 \r
257       // Comment\r
258       if ( html.indexOf("<!--") === 0 ) {\r
259         // comments containing -- are not allowed unless they terminate the comment\r
260         index = html.indexOf("--", 4);\r
261 \r
262         if ( index >= 0 && html.lastIndexOf("-->", index) === index) {\r
263           if (handler.comment) handler.comment( html.substring( 4, index ) );\r
264           html = html.substring( index + 3 );\r
265           chars = false;\r
266         }\r
267       // DOCTYPE\r
268       } else if ( DOCTYPE_REGEXP.test(html) ) {\r
269         match = html.match( DOCTYPE_REGEXP );\r
270 \r
271         if ( match ) {\r
272           html = html.replace( match[0], '');\r
273           chars = false;\r
274         }\r
275       // end tag\r
276       } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {\r
277         match = html.match( END_TAG_REGEXP );\r
278 \r
279         if ( match ) {\r
280           html = html.substring( match[0].length );\r
281           match[0].replace( END_TAG_REGEXP, parseEndTag );\r
282           chars = false;\r
283         }\r
284 \r
285       // start tag\r
286       } else if ( BEGIN_TAG_REGEXP.test(html) ) {\r
287         match = html.match( START_TAG_REGEXP );\r
288 \r
289         if ( match ) {\r
290           // We only have a valid start-tag if there is a '>'.\r
291           if ( match[4] ) {\r
292             html = html.substring( match[0].length );\r
293             match[0].replace( START_TAG_REGEXP, parseStartTag );\r
294           }\r
295           chars = false;\r
296         } else {\r
297           // no ending tag found --- this piece should be encoded as an entity.\r
298           text += '<';\r
299           html = html.substring(1);\r
300         }\r
301       }\r
302 \r
303       if ( chars ) {\r
304         index = html.indexOf("<");\r
305 \r
306         text += index < 0 ? html : html.substring( 0, index );\r
307         html = index < 0 ? "" : html.substring( index );\r
308 \r
309         if (handler.chars) handler.chars( decodeEntities(text) );\r
310       }\r
311 \r
312     } else {\r
313       html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),\r
314         function(all, text){\r
315           text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");\r
316 \r
317           if (handler.chars) handler.chars( decodeEntities(text) );\r
318 \r
319           return "";\r
320       });\r
321 \r
322       parseEndTag( "", stack.last() );\r
323     }\r
324 \r
325     if ( html == last ) {\r
326       throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +\r
327                                         "of html: {0}", html);\r
328     }\r
329     last = html;\r
330   }\r
331 \r
332   // Clean up any remaining tags\r
333   parseEndTag();\r
334 \r
335   function parseStartTag( tag, tagName, rest, unary ) {\r
336     tagName = angular.lowercase(tagName);\r
337     if ( blockElements[ tagName ] ) {\r
338       while ( stack.last() && inlineElements[ stack.last() ] ) {\r
339         parseEndTag( "", stack.last() );\r
340       }\r
341     }\r
342 \r
343     if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {\r
344       parseEndTag( "", tagName );\r
345     }\r
346 \r
347     unary = voidElements[ tagName ] || !!unary;\r
348 \r
349     if ( !unary )\r
350       stack.push( tagName );\r
351 \r
352     var attrs = {};\r
353 \r
354     rest.replace(ATTR_REGEXP,\r
355       function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {\r
356         var value = doubleQuotedValue\r
357           || singleQuotedValue\r
358           || unquotedValue\r
359           || '';\r
360 \r
361         attrs[name] = decodeEntities(value);\r
362     });\r
363     if (handler.start) handler.start( tagName, attrs, unary );\r
364   }\r
365 \r
366   function parseEndTag( tag, tagName ) {\r
367     var pos = 0, i;\r
368     tagName = angular.lowercase(tagName);\r
369     if ( tagName )\r
370       // Find the closest opened tag of the same type\r
371       for ( pos = stack.length - 1; pos >= 0; pos-- )\r
372         if ( stack[ pos ] == tagName )\r
373           break;\r
374 \r
375     if ( pos >= 0 ) {\r
376       // Close all the open elements, up the stack\r
377       for ( i = stack.length - 1; i >= pos; i-- )\r
378         if (handler.end) handler.end( stack[ i ] );\r
379 \r
380       // Remove the open elements from the stack\r
381       stack.length = pos;\r
382     }\r
383   }\r
384 }\r
385 \r
386 var hiddenPre=document.createElement("pre");\r
387 var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;\r
388 /**\r
389  * decodes all entities into regular string\r
390  * @param value\r
391  * @returns {string} A string with decoded entities.\r
392  */\r
393 function decodeEntities(value) {\r
394   if (!value) { return ''; }\r
395 \r
396   // Note: IE8 does not preserve spaces at the start/end of innerHTML\r
397   // so we must capture them and reattach them afterward\r
398   var parts = spaceRe.exec(value);\r
399   var spaceBefore = parts[1];\r
400   var spaceAfter = parts[3];\r
401   var content = parts[2];\r
402   if (content) {\r
403     hiddenPre.innerHTML=content.replace(/</g,"&lt;");\r
404     // innerText depends on styling as it doesn't display hidden elements.\r
405     // Therefore, it's better to use textContent not to cause unnecessary\r
406     // reflows. However, IE<9 don't support textContent so the innerText\r
407     // fallback is necessary.\r
408     content = 'textContent' in hiddenPre ?\r
409       hiddenPre.textContent : hiddenPre.innerText;\r
410   }\r
411   return spaceBefore + content + spaceAfter;\r
412 }\r
413 \r
414 /**\r
415  * Escapes all potentially dangerous characters, so that the\r
416  * resulting string can be safely inserted into attribute or\r
417  * element text.\r
418  * @param value\r
419  * @returns {string} escaped text\r
420  */\r
421 function encodeEntities(value) {\r
422   return value.\r
423     replace(/&/g, '&amp;').\r
424     replace(SURROGATE_PAIR_REGEXP, function (value) {\r
425       var hi = value.charCodeAt(0);\r
426       var low = value.charCodeAt(1);\r
427       return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';\r
428     }).\r
429     replace(NON_ALPHANUMERIC_REGEXP, function(value){\r
430       return '&#' + value.charCodeAt(0) + ';';\r
431     }).\r
432     replace(/</g, '&lt;').\r
433     replace(/>/g, '&gt;');\r
434 }\r
435 \r
436 /**\r
437  * create an HTML/XML writer which writes to buffer\r
438  * @param {Array} buf use buf.jain('') to get out sanitized html string\r
439  * @returns {object} in the form of {\r
440  *     start: function(tag, attrs, unary) {},\r
441  *     end: function(tag) {},\r
442  *     chars: function(text) {},\r
443  *     comment: function(text) {}\r
444  * }\r
445  */\r
446 function htmlSanitizeWriter(buf, uriValidator){\r
447   var ignore = false;\r
448   var out = angular.bind(buf, buf.push);\r
449   return {\r
450     start: function(tag, attrs, unary){\r
451       tag = angular.lowercase(tag);\r
452       if (!ignore && specialElements[tag]) {\r
453         ignore = tag;\r
454       }\r
455       if (!ignore && validElements[tag] === true) {\r
456         out('<');\r
457         out(tag);\r
458         angular.forEach(attrs, function(value, key){\r
459           var lkey=angular.lowercase(key);\r
460           var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');\r
461           if (validAttrs[lkey] === true &&\r
462             (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {\r
463             out(' ');\r
464             out(key);\r
465             out('="');\r
466             out(encodeEntities(value));\r
467             out('"');\r
468           }\r
469         });\r
470         out(unary ? '/>' : '>');\r
471       }\r
472     },\r
473     end: function(tag){\r
474         tag = angular.lowercase(tag);\r
475         if (!ignore && validElements[tag] === true) {\r
476           out('</');\r
477           out(tag);\r
478           out('>');\r
479         }\r
480         if (tag == ignore) {\r
481           ignore = false;\r
482         }\r
483       },\r
484     chars: function(chars){\r
485         if (!ignore) {\r
486           out(encodeEntities(chars));\r
487         }\r
488       }\r
489   };\r
490 }\r
491 \r
492 \r
493 // define ngSanitize module and register $sanitize service\r
494 angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);\r
495 \r
496 /* global sanitizeText: false */\r
497 \r
498 /**\r
499  * @ngdoc filter\r
500  * @name linky\r
501  * @kind function\r
502  *\r
503  * @description\r
504  * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and\r
505  * plain email address links.\r
506  *\r
507  * Requires the {@link ngSanitize `ngSanitize`} module to be installed.\r
508  *\r
509  * @param {string} text Input text.\r
510  * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.\r
511  * @returns {string} Html-linkified text.\r
512  *\r
513  * @usage\r
514    <span ng-bind-html="linky_expression | linky"></span>\r
515  *\r
516  * @example\r
517    <example module="linkyExample" deps="angular-sanitize.js">\r
518      <file name="index.html">\r
519        <script>\r
520          angular.module('linkyExample', ['ngSanitize'])\r
521            .controller('ExampleController', ['$scope', function($scope) {\r
522              $scope.snippet =\r
523                'Pretty text with some links:\n'+\r
524                'http://angularjs.org/,\n'+\r
525                'mailto:us@somewhere.org,\n'+\r
526                'another@somewhere.org,\n'+\r
527                'and one more: ftp://127.0.0.1/.';\r
528              $scope.snippetWithTarget = 'http://angularjs.org/';\r
529            }]);\r
530        </script>\r
531        <div ng-controller="ExampleController">\r
532        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>\r
533        <table>\r
534          <tr>\r
535            <td>Filter</td>\r
536            <td>Source</td>\r
537            <td>Rendered</td>\r
538          </tr>\r
539          <tr id="linky-filter">\r
540            <td>linky filter</td>\r
541            <td>\r
542              <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>\r
543            </td>\r
544            <td>\r
545              <div ng-bind-html="snippet | linky"></div>\r
546            </td>\r
547          </tr>\r
548          <tr id="linky-target">\r
549           <td>linky target</td>\r
550           <td>\r
551             <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>\r
552           </td>\r
553           <td>\r
554             <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>\r
555           </td>\r
556          </tr>\r
557          <tr id="escaped-html">\r
558            <td>no filter</td>\r
559            <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>\r
560            <td><div ng-bind="snippet"></div></td>\r
561          </tr>\r
562        </table>\r
563      </file>\r
564      <file name="protractor.js" type="protractor">\r
565        it('should linkify the snippet with urls', function() {\r
566          expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).\r
567              toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +\r
568                   'another@somewhere.org, and one more: ftp://127.0.0.1/.');\r
569          expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);\r
570        });\r
571 \r
572        it('should not linkify snippet without the linky filter', function() {\r
573          expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).\r
574              toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +\r
575                   'another@somewhere.org, and one more: ftp://127.0.0.1/.');\r
576          expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);\r
577        });\r
578 \r
579        it('should update', function() {\r
580          element(by.model('snippet')).clear();\r
581          element(by.model('snippet')).sendKeys('new http://link.');\r
582          expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).\r
583              toBe('new http://link.');\r
584          expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);\r
585          expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())\r
586              .toBe('new http://link.');\r
587        });\r
588 \r
589        it('should work with the target property', function() {\r
590         expect(element(by.id('linky-target')).\r
591             element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).\r
592             toBe('http://angularjs.org/');\r
593         expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');\r
594        });\r
595      </file>\r
596    </example>\r
597  */\r
598 angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {\r
599   var LINKY_URL_REGEXP =\r
600         /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,\r
601       MAILTO_REGEXP = /^mailto:/;\r
602 \r
603   return function(text, target) {\r
604     if (!text) return text;\r
605     var match;\r
606     var raw = text;\r
607     var html = [];\r
608     var url;\r
609     var i;\r
610     while ((match = raw.match(LINKY_URL_REGEXP))) {\r
611       // We can not end in these as they are sometimes found at the end of the sentence\r
612       url = match[0];\r
613       // if we did not match ftp/http/mailto then assume mailto\r
614       if (match[2] == match[3]) url = 'mailto:' + url;\r
615       i = match.index;\r
616       addText(raw.substr(0, i));\r
617       addLink(url, match[0].replace(MAILTO_REGEXP, ''));\r
618       raw = raw.substring(i + match[0].length);\r
619     }\r
620     addText(raw);\r
621     return $sanitize(html.join(''));\r
622 \r
623     function addText(text) {\r
624       if (!text) {\r
625         return;\r
626       }\r
627       html.push(sanitizeText(text));\r
628     }\r
629 \r
630     function addLink(url, text) {\r
631       html.push('<a ');\r
632       if (angular.isDefined(target)) {\r
633         html.push('target="');\r
634         html.push(target);\r
635         html.push('" ');\r
636       }\r
637       html.push('href="');\r
638       html.push(url);\r
639       html.push('">');\r
640       addText(text);\r
641       html.push('</a>');\r
642     }\r
643   };\r
644 }]);\r
645 \r
646 \r
647 })(window, window.angular);\r