]> git.saurik.com Git - wxWidgets.git/blame - src/common/mimetype.cpp
no message
[wxWidgets.git] / src / common / mimetype.cpp
CommitLineData
b13d92d1
VZ
1/////////////////////////////////////////////////////////////////////////////
2// Name: common/mimetype.cpp
3// Purpose: classes and functions to manage MIME types
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 23.09.98
7// RCS-ID: $Id$
8// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// Licence: wxWindows license (part of wxExtra library)
10/////////////////////////////////////////////////////////////////////////////
11
12#ifdef __GNUG__
13 #pragma implementation "mimetype.h"
14#endif
15
16// ============================================================================
17// declarations
18// ============================================================================
19
20// ----------------------------------------------------------------------------
21// headers
22// ----------------------------------------------------------------------------
23
24// for compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
28 #pragma hdrstop
29#endif
30
31// wxWindows
32#ifndef WX_PRECOMP
33 #include "wx/string.h"
2432b92d 34 #include "wx/icon.h"
b13d92d1
VZ
35#endif //WX_PRECOMP
36
37#include "wx/log.h"
38#include "wx/intl.h"
39#include "wx/dynarray.h"
2432b92d 40#include "wx/confbase.h"
b13d92d1
VZ
41
42#ifdef __WXMSW__
43 #include "wx/msw/registry.h"
2432b92d 44 #include "windows.h"
b13d92d1
VZ
45#else // Unix
46 #include "wx/textfile.h"
47#endif // OS
48
49#include "wx/mimetype.h"
50
51// other standard headers
52#include <ctype.h>
53
54// ----------------------------------------------------------------------------
55// private classes
56// ----------------------------------------------------------------------------
57
58// implementation classes, platform dependent
59#ifdef __WXMSW__
60
61// These classes use Windows registry to retrieve the required information.
62//
63// Keys used (not all of them are documented, so it might actually stop working
64// in futur versions of Windows...):
65// 1. "HKCR\MIME\Database\Content Type" contains subkeys for all known MIME
66// types, each key has a string value "Extension" which gives (dot preceded)
67// extension for the files of this MIME type.
68//
69// 2. "HKCR\.ext" contains
70// a) unnamed value containing the "filetype"
71// b) value "Content Type" containing the MIME type
72//
73// 3. "HKCR\filetype" contains
74// a) unnamed value containing the description
75// b) subkey "DefaultIcon" with single unnamed value giving the icon index in
76// an icon file
77// c) shell\open\command and shell\open\print subkeys containing the commands
78// to open/print the file (the positional parameters are introduced by %1,
79// %2, ... in these strings, we change them to %s ourselves)
80
81class wxFileTypeImpl
82{
83public:
84 // ctor
85 wxFileTypeImpl() { }
86
87 // initialize us with our file type name
88 void SetFileType(const wxString& strFileType)
89 { m_strFileType = strFileType; }
90 void SetExt(const wxString& ext)
91 { m_ext = ext; }
92
93 // implement accessor functions
94 bool GetExtensions(wxArrayString& extensions);
95 bool GetMimeType(wxString *mimeType) const;
96 bool GetIcon(wxIcon *icon) const;
97 bool GetDescription(wxString *desc) const;
98 bool GetOpenCommand(wxString *openCmd,
99 const wxFileType::MessageParameters&) const
100 { return GetCommand(openCmd, "open"); }
101 bool GetPrintCommand(wxString *printCmd,
102 const wxFileType::MessageParameters&) const
103 { return GetCommand(printCmd, "print"); }
104
105private:
106 // helper function
107 bool GetCommand(wxString *command, const char *verb) const;
108
109 wxString m_strFileType, m_ext;
110};
111
112class wxMimeTypesManagerImpl
113{
114public:
115 // nothing to do here, we don't load any data but just go and fetch it from
116 // the registry when asked for
117 wxMimeTypesManagerImpl() { }
118
119 // implement containing class functions
120 wxFileType *GetFileTypeFromExtension(const wxString& ext);
121 wxFileType *GetFileTypeFromMimeType(const wxString& mimeType);
122
123 // this are NOPs under Windows
124 void ReadMailcap(const wxString& filename) { }
125 void ReadMimeTypes(const wxString& filename) { }
126};
127
128#else // Unix
129
130// this class uses both mailcap and mime.types to gather information about file
131// types.
132//
133// The information about mailcap file was extracted from metamail(1) sources and
134// documentation.
135//
136// Format of mailcap file: spaces are ignored, each line is either a comment
137// (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
138// A backslash can be used to quote semicolons and newlines (and, in fact,
139// anything else including itself).
140//
141// The first field is always the MIME type in the form of type/subtype (see RFC
142// 822) where subtype may be '*' meaning "any". Following metamail, we accept
143// "type" which means the same as "type/*", although I'm not sure whether this
144// is standard.
145//
146// The second field is always the command to run. It is subject to
147// parameter/filename expansion described below.
148//
149// All the following fields are optional and may not be present at all. If
150// they're present they may appear in any order, although each of them should
151// appear only once. The optional fields are the following:
152// * notes=xxx is an uninterpreted string which is silently ignored
153// * test=xxx is the command to be used to determine whether this mailcap line
154// applies to our data or not. The RHS of this field goes through the
155// parameter/filename expansion (as the 2nd field) and the resulting string
156// is executed. The line applies only if the command succeeds, i.e. returns 0
157// exit code.
158// * print=xxx is the command to be used to print (and not view) the data of
159// this type (parameter/filename expansion is done here too)
160// * edit=xxx is the command to open/edit the data of this type
161// * needsterminal means that a new console must be created for the viewer
162// * copiousoutput means that the viewer doesn't interact with the user but
163// produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
164// good example), thus it might be a good idea to use some kind of paging
165// mechanism.
166// * textualnewlines means not to perform CR/LF translation (not honored)
167// * compose and composetyped fields are used to determine the program to be
168// called to create a new message pert in the specified format (unused).
169//
170// Parameter/filename xpansion:
171// * %s is replaced with the (full) file name
172// * %t is replaced with MIME type/subtype of the entry
173// * for multipart type only %n is replaced with the nnumber of parts and %F is
174// replaced by an array of (content-type, temporary file name) pairs for all
175// message parts (TODO)
176// * %{parameter} is replaced with the value of parameter taken from
177// Content-type header line of the message.
178//
179// FIXME any docs with real descriptions of these files??
180//
181// There are 2 possible formats for mime.types file, one entry per line (used
182// for global mime.types) and "expanded" format where an entry takes multiple
183// lines (used for users mime.types).
184//
185// For both formats spaces are ignored and lines starting with a '#' are
186// comments. Each record has one of two following forms:
187// a) for "brief" format:
188// <mime type> <space separated list of extensions>
189// b) for "expanded" format:
190// type=<mime type> \ desc="<description>" \ exts="ext"
191//
192// We try to autodetect the format of mime.types: if a non-comment line starts
193// with "type=" we assume the second format, otherwise the first one.
194
195// there may be more than one entry for one and the same mime type, to
196// choose the right one we have to run the command specified in the test
197// field on our data.
198class MailCapEntry
199{
200public:
201 // ctor
202 MailCapEntry(const wxString& openCmd,
203 const wxString& printCmd,
204 const wxString& testCmd)
205 : m_openCmd(openCmd), m_printCmd(printCmd), m_testCmd(testCmd)
206 {
207 m_next = NULL;
208 }
209
210 // accessors
211 const wxString& GetOpenCmd() const { return m_openCmd; }
212 const wxString& GetPrintCmd() const { return m_printCmd; }
213 const wxString& GetTestCmd() const { return m_testCmd; }
214
215 MailCapEntry *GetNext() const { return m_next; }
216
217 // operations
218 // prepend this element to the list
219 void Prepend(MailCapEntry *next) { m_next = next; }
220 // append to the list
221 void Append(MailCapEntry *next)
222 {
223 // FIXME slooow...
224 MailCapEntry *cur;
225 for ( cur = next; cur->m_next != NULL; cur = cur->m_next )
226 ;
227
228 cur->m_next = this;
229
230 // we initialize it in the ctor and there is no reason to both Prepend()
231 // and Append() one and the same entry
232 wxASSERT( m_next == NULL );
233 }
234
235private:
236 wxString m_openCmd, // command to use to open/view the file
237 m_printCmd, // print
238 m_testCmd; // only apply this entry if test yields
239 // true (i.e. the command returns 0)
240
241 MailCapEntry *m_next; // in the linked list
242};
243
244WX_DEFINE_ARRAY(MailCapEntry *, ArrayTypeEntries);
245
246class wxMimeTypesManagerImpl
247{
248friend class wxFileTypeImpl; // give it access to m_aXXX variables
249
250public:
251 // ctor loads all info into memory for quicker access later on
252 // @@ it would be nice to load them all, but parse on demand only...
253 wxMimeTypesManagerImpl();
254
255 // implement containing class functions
256 wxFileType *GetFileTypeFromExtension(const wxString& ext);
257 wxFileType *GetFileTypeFromMimeType(const wxString& mimeType);
258
259 void ReadMailcap(const wxString& filename);
260 void ReadMimeTypes(const wxString& filename);
261
262 // accessors
263 // get the string containing space separated extensions for the given
264 // file type
265 wxString GetExtension(size_t index) { return m_aExtensions[index]; }
266
267private:
268 wxArrayString m_aTypes, // MIME types
269 m_aDescriptions, // descriptions (just some text)
270 m_aExtensions; // space separated list of extensions
271 ArrayTypeEntries m_aEntries; // commands and tests for this file type
272};
273
274class wxFileTypeImpl
275{
276public:
277 // initialization functions
278 void Init(wxMimeTypesManagerImpl *manager, size_t index)
279 { m_manager = manager; m_index = index; }
280
281 // accessors
282 bool GetExtensions(wxArrayString& extensions);
283 bool GetMimeType(wxString *mimeType) const
284 { *mimeType = m_manager->m_aTypes[m_index]; return TRUE; }
285 bool GetIcon(wxIcon *icon) const
286 { return FALSE; } // @@ maybe with Gnome/KDE integration...
287 bool GetDescription(wxString *desc) const
288 { *desc = m_manager->m_aDescriptions[m_index]; return TRUE; }
289
290 bool GetOpenCommand(wxString *openCmd,
291 const wxFileType::MessageParameters& params) const
292 {
293 return GetExpandedCommand(openCmd, params, TRUE);
294 }
295
296 bool GetPrintCommand(wxString *printCmd,
297 const wxFileType::MessageParameters& params) const
298 {
299 return GetExpandedCommand(printCmd, params, FALSE);
300 }
301
302private:
303 // get the entry which passes the test (may return NULL)
304 MailCapEntry *GetEntry(const wxFileType::MessageParameters& params) const;
305
306 // choose the correct entry to use and expand the command
307 bool GetExpandedCommand(wxString *expandedCmd,
308 const wxFileType::MessageParameters& params,
309 bool open) const;
310
311 wxMimeTypesManagerImpl *m_manager;
312 size_t m_index; // in the wxMimeTypesManagerImpl arrays
313};
314
315#endif // OS type
316
317// ============================================================================
318// implementation of the wrapper classes
319// ============================================================================
320
321// ----------------------------------------------------------------------------
322// wxFileType
323// ----------------------------------------------------------------------------
324
325wxString wxFileType::ExpandCommand(const wxString& command,
326 const wxFileType::MessageParameters& params)
327{
328 bool hasFilename = FALSE;
329
330 wxString str;
331 for ( const char *pc = command.c_str(); *pc != '\0'; pc++ ) {
332 if ( *pc == '%' ) {
333 switch ( *++pc ) {
334 case 's':
335 // '%s' expands into file name (quoted because it might
336 // contain spaces) - except if there are already quotes
337 // there because otherwise some programs may get confused by
338 // double double quotes
339#if 0
340 if ( *(pc - 2) == '"' )
341 str << params.GetFileName();
342 else
343 str << '"' << params.GetFileName() << '"';
344#endif
345 str << params.GetFileName();
346 hasFilename = TRUE;
347 break;
348
349 case 't':
350 // '%t' expands into MIME type (quote it too just to be
351 // consistent)
352 str << '\'' << params.GetMimeType() << '\'';
353 break;
354
355 case '{':
356 {
357 const char *pEnd = strchr(pc, '}');
358 if ( pEnd == NULL ) {
359 wxString mimetype;
360 wxLogWarning(_("Unmatched '{' in an entry for "
361 "mime type %s."),
362 params.GetMimeType().c_str());
363 str << "%{";
364 }
365 else {
366 wxString param(pc + 1, pEnd - pc - 1);
367 str << '\'' << params.GetParamValue(param) << '\'';
368 pc = pEnd;
369 }
370 }
371 break;
372
373 case 'n':
374 case 'F':
375 // TODO %n is the number of parts, %F is an array containing
376 // the names of temp files these parts were written to
377 // and their mime types.
378 break;
379
380 default:
381 wxLogDebug("Unknown field %%%c in command '%s'.",
382 *pc, command.c_str());
383 str << *pc;
384 }
385 }
386 else {
387 str << *pc;
388 }
389 }
390
391 // metamail(1) man page states that if the mailcap entry doesn't have '%s'
392 // the program will accept the data on stdin: so give it to it!
393 if ( !hasFilename && !str.IsEmpty() ) {
394 str << " < '" << params.GetFileName() << '\'';
395 }
396
397 return str;
398}
399
400wxFileType::wxFileType()
401{
402 m_impl = new wxFileTypeImpl;
403}
404
405wxFileType::~wxFileType()
406{
407 delete m_impl;
408}
409
410bool wxFileType::GetExtensions(wxArrayString& extensions)
411{
412 return m_impl->GetExtensions(extensions);
413}
414
415bool wxFileType::GetMimeType(wxString *mimeType) const
416{
417 return m_impl->GetMimeType(mimeType);
418}
419
420bool wxFileType::GetIcon(wxIcon *icon) const
421{
422 return m_impl->GetIcon(icon);
423}
424
425bool wxFileType::GetDescription(wxString *desc) const
426{
427 return m_impl->GetDescription(desc);
428}
429
430bool
431wxFileType::GetOpenCommand(wxString *openCmd,
432 const wxFileType::MessageParameters& params) const
433{
434 return m_impl->GetOpenCommand(openCmd, params);
435}
436
437bool
438wxFileType::GetPrintCommand(wxString *printCmd,
439 const wxFileType::MessageParameters& params) const
440{
441 return m_impl->GetPrintCommand(printCmd, params);
442}
443
444// ----------------------------------------------------------------------------
445// wxMimeTypesManager
446// ----------------------------------------------------------------------------
447
448wxMimeTypesManager::wxMimeTypesManager()
449{
450 m_impl = new wxMimeTypesManagerImpl;
451}
452
453wxMimeTypesManager::~wxMimeTypesManager()
454{
455 delete m_impl;
456}
457
458wxFileType *
459wxMimeTypesManager::GetFileTypeFromExtension(const wxString& ext)
460{
461 return m_impl->GetFileTypeFromExtension(ext);
462}
463
464wxFileType *
465wxMimeTypesManager::GetFileTypeFromMimeType(const wxString& mimeType)
466{
467 return m_impl->GetFileTypeFromMimeType(mimeType);
468}
469
470// ============================================================================
471// real (OS specific) implementation
472// ============================================================================
473
474#ifdef __WXMSW__
475
476bool wxFileTypeImpl::GetCommand(wxString *command, const char *verb) const
477{
478 // suppress possible error messages
479 wxLogNull nolog;
480 wxString strKey;
481 strKey << m_strFileType << "\\shell\\" << verb << "\\command";
482 wxRegKey key(wxRegKey::HKCR, strKey);
483
484 if ( key.Open() ) {
485 // it's the default value of the key
486 if ( key.QueryValue("", *command) ) {
487 // transform it from '%1' to '%s' style format string
488 // @@ we don't make any attempt to verify that the string is valid,
489 // i.e. doesn't contain %2, or second %1 or .... But we do make
490 // sure that we return a string with _exactly_ one '%s'!
491 size_t len = command->Len();
492 for ( size_t n = 0; n < len; n++ ) {
493 if ( command->GetChar(n) == '%' &&
494 (n + 1 < len) && command->GetChar(n + 1) == '1' ) {
495 // replace it with '%s'
496 command->SetChar(n + 1, 's');
497
498 return TRUE;
499 }
500 }
501
502 // we didn't find any '%1'!
503 // @@@ hack: append the filename at the end, hope that it will do
504 *command << " %s";
505
506 return TRUE;
507 }
508 }
509
510 // no such file type or no value
511 return FALSE;
512}
513
514// @@ this function is half implemented
515bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
516{
517 if ( m_ext.IsEmpty() ) {
518 // the only way to get the list of extensions from the file type is to
519 // scan through all extensions in the registry - too slow...
520 return FALSE;
521 }
522 else {
523 extensions.Empty();
524 extensions.Add(m_ext);
525
526 // it's a lie too, we don't return _all_ extensions...
527 return TRUE;
528 }
529}
530
531bool wxFileTypeImpl::GetMimeType(wxString *mimeType) const
532{
533 // suppress possible error messages
534 wxLogNull nolog;
535 wxRegKey key(wxRegKey::HKCR, m_strFileType);
536 if ( key.Open() && key.QueryValue("Content Type", *mimeType) ) {
537 return TRUE;
538 }
539 else {
540 return FALSE;
541 }
542}
543
544bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
545{
546 wxString strIconKey;
547 strIconKey << m_strFileType << "\\DefaultIcon";
548
549 // suppress possible error messages
550 wxLogNull nolog;
551 wxRegKey key(wxRegKey::HKCR, strIconKey);
552
553 if ( key.Open() ) {
554 wxString strIcon;
555 // it's the default value of the key
556 if ( key.QueryValue("", strIcon) ) {
557 // the format is the following: <full path to file>, <icon index>
558 // NB: icon index may be negative as well as positive and the full
559 // path may contain the environment variables inside '%'
560 wxString strFullPath = strIcon.Before(','),
561 strIndex = strIcon.Right(',');
562
563 // index may be omitted, in which case Before(',') is empty and
564 // Right(',') is the whole string
565 if ( strFullPath.IsEmpty() ) {
566 strFullPath = strIndex;
567 strIndex = "0";
568 }
569
570 wxString strExpPath = wxExpandEnvVars(strFullPath);
571 int nIndex = atoi(strIndex);
572
573 HICON hIcon = ExtractIcon(GetModuleHandle(NULL), strExpPath, nIndex);
574 switch ( (int)hIcon ) {
575 case 0: // means no icons were found
576 case 1: // means no such file or it wasn't a DLL/EXE/OCX/ICO/...
577 wxLogDebug("incorrect registry entry '%s': no such icon.",
578 key.GetName().c_str());
579 break;
580
581 default:
582 icon->SetHICON((WXHICON)hIcon);
583 return TRUE;
584 }
585 }
586 }
587
588 // no such file type or no value or incorrect icon entry
589 return FALSE;
590}
591
592bool wxFileTypeImpl::GetDescription(wxString *desc) const
593{
594 // suppress possible error messages
595 wxLogNull nolog;
596 wxRegKey key(wxRegKey::HKCR, m_strFileType);
597
598 if ( key.Open() ) {
599 // it's the default value of the key
600 if ( key.QueryValue("", *desc) ) {
601 return TRUE;
602 }
603 }
604
605 return FALSE;
606}
607
608// extension -> file type
609wxFileType *
610wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
611{
612 // add the leading point if necessary
613 wxString str;
614 if ( ext[0u] != '.' ) {
615 str = '.';
616 }
617 str << ext;
618
619 // suppress possible error messages
620 wxLogNull nolog;
621
622 wxString strFileType;
623 wxRegKey key(wxRegKey::HKCR, str);
624 if ( key.Open() ) {
625 // it's the default value of the key
626 if ( key.QueryValue("", strFileType) ) {
627 // create the new wxFileType object
628 wxFileType *fileType = new wxFileType;
629 fileType->m_impl->SetFileType(strFileType);
630 fileType->m_impl->SetExt(ext);
631
632 return fileType;
633 }
634 }
635
636 // unknown extension
637 return NULL;
638}
639
640// MIME type -> extension -> file type
641wxFileType *
642wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
643{
644 // @@@ I don't know of any official documentation which mentions this
645 // location, but as a matter of fact IE uses it, so why not we?
646 static const char *szMimeDbase = "MIME\\Database\\Content Type\\";
647
648 wxString strKey = szMimeDbase;
649 strKey << mimeType;
650
651 // suppress possible error messages
652 wxLogNull nolog;
653
654 wxString ext;
655 wxRegKey key(wxRegKey::HKCR, strKey);
656 if ( key.Open() ) {
657 if ( key.QueryValue("Extension", ext) ) {
658 return GetFileTypeFromExtension(ext);
659 }
660 }
661
662 // unknown MIME type
663 return NULL;
664}
665
666#else // Unix
667
668MailCapEntry *
669wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
670{
671 wxString command;
672 MailCapEntry *entry = m_manager->m_aEntries[m_index];
673 while ( entry != NULL ) {
674 // notice that an empty command would always succeed (@@ is it ok?)
675 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
676
677 if ( command.IsEmpty() || (system(command) == 0) ) {
678 // ok, passed
679 wxLogTrace("Test '%s' for mime type '%s' succeeded.",
680 command.c_str(), params.GetMimeType().c_str());
681 break;
682 }
683 else {
684 wxLogTrace("Test '%s' for mime type '%s' failed.",
685 command.c_str(), params.GetMimeType().c_str());
686 }
687
688 entry = entry->GetNext();
689 }
690
691 return entry;
692}
693
694bool
695wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
696 const wxFileType::MessageParameters& params,
697 bool open) const
698{
699 MailCapEntry *entry = GetEntry(params);
700 if ( entry == NULL ) {
701 // all tests failed...
702 return FALSE;
703 }
704
705 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
706 if ( cmd.IsEmpty() ) {
707 // may happen, especially for "print"
708 return FALSE;
709 }
710
711 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
712 return TRUE;
713}
714
715bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
716{
717 wxString strExtensions = m_manager->GetExtension(m_index);
718 extensions.Empty();
719
720 // one extension in the space or comma delimitid list
721 wxString strExt;
722 for ( const char *p = strExtensions; ; p++ ) {
723 if ( *p == ' ' || *p == ',' || *p == '\0' ) {
724 if ( !strExt.IsEmpty() ) {
725 extensions.Add(strExt);
726 strExt.Empty();
727 }
728 //else: repeated spaces (shouldn't happen, but it's not that
729 // important if it does happen)
730
731 if ( *p == '\0' )
732 break;
733 }
734 else if ( *p == '.' ) {
735 // remove the dot from extension (but only if it's the first char)
736 if ( !strExt.IsEmpty() ) {
737 strExt += '.';
738 }
739 //else: no, don't append it
740 }
741 else {
742 strExt += *p;
743 }
744 }
745
746 return TRUE;
747}
748
749// read system and user mailcaps (TODO implement mime.types support)
750wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
751{
752 // directories where we look for mailcap and mime.types by default
753 // (taken from metamail(1) sources)
754 static const char *aStandardLocations[] =
755 {
756 "/etc",
757 "/usr/etc",
758 "/usr/local/etc",
759 "/etc/mail",
760 "/usr/public/lib"
761 };
762
763 // first read the system wide file(s)
764 for ( size_t n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
765 wxString dir = aStandardLocations[n];
766
767 wxString file = dir + "/mailcap";
768 if ( wxFile::Exists(file) ) {
769 ReadMailcap(file);
770 }
771
772 file = dir + "/mime.types";
773 if ( wxFile::Exists(file) ) {
774 ReadMimeTypes(file);
775 }
776 }
777
778 wxString strHome = getenv("HOME");
779
780 // and now the users mailcap
781 wxString strUserMailcap = strHome + "/.mailcap";
782 if ( wxFile::Exists(strUserMailcap) ) {
783 ReadMailcap(strUserMailcap);
784 }
785
786 // read the users mime.types
787 wxString strUserMimeTypes = strHome + "/.mime.types";
788 if ( wxFile::Exists(strUserMimeTypes) ) {
789 ReadMimeTypes(strUserMimeTypes);
790 }
791}
792
793wxFileType *
794wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
795{
796 wxFAIL_MSG("not implemented (must parse mime.types)");
797
798 return NULL;
799}
800
801wxFileType *
802wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
803{
804 // mime types are not case-sensitive
805 wxString mimetype(mimeType);
806 mimetype.MakeLower();
807
808 // first look for an exact match
809 int index = m_aTypes.Index(mimetype);
810 if ( index == NOT_FOUND ) {
811 // then try to find "text/*" as match for "text/plain" (for example)
812 // NB: if mimeType doesn't contain '/' at all, Left() will return the
813 // whole string - ok.
814 wxString strCategory = mimetype.Left('/');
815
816 size_t nCount = m_aTypes.Count();
817 for ( size_t n = 0; n < nCount; n++ ) {
818 if ( (m_aTypes[n].Before('/') == strCategory ) &&
819 m_aTypes[n].Right('/') == "*" ) {
820 index = n;
821 break;
822 }
823 }
824 }
825
826 if ( index != NOT_FOUND ) {
827 wxFileType *fileType = new wxFileType;
828 fileType->m_impl->Init(this, index);
829
830 return fileType;
831 }
832 else {
833 // not found...
834 return NULL;
835 }
836}
837
838void wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
839{
840 wxLogTrace("--- Parsing mime.types file '%s' ---", strFileName.c_str());
841
842 wxTextFile file(strFileName);
843 if ( !file.Open() )
844 return;
845
846 // the information we extract
847 wxString strMimeType, strDesc, strExtensions;
848
849 size_t nLineCount = file.GetLineCount();
850 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
851 // now we're at the start of the line
852 const char *pc = file[nLine].c_str();
853
854 // skip whitespace
855 while ( isspace(*pc) )
856 pc++;
857
858 // comment?
859 if ( *pc == '#' )
860 continue;
861
862 // detect file format
863 const char *pEqualSign = strchr(pc, '=');
864 if ( pEqualSign == NULL ) {
865 // brief format
866 // ------------
867
868 // first field is mime type
869 for ( strMimeType.Empty(); !isspace(*pc) && *pc != '\0'; pc++ ) {
870 strMimeType += *pc;
871 }
872
873 // skip whitespace
874 while ( isspace(*pc) )
875 pc++;
876
877 // take all the rest of the string
878 strExtensions = pc;
879
880 // no description...
881 strDesc.Empty();
882 }
883 else {
884 // expanded format
885 // ---------------
886
887 // the string on the left of '=' is the field name
888 wxString strLHS(pc, pEqualSign - pc);
889
890 // eat whitespace
891 for ( pc = pEqualSign + 1; isspace(*pc); pc++ )
892 ;
893
894 const char *pEnd;
895 if ( *pc == '"' ) {
896 // the string is quoted and ends at the matching quote
897 pEnd = strchr(++pc, '"');
898 if ( pEnd == NULL ) {
899 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
900 "quoted string."),
901 strFileName.c_str(), nLine + 1);
902 }
903 }
904 else {
905 // unquoted stringends at the first space
906 for ( pEnd = pc; !isspace(*pEnd); pEnd++ )
907 ;
908 }
909
910 // now we have the RHS (field value)
911 wxString strRHS(pc, pEnd - pc);
912
913 // check that it's more or less what we're waiting for, i.e. that
914 // only '\' is left on the line
915 if ( *pEnd == '"' ) {
916 // skip this quote
917 pEnd++;
918 }
919
920 for ( pc = pEnd; isspace(*pc); pc++ )
921 ;
922
923 // only '\\' may be left on the line normally
924 bool entryEnded = *pc == '\0';
925 if ( !entryEnded && ((*pc != '\\') || (*++pc != '\0')) ) {
926 wxLogWarning(_("Mime.types file %s, line %d: extra characters "
927 "after the field value ignored."),
928 strFileName.c_str(), nLine + 1);
929 }
930 // if there is a trailing backslash entryEnded = FALSE
931
932 // now see what we got
933 if ( strLHS == "type" ) {
934 strMimeType = strRHS;
935 }
936 else if ( strLHS == "desc" ) {
937 strDesc = strRHS;
938 }
939 else if ( strLHS == "exts" ) {
940 strExtensions = strRHS;
941 }
942 else {
943 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
944 strFileName.c_str(), nLine + 1, strLHS.c_str());
945 }
946
947 if ( !entryEnded ) {
948 // as we don't reset strMimeType, the next line in this entry
949 // will be interpreted correctly.
950 continue;
951 }
952 }
953
954 int index = m_aTypes.Index(strMimeType);
955 if ( index == NOT_FOUND ) {
956 // add a new entry
957 m_aTypes.Add(strMimeType);
958 m_aEntries.Add(NULL);
959 m_aExtensions.Add(strExtensions);
960 m_aDescriptions.Add(strDesc);
961 }
962 else {
963 // modify an existing one
964 if ( !strDesc.IsEmpty() ) {
965 m_aDescriptions[index] = strDesc; // replace old value
966 }
967 m_aExtensions[index] += strExtensions;
968 }
969 }
970
971 // check our data integriry
972 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
973 m_aTypes.Count() == m_aExtensions.Count() &&
974 m_aTypes.Count() == m_aDescriptions.Count() );
975}
976
977void wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName)
978{
979 wxLogTrace("--- Parsing mailcap file '%s' ---", strFileName.c_str());
980
981 wxTextFile file(strFileName);
982 if ( !file.Open() )
983 return;
984
985 // see the comments near the end of function for the reason we need this
986 wxArrayInt aEntryIndices;
987
988 size_t nLineCount = file.GetLineCount();
989 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
990 // now we're at the start of the line
991 const char *pc = file[nLine].c_str();
992
993 // skip whitespace
994 while ( isspace(*pc) )
995 pc++;
996
997 // comment or empty string?
998 if ( *pc == '#' || *pc == '\0' )
999 continue;
1000
1001 // no, do parse
1002
1003 // what field are we currently in? The first 2 are fixed and there may
1004 // be an arbitrary number of other fields -- currently, we are not
1005 // interested in any of them, but we should parse them as well...
1006 enum
1007 {
1008 Field_Type,
1009 Field_OpenCmd,
1010 Field_Other
1011 } currentToken = Field_Type;
1012
1013 // the flags and field values on the current line
1014 bool needsterminal = false,
1015 copiousoutput = false;
1016 wxString strType,
1017 strOpenCmd,
1018 strPrintCmd,
1019 strTest,
1020 strDesc,
1021 curField; // accumulator
1022 for ( bool cont = TRUE; cont; pc++ ) {
1023 switch ( *pc ) {
1024 case '\\':
1025 // interpret the next character literally (notice that
1026 // backslash can be used for line continuation)
1027 if ( *++pc == '\0' ) {
1028 // fetch the next line.
1029
1030 // pc currently points to nowhere, but after the next
1031 // pc++ in the for line it will point to the beginning
1032 // of the next line in the file
1033 pc = file[++nLine].c_str() - 1;
1034 }
1035 else {
1036 // just a normal character
1037 curField += *pc;
1038 }
1039 break;
1040
1041 case '\0':
1042 cont = FALSE; // end of line reached, exit the loop
1043
1044 // fall through
1045
1046 case ';':
1047 // store this field and start looking for the next one
1048
1049 // trim whitespaces from both sides
1050 curField.Trim(TRUE).Trim(FALSE);
1051
1052 switch ( currentToken ) {
1053 case Field_Type:
1054 strType = curField;
1055 if ( strType.Find('/') == NOT_FOUND ) {
1056 // we interpret "type" as "type/*"
1057 strType += "/*";
1058 }
1059
1060 currentToken = Field_OpenCmd;
1061 break;
1062
1063 case Field_OpenCmd:
1064 strOpenCmd = curField;
1065
1066 currentToken = Field_Other;
1067 break;
1068
1069 case Field_Other:
1070 {
1071 // "good" mailcap entry?
1072 bool ok = TRUE;
1073
1074 // is this something of the form foo=bar?
1075 const char *pEq = strchr(curField, '=');
1076 if ( pEq != NULL ) {
1077 wxString lhs = curField.Left('='),
1078 rhs = curField.After('=');
1079
1080 lhs.Trim(TRUE); // from right
1081 rhs.Trim(FALSE); // from left
1082
1083 if ( lhs == "print" )
1084 strPrintCmd = rhs;
1085 else if ( lhs == "test" )
1086 strTest = rhs;
1087 else if ( lhs == "description" ) {
1088 // it might be quoted
1089 if ( rhs[0u] == '"' &&
1090 rhs.Last() == '"' ) {
1091 strDesc = wxString(rhs.c_str() + 1,
1092 rhs.Len() - 2);
1093 }
1094 else {
1095 strDesc = rhs;
1096 }
1097 }
1098 else if ( lhs == "compose" ||
1099 lhs == "composetyped" ||
1100 lhs == "notes" ||
1101 lhs == "edit" )
1102 ; // ignore
1103 else
1104 ok = FALSE;
1105
1106 }
1107 else {
1108 // no, it's a simple flag
1109 // TODO support the flags:
1110 // 1. create an xterm for 'needsterminal'
1111 // 2. append "| $PAGER" for 'copiousoutput'
1112 if ( curField == "needsterminal" )
1113 needsterminal = TRUE;
1114 else if ( curField == "copiousoutput" )
1115 copiousoutput = TRUE;
1116 else if ( curField == "textualnewlines" )
1117 ; // ignore
1118 else
1119 ok = FALSE;
1120 }
1121
1122 if ( !ok )
1123 {
1124 // don't flood the user with error messages
1125 // if we don't understand something in his
1126 // mailcap
1127 wxLogDebug
1128 (
1129 _("Mailcap file %s, line %d: unknown "
1130 "field '%s' for the MIME type "
1131 "'%s' ignored."),
1132 strFileName.c_str(),
1133 nLine + 1,
1134 curField.c_str(),
1135 strType.c_str()
1136 );
1137 }
1138 }
1139
1140 // it already has this value
1141 //currentToken = Field_Other;
1142 break;
1143
1144 default:
1145 wxFAIL_MSG("unknown field type in mailcap");
1146 }
1147
1148 // next token starts immediately after ';'
1149 curField.Empty();
1150 break;
1151
1152 default:
1153 curField += *pc;
1154 }
1155 }
1156
1157 // check that we really read something reasonable
1158 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1159 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1160 "ignored."),
1161 strFileName.c_str(), nLine + 1);
1162 }
1163 else {
1164 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1165 strPrintCmd,
1166 strTest);
1167
1168 strType.MakeLower();
1169 int nIndex = m_aTypes.Index(strType);
1170 if ( nIndex == NOT_FOUND ) {
1171 // new file type
1172 m_aTypes.Add(strType);
1173
1174 m_aEntries.Add(entry);
1175 m_aExtensions.Add("");
1176 m_aDescriptions.Add(strDesc);
1177 }
1178 else {
1179 // modify the existing entry: the entry in one and the same file
1180 // are read in top-to-bottom order, i.e. the entries read first
1181 // should be tried before the entries below. However, the files
1182 // read later should override the settings in the files read
1183 // before, thus we Append() the new entry to the list if it has
1184 // already occured in _this_ file, but Prepend() it if it
1185 // occured in some of the previous ones.
1186 if ( aEntryIndices.Index(nIndex) == NOT_FOUND ) {
1187 // first time in this file
1188 aEntryIndices.Add(nIndex);
1189 entry->Prepend(m_aEntries[nIndex]);
1190 m_aEntries[nIndex] = entry;
1191 }
1192 else {
1193 // not the first time in _this_ file
1194 entry->Append(m_aEntries[nIndex]);
1195 }
1196
1197 if ( !strDesc.IsEmpty() ) {
1198 // @@ replace the old one - what else can we do??
1199 m_aDescriptions[nIndex] = strDesc;
1200 }
1201 }
1202 }
1203
1204 // check our data integriry
1205 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1206 m_aTypes.Count() == m_aExtensions.Count() &&
1207 m_aTypes.Count() == m_aDescriptions.Count() );
1208 }
1209}
1210
1211#endif // OS type
1212
1213/* vi: set cin tw=80 ts=4 sw=4: */
1214