//\r
// ScriptResourceHandler.cs\r
//\r
-// Author:\r
+// Authors:\r
// Igor Zelmanovich <igorz@mainsoft.com>\r
+// Marek Habersack <grendel@twistedcode.net>\r
//\r
// (C) 2007 Mainsoft, Inc. http://www.mainsoft.com\r
+// (C) 2011 Novell, Inc. http://novell.com\r
//\r
//\r
// Permission is hereby granted, free of charge, to any person obtaining\r
//\r
\r
using System;\r
+using System.Collections;\r
using System.Collections.Generic;\r
+using System.IO;\r
+using System.Security.Cryptography;\r
+using System.Reflection;\r
+using System.Resources;\r
using System.Text;\r
+using System.Threading;\r
+using System.Web.Configuration;\r
+using System.Web.Hosting;\r
+using System.Web.UI;\r
+using System.Web.Util;\r
\r
namespace System.Web.Handlers\r
{\r
public partial class ScriptResourceHandler : IHttpHandler\r
- {\r
+ { \r
protected virtual bool IsReusable {\r
get { return true; }\r
}\r
}\r
\r
#endregion\r
+ void AppendResourceScriptContents (StringWriter sw, CompositeEntry entry)\r
+ {\r
+ if (entry.Assembly == null || entry.Attribute == null || String.IsNullOrEmpty (entry.NameOrPath))\r
+ return;\r
+\r
+ using (Stream s = entry.Assembly.GetManifestResourceStream (entry.NameOrPath)) {\r
+ if (s == null)\r
+ throw new HttpException (404, "Resource '" + entry.NameOrPath + "' not found");\r
+\r
+ if (entry.Attribute.PerformSubstitution) {\r
+ using (var r = new StreamReader (s)) {\r
+ new PerformSubstitutionHelper (entry.Assembly).PerformSubstitution (r, sw);\r
+ }\r
+ } else {\r
+ using (var r = new StreamReader (s)) {\r
+ string line = r.ReadLine ();\r
+ while (line != null) {\r
+ sw.WriteLine (line);\r
+ line = r.ReadLine ();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ void AppendFileScriptContents (StringWriter sw, CompositeEntry entry)\r
+ {\r
+ // FIXME: should we limit the script size in any way?\r
+ if (String.IsNullOrEmpty (entry.NameOrPath))\r
+ return;\r
+\r
+ string mappedPath;\r
+ if (!HostingEnvironment.HaveCustomVPP) {\r
+ // We'll take a shortcut here by bypassing the default VPP layers\r
+ mappedPath = HostingEnvironment.MapPath (entry.NameOrPath);\r
+ if (!File.Exists (mappedPath))\r
+ return;\r
+ sw.Write (File.ReadAllText (mappedPath));\r
+ return;\r
+ }\r
+\r
+ VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;\r
+ if (!vpp.FileExists (entry.NameOrPath))\r
+ return;\r
+ VirtualFile file = vpp.GetFile (entry.NameOrPath);\r
+ if (file == null)\r
+ return;\r
+ using (Stream s = file.Open ()) {\r
+ using (var r = new StreamReader (s)) {\r
+ string line = r.ReadLine ();\r
+ while (line != null) {\r
+ sw.WriteLine (line);\r
+ line = r.ReadLine ();\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ void AppendScriptContents (StringWriter sw, CompositeEntry entry)\r
+ {\r
+ if (entry.Assembly != null)\r
+ AppendResourceScriptContents (sw, entry);\r
+ else\r
+ AppendFileScriptContents (sw, entry);\r
+ }\r
+ \r
+ void SendCompositeScript (HttpContext context, HttpRequest request, bool notifyScriptLoaded, List <CompositeEntry> entries)\r
+ {\r
+ if (entries.Count == 0)\r
+ throw new HttpException (404, "Resource not found");\r
+\r
+ long atime;\r
+ DateTime modifiedSince;\r
+ bool hasIfModifiedSince = HasIfModifiedSince (context.Request, out modifiedSince);\r
+ \r
+ if (hasIfModifiedSince) {\r
+ bool notModified = true;\r
+ \r
+ foreach (CompositeEntry entry in entries) {\r
+ if (entry == null)\r
+ continue;\r
+ if (notModified) {\r
+ if (hasIfModifiedSince && entry.IsModifiedSince (modifiedSince))\r
+ notModified = false;\r
+ }\r
+ }\r
+\r
+ if (notModified) {\r
+ RespondWithNotModified (context);\r
+ return;\r
+ }\r
+ }\r
+ \r
+ StringBuilder contents = new StringBuilder ();\r
+ using (var sw = new StringWriter (contents)) {\r
+ foreach (CompositeEntry entry in entries) {\r
+ if (entry == null)\r
+ continue;\r
+ AppendScriptContents (sw, entry);\r
+ }\r
+ }\r
+ if (contents.Length == 0)\r
+ throw new HttpException (404, "Resource not found");\r
+\r
+ HttpResponse response = context.Response;\r
+ DateTime utcnow = DateTime.UtcNow;\r
+\r
+ response.ContentType = "text/javascript";\r
+ response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));\r
+ response.ExpiresAbsolute = utcnow.AddYears (1);\r
+ response.CacheControl = "public";\r
+\r
+ response.Output.Write (contents.ToString ());\r
+ if (notifyScriptLoaded)\r
+ OutputScriptLoadedNotification (response.Output);\r
+ }\r
+ void OutputScriptLoadedNotification (TextWriter writer)\r
+ {\r
+ writer.WriteLine ();\r
+ writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");\r
+ }\r
+ \r
+ protected virtual void ProcessRequest (HttpContext context)\r
+ {\r
+ HttpRequest request = context.Request;\r
+ bool notifyScriptLoaded = request.QueryString ["n"] == "t";\r
+ List <CompositeEntry> compositeEntries = CompositeScriptReference.GetCompositeScriptEntries (request.RawUrl);\r
+ if (compositeEntries != null) {\r
+ SendCompositeScript (context, request, notifyScriptLoaded, compositeEntries);\r
+ return;\r
+ }\r
+ EmbeddedResource res;\r
+ Assembly assembly; \r
+ SendEmbeddedResource (context, out res, out assembly);\r
+\r
+ HttpResponse response = context.Response;\r
+ TextWriter writer = response.Output;\r
+ foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {\r
+ if (String.Compare (sra.ScriptName, res.Name, StringComparison.Ordinal) == 0) {\r
+ string scriptResourceName = sra.ScriptResourceName;\r
+ ResourceSet rset = null;\r
+ try {\r
+ rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);\r
+ }\r
+ catch (MissingManifestResourceException) {\r
+ if (scriptResourceName.EndsWith (".resources", RuntimeHelpers.StringComparison)) {\r
+ scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);\r
+ rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);\r
+ }\r
+ else\r
+ throw;\r
+ }\r
+ if (rset == null)\r
+ break;\r
+ writer.WriteLine ();\r
+ string ns = sra.TypeName;\r
+ int indx = ns.LastIndexOf ('.');\r
+ if (indx > 0)\r
+ writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");\r
+ writer.Write ("{0}={{", sra.TypeName);\r
+ bool first = true;\r
+ foreach (DictionaryEntry de in rset) {\r
+ string value = de.Value as string;\r
+ if (value != null) {\r
+ if (first)\r
+ first = false;\r
+ else\r
+ writer.Write (',');\r
+ writer.WriteLine ();\r
+ writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) de.Key), GetScriptStringLiteral (value));\r
+ }\r
+ }\r
+ writer.WriteLine ();\r
+ writer.WriteLine ("};");\r
+ break;\r
+ }\r
+ }\r
+ \r
+ if (notifyScriptLoaded)\r
+ OutputScriptLoadedNotification (writer);\r
+ }\r
+ static void CheckIfResourceIsCompositeScript (string resourceName, ref bool includeTimeStamp)\r
+ {\r
+ bool isCompositeScript = resourceName.StartsWith (CompositeScriptReference.COMPOSITE_SCRIPT_REFERENCE_PREFIX, StringComparison.Ordinal);\r
+ if (!isCompositeScript)\r
+ return;\r
+ \r
+ includeTimeStamp = false;\r
+ }\r
+\r
+ bool HandleCompositeScriptRequest (HttpContext context, HttpRequest request, string d)\r
+ {\r
+ return false;\r
+ }\r
+ // TODO: add value cache?\r
+ static string GetScriptStringLiteral (string value)\r
+ {\r
+ if (String.IsNullOrEmpty (value))\r
+ return "\"" + value + "\"";\r
+ \r
+ var sb = new StringBuilder ("\"");\r
+ for (int i = 0; i < value.Length; i++) {\r
+ char ch = value [i];\r
+ switch (ch) {\r
+ case '\'':\r
+ sb.Append ("\\u0027");\r
+ break;\r
+\r
+ case '"':\r
+ sb.Append ("\\\"");\r
+ break;\r
+\r
+ case '\\':\r
+ sb.Append ("\\\\");\r
+ break;\r
+\r
+ case '\n':\r
+ sb.Append ("\\n");\r
+ break;\r
+\r
+ case '\r':\r
+ sb.Append ("\\r");\r
+ break;\r
+\r
+ default:\r
+ sb.Append (ch);\r
+ break;\r
+ }\r
+ }\r
+ sb.Append ("\"");\r
+ \r
+ return sb.ToString ();\r
+ }\r
}\r
}\r