* ListView.cs: When doing layout calculations don't use a ref
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Mime.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2006 Alexander Olk
21 //
22 // Authors:
23 //
24 //  Alexander Olk       alex.olk@googlemail.com
25 //
26
27 using System;
28 using System.IO;
29 using System.Collections;
30 using System.Collections.Specialized;
31 using System.Text.RegularExpressions;
32 using System.Text;
33
34 // Usage:
35 // - for files:
36 //   string mimeType = Mime.GetMimeTypeForFile( string filename );
37 // - for byte array:
38 //   string mimeType = Mime.GetMimeTypeForData( byte[] data );
39 // - for string (maybe an email):
40 //   string mimeType = Mime.GetMimeTypeForString( string input );
41
42 // - get alias for mime type:
43 //   string alias = Mime.GetMimeAlias( string mimeType );
44 // - get subclass for mime type:
45 //   string subtype = Mime.GetMimeSubClass( string mimeType );
46 // - get all available mime types:
47 //   string[] available = Mime.AvailableMimeTypes;
48
49 // TODO:
50 // - optimize even more :)
51 // - async callback ?!?
52 // - freedesktop org file extensions can have regular expressions also, resolve them too
53 // - sort match collections by magic priority ( higher = first ) ?
54
55 // internal test:
56 // looking up the mime types 20 times for 2757 files in /usr/lib without caching (mime_file_cache)
57 // old version: Time: 00:00:32.3791220
58 // new version: Time: 00:00:16.9991810
59
60 namespace System.Windows.Forms
61 {
62         internal class Mime
63         {
64                 public static Mime Instance = new Mime();
65                 
66                 private string current_file_name;
67                 private string global_result = octet_stream;
68                 
69                 private FileStream file_stream;
70                 
71                 private byte[] buffer = null;
72                 
73                 private const string octet_stream = "application/octet-stream";
74                 private const string text_plain = "text/plain";
75                 private const string zero_file = "application/x-zerosize";
76                 
77                 private StringDictionary mime_file_cache = new StringDictionary();
78                 
79                 private const int mime_file_cache_max_size = 3000;
80                 
81                 private string search_string;
82                 
83                 private static object lock_object = new Object();
84                 
85                 private bool is_zero_file = false;
86                 
87                 private int bytes_read = 0;
88                 
89                 private bool mime_available = false;
90                 
91                 public static NameValueCollection Aliases;
92                 public static NameValueCollection SubClasses;
93                 
94                 public static NameValueCollection GlobalPatternsShort;
95                 public static NameValueCollection GlobalPatternsLong;
96                 public static NameValueCollection GlobalLiterals;
97                 public static NameValueCollection GlobalSufPref;
98                 
99                 public static ArrayList Matches80Plus;
100                 public static ArrayList MatchesBelow80;
101                 
102                 private Mime ()
103                 {
104 #if NET_2_0
105                         Aliases = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
106                         SubClasses = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
107                         GlobalPatternsShort = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
108                         GlobalPatternsLong = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
109                         GlobalLiterals = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
110                         GlobalSufPref = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
111 #else
112                         Aliases = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
113                         SubClasses = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
114                         GlobalPatternsShort = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
115                         GlobalPatternsLong = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
116                         GlobalLiterals = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
117                         GlobalSufPref = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
118 #endif
119                         
120                         Matches80Plus = new ArrayList ();
121                         MatchesBelow80 = new ArrayList ();
122                         
123                         FDOMimeConfigReader fmcr = new FDOMimeConfigReader ();
124                         int buffer_length = fmcr.Init ();
125                         
126                         if (buffer_length != -1) {
127                                 buffer = new byte [buffer_length];
128                                 mime_available = true;
129                         }
130                 }
131                 
132                 public static bool MimeAvailable {
133                         get {
134                                 return Instance.mime_available;
135                         }
136                 }
137                 
138                 public static string GetMimeTypeForFile (string filename)
139                 {
140                         lock (lock_object) {
141                                 Instance.StartByFileName (filename);
142                         }
143                         
144                         return Instance.global_result;
145                 }
146                 
147                 // not tested
148                 public static string GetMimeTypeForData (byte[] data)
149                 {
150                         lock (lock_object) {
151                                 Instance.StartDataLookup (data);
152                         }
153                         
154                         return Instance.global_result;
155                 }
156                 
157                 public static string GetMimeTypeForString (string input)
158                 {
159                         lock (lock_object) {
160                                 Instance.StartStringLookup (input);
161                         }
162                         
163                         return Instance.global_result;
164                 }
165                 
166                 public static string GetMimeAlias (string mimetype)
167                 {
168                         return Aliases [mimetype];
169                 }
170                 
171                 public static string GetMimeSubClass (string mimetype)
172                 {
173                         return SubClasses [mimetype];
174                 }
175                 
176                 public static void CleanFileCache ()
177                 {
178                         lock (lock_object) {
179                                 Instance.mime_file_cache.Clear ();
180                         }
181                 }
182                 
183                 private void StartByFileName (string filename)
184                 {
185                         if (mime_file_cache.ContainsKey (filename)) {
186                                 global_result = mime_file_cache [filename];
187                                 return;
188                         }
189                         
190                         current_file_name = filename;
191                         is_zero_file = false;
192                         
193                         global_result = octet_stream;
194                         
195                         GoByFileName ();
196                         
197                         mime_file_cache.Add (current_file_name, global_result);
198                         
199                         if (mime_file_cache.Count > mime_file_cache_max_size) {
200                                 IEnumerator enumerator = mime_file_cache.GetEnumerator ();
201                                 
202                                 int counter = mime_file_cache_max_size - 500;
203                                 
204                                 while (enumerator.MoveNext ()) {
205                                         mime_file_cache.Remove (enumerator.Current.ToString ());
206                                         counter--;
207                                         
208                                         if (counter == 0)
209                                                 break;
210                                 }
211                         }
212                 }
213                 
214                 private void StartDataLookup (byte[] data)
215                 {
216                         global_result = octet_stream;
217                         
218                         System.Array.Clear (buffer, 0, buffer.Length);
219                         
220                         if (data.Length > buffer.Length) {
221                                 System.Array.Copy (data, buffer, buffer.Length);
222                         } else {
223                                 System.Array.Copy (data, buffer, data.Length);
224                         }
225                         
226                         if (CheckMatch80Plus ())
227                                 return;
228                         
229                         if (CheckMatchBelow80 ())
230                                 return;
231                         
232                         CheckForBinaryOrText ();
233                 }
234                 
235                 private void StartStringLookup (string input)
236                 {
237                         global_result = text_plain;
238                         
239                         search_string = input;
240                         
241                         if (CheckForContentTypeString ())
242                                 return;
243                 }
244                 
245                 private void GoByFileName ()
246                 {
247                         // check if we can open the file
248                         if (!OpenFile ()) {
249                                 // couldn't open the file, check globals only
250                                 CheckGlobalPatterns ();
251                                 
252                                 return;
253                         }
254                         
255                         if (!is_zero_file) {
256                                 // check for matches with a priority >= 80
257                                 if (CheckMatch80Plus ())
258                                         return;
259                         }
260                         
261                         // check global patterns, aka file extensions...
262                         // this should be done for zero size files also,
263                         // for example zero size file trash.ccc~ should return
264                         // application/x-trash instead of application/x-zerosize
265                         if (CheckGlobalPatterns ())
266                                 return;
267                         
268                         // if file size is zero, no other checks are needed
269                         if (is_zero_file)
270                                 return;
271                         
272                         // ok, still nothing matches then try matches with a priority < 80
273                         if (CheckMatchBelow80 ())
274                                 return;
275                         
276                         // wow, still nothing... return application/octet-stream for binary data, or text/plain for textual data
277                         CheckForBinaryOrText ();
278                 }
279                 
280                 private bool CheckMatch80Plus ()
281                 {
282                         foreach (Match match in Matches80Plus) {
283                                 if (TestMatch (match)) {
284                                         global_result = match.MimeType;
285                                         
286                                         return true;
287                                 }
288                         }
289                         
290                         return false;
291                 }
292                 
293                 // this little helper method gives us a real speed improvement
294                 private bool FastEndsWidth (string input, string value)
295                 {
296                         if (value.Length > input.Length)
297                                 return false;
298                         
299                         int z = input.Length - 1;
300                         
301                         for (int i = value.Length - 1; i > -1; i--) {
302                                 if (value [i] != input [z])
303                                         return false;
304                                 
305                                 z--;
306                         }
307                         
308                         return true;
309                 }
310                 
311                 private bool FastStartsWith (string input, string value)
312                 {
313                         if (value.Length > input.Length)
314                                 return false;
315                         
316                         for (int i = 0; i < value.Length; i++)
317                                 if (value [i] != input [i])
318                                         return false;
319                         
320                         return true;
321                 }
322                 
323                 // start always with index = 0
324                 private int FastIndexOf (string input, char value)
325                 {
326                         if (input.Length == 0)
327                                 return -1;
328                         
329                         for (int i = 0; i < input.Length; i++)
330                                 if (input [i] == value)
331                                         return i;
332                         
333                         return -1;
334                 }
335                 
336                 private int FastIndexOf (string input, string value)
337                 {
338                         if (input.Length == 0)
339                                 return -1;
340                         
341                         for (int i = 0; i < input.Length - value.Length; i++) {
342                                 if (input [i] == value [0]) {
343                                         int counter = 0;
344                                         for (int z = 1; z < value.Length; z++) {
345                                                 if (input [i + z] != value [z])
346                                                         break;
347                                                 
348                                                 counter++;
349                                         }
350                                         if (counter == value.Length - 1) {
351                                                 return i;
352                                         }
353                                 }
354                         }
355                         
356                         return -1;
357                 }
358                 
359                 private void CheckGlobalResult ()
360                 {
361                         int comma_index = FastIndexOf (global_result, ',');
362                         
363                         if (comma_index != -1) {
364                                 global_result = global_result.Substring (0, comma_index);
365                         }
366                 }
367                 
368                 private bool CheckGlobalPatterns ()
369                 {
370                         string filename = Path.GetFileName (current_file_name);
371                         
372                         // first check for literals
373                         for (int i = 0; i < GlobalLiterals.Count; i++) {
374                                 string key = GlobalLiterals.GetKey (i);
375                                 
376                                 // no regex char
377                                 if (FastIndexOf (key, '[') == -1) {
378                                         if (FastIndexOf (filename, key) != -1) {
379                                                 global_result = GlobalLiterals [i];
380                                                 CheckGlobalResult ();
381                                                 return true;
382                                         }
383                                 } else {
384                                         if (Regex.IsMatch (filename, key)) {
385                                                 global_result = GlobalLiterals [i];
386                                                 CheckGlobalResult ();
387                                                 return true;
388                                         }
389                                 }
390                         }
391                         
392                         if (FastIndexOf (filename, '.') != -1) {
393                                 // check for double extension like .tar.gz
394                                 for (int i = 0; i < GlobalPatternsLong.Count; i++) {
395                                         string key = GlobalPatternsLong.GetKey (i);
396                                         
397                                         if (FastEndsWidth (filename, key)) {
398                                                 global_result = GlobalPatternsLong [i];
399                                                 CheckGlobalResult ();
400                                                 return true;
401                                         } else {
402                                                 if (FastEndsWidth (filename.ToLower (), key)) {
403                                                         global_result = GlobalPatternsLong [i];
404                                                         CheckGlobalResult ();
405                                                         return true;
406                                                 }
407                                         }
408                                 }
409                                 
410                                 // check normal extensions...
411                                 string extension = Path.GetExtension (current_file_name);
412                                 
413                                 if (extension.Length != 0) {
414                                         string global_result_tmp = GlobalPatternsShort [extension];
415                                         
416                                         if (global_result_tmp != null) {
417                                                 global_result = global_result_tmp;
418                                                 CheckGlobalResult ();
419                                                 return true;
420                                         }
421                                         
422                                         global_result_tmp = GlobalPatternsShort [extension.ToLower ()];
423                                         
424                                         if (global_result_tmp != null) {
425                                                 global_result = global_result_tmp;
426                                                 CheckGlobalResult ();
427                                                 return true;
428                                         }
429                                 }
430                         }
431                         
432                         // finally check if a prefix or suffix matches
433                         for (int i = 0; i < GlobalSufPref.Count; i++) {
434                                 string key = GlobalSufPref.GetKey (i);
435                                 
436                                 if (key [0] == '*') {
437                                         if (FastEndsWidth (filename, key.Replace ("*", String.Empty))) {
438                                                 global_result = GlobalSufPref [i];
439                                                 CheckGlobalResult ();
440                                                 return true;
441                                         }
442                                 } else {
443                                         if (FastStartsWith (filename, key.Replace ("*", String.Empty))) {
444                                                 global_result = GlobalSufPref [i];
445                                                 CheckGlobalResult ();
446                                                 return true;
447                                         }
448                                 }
449                         }
450                         
451                         return false;
452                 }
453                 
454                 private bool CheckMatchBelow80 ()
455                 {
456                         foreach (Match match in MatchesBelow80) {
457                                 if (TestMatch (match)) {
458                                         global_result = match.MimeType;
459                                         
460                                         return true;
461                                 }
462                         }
463                         
464                         return false;
465                 }
466                 
467                 private void CheckForBinaryOrText ()
468                 {
469                         // check the first 32 bytes
470                         
471                         for (int i = 0; i < 32; i++) {
472                                 char c = System.Convert.ToChar (buffer [i]);
473                                 
474                                 if (c != '\t' &&  c != '\n' && c != '\r' && c != 12 && c < 32) {
475                                         global_result = octet_stream;
476                                         return;
477                                 }
478                         }
479                         
480                         global_result = text_plain;
481                 }
482                 
483                 private bool TestMatch (Match match)
484                 {
485                         foreach (Matchlet matchlet in match.Matchlets)
486                                 if (TestMatchlet (matchlet))
487                                         return true;
488                         
489                         return false;
490                 }
491                 
492                 private bool TestMatchlet (Matchlet matchlet)
493                 {
494                         //  using a simple brute force search algorithm
495                         // compare each (masked) value from the buffer with the (masked) value from the matchlet
496                         
497                         // no need to check if the offset + the bytevalue length exceed the # bytes read
498                         if (matchlet.Offset + matchlet.ByteValue.Length > bytes_read)
499                                 return false;
500                         
501                         for (int offset_counter = 0; offset_counter < matchlet.OffsetLength; offset_counter++) {
502                                 if (matchlet.Offset + offset_counter + matchlet.ByteValue.Length > bytes_read)
503                                         return false;
504                                 
505                                 if (matchlet.Mask == null) {
506                                         if (buffer [matchlet.Offset + offset_counter] == matchlet.ByteValue [0]) {
507                                                 if (matchlet.ByteValue.Length == 1) {
508                                                         if (matchlet.Matchlets.Count > 0) {
509                                                                 foreach (Matchlet sub_matchlet in matchlet.Matchlets) {
510                                                                         if (TestMatchlet (sub_matchlet))
511                                                                                 return true;
512                                                                 }
513                                                         } else
514                                                                 return true;
515                                                 }
516                                                 
517                                                 int minus = 0;
518                                                 // check if the last matchlet byte value is the same as the byte value in the buffer...
519                                                 if (matchlet.ByteValue.Length > 2) {
520                                                         if (buffer [matchlet.Offset + offset_counter + matchlet.ByteValue.Length - 1] != matchlet.ByteValue [matchlet.ByteValue.Length - 1])
521                                                                 return false;
522                                                         
523                                                         minus = 1;
524                                                 }
525                                                 
526                                                 for (int i = 1; i < matchlet.ByteValue.Length - minus; i++) {
527                                                         if (buffer [matchlet.Offset + offset_counter + i] != matchlet.ByteValue [i])
528                                                                 return false;
529                                                 }
530                                                 
531                                                 if (matchlet.Matchlets.Count > 0) {
532                                                         foreach (Matchlet sub_matchlets in matchlet.Matchlets) {
533                                                                 if (TestMatchlet (sub_matchlets))
534                                                                         return true;
535                                                         }
536                                                 } else
537                                                         return true;
538                                         }
539                                 } else {
540                                         if ((buffer [matchlet.Offset + offset_counter] & matchlet.Mask [0])  ==
541                                             (matchlet.ByteValue [0] & matchlet.Mask [0])) {
542                                                 if (matchlet.ByteValue.Length == 1) {
543                                                         if (matchlet.Matchlets.Count > 0) {
544                                                                 foreach (Matchlet sub_matchlets in matchlet.Matchlets) {
545                                                                         if (TestMatchlet (sub_matchlets))
546                                                                                 return true;
547                                                                 }
548                                                         } else
549                                                                 return true;
550                                                 }
551                                                 
552                                                 int minus = 0;
553                                                 // check if the last matchlet byte value is the same as the byte value in the buffer...
554                                                 if (matchlet.ByteValue.Length > 2) {
555                                                         
556                                                         if ((buffer [matchlet.Offset + offset_counter + matchlet.ByteValue.Length - 1] & matchlet.Mask [matchlet.ByteValue.Length - 1])
557                                                             != (matchlet.ByteValue [matchlet.ByteValue.Length - 1] & matchlet.Mask [matchlet.ByteValue.Length - 1]))
558                                                                 return false;
559                                                         
560                                                         minus = 1;
561                                                 }
562                                                 
563                                                 for (int i = 1; i < matchlet.ByteValue.Length - minus; i++) {
564                                                         if ((buffer [matchlet.Offset + offset_counter + i]  & matchlet.Mask [i]) !=
565                                                             (matchlet.ByteValue [i] & matchlet.Mask [i]))
566                                                                 return false;
567                                                 }
568                                                 
569                                                 if (matchlet.Matchlets.Count > 0) {
570                                                         foreach (Matchlet sub_matchlets in matchlet.Matchlets) {
571                                                                 if (TestMatchlet (sub_matchlets))
572                                                                         return true;
573                                                         }
574                                                 } else
575                                                         return true;
576                                         }
577                                 }
578                         }
579                         
580                         return false;
581                 }
582                 
583                 private bool OpenFile ()
584                 {
585                         try {
586                                 file_stream = new FileStream (current_file_name, FileMode.Open, FileAccess.Read); // FileShare ??? use BinaryReader ???
587                                 
588                                 if (file_stream.Length == 0) {
589                                         global_result = zero_file;
590                                         is_zero_file = true;
591                                 } else {
592                                         bytes_read = file_stream.Read (buffer, 0, buffer.Length);
593                                         
594                                         // do not clear the whole buffer everytime; clear only what's needed
595                                         if (bytes_read < buffer.Length) {
596                                                 System.Array.Clear (buffer, bytes_read, buffer.Length - bytes_read);
597                                         }
598                                 }
599                                 
600                                 file_stream.Close ();
601                         } catch (Exception) {
602                                 return false;
603                         }
604                         
605                         return true;
606                 }
607                 
608                 private bool CheckForContentTypeString ()
609                 {
610                         int index = search_string.IndexOf ("Content-type:");
611                         
612                         if (index != -1) {
613                                 index += 13; // Length of string "Content-type:"
614                                 
615                                 global_result = String.Empty;
616                                 
617                                 while (search_string [index] != ';') {
618                                         global_result += search_string [index++];
619                                 }
620                                 
621                                 global_result.Trim ();
622                                 
623                                 return true;
624                         }
625                         
626                         // convert string to byte array
627                         byte[] string_byte = (new ASCIIEncoding ()).GetBytes (search_string);
628                         
629                         System.Array.Clear (buffer, 0, buffer.Length);
630                         
631                         if (string_byte.Length > buffer.Length) {
632                                 System.Array.Copy (string_byte, buffer, buffer.Length);
633                         } else {
634                                 System.Array.Copy (string_byte, buffer, string_byte.Length);
635                         }
636                         
637                         if (CheckMatch80Plus ())
638                                 return true;
639                         
640                         if (CheckMatchBelow80 ())
641                                 return true;
642                         
643                         return false;
644                 }
645         }
646         
647         internal class FDOMimeConfigReader
648         {
649                 bool fdo_mime_available = false;
650                 StringCollection shared_mime_paths = new StringCollection ();
651                 BinaryReader br;
652                 
653                 int max_offset_and_range = 0;
654                 
655                 public int Init ()
656                 {
657                         CheckFDOMimePaths ();
658                         
659                         if (!fdo_mime_available)
660                                 return -1;
661                         
662                         ReadMagicData ();
663                         
664                         ReadGlobsData ();
665                         
666                         ReadSubclasses ();
667                         
668                         ReadAliases ();
669                         
670                         shared_mime_paths = null;
671                         br = null;
672                         
673                         return max_offset_and_range;
674                 }
675                 
676                 private void CheckFDOMimePaths ()
677                 {
678                         if (Directory.Exists ("/usr/share/mime"))
679                                 shared_mime_paths.Add ("/usr/share/mime/");
680                         else
681                         if (Directory.Exists ("/usr/local/share/mime"))
682                                 shared_mime_paths.Add ("/usr/local/share/mime/");
683                         
684                         if (Directory.Exists (System.Environment.GetFolderPath (Environment.SpecialFolder.Personal) + "/.local/share/mime"))
685                                 shared_mime_paths.Add (System.Environment.GetFolderPath (Environment.SpecialFolder.Personal) + "/.local/share/mime/");
686                         
687                         if (shared_mime_paths.Count == 0)
688                                 return;
689                         
690                         fdo_mime_available = true;
691                 }
692                 
693                 private void ReadMagicData ()
694                 {
695                         foreach (string path in shared_mime_paths) {
696                                 if (!File.Exists (path + "/magic"))
697                                         continue;
698                                 
699                                 try {
700                                         FileStream fs = File.OpenRead (path + "/magic");
701                                         br = new BinaryReader (fs);
702                                         
703                                         if (CheckMagicHeader ()) {
704                                                 MakeMatches ();
705                                         }
706                                         
707                                         br.Close ();
708                                         fs.Close ();
709                                 } catch (Exception ) {
710                                 }
711                         }
712                 }
713                 
714                 private void MakeMatches ()
715                 {
716                         Matchlet[] matchlets = new Matchlet [30];
717                         
718                         while (br.PeekChar () != -1) {
719                                 int priority = -1;
720                                 string mime_type = ReadPriorityAndMimeType (ref priority);
721                                 
722                                 if (mime_type != null) {
723                                         Match match = new Match ();
724                                         match.Priority = priority;
725                                         match.MimeType = mime_type;
726                                         
727                                         while (true) {
728                                                 int indent = 0;
729                                                 // indent
730                                                 char c;
731                                                 if (br.PeekChar () != '>') {
732                                                         StringBuilder indent_string = new StringBuilder ();
733                                                         //string indent_string = String.Empty;
734                                                         while (true) {
735                                                                 if (br.PeekChar () == '>')
736                                                                         break;
737                                                                 
738                                                                 c = br.ReadChar ();
739                                                                 //indent_string += c;
740                                                                 indent_string.Append (c);
741                                                         }
742                                                         indent = Convert.ToInt32 (indent_string.ToString ());
743                                                 }
744                                                 
745                                                 int offset = 0;
746                                                 
747                                                 // offset
748                                                 if (br.PeekChar () == '>') {
749                                                         br.ReadChar ();
750                                                         offset = ReadValue ();
751                                                 }
752                                                 
753                                                 int value_length = 0;
754                                                 byte[] value = null;
755                                                 // value length and value
756                                                 if (br.PeekChar () == '=') {
757                                                         br.ReadChar ();
758                                                         
759                                                         // read 2 bytes value length (always big endian)
760                                                         byte first = br.ReadByte ();
761                                                         byte second = br.ReadByte ();
762                                                         
763                                                         value_length = first * 256 + second;
764                                                         
765                                                         value = br.ReadBytes (value_length);
766                                                 }
767                                                 
768                                                 // mask
769                                                 byte[] mask = null;
770                                                 
771                                                 if (br.PeekChar () == '&') {
772                                                         br.ReadChar ();
773                                                         
774                                                         mask = br.ReadBytes (value_length);
775                                                 }
776                                                 
777                                                 // word_size
778                                                 int word_size = 1;
779                                                 if (br.PeekChar () == '~') {
780                                                         br.ReadChar ();
781                                                         
782                                                         c = br.ReadChar ();
783                                                         
784                                                         word_size = Convert.ToInt32 (c - 0x30);
785                                                         
786                                                         // data is stored in big endian format. 
787                                                         if (word_size > 1 && System.BitConverter.IsLittleEndian) {
788                                                                 //convert the value and, if available, the mask data to little endian
789                                                                 if (word_size == 2) {
790                                                                         if (value != null) {
791                                                                                 for (int i = 0; i < value.Length; i += 2) {
792                                                                                         byte one = value [i];
793                                                                                         byte two = value [i + 1];
794                                                                                         value [i] = two;
795                                                                                         value [i + 1] = one;
796                                                                                 }
797                                                                         }
798                                                                         if (mask != null) {
799                                                                                 for (int i = 0; i < mask.Length; i += 2) {
800                                                                                         byte one = mask [i];
801                                                                                         byte two = mask [i + 1];
802                                                                                         mask [i] = two;
803                                                                                         mask [i + 1] = one;
804                                                                                 }
805                                                                         }
806                                                                 } else if (word_size == 4) {
807                                                                         if (value != null) {
808                                                                                 for (int i = 0; i < value.Length; i += 4) {
809                                                                                         byte one = value [i];
810                                                                                         byte two = value [i + 1];
811                                                                                         byte three = value [i + 2];
812                                                                                         byte four = value [i + 3];
813                                                                                         value [i] = four;
814                                                                                         value [i + 1] = three;
815                                                                                         value [i + 2] = two;
816                                                                                         value [i + 3] = one;
817                                                                                 }
818                                                                         }
819                                                                         if (mask != null) {
820                                                                                 for (int i = 0; i < mask.Length; i += 4) {
821                                                                                         byte one = mask [i];
822                                                                                         byte two = mask [i + 1];
823                                                                                         byte three = mask [i + 2];
824                                                                                         byte four = mask [i + 3];
825                                                                                         mask [i] = four;
826                                                                                         mask [i + 1] = three;
827                                                                                         mask [i + 2] = two;
828                                                                                         mask [i + 3] = one;
829                                                                                         
830                                                                                 }
831                                                                         }
832                                                                 }
833                                                         }
834                                                 }
835                                                 
836                                                 // range length
837                                                 int range_length = 1;
838                                                 if (br.PeekChar () == '+') {
839                                                         br.ReadChar ();
840                                                         range_length = ReadValue ();
841                                                 }
842                                                 
843                                                 // read \n
844                                                 br.ReadChar ();
845                                                 
846                                                 // create the matchlet
847                                                 matchlets [indent] = new Matchlet ();
848                                                 matchlets [indent].Offset = offset;
849                                                 matchlets [indent].OffsetLength = range_length;
850                                                 matchlets [indent].ByteValue = value;
851                                                 if (mask != null)
852                                                         matchlets [indent].Mask = mask;
853                                                 
854                                                 if (indent == 0) {
855                                                         match.Matchlets.Add (matchlets [indent]);
856                                                 } else {
857                                                         matchlets [indent - 1].Matchlets.Add (matchlets [indent]);
858                                                 }
859                                                 
860                                                 if (max_offset_and_range < matchlets [indent].Offset + matchlets [indent].OffsetLength + matchlets [indent].ByteValue.Length + 1)
861                                                         max_offset_and_range = matchlets [indent].Offset + matchlets [indent].OffsetLength + matchlets [indent].ByteValue.Length  + 1;
862                                                 
863                                                 // if '[' move to next mime type
864                                                 if (br.PeekChar () == '[')
865                                                         break;
866                                         }
867                                         
868                                         if (priority < 80)
869                                                 Mime.MatchesBelow80.Add (match);
870                                         else
871                                                 Mime.Matches80Plus.Add (match);
872                                 }
873                         }
874                 }
875                 
876                 private void ReadGlobsData ()
877                 {
878                         foreach (string path in shared_mime_paths) {
879                                 if (!File.Exists (path + "/globs"))
880                                         continue;
881                                 
882                                 try {
883                                         StreamReader sr = new StreamReader (path + "/globs");
884                                         
885                                         while (sr.Peek () != -1) {
886                                                 string line = sr.ReadLine ().Trim ();
887                                                 
888                                                 if (line.StartsWith ("#"))
889                                                         continue;
890                                                 
891                                                 string[] split = line.Split (new char [] {':'});
892                                                 
893                                                 if (split [1].IndexOf ('*') > -1 && split [1].IndexOf ('.') == -1) {
894                                                         Mime.GlobalSufPref.Add (split [1], split [0]);
895                                                 } else if (split [1]. IndexOf ('*') == -1) {
896                                                         Mime.GlobalLiterals.Add (split [1], split [0]);
897                                                 } else {
898                                                         string[] split2 = split [1].Split (new char [] {'.'});
899                                                         
900                                                         if (split2.Length > 2) {
901                                                                 // more than one dot
902                                                                 Mime.GlobalPatternsLong.Add (split [1].Remove (0, 1), split [0]);
903                                                         } else {
904                                                                 // normal
905                                                                 Mime.GlobalPatternsShort.Add (split [1].Remove (0, 1), split [0]);
906                                                         }
907                                                 }
908                                         }
909                                         
910                                         sr.Close ();
911                                 } catch (Exception ) {
912                                 }
913                         }
914                 }
915                 
916                 private void ReadSubclasses ()
917                 {
918                         foreach (string path in shared_mime_paths) {
919                                 if (!File.Exists (path + "/subclasses"))
920                                         continue;
921                                 
922                                 try {
923                                         StreamReader sr = new StreamReader (path + "/subclasses");
924                                         
925                                         while (sr.Peek () != -1) {
926                                                 string line = sr.ReadLine ().Trim ();
927                                                 
928                                                 if (line.StartsWith ("#"))
929                                                         continue;
930                                                 
931                                                 string[] split = line.Split (new char [] {' '});
932                                                 
933                                                 Mime.SubClasses.Add (split [0], split [1]);
934                                         }
935                                         
936                                         sr.Close ();
937                                 } catch (Exception ) {
938                                 }
939                         }
940                 }
941                 
942                 private void ReadAliases ()
943                 {
944                         foreach (string path in shared_mime_paths) {
945                                 if (!File.Exists (path + "/aliases"))
946                                         continue;
947                                 
948                                 try {
949                                         StreamReader sr = new StreamReader (path + "/aliases");
950                                         
951                                         while (sr.Peek () != -1) {
952                                                 string line = sr.ReadLine ().Trim ();
953                                                 
954                                                 if (line.StartsWith ("#"))
955                                                         continue;
956                                                 
957                                                 string[] split = line.Split (new char [] {' '});
958                                                 
959                                                 Mime.Aliases.Add (split [0], split [1]);
960                                         }
961                                         
962                                         sr.Close ();
963                                 } catch (Exception ) {
964                                 }
965                         }
966                 }
967                 
968                 private int ReadValue ()
969                 {
970                         StringBuilder result_string = new StringBuilder ();
971                         int result = 0;
972                         char c;
973                         
974                         while (true) {
975                                 if (br.PeekChar () == '=' || br.PeekChar () == '\n')
976                                         break;
977                                 
978                                 c = br.ReadChar ();
979                                 result_string.Append (c);
980                         }
981                         
982                         result = Convert.ToInt32 (result_string.ToString ());
983                         
984                         return result;
985                 }
986                 
987                 private string ReadPriorityAndMimeType (ref int priority)
988                 {
989                         if (br.ReadChar () == '[') {
990                                 StringBuilder priority_string = new StringBuilder ();
991                                 while (true) {
992                                         char c = br.ReadChar ();
993                                         if (c == ':')
994                                                 break;
995                                         priority_string.Append (c);
996                                 }
997                                 
998                                 priority = System.Convert.ToInt32 (priority_string.ToString ());
999                                 
1000                                 StringBuilder mime_type_result = new StringBuilder ();
1001                                 while (true) {
1002                                         char c = br.ReadChar ();
1003                                         if (c == ']')
1004                                                 break;
1005                                         
1006                                         mime_type_result.Append (c);
1007                                 }
1008                                 
1009                                 if (br.ReadChar () == '\n')
1010                                         return mime_type_result.ToString ();
1011                         }
1012                         return null;
1013                 }
1014                 
1015                 private bool CheckMagicHeader ()
1016                 {
1017                         char[] chars = br.ReadChars (10);
1018                         string magic_header = new String (chars);
1019                         
1020                         if (magic_header != "MIME-Magic")
1021                                 return false;
1022                         
1023                         if (br.ReadByte () != 0)
1024                                 return false;
1025                         if (br.ReadChar () != '\n')
1026                                 return false;
1027                         
1028                         return true;
1029                 }
1030         }
1031         
1032         internal class Match
1033         {
1034                 string mimeType;
1035                 int priority;
1036                 ArrayList matchlets = new ArrayList();
1037                 
1038                 public string MimeType {
1039                         set {
1040                                 mimeType = value;
1041                         }
1042                         
1043                         get {
1044                                 return mimeType;
1045                         }
1046                 }
1047                 
1048                 public int Priority {
1049                         set {
1050                                 priority = value;
1051                         }
1052                         
1053                         get {
1054                                 return priority;
1055                         }
1056                 }
1057                 
1058                 public ArrayList Matchlets {
1059                         get {
1060                                 return matchlets;
1061                         }
1062                 }
1063         }
1064         
1065         internal class Matchlet
1066         {
1067                 byte[] byteValue;
1068                 byte[] mask = null;
1069                 
1070                 int offset;
1071                 int offsetLength;
1072                 int wordSize = 1;
1073                 
1074                 ArrayList matchlets = new ArrayList ();
1075                 
1076                 public byte[] ByteValue {
1077                         set {
1078                                 byteValue = value;
1079                         }
1080                         
1081                         get {
1082                                 return byteValue;
1083                         }
1084                 }
1085                 
1086                 public byte[] Mask {
1087                         set {
1088                                 mask = value;
1089                         }
1090                         
1091                         get {
1092                                 return mask;
1093                         }
1094                 }
1095                 
1096                 public int Offset {
1097                         set {
1098                                 offset = value;
1099                         }
1100                         
1101                         get {
1102                                 return offset;
1103                         }
1104                 }
1105                 
1106                 public int OffsetLength {
1107                         set {
1108                                 offsetLength = value;
1109                         }
1110                         
1111                         get {
1112                                 return offsetLength;
1113                         }
1114                 }
1115                 
1116                 public int WordSize {
1117                         set {
1118                                 wordSize = value;
1119                         }
1120                         
1121                         get {
1122                                 return wordSize;
1123                         }
1124                 }
1125                 
1126                 public ArrayList Matchlets {
1127                         get {
1128                                 return matchlets;
1129                         }
1130                 }
1131         }
1132 }
1133