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