1 /////////////////////////////////////////////////////////////////////////////
2 // Name: common/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 /////////////////////////////////////////////////////////////////////////////
13 #pragma implementation "mimetype.h"
16 // for compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
27 #if (wxUSE_FILE && wxUSE_TEXTFILE) || defined(__WXMSW__)
30 #include "wx/string.h"
34 // Doesn't compile in WIN16 mode
40 #include "wx/dynarray.h"
41 #include "wx/confbase.h"
44 #include "wx/msw/registry.h"
47 #include "wx/textfile.h"
50 #include "wx/mimetype.h"
52 // other standard headers
55 // ----------------------------------------------------------------------------
57 // ----------------------------------------------------------------------------
59 // implementation classes, platform dependent
62 // These classes use Windows registry to retrieve the required information.
64 // Keys used (not all of them are documented, so it might actually stop working
65 // in futur versions of Windows...):
66 // 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
67 // types, each key has a string value "Extension" which gives (dot preceded)
68 // extension for the files of this MIME type.
70 // 2. "HKCR\.ext" contains
71 // a) unnamed value containing the "filetype"
72 // b) value "Content Type" containing the MIME type
74 // 3. "HKCR\filetype" contains
75 // a) unnamed value containing the description
76 // b) subkey "DefaultIcon" with single unnamed value giving the icon index in
78 // c) shell\open\command and shell\open\print subkeys containing the commands
79 // to open/print the file (the positional parameters are introduced by %1,
80 // %2, ... in these strings, we change them to %s ourselves)
86 wxFileTypeImpl() { m_info
= NULL
; }
88 // one of these Init() function must be called (ctor can't take any
89 // arguments because it's common)
91 // initialize us with our file type name and extension - in this case
92 // we will read all other data from the registry
93 void Init(const wxString
& strFileType
, const wxString
& ext
)
94 { m_strFileType
= strFileType
; m_ext
= ext
; }
96 // initialize us with a wxFileTypeInfo object - it contains all the
98 void Init(const wxFileTypeInfo
& info
)
101 // implement accessor functions
102 bool GetExtensions(wxArrayString
& extensions
);
103 bool GetMimeType(wxString
*mimeType
) const;
104 bool GetIcon(wxIcon
*icon
) const;
105 bool GetDescription(wxString
*desc
) const;
106 bool GetOpenCommand(wxString
*openCmd
,
107 const wxFileType::MessageParameters
& params
) const;
108 bool GetPrintCommand(wxString
*printCmd
,
109 const wxFileType::MessageParameters
& params
) const;
112 // helper function: reads the command corresponding to the specified verb
113 // from the registry (returns an empty string if not found)
114 wxString
GetCommand(const wxChar
*verb
) const;
116 // we use either m_info or read the data from the registry if m_info == NULL
117 const wxFileTypeInfo
*m_info
;
118 wxString m_strFileType
,
122 WX_DECLARE_OBJARRAY(wxFileTypeInfo
, wxArrayFileTypeInfo
);
123 #include "wx/arrimpl.cpp"
124 WX_DEFINE_OBJARRAY(wxArrayFileTypeInfo
);
126 class wxMimeTypesManagerImpl
129 // nothing to do here, we don't load any data but just go and fetch it from
130 // the registry when asked for
131 wxMimeTypesManagerImpl() { }
133 // implement containing class functions
134 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
135 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
137 // this are NOPs under Windows
138 bool ReadMailcap(const wxString
& filename
, bool fallback
= TRUE
)
140 bool ReadMimeTypes(const wxString
& filename
)
143 void AddFallback(const wxFileTypeInfo
& ft
) { m_fallbacks
.Add(ft
); }
146 wxArrayFileTypeInfo m_fallbacks
;
151 // this class uses both mailcap and mime.types to gather information about file
154 // The information about mailcap file was extracted from metamail(1) sources and
157 // Format of mailcap file: spaces are ignored, each line is either a comment
158 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
159 // A backslash can be used to quote semicolons and newlines (and, in fact,
160 // anything else including itself).
162 // The first field is always the MIME type in the form of type/subtype (see RFC
163 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
164 // "type" which means the same as "type/*", although I'm not sure whether this
167 // The second field is always the command to run. It is subject to
168 // parameter/filename expansion described below.
170 // All the following fields are optional and may not be present at all. If
171 // they're present they may appear in any order, although each of them should
172 // appear only once. The optional fields are the following:
173 // * notes=xxx is an uninterpreted string which is silently ignored
174 // * test=xxx is the command to be used to determine whether this mailcap line
175 // applies to our data or not. The RHS of this field goes through the
176 // parameter/filename expansion (as the 2nd field) and the resulting string
177 // is executed. The line applies only if the command succeeds, i.e. returns 0
179 // * print=xxx is the command to be used to print (and not view) the data of
180 // this type (parameter/filename expansion is done here too)
181 // * edit=xxx is the command to open/edit the data of this type
182 // * needsterminal means that a new console must be created for the viewer
183 // * copiousoutput means that the viewer doesn't interact with the user but
184 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
185 // good example), thus it might be a good idea to use some kind of paging
187 // * textualnewlines means not to perform CR/LF translation (not honored)
188 // * compose and composetyped fields are used to determine the program to be
189 // called to create a new message pert in the specified format (unused).
191 // Parameter/filename xpansion:
192 // * %s is replaced with the (full) file name
193 // * %t is replaced with MIME type/subtype of the entry
194 // * for multipart type only %n is replaced with the nnumber of parts and %F is
195 // replaced by an array of (content-type, temporary file name) pairs for all
196 // message parts (TODO)
197 // * %{parameter} is replaced with the value of parameter taken from
198 // Content-type header line of the message.
200 // FIXME any docs with real descriptions of these files??
202 // There are 2 possible formats for mime.types file, one entry per line (used
203 // for global mime.types) and "expanded" format where an entry takes multiple
204 // lines (used for users mime.types).
206 // For both formats spaces are ignored and lines starting with a '#' are
207 // comments. Each record has one of two following forms:
208 // a) for "brief" format:
209 // <mime type> <space separated list of extensions>
210 // b) for "expanded" format:
211 // type=<mime type> \ desc="<description>" \ exts="ext"
213 // We try to autodetect the format of mime.types: if a non-comment line starts
214 // with "type=" we assume the second format, otherwise the first one.
216 // there may be more than one entry for one and the same mime type, to
217 // choose the right one we have to run the command specified in the test
218 // field on our data.
223 MailCapEntry(const wxString
& openCmd
,
224 const wxString
& printCmd
,
225 const wxString
& testCmd
)
226 : m_openCmd(openCmd
), m_printCmd(printCmd
), m_testCmd(testCmd
)
232 const wxString
& GetOpenCmd() const { return m_openCmd
; }
233 const wxString
& GetPrintCmd() const { return m_printCmd
; }
234 const wxString
& GetTestCmd() const { return m_testCmd
; }
236 MailCapEntry
*GetNext() const { return m_next
; }
239 // prepend this element to the list
240 void Prepend(MailCapEntry
*next
) { m_next
= next
; }
241 // insert into the list at given position
242 void Insert(MailCapEntry
*next
, size_t pos
)
247 for ( cur
= next
; cur
!= NULL
; cur
= cur
->m_next
, n
++ ) {
252 wxASSERT_MSG( n
== pos
, _T("invalid position in MailCapEntry::Insert") );
254 m_next
= cur
->m_next
;
257 // append this element to the list
258 void Append(MailCapEntry
*next
)
260 wxCHECK_RET( next
!= NULL
, _T("Append()ing to what?") );
264 for ( cur
= next
; cur
->m_next
!= NULL
; cur
= cur
->m_next
)
269 wxASSERT_MSG( !m_next
, _T("Append()ing element already in the list?") );
273 wxString m_openCmd
, // command to use to open/view the file
275 m_testCmd
; // only apply this entry if test yields
276 // true (i.e. the command returns 0)
278 MailCapEntry
*m_next
; // in the linked list
281 WX_DEFINE_ARRAY(MailCapEntry
*, ArrayTypeEntries
);
283 class wxMimeTypesManagerImpl
285 friend class wxFileTypeImpl
; // give it access to m_aXXX variables
288 // ctor loads all info into memory for quicker access later on
289 // TODO it would be nice to load them all, but parse on demand only...
290 wxMimeTypesManagerImpl();
292 // implement containing class functions
293 wxFileType
*GetFileTypeFromExtension(const wxString
& ext
);
294 wxFileType
*GetFileTypeFromMimeType(const wxString
& mimeType
);
296 bool ReadMailcap(const wxString
& filename
, bool fallback
= FALSE
);
297 bool ReadMimeTypes(const wxString
& filename
);
299 void AddFallback(const wxFileTypeInfo
& filetype
);
301 // add information about the given mimetype
302 void AddMimeTypeInfo(const wxString
& mimetype
,
303 const wxString
& extensions
,
304 const wxString
& description
);
305 void AddMailcapInfo(const wxString
& strType
,
306 const wxString
& strOpenCmd
,
307 const wxString
& strPrintCmd
,
308 const wxString
& strTest
,
309 const wxString
& strDesc
);
312 // get the string containing space separated extensions for the given
314 wxString
GetExtension(size_t index
) { return m_aExtensions
[index
]; }
317 wxArrayString m_aTypes
, // MIME types
318 m_aDescriptions
, // descriptions (just some text)
319 m_aExtensions
; // space separated list of extensions
320 ArrayTypeEntries m_aEntries
; // commands and tests for this file type
326 // initialization functions
327 void Init(wxMimeTypesManagerImpl
*manager
, size_t index
)
328 { m_manager
= manager
; m_index
= index
; }
331 bool GetExtensions(wxArrayString
& extensions
);
332 bool GetMimeType(wxString
*mimeType
) const
333 { *mimeType
= m_manager
->m_aTypes
[m_index
]; return TRUE
; }
334 bool GetIcon(wxIcon
* WXUNUSED(icon
)) const
335 { return FALSE
; } // TODO maybe with Gnome/KDE integration...
336 bool GetDescription(wxString
*desc
) const
337 { *desc
= m_manager
->m_aDescriptions
[m_index
]; return TRUE
; }
339 bool GetOpenCommand(wxString
*openCmd
,
340 const wxFileType::MessageParameters
& params
) const
342 return GetExpandedCommand(openCmd
, params
, TRUE
);
345 bool GetPrintCommand(wxString
*printCmd
,
346 const wxFileType::MessageParameters
& params
) const
348 return GetExpandedCommand(printCmd
, params
, FALSE
);
352 // get the entry which passes the test (may return NULL)
353 MailCapEntry
*GetEntry(const wxFileType::MessageParameters
& params
) const;
355 // choose the correct entry to use and expand the command
356 bool GetExpandedCommand(wxString
*expandedCmd
,
357 const wxFileType::MessageParameters
& params
,
360 wxMimeTypesManagerImpl
*m_manager
;
361 size_t m_index
; // in the wxMimeTypesManagerImpl arrays
366 // ============================================================================
368 // ============================================================================
370 // ----------------------------------------------------------------------------
372 // ----------------------------------------------------------------------------
374 wxFileTypeInfo::wxFileTypeInfo(const char *mimeType
,
376 const char *printCmd
,
379 : m_mimeType(mimeType
),
381 m_printCmd(printCmd
),
385 va_start(argptr
, desc
);
389 const char *ext
= va_arg(argptr
, const char *);
392 // NULL terminates the list
402 // ============================================================================
403 // implementation of the wrapper classes
404 // ============================================================================
406 // ----------------------------------------------------------------------------
408 // ----------------------------------------------------------------------------
410 wxString
wxFileType::ExpandCommand(const wxString
& command
,
411 const wxFileType::MessageParameters
& params
)
413 bool hasFilename
= FALSE
;
416 for ( const wxChar
*pc
= command
.c_str(); *pc
!= _T('\0'); pc
++ ) {
417 if ( *pc
== _T('%') ) {
420 // '%s' expands into file name (quoted because it might
421 // contain spaces) - except if there are already quotes
422 // there because otherwise some programs may get confused
423 // by double double quotes
425 if ( *(pc
- 2) == _T('"') )
426 str
<< params
.GetFileName();
428 str
<< _T('"') << params
.GetFileName() << _T('"');
430 str
<< params
.GetFileName();
435 // '%t' expands into MIME type (quote it too just to be
437 str
<< _T('\'') << params
.GetMimeType() << _T('\'');
442 const wxChar
*pEnd
= wxStrchr(pc
, _T('}'));
443 if ( pEnd
== NULL
) {
445 wxLogWarning(_("Unmatched '{' in an entry for "
447 params
.GetMimeType().c_str());
451 wxString
param(pc
+ 1, pEnd
- pc
- 1);
452 str
<< _T('\'') << params
.GetParamValue(param
) << _T('\'');
460 // TODO %n is the number of parts, %F is an array containing
461 // the names of temp files these parts were written to
462 // and their mime types.
466 wxLogDebug(_T("Unknown field %%%c in command '%s'."),
467 *pc
, command
.c_str());
476 // metamail(1) man page states that if the mailcap entry doesn't have '%s'
477 // the program will accept the data on stdin: so give it to it!
478 if ( !hasFilename
&& !str
.IsEmpty() ) {
479 str
<< _T(" < '") << params
.GetFileName() << _T('\'');
485 wxFileType::wxFileType()
487 m_impl
= new wxFileTypeImpl
;
490 wxFileType::~wxFileType()
495 bool wxFileType::GetExtensions(wxArrayString
& extensions
)
497 return m_impl
->GetExtensions(extensions
);
500 bool wxFileType::GetMimeType(wxString
*mimeType
) const
502 return m_impl
->GetMimeType(mimeType
);
505 bool wxFileType::GetIcon(wxIcon
*icon
) const
507 return m_impl
->GetIcon(icon
);
510 bool wxFileType::GetDescription(wxString
*desc
) const
512 return m_impl
->GetDescription(desc
);
516 wxFileType::GetOpenCommand(wxString
*openCmd
,
517 const wxFileType::MessageParameters
& params
) const
519 return m_impl
->GetOpenCommand(openCmd
, params
);
523 wxFileType::GetPrintCommand(wxString
*printCmd
,
524 const wxFileType::MessageParameters
& params
) const
526 return m_impl
->GetPrintCommand(printCmd
, params
);
529 // ----------------------------------------------------------------------------
530 // wxMimeTypesManager
531 // ----------------------------------------------------------------------------
533 bool wxMimeTypesManager::IsOfType(const wxString
& mimeType
,
534 const wxString
& wildcard
)
536 wxASSERT_MSG( mimeType
.Find(_T('*')) == wxNOT_FOUND
,
537 _T("first MIME type can't contain wildcards") );
539 // all comparaisons are case insensitive (2nd arg of IsSameAs() is FALSE)
540 if ( wildcard
.BeforeFirst(_T('/')).IsSameAs(mimeType
.BeforeFirst(_T('/')), FALSE
) )
542 wxString strSubtype
= wildcard
.AfterFirst(_T('/'));
544 if ( strSubtype
== _T("*") ||
545 strSubtype
.IsSameAs(mimeType
.AfterFirst(_T('/')), FALSE
) )
547 // matches (either exactly or it's a wildcard)
555 wxMimeTypesManager::wxMimeTypesManager()
557 m_impl
= new wxMimeTypesManagerImpl
;
560 wxMimeTypesManager::~wxMimeTypesManager()
566 wxMimeTypesManager::GetFileTypeFromExtension(const wxString
& ext
)
568 return m_impl
->GetFileTypeFromExtension(ext
);
572 wxMimeTypesManager::GetFileTypeFromMimeType(const wxString
& mimeType
)
574 return m_impl
->GetFileTypeFromMimeType(mimeType
);
577 bool wxMimeTypesManager::ReadMailcap(const wxString
& filename
, bool fallback
)
579 return m_impl
->ReadMailcap(filename
, fallback
);
582 bool wxMimeTypesManager::ReadMimeTypes(const wxString
& filename
)
584 return m_impl
->ReadMimeTypes(filename
);
587 void wxMimeTypesManager::AddFallbacks(const wxFileTypeInfo
*filetypes
)
589 for ( const wxFileTypeInfo
*ft
= filetypes
; ft
->IsValid(); ft
++ ) {
590 m_impl
->AddFallback(*ft
);
594 // ============================================================================
595 // real (OS specific) implementation
596 // ============================================================================
600 wxString
wxFileTypeImpl::GetCommand(const wxChar
*verb
) const
602 // suppress possible error messages
605 strKey
<< m_strFileType
<< _T("\\shell\\") << verb
<< _T("\\command");
606 wxRegKey
key(wxRegKey::HKCR
, strKey
);
610 // it's the default value of the key
611 if ( key
.QueryValue(_T(""), command
) ) {
612 // transform it from '%1' to '%s' style format string
614 // NB: we don't make any attempt to verify that the string is valid,
615 // i.e. doesn't contain %2, or second %1 or .... But we do make
616 // sure that we return a string with _exactly_ one '%s'!
617 bool foundFilename
= FALSE
;
618 size_t len
= command
.Len();
619 for ( size_t n
= 0; (n
< len
) && !foundFilename
; n
++ ) {
620 if ( command
[n
] == _T('%') &&
621 (n
+ 1 < len
) && command
[n
+ 1] == _T('1') ) {
622 // replace it with '%s'
623 command
[n
+ 1] = _T('s');
625 foundFilename
= TRUE
;
629 if ( !foundFilename
) {
630 // we didn't find any '%1'!
631 // HACK: append the filename at the end, hope that it will do
632 command
<< _T(" %s");
637 // no such file type or no value
642 wxFileTypeImpl::GetOpenCommand(wxString
*openCmd
,
643 const wxFileType::MessageParameters
& params
)
648 cmd
= m_info
->GetOpenCommand();
651 cmd
= GetCommand(_T("open"));
654 *openCmd
= wxFileType::ExpandCommand(cmd
, params
);
656 return !openCmd
->IsEmpty();
660 wxFileTypeImpl::GetPrintCommand(wxString
*printCmd
,
661 const wxFileType::MessageParameters
& params
)
666 cmd
= m_info
->GetPrintCommand();
669 cmd
= GetCommand(_T("print"));
672 *printCmd
= wxFileType::ExpandCommand(cmd
, params
);
674 return !printCmd
->IsEmpty();
677 // TODO this function is half implemented
678 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
681 extensions
= m_info
->GetExtensions();
685 else if ( m_ext
.IsEmpty() ) {
686 // the only way to get the list of extensions from the file type is to
687 // scan through all extensions in the registry - too slow...
692 extensions
.Add(m_ext
);
694 // it's a lie too, we don't return _all_ extensions...
699 bool wxFileTypeImpl::GetMimeType(wxString
*mimeType
) const
702 // we already have it
703 *mimeType
= m_info
->GetMimeType();
708 // suppress possible error messages
710 wxRegKey
key(wxRegKey::HKCR
, /*m_strFileType*/ _T(".") + m_ext
);
711 if ( key
.Open() && key
.QueryValue(_T("Content Type"), *mimeType
) ) {
719 bool wxFileTypeImpl::GetIcon(wxIcon
*icon
) const
722 // we don't have icons in the fallback resources
727 strIconKey
<< m_strFileType
<< _T("\\DefaultIcon");
729 // suppress possible error messages
731 wxRegKey
key(wxRegKey::HKCR
, strIconKey
);
735 // it's the default value of the key
736 if ( key
.QueryValue(_T(""), strIcon
) ) {
737 // the format is the following: <full path to file>, <icon index>
738 // NB: icon index may be negative as well as positive and the full
739 // path may contain the environment variables inside '%'
740 wxString strFullPath
= strIcon
.BeforeLast(_T(',')),
741 strIndex
= strIcon
.AfterLast(_T(','));
743 // index may be omitted, in which case BeforeLast(',') is empty and
744 // AfterLast(',') is the whole string
745 if ( strFullPath
.IsEmpty() ) {
746 strFullPath
= strIndex
;
750 wxString strExpPath
= wxExpandEnvVars(strFullPath
);
751 int nIndex
= wxAtoi(strIndex
);
753 HICON hIcon
= ExtractIcon(GetModuleHandle(NULL
), strExpPath
, nIndex
);
754 switch ( (int)hIcon
) {
755 case 0: // means no icons were found
756 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
757 wxLogDebug(_T("incorrect registry entry '%s': no such icon."),
758 key
.GetName().c_str());
762 icon
->SetHICON((WXHICON
)hIcon
);
768 // no such file type or no value or incorrect icon entry
772 bool wxFileTypeImpl::GetDescription(wxString
*desc
) const
775 // we already have it
776 *desc
= m_info
->GetDescription();
781 // suppress possible error messages
783 wxRegKey
key(wxRegKey::HKCR
, m_strFileType
);
786 // it's the default value of the key
787 if ( key
.QueryValue(_T(""), *desc
) ) {
795 // extension -> file type
797 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
799 // add the leading point if necessary
801 if ( ext
[0u] != _T('.') ) {
806 // suppress possible error messages
809 wxString strFileType
;
810 wxRegKey
key(wxRegKey::HKCR
, str
);
812 // it's the default value of the key
813 if ( key
.QueryValue(_T(""), strFileType
) ) {
814 // create the new wxFileType object
815 wxFileType
*fileType
= new wxFileType
;
816 fileType
->m_impl
->Init(strFileType
, ext
);
822 // check the fallbacks
823 // TODO linear search is potentially slow, perhaps we should use a sorted
825 size_t count
= m_fallbacks
.GetCount();
826 for ( size_t n
= 0; n
< count
; n
++ ) {
827 if ( m_fallbacks
[n
].GetExtensions().Index(ext
) != wxNOT_FOUND
) {
828 wxFileType
*fileType
= new wxFileType
;
829 fileType
->m_impl
->Init(m_fallbacks
[n
]);
839 // MIME type -> extension -> file type
841 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
843 // HACK I don't know of any official documentation which mentions this
844 // location, but as a matter of fact IE uses it, so why not we?
845 static const wxChar
*szMimeDbase
= _T("MIME\\Database\\Content Type\\");
847 wxString strKey
= szMimeDbase
;
850 // suppress possible error messages
854 wxRegKey
key(wxRegKey::HKCR
, strKey
);
856 if ( key
.QueryValue(_T("Extension"), ext
) ) {
857 return GetFileTypeFromExtension(ext
);
861 // check the fallbacks
862 // TODO linear search is potentially slow, perhaps we should use a sorted
864 size_t count
= m_fallbacks
.GetCount();
865 for ( size_t n
= 0; n
< count
; n
++ ) {
866 if ( wxMimeTypesManager::IsOfType(mimeType
,
867 m_fallbacks
[n
].GetMimeType()) ) {
868 wxFileType
*fileType
= new wxFileType
;
869 fileType
->m_impl
->Init(m_fallbacks
[n
]);
882 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters
& params
) const
885 MailCapEntry
*entry
= m_manager
->m_aEntries
[m_index
];
886 while ( entry
!= NULL
) {
887 // notice that an empty command would always succeed (it's ok)
888 command
= wxFileType::ExpandCommand(entry
->GetTestCmd(), params
);
890 if ( command
.IsEmpty() || (wxSystem(command
) == 0) ) {
892 wxLogTrace(_T("Test '%s' for mime type '%s' succeeded."),
893 command
.c_str(), params
.GetMimeType().c_str());
897 wxLogTrace(_T("Test '%s' for mime type '%s' failed."),
898 command
.c_str(), params
.GetMimeType().c_str());
901 entry
= entry
->GetNext();
908 wxFileTypeImpl::GetExpandedCommand(wxString
*expandedCmd
,
909 const wxFileType::MessageParameters
& params
,
912 MailCapEntry
*entry
= GetEntry(params
);
913 if ( entry
== NULL
) {
914 // all tests failed...
918 wxString cmd
= open
? entry
->GetOpenCmd() : entry
->GetPrintCmd();
919 if ( cmd
.IsEmpty() ) {
920 // may happen, especially for "print"
924 *expandedCmd
= wxFileType::ExpandCommand(cmd
, params
);
928 bool wxFileTypeImpl::GetExtensions(wxArrayString
& extensions
)
930 wxString strExtensions
= m_manager
->GetExtension(m_index
);
933 // one extension in the space or comma delimitid list
935 for ( const wxChar
*p
= strExtensions
; ; p
++ ) {
936 if ( *p
== _T(' ') || *p
== _T(',') || *p
== _T('\0') ) {
937 if ( !strExt
.IsEmpty() ) {
938 extensions
.Add(strExt
);
941 //else: repeated spaces (shouldn't happen, but it's not that
942 // important if it does happen)
944 if ( *p
== _T('\0') )
947 else if ( *p
== _T('.') ) {
948 // remove the dot from extension (but only if it's the first char)
949 if ( !strExt
.IsEmpty() ) {
952 //else: no, don't append it
962 // read system and user mailcaps (TODO implement mime.types support)
963 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
965 // directories where we look for mailcap and mime.types by default
966 // (taken from metamail(1) sources)
967 static const wxChar
*aStandardLocations
[] =
971 _T("/usr/local/etc"),
973 _T("/usr/public/lib")
976 // first read the system wide file(s)
977 for ( size_t n
= 0; n
< WXSIZEOF(aStandardLocations
); n
++ ) {
978 wxString dir
= aStandardLocations
[n
];
980 wxString file
= dir
+ _T("/mailcap");
981 if ( wxFile::Exists(file
) ) {
985 file
= dir
+ _T("/mime.types");
986 if ( wxFile::Exists(file
) ) {
991 wxString strHome
= wxGetenv(_T("HOME"));
993 // and now the users mailcap
994 wxString strUserMailcap
= strHome
+ _T("/.mailcap");
995 if ( wxFile::Exists(strUserMailcap
) ) {
996 ReadMailcap(strUserMailcap
);
999 // read the users mime.types
1000 wxString strUserMimeTypes
= strHome
+ _T("/.mime.types");
1001 if ( wxFile::Exists(strUserMimeTypes
) ) {
1002 ReadMimeTypes(strUserMimeTypes
);
1007 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString
& ext
)
1009 size_t count
= m_aExtensions
.GetCount();
1010 for ( size_t n
= 0; n
< count
; n
++ ) {
1011 wxString extensions
= m_aExtensions
[n
];
1012 while ( !extensions
.IsEmpty() ) {
1013 wxString field
= extensions
.BeforeFirst(_T(' '));
1014 extensions
= extensions
.AfterFirst(_T(' '));
1016 // consider extensions as not being case-sensitive
1017 if ( field
.IsSameAs(ext
, FALSE
/* no case */) ) {
1019 wxFileType
*fileType
= new wxFileType
;
1020 fileType
->m_impl
->Init(this, n
);
1032 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString
& mimeType
)
1034 // mime types are not case-sensitive
1035 wxString
mimetype(mimeType
);
1036 mimetype
.MakeLower();
1038 // first look for an exact match
1039 int index
= m_aTypes
.Index(mimetype
);
1040 if ( index
== wxNOT_FOUND
) {
1041 // then try to find "text/*" as match for "text/plain" (for example)
1042 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1043 // the whole string - ok.
1044 wxString strCategory
= mimetype
.BeforeFirst(_T('/'));
1046 size_t nCount
= m_aTypes
.Count();
1047 for ( size_t n
= 0; n
< nCount
; n
++ ) {
1048 if ( (m_aTypes
[n
].BeforeFirst(_T('/')) == strCategory
) &&
1049 m_aTypes
[n
].AfterFirst(_T('/')) == _T("*") ) {
1056 if ( index
!= wxNOT_FOUND
) {
1057 wxFileType
*fileType
= new wxFileType
;
1058 fileType
->m_impl
->Init(this, index
);
1068 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo
& filetype
)
1070 const wxArrayString
& exts
= filetype
.GetExtensions();
1071 size_t nExts
= exts
.GetCount();
1072 for ( size_t nExt
= 0; nExt
< nExts
; nExt
++ ) {
1074 extensions
+= _T(' ');
1076 extensions
+= exts
[nExt
];
1079 AddMimeTypeInfo(filetype
.GetMimeType(),
1081 filetype
.GetDescription());
1083 AddMailcapInfo(filetype
.GetMimeType(),
1084 filetype
.GetOpenCommand(),
1085 filetype
.GetPrintCommand(),
1087 filetype
.GetDescription());
1090 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString
& strMimeType
,
1091 const wxString
& strExtensions
,
1092 const wxString
& strDesc
)
1094 int index
= m_aTypes
.Index(strMimeType
);
1095 if ( index
== wxNOT_FOUND
) {
1097 m_aTypes
.Add(strMimeType
);
1098 m_aEntries
.Add(NULL
);
1099 m_aExtensions
.Add(strExtensions
);
1100 m_aDescriptions
.Add(strDesc
);
1103 // modify an existing one
1104 if ( !strDesc
.IsEmpty() ) {
1105 m_aDescriptions
[index
] = strDesc
; // replace old value
1107 m_aExtensions
[index
] += strExtensions
;
1111 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString
& strType
,
1112 const wxString
& strOpenCmd
,
1113 const wxString
& strPrintCmd
,
1114 const wxString
& strTest
,
1115 const wxString
& strDesc
)
1117 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
, strPrintCmd
, strTest
);
1119 int nIndex
= m_aTypes
.Index(strType
);
1120 if ( nIndex
== wxNOT_FOUND
) {
1122 m_aTypes
.Add(strType
);
1124 m_aEntries
.Add(entry
);
1125 m_aExtensions
.Add(_T(""));
1126 m_aDescriptions
.Add(strDesc
);
1129 // always append the entry in the tail of the list - info added with
1130 // this function can only come from AddFallbacks()
1131 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1133 entry
->Append(entryOld
);
1135 m_aEntries
[nIndex
] = entry
;
1139 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString
& strFileName
)
1141 wxLogTrace(_T("--- Parsing mime.types file '%s' ---"), strFileName
.c_str());
1143 wxTextFile
file(strFileName
);
1147 // the information we extract
1148 wxString strMimeType
, strDesc
, strExtensions
;
1150 size_t nLineCount
= file
.GetLineCount();
1151 const wxChar
*pc
= NULL
;
1152 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1154 // now we're at the start of the line
1155 pc
= file
[nLine
].c_str();
1158 // we didn't finish with the previous line yet
1163 while ( wxIsspace(*pc
) )
1167 if ( *pc
== _T('#') ) {
1168 // skip the whole line
1173 // detect file format
1174 const wxChar
*pEqualSign
= wxStrchr(pc
, _T('='));
1175 if ( pEqualSign
== NULL
) {
1179 // first field is mime type
1180 for ( strMimeType
.Empty(); !wxIsspace(*pc
) && *pc
!= _T('\0'); pc
++ ) {
1185 while ( wxIsspace(*pc
) )
1188 // take all the rest of the string
1191 // no description...
1198 // the string on the left of '=' is the field name
1199 wxString
strLHS(pc
, pEqualSign
- pc
);
1202 for ( pc
= pEqualSign
+ 1; wxIsspace(*pc
); pc
++ )
1206 if ( *pc
== _T('"') ) {
1207 // the string is quoted and ends at the matching quote
1208 pEnd
= wxStrchr(++pc
, _T('"'));
1209 if ( pEnd
== NULL
) {
1210 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1212 strFileName
.c_str(), nLine
+ 1);
1216 // unquoted string ends at the first space
1217 for ( pEnd
= pc
; !wxIsspace(*pEnd
); pEnd
++ )
1221 // now we have the RHS (field value)
1222 wxString
strRHS(pc
, pEnd
- pc
);
1224 // check what follows this entry
1225 if ( *pEnd
== _T('"') ) {
1230 for ( pc
= pEnd
; wxIsspace(*pc
); pc
++ )
1233 // if there is something left, it may be either a '\\' to continue
1234 // the line or the next field of the same entry
1235 bool entryEnded
= *pc
== _T('\0'),
1236 nextFieldOnSameLine
= FALSE
;
1237 if ( !entryEnded
) {
1238 nextFieldOnSameLine
= ((*pc
!= _T('\\')) || (pc
[1] != _T('\0')));
1241 // now see what we got
1242 if ( strLHS
== _T("type") ) {
1243 strMimeType
= strRHS
;
1245 else if ( strLHS
== _T("desc") ) {
1248 else if ( strLHS
== _T("exts") ) {
1249 strExtensions
= strRHS
;
1252 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1253 strFileName
.c_str(), nLine
+ 1, strLHS
.c_str());
1256 if ( !entryEnded
) {
1257 if ( !nextFieldOnSameLine
)
1259 //else: don't reset it
1261 // as we don't reset strMimeType, the next field in this entry
1262 // will be interpreted correctly.
1268 // although it doesn't seem to be covered by RFCs, some programs
1269 // (notably Netscape) create their entries with several comma
1270 // separated extensions (RFC mention the spaces only)
1271 strExtensions
.Replace(_T(","), _T(" "));
1273 // also deal with the leading dot
1274 if ( !strExtensions
.IsEmpty() && strExtensions
[0] == _T('.') ) {
1275 strExtensions
.erase(0, 1);
1278 AddMimeTypeInfo(strMimeType
, strExtensions
, strDesc
);
1280 // finished with this line
1284 // check our data integriry
1285 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1286 m_aTypes
.Count() == m_aExtensions
.Count() &&
1287 m_aTypes
.Count() == m_aDescriptions
.Count() );
1292 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString
& strFileName
,
1295 wxLogTrace(_T("--- Parsing mailcap file '%s' ---"), strFileName
.c_str());
1297 wxTextFile
file(strFileName
);
1301 // see the comments near the end of function for the reason we need these
1302 // variables (search for the next occurence of them)
1303 // indices of MIME types (in m_aTypes) we already found in this file
1304 wxArrayInt aEntryIndices
;
1305 // aLastIndices[n] is the index of last element in
1306 // m_aEntries[aEntryIndices[n]] from this file
1307 wxArrayInt aLastIndices
;
1309 size_t nLineCount
= file
.GetLineCount();
1310 for ( size_t nLine
= 0; nLine
< nLineCount
; nLine
++ ) {
1311 // now we're at the start of the line
1312 const wxChar
*pc
= file
[nLine
].c_str();
1315 while ( wxIsspace(*pc
) )
1318 // comment or empty string?
1319 if ( *pc
== _T('#') || *pc
== _T('\0') )
1324 // what field are we currently in? The first 2 are fixed and there may
1325 // be an arbitrary number of other fields -- currently, we are not
1326 // interested in any of them, but we should parse them as well...
1332 } currentToken
= Field_Type
;
1334 // the flags and field values on the current line
1335 bool needsterminal
= FALSE
,
1336 copiousoutput
= FALSE
;
1342 curField
; // accumulator
1343 for ( bool cont
= TRUE
; cont
; pc
++ ) {
1346 // interpret the next character literally (notice that
1347 // backslash can be used for line continuation)
1348 if ( *++pc
== _T('\0') ) {
1349 // fetch the next line.
1351 // pc currently points to nowhere, but after the next
1352 // pc++ in the for line it will point to the beginning
1353 // of the next line in the file
1354 pc
= file
[++nLine
].c_str() - 1;
1357 // just a normal character
1363 cont
= FALSE
; // end of line reached, exit the loop
1368 // store this field and start looking for the next one
1370 // trim whitespaces from both sides
1371 curField
.Trim(TRUE
).Trim(FALSE
);
1373 switch ( currentToken
) {
1376 if ( strType
.Find(_T('/')) == wxNOT_FOUND
) {
1377 // we interpret "type" as "type/*"
1378 strType
+= _T("/*");
1381 currentToken
= Field_OpenCmd
;
1385 strOpenCmd
= curField
;
1387 currentToken
= Field_Other
;
1392 // "good" mailcap entry?
1395 // is this something of the form foo=bar?
1396 const wxChar
*pEq
= wxStrchr(curField
, _T('='));
1397 if ( pEq
!= NULL
) {
1398 wxString lhs
= curField
.BeforeFirst(_T('=')),
1399 rhs
= curField
.AfterFirst(_T('='));
1401 lhs
.Trim(TRUE
); // from right
1402 rhs
.Trim(FALSE
); // from left
1404 if ( lhs
== _T("print") )
1406 else if ( lhs
== _T("test") )
1408 else if ( lhs
== _T("description") ) {
1409 // it might be quoted
1410 if ( rhs
[0u] == _T('"') &&
1411 rhs
.Last() == _T('"') ) {
1412 strDesc
= wxString(rhs
.c_str() + 1,
1419 else if ( lhs
== _T("compose") ||
1420 lhs
== _T("composetyped") ||
1421 lhs
== _T("notes") ||
1429 // no, it's a simple flag
1430 // TODO support the flags:
1431 // 1. create an xterm for 'needsterminal'
1432 // 2. append "| $PAGER" for 'copiousoutput'
1433 if ( curField
== _T("needsterminal") )
1434 needsterminal
= TRUE
;
1435 else if ( curField
== _T("copiousoutput") )
1436 copiousoutput
= TRUE
;
1437 else if ( curField
== _T("textualnewlines") )
1445 // don't flood the user with error messages
1446 // if we don't understand something in his
1447 // mailcap, but give them in debug mode
1448 // because this might be useful for the
1452 _T("Mailcap file %s, line %d: unknown "
1453 "field '%s' for the MIME type "
1455 strFileName
.c_str(),
1463 // it already has this value
1464 //currentToken = Field_Other;
1468 wxFAIL_MSG(_T("unknown field type in mailcap"));
1471 // next token starts immediately after ';'
1480 // check that we really read something reasonable
1481 if ( currentToken
== Field_Type
|| currentToken
== Field_OpenCmd
) {
1482 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1484 strFileName
.c_str(), nLine
+ 1);
1487 MailCapEntry
*entry
= new MailCapEntry(strOpenCmd
,
1491 // NB: because of complications below (we must get entries priority
1492 // right), we can't use AddMailcapInfo() here, unfortunately.
1493 strType
.MakeLower();
1494 int nIndex
= m_aTypes
.Index(strType
);
1495 if ( nIndex
== wxNOT_FOUND
) {
1497 m_aTypes
.Add(strType
);
1499 m_aEntries
.Add(entry
);
1500 m_aExtensions
.Add(_T(""));
1501 m_aDescriptions
.Add(strDesc
);
1504 // modify the existing entry: the entries in one and the same
1505 // file are read in top-to-bottom order, i.e. the entries read
1506 // first should be tried before the entries below. However,
1507 // the files read later should override the settings in the
1508 // files read before (except if fallback is TRUE), thus we
1509 // Insert() the new entry to the list if it has already
1510 // occured in _this_ file, but Prepend() it if it occured in
1511 // some of the previous ones and Append() to it in the
1515 // 'fallback' parameter prevents the entries from this
1516 // file from overriding the other ones - always append
1517 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1519 entry
->Append(entryOld
);
1521 m_aEntries
[nIndex
] = entry
;
1524 int entryIndex
= aEntryIndices
.Index(nIndex
);
1525 if ( entryIndex
== wxNOT_FOUND
) {
1526 // first time in this file
1527 aEntryIndices
.Add(nIndex
);
1528 aLastIndices
.Add(0);
1530 entry
->Prepend(m_aEntries
[nIndex
]);
1531 m_aEntries
[nIndex
] = entry
;
1534 // not the first time in _this_ file
1535 size_t nEntryIndex
= (size_t)entryIndex
;
1536 MailCapEntry
*entryOld
= m_aEntries
[nIndex
];
1538 entry
->Insert(entryOld
, aLastIndices
[nEntryIndex
]);
1540 m_aEntries
[nIndex
] = entry
;
1542 // the indices were shifted by 1
1543 aLastIndices
[nEntryIndex
]++;
1547 if ( !strDesc
.IsEmpty() ) {
1548 // replace the old one - what else can we do??
1549 m_aDescriptions
[nIndex
] = strDesc
;
1554 // check our data integriry
1555 wxASSERT( m_aTypes
.Count() == m_aEntries
.Count() &&
1556 m_aTypes
.Count() == m_aExtensions
.Count() &&
1557 m_aTypes
.Count() == m_aDescriptions
.Count() );
1567 // wxUSE_FILE && wxUSE_TEXTFILE