1 /////////////////////////////////////////////////////////////////////////////
2 // Name: unix/mimetype.cpp
3 // Purpose: classes and functions to manage MIME types
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
21 #pragma implementation "mimetype.h"
24 // for compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
35 #if wxUSE_FILE && wxUSE_TEXTFILE
38 #include "wx/string.h"
48 #include "wx/dynarray.h"
49 #include "wx/confbase.h"
52 #include "wx/textfile.h"
55 #include "wx/tokenzr.h"
57 #include "wx/unix/mimetype.h"
59 // other standard headers
62 // in case we're compiling in non-GUI mode
63 class WXDLLEXPORT wxIcon
;
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 // MIME code tracing mask
70 #define TRACE_MIME _T("mime")
72 // ----------------------------------------------------------------------------
74 // ----------------------------------------------------------------------------
76 // there are some fields which we don't understand but for which we don't give
77 // warnings as we know that they're not important - this function is used to
79 static bool IsKnownUnimportantField(const wxString
& field
);
81 // ----------------------------------------------------------------------------
83 // ----------------------------------------------------------------------------
85 // This class uses both mailcap and mime.types to gather information about file
88 // The information about mailcap file was extracted from metamail(1) sources
89 // and documentation and subsequently revised when I found the RFC 1524
92 // Format of mailcap file: spaces are ignored, each line is either a comment
93 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
94 // A backslash can be used to quote semicolons and newlines (and, in fact,
95 // anything else including itself).
97 // The first field is always the MIME type in the form of type/subtype (see RFC
98 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
99 // "type" which means the same as "type/*", although I'm not sure whether this
102 // The second field is always the command to run. It is subject to
103 // parameter/filename expansion described below.
105 // All the following fields are optional and may not be present at all. If
106 // they're present they may appear in any order, although each of them should
107 // appear only once. The optional fields are the following:
108 // * notes=xxx is an uninterpreted string which is silently ignored
109 // * test=xxx is the command to be used to determine whether this mailcap line
110 // applies to our data or not. The RHS of this field goes through the
111 // parameter/filename expansion (as the 2nd field) and the resulting string
112 // is executed. The line applies only if the command succeeds, i.e. returns 0
114 // * print=xxx is the command to be used to print (and not view) the data of
115 // this type (parameter/filename expansion is done here too)
116 // * edit=xxx is the command to open/edit the data of this type
117 // * needsterminal means that a new interactive console must be created for
119 // * copiousoutput means that the viewer doesn't interact with the user but
120 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
121 // good example), thus it might be a good idea to use some kind of paging
123 // * textualnewlines means not to perform CR/LF translation (not honored)
124 // * compose and composetyped fields are used to determine the program to be
125 // called to create a new message pert in the specified format (unused).
127 // Parameter/filename expansion:
128 // * %s is replaced with the (full) file name
129 // * %t is replaced with MIME type/subtype of the entry
130 // * for multipart type only %n is replaced with the nnumber of parts and %F is
131 // replaced by an array of (content-type, temporary file name) pairs for all
132 // message parts (TODO)
133 // * %{parameter} is replaced with the value of parameter taken from
134 // Content-type header line of the message.
137 // There are 2 possible formats for mime.types file, one entry per line (used
138 // for global mime.types and called Mosaic format) and "expanded" format where
139 // an entry takes multiple lines (used for users mime.types and called
142 // For both formats spaces are ignored and lines starting with a '#' are
143 // comments. Each record has one of two following forms:
144 // a) for "brief" format:
145 // <mime type> <space separated list of extensions>
146 // b) for "expanded" format:
147 // type=<mime type> \
148 // desc="<description>" \
149 // exts="<comma separated list of extensions>"
151 // We try to autodetect the format of mime.types: if a non-comment line starts
152 // with "type=" we assume the second format, otherwise the first one.
154 // there may be more than one entry for one and the same mime type, to
155 // choose the right one we have to run the command specified in the test
156 // field on our data.
161 MailCapEntry(const wxString
& openCmd
,
162 const wxString
& printCmd
,
163 const wxString
& testCmd
)
164 : m_openCmd(openCmd
), m_printCmd(printCmd
), m_testCmd(testCmd
)
171 if (m_next
) delete m_next
;
175 const wxString
& GetOpenCmd() const { return m_openCmd
; }
176 const wxString
& GetPrintCmd() const { return m_printCmd
; }
177 const wxString
& GetTestCmd() const { return m_testCmd
; }
179 MailCapEntry
*GetNext() const { return m_next
; }
182 // prepend this element to the list
183 void Prepend(MailCapEntry
*next
) { m_next
= next
; }
184 // insert into the list at given position
185 void Insert(MailCapEntry
*next
, size_t pos
)
190 for ( cur
= next
; cur
!= NULL
; cur
= cur
->m_next
, n
++ ) {
195 wxASSERT_MSG( n
== pos
, wxT("invalid position in MailCapEntry::Insert") );
197 m_next
= cur
->m_next
;
200 // append this element to the list
201 void Append(MailCapEntry
*next
)
203 wxCHECK_RET( next
!= NULL
, wxT("Append()ing to what?") );
207 for ( cur
= next
; cur
->m_next
!= NULL
; cur
= cur
->m_next
)
212 wxASSERT_MSG( !m_next
, wxT("Append()ing element already in the list?") );
216 wxString m_openCmd
, // command to use to open/view the file
218 m_testCmd
; // only apply this entry if test yields
219 // true (i.e. the command returns 0)
221 MailCapEntry
*m_next
; // in the linked list
225 // the base class which may be used to find an icon for the MIME type
226 class wxMimeTypeIconHandler
229 virtual bool GetIcon(const wxString
& mimetype
, wxIcon
*icon
) = 0;
231 // this function fills manager with MIME types information gathered
232 // (as side effect) when searching for icons. This may be particularly
233 // useful if mime.types is incomplete (e.g. RedHat distributions).
234 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
) = 0;
238 // the icon handler which uses GNOME MIME database
239 class wxGNOMEIconHandler
: public wxMimeTypeIconHandler
242 virtual bool GetIcon(const wxString
& mimetype
, wxIcon
*icon
);
243 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
);
247 void LoadIconsFromKeyFile(const wxString
& filename
);
248 void LoadKeyFilesFromDir(const wxString
& dirbase
);
250 void LoadMimeTypesFromMimeFile(const wxString
& filename
, wxMimeTypesManagerImpl
*manager
);
251 void LoadMimeFilesFromDir(const wxString
& dirbase
, wxMimeTypesManagerImpl
*manager
);
253 static bool m_inited
;
255 static wxSortedArrayString ms_mimetypes
;
256 static wxArrayString ms_icons
;
259 // the icon handler which uses KDE MIME database
260 class wxKDEIconHandler
: public wxMimeTypeIconHandler
263 virtual bool GetIcon(const wxString
& mimetype
, wxIcon
*icon
);
264 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
);
267 void LoadLinksForMimeSubtype(const wxString
& dirbase
,
268 const wxString
& subdir
,
269 const wxString
& filename
,
270 const wxArrayString
& icondirs
);
271 void LoadLinksForMimeType(const wxString
& dirbase
,
272 const wxString
& subdir
,
273 const wxArrayString
& icondirs
);
274 void LoadLinkFilesFromDir(const wxString
& dirbase
,
275 const wxArrayString
& icondirs
);
278 static bool m_inited
;
280 static wxSortedArrayString ms_mimetypes
;
281 static wxArrayString ms_icons
;
283 static wxArrayString ms_infoTypes
;
284 static wxArrayString ms_infoDescriptions
;
285 static wxArrayString ms_infoExtensions
;
290 // ----------------------------------------------------------------------------
292 // ----------------------------------------------------------------------------
294 static wxGNOMEIconHandler gs_iconHandlerGNOME
;
295 static wxKDEIconHandler gs_iconHandlerKDE
;
297 bool wxGNOMEIconHandler::m_inited
= FALSE
;
298 wxSortedArrayString
wxGNOMEIconHandler::ms_mimetypes
;
299 wxArrayString
wxGNOMEIconHandler::ms_icons
;
301 bool wxKDEIconHandler::m_inited
= FALSE
;
302 wxSortedArrayString
wxKDEIconHandler::ms_mimetypes
;
303 wxArrayString
wxKDEIconHandler::ms_icons
;
305 wxArrayString
wxKDEIconHandler::ms_infoTypes
;
306 wxArrayString
wxKDEIconHandler::ms_infoDescriptions
;
307 wxArrayString
wxKDEIconHandler::ms_infoExtensions
;
310 ArrayIconHandlers
wxMimeTypesManagerImpl::ms_iconHandlers
;
312 // ----------------------------------------------------------------------------
313 // wxGNOMEIconHandler
314 // ----------------------------------------------------------------------------
316 // GNOME stores the info we're interested in in several locations:
317 // 1. xxx.keys files under /usr/share/mime-info
318 // 2. xxx.keys files under ~/.gnome/mime-info
320 // The format of xxx.keys file is the following:
325 // with blank lines separating the entries and indented lines starting with
326 // TABs. We're interested in the field icon-filename whose value is the path
327 // containing the icon.
329 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
330 // just before the field name.
332 void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString
& filename
)
334 wxTextFile
textfile(filename
);
335 if ( !textfile
.Open() )
338 // values for the entry being parsed
339 wxString curMimeType
, curIconFile
;
342 size_t nLineCount
= textfile
.GetLineCount();
343 for ( size_t nLine
= 0; ; nLine
++ )
345 if ( nLine
< nLineCount
)
347 pc
= textfile
[nLine
].c_str();
348 if ( *pc
== _T('#') )
356 // so that we will fall into the "if" below
363 if ( !!curMimeType
&& !!curIconFile
)
365 // do we already know this mimetype?
366 int i
= ms_mimetypes
.Index(curMimeType
);
367 if ( i
== wxNOT_FOUND
)
370 size_t n
= ms_mimetypes
.Add(curMimeType
);
371 ms_icons
.Insert(curIconFile
, n
);
375 // replace the existing one (this means that the directories
376 // should be searched in order of increased priority!)
377 ms_icons
[(size_t)i
] = curIconFile
;
383 // the end - this can only happen if nLine == nLineCount
392 // what do we have here?
393 if ( *pc
== _T('\t') )
395 // this is a field=value ling
396 pc
++; // skip leading TAB
398 // skip optional "[lang]"
399 if ( *pc
== _T('[') )
403 if ( *pc
++ == _T(']') )
408 static const int lenField
= 13; // strlen("icon-filename")
409 if ( wxStrncmp(pc
, _T("icon-filename"), lenField
) == 0 )
411 // skip '=' which follows and take everything left until the end
413 curIconFile
= pc
+ lenField
+ 1;
415 //else: some other field, we don't care
419 // this is the start of the new section
422 while ( *pc
!= _T(':') && *pc
!= _T('\0') )
424 curMimeType
+= *pc
++;
430 void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString
& dirbase
)
432 wxASSERT_MSG( !!dirbase
&& !wxEndsWithPathSeparator(dirbase
),
433 _T("base directory shouldn't end with a slash") );
435 wxString dirname
= dirbase
;
436 dirname
<< _T("/mime-info");
438 if ( !wxDir::Exists(dirname
) )
442 if ( !dir
.IsOpened() )
445 // we will concatenate it with filename to get the full path below
449 bool cont
= dir
.GetFirst(&filename
, _T("*.keys"), wxDIR_FILES
);
452 LoadIconsFromKeyFile(dirname
+ filename
);
454 cont
= dir
.GetNext(&filename
);
459 void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString
& filename
, wxMimeTypesManagerImpl
*manager
)
461 wxTextFile
textfile(filename
);
462 if ( !textfile
.Open() )
465 // values for the entry being parsed
466 wxString curMimeType
, curExtList
;
469 size_t nLineCount
= textfile
.GetLineCount();
470 for ( size_t nLine
= 0; ; nLine
++ )
472 if ( nLine
< nLineCount
)
474 pc
= textfile
[nLine
].c_str();
475 if ( *pc
== _T('#') )
483 // so that we will fall into the "if" below
490 if ( !!curMimeType
&& !!curExtList
)
492 manager
-> AddMimeTypeInfo(curMimeType
, curExtList
, wxEmptyString
);
497 // the end - this can only happen if nLine == nLineCount
506 // what do we have here?
507 if ( *pc
== _T('\t') )
509 // this is a field=value ling
510 pc
++; // skip leading TAB
512 static const int lenField
= 4; // strlen("ext:")
513 if ( wxStrncmp(pc
, _T("ext:"), lenField
) == 0 )
515 // skip ' ' which follows and take everything left until the end
517 curExtList
= pc
+ lenField
+ 1;
519 //else: some other field, we don't care
523 // this is the start of the new section
526 while ( *pc
!= _T(':') && *pc
!= _T('\0') )
528 curMimeType
+= *pc
++;
535 void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString
& dirbase
, wxMimeTypesManagerImpl
*manager
)
537 wxASSERT_MSG( !!dirbase
&& !wxEndsWithPathSeparator(dirbase
),
538 _T("base directory shouldn't end with a slash") );
540 wxString dirname
= dirbase
;
541 dirname
<< _T("/mime-info");
543 if ( !wxDir::Exists(dirname
) )
547 if ( !dir
.IsOpened() )
550 // we will concatenate it with filename to get the full path below
554 bool cont
= dir
.GetFirst(&filename
, _T("*.mime"), wxDIR_FILES
);
557 LoadMimeTypesFromMimeFile(dirname
+ filename
, manager
);
559 cont
= dir
.GetNext(&filename
);
564 void wxGNOMEIconHandler::Init()
567 dirs
.Add(_T("/usr/share"));
568 dirs
.Add(_T("/usr/local/share"));
571 wxGetHomeDir( &gnomedir
);
572 gnomedir
+= _T("/.gnome");
573 dirs
.Add( gnomedir
);
575 size_t nDirs
= dirs
.GetCount();
576 for ( size_t nDir
= 0; nDir
< nDirs
; nDir
++ )
578 LoadKeyFilesFromDir(dirs
[nDir
]);
585 void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
)
593 dirs
.Add(_T("/usr/share"));
594 dirs
.Add(_T("/usr/local/share"));
597 wxGetHomeDir( &gnomedir
);
598 gnomedir
+= _T("/.gnome");
599 dirs
.Add( gnomedir
);
601 size_t nDirs
= dirs
.GetCount();
602 for ( size_t nDir
= 0; nDir
< nDirs
; nDir
++ )
604 LoadMimeFilesFromDir(dirs
[nDir
], manager
);
609 #define WXUNUSED_UNLESS_GUI(p) p
611 #define WXUNUSED_UNLESS_GUI(p)
614 bool wxGNOMEIconHandler::GetIcon(const wxString
& mimetype
,
615 wxIcon
* WXUNUSED_UNLESS_GUI(icon
))
622 int index
= ms_mimetypes
.Index(mimetype
);
623 if ( index
== wxNOT_FOUND
)
626 wxString iconname
= ms_icons
[(size_t)index
];
631 if (iconname
.Right(4).MakeUpper() == _T(".XPM"))
632 icn
= wxIcon(iconname
);
634 icn
= wxIcon(iconname
, wxBITMAP_TYPE_ANY
);
641 // helpful for testing in console mode
642 wxLogTrace(TRACE_MIME
, _T("Found GNOME icon for '%s': '%s'\n"),
643 mimetype
.c_str(), iconname
.c_str());
649 // ----------------------------------------------------------------------------
651 // ----------------------------------------------------------------------------
653 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
654 // may be found in either of the following locations
656 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
657 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
659 // The format of a .kdelnk file is almost the same as the one used by
660 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
661 // value for the entry "Type"
663 void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString
& dirbase
,
664 const wxString
& subdir
,
665 const wxString
& filename
,
666 const wxArrayString
& icondirs
)
668 wxFFile
file(dirbase
+ filename
);
669 if ( !file
.IsOpened() )
672 // construct mimetype from the directory name and the basename of the
673 // file (it always has .kdelnk extension)
675 mimetype
<< subdir
<< _T('/') << filename
.BeforeLast(_T('.'));
677 // these files are small, slurp the entire file at once
679 if ( !file
.ReadAll(&text
) )
685 // before trying to find an icon, grab mimetype information
686 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
687 // have edited it in control panel...)
689 wxString mime_extension
, mime_desc
;
692 if (wxGetLocale() != NULL
)
693 mime_desc
= _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
694 if (pos
== wxNOT_FOUND
) mime_desc
= _T("Comment=");
695 pos
= text
.Find(mime_desc
);
696 if (pos
== wxNOT_FOUND
) mime_desc
= wxEmptyString
;
699 pc
= text
.c_str() + pos
+ mime_desc
.Length();
700 mime_desc
= wxEmptyString
;
701 while ( *pc
&& *pc
!= _T('\n') ) mime_desc
+= *pc
++;
704 pos
= text
.Find(_T("Patterns="));
705 if (pos
!= wxNOT_FOUND
)
708 pc
= text
.c_str() + pos
+ 9;
709 while ( *pc
&& *pc
!= _T('\n') ) exts
+= *pc
++;
710 wxStringTokenizer
tokenizer(exts
, _T(";"));
713 while (tokenizer
.HasMoreTokens())
715 e
= tokenizer
.GetNextToken();
716 if (e
.Left(2) != _T("*.")) continue; // don't support too difficult patterns
717 mime_extension
<< e
.Mid(2);
718 mime_extension
<< _T(' ');
720 mime_extension
.RemoveLast();
723 ms_infoTypes
.Add(mimetype
);
724 ms_infoDescriptions
.Add(mime_desc
);
725 ms_infoExtensions
.Add(mime_extension
);
727 // ok, now we can take care of icon:
729 pos
= text
.Find(_T("Icon="));
730 if ( pos
== wxNOT_FOUND
)
738 pc
= text
.c_str() + pos
+ 5; // 5 == strlen("Icon=")
739 while ( *pc
&& *pc
!= _T('\n') )
746 // we must check if the file exists because it may be stored
747 // in many locations, at least ~/.kde and $KDEDIR
748 size_t nDir
, nDirs
= icondirs
.GetCount();
749 for ( nDir
= 0; nDir
< nDirs
; nDir
++ )
750 if (wxFileExists(icondirs
[nDir
] + icon
))
752 icon
.Prepend(icondirs
[nDir
]);
755 if (nDir
== nDirs
) return; //does not exist
757 // do we already have this MIME type?
758 int i
= ms_mimetypes
.Index(mimetype
);
759 if ( i
== wxNOT_FOUND
)
762 size_t n
= ms_mimetypes
.Add(mimetype
);
763 ms_icons
.Insert(icon
, n
);
767 // replace the old value
768 ms_icons
[(size_t)i
] = icon
;
773 void wxKDEIconHandler::LoadLinksForMimeType(const wxString
& dirbase
,
774 const wxString
& subdir
,
775 const wxArrayString
& icondirs
)
777 wxString dirname
= dirbase
;
780 if ( !dir
.IsOpened() )
786 bool cont
= dir
.GetFirst(&filename
, _T("*.kdelnk"), wxDIR_FILES
);
789 LoadLinksForMimeSubtype(dirname
, subdir
, filename
, icondirs
);
791 cont
= dir
.GetNext(&filename
);
795 void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString
& dirbase
,
796 const wxArrayString
& icondirs
)
798 wxASSERT_MSG( !!dirbase
&& !wxEndsWithPathSeparator(dirbase
),
799 _T("base directory shouldn't end with a slash") );
801 wxString dirname
= dirbase
;
802 dirname
<< _T("/mimelnk");
804 if ( !wxDir::Exists(dirname
) )
808 if ( !dir
.IsOpened() )
811 // we will concatenate it with dir name to get the full path below
815 bool cont
= dir
.GetFirst(&subdir
, wxEmptyString
, wxDIR_DIRS
);
818 LoadLinksForMimeType(dirname
, subdir
, icondirs
);
820 cont
= dir
.GetNext(&subdir
);
824 void wxKDEIconHandler::Init()
827 wxArrayString icondirs
;
829 // settings in ~/.kde have maximal priority
830 dirs
.Add(wxGetHomeDir() + _T("/.kde/share"));
831 icondirs
.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
833 // the variable KDEDIR is set when KDE is running
834 const char *kdedir
= getenv("KDEDIR");
837 dirs
.Add(wxString(kdedir
) + _T("/share"));
838 icondirs
.Add(wxString(kdedir
) + _T("/share/icons/"));
842 // try to guess KDEDIR
843 dirs
.Add(_T("/usr/share"));
844 dirs
.Add(_T("/opt/kde/share"));
845 icondirs
.Add(_T("/usr/share/icons/"));
846 icondirs
.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
847 icondirs
.Add(_T("/opt/kde/share/icons/"));
850 size_t nDirs
= dirs
.GetCount();
851 for ( size_t nDir
= 0; nDir
< nDirs
; nDir
++ )
853 LoadLinkFilesFromDir(dirs
[nDir
], icondirs
);
859 bool wxKDEIconHandler::GetIcon(const wxString
& mimetype
,
860 wxIcon
* WXUNUSED_UNLESS_GUI(icon
))
867 int index
= ms_mimetypes
.Index(mimetype
);
868 if ( index
== wxNOT_FOUND
)
871 wxString iconname
= ms_icons
[(size_t)index
];
876 if (iconname
.Right(4).MakeUpper() == _T(".XPM"))
877 icn
= wxIcon(iconname
);
879 icn
= wxIcon(iconname
, wxBITMAP_TYPE_ANY
);
887 // helpful for testing in console mode
888 wxLogTrace(TRACE_MIME
, _T("Found KDE icon for '%s': '%s'\n"),
889 mimetype
.c_str(), iconname
.c_str());
896 void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
)
898 if ( !m_inited
) Init();
900 size_t cnt
= ms_infoTypes
.GetCount();
901 for (unsigned i
= 0; i
< cnt
; i
++)
902 manager
-> AddMimeTypeInfo(ms_infoTypes
[i
], ms_infoExtensions
[i
], ms_infoDescriptions
[i
]);
906 // ----------------------------------------------------------------------------
907 // wxFileTypeImpl (Unix)
908 // ----------------------------------------------------------------------------
911 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters
& params
) const
914 MailCapEntry
*entry
= m_manager
->m_aEntries
[m_index
[0]];
915 while ( entry
!= NULL
) {
916 // get the command to run as the test for this entry
917 command
= wxFileType::ExpandCommand(entry
->GetTestCmd(), params
);
919 // don't trace the test result if there is no test at all
920 if ( command
.IsEmpty() )
922 // no test at all, ok
926 if ( wxSystem(command
) == 0 ) {
928 wxLogTrace(TRACE_MIME
,
929 wxT("Test '%s' for mime type '%s' succeeded."),
930 command
.c_str(), params
.GetMimeType().c_str());
934 wxLogTrace(TRACE_MIME
,
935 wxT("Test '%s' for mime type '%s' failed."),
936 command
.c_str(), params
.GetMimeType().c_str());
939 entry
= entry
->GetNext();
945 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
947 wxArrayString mimetypes
;
948 GetMimeTypes(mimetypes
);
950 ArrayIconHandlers
& handlers
= m_manager
->GetIconHandlers();
951 size_t count
= handlers
.GetCount();
952 size_t counttypes
= mimetypes
.GetCount();
953 for ( size_t n
= 0; n
< count
; n
++ )
955 for ( size_t n2
= 0; n2
< counttypes
; n2
++ )
957 if ( handlers
[n
]->GetIcon(mimetypes
[n2
], icon
) )
967 wxFileTypeImpl::GetMimeTypes(wxArrayString
& mimeTypes
) const
970 for (size_t i
= 0; i
< m_index
.GetCount(); i
++)
971 mimeTypes
.Add(m_manager
->m_aTypes
[m_index
[i
]]);
977 wxFileTypeImpl::GetExpandedCommand(wxString
*expandedCmd
,
978 const wxFileType::MessageParameters
& params
,
981 MailCapEntry
*entry
= GetEntry(params
);
982 if ( entry
== NULL
) {
983 // all tests failed...
987 wxString cmd
= open
? entry
->GetOpenCmd() : entry
->GetPrintCmd();
988 if ( cmd
.IsEmpty() ) {
989 // may happen, especially for "print"
993 *expandedCmd
= wxFileType::ExpandCommand(cmd
, params
);
997 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
999 wxString strExtensions
= m_manager
->GetExtension(m_index
[0]);
1002 // one extension in the space or comma delimitid list
1004 for ( const wxChar
*p
= strExtensions
; ; p
++ ) {
1005 if ( *p
== wxT(' ') || *p
== wxT(',') || *p
== wxT('\0') ) {
1006 if ( !strExt
.IsEmpty() ) {
1007 extensions
.Add(strExt
);
1010 //else: repeated spaces (shouldn't happen, but it's not that
1011 // important if it does happen)
1013 if ( *p
== wxT('\0') )
1016 else if ( *p
== wxT('.') ) {
1017 // remove the dot from extension (but only if it's the first char)
1018 if ( !strExt
.IsEmpty() ) {
1021 //else: no, don't append it
1031 // ----------------------------------------------------------------------------
1032 // wxMimeTypesManagerImpl (Unix)
1033 // ----------------------------------------------------------------------------
1036 ArrayIconHandlers
& wxMimeTypesManagerImpl::GetIconHandlers()
1038 if ( ms_iconHandlers
.GetCount() == 0 )
1040 ms_iconHandlers
.Add(&gs_iconHandlerGNOME
);
1041 ms_iconHandlers
.Add(&gs_iconHandlerKDE
);
1044 return ms_iconHandlers
;
1047 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1049 m_initialized
= FALSE
;
1052 // read system and user mailcaps and other files
1053 void wxMimeTypesManagerImpl::Initialize()
1055 // directories where we look for mailcap and mime.types by default
1056 // (taken from metamail(1) sources)
1058 // although RFC 1524 specifies the search path of
1059 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1060 // places - OTOH, the RFC also says that this path can be changed with
1061 // MAILCAPS environment variable (containing the colon separated full
1062 // filenames to try) which is not done yet (TODO?)
1063 static const wxChar
*aStandardLocations
[] =
1067 wxT("/usr/local/etc"),
1069 wxT("/usr/public/lib")
1072 // first read the system wide file(s)
1074 for ( n
= 0; n
< WXSIZEOF(aStandardLocations
); n
++ ) {
1075 wxString dir
= aStandardLocations
[n
];
1077 wxString file
= dir
+ wxT("/mailcap");
1078 if ( wxFile::Exists(file
) ) {
1082 file
= dir
+ wxT("/mime.types");
1083 if ( wxFile::Exists(file
) ) {
1084 ReadMimeTypes(file
);
1088 wxString strHome
= wxGetenv(wxT("HOME"));
1090 // and now the users mailcap
1091 wxString strUserMailcap
= strHome
+ wxT("/.mailcap");
1092 if ( wxFile::Exists(strUserMailcap
) ) {
1093 ReadMailcap(strUserMailcap
);
1096 // read the users mime.types
1097 wxString strUserMimeTypes
= strHome
+ wxT("/.mime.types");
1098 if ( wxFile::Exists(strUserMimeTypes
) ) {
1099 ReadMimeTypes(strUserMimeTypes
);
1102 // read KDE/GNOME tables
1103 ArrayIconHandlers
& handlers
= GetIconHandlers();
1104 size_t count
= handlers
.GetCount();
1105 for ( size_t hn
= 0; hn
< count
; hn
++ )
1106 handlers
[hn
]->GetMimeInfoRecords(this);
1110 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1112 size_t cnt
= m_aEntries
.GetCount();
1113 for (size_t i
= 0; i
< cnt
; i
++)
1114 delete m_aEntries
[i
];
1118 wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo
& ftInfo
)
1120 wxFAIL_MSG( _T("unimplemented") ); // TODO
1126 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
1130 wxFileType
*fileType
= NULL
;
1131 size_t count
= m_aExtensions
.GetCount();
1132 for ( size_t n
= 0; n
< count
; n
++ ) {
1133 wxString extensions
= m_aExtensions
[n
];
1134 while ( !extensions
.IsEmpty() ) {
1135 wxString field
= extensions
.BeforeFirst(wxT(' '));
1136 extensions
= extensions
.AfterFirst(wxT(' '));
1138 // consider extensions as not being case-sensitive
1139 if ( field
.IsSameAs(ext
, FALSE
/* no case */) ) {
1141 if (fileType
== NULL
) fileType
= new wxFileType
;
1142 fileType
->m_impl
->Init(this, n
);
1143 // adds this mime type to _list_ of mime types with this extension
1152 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
1156 // mime types are not case-sensitive
1157 wxString
mimetype(mimeType
);
1158 mimetype
.MakeLower();
1160 // first look for an exact match
1161 int index
= m_aTypes
.Index(mimetype
);
1162 if ( index
== wxNOT_FOUND
) {
1163 // then try to find "text/*" as match for "text/plain" (for example)
1164 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1165 // the whole string - ok.
1166 wxString strCategory
= mimetype
.BeforeFirst(wxT('/'));
1168 size_t nCount
= m_aTypes
.Count();
1169 for ( size_t n
= 0; n
< nCount
; n
++ ) {
1170 if ( (m_aTypes
[n
].BeforeFirst(wxT('/')) == strCategory
) &&
1171 m_aTypes
[n
].AfterFirst(wxT('/')) == wxT("*") ) {
1178 if ( index
!= wxNOT_FOUND
) {
1179 wxFileType
*fileType
= new wxFileType
;
1180 fileType
->m_impl
->Init(this, index
);
1190 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo
& filetype
)
1194 wxString extensions
;
1195 const wxArrayString
& exts
= filetype
.GetExtensions();
1196 size_t nExts
= exts
.GetCount();
1197 for ( size_t nExt
= 0; nExt
< nExts
; nExt
++ ) {
1199 extensions
+= wxT(' ');
1201 extensions
+= exts
[nExt
];
1204 AddMimeTypeInfo(filetype
.GetMimeType(),
1206 filetype
.GetDescription());
1208 AddMailcapInfo(filetype
.GetMimeType(),
1209 filetype
.GetOpenCommand(),
1210 filetype
.GetPrintCommand(),
1212 filetype
.GetDescription());
1215 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString
& strMimeType
,
1216 const wxString
& strExtensions
,
1217 const wxString
& strDesc
)
1221 int index
= m_aTypes
.Index(strMimeType
);
1222 if ( index
== wxNOT_FOUND
) {
1224 m_aTypes
.Add(strMimeType
);
1225 m_aEntries
.Add(NULL
);
1226 m_aExtensions
.Add(strExtensions
);
1227 m_aDescriptions
.Add(strDesc
);
1230 // modify an existing one
1231 if ( !strDesc
.IsEmpty() ) {
1232 m_aDescriptions
[index
] = strDesc
; // replace old value
1234 m_aExtensions
[index
] += ' ' + strExtensions
;
1238 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString
& strType
,
1239 const wxString
& strOpenCmd
,
1240 const wxString
& strPrintCmd
,
1241 const wxString
& strTest
,
1242 const wxString
& strDesc
)
1246 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
, strPrintCmd
, strTest
);
1248 int nIndex
= m_aTypes
.Index(strType
);
1249 if ( nIndex
== wxNOT_FOUND
) {
1251 m_aTypes
.Add(strType
);
1253 m_aEntries
.Add(entry
);
1254 m_aExtensions
.Add(wxT(""));
1255 m_aDescriptions
.Add(strDesc
);
1258 // always append the entry in the tail of the list - info added with
1259 // this function can only come from AddFallbacks()
1260 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1262 entry
->Append(entryOld
);
1264 m_aEntries
[nIndex
] = entry
;
1268 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString
& strFileName
)
1270 wxLogTrace(TRACE_MIME
, wxT("--- Parsing mime.types file '%s' ---"),
1271 strFileName
.c_str());
1273 wxTextFile
file(strFileName
);
1277 // the information we extract
1278 wxString strMimeType
, strDesc
, strExtensions
;
1280 size_t nLineCount
= file
.GetLineCount();
1281 const wxChar
*pc
= NULL
;
1282 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1284 // now we're at the start of the line
1285 pc
= file
[nLine
].c_str();
1288 // we didn't finish with the previous line yet
1293 while ( wxIsspace(*pc
) )
1296 // comment or blank line?
1297 if ( *pc
== wxT('#') || !*pc
) {
1298 // skip the whole line
1303 // detect file format
1304 const wxChar
*pEqualSign
= wxStrchr(pc
, wxT('='));
1305 if ( pEqualSign
== NULL
) {
1309 // first field is mime type
1310 for ( strMimeType
.Empty(); !wxIsspace(*pc
) && *pc
!= wxT('\0'); pc
++ ) {
1315 while ( wxIsspace(*pc
) )
1318 // take all the rest of the string
1321 // no description...
1328 // the string on the left of '=' is the field name
1329 wxString
strLHS(pc
, pEqualSign
- pc
);
1332 for ( pc
= pEqualSign
+ 1; wxIsspace(*pc
); pc
++ )
1336 if ( *pc
== wxT('"') ) {
1337 // the string is quoted and ends at the matching quote
1338 pEnd
= wxStrchr(++pc
, wxT('"'));
1339 if ( pEnd
== NULL
) {
1340 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1342 strFileName
.c_str(), nLine
+ 1);
1346 // unquoted string ends at the first space or at the end of
1348 for ( pEnd
= pc
; *pEnd
&& !wxIsspace(*pEnd
); pEnd
++ )
1352 // now we have the RHS (field value)
1353 wxString
strRHS(pc
, pEnd
- pc
);
1355 // check what follows this entry
1356 if ( *pEnd
== wxT('"') ) {
1361 for ( pc
= pEnd
; wxIsspace(*pc
); pc
++ )
1364 // if there is something left, it may be either a '\\' to continue
1365 // the line or the next field of the same entry
1366 bool entryEnded
= *pc
== wxT('\0'),
1367 nextFieldOnSameLine
= FALSE
;
1368 if ( !entryEnded
) {
1369 nextFieldOnSameLine
= ((*pc
!= wxT('\\')) || (pc
[1] != wxT('\0')));
1372 // now see what we got
1373 if ( strLHS
== wxT("type") ) {
1374 strMimeType
= strRHS
;
1376 else if ( strLHS
== wxT("desc") ) {
1379 else if ( strLHS
== wxT("exts") ) {
1380 strExtensions
= strRHS
;
1383 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1384 strFileName
.c_str(), nLine
+ 1, strLHS
.c_str());
1387 if ( !entryEnded
) {
1388 if ( !nextFieldOnSameLine
)
1390 //else: don't reset it
1392 // as we don't reset strMimeType, the next field in this entry
1393 // will be interpreted correctly.
1399 // depending on the format (Mosaic or Netscape) either space or comma
1400 // is used to separate the extensions
1401 strExtensions
.Replace(wxT(","), wxT(" "));
1403 // also deal with the leading dot
1404 if ( !strExtensions
.IsEmpty() && strExtensions
[0u] == wxT('.') )
1406 strExtensions
.erase(0, 1);
1409 AddMimeTypeInfo(strMimeType
, strExtensions
, strDesc
);
1411 // finished with this line
1415 // check our data integriry
1416 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1417 m_aTypes
.Count() == m_aExtensions
.Count() &&
1418 m_aTypes
.Count() == m_aDescriptions
.Count() );
1423 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString
& strFileName
,
1426 wxLogTrace(TRACE_MIME
, wxT("--- Parsing mailcap file '%s' ---"),
1427 strFileName
.c_str());
1429 wxTextFile
file(strFileName
);
1433 // see the comments near the end of function for the reason we need these
1434 // variables (search for the next occurence of them)
1435 // indices of MIME types (in m_aTypes) we already found in this file
1436 wxArrayInt aEntryIndices
;
1437 // aLastIndices[n] is the index of last element in
1438 // m_aEntries[aEntryIndices[n]] from this file
1439 wxArrayInt aLastIndices
;
1441 size_t nLineCount
= file
.GetLineCount();
1442 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1443 // now we're at the start of the line
1444 const wxChar
*pc
= file
[nLine
].c_str();
1447 while ( wxIsspace(*pc
) )
1450 // comment or empty string?
1451 if ( *pc
== wxT('#') || *pc
== wxT('\0') )
1456 // what field are we currently in? The first 2 are fixed and there may
1457 // be an arbitrary number of other fields -- currently, we are not
1458 // interested in any of them, but we should parse them as well...
1464 } currentToken
= Field_Type
;
1466 // the flags and field values on the current line
1467 bool needsterminal
= FALSE
,
1468 copiousoutput
= FALSE
;
1474 curField
; // accumulator
1475 for ( bool cont
= TRUE
; cont
; pc
++ ) {
1478 // interpret the next character literally (notice that
1479 // backslash can be used for line continuation)
1480 if ( *++pc
== wxT('\0') ) {
1481 // fetch the next line.
1483 // pc currently points to nowhere, but after the next
1484 // pc++ in the for line it will point to the beginning
1485 // of the next line in the file
1486 pc
= file
[++nLine
].c_str() - 1;
1489 // just a normal character
1495 cont
= FALSE
; // end of line reached, exit the loop
1500 // store this field and start looking for the next one
1502 // trim whitespaces from both sides
1503 curField
.Trim(TRUE
).Trim(FALSE
);
1505 switch ( currentToken
) {
1508 if ( strType
.Find(wxT('/')) == wxNOT_FOUND
) {
1509 // we interpret "type" as "type/*"
1510 strType
+= wxT("/*");
1513 currentToken
= Field_OpenCmd
;
1517 strOpenCmd
= curField
;
1519 currentToken
= Field_Other
;
1524 // "good" mailcap entry?
1527 // is this something of the form foo=bar?
1528 const wxChar
*pEq
= wxStrchr(curField
, wxT('='));
1529 if ( pEq
!= NULL
) {
1530 wxString lhs
= curField
.BeforeFirst(wxT('=')),
1531 rhs
= curField
.AfterFirst(wxT('='));
1533 lhs
.Trim(TRUE
); // from right
1534 rhs
.Trim(FALSE
); // from left
1536 if ( lhs
== wxT("print") )
1538 else if ( lhs
== wxT("test") )
1540 else if ( lhs
== wxT("description") ) {
1541 // it might be quoted
1542 if ( rhs
[0u] == wxT('"') &&
1543 rhs
.Last() == wxT('"') ) {
1544 strDesc
= wxString(rhs
.c_str() + 1,
1551 else if ( lhs
== wxT("compose") ||
1552 lhs
== wxT("composetyped") ||
1553 lhs
== wxT("notes") ||
1554 lhs
== wxT("edit") )
1561 // no, it's a simple flag
1562 if ( curField
== wxT("needsterminal") )
1563 needsterminal
= TRUE
;
1564 else if ( curField
== wxT("copiousoutput")) {
1565 // copiousoutput impies that the
1566 // viewer is a console program
1568 copiousoutput
= TRUE
;
1578 if ( !IsKnownUnimportantField(curField
) )
1580 // don't flood the user with error
1581 // messages if we don't understand
1582 // something in his mailcap, but give
1583 // them in debug mode because this might
1584 // be useful for the programmer
1587 wxT("Mailcap file %s, line %d: "
1588 "unknown field '%s' for the "
1589 "MIME type '%s' ignored."),
1590 strFileName
.c_str(),
1599 // it already has this value
1600 //currentToken = Field_Other;
1604 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1607 // next token starts immediately after ';'
1616 // check that we really read something reasonable
1617 if ( currentToken
== Field_Type
|| currentToken
== Field_OpenCmd
) {
1618 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1620 strFileName
.c_str(), nLine
+ 1);
1623 // support for flags:
1624 // 1. create an xterm for 'needsterminal'
1625 // 2. append "| $PAGER" for 'copiousoutput'
1627 // Note that the RFC says that having both needsterminal and
1628 // copiousoutput is probably a mistake, so it seems that running
1629 // programs with copiousoutput inside an xterm as it is done now
1630 // is a bad idea (FIXME)
1631 if ( copiousoutput
) {
1632 const wxChar
*p
= wxGetenv(_T("PAGER"));
1633 strOpenCmd
<< _T(" | ") << (p
? p
: _T("more"));
1636 if ( needsterminal
) {
1637 strOpenCmd
.Printf(_T("xterm -e sh -c '%s'"),
1638 strOpenCmd
.c_str());
1641 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
,
1645 // NB: because of complications below (we must get entries priority
1646 // right), we can't use AddMailcapInfo() here, unfortunately.
1647 strType
.MakeLower();
1648 int nIndex
= m_aTypes
.Index(strType
);
1649 if ( nIndex
== wxNOT_FOUND
) {
1651 m_aTypes
.Add(strType
);
1653 m_aEntries
.Add(entry
);
1654 m_aExtensions
.Add(wxT(""));
1655 m_aDescriptions
.Add(strDesc
);
1658 // modify the existing entry: the entries in one and the same
1659 // file are read in top-to-bottom order, i.e. the entries read
1660 // first should be tried before the entries below. However,
1661 // the files read later should override the settings in the
1662 // files read before (except if fallback is TRUE), thus we
1663 // Insert() the new entry to the list if it has already
1664 // occured in _this_ file, but Prepend() it if it occured in
1665 // some of the previous ones and Append() to it in the
1669 // 'fallback' parameter prevents the entries from this
1670 // file from overriding the other ones - always append
1671 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1673 entry
->Append(entryOld
);
1675 m_aEntries
[nIndex
] = entry
;
1678 int entryIndex
= aEntryIndices
.Index(nIndex
);
1679 if ( entryIndex
== wxNOT_FOUND
) {
1680 // first time in this file
1681 aEntryIndices
.Add(nIndex
);
1682 aLastIndices
.Add(0);
1684 entry
->Prepend(m_aEntries
[nIndex
]);
1685 m_aEntries
[nIndex
] = entry
;
1688 // not the first time in _this_ file
1689 size_t nEntryIndex
= (size_t)entryIndex
;
1690 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1692 entry
->Insert(entryOld
, aLastIndices
[nEntryIndex
]);
1694 m_aEntries
[nIndex
] = entry
;
1696 // the indices were shifted by 1
1697 aLastIndices
[nEntryIndex
]++;
1701 if ( !strDesc
.IsEmpty() ) {
1702 // replace the old one - what else can we do??
1703 m_aDescriptions
[nIndex
] = strDesc
;
1708 // check our data integriry
1709 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1710 m_aTypes
.Count() == m_aExtensions
.Count() &&
1711 m_aTypes
.Count() == m_aDescriptions
.Count() );
1717 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
1724 size_t count
= m_aTypes
.GetCount();
1725 for ( size_t n
= 0; n
< count
; n
++ )
1727 // don't return template types from here (i.e. anything containg '*')
1729 if ( type
.Find(_T('*')) == wxNOT_FOUND
)
1731 mimetypes
.Add(type
);
1735 return mimetypes
.GetCount();
1738 // ----------------------------------------------------------------------------
1739 // writing to MIME type files
1740 // ----------------------------------------------------------------------------
1742 bool wxFileTypeImpl::Unassociate()
1744 wxFAIL_MSG( _T("unimplemented") ); // TODO
1749 // ----------------------------------------------------------------------------
1750 // private functions
1751 // ----------------------------------------------------------------------------
1753 static bool IsKnownUnimportantField(const wxString
& fieldAll
)
1755 static const wxChar
*knownFields
[] =
1757 _T("x-mozilla-flags"),
1759 _T("textualnewlines"),
1762 wxString field
= fieldAll
.BeforeFirst(_T('='));
1763 for ( size_t n
= 0; n
< WXSIZEOF(knownFields
); n
++ )
1765 if ( field
.CmpNoCase(knownFields
[n
]) == 0 )
1773 // wxUSE_FILE && wxUSE_TEXTFILE