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