Merge pull request #4045 from lambdageek/bug-47867
[mono.git] / mcs / class / System.Web.Extensions / System.Web.Handlers / ScriptResourceHandler.cs
1 //\r
2 // ScriptResourceHandler.cs\r
3 //\r
4 // Authors:\r
5 //   Igor Zelmanovich <igorz@mainsoft.com>\r
6 //   Marek Habersack <grendel@twistedcode.net>\r
7 //\r
8 // (C) 2007 Mainsoft, Inc.  http://www.mainsoft.com\r
9 // (C) 2011 Novell, Inc.  http://novell.com\r
10 //\r
11 //\r
12 // Permission is hereby granted, free of charge, to any person obtaining\r
13 // a copy of this software and associated documentation files (the\r
14 // "Software"), to deal in the Software without restriction, including\r
15 // without limitation the rights to use, copy, modify, merge, publish,\r
16 // distribute, sublicense, and/or sell copies of the Software, and to\r
17 // permit persons to whom the Software is furnished to do so, subject to\r
18 // the following conditions:\r
19 // \r
20 // The above copyright notice and this permission notice shall be\r
21 // included in all copies or substantial portions of the Software.\r
22 // \r
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
30 //\r
31 \r
32 using System;\r
33 using System.Collections;\r
34 using System.Collections.Generic;\r
35 using System.IO;\r
36 using System.Security.Cryptography;\r
37 using System.Reflection;\r
38 using System.Resources;\r
39 using System.Text;\r
40 using System.Threading;\r
41 using System.Web.Configuration;\r
42 using System.Web.Hosting;\r
43 using System.Web.UI;\r
44 using System.Web.Util;\r
45 \r
46 namespace System.Web.Handlers\r
47 {\r
48         public partial class ScriptResourceHandler : IHttpHandler\r
49         {               \r
50                 protected virtual bool IsReusable {\r
51                         get { return true; }\r
52                 }\r
53 \r
54                 #region IHttpHandler Members\r
55 \r
56                 bool IHttpHandler.IsReusable {\r
57                         get { return IsReusable; }\r
58                 }\r
59 \r
60                 void IHttpHandler.ProcessRequest (HttpContext context) {\r
61                         ProcessRequest (context);\r
62                 }\r
63 \r
64                 #endregion\r
65                 void AppendResourceScriptContents (StringWriter sw, CompositeEntry entry)\r
66                 {\r
67                         if (entry.Assembly == null || entry.Attribute == null || String.IsNullOrEmpty (entry.NameOrPath))\r
68                                 return;\r
69 \r
70                         using (Stream s = entry.Assembly.GetManifestResourceStream (entry.NameOrPath)) {\r
71                                 if (s == null)\r
72                                         throw new HttpException (404, "Resource '" + entry.NameOrPath + "' not found");\r
73 \r
74                                 if (entry.Attribute.PerformSubstitution) {\r
75                                         using (var r = new StreamReader (s)) {\r
76                                                 new PerformSubstitutionHelper (entry.Assembly).PerformSubstitution (r, sw);\r
77                                         }\r
78                                 } else {\r
79                                         using (var r = new StreamReader (s)) {\r
80                                                 string line = r.ReadLine ();\r
81                                                 while (line != null) {\r
82                                                         sw.WriteLine (line);\r
83                                                         line = r.ReadLine ();\r
84                                                 }\r
85                                         }\r
86                                 }\r
87                         }\r
88                 }\r
89 \r
90                 void AppendFileScriptContents (StringWriter sw, CompositeEntry entry)\r
91                 {\r
92                         // FIXME: should we limit the script size in any way?\r
93                         if (String.IsNullOrEmpty (entry.NameOrPath))\r
94                                 return;\r
95 \r
96                         string mappedPath;\r
97                         if (!HostingEnvironment.HaveCustomVPP) {\r
98                                 // We'll take a shortcut here by bypassing the default VPP layers\r
99                                 mappedPath = HostingEnvironment.MapPath (entry.NameOrPath);\r
100                                 if (!File.Exists (mappedPath))\r
101                                         return;\r
102                                 sw.Write (File.ReadAllText (mappedPath));\r
103                                 return;\r
104                         }\r
105 \r
106                         VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;\r
107                         if (!vpp.FileExists (entry.NameOrPath))\r
108                                 return;\r
109                         VirtualFile file = vpp.GetFile (entry.NameOrPath);\r
110                         if (file == null)\r
111                                 return;\r
112                         using (Stream s = file.Open ()) {\r
113                                 using (var r = new StreamReader (s)) {\r
114                                         string line = r.ReadLine ();\r
115                                         while (line != null) {\r
116                                                 sw.WriteLine (line);\r
117                                                 line = r.ReadLine ();\r
118                                         }\r
119                                 }\r
120                         }\r
121                 }\r
122                 \r
123                 void AppendScriptContents (StringWriter sw, CompositeEntry entry)\r
124                 {\r
125                         if (entry.Assembly != null)\r
126                                 AppendResourceScriptContents (sw, entry);\r
127                         else\r
128                                 AppendFileScriptContents (sw, entry);\r
129                 }\r
130                 \r
131                 void SendCompositeScript (HttpContext context, HttpRequest request, bool notifyScriptLoaded, List <CompositeEntry> entries)\r
132                 {\r
133                         if (entries.Count == 0)\r
134                                 throw new HttpException (404, "Resource not found");\r
135 \r
136                         DateTime modifiedSince;\r
137                         bool hasIfModifiedSince = HasIfModifiedSince (context.Request, out modifiedSince);\r
138                         \r
139                         if (hasIfModifiedSince) {\r
140                                 bool notModified = true;\r
141                         \r
142                                 foreach (CompositeEntry entry in entries) {\r
143                                         if (entry == null)\r
144                                                 continue;\r
145                                         if (notModified) {\r
146                                                 if (hasIfModifiedSince && entry.IsModifiedSince (modifiedSince))\r
147                                                         notModified = false;\r
148                                         }\r
149                                 }\r
150 \r
151                                 if (notModified) {\r
152                                         RespondWithNotModified (context);\r
153                                         return;\r
154                                 }\r
155                         }\r
156                         \r
157                         StringBuilder contents = new StringBuilder ();\r
158                         using (var sw = new StringWriter (contents)) {\r
159                                 foreach (CompositeEntry entry in entries) {\r
160                                         if (entry == null)\r
161                                                 continue;\r
162                                         AppendScriptContents (sw, entry);\r
163                                 }\r
164                         }\r
165                         if (contents.Length == 0)\r
166                                 throw new HttpException (404, "Resource not found");\r
167 \r
168                         HttpResponse response = context.Response;\r
169                         DateTime utcnow = DateTime.UtcNow;\r
170 \r
171                         response.ContentType = "text/javascript";\r
172                         response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));\r
173                         response.ExpiresAbsolute = utcnow.AddYears (1);\r
174                         response.CacheControl = "public";\r
175 \r
176                         response.Output.Write (contents.ToString ());\r
177                         if (notifyScriptLoaded)\r
178                                 OutputScriptLoadedNotification (response.Output);\r
179                 }\r
180                 void OutputScriptLoadedNotification (TextWriter writer)\r
181                 {\r
182                         writer.WriteLine ();\r
183                         writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");\r
184                 }\r
185                 \r
186                 protected virtual void ProcessRequest (HttpContext context)\r
187                 {\r
188                         HttpRequest request = context.Request;\r
189                         bool notifyScriptLoaded = request.QueryString ["n"] == "t";\r
190                         List <CompositeEntry> compositeEntries = CompositeScriptReference.GetCompositeScriptEntries (request.RawUrl);\r
191                         if (compositeEntries != null) {\r
192                                 SendCompositeScript (context, request, notifyScriptLoaded, compositeEntries);\r
193                                 return;\r
194                         }\r
195                         EmbeddedResource res;\r
196                         Assembly assembly;                      \r
197                         SendEmbeddedResource (context, out res, out assembly);\r
198 \r
199                         HttpResponse response = context.Response;\r
200                         TextWriter writer = response.Output;\r
201                         foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {\r
202                                 if (String.Compare (sra.ScriptName, res.Name, StringComparison.Ordinal) == 0) {\r
203                                         string scriptResourceName = sra.ScriptResourceName;\r
204                                         ResourceSet rset = null;\r
205                                         try {\r
206                                                 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);\r
207                                         }\r
208                                         catch (MissingManifestResourceException) {\r
209                                                 if (scriptResourceName.EndsWith (".resources", RuntimeHelpers.StringComparison)) {\r
210                                                         scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);\r
211                                                         rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);\r
212                                                 }\r
213                                                 else\r
214                                                         throw;\r
215                                         }\r
216                                         if (rset == null)\r
217                                                 break;\r
218                                         writer.WriteLine ();\r
219                                         string ns = sra.TypeName;\r
220                                         int indx = ns.LastIndexOf ('.');\r
221                                         if (indx > 0)\r
222                                                 writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");\r
223                                         writer.Write ("{0}={{", sra.TypeName);\r
224                                         bool first = true;\r
225                                         foreach (DictionaryEntry de in rset) {\r
226                                                 string value = de.Value as string;\r
227                                                 if (value != null) {\r
228                                                         if (first)\r
229                                                                 first = false;\r
230                                                         else\r
231                                                                 writer.Write (',');\r
232                                                         writer.WriteLine ();\r
233                                                         writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) de.Key), GetScriptStringLiteral (value));\r
234                                                 }\r
235                                         }\r
236                                         writer.WriteLine ();\r
237                                         writer.WriteLine ("};");\r
238                                         break;\r
239                                 }\r
240                         }\r
241                         \r
242                         if (notifyScriptLoaded)\r
243                                 OutputScriptLoadedNotification (writer);\r
244                 }\r
245                 static void CheckIfResourceIsCompositeScript (string resourceName, ref bool includeTimeStamp)\r
246                 {\r
247                         bool isCompositeScript = resourceName.StartsWith (CompositeScriptReference.COMPOSITE_SCRIPT_REFERENCE_PREFIX, StringComparison.Ordinal);\r
248                         if (!isCompositeScript)\r
249                                 return;\r
250                         \r
251                         includeTimeStamp = false;\r
252                 }\r
253 \r
254                 bool HandleCompositeScriptRequest (HttpContext context, HttpRequest request, string d)\r
255                 {\r
256                         return false;\r
257                 }\r
258                 // TODO: add value cache?\r
259                 static string GetScriptStringLiteral (string value)\r
260                 {\r
261                         if (String.IsNullOrEmpty (value))\r
262                                 return "\"" + value + "\"";\r
263                         \r
264                         var sb = new StringBuilder ("\"");\r
265                         for (int i = 0; i < value.Length; i++) {\r
266                                 char ch = value [i];\r
267                                 switch (ch) {\r
268                                         case '\'':\r
269                                                 sb.Append ("\\u0027");\r
270                                                 break;\r
271 \r
272                                         case '"':\r
273                                                 sb.Append ("\\\"");\r
274                                                 break;\r
275 \r
276                                         case '\\':\r
277                                                 sb.Append ("\\\\");\r
278                                                 break;\r
279 \r
280                                         case '\n':\r
281                                                 sb.Append ("\\n");\r
282                                                 break;\r
283 \r
284                                         case '\r':\r
285                                                 sb.Append ("\\r");\r
286                                                 break;\r
287 \r
288                                         default:\r
289                                                 sb.Append (ch);\r
290                                                 break;\r
291                                 }\r
292                         }\r
293                         sb.Append ("\"");\r
294                         \r
295                         return sb.ToString ();\r
296                 }\r
297         }\r
298 }\r