Restructure of mono.sln and build properties to better fix static/dynamic library...
[mono.git] / docs / HtmlAgilityPack / HtmlWeb.cs
1 // HtmlAgilityPack V1.0 - Simon Mourier <simon underscore mourier at hotmail dot com>\r
2 using System;\r
3 using System.IO;\r
4 using System.Net;\r
5 using System.Text;\r
6 using System.Xml;\r
7 using System.Xml.Serialization;\r
8 using System.Xml.Xsl;\r
9 using Microsoft.Win32;\r
10 \r
11 namespace HtmlAgilityPack\r
12 {\r
13     /// <summary>\r
14     /// A utility class to get HTML document from HTTP.\r
15     /// </summary>\r
16     public class HtmlWeb\r
17     {\r
18         #region Delegates\r
19 \r
20         /// <summary>\r
21         /// Represents the method that will handle the PostResponse event.\r
22         /// </summary>\r
23         public delegate void PostResponseHandler(HttpWebRequest request, HttpWebResponse response);\r
24 \r
25         /// <summary>\r
26         /// Represents the method that will handle the PreHandleDocument event.\r
27         /// </summary>\r
28         public delegate void PreHandleDocumentHandler(HtmlDocument document);\r
29 \r
30         /// <summary>\r
31         /// Represents the method that will handle the PreRequest event.\r
32         /// </summary>\r
33         public delegate bool PreRequestHandler(HttpWebRequest request);\r
34 \r
35         #endregion\r
36 \r
37         #region Fields\r
38 \r
39         private bool _autoDetectEncoding = true;\r
40         private bool _cacheOnly;\r
41 \r
42         private string _cachePath;\r
43         private bool _fromCache;\r
44         private int _requestDuration;\r
45         private Uri _responseUri;\r
46         private HttpStatusCode _statusCode = HttpStatusCode.OK;\r
47         private int _streamBufferSize = 1024;\r
48         private bool _useCookies;\r
49         private bool _usingCache;\r
50 \r
51         /// <summary>\r
52         /// Occurs after an HTTP request has been executed.\r
53         /// </summary>\r
54         public PostResponseHandler PostResponse;\r
55 \r
56         /// <summary>\r
57         /// Occurs before an HTML document is handled.\r
58         /// </summary>\r
59         public PreHandleDocumentHandler PreHandleDocument;\r
60 \r
61         /// <summary>\r
62         /// Occurs before an HTTP request is executed.\r
63         /// </summary>\r
64         public PreRequestHandler PreRequest;\r
65 \r
66         #endregion\r
67 \r
68         #region Properties\r
69 \r
70         /// <summary>\r
71         /// Gets or Sets a value indicating if document encoding must be automatically detected.\r
72         /// </summary>\r
73         public bool AutoDetectEncoding\r
74         {\r
75             get { return _autoDetectEncoding; }\r
76             set { _autoDetectEncoding = value; }\r
77         }\r
78 \r
79         /// <summary>\r
80         /// Gets or Sets a value indicating whether to get document only from the cache.\r
81         /// If this is set to true and document is not found in the cache, nothing will be loaded.\r
82         /// </summary>\r
83         public bool CacheOnly\r
84         {\r
85             get { return _cacheOnly; }\r
86             set\r
87             {\r
88                 if ((value) && !UsingCache)\r
89                 {\r
90                     throw new HtmlWebException("Cache is not enabled. Set UsingCache to true first.");\r
91                 }\r
92                 _cacheOnly = value;\r
93             }\r
94         }\r
95 \r
96         /// <summary>\r
97         /// Gets or Sets the cache path. If null, no caching mechanism will be used.\r
98         /// </summary>\r
99         public string CachePath\r
100         {\r
101             get { return _cachePath; }\r
102             set { _cachePath = value; }\r
103         }\r
104 \r
105         /// <summary>\r
106         /// Gets a value indicating if the last document was retrieved from the cache.\r
107         /// </summary>\r
108         public bool FromCache\r
109         {\r
110             get { return _fromCache; }\r
111         }\r
112 \r
113         /// <summary>\r
114         /// Gets the last request duration in milliseconds.\r
115         /// </summary>\r
116         public int RequestDuration\r
117         {\r
118             get { return _requestDuration; }\r
119         }\r
120 \r
121         /// <summary>\r
122         /// Gets the URI of the Internet resource that actually responded to the request.\r
123         /// </summary>\r
124         public Uri ResponseUri\r
125         {\r
126             get { return _responseUri; }\r
127         }\r
128 \r
129         /// <summary>\r
130         /// Gets the last request status.\r
131         /// </summary>\r
132         public HttpStatusCode StatusCode\r
133         {\r
134             get { return _statusCode; }\r
135         }\r
136 \r
137         /// <summary>\r
138         /// Gets or Sets the size of the buffer used for memory operations.\r
139         /// </summary>\r
140         public int StreamBufferSize\r
141         {\r
142             get { return _streamBufferSize; }\r
143             set\r
144             {\r
145                 if (_streamBufferSize <= 0)\r
146                 {\r
147                     throw new ArgumentException("Size must be greater than zero.");\r
148                 }\r
149                 _streamBufferSize = value;\r
150             }\r
151         }\r
152 \r
153         /// <summary>\r
154         /// Gets or Sets a value indicating if cookies will be stored.\r
155         /// </summary>\r
156         public bool UseCookies\r
157         {\r
158             get { return _useCookies; }\r
159             set { _useCookies = value; }\r
160         }\r
161 \r
162         /// <summary>\r
163         /// Gets or Sets a value indicating whether the caching mechanisms should be used or not.\r
164         /// </summary>\r
165         public bool UsingCache\r
166         {\r
167             get\r
168             {\r
169                 if (_cachePath == null)\r
170                 {\r
171                     return false;\r
172                 }\r
173                 return _usingCache;\r
174             }\r
175             set\r
176             {\r
177                 if ((value) && (_cachePath == null))\r
178                 {\r
179                     throw new HtmlWebException("You need to define a CachePath first.");\r
180                 }\r
181                 _usingCache = value;\r
182             }\r
183         }\r
184 \r
185         #endregion\r
186 \r
187         #region Public Methods\r
188 \r
189         /// <summary>\r
190         /// Gets the MIME content type for a given path extension.\r
191         /// </summary>\r
192         /// <param name="extension">The input path extension.</param>\r
193         /// <param name="def">The default content type to return if any error occurs.</param>\r
194         /// <returns>The path extension's MIME content type.</returns>\r
195         public static string GetContentTypeForExtension(string extension, string def)\r
196         {\r
197             if (string.IsNullOrEmpty(extension))\r
198             {\r
199                 return def;\r
200             }\r
201             string contentType = "";\r
202             try\r
203             {\r
204                 RegistryKey reg = Registry.ClassesRoot;\r
205                 reg = reg.OpenSubKey(extension, false);\r
206                 if (reg != null) contentType = (string)reg.GetValue("", def);\r
207             }\r
208             catch (Exception)\r
209             {\r
210                 contentType = def;\r
211             }\r
212             return contentType;\r
213         }\r
214 \r
215         /// <summary>\r
216         /// Gets the path extension for a given MIME content type.\r
217         /// </summary>\r
218         /// <param name="contentType">The input MIME content type.</param>\r
219         /// <param name="def">The default path extension to return if any error occurs.</param>\r
220         /// <returns>The MIME content type's path extension.</returns>\r
221         public static string GetExtensionForContentType(string contentType, string def)\r
222         {\r
223             if (string.IsNullOrEmpty(contentType))\r
224             {\r
225                 return def;\r
226             }\r
227             string ext = "";\r
228             try\r
229             {\r
230                 RegistryKey reg = Registry.ClassesRoot;\r
231                 reg = reg.OpenSubKey(@"MIME\Database\Content Type\" + contentType, false);\r
232                 if (reg != null) ext = (string)reg.GetValue("Extension", def);\r
233             }\r
234             catch (Exception)\r
235             {\r
236                 ext = def;\r
237             }\r
238             return ext;\r
239         }\r
240 \r
241         /// <summary>\r
242         /// Creates an instance of the given type from the specified Internet resource.\r
243         /// </summary>\r
244         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
245         /// <param name="type">The requested type.</param>\r
246         /// <returns>An newly created instance.</returns>\r
247         public object CreateInstance(string url, Type type)\r
248         {\r
249             return CreateInstance(url, null, null, type);\r
250         }\r
251 \r
252         /// <summary>\r
253         /// Creates an instance of the given type from the specified Internet resource.\r
254         /// </summary>\r
255         /// <param name="htmlUrl">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
256         /// <param name="xsltUrl">The URL that specifies the XSLT stylesheet to load.</param>\r
257         /// <param name="xsltArgs">An <see cref="XsltArgumentList"/> containing the namespace-qualified arguments used as input to the transform.</param>\r
258         /// <param name="type">The requested type.</param>\r
259         /// <returns>An newly created instance.</returns>\r
260         public object CreateInstance(string htmlUrl, string xsltUrl, XsltArgumentList xsltArgs, Type type)\r
261         {\r
262             return CreateInstance(htmlUrl, xsltUrl, xsltArgs, type, null);\r
263         }\r
264 \r
265         /// <summary>\r
266         /// Creates an instance of the given type from the specified Internet resource.\r
267         /// </summary>\r
268         /// <param name="htmlUrl">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
269         /// <param name="xsltUrl">The URL that specifies the XSLT stylesheet to load.</param>\r
270         /// <param name="xsltArgs">An <see cref="XsltArgumentList"/> containing the namespace-qualified arguments used as input to the transform.</param>\r
271         /// <param name="type">The requested type.</param>\r
272         /// <param name="xmlPath">A file path where the temporary XML before transformation will be saved. Mostly used for debugging purposes.</param>\r
273         /// <returns>An newly created instance.</returns>\r
274         public object CreateInstance(string htmlUrl, string xsltUrl, XsltArgumentList xsltArgs, Type type,\r
275                                      string xmlPath)\r
276         {\r
277             StringWriter sw = new StringWriter();\r
278             XmlTextWriter writer = new XmlTextWriter(sw);\r
279             if (xsltUrl == null)\r
280             {\r
281                 LoadHtmlAsXml(htmlUrl, writer);\r
282             }\r
283             else\r
284             {\r
285                 if (xmlPath == null)\r
286                 {\r
287                     LoadHtmlAsXml(htmlUrl, xsltUrl, xsltArgs, writer);\r
288                 }\r
289                 else\r
290                 {\r
291                     LoadHtmlAsXml(htmlUrl, xsltUrl, xsltArgs, writer, xmlPath);\r
292                 }\r
293             }\r
294             writer.Flush();\r
295             StringReader sr = new StringReader(sw.ToString());\r
296             XmlTextReader reader = new XmlTextReader(sr);\r
297             XmlSerializer serializer = new XmlSerializer(type);\r
298             object o;\r
299             try\r
300             {\r
301                 o = serializer.Deserialize(reader);\r
302             }\r
303             catch (InvalidOperationException ex)\r
304             {\r
305                 throw new Exception(ex + ", --- xml:" + sw);\r
306             }\r
307             return o;\r
308         }\r
309 \r
310         /// <summary>\r
311         /// Gets an HTML document from an Internet resource and saves it to the specified file.\r
312         /// </summary>\r
313         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
314         /// <param name="path">The location of the file where you want to save the document.</param>\r
315         public void Get(string url, string path)\r
316         {\r
317             Get(url, path, "GET");\r
318         }\r
319 \r
320         /// <summary>\r
321         /// Gets an HTML document from an Internet resource and saves it to the specified file. - Proxy aware\r
322         /// </summary>\r
323         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
324         /// <param name="path">The location of the file where you want to save the document.</param>\r
325         /// <param name="proxy"></param>\r
326         /// <param name="credentials"></param>\r
327         public void Get(string url, string path, WebProxy proxy, NetworkCredential credentials)\r
328         {\r
329             Get(url, path, proxy, credentials, "GET");\r
330         }\r
331 \r
332         /// <summary>\r
333         /// Gets an HTML document from an Internet resource and saves it to the specified file.\r
334         /// </summary>\r
335         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
336         /// <param name="path">The location of the file where you want to save the document.</param>\r
337         /// <param name="method">The HTTP method used to open the connection, such as GET, POST, PUT, or PROPFIND.</param>\r
338         public void Get(string url, string path, string method)\r
339         {\r
340             Uri uri = new Uri(url);\r
341             if ((uri.Scheme == Uri.UriSchemeHttps) ||\r
342                 (uri.Scheme == Uri.UriSchemeHttp))\r
343             {\r
344                 Get(uri, method, path, null, null, null);\r
345             }\r
346             else\r
347             {\r
348                 throw new HtmlWebException("Unsupported uri scheme: '" + uri.Scheme + "'.");\r
349             }\r
350         }\r
351 \r
352         /// <summary>\r
353         /// Gets an HTML document from an Internet resource and saves it to the specified file.  Understands Proxies\r
354         /// </summary>\r
355         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
356         /// <param name="path">The location of the file where you want to save the document.</param>\r
357         /// <param name="credentials"></param>\r
358         /// <param name="method">The HTTP method used to open the connection, such as GET, POST, PUT, or PROPFIND.</param>\r
359         /// <param name="proxy"></param>\r
360         public void Get(string url, string path, WebProxy proxy, NetworkCredential credentials, string method)\r
361         {\r
362             Uri uri = new Uri(url);\r
363             if ((uri.Scheme == Uri.UriSchemeHttps) ||\r
364                 (uri.Scheme == Uri.UriSchemeHttp))\r
365             {\r
366                 Get(uri, method, path, null, proxy, credentials);\r
367             }\r
368             else\r
369             {\r
370                 throw new HtmlWebException("Unsupported uri scheme: '" + uri.Scheme + "'.");\r
371             }\r
372         }\r
373 \r
374         /// <summary>\r
375         /// Gets the cache file path for a specified url.\r
376         /// </summary>\r
377         /// <param name="uri">The url fo which to retrieve the cache path. May not be null.</param>\r
378         /// <returns>The cache file path.</returns>\r
379         public string GetCachePath(Uri uri)\r
380         {\r
381             if (uri == null)\r
382             {\r
383                 throw new ArgumentNullException("uri");\r
384             }\r
385             if (!UsingCache)\r
386             {\r
387                 throw new HtmlWebException("Cache is not enabled. Set UsingCache to true first.");\r
388             }\r
389             string cachePath;\r
390             if (uri.AbsolutePath == "/")\r
391             {\r
392                 cachePath = Path.Combine(_cachePath, ".htm");\r
393             }\r
394             else\r
395             {\r
396                 cachePath = Path.Combine(_cachePath, (uri.Host + uri.AbsolutePath).Replace('/', '\\'));\r
397             }\r
398             return cachePath;\r
399         }\r
400 \r
401         /// <summary>\r
402         /// Gets an HTML document from an Internet resource.\r
403         /// </summary>\r
404         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
405         /// <returns>A new HTML document.</returns>\r
406         public HtmlDocument Load(string url)\r
407         {\r
408             return Load(url, "GET");\r
409         }\r
410 \r
411         /// <summary>\r
412         /// Gets an HTML document from an Internet resource.\r
413         /// </summary>\r
414         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
415         /// <param name="proxyHost">Host to use for Proxy</param>\r
416         /// <param name="proxyPort">Port the Proxy is on</param>\r
417         /// <param name="userId">User Id for Authentication</param>\r
418         /// <param name="password">Password for Authentication</param>\r
419         /// <returns>A new HTML document.</returns>\r
420         public HtmlDocument Load(string url, string proxyHost, int proxyPort, string userId, string password)\r
421         {\r
422             //Create my proxy\r
423             WebProxy myProxy = new WebProxy(proxyHost, proxyPort);\r
424             myProxy.BypassProxyOnLocal = true;\r
425 \r
426             //Create my credentials\r
427             NetworkCredential myCreds = null;\r
428             if ((userId != null) && (password != null))\r
429             {\r
430                 myCreds = new NetworkCredential(userId, password);\r
431                 CredentialCache credCache = new CredentialCache();\r
432                 //Add the creds\r
433                 credCache.Add(myProxy.Address, "Basic", myCreds);\r
434                 credCache.Add(myProxy.Address, "Digest", myCreds);\r
435             }\r
436 \r
437             return Load(url, "GET", myProxy, myCreds);\r
438         }\r
439 \r
440         /// <summary>\r
441         /// Loads an HTML document from an Internet resource.\r
442         /// </summary>\r
443         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
444         /// <param name="method">The HTTP method used to open the connection, such as GET, POST, PUT, or PROPFIND.</param>\r
445         /// <returns>A new HTML document.</returns>\r
446         public HtmlDocument Load(string url, string method)\r
447         {\r
448             Uri uri = new Uri(url);\r
449             HtmlDocument doc;\r
450             if ((uri.Scheme == Uri.UriSchemeHttps) ||\r
451                 (uri.Scheme == Uri.UriSchemeHttp))\r
452             {\r
453                 doc = LoadUrl(uri, method, null, null);\r
454             }\r
455             else\r
456             {\r
457                 if (uri.Scheme == Uri.UriSchemeFile)\r
458                 {\r
459                     doc = new HtmlDocument();\r
460                     doc.OptionAutoCloseOnEnd = false;\r
461                     doc.OptionAutoCloseOnEnd = true;\r
462                     doc.DetectEncodingAndLoad(url, _autoDetectEncoding);\r
463                 }\r
464                 else\r
465                 {\r
466                     throw new HtmlWebException("Unsupported uri scheme: '" + uri.Scheme + "'.");\r
467                 }\r
468             }\r
469             if (PreHandleDocument != null)\r
470             {\r
471                 PreHandleDocument(doc);\r
472             }\r
473             return doc;\r
474         }\r
475 \r
476         /// <summary>\r
477         /// Loads an HTML document from an Internet resource.\r
478         /// </summary>\r
479         /// <param name="url">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
480         /// <param name="method">The HTTP method used to open the connection, such as GET, POST, PUT, or PROPFIND.</param>\r
481         /// <param name="proxy">Proxy to use with this request</param>\r
482         /// <param name="credentials">Credentials to use when authenticating</param>\r
483         /// <returns>A new HTML document.</returns>\r
484         public HtmlDocument Load(string url, string method, WebProxy proxy, NetworkCredential credentials)\r
485         {\r
486             Uri uri = new Uri(url);\r
487             HtmlDocument doc;\r
488             if ((uri.Scheme == Uri.UriSchemeHttps) ||\r
489                 (uri.Scheme == Uri.UriSchemeHttp))\r
490             {\r
491                 doc = LoadUrl(uri, method, proxy, credentials);\r
492             }\r
493             else\r
494             {\r
495                 if (uri.Scheme == Uri.UriSchemeFile)\r
496                 {\r
497                     doc = new HtmlDocument();\r
498                     doc.OptionAutoCloseOnEnd = false;\r
499                     doc.OptionAutoCloseOnEnd = true;\r
500                     doc.DetectEncodingAndLoad(url, _autoDetectEncoding);\r
501                 }\r
502                 else\r
503                 {\r
504                     throw new HtmlWebException("Unsupported uri scheme: '" + uri.Scheme + "'.");\r
505                 }\r
506             }\r
507             if (PreHandleDocument != null)\r
508             {\r
509                 PreHandleDocument(doc);\r
510             }\r
511             return doc;\r
512         }\r
513 \r
514         /// <summary>\r
515         /// Loads an HTML document from an Internet resource and saves it to the specified XmlTextWriter.\r
516         /// </summary>\r
517         /// <param name="htmlUrl">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
518         /// <param name="writer">The XmlTextWriter to which you want to save.</param>\r
519         public void LoadHtmlAsXml(string htmlUrl, XmlTextWriter writer)\r
520         {\r
521             HtmlDocument doc = Load(htmlUrl);\r
522             doc.Save(writer);\r
523         }\r
524 \r
525         /// <summary>\r
526         /// Loads an HTML document from an Internet resource and saves it to the specified XmlTextWriter, after an XSLT transformation.\r
527         /// </summary>\r
528         /// <param name="htmlUrl">The requested URL, such as "http://Myserver/Mypath/Myfile.asp".</param>\r
529         /// <param name="xsltUrl">The URL that specifies the XSLT stylesheet to load.</param>\r
530         /// <param name="xsltArgs">An XsltArgumentList containing the namespace-qualified arguments used as input to the transform.</param>\r
531         /// <param name="writer">The XmlTextWriter to which you want to save.</param>\r
532         public void LoadHtmlAsXml(string htmlUrl, string xsltUrl, XsltArgumentList xsltArgs, XmlTextWriter writer)\r
533         {\r
534             LoadHtmlAsXml(htmlUrl, xsltUrl, xsltArgs, writer, null);\r
535         }\r
536 \r
537         /// <summary>\r
538         /// Loads an HTML document from an Internet resource and saves it to the specified XmlTextWriter, after an XSLT transformation.\r
539         /// </summary>\r
540         /// <param name="htmlUrl">The requested URL, such as "http://Myserver/Mypath/Myfile.asp". May not be null.</param>\r
541         /// <param name="xsltUrl">The URL that specifies the XSLT stylesheet to load.</param>\r
542         /// <param name="xsltArgs">An XsltArgumentList containing the namespace-qualified arguments used as input to the transform.</param>\r
543         /// <param name="writer">The XmlTextWriter to which you want to save.</param>\r
544         /// <param name="xmlPath">A file path where the temporary XML before transformation will be saved. Mostly used for debugging purposes.</param>\r
545         public void LoadHtmlAsXml(string htmlUrl, string xsltUrl, XsltArgumentList xsltArgs, XmlTextWriter writer,\r
546                                   string xmlPath)\r
547         {\r
548             if (htmlUrl == null)\r
549             {\r
550                 throw new ArgumentNullException("htmlUrl");\r
551             }\r
552 \r
553             HtmlDocument doc = Load(htmlUrl);\r
554 \r
555             if (xmlPath != null)\r
556             {\r
557                 XmlTextWriter w = new XmlTextWriter(xmlPath, doc.Encoding);\r
558                 doc.Save(w);\r
559                 w.Close();\r
560             }\r
561             if (xsltArgs == null)\r
562             {\r
563                 xsltArgs = new XsltArgumentList();\r
564             }\r
565 \r
566             // add some useful variables to the xslt doc\r
567             xsltArgs.AddParam("url", "", htmlUrl);\r
568             xsltArgs.AddParam("requestDuration", "", RequestDuration);\r
569             xsltArgs.AddParam("fromCache", "", FromCache);\r
570 \r
571             XslCompiledTransform xslt = new XslCompiledTransform();\r
572             xslt.Load(xsltUrl);\r
573             xslt.Transform(doc, xsltArgs, writer);\r
574         }\r
575 \r
576         #endregion\r
577 \r
578         #region Private Methods\r
579 \r
580         private static void FilePreparePath(string target)\r
581         {\r
582             if (File.Exists(target))\r
583             {\r
584                 FileAttributes atts = File.GetAttributes(target);\r
585                 File.SetAttributes(target, atts & ~FileAttributes.ReadOnly);\r
586             }\r
587             else\r
588             {\r
589                 string dir = Path.GetDirectoryName(target);\r
590                 if (!Directory.Exists(dir))\r
591                 {\r
592                     Directory.CreateDirectory(dir);\r
593                 }\r
594             }\r
595         }\r
596 \r
597         private static DateTime RemoveMilliseconds(DateTime t)\r
598         {\r
599             return new DateTime(t.Year, t.Month, t.Day, t.Hour, t.Minute, t.Second, 0);\r
600         }\r
601 \r
602         // ReSharper disable UnusedMethodReturnValue.Local\r
603         private static long SaveStream(Stream stream, string path, DateTime touchDate, int streamBufferSize)\r
604         // ReSharper restore UnusedMethodReturnValue.Local\r
605         {\r
606             FilePreparePath(path);\r
607             FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);\r
608             BinaryReader br = null;\r
609             BinaryWriter bw = null;\r
610             long len = 0;\r
611             try\r
612             {\r
613                 br = new BinaryReader(stream);\r
614                 bw = new BinaryWriter(fs);\r
615 \r
616                 byte[] buffer;\r
617                 do\r
618                 {\r
619                     buffer = br.ReadBytes(streamBufferSize);\r
620                     len += buffer.Length;\r
621                     if (buffer.Length > 0)\r
622                     {\r
623                         bw.Write(buffer);\r
624                     }\r
625                 } while (buffer.Length > 0);\r
626             }\r
627             finally\r
628             {\r
629                 if (br != null)\r
630                 {\r
631                     br.Close();\r
632                 }\r
633                 if (bw != null)\r
634                 {\r
635                     bw.Flush();\r
636                     bw.Close();\r
637                 }\r
638                 if (fs != null)\r
639                 {\r
640                     fs.Close();\r
641                 }\r
642             }\r
643             File.SetLastWriteTime(path, touchDate);\r
644             return len;\r
645         }\r
646 \r
647         private HttpStatusCode Get(Uri uri, string method, string path, HtmlDocument doc, IWebProxy proxy,\r
648                                    ICredentials creds)\r
649         {\r
650             string cachePath = null;\r
651             HttpWebRequest req;\r
652             bool oldFile = false;\r
653 \r
654             req = WebRequest.Create(uri) as HttpWebRequest;\r
655             req.Method = method;\r
656 \r
657             if (proxy != null)\r
658             {\r
659                 if (creds != null)\r
660                 {\r
661                     proxy.Credentials = creds;\r
662                     req.Credentials = creds;\r
663                 }\r
664                 else\r
665                 {\r
666                     proxy.Credentials = CredentialCache.DefaultCredentials;\r
667                     req.Credentials = CredentialCache.DefaultCredentials;\r
668                 }\r
669                 req.Proxy = proxy;\r
670             }\r
671 \r
672             _fromCache = false;\r
673             _requestDuration = 0;\r
674             int tc = Environment.TickCount;\r
675             if (UsingCache)\r
676             {\r
677                 cachePath = GetCachePath(req.RequestUri);\r
678                 if (File.Exists(cachePath))\r
679                 {\r
680                     req.IfModifiedSince = File.GetLastAccessTime(cachePath);\r
681                     oldFile = true;\r
682                 }\r
683             }\r
684 \r
685             if (_cacheOnly)\r
686             {\r
687                 if (!File.Exists(cachePath))\r
688                 {\r
689                     throw new HtmlWebException("File was not found at cache path: '" + cachePath + "'");\r
690                 }\r
691 \r
692                 if (path != null)\r
693                 {\r
694                     IOLibrary.CopyAlways(cachePath, path);\r
695                     // touch the file\r
696                     File.SetLastWriteTime(path, File.GetLastWriteTime(cachePath));\r
697                 }\r
698                 _fromCache = true;\r
699                 return HttpStatusCode.NotModified;\r
700             }\r
701 \r
702             if (_useCookies)\r
703             {\r
704                 req.CookieContainer = new CookieContainer();\r
705             }\r
706 \r
707             if (PreRequest != null)\r
708             {\r
709                 // allow our user to change the request at will\r
710                 if (!PreRequest(req))\r
711                 {\r
712                     return HttpStatusCode.ResetContent;\r
713                 }\r
714 \r
715                 // dump cookie\r
716                 //                              if (_useCookies)\r
717                 //                              {\r
718                 //                                      foreach(Cookie cookie in req.CookieContainer.GetCookies(req.RequestUri))\r
719                 //                                      {\r
720                 //                                              HtmlLibrary.Trace("Cookie " + cookie.Name + "=" + cookie.Value + " path=" + cookie.Path + " domain=" + cookie.Domain);\r
721                 //                                      }\r
722                 //                              }\r
723             }\r
724 \r
725             HttpWebResponse resp;\r
726 \r
727             try\r
728             {\r
729                 resp = req.GetResponse() as HttpWebResponse;\r
730             }\r
731             catch (WebException we)\r
732             {\r
733                 _requestDuration = Environment.TickCount - tc;\r
734                 resp = (HttpWebResponse)we.Response;\r
735                 if (resp == null)\r
736                 {\r
737                     if (oldFile)\r
738                     {\r
739                         if (path != null)\r
740                         {\r
741                             IOLibrary.CopyAlways(cachePath, path);\r
742                             // touch the file\r
743                             File.SetLastWriteTime(path, File.GetLastWriteTime(cachePath));\r
744                         }\r
745                         return HttpStatusCode.NotModified;\r
746                     }\r
747                     throw;\r
748                 }\r
749             }\r
750             catch (Exception)\r
751             {\r
752                 _requestDuration = Environment.TickCount - tc;\r
753                 throw;\r
754             }\r
755 \r
756             // allow our user to get some info from the response\r
757             if (PostResponse != null)\r
758             {\r
759                 PostResponse(req, resp);\r
760             }\r
761 \r
762             _requestDuration = Environment.TickCount - tc;\r
763             _responseUri = resp.ResponseUri;\r
764 \r
765             bool html = IsHtmlContent(resp.ContentType);\r
766             Encoding respenc;\r
767 \r
768             if ((resp.ContentEncoding != null) && (resp.ContentEncoding.Length > 0))\r
769             {\r
770                 respenc = Encoding.GetEncoding(resp.ContentEncoding);\r
771             }\r
772             else\r
773             {\r
774                 respenc = null;\r
775             }\r
776 \r
777             if (resp.StatusCode == HttpStatusCode.NotModified)\r
778             {\r
779                 if (UsingCache)\r
780                 {\r
781                     _fromCache = true;\r
782                     if (path != null)\r
783                     {\r
784                         IOLibrary.CopyAlways(cachePath, path);\r
785                         // touch the file\r
786                         File.SetLastWriteTime(path, File.GetLastWriteTime(cachePath));\r
787                     }\r
788                     return resp.StatusCode;\r
789                 }\r
790                 else\r
791                 {\r
792                     // this should *never* happen...\r
793                     throw new HtmlWebException("Server has send a NotModifed code, without cache enabled.");\r
794                 }\r
795             }\r
796             Stream s = resp.GetResponseStream();\r
797             if (s != null)\r
798             {\r
799                 if (UsingCache)\r
800                 {\r
801                     // NOTE: LastModified does not contain milliseconds, so we remove them to the file\r
802                     SaveStream(s, cachePath, RemoveMilliseconds(resp.LastModified), _streamBufferSize);\r
803 \r
804                     // save headers\r
805                     SaveCacheHeaders(req.RequestUri, resp);\r
806 \r
807                     if (path != null)\r
808                     {\r
809                         // copy and touch the file\r
810                         IOLibrary.CopyAlways(cachePath, path);\r
811                         File.SetLastWriteTime(path, File.GetLastWriteTime(cachePath));\r
812                     }\r
813                 }\r
814                 else\r
815                 {\r
816                     // try to work in-memory\r
817                     if ((doc != null) && (html))\r
818                     {\r
819                         if (respenc != null)\r
820                         {\r
821                             doc.Load(s, respenc);\r
822                         }\r
823                         else\r
824                         {\r
825                             doc.Load(s, true);\r
826                         }\r
827                     }\r
828                 }\r
829                 resp.Close();\r
830             }\r
831             return resp.StatusCode;\r
832         }\r
833 \r
834         private string GetCacheHeader(Uri requestUri, string name, string def)\r
835         {\r
836             // note: some headers are collection (ex: www-authenticate)\r
837             // we don't handle that here\r
838             XmlDocument doc = new XmlDocument();\r
839             doc.Load(GetCacheHeadersPath(requestUri));\r
840             XmlNode node =\r
841                 doc.SelectSingleNode("//h[translate(@n, 'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')='" +\r
842                                      name.ToUpper() + "']");\r
843             if (node == null)\r
844             {\r
845                 return def;\r
846             }\r
847             // attribute should exist\r
848             return node.Attributes[name].Value;\r
849         }\r
850 \r
851         private string GetCacheHeadersPath(Uri uri)\r
852         {\r
853             //return Path.Combine(GetCachePath(uri), ".h.xml");\r
854             return GetCachePath(uri) + ".h.xml";\r
855         }\r
856 \r
857         private bool IsCacheHtmlContent(string path)\r
858         {\r
859             string ct = GetContentTypeForExtension(Path.GetExtension(path), null);\r
860             return IsHtmlContent(ct);\r
861         }\r
862 \r
863         private bool IsHtmlContent(string contentType)\r
864         {\r
865             return contentType.ToLower().StartsWith("text/html");\r
866         }\r
867 \r
868         private HtmlDocument LoadUrl(Uri uri, string method, WebProxy proxy, NetworkCredential creds)\r
869         {\r
870             HtmlDocument doc = new HtmlDocument();\r
871             doc.OptionAutoCloseOnEnd = false;\r
872             doc.OptionFixNestedTags = true;\r
873             _statusCode = Get(uri, method, null, doc, proxy, creds);\r
874             if (_statusCode == HttpStatusCode.NotModified)\r
875             {\r
876                 // read cached encoding\r
877                 doc.DetectEncodingAndLoad(GetCachePath(uri));\r
878             }\r
879             return doc;\r
880         }\r
881 \r
882         private void SaveCacheHeaders(Uri requestUri, HttpWebResponse resp)\r
883         {\r
884             // we cache the original headers aside the cached document.\r
885             string file = GetCacheHeadersPath(requestUri);\r
886             XmlDocument doc = new XmlDocument();\r
887             doc.LoadXml("<c></c>");\r
888             XmlNode cache = doc.FirstChild;\r
889             foreach (string header in resp.Headers)\r
890             {\r
891                 XmlNode entry = doc.CreateElement("h");\r
892                 XmlAttribute att = doc.CreateAttribute("n");\r
893                 att.Value = header;\r
894                 entry.Attributes.Append(att);\r
895 \r
896                 att = doc.CreateAttribute("v");\r
897                 att.Value = resp.Headers[header];\r
898                 entry.Attributes.Append(att);\r
899 \r
900                 cache.AppendChild(entry);\r
901             }\r
902             doc.Save(file);\r
903         }\r
904 \r
905         #endregion\r
906     }\r
907 }