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