2009-07-08 Gonzalo Paniagua Javier <gonzalo@novell.com>
[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                                         ResourceKey rkNoNotify = new ResourceKey (resourceName, false);
75                                         ResourceKey rkNotify = new ResourceKey (resourceName, true);
76
77                                         if (!hashtable.Contains (rkNoNotify))
78                                                 hashtable.Add (rkNoNotify, CreateResourceUrl (assembly, resourceName, false));
79                                         if (!hashtable.Contains (rkNotify))
80                                                 hashtable.Add (rkNotify, CreateResourceUrl (assembly, resourceName, true));
81 #else
82                                         if (!hashtable.Contains (resourceName))
83                                                 hashtable.Add (resourceName, CreateResourceUrl (assembly, resourceName, false));
84 #endif
85                                 }
86                         }
87                 }
88
89 #if !SYSTEM_WEB_EXTENSIONS
90                 internal static string GetResourceUrl (Type type, string resourceName)
91                 {
92                         return GetResourceUrl (type.Assembly, resourceName, false);
93                 }
94 #endif
95
96                 static string GetHexString (byte [] bytes)
97                 {
98                         const int letterPart = 55;
99                         const int numberPart = 48;
100                         char [] result = new char [bytes.Length * 2];
101                         for (int i = 0; i < bytes.Length; i++) {
102                                 int tmp = (int) bytes [i];
103                                 int second = tmp & 15;
104                                 int first = (tmp >> 4) & 15;
105                                 result [(i * 2)] = (char) (first > 9 ? letterPart + first : numberPart + first);
106                                 result [(i * 2) + 1] = (char) (second > 9 ? letterPart + second : numberPart + second);
107                         }
108                         return new string (result);
109                 }
110                 
111                 static byte[] GetEncryptionKey ()
112                 {
113 #if NET_2_0
114                         return MachineKeySectionUtils.DecryptionKey192Bits ();
115 #else
116                         MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
117                         return config.DecryptionKey192Bits;
118 #endif
119                 }
120
121                 static byte[] GetBytes (string val)
122                 {
123 #if NET_2_0
124                         return MachineKeySectionUtils.GetBytes (val, val.Length);
125 #else
126                         return MachineKeyConfig.GetBytes (val, val.Length);
127 #endif
128                 }               
129                 
130                 static byte [] init_vector = { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF };
131                 
132                 static string EncryptAssemblyResource (string asmName, string resName)
133                 {
134                         byte[] key = GetEncryptionKey ();
135                         byte[] bytes = Encoding.UTF8.GetBytes (String.Concat (asmName, ";", resName));
136                         string result;
137                         
138                         ICryptoTransform encryptor = TripleDES.Create ().CreateEncryptor (key, init_vector);
139                         result = GetHexString (encryptor.TransformFinalBlock (bytes, 0, bytes.Length));
140                         bytes = null;
141
142                         return String.Concat ("d=", result.ToLower (CultureInfo.InvariantCulture));
143                 }
144
145                 static void DecryptAssemblyResource (string val, out string asmName, out string resName)
146                 {
147                         byte[] key = GetEncryptionKey ();
148                         byte[] bytes = GetBytes (val);
149                         byte[] result;
150
151                         asmName = null;
152                         resName = null;                 
153
154                         ICryptoTransform decryptor = TripleDES.Create ().CreateDecryptor (key, init_vector);
155                         result = decryptor.TransformFinalBlock (bytes, 0, bytes.Length);
156                         bytes = null;
157
158                         string data = Encoding.UTF8.GetString (result);
159                         result = null;
160
161                         string[] parts = data.Split (';');
162                         if (parts.Length != 2)
163                                 return;
164                         
165                         asmName = parts [0];
166                         resName = parts [1];
167                 }
168
169                 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
170                 {
171                         Hashtable hashtable = (Hashtable)_embeddedResources [assembly];
172                         if (hashtable == null) {
173                                 hashtable = new Hashtable ();
174                                 InitEmbeddedResourcesUrls (assembly, hashtable);
175                                 _embeddedResources [assembly] = hashtable;
176                         }
177 #if SYSTEM_WEB_EXTENSIONS
178                         string url = (string) hashtable [new ResourceKey (resourceName, notifyScriptLoaded)];
179 #else
180                         string url = (string) hashtable [resourceName];
181 #endif
182                         if (url == null)
183                                 url = CreateResourceUrl (assembly, resourceName, notifyScriptLoaded);
184                         return url;
185                 }
186                 
187                 static string CreateResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
188                 {
189
190                         string aname = assembly == currAsm ? "s" : assembly.GetName ().FullName;
191                         string apath = assembly.Location;
192                         string atime = String.Empty;
193                         string extra = String.Empty;
194 #if SYSTEM_WEB_EXTENSIONS
195                         extra = String.Concat (QueryParamSeparator, "n=", notifyScriptLoaded ? "t" : "f");
196 #endif
197
198 #if TARGET_JVM
199                         atime = String.Format ("{0}t={1}", QueryParamSeparator, assembly.GetHashCode ());
200 #else
201                         if (apath != String.Empty)
202                                 atime = String.Concat (QueryParamSeparator, "t=", File.GetLastWriteTimeUtc (apath).Ticks);
203 #endif
204                         string href = HandlerFileName + "?" + EncryptAssemblyResource (aname, resourceName) + atime + extra;
205
206                         HttpContext ctx = HttpContext.Current;
207                         if (ctx != null && ctx.Request != null) {
208                                 string appPath = VirtualPathUtility.AppendTrailingSlash (ctx.Request.ApplicationPath);
209                                 href = appPath + href;
210                         }
211                         
212                         return href;
213                 }
214
215 #if SYSTEM_WEB_EXTENSIONS
216                 protected virtual void ProcessRequest (HttpContext context)
217 #else
218                 [MonoTODO ("Substitution not implemented")]
219                 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
220 #endif
221                 {
222                         HttpRequest request = context.Request;
223                         HttpResponse response = context.Response;
224                         string resourceName;
225                         string asmName;
226                         Assembly assembly;
227
228                         DecryptAssemblyResource (request.QueryString ["d"], out asmName, out resourceName);
229                         if (resourceName == null)
230                                 throw new HttpException (404, "No resource name given");
231
232                         if (asmName == null || asmName == "s")
233                                 assembly = currAsm;
234                         else
235                                 assembly = Assembly.Load (asmName);
236
237                         WebResourceAttribute wra = null;
238                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
239                         for (int i = 0; i < attrs.Length; i++) {
240                                 if (attrs [i].WebResource == resourceName) {
241                                         wra = attrs [i];
242                                         break;
243                                 }
244                         }
245 #if SYSTEM_WEB_EXTENSIONS
246                         if (wra == null && resourceName.Length > 9 && resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase)) {
247                                 resourceName = String.Concat (resourceName.Substring (0, resourceName.Length - 9), ".js");
248                                 for (int i = 0; i < attrs.Length; i++) {
249                                         if (attrs [i].WebResource == resourceName) {
250                                                 wra = attrs [i];
251                                                 break;
252                                         }
253                                 }
254                         }
255 #endif
256                         if (wra == null)
257                                 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
258                         
259                         string req_cache = request.Headers ["Cache-Control"];
260                         if (req_cache == "max-age=0") {
261                                 long atime;
262 #if NET_2_0
263                                 if (Int64.TryParse (request.QueryString ["t"], out atime)) {
264 #else
265                                 atime = -1;
266                                 try {
267                                         atime = Int64.Parse (request.QueryString ["t"]);
268                                 } catch {}
269                                 if (atime > -1) {
270 #endif
271                                         if (atime == File.GetLastWriteTimeUtc (assembly.Location).Ticks) {
272                                                 response.Clear ();
273                                                 response.StatusCode = 304;
274                                                 response.ContentType = null;
275                                                 response.CacheControl = "public"; // easier to set it to public as MS than remove it
276                                                 context.ApplicationInstance.CompleteRequest ();
277                                                 return;
278                                         }
279                                 }
280                         }
281                         string modif_since = request.Headers ["If-Modified-Since"];
282                         if (modif_since != null && modif_since != "") {
283                                 try {
284                                         DateTime modif;
285 #if NET_2_0
286                                         if (DateTime.TryParseExact (modif_since, "r", null, 0, out modif))
287 #else
288                                         modif = DateTime.MinValue;
289                                         try {
290                                                 modif = DateTime.ParseExact (modif_since, "r", null, 0);
291                                         } catch { }
292                                         if (modif != DateTime.MinValue)
293 #endif
294                                                 if (File.GetLastWriteTimeUtc (assembly.Location) <= modif) {
295                                                         response.Clear ();
296                                                         response.StatusCode = 304;
297                                                         response.ContentType = null;
298                                                         response.CacheControl = "public"; // easier to set it to public as MS than remove it
299                                                         context.ApplicationInstance.CompleteRequest ();
300                                                         return;
301                                                 }
302                                 } catch {}
303                         }
304
305                         response.ContentType = wra.ContentType;
306
307                         DateTime utcnow = DateTime.UtcNow;
308                         response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));
309                         response.ExpiresAbsolute = utcnow.AddYears (1);
310                         response.CacheControl = "public";
311
312                         Stream s = assembly.GetManifestResourceStream (resourceName);
313                         if (s == null)
314                                 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
315
316                         if (wra.PerformSubstitution) {
317                                 using (StreamReader r = new StreamReader (s)) {
318                                         TextWriter w = response.Output;
319                                         new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
320                                 }
321 #if NET_2_0
322                         } else if (response.OutputStream is HttpResponseStream) {
323                                 UnmanagedMemoryStream st = (UnmanagedMemoryStream) s;
324                                 HttpResponseStream hstream = (HttpResponseStream) response.OutputStream;
325                                 unsafe {
326                                         hstream.WritePtr (new IntPtr (st.PositionPointer), (int) st.Length);
327                                 }
328 #endif
329                         } else {
330                                 byte [] buf = new byte [1024];
331                                 Stream output = response.OutputStream;
332                                 int c;
333                                 do {
334                                         c = s.Read (buf, 0, 1024);
335                                         output.Write (buf, 0, c);
336                                 } while (c > 0);
337                         }
338 #if SYSTEM_WEB_EXTENSIONS
339                         TextWriter writer = response.Output;
340                         foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
341                                 if (sra.ScriptName == resourceName) {
342                                         string scriptResourceName = sra.ScriptResourceName;
343                                         ResourceSet rset = null;
344                                         try {
345                                                 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
346                                         }
347                                         catch (MissingManifestResourceException) {
348 #if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
349                                         }
350                                         if (rset == null) {
351 #endif
352                                                 if (scriptResourceName.EndsWith (".resources")) {
353                                                         scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
354                                                         rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
355                                                 }
356 #if !TARGET_JVM
357                                                 else
358                                                         throw;
359 #endif
360                                         }
361                                         if (rset == null)
362                                                 break;
363                                         writer.WriteLine ();
364                                         string ns = sra.TypeName;
365                                         int indx = ns.LastIndexOf ('.');
366                                         if (indx > 0)
367                                                 writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
368                                         writer.Write ("{0}={{", sra.TypeName);
369                                         bool first = true;
370                                         foreach (DictionaryEntry entry in rset) {
371                                                 string value = entry.Value as string;
372                                                 if (value != null) {
373                                                         if (first)
374                                                                 first = false;
375                                                         else
376                                                                 writer.Write (',');
377                                                         writer.WriteLine ();
378                                                         writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) entry.Key), GetScriptStringLiteral (value));
379                                                 }
380                                         }
381                                         writer.WriteLine ();
382                                         writer.WriteLine ("};");
383                                         break;
384                                 }
385                         }
386
387                         bool notifyScriptLoaded = request.QueryString ["n"] == "t";
388                         if (notifyScriptLoaded) {
389                                 writer.WriteLine ();
390                                 writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
391                         }
392 #endif
393                 }
394
395                 sealed class PerformSubstitutionHelper
396                 {
397                         readonly Assembly _assembly;
398                         static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
399
400                         public PerformSubstitutionHelper (Assembly assembly) {
401                                 _assembly = assembly;
402                         }
403
404                         public void PerformSubstitution (TextReader reader, TextWriter writer) {
405                                 string line = reader.ReadLine ();
406                                 while (line != null) {
407                                         if (line.Length > 0 && _regex.IsMatch (line))
408                                                 line = _regex.Replace (line, new MatchEvaluator (PerformSubstitutionReplace));
409                                         writer.WriteLine (line);
410                                         line = reader.ReadLine ();
411                                 }
412                         }
413
414                         string PerformSubstitutionReplace (Match m) {
415                                 string resourceName = m.Groups [1].Value;
416 #if SYSTEM_WEB_EXTENSIONS
417                                 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
418 #else
419                                 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
420 #endif
421                         }
422                 }
423                 
424 #if !SYSTEM_WEB_EXTENSIONS
425                 bool System.Web.IHttpHandler.IsReusable { get { return true; } }
426 #endif
427         }
428 }
429