80a854b2d3a412bab3a64e775488fc130c79d77f
[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 #else
48         #if NET_2_0
49         public sealed
50         #else
51         internal // since this is in the .config file, we need to support it, since we dont have versoned support.
52         #endif
53         class AssemblyResourceLoader : IHttpHandler {           
54                 const string HandlerFileName = "WebResource.axd";
55 #endif
56                 const char QueryParamSeparator = '&';
57
58                 static readonly Hashtable _embeddedResources;
59 #if SYSTEM_WEB_EXTENSIONS
60         static ScriptResourceHandler()
61 #else
62                 static AssemblyResourceLoader()
63 #endif
64                 {
65                         _embeddedResources = new Hashtable ();
66                         InitEmbeddedResourcesUrls ();
67                 }
68
69                 static void InitEmbeddedResourcesUrls ()
70                 {
71                         Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
72                         WebResourceAttribute [] attrs = (WebResourceAttribute []) currAsm.GetCustomAttributes (typeof (WebResourceAttribute), false);
73                         for (int i = 0; i < attrs.Length; i++) {
74                                 string resourceName = attrs [i].WebResource;
75                                 if (resourceName != null && resourceName.Length > 0)
76                                         _embeddedResources.Add (resourceName, GetResourceUrl (currAsm, resourceName, false));
77                         }
78                 }
79
80                 internal static string GetResourceUrl (Type type, string resourceName)
81                 {
82                         string url = null;
83                         Assembly assembly = type.Assembly;
84                         
85                         if (assembly == typeof (AssemblyResourceLoader).Assembly)
86                                 url = (string) _embeddedResources [resourceName];
87
88                         return (url != null) ? url : GetResourceUrl (assembly, resourceName, false);
89                 }
90
91                 static string GetHexString (byte [] bytes)
92                 {
93                         const int letterPart = 55;
94                         const int numberPart = 48;
95                         char [] result = new char [bytes.Length * 2];
96                         for (int i = 0; i < bytes.Length; i++) {
97                                 int tmp = (int) bytes [i];
98                                 int second = tmp & 15;
99                                 int first = (tmp >> 4) & 15;
100                                 result [(i * 2)] = (char) (first > 9 ? letterPart + first : numberPart + first);
101                                 result [(i * 2) + 1] = (char) (second > 9 ? letterPart + second : numberPart + second);
102                         }
103                         return new string (result);
104                 }
105                 
106                 static byte[] GetEncryptionKey ()
107                 {
108 #if NET_2_0
109                         return MachineKeySectionUtils.DecryptionKey192Bits ();
110 #else
111                         MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
112                         return config.DecryptionKey192Bits;
113 #endif
114                 }
115
116                 static byte[] GetBytes (string val)
117                 {
118 #if NET_2_0
119                         return MachineKeySectionUtils.GetBytes (val, val.Length);
120 #else
121                         return MachineKeyConfig.GetBytes (val, val.Length);
122 #endif
123                 }               
124                 
125                 static byte [] init_vector = { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF };
126                 
127                 static string EncryptAssemblyResource (string asmName, string resName)
128                 {
129                         byte[] key = GetEncryptionKey ();
130                         byte[] bytes = Encoding.UTF8.GetBytes (String.Format ("{0};{1}", asmName, resName));
131                         string result;
132                         
133                         ICryptoTransform encryptor = TripleDES.Create ().CreateEncryptor (key, init_vector);
134                         result = GetHexString (encryptor.TransformFinalBlock (bytes, 0, bytes.Length));
135                         bytes = null;
136
137                         return String.Format ("d={0}", result.ToLower (CultureInfo.InvariantCulture));
138                 }
139
140                 static void DecryptAssemblyResource (string val, out string asmName, out string resName)
141                 {
142                         byte[] key = GetEncryptionKey ();
143                         byte[] bytes = GetBytes (val);
144                         byte[] result;
145
146                         asmName = null;
147                         resName = null;                 
148
149                         ICryptoTransform decryptor = TripleDES.Create ().CreateDecryptor (key, init_vector);
150                         result = decryptor.TransformFinalBlock (bytes, 0, bytes.Length);
151                         bytes = null;
152
153                         string data = Encoding.UTF8.GetString (result);
154                         result = null;
155
156                         string[] parts = data.Split (';');
157                         if (parts.Length != 2)
158                                 return;
159                         
160                         asmName = parts [0];
161                         resName = parts [1];
162                 }
163                 
164                 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
165                 {
166                         string aname = assembly == typeof (AssemblyResourceLoader).Assembly ? "s" : assembly.GetName ().FullName;
167                         string apath = assembly.Location;
168                         string atime = String.Empty;
169                         string extra = String.Empty;
170 #if SYSTEM_WEB_EXTENSIONS
171                         extra = String.Format ("{0}n={1}", QueryParamSeparator, notifyScriptLoaded ? "t" : "f");
172 #endif
173
174 #if TARGET_JVM
175                         atime = String.Format ("{0}t={1}", QueryParamSeparator, assembly.GetHashCode ());
176 #else
177                         if (apath != String.Empty)
178                                 atime = String.Format ("{0}t={1}", QueryParamSeparator, File.GetLastWriteTimeUtc (apath).Ticks);
179 #endif
180                         string href = String.Format ("{0}?{1}{2}{3}", HandlerFileName,
181                                                      EncryptAssemblyResource (aname, resourceName), atime, extra);
182
183                         HttpContext ctx = HttpContext.Current;
184                         if (ctx != null && ctx.Request != null) {
185                                 string appPath = VirtualPathUtility.AppendTrailingSlash (ctx.Request.ApplicationPath);
186                                 href = appPath + href;
187                         }
188                         
189                         return href;
190                 }
191
192         
193 #if SYSTEM_WEB_EXTENSIONS
194                 protected virtual void ProcessRequest (HttpContext context)
195 #else
196                 [MonoTODO ("Substitution not implemented")]
197                 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
198 #endif
199                 {
200                         string resourceName;
201                         string asmName;
202                         Assembly assembly;
203
204                         DecryptAssemblyResource (context.Request.QueryString ["d"], out asmName, out resourceName);
205                         if (resourceName == null)
206                                 throw new HttpException (404, "No resource name given");
207                         
208                         if (asmName == null || asmName == "s")
209                                 assembly = typeof (AssemblyResourceLoader).Assembly;
210                         else
211                                 assembly = Assembly.Load (asmName);
212                         
213                         WebResourceAttribute wra = null;
214                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
215                         for (int i = 0; i < attrs.Length; i++) {
216                                 if (attrs [i].WebResource == resourceName) {
217                                         wra = attrs [i];
218                                         break;
219                                 }
220                         }
221 #if SYSTEM_WEB_EXTENSIONS
222                         if (wra == null && resourceName.Length > 9 && resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase)) {
223                                 resourceName = String.Concat (resourceName.Substring (0, resourceName.Length - 9), ".js");
224                                 for (int i = 0; i < attrs.Length; i++) {
225                                         if (attrs [i].WebResource == resourceName) {
226                                                 wra = attrs [i];
227                                                 break;
228                                         }
229                                 }
230                         }
231 #endif
232                         if (wra == null)
233                                 throw new HttpException (404, string.Format ("Resource {0} not found", resourceName));
234                         
235                         context.Response.ContentType = wra.ContentType;
236
237                         /* tell the client they can cache resources for 1 year */
238                         context.Response.ExpiresAbsolute = DateTime.Now.AddYears (1);
239                         context.Response.CacheControl = "private";
240
241                         Stream s = assembly.GetManifestResourceStream (resourceName);
242                         if (s == null)
243                                 throw new HttpException (404, string.Format ("Resource {0} not found", resourceName));
244
245                         if (wra.PerformSubstitution) {
246                                 StreamReader r = new StreamReader (s);
247                                 TextWriter w = context.Response.Output;
248                                 new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
249                         }
250                         else {
251                                 byte [] buf = new byte [1024];
252                                 Stream output = context.Response.OutputStream;
253                                 int c;
254                                 do {
255                                         c = s.Read (buf, 0, 1024);
256                                         output.Write (buf, 0, c);
257                                 } while (c > 0);
258                         }
259 #if SYSTEM_WEB_EXTENSIONS
260                         TextWriter writer = context.Response.Output;
261                         foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
262                                 if (sra.ScriptName == resourceName) {
263                                         string scriptResourceName = sra.ScriptResourceName;
264                                         ResourceSet rset = null;
265                                         try {
266                                                 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
267                                         }
268                                         catch (MissingManifestResourceException) {
269 #if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
270                                         }
271                                         if (rset == null) {
272 #endif
273                                                 if (scriptResourceName.EndsWith (".resources")) {
274                                                         scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
275                                                         rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
276                                                 }
277 #if !TARGET_JVM
278                                                 else
279                                                         throw;
280 #endif
281                                         }
282                                         if (rset == null)
283                                                 break;
284                                         writer.WriteLine ();
285                                         writer.Write ("{0}={{", sra.TypeName);
286                                         bool first = true;
287                                         foreach (DictionaryEntry entry in rset) {
288                                                 string value = entry.Value as string;
289                                                 if (value != null) {
290                                                         if (first)
291                                                                 first = false;
292                                                         else
293                                                                 writer.Write (',');
294                                                         writer.WriteLine ();
295                                                         writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) entry.Key), GetScriptStringLiteral (value));
296                                                 }
297                                         }
298                                         writer.WriteLine ();
299                                         writer.WriteLine ("};");
300                                         break;
301                                 }
302                         }
303
304                         bool notifyScriptLoaded = context.Request.QueryString ["n"] == "t";
305                         if (notifyScriptLoaded) {
306                                 writer.WriteLine ();
307                                 writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
308                         }
309 #endif
310                 }
311
312                 sealed class PerformSubstitutionHelper
313                 {
314                         readonly Assembly _assembly;
315                         static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
316
317                         public PerformSubstitutionHelper (Assembly assembly) {
318                                 _assembly = assembly;
319                         }
320
321                         public void PerformSubstitution (TextReader reader, TextWriter writer) {
322                                 string line = reader.ReadLine ();
323                                 while (line != null) {
324                                         if (line.Length > 0 && _regex.IsMatch (line))
325                                                 line = _regex.Replace (line, new MatchEvaluator (PerformSubstitutionReplace));
326                                         writer.WriteLine (line);
327                                         line = reader.ReadLine ();
328                                 }
329                         }
330
331                         string PerformSubstitutionReplace (Match m) {
332                                 string resourceName = m.Groups [1].Value;
333 #if SYSTEM_WEB_EXTENSIONS
334                                 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
335 #else
336                                 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
337 #endif
338                         }
339                 }
340                 
341 #if !SYSTEM_WEB_EXTENSIONS
342                 bool System.Web.IHttpHandler.IsReusable { get { return true; } }
343 #endif
344         }
345 }
346