Merge pull request #1473 from esdrubal/sq
[mono.git] / mcs / class / System.Web.Extensions / System.Web.UI / ScriptReferenceBase.cs
1 //
2 // Author:
3 //   Marek Habersack <grendel@twistedcode.net>
4 //
5 // (C) 2009-2011 Novell, Inc.  http://novell.com/
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining
8 // a copy of this software and associated documentation files (the
9 // "Software"), to deal in the Software without restriction, including
10 // without limitation the rights to use, copy, modify, merge, publish,
11 // distribute, sublicense, and/or sell copies of the Software, and to
12 // permit persons to whom the Software is furnished to do so, subject to
13 // the following conditions:
14 // 
15 // The above copyright notice and this permission notice shall be
16 // included in all copies or substantial portions of the Software.
17 // 
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26 using System;
27 using System.Collections.Generic;
28 using System.Collections.Concurrent;
29 using System.IO;
30 using System.Text;
31 using System.ComponentModel;
32 using System.Reflection;
33 using System.Security.Permissions;
34 using System.Threading;
35 using System.Web.UI.WebControls;
36
37 namespace System.Web.UI
38 {
39         [AspNetHostingPermissionAttribute(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
40         [AspNetHostingPermissionAttribute(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
41         public abstract class ScriptReferenceBase
42         {
43                 static SplitOrderedList <ResourceCacheEntry, bool> resourceCache;
44                 string _path;
45                 
46                 public bool NotifyScriptLoaded {
47                         get; set;
48                 }
49
50                 public string Path {
51                         get { return _path != null ? _path : String.Empty; }
52                         set { _path = value; }
53                 }
54                 
55                 [TypeConverterAttribute(typeof(StringArrayConverter))]
56                 public string[] ResourceUICultures {
57                         get; set;
58                 }
59                 
60                 public ScriptMode ScriptMode {
61                         get; set;
62                 }
63
64                 internal static Assembly ThisAssembly {
65                         get; private set;
66                 }
67                 
68                 static ScriptReferenceBase ()
69                 {
70                         ThisAssembly = typeof (ScriptReferenceBase).Assembly;
71                         resourceCache = new SplitOrderedList <ResourceCacheEntry, bool> (EqualityComparer <ResourceCacheEntry>.Default);
72                 }
73                 
74                 protected ScriptReferenceBase ()
75                 {
76                         this.NotifyScriptLoaded = true;
77                         this.ScriptMode = ScriptMode.Auto;
78                 }
79 #if NET_4_0
80                 protected internal virtual bool IsAjaxFrameworkScript (ScriptManager scriptManager)
81                 {
82                         return false;
83                 }
84                 
85                 [Obsolete ("Use IsAjaxFrameworkScript(ScriptManager)")]
86 #endif
87                 protected internal abstract bool IsFromSystemWebExtensions ();
88                 protected internal abstract string GetUrl (ScriptManager scriptManager, bool zip);
89
90                 // This method is an example of particularily bad coding - .NET performs NO checks
91                 // on pathOrName!
92                 protected static string ReplaceExtension (string pathOrName)
93                 {
94                         // emulate .NET behavior
95                         if (pathOrName == null)
96                                 throw new NullReferenceException ();
97                         
98                         // We should check the length, but since .NET doesn't do that, we won't
99                         // either. Ugh.
100                         return pathOrName.Substring (0, pathOrName.Length - 2) + "debug.js";
101                 }
102
103                 internal static string GetScriptName (string releaseName, bool isDebugMode, string [] supportedUICultures, Assembly assembly, out WebResourceAttribute wra)
104                 {
105                         if (assembly != null)
106                                 VerifyAssemblyContainsResource (assembly, releaseName, out wra);
107                         else
108                                 wra = null;
109                         
110                         if (!isDebugMode && (supportedUICultures == null || supportedUICultures.Length == 0))
111                                 return releaseName;
112
113                         if (releaseName.Length < 3 || !releaseName.EndsWith (".js", StringComparison.OrdinalIgnoreCase))
114                                 throw new InvalidOperationException (String.Format ("'{0}' is not a valid script path.  The path must end in '.js'.", releaseName));
115                         
116                         StringBuilder sb = new StringBuilder (releaseName);
117                         sb.Length -= 3;
118                         if (isDebugMode)
119                                 sb.Append (".debug");
120                         string culture = Thread.CurrentThread.CurrentUICulture.Name;
121                         if (supportedUICultures != null && Array.IndexOf<string> (supportedUICultures, culture) >= 0)
122                                 sb.AppendFormat (".{0}", culture);
123                         sb.Append (".js");
124
125                         string ret = sb.ToString ();
126                         WebResourceAttribute debugWra;
127                         if (!CheckIfAssemblyContainsResource (assembly, ret, out debugWra))
128                                 return releaseName;
129                         wra = debugWra;
130                         
131                         return ret;
132                 }
133                 
134                 static void VerifyAssemblyContainsResource (Assembly assembly, string resourceName, out WebResourceAttribute wra)
135                 {
136                         var rce = new ResourceCacheEntry {
137                                 Assembly = assembly,
138                                 ResourceName = resourceName
139                         };
140
141                         WebResourceAttribute attr = null;
142                         if (!resourceCache.InsertOrGet ((uint)rce.GetHashCode (), rce, false, () => CheckIfAssemblyContainsResource (assembly, resourceName, out attr)))
143                                 throw new InvalidOperationException (String.Format ("Assembly '{0}' does not contain a Web resource with name '{1}'.",
144                                                                                     assembly.FullName, resourceName));
145                         wra = attr;
146                 }
147
148                 static bool CheckIfAssemblyContainsResource (Assembly assembly, string resourceName, out WebResourceAttribute wra)
149                 {
150                         foreach (WebResourceAttribute attr in assembly.GetCustomAttributes (typeof (WebResourceAttribute), false)) {
151                                 if (String.Compare (resourceName, attr.WebResource, StringComparison.Ordinal) == 0) {
152                                         using (Stream rs = assembly.GetManifestResourceStream (resourceName)) {
153                                                 if (rs == null)
154                                                         throw new InvalidOperationException (
155                                                                 String.Format ("Assembly '{0}' contains a Web resource with name '{1}' but does not contain an embedded resource with name '{1}'.",
156                                                                                assembly.FullName, resourceName)
157                                                         );
158                                         }
159                                         wra = attr;
160                                         return true;
161                                 
162                                 }
163                         }
164                         wra = null;
165                         return false;
166                 }
167
168                 sealed class ResourceCacheEntry
169                 {
170                         public Assembly Assembly;
171                         public string ResourceName;
172
173                         public override int GetHashCode ()
174                         {
175                                 int ret = 0;
176                                 if (Assembly != null)
177                                         ret ^= Assembly.GetHashCode ();
178                                 if (ResourceName != null)
179                                         ret ^= ResourceName.GetHashCode ();
180                                 return ret;
181                         }
182                 }
183         }
184 }