SYSTEM_WEB_EXTENSIONS:
[mono.git] / mcs / class / System.Web / System.Web.Handlers / AssemblyResourceLoader.cs
1 //
2 // System.Web.Handlers.AssemblyResourceLoader
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //
7 // (C) 2003 Ben Maurer
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Web.UI;
32 using System.Globalization;
33 using System.Reflection;
34 using System.IO;
35 using System.Resources;
36 using System.Collections;
37 using System.Security.Cryptography;
38 using System.Text;
39 using System.Text.RegularExpressions;
40 using System.Web.Configuration;
41
42 namespace System.Web.Handlers {
43 #if SYSTEM_WEB_EXTENSIONS
44         partial class ScriptResourceHandler
45         {
46                 const string HandlerFileName = "ScriptResource.axd";
47                 static Assembly currAsm = typeof (ScriptResourceHandler).Assembly;
48 #else
49         #if NET_2_0
50         public sealed
51         #else
52         internal // since this is in the .config file, we need to support it, since we dont have versoned support.
53         #endif
54         class AssemblyResourceLoader : IHttpHandler {           
55                 const string HandlerFileName = "WebResource.axd";
56                 static Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
57 #endif
58                 const char QueryParamSeparator = '&';
59
60                 static readonly Hashtable _embeddedResources = Hashtable.Synchronized (new Hashtable ());
61 #if SYSTEM_WEB_EXTENSIONS
62                 static ScriptResourceHandler () {
63                         MachineKeySectionUtils.AutoGenKeys ();
64                 }
65 #endif
66
67                 static void InitEmbeddedResourcesUrls (Assembly assembly, Hashtable hashtable)
68                 {
69                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
70                         for (int i = 0; i < attrs.Length; i++) {
71                                 string resourceName = attrs [i].WebResource;
72                                 if (resourceName != null && resourceName.Length > 0) {
73 #if SYSTEM_WEB_EXTENSIONS
74                                         hashtable.Add (new ResourceKey (resourceName, false), CreateResourceUrl (assembly, resourceName, false));
75                                         hashtable.Add (new ResourceKey (resourceName, true), CreateResourceUrl (assembly, resourceName, true));
76 #else
77                                         hashtable.Add (resourceName, CreateResourceUrl (assembly, resourceName, false));
78 #endif
79                                 }
80                         }
81                 }
82
83 #if !SYSTEM_WEB_EXTENSIONS
84                 internal static string GetResourceUrl (Type type, string resourceName)
85                 {
86                         return GetResourceUrl (type.Assembly, resourceName, false);
87                 }
88 #endif
89
90                 static string GetHexString (byte [] bytes)
91                 {
92                         const int letterPart = 55;
93                         const int numberPart = 48;
94                         char [] result = new char [bytes.Length * 2];
95                         for (int i = 0; i < bytes.Length; i++) {
96                                 int tmp = (int) bytes [i];
97                                 int second = tmp & 15;
98                                 int first = (tmp >> 4) & 15;
99                                 result [(i * 2)] = (char) (first > 9 ? letterPart + first : numberPart + first);
100                                 result [(i * 2) + 1] = (char) (second > 9 ? letterPart + second : numberPart + second);
101                         }
102                         return new string (result);
103                 }
104                 
105                 static byte[] GetEncryptionKey ()
106                 {
107 #if NET_2_0
108                         return MachineKeySectionUtils.DecryptionKey192Bits ();
109 #else
110                         MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
111                         return config.DecryptionKey192Bits;
112 #endif
113                 }
114
115                 static byte[] GetBytes (string val)
116                 {
117 #if NET_2_0
118                         return MachineKeySectionUtils.GetBytes (val, val.Length);
119 #else
120                         return MachineKeyConfig.GetBytes (val, val.Length);
121 #endif
122                 }               
123                 
124                 static byte [] init_vector = { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF };
125                 
126                 static string EncryptAssemblyResource (string asmName, string resName)
127                 {
128                         byte[] key = GetEncryptionKey ();
129                         byte[] bytes = Encoding.UTF8.GetBytes (String.Concat (asmName, ";", resName));
130                         string result;
131                         
132                         ICryptoTransform encryptor = TripleDES.Create ().CreateEncryptor (key, init_vector);
133                         result = GetHexString (encryptor.TransformFinalBlock (bytes, 0, bytes.Length));
134                         bytes = null;
135
136                         return String.Concat ("d=", result.ToLower (CultureInfo.InvariantCulture));
137                 }
138
139                 static void DecryptAssemblyResource (string val, out string asmName, out string resName)
140                 {
141                         byte[] key = GetEncryptionKey ();
142                         byte[] bytes = GetBytes (val);
143                         byte[] result;
144
145                         asmName = null;
146                         resName = null;                 
147
148                         ICryptoTransform decryptor = TripleDES.Create ().CreateDecryptor (key, init_vector);
149                         result = decryptor.TransformFinalBlock (bytes, 0, bytes.Length);
150                         bytes = null;
151
152                         string data = Encoding.UTF8.GetString (result);
153                         result = null;
154
155                         string[] parts = data.Split (';');
156                         if (parts.Length != 2)
157                                 return;
158                         
159                         asmName = parts [0];
160                         resName = parts [1];
161                 }
162
163                 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
164                 {
165                         Hashtable hashtable = (Hashtable)_embeddedResources [assembly];
166                         if (hashtable == null) {
167                                 hashtable = new Hashtable ();
168                                 InitEmbeddedResourcesUrls (assembly, hashtable);
169                                 _embeddedResources [assembly] = hashtable;
170                         }
171 #if SYSTEM_WEB_EXTENSIONS
172                         string url = (string) hashtable [new ResourceKey (resourceName, notifyScriptLoaded)];
173 #else
174                         string url = (string) hashtable [resourceName];
175 #endif
176                         if (url == null)
177                                 url = CreateResourceUrl (assembly, resourceName, notifyScriptLoaded);
178                         return url;
179                 }
180                 
181                 static string CreateResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
182                 {
183
184                         string aname = assembly == currAsm ? "s" : assembly.GetName ().FullName;
185                         string apath = assembly.Location;
186                         string atime = String.Empty;
187                         string extra = String.Empty;
188 #if SYSTEM_WEB_EXTENSIONS
189                         extra = String.Concat (QueryParamSeparator, "n=", notifyScriptLoaded ? "t" : "f");
190 #endif
191
192 #if TARGET_JVM
193                         atime = String.Format ("{0}t={1}", QueryParamSeparator, assembly.GetHashCode ());
194 #else
195                         if (apath != String.Empty)
196                                 atime = String.Concat (QueryParamSeparator, "t=", File.GetLastWriteTimeUtc (apath).Ticks);
197 #endif
198                         string href = HandlerFileName + "?" + EncryptAssemblyResource (aname, resourceName) + atime + extra;
199
200                         HttpContext ctx = HttpContext.Current;
201                         if (ctx != null && ctx.Request != null) {
202                                 string appPath = VirtualPathUtility.AppendTrailingSlash (ctx.Request.ApplicationPath);
203                                 href = appPath + href;
204                         }
205                         
206                         return href;
207                 }
208
209         
210 #if SYSTEM_WEB_EXTENSIONS
211                 protected virtual void ProcessRequest (HttpContext context)
212 #else
213                 [MonoTODO ("Substitution not implemented")]
214                 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
215 #endif
216                 {
217                         string resourceName;
218                         string asmName;
219                         Assembly assembly;
220
221                         DecryptAssemblyResource (context.Request.QueryString ["d"], out asmName, out resourceName);
222                         if (resourceName == null)
223                                 throw new HttpException (404, "No resource name given");
224                         
225                         if (asmName == null || asmName == "s")
226                                 assembly = currAsm;
227                         else
228                                 assembly = Assembly.Load (asmName);
229                         
230                         WebResourceAttribute wra = null;
231                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
232                         for (int i = 0; i < attrs.Length; i++) {
233                                 if (attrs [i].WebResource == resourceName) {
234                                         wra = attrs [i];
235                                         break;
236                                 }
237                         }
238 #if SYSTEM_WEB_EXTENSIONS
239                         if (wra == null && resourceName.Length > 9 && resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase)) {
240                                 resourceName = String.Concat (resourceName.Substring (0, resourceName.Length - 9), ".js");
241                                 for (int i = 0; i < attrs.Length; i++) {
242                                         if (attrs [i].WebResource == resourceName) {
243                                                 wra = attrs [i];
244                                                 break;
245                                         }
246                                 }
247                         }
248 #endif
249                         if (wra == null)
250                                 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
251                         
252                         context.Response.ContentType = wra.ContentType;
253
254                         /* tell the client they can cache resources for 1 year */
255                         context.Response.ExpiresAbsolute = DateTime.Now.AddYears (1);
256                         context.Response.CacheControl = "private";
257
258                         Stream s = assembly.GetManifestResourceStream (resourceName);
259                         if (s == null)
260                                 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
261
262                         if (wra.PerformSubstitution) {
263                                 StreamReader r = new StreamReader (s);
264                                 TextWriter w = context.Response.Output;
265                                 new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
266                         }
267                         else {
268                                 byte [] buf = new byte [1024];
269                                 Stream output = context.Response.OutputStream;
270                                 int c;
271                                 do {
272                                         c = s.Read (buf, 0, 1024);
273                                         output.Write (buf, 0, c);
274                                 } while (c > 0);
275                         }
276 #if SYSTEM_WEB_EXTENSIONS
277                         TextWriter writer = context.Response.Output;
278                         foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
279                                 if (sra.ScriptName == resourceName) {
280                                         string scriptResourceName = sra.ScriptResourceName;
281                                         ResourceSet rset = null;
282                                         try {
283                                                 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
284                                         }
285                                         catch (MissingManifestResourceException) {
286 #if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
287                                         }
288                                         if (rset == null) {
289 #endif
290                                                 if (scriptResourceName.EndsWith (".resources")) {
291                                                         scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
292                                                         rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
293                                                 }
294 #if !TARGET_JVM
295                                                 else
296                                                         throw;
297 #endif
298                                         }
299                                         if (rset == null)
300                                                 break;
301                                         writer.WriteLine ();
302                                         string ns = sra.TypeName;
303                                         int indx = ns.LastIndexOf ('.');
304                                         if (indx > 0)
305                                                 writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
306                                         writer.Write ("{0}={{", sra.TypeName);
307                                         bool first = true;
308                                         foreach (DictionaryEntry entry in rset) {
309                                                 string value = entry.Value as string;
310                                                 if (value != null) {
311                                                         if (first)
312                                                                 first = false;
313                                                         else
314                                                                 writer.Write (',');
315                                                         writer.WriteLine ();
316                                                         writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) entry.Key), GetScriptStringLiteral (value));
317                                                 }
318                                         }
319                                         writer.WriteLine ();
320                                         writer.WriteLine ("};");
321                                         break;
322                                 }
323                         }
324
325                         bool notifyScriptLoaded = context.Request.QueryString ["n"] == "t";
326                         if (notifyScriptLoaded) {
327                                 writer.WriteLine ();
328                                 writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
329                         }
330 #endif
331                 }
332
333                 sealed class PerformSubstitutionHelper
334                 {
335                         readonly Assembly _assembly;
336                         static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
337
338                         public PerformSubstitutionHelper (Assembly assembly) {
339                                 _assembly = assembly;
340                         }
341
342                         public void PerformSubstitution (TextReader reader, TextWriter writer) {
343                                 string line = reader.ReadLine ();
344                                 while (line != null) {
345                                         if (line.Length > 0 && _regex.IsMatch (line))
346                                                 line = _regex.Replace (line, new MatchEvaluator (PerformSubstitutionReplace));
347                                         writer.WriteLine (line);
348                                         line = reader.ReadLine ();
349                                 }
350                         }
351
352                         string PerformSubstitutionReplace (Match m) {
353                                 string resourceName = m.Groups [1].Value;
354 #if SYSTEM_WEB_EXTENSIONS
355                                 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
356 #else
357                                 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
358 #endif
359                         }
360                 }
361                 
362 #if !SYSTEM_WEB_EXTENSIONS
363                 bool System.Web.IHttpHandler.IsReusable { get { return true; } }
364 #endif
365         }
366 }
367