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
) )
682 // first find the description string: it is the value in either "Comment="
683 // line or "Comment[<locale_name>]=" one
684 int posComment
= wxNOT_FOUND
;
688 wxLocale
*locale
= wxGetLocale();
691 // try "Comment[locale name]" first
692 comment
<< _T("Comment[") + locale
->GetName() + _T("]=");
694 posComment
= text
.Find(comment
);
698 if ( posComment
== wxNOT_FOUND
)
700 comment
= _T("Comment=");
702 posComment
= text
.Find(comment
);
706 if ( posComment
!= wxNOT_FOUND
)
708 // found desc: it follows the comment until the end of line
709 const wxChar
*pc
= text
.c_str() + posComment
+ comment
.length();
710 while ( *pc
&& *pc
!= _T('\n') )
715 //else: no description
717 // next find the extensions
718 wxString mime_extension
;
720 int posExts
= text
.Find(_T("Patterns="));
721 if ( posExts
!= wxNOT_FOUND
)
724 const wxChar
*pc
= text
.c_str() + posExts
+ 9; // strlen("Patterns=")
725 while ( *pc
&& *pc
!= _T('\n') )
730 wxStringTokenizer
tokenizer(exts
, _T(";"));
731 while ( tokenizer
.HasMoreTokens() )
733 wxString e
= tokenizer
.GetNextToken();
734 if ( e
.Left(2) != _T("*.") )
735 continue; // don't support too difficult patterns
737 if ( !mime_extension
.empty() )
739 // separate from the previous ext
740 mime_extension
<< _T(' ');
743 mime_extension
<< e
.Mid(2);
747 ms_infoTypes
.Add(mimetype
);
748 ms_infoDescriptions
.Add(mime_desc
);
749 ms_infoExtensions
.Add(mime_extension
);
751 // ok, now we can take care of icon:
753 int posIcon
= text
.Find(_T("Icon="));
754 if ( posIcon
== wxNOT_FOUND
)
762 const wxChar
*pc
= text
.c_str() + posIcon
+ 5; // 5 == strlen("Icon=")
763 while ( *pc
&& *pc
!= _T('\n') )
770 // we must check if the file exists because it may be stored
771 // in many locations, at least ~/.kde and $KDEDIR
772 size_t nDir
, nDirs
= icondirs
.GetCount();
773 for ( nDir
= 0; nDir
< nDirs
; nDir
++ )
774 if (wxFileExists(icondirs
[nDir
] + icon
))
776 icon
.Prepend(icondirs
[nDir
]);
779 if (nDir
== nDirs
) return; //does not exist
781 // do we already have this MIME type?
782 int i
= ms_mimetypes
.Index(mimetype
);
783 if ( i
== wxNOT_FOUND
)
786 size_t n
= ms_mimetypes
.Add(mimetype
);
787 ms_icons
.Insert(icon
, n
);
791 // replace the old value
792 ms_icons
[(size_t)i
] = icon
;
797 void wxKDEIconHandler::LoadLinksForMimeType(const wxString
& dirbase
,
798 const wxString
& subdir
,
799 const wxArrayString
& icondirs
)
801 wxString dirname
= dirbase
;
804 if ( !dir
.IsOpened() )
810 bool cont
= dir
.GetFirst(&filename
, _T("*.kdelnk"), wxDIR_FILES
);
813 LoadLinksForMimeSubtype(dirname
, subdir
, filename
, icondirs
);
815 cont
= dir
.GetNext(&filename
);
819 void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString
& dirbase
,
820 const wxArrayString
& icondirs
)
822 wxASSERT_MSG( !!dirbase
&& !wxEndsWithPathSeparator(dirbase
),
823 _T("base directory shouldn't end with a slash") );
825 wxString dirname
= dirbase
;
826 dirname
<< _T("/mimelnk");
828 if ( !wxDir::Exists(dirname
) )
832 if ( !dir
.IsOpened() )
835 // we will concatenate it with dir name to get the full path below
839 bool cont
= dir
.GetFirst(&subdir
, wxEmptyString
, wxDIR_DIRS
);
842 LoadLinksForMimeType(dirname
, subdir
, icondirs
);
844 cont
= dir
.GetNext(&subdir
);
848 void wxKDEIconHandler::Init()
851 wxArrayString icondirs
;
853 // settings in ~/.kde have maximal priority
854 dirs
.Add(wxGetHomeDir() + _T("/.kde/share"));
855 icondirs
.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
857 // the variable KDEDIR is set when KDE is running
858 const char *kdedir
= getenv("KDEDIR");
861 dirs
.Add(wxString(kdedir
) + _T("/share"));
862 icondirs
.Add(wxString(kdedir
) + _T("/share/icons/"));
866 // try to guess KDEDIR
867 dirs
.Add(_T("/usr/share"));
868 dirs
.Add(_T("/opt/kde/share"));
869 icondirs
.Add(_T("/usr/share/icons/"));
870 icondirs
.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
871 icondirs
.Add(_T("/opt/kde/share/icons/"));
874 size_t nDirs
= dirs
.GetCount();
875 for ( size_t nDir
= 0; nDir
< nDirs
; nDir
++ )
877 LoadLinkFilesFromDir(dirs
[nDir
], icondirs
);
883 bool wxKDEIconHandler::GetIcon(const wxString
& mimetype
,
884 wxIcon
* WXUNUSED_UNLESS_GUI(icon
))
891 int index
= ms_mimetypes
.Index(mimetype
);
892 if ( index
== wxNOT_FOUND
)
895 wxString iconname
= ms_icons
[(size_t)index
];
900 if (iconname
.Right(4).MakeUpper() == _T(".XPM"))
901 icn
= wxIcon(iconname
);
903 icn
= wxIcon(iconname
, wxBITMAP_TYPE_ANY
);
911 // helpful for testing in console mode
912 wxLogTrace(TRACE_MIME
, _T("Found KDE icon for '%s': '%s'\n"),
913 mimetype
.c_str(), iconname
.c_str());
920 void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl
*manager
)
922 if ( !m_inited
) Init();
924 size_t cnt
= ms_infoTypes
.GetCount();
925 for (unsigned i
= 0; i
< cnt
; i
++)
926 manager
-> AddMimeTypeInfo(ms_infoTypes
[i
], ms_infoExtensions
[i
], ms_infoDescriptions
[i
]);
930 // ----------------------------------------------------------------------------
931 // wxFileTypeImpl (Unix)
932 // ----------------------------------------------------------------------------
935 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters
& params
) const
938 MailCapEntry
*entry
= m_manager
->m_aEntries
[m_index
[0]];
939 while ( entry
!= NULL
) {
940 // get the command to run as the test for this entry
941 command
= wxFileType::ExpandCommand(entry
->GetTestCmd(), params
);
943 // don't trace the test result if there is no test at all
944 if ( command
.IsEmpty() )
946 // no test at all, ok
950 if ( wxSystem(command
) == 0 ) {
952 wxLogTrace(TRACE_MIME
,
953 wxT("Test '%s' for mime type '%s' succeeded."),
954 command
.c_str(), params
.GetMimeType().c_str());
958 wxLogTrace(TRACE_MIME
,
959 wxT("Test '%s' for mime type '%s' failed."),
960 command
.c_str(), params
.GetMimeType().c_str());
963 entry
= entry
->GetNext();
969 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
971 wxArrayString mimetypes
;
972 GetMimeTypes(mimetypes
);
974 ArrayIconHandlers
& handlers
= m_manager
->GetIconHandlers();
975 size_t count
= handlers
.GetCount();
976 size_t counttypes
= mimetypes
.GetCount();
977 for ( size_t n
= 0; n
< count
; n
++ )
979 for ( size_t n2
= 0; n2
< counttypes
; n2
++ )
981 if ( handlers
[n
]->GetIcon(mimetypes
[n2
], icon
) )
991 wxFileTypeImpl::GetMimeTypes(wxArrayString
& mimeTypes
) const
994 for (size_t i
= 0; i
< m_index
.GetCount(); i
++)
995 mimeTypes
.Add(m_manager
->m_aTypes
[m_index
[i
]]);
1001 wxFileTypeImpl::GetExpandedCommand(wxString
*expandedCmd
,
1002 const wxFileType::MessageParameters
& params
,
1005 MailCapEntry
*entry
= GetEntry(params
);
1006 if ( entry
== NULL
) {
1007 // all tests failed...
1011 wxString cmd
= open
? entry
->GetOpenCmd() : entry
->GetPrintCmd();
1012 if ( cmd
.IsEmpty() ) {
1013 // may happen, especially for "print"
1017 *expandedCmd
= wxFileType::ExpandCommand(cmd
, params
);
1021 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
1023 wxString strExtensions
= m_manager
->GetExtension(m_index
[0]);
1026 // one extension in the space or comma delimitid list
1028 for ( const wxChar
*p
= strExtensions
; ; p
++ ) {
1029 if ( *p
== wxT(' ') || *p
== wxT(',') || *p
== wxT('\0') ) {
1030 if ( !strExt
.IsEmpty() ) {
1031 extensions
.Add(strExt
);
1034 //else: repeated spaces (shouldn't happen, but it's not that
1035 // important if it does happen)
1037 if ( *p
== wxT('\0') )
1040 else if ( *p
== wxT('.') ) {
1041 // remove the dot from extension (but only if it's the first char)
1042 if ( !strExt
.IsEmpty() ) {
1045 //else: no, don't append it
1055 // ----------------------------------------------------------------------------
1056 // wxMimeTypesManagerImpl (Unix)
1057 // ----------------------------------------------------------------------------
1060 ArrayIconHandlers
& wxMimeTypesManagerImpl::GetIconHandlers()
1062 if ( ms_iconHandlers
.GetCount() == 0 )
1064 ms_iconHandlers
.Add(&gs_iconHandlerGNOME
);
1065 ms_iconHandlers
.Add(&gs_iconHandlerKDE
);
1068 return ms_iconHandlers
;
1071 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1073 m_initialized
= FALSE
;
1076 // read system and user mailcaps and other files
1077 void wxMimeTypesManagerImpl::Initialize()
1079 // directories where we look for mailcap and mime.types by default
1080 // (taken from metamail(1) sources)
1082 // although RFC 1524 specifies the search path of
1083 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1084 // places - OTOH, the RFC also says that this path can be changed with
1085 // MAILCAPS environment variable (containing the colon separated full
1086 // filenames to try) which is not done yet (TODO?)
1087 static const wxChar
*aStandardLocations
[] =
1091 wxT("/usr/local/etc"),
1093 wxT("/usr/public/lib")
1096 // first read the system wide file(s)
1098 for ( n
= 0; n
< WXSIZEOF(aStandardLocations
); n
++ ) {
1099 wxString dir
= aStandardLocations
[n
];
1101 wxString file
= dir
+ wxT("/mailcap");
1102 if ( wxFile::Exists(file
) ) {
1106 file
= dir
+ wxT("/mime.types");
1107 if ( wxFile::Exists(file
) ) {
1108 ReadMimeTypes(file
);
1112 wxString strHome
= wxGetenv(wxT("HOME"));
1114 // and now the users mailcap
1115 wxString strUserMailcap
= strHome
+ wxT("/.mailcap");
1116 if ( wxFile::Exists(strUserMailcap
) ) {
1117 ReadMailcap(strUserMailcap
);
1120 // read the users mime.types
1121 wxString strUserMimeTypes
= strHome
+ wxT("/.mime.types");
1122 if ( wxFile::Exists(strUserMimeTypes
) ) {
1123 ReadMimeTypes(strUserMimeTypes
);
1126 // read KDE/GNOME tables
1127 ArrayIconHandlers
& handlers
= GetIconHandlers();
1128 size_t count
= handlers
.GetCount();
1129 for ( size_t hn
= 0; hn
< count
; hn
++ )
1130 handlers
[hn
]->GetMimeInfoRecords(this);
1134 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1136 size_t cnt
= m_aEntries
.GetCount();
1137 for (size_t i
= 0; i
< cnt
; i
++)
1138 delete m_aEntries
[i
];
1142 wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo
& ftInfo
)
1144 wxFAIL_MSG( _T("unimplemented") ); // TODO
1150 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
1154 wxFileType
*fileType
= NULL
;
1155 size_t count
= m_aExtensions
.GetCount();
1156 for ( size_t n
= 0; n
< count
; n
++ ) {
1157 wxString extensions
= m_aExtensions
[n
];
1158 while ( !extensions
.IsEmpty() ) {
1159 wxString field
= extensions
.BeforeFirst(wxT(' '));
1160 extensions
= extensions
.AfterFirst(wxT(' '));
1162 // consider extensions as not being case-sensitive
1163 if ( field
.IsSameAs(ext
, FALSE
/* no case */) ) {
1165 if (fileType
== NULL
) fileType
= new wxFileType
;
1166 fileType
->m_impl
->Init(this, n
);
1167 // adds this mime type to _list_ of mime types with this extension
1176 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
1180 // mime types are not case-sensitive
1181 wxString
mimetype(mimeType
);
1182 mimetype
.MakeLower();
1184 // first look for an exact match
1185 int index
= m_aTypes
.Index(mimetype
);
1186 if ( index
== wxNOT_FOUND
) {
1187 // then try to find "text/*" as match for "text/plain" (for example)
1188 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1189 // the whole string - ok.
1190 wxString strCategory
= mimetype
.BeforeFirst(wxT('/'));
1192 size_t nCount
= m_aTypes
.Count();
1193 for ( size_t n
= 0; n
< nCount
; n
++ ) {
1194 if ( (m_aTypes
[n
].BeforeFirst(wxT('/')) == strCategory
) &&
1195 m_aTypes
[n
].AfterFirst(wxT('/')) == wxT("*") ) {
1202 if ( index
!= wxNOT_FOUND
) {
1203 wxFileType
*fileType
= new wxFileType
;
1204 fileType
->m_impl
->Init(this, index
);
1214 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo
& filetype
)
1218 wxString extensions
;
1219 const wxArrayString
& exts
= filetype
.GetExtensions();
1220 size_t nExts
= exts
.GetCount();
1221 for ( size_t nExt
= 0; nExt
< nExts
; nExt
++ ) {
1223 extensions
+= wxT(' ');
1225 extensions
+= exts
[nExt
];
1228 AddMimeTypeInfo(filetype
.GetMimeType(),
1230 filetype
.GetDescription());
1232 AddMailcapInfo(filetype
.GetMimeType(),
1233 filetype
.GetOpenCommand(),
1234 filetype
.GetPrintCommand(),
1236 filetype
.GetDescription());
1239 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString
& strMimeType
,
1240 const wxString
& strExtensions
,
1241 const wxString
& strDesc
)
1245 int index
= m_aTypes
.Index(strMimeType
);
1246 if ( index
== wxNOT_FOUND
) {
1248 m_aTypes
.Add(strMimeType
);
1249 m_aEntries
.Add(NULL
);
1250 m_aExtensions
.Add(strExtensions
);
1251 m_aDescriptions
.Add(strDesc
);
1254 // modify an existing one
1255 if ( !strDesc
.IsEmpty() ) {
1256 m_aDescriptions
[index
] = strDesc
; // replace old value
1258 m_aExtensions
[index
] += ' ' + strExtensions
;
1262 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString
& strType
,
1263 const wxString
& strOpenCmd
,
1264 const wxString
& strPrintCmd
,
1265 const wxString
& strTest
,
1266 const wxString
& strDesc
)
1270 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
, strPrintCmd
, strTest
);
1272 int nIndex
= m_aTypes
.Index(strType
);
1273 if ( nIndex
== wxNOT_FOUND
) {
1275 m_aTypes
.Add(strType
);
1277 m_aEntries
.Add(entry
);
1278 m_aExtensions
.Add(wxT(""));
1279 m_aDescriptions
.Add(strDesc
);
1282 // always append the entry in the tail of the list - info added with
1283 // this function can only come from AddFallbacks()
1284 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1286 entry
->Append(entryOld
);
1288 m_aEntries
[nIndex
] = entry
;
1292 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString
& strFileName
)
1294 wxLogTrace(TRACE_MIME
, wxT("--- Parsing mime.types file '%s' ---"),
1295 strFileName
.c_str());
1297 wxTextFile
file(strFileName
);
1301 // the information we extract
1302 wxString strMimeType
, strDesc
, strExtensions
;
1304 size_t nLineCount
= file
.GetLineCount();
1305 const wxChar
*pc
= NULL
;
1306 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1308 // now we're at the start of the line
1309 pc
= file
[nLine
].c_str();
1312 // we didn't finish with the previous line yet
1317 while ( wxIsspace(*pc
) )
1320 // comment or blank line?
1321 if ( *pc
== wxT('#') || !*pc
) {
1322 // skip the whole line
1327 // detect file format
1328 const wxChar
*pEqualSign
= wxStrchr(pc
, wxT('='));
1329 if ( pEqualSign
== NULL
) {
1333 // first field is mime type
1334 for ( strMimeType
.Empty(); !wxIsspace(*pc
) && *pc
!= wxT('\0'); pc
++ ) {
1339 while ( wxIsspace(*pc
) )
1342 // take all the rest of the string
1345 // no description...
1352 // the string on the left of '=' is the field name
1353 wxString
strLHS(pc
, pEqualSign
- pc
);
1356 for ( pc
= pEqualSign
+ 1; wxIsspace(*pc
); pc
++ )
1360 if ( *pc
== wxT('"') ) {
1361 // the string is quoted and ends at the matching quote
1362 pEnd
= wxStrchr(++pc
, wxT('"'));
1363 if ( pEnd
== NULL
) {
1364 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1366 strFileName
.c_str(), nLine
+ 1);
1370 // unquoted string ends at the first space or at the end of
1372 for ( pEnd
= pc
; *pEnd
&& !wxIsspace(*pEnd
); pEnd
++ )
1376 // now we have the RHS (field value)
1377 wxString
strRHS(pc
, pEnd
- pc
);
1379 // check what follows this entry
1380 if ( *pEnd
== wxT('"') ) {
1385 for ( pc
= pEnd
; wxIsspace(*pc
); pc
++ )
1388 // if there is something left, it may be either a '\\' to continue
1389 // the line or the next field of the same entry
1390 bool entryEnded
= *pc
== wxT('\0'),
1391 nextFieldOnSameLine
= FALSE
;
1392 if ( !entryEnded
) {
1393 nextFieldOnSameLine
= ((*pc
!= wxT('\\')) || (pc
[1] != wxT('\0')));
1396 // now see what we got
1397 if ( strLHS
== wxT("type") ) {
1398 strMimeType
= strRHS
;
1400 else if ( strLHS
== wxT("desc") ) {
1403 else if ( strLHS
== wxT("exts") ) {
1404 strExtensions
= strRHS
;
1407 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1408 strFileName
.c_str(), nLine
+ 1, strLHS
.c_str());
1411 if ( !entryEnded
) {
1412 if ( !nextFieldOnSameLine
)
1414 //else: don't reset it
1416 // as we don't reset strMimeType, the next field in this entry
1417 // will be interpreted correctly.
1423 // depending on the format (Mosaic or Netscape) either space or comma
1424 // is used to separate the extensions
1425 strExtensions
.Replace(wxT(","), wxT(" "));
1427 // also deal with the leading dot
1428 if ( !strExtensions
.IsEmpty() && strExtensions
[0u] == wxT('.') )
1430 strExtensions
.erase(0, 1);
1433 AddMimeTypeInfo(strMimeType
, strExtensions
, strDesc
);
1435 // finished with this line
1439 // check our data integriry
1440 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1441 m_aTypes
.Count() == m_aExtensions
.Count() &&
1442 m_aTypes
.Count() == m_aDescriptions
.Count() );
1447 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString
& strFileName
,
1450 wxLogTrace(TRACE_MIME
, wxT("--- Parsing mailcap file '%s' ---"),
1451 strFileName
.c_str());
1453 wxTextFile
file(strFileName
);
1457 // see the comments near the end of function for the reason we need these
1458 // variables (search for the next occurence of them)
1459 // indices of MIME types (in m_aTypes) we already found in this file
1460 wxArrayInt aEntryIndices
;
1461 // aLastIndices[n] is the index of last element in
1462 // m_aEntries[aEntryIndices[n]] from this file
1463 wxArrayInt aLastIndices
;
1465 size_t nLineCount
= file
.GetLineCount();
1466 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1467 // now we're at the start of the line
1468 const wxChar
*pc
= file
[nLine
].c_str();
1471 while ( wxIsspace(*pc
) )
1474 // comment or empty string?
1475 if ( *pc
== wxT('#') || *pc
== wxT('\0') )
1480 // what field are we currently in? The first 2 are fixed and there may
1481 // be an arbitrary number of other fields -- currently, we are not
1482 // interested in any of them, but we should parse them as well...
1488 } currentToken
= Field_Type
;
1490 // the flags and field values on the current line
1491 bool needsterminal
= FALSE
,
1492 copiousoutput
= FALSE
;
1498 curField
; // accumulator
1503 // interpret the next character literally (notice that
1504 // backslash can be used for line continuation)
1505 if ( *++pc
== wxT('\0') ) {
1506 // fetch the next line if there is one
1507 if ( nLine
== nLineCount
- 1 ) {
1508 // something is wrong, bail out
1511 wxLogDebug(wxT("Mailcap file %s, line %d: "
1512 "'\\' on the end of the last line "
1514 strFileName
.c_str(),
1518 // pass to the beginning of the next line
1519 pc
= file
[++nLine
].c_str();
1521 // skip pc++ at the end of the loop
1526 // just a normal character
1532 cont
= FALSE
; // end of line reached, exit the loop
1537 // store this field and start looking for the next one
1539 // trim whitespaces from both sides
1540 curField
.Trim(TRUE
).Trim(FALSE
);
1542 switch ( currentToken
) {
1545 if ( strType
.empty() ) {
1546 // I don't think that this is a valid mailcap
1547 // entry, but try to interpret it somehow
1551 if ( strType
.Find(wxT('/')) == wxNOT_FOUND
) {
1552 // we interpret "type" as "type/*"
1553 strType
+= wxT("/*");
1556 currentToken
= Field_OpenCmd
;
1560 strOpenCmd
= curField
;
1562 currentToken
= Field_Other
;
1566 if ( !curField
.empty() ) {
1567 // "good" mailcap entry?
1570 // is this something of the form foo=bar?
1571 const wxChar
*pEq
= wxStrchr(curField
, wxT('='));
1572 if ( pEq
!= NULL
) {
1573 wxString lhs
= curField
.BeforeFirst(wxT('=')),
1574 rhs
= curField
.AfterFirst(wxT('='));
1576 lhs
.Trim(TRUE
); // from right
1577 rhs
.Trim(FALSE
); // from left
1579 if ( lhs
== wxT("print") )
1581 else if ( lhs
== wxT("test") )
1583 else if ( lhs
== wxT("description") ) {
1584 // it might be quoted
1585 if ( rhs
[0u] == wxT('"') &&
1586 rhs
.Last() == wxT('"') ) {
1587 strDesc
= wxString(rhs
.c_str() + 1,
1594 else if ( lhs
== wxT("compose") ||
1595 lhs
== wxT("composetyped") ||
1596 lhs
== wxT("notes") ||
1597 lhs
== wxT("edit") )
1604 // no, it's a simple flag
1605 if ( curField
== wxT("needsterminal") )
1606 needsterminal
= TRUE
;
1607 else if ( curField
== wxT("copiousoutput")) {
1608 // copiousoutput impies that the
1609 // viewer is a console program
1611 copiousoutput
= TRUE
;
1621 if ( !IsKnownUnimportantField(curField
) )
1623 // don't flood the user with error
1624 // messages if we don't understand
1625 // something in his mailcap, but give
1626 // them in debug mode because this might
1627 // be useful for the programmer
1630 wxT("Mailcap file %s, line %d: "
1631 "unknown field '%s' for the "
1632 "MIME type '%s' ignored."),
1633 strFileName
.c_str(),
1641 //else: the field is empty, ignore silently
1643 // it already has this value
1644 //currentToken = Field_Other;
1648 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1651 // next token starts immediately after ';'
1659 // continue in the same line
1663 // check that we really read something reasonable
1664 if ( currentToken
== Field_Type
|| currentToken
== Field_OpenCmd
) {
1665 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1667 strFileName
.c_str(), nLine
+ 1);
1670 // support for flags:
1671 // 1. create an xterm for 'needsterminal'
1672 // 2. append "| $PAGER" for 'copiousoutput'
1674 // Note that the RFC says that having both needsterminal and
1675 // copiousoutput is probably a mistake, so it seems that running
1676 // programs with copiousoutput inside an xterm as it is done now
1677 // is a bad idea (FIXME)
1678 if ( copiousoutput
) {
1679 const wxChar
*p
= wxGetenv(_T("PAGER"));
1680 strOpenCmd
<< _T(" | ") << (p
? p
: _T("more"));
1683 if ( needsterminal
) {
1684 strOpenCmd
.Printf(_T("xterm -e sh -c '%s'"),
1685 strOpenCmd
.c_str());
1688 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
,
1692 // NB: because of complications below (we must get entries priority
1693 // right), we can't use AddMailcapInfo() here, unfortunately.
1694 strType
.MakeLower();
1695 int nIndex
= m_aTypes
.Index(strType
);
1696 if ( nIndex
== wxNOT_FOUND
) {
1698 m_aTypes
.Add(strType
);
1700 m_aEntries
.Add(entry
);
1701 m_aExtensions
.Add(wxT(""));
1702 m_aDescriptions
.Add(strDesc
);
1705 // modify the existing entry: the entries in one and the same
1706 // file are read in top-to-bottom order, i.e. the entries read
1707 // first should be tried before the entries below. However,
1708 // the files read later should override the settings in the
1709 // files read before (except if fallback is TRUE), thus we
1710 // Insert() the new entry to the list if it has already
1711 // occured in _this_ file, but Prepend() it if it occured in
1712 // some of the previous ones and Append() to it in the
1716 // 'fallback' parameter prevents the entries from this
1717 // file from overriding the other ones - always append
1718 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1720 entry
->Append(entryOld
);
1722 m_aEntries
[nIndex
] = entry
;
1725 int entryIndex
= aEntryIndices
.Index(nIndex
);
1726 if ( entryIndex
== wxNOT_FOUND
) {
1727 // first time in this file
1728 aEntryIndices
.Add(nIndex
);
1729 aLastIndices
.Add(0);
1731 entry
->Prepend(m_aEntries
[nIndex
]);
1732 m_aEntries
[nIndex
] = entry
;
1735 // not the first time in _this_ file
1736 size_t nEntryIndex
= (size_t)entryIndex
;
1737 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1739 entry
->Insert(entryOld
, aLastIndices
[nEntryIndex
]);
1741 m_aEntries
[nIndex
] = entry
;
1743 // the indices were shifted by 1
1744 aLastIndices
[nEntryIndex
]++;
1748 if ( !strDesc
.IsEmpty() ) {
1749 // replace the old one - what else can we do??
1750 m_aDescriptions
[nIndex
] = strDesc
;
1755 // check our data integriry
1756 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1757 m_aTypes
.Count() == m_aExtensions
.Count() &&
1758 m_aTypes
.Count() == m_aDescriptions
.Count() );
1764 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString
& mimetypes
)
1771 size_t count
= m_aTypes
.GetCount();
1772 for ( size_t n
= 0; n
< count
; n
++ )
1774 // don't return template types from here (i.e. anything containg '*')
1776 if ( type
.Find(_T('*')) == wxNOT_FOUND
)
1778 mimetypes
.Add(type
);
1782 return mimetypes
.GetCount();
1785 // ----------------------------------------------------------------------------
1786 // writing to MIME type files
1787 // ----------------------------------------------------------------------------
1789 bool wxFileTypeImpl::Unassociate()
1791 wxFAIL_MSG( _T("unimplemented") ); // TODO
1796 // ----------------------------------------------------------------------------
1797 // private functions
1798 // ----------------------------------------------------------------------------
1800 static bool IsKnownUnimportantField(const wxString
& fieldAll
)
1802 static const wxChar
*knownFields
[] =
1804 _T("x-mozilla-flags"),
1806 _T("textualnewlines"),
1809 wxString field
= fieldAll
.BeforeFirst(_T('='));
1810 for ( size_t n
= 0; n
< WXSIZEOF(knownFields
); n
++ )
1812 if ( field
.CmpNoCase(knownFields
[n
]) == 0 )
1820 // wxUSE_FILE && wxUSE_TEXTFILE