2 // System.Web.Handlers.AssemblyResourceLoader
5 // Ben Maurer (bmaurer@users.sourceforge.net)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System.Globalization;
33 using System.Reflection;
35 using System.Resources;
36 using System.Collections;
37 using System.Security.Cryptography;
39 using System.Text.RegularExpressions;
40 using System.Web.Configuration;
42 namespace System.Web.Handlers {
43 #if SYSTEM_WEB_EXTENSIONS
44 partial class ScriptResourceHandler
46 const string HandlerFileName = "ScriptResource.axd";
47 static Assembly currAsm = typeof (ScriptResourceHandler).Assembly;
52 internal // since this is in the .config file, we need to support it, since we dont have versoned support.
54 class AssemblyResourceLoader : IHttpHandler {
55 const string HandlerFileName = "WebResource.axd";
56 static Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
58 const char QueryParamSeparator = '&';
60 static readonly Hashtable _embeddedResources = Hashtable.Synchronized (new Hashtable ());
61 #if SYSTEM_WEB_EXTENSIONS
62 static ScriptResourceHandler () {
63 MachineKeySectionUtils.AutoGenKeys ();
67 static void InitEmbeddedResourcesUrls (Assembly assembly, Hashtable hashtable)
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);
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));
82 if (!hashtable.Contains (resourceName))
83 hashtable.Add (resourceName, CreateResourceUrl (assembly, resourceName, false));
89 #if !SYSTEM_WEB_EXTENSIONS
90 internal static string GetResourceUrl (Type type, string resourceName)
92 return GetResourceUrl (type.Assembly, resourceName, false);
96 static string GetHexString (byte [] bytes)
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);
108 return new string (result);
111 static byte[] GetEncryptionKey ()
114 return MachineKeySectionUtils.DecryptionKey192Bits ();
116 MachineKeyConfig config = HttpContext.GetAppConfig ("system.web/machineKey") as MachineKeyConfig;
117 return config.DecryptionKey192Bits;
121 static byte[] GetBytes (string val)
124 return MachineKeySectionUtils.GetBytes (val, val.Length);
126 return MachineKeyConfig.GetBytes (val, val.Length);
130 static byte [] init_vector = { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF };
132 static string EncryptAssemblyResource (string asmName, string resName)
134 byte[] key = GetEncryptionKey ();
135 byte[] bytes = Encoding.UTF8.GetBytes (String.Concat (asmName, ";", resName));
138 ICryptoTransform encryptor = TripleDES.Create ().CreateEncryptor (key, init_vector);
139 result = GetHexString (encryptor.TransformFinalBlock (bytes, 0, bytes.Length));
142 return String.Concat ("d=", result.ToLower (CultureInfo.InvariantCulture));
145 static void DecryptAssemblyResource (string val, out string asmName, out string resName)
147 byte[] key = GetEncryptionKey ();
148 byte[] bytes = GetBytes (val);
154 ICryptoTransform decryptor = TripleDES.Create ().CreateDecryptor (key, init_vector);
155 result = decryptor.TransformFinalBlock (bytes, 0, bytes.Length);
158 string data = Encoding.UTF8.GetString (result);
161 string[] parts = data.Split (';');
162 if (parts.Length != 2)
169 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
171 Hashtable hashtable = (Hashtable)_embeddedResources [assembly];
172 if (hashtable == null) {
173 hashtable = new Hashtable ();
174 InitEmbeddedResourcesUrls (assembly, hashtable);
175 _embeddedResources [assembly] = hashtable;
177 #if SYSTEM_WEB_EXTENSIONS
178 string url = (string) hashtable [new ResourceKey (resourceName, notifyScriptLoaded)];
180 string url = (string) hashtable [resourceName];
183 url = CreateResourceUrl (assembly, resourceName, notifyScriptLoaded);
187 static string CreateResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
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");
199 atime = String.Format ("{0}t={1}", QueryParamSeparator, assembly.GetHashCode ());
201 if (apath != String.Empty)
202 atime = String.Concat (QueryParamSeparator, "t=", File.GetLastWriteTimeUtc (apath).Ticks);
204 string href = HandlerFileName + "?" + EncryptAssemblyResource (aname, resourceName) + atime + extra;
206 HttpContext ctx = HttpContext.Current;
207 if (ctx != null && ctx.Request != null) {
208 string appPath = VirtualPathUtility.AppendTrailingSlash (ctx.Request.ApplicationPath);
209 href = appPath + href;
215 #if SYSTEM_WEB_EXTENSIONS
216 protected virtual void ProcessRequest (HttpContext context)
218 [MonoTODO ("Substitution not implemented")]
219 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
222 HttpRequest request = context.Request;
223 HttpResponse response = context.Response;
228 DecryptAssemblyResource (request.QueryString ["d"], out asmName, out resourceName);
229 if (resourceName == null)
230 throw new HttpException (404, "No resource name given");
232 if (asmName == null || asmName == "s")
235 assembly = Assembly.Load (asmName);
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) {
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) {
257 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
259 string req_cache = request.Headers ["Cache-Control"];
260 if (req_cache == "max-age=0") {
263 if (Int64.TryParse (request.QueryString ["t"], out atime)) {
267 atime = Int64.Parse (request.QueryString ["t"]);
271 if (atime == File.GetLastWriteTimeUtc (assembly.Location).Ticks) {
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 ();
281 string modif_since = request.Headers ["If-Modified-Since"];
282 if (modif_since != null && modif_since != "") {
286 if (DateTime.TryParseExact (modif_since, "r", null, 0, out modif))
288 modif = DateTime.MinValue;
290 modif = DateTime.ParseExact (modif_since, "r", null, 0);
292 if (modif != DateTime.MinValue)
294 if (File.GetLastWriteTimeUtc (assembly.Location) <= modif) {
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 ();
305 response.ContentType = wra.ContentType;
307 DateTime utcnow = DateTime.UtcNow;
308 response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));
309 response.ExpiresAbsolute = utcnow.AddYears (1);
310 response.CacheControl = "public";
312 Stream s = assembly.GetManifestResourceStream (resourceName);
314 throw new HttpException (404, String.Concat ("Resource ", resourceName, " not found"));
316 if (wra.PerformSubstitution) {
317 using (StreamReader r = new StreamReader (s)) {
318 TextWriter w = response.Output;
319 new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
322 } else if (response.OutputStream is HttpResponseStream) {
323 UnmanagedMemoryStream st = (UnmanagedMemoryStream) s;
324 HttpResponseStream hstream = (HttpResponseStream) response.OutputStream;
326 hstream.WritePtr (new IntPtr (st.PositionPointer), (int) st.Length);
330 byte [] buf = new byte [1024];
331 Stream output = response.OutputStream;
334 c = s.Read (buf, 0, 1024);
335 output.Write (buf, 0, c);
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;
345 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
347 catch (MissingManifestResourceException) {
348 #if TARGET_JVM // GetResourceSet does not throw MissingManifestResourceException if ressource is not exists
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);
364 string ns = sra.TypeName;
365 int indx = ns.LastIndexOf ('.');
367 writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
368 writer.Write ("{0}={{", sra.TypeName);
370 foreach (DictionaryEntry entry in rset) {
371 string value = entry.Value as string;
378 writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) entry.Key), GetScriptStringLiteral (value));
382 writer.WriteLine ("};");
387 bool notifyScriptLoaded = request.QueryString ["n"] == "t";
388 if (notifyScriptLoaded) {
390 writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
395 sealed class PerformSubstitutionHelper
397 readonly Assembly _assembly;
398 static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
400 public PerformSubstitutionHelper (Assembly assembly) {
401 _assembly = assembly;
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 ();
414 string PerformSubstitutionReplace (Match m) {
415 string resourceName = m.Groups [1].Value;
416 #if SYSTEM_WEB_EXTENSIONS
417 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
419 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
424 #if !SYSTEM_WEB_EXTENSIONS
425 bool System.Web.IHttpHandler.IsReusable { get { return true; } }