]> git.saurik.com Git - wxWidgets.git/blob - src/common/mimetype.cpp
no message
[wxWidgets.git] / src / common / mimetype.cpp
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"
34 #include "wx/icon.h"
35 #endif //WX_PRECOMP
36
37 #include "wx/log.h"
38 #include "wx/intl.h"
39 #include "wx/dynarray.h"
40 #include "wx/confbase.h"
41
42 #ifdef __WXMSW__
43 #include "wx/msw/registry.h"
44 #include "windows.h"
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
81 class wxFileTypeImpl
82 {
83 public:
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
105 private:
106 // helper function
107 bool GetCommand(wxString *command, const char *verb) const;
108
109 wxString m_strFileType, m_ext;
110 };
111
112 class wxMimeTypesManagerImpl
113 {
114 public:
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.
198 class MailCapEntry
199 {
200 public:
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
235 private:
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
244 WX_DEFINE_ARRAY(MailCapEntry *, ArrayTypeEntries);
245
246 class wxMimeTypesManagerImpl
247 {
248 friend class wxFileTypeImpl; // give it access to m_aXXX variables
249
250 public:
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
267 private:
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
274 class wxFileTypeImpl
275 {
276 public:
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
302 private:
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
325 wxString 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
400 wxFileType::wxFileType()
401 {
402 m_impl = new wxFileTypeImpl;
403 }
404
405 wxFileType::~wxFileType()
406 {
407 delete m_impl;
408 }
409
410 bool wxFileType::GetExtensions(wxArrayString& extensions)
411 {
412 return m_impl->GetExtensions(extensions);
413 }
414
415 bool wxFileType::GetMimeType(wxString *mimeType) const
416 {
417 return m_impl->GetMimeType(mimeType);
418 }
419
420 bool wxFileType::GetIcon(wxIcon *icon) const
421 {
422 return m_impl->GetIcon(icon);
423 }
424
425 bool wxFileType::GetDescription(wxString *desc) const
426 {
427 return m_impl->GetDescription(desc);
428 }
429
430 bool
431 wxFileType::GetOpenCommand(wxString *openCmd,
432 const wxFileType::MessageParameters& params) const
433 {
434 return m_impl->GetOpenCommand(openCmd, params);
435 }
436
437 bool
438 wxFileType::GetPrintCommand(wxString *printCmd,
439 const wxFileType::MessageParameters& params) const
440 {
441 return m_impl->GetPrintCommand(printCmd, params);
442 }
443
444 // ----------------------------------------------------------------------------
445 // wxMimeTypesManager
446 // ----------------------------------------------------------------------------
447
448 wxMimeTypesManager::wxMimeTypesManager()
449 {
450 m_impl = new wxMimeTypesManagerImpl;
451 }
452
453 wxMimeTypesManager::~wxMimeTypesManager()
454 {
455 delete m_impl;
456 }
457
458 wxFileType *
459 wxMimeTypesManager::GetFileTypeFromExtension(const wxString& ext)
460 {
461 return m_impl->GetFileTypeFromExtension(ext);
462 }
463
464 wxFileType *
465 wxMimeTypesManager::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
476 bool 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
515 bool 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
531 bool 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
544 bool 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
592 bool 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
609 wxFileType *
610 wxMimeTypesManagerImpl::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
641 wxFileType *
642 wxMimeTypesManagerImpl::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
668 MailCapEntry *
669 wxFileTypeImpl::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
694 bool
695 wxFileTypeImpl::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
715 bool 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)
750 wxMimeTypesManagerImpl::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
793 wxFileType *
794 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
795 {
796 wxFAIL_MSG("not implemented (must parse mime.types)");
797
798 return NULL;
799 }
800
801 wxFileType *
802 wxMimeTypesManagerImpl::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
838 void 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
977 void 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