fixed crash with '\' on the end of the last line
[wxWidgets.git] / src / unix / mimetype.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: unix/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 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "mimetype.h"
22 #endif
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 #ifndef WX_PRECOMP
32 #include "wx/defs.h"
33 #endif
34
35 #if wxUSE_FILE && wxUSE_TEXTFILE
36
37 #ifndef WX_PRECOMP
38 #include "wx/string.h"
39 #if wxUSE_GUI
40 #include "wx/icon.h"
41 #endif
42 #endif //WX_PRECOMP
43
44
45 #include "wx/log.h"
46 #include "wx/file.h"
47 #include "wx/intl.h"
48 #include "wx/dynarray.h"
49 #include "wx/confbase.h"
50
51 #include "wx/ffile.h"
52 #include "wx/textfile.h"
53 #include "wx/dir.h"
54 #include "wx/utils.h"
55 #include "wx/tokenzr.h"
56
57 #include "wx/unix/mimetype.h"
58
59 // other standard headers
60 #include <ctype.h>
61
62 // in case we're compiling in non-GUI mode
63 class WXDLLEXPORT wxIcon;
64
65 // ----------------------------------------------------------------------------
66 // constants
67 // ----------------------------------------------------------------------------
68
69 // MIME code tracing mask
70 #define TRACE_MIME _T("mime")
71
72 // ----------------------------------------------------------------------------
73 // private functions
74 // ----------------------------------------------------------------------------
75
76 // there are some fields which we don't understand but for which we don't give
77 // warnings as we know that they're not important - this function is used to
78 // test for them
79 static bool IsKnownUnimportantField(const wxString& field);
80
81 // ----------------------------------------------------------------------------
82 // private classes
83 // ----------------------------------------------------------------------------
84
85 // This class uses both mailcap and mime.types to gather information about file
86 // types.
87 //
88 // The information about mailcap file was extracted from metamail(1) sources
89 // and documentation and subsequently revised when I found the RFC 1524
90 // describing it.
91 //
92 // Format of mailcap file: spaces are ignored, each line is either a comment
93 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
94 // A backslash can be used to quote semicolons and newlines (and, in fact,
95 // anything else including itself).
96 //
97 // The first field is always the MIME type in the form of type/subtype (see RFC
98 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
99 // "type" which means the same as "type/*", although I'm not sure whether this
100 // is standard.
101 //
102 // The second field is always the command to run. It is subject to
103 // parameter/filename expansion described below.
104 //
105 // All the following fields are optional and may not be present at all. If
106 // they're present they may appear in any order, although each of them should
107 // appear only once. The optional fields are the following:
108 // * notes=xxx is an uninterpreted string which is silently ignored
109 // * test=xxx is the command to be used to determine whether this mailcap line
110 // applies to our data or not. The RHS of this field goes through the
111 // parameter/filename expansion (as the 2nd field) and the resulting string
112 // is executed. The line applies only if the command succeeds, i.e. returns 0
113 // exit code.
114 // * print=xxx is the command to be used to print (and not view) the data of
115 // this type (parameter/filename expansion is done here too)
116 // * edit=xxx is the command to open/edit the data of this type
117 // * needsterminal means that a new interactive console must be created for
118 // the viewer
119 // * copiousoutput means that the viewer doesn't interact with the user but
120 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
121 // good example), thus it might be a good idea to use some kind of paging
122 // mechanism.
123 // * textualnewlines means not to perform CR/LF translation (not honored)
124 // * compose and composetyped fields are used to determine the program to be
125 // called to create a new message pert in the specified format (unused).
126 //
127 // Parameter/filename expansion:
128 // * %s is replaced with the (full) file name
129 // * %t is replaced with MIME type/subtype of the entry
130 // * for multipart type only %n is replaced with the nnumber of parts and %F is
131 // replaced by an array of (content-type, temporary file name) pairs for all
132 // message parts (TODO)
133 // * %{parameter} is replaced with the value of parameter taken from
134 // Content-type header line of the message.
135 //
136 //
137 // There are 2 possible formats for mime.types file, one entry per line (used
138 // for global mime.types and called Mosaic format) and "expanded" format where
139 // an entry takes multiple lines (used for users mime.types and called
140 // Netscape format).
141 //
142 // For both formats spaces are ignored and lines starting with a '#' are
143 // comments. Each record has one of two following forms:
144 // a) for "brief" format:
145 // <mime type> <space separated list of extensions>
146 // b) for "expanded" format:
147 // type=<mime type> \
148 // desc="<description>" \
149 // exts="<comma separated list of extensions>"
150 //
151 // We try to autodetect the format of mime.types: if a non-comment line starts
152 // with "type=" we assume the second format, otherwise the first one.
153
154 // there may be more than one entry for one and the same mime type, to
155 // choose the right one we have to run the command specified in the test
156 // field on our data.
157 class MailCapEntry
158 {
159 public:
160 // ctor
161 MailCapEntry(const wxString& openCmd,
162 const wxString& printCmd,
163 const wxString& testCmd)
164 : m_openCmd(openCmd), m_printCmd(printCmd), m_testCmd(testCmd)
165 {
166 m_next = NULL;
167 }
168
169 ~MailCapEntry()
170 {
171 if (m_next) delete m_next;
172 }
173
174 // accessors
175 const wxString& GetOpenCmd() const { return m_openCmd; }
176 const wxString& GetPrintCmd() const { return m_printCmd; }
177 const wxString& GetTestCmd() const { return m_testCmd; }
178
179 MailCapEntry *GetNext() const { return m_next; }
180
181 // operations
182 // prepend this element to the list
183 void Prepend(MailCapEntry *next) { m_next = next; }
184 // insert into the list at given position
185 void Insert(MailCapEntry *next, size_t pos)
186 {
187 // FIXME slooow...
188 MailCapEntry *cur;
189 size_t n = 0;
190 for ( cur = next; cur != NULL; cur = cur->m_next, n++ ) {
191 if ( n == pos )
192 break;
193 }
194
195 wxASSERT_MSG( n == pos, wxT("invalid position in MailCapEntry::Insert") );
196
197 m_next = cur->m_next;
198 cur->m_next = this;
199 }
200 // append this element to the list
201 void Append(MailCapEntry *next)
202 {
203 wxCHECK_RET( next != NULL, wxT("Append()ing to what?") );
204
205 // FIXME slooow...
206 MailCapEntry *cur;
207 for ( cur = next; cur->m_next != NULL; cur = cur->m_next )
208 ;
209
210 cur->m_next = this;
211
212 wxASSERT_MSG( !m_next, wxT("Append()ing element already in the list?") );
213 }
214
215 private:
216 wxString m_openCmd, // command to use to open/view the file
217 m_printCmd, // print
218 m_testCmd; // only apply this entry if test yields
219 // true (i.e. the command returns 0)
220
221 MailCapEntry *m_next; // in the linked list
222 };
223
224
225 // the base class which may be used to find an icon for the MIME type
226 class wxMimeTypeIconHandler
227 {
228 public:
229 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon) = 0;
230
231 // this function fills manager with MIME types information gathered
232 // (as side effect) when searching for icons. This may be particularly
233 // useful if mime.types is incomplete (e.g. RedHat distributions).
234 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager) = 0;
235 };
236
237
238 // the icon handler which uses GNOME MIME database
239 class wxGNOMEIconHandler : public wxMimeTypeIconHandler
240 {
241 public:
242 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
243 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
244
245 private:
246 void Init();
247 void LoadIconsFromKeyFile(const wxString& filename);
248 void LoadKeyFilesFromDir(const wxString& dirbase);
249
250 void LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager);
251 void LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager);
252
253 static bool m_inited;
254
255 static wxSortedArrayString ms_mimetypes;
256 static wxArrayString ms_icons;
257 };
258
259 // the icon handler which uses KDE MIME database
260 class wxKDEIconHandler : public wxMimeTypeIconHandler
261 {
262 public:
263 virtual bool GetIcon(const wxString& mimetype, wxIcon *icon);
264 virtual void GetMimeInfoRecords(wxMimeTypesManagerImpl *manager);
265
266 private:
267 void LoadLinksForMimeSubtype(const wxString& dirbase,
268 const wxString& subdir,
269 const wxString& filename,
270 const wxArrayString& icondirs);
271 void LoadLinksForMimeType(const wxString& dirbase,
272 const wxString& subdir,
273 const wxArrayString& icondirs);
274 void LoadLinkFilesFromDir(const wxString& dirbase,
275 const wxArrayString& icondirs);
276 void Init();
277
278 static bool m_inited;
279
280 static wxSortedArrayString ms_mimetypes;
281 static wxArrayString ms_icons;
282
283 static wxArrayString ms_infoTypes;
284 static wxArrayString ms_infoDescriptions;
285 static wxArrayString ms_infoExtensions;
286 };
287
288
289
290 // ----------------------------------------------------------------------------
291 // various statics
292 // ----------------------------------------------------------------------------
293
294 static wxGNOMEIconHandler gs_iconHandlerGNOME;
295 static wxKDEIconHandler gs_iconHandlerKDE;
296
297 bool wxGNOMEIconHandler::m_inited = FALSE;
298 wxSortedArrayString wxGNOMEIconHandler::ms_mimetypes;
299 wxArrayString wxGNOMEIconHandler::ms_icons;
300
301 bool wxKDEIconHandler::m_inited = FALSE;
302 wxSortedArrayString wxKDEIconHandler::ms_mimetypes;
303 wxArrayString wxKDEIconHandler::ms_icons;
304
305 wxArrayString wxKDEIconHandler::ms_infoTypes;
306 wxArrayString wxKDEIconHandler::ms_infoDescriptions;
307 wxArrayString wxKDEIconHandler::ms_infoExtensions;
308
309
310 ArrayIconHandlers wxMimeTypesManagerImpl::ms_iconHandlers;
311
312 // ----------------------------------------------------------------------------
313 // wxGNOMEIconHandler
314 // ----------------------------------------------------------------------------
315
316 // GNOME stores the info we're interested in in several locations:
317 // 1. xxx.keys files under /usr/share/mime-info
318 // 2. xxx.keys files under ~/.gnome/mime-info
319 //
320 // The format of xxx.keys file is the following:
321 //
322 // mimetype/subtype:
323 // field=value
324 //
325 // with blank lines separating the entries and indented lines starting with
326 // TABs. We're interested in the field icon-filename whose value is the path
327 // containing the icon.
328 //
329 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
330 // just before the field name.
331
332 void wxGNOMEIconHandler::LoadIconsFromKeyFile(const wxString& filename)
333 {
334 wxTextFile textfile(filename);
335 if ( !textfile.Open() )
336 return;
337
338 // values for the entry being parsed
339 wxString curMimeType, curIconFile;
340
341 const wxChar *pc;
342 size_t nLineCount = textfile.GetLineCount();
343 for ( size_t nLine = 0; ; nLine++ )
344 {
345 if ( nLine < nLineCount )
346 {
347 pc = textfile[nLine].c_str();
348 if ( *pc == _T('#') )
349 {
350 // skip comments
351 continue;
352 }
353 }
354 else
355 {
356 // so that we will fall into the "if" below
357 pc = NULL;
358 }
359
360 if ( !pc || !*pc )
361 {
362 // end of the entry
363 if ( !!curMimeType && !!curIconFile )
364 {
365 // do we already know this mimetype?
366 int i = ms_mimetypes.Index(curMimeType);
367 if ( i == wxNOT_FOUND )
368 {
369 // add a new entry
370 size_t n = ms_mimetypes.Add(curMimeType);
371 ms_icons.Insert(curIconFile, n);
372 }
373 else
374 {
375 // replace the existing one (this means that the directories
376 // should be searched in order of increased priority!)
377 ms_icons[(size_t)i] = curIconFile;
378 }
379 }
380
381 if ( !pc )
382 {
383 // the end - this can only happen if nLine == nLineCount
384 break;
385 }
386
387 curIconFile.Empty();
388
389 continue;
390 }
391
392 // what do we have here?
393 if ( *pc == _T('\t') )
394 {
395 // this is a field=value ling
396 pc++; // skip leading TAB
397
398 // skip optional "[lang]"
399 if ( *pc == _T('[') )
400 {
401 while ( *pc )
402 {
403 if ( *pc++ == _T(']') )
404 break;
405 }
406 }
407
408 static const int lenField = 13; // strlen("icon-filename")
409 if ( wxStrncmp(pc, _T("icon-filename"), lenField) == 0 )
410 {
411 // skip '=' which follows and take everything left until the end
412 // of line
413 curIconFile = pc + lenField + 1;
414 }
415 //else: some other field, we don't care
416 }
417 else
418 {
419 // this is the start of the new section
420 curMimeType.Empty();
421
422 while ( *pc != _T(':') && *pc != _T('\0') )
423 {
424 curMimeType += *pc++;
425 }
426 }
427 }
428 }
429
430 void wxGNOMEIconHandler::LoadKeyFilesFromDir(const wxString& dirbase)
431 {
432 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
433 _T("base directory shouldn't end with a slash") );
434
435 wxString dirname = dirbase;
436 dirname << _T("/mime-info");
437
438 if ( !wxDir::Exists(dirname) )
439 return;
440
441 wxDir dir(dirname);
442 if ( !dir.IsOpened() )
443 return;
444
445 // we will concatenate it with filename to get the full path below
446 dirname += _T('/');
447
448 wxString filename;
449 bool cont = dir.GetFirst(&filename, _T("*.keys"), wxDIR_FILES);
450 while ( cont )
451 {
452 LoadIconsFromKeyFile(dirname + filename);
453
454 cont = dir.GetNext(&filename);
455 }
456 }
457
458
459 void wxGNOMEIconHandler::LoadMimeTypesFromMimeFile(const wxString& filename, wxMimeTypesManagerImpl *manager)
460 {
461 wxTextFile textfile(filename);
462 if ( !textfile.Open() )
463 return;
464
465 // values for the entry being parsed
466 wxString curMimeType, curExtList;
467
468 const wxChar *pc;
469 size_t nLineCount = textfile.GetLineCount();
470 for ( size_t nLine = 0; ; nLine++ )
471 {
472 if ( nLine < nLineCount )
473 {
474 pc = textfile[nLine].c_str();
475 if ( *pc == _T('#') )
476 {
477 // skip comments
478 continue;
479 }
480 }
481 else
482 {
483 // so that we will fall into the "if" below
484 pc = NULL;
485 }
486
487 if ( !pc || !*pc )
488 {
489 // end of the entry
490 if ( !!curMimeType && !!curExtList )
491 {
492 manager -> AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
493 }
494
495 if ( !pc )
496 {
497 // the end - this can only happen if nLine == nLineCount
498 break;
499 }
500
501 curExtList.Empty();
502
503 continue;
504 }
505
506 // what do we have here?
507 if ( *pc == _T('\t') )
508 {
509 // this is a field=value ling
510 pc++; // skip leading TAB
511
512 static const int lenField = 4; // strlen("ext:")
513 if ( wxStrncmp(pc, _T("ext:"), lenField) == 0 )
514 {
515 // skip ' ' which follows and take everything left until the end
516 // of line
517 curExtList = pc + lenField + 1;
518 }
519 //else: some other field, we don't care
520 }
521 else
522 {
523 // this is the start of the new section
524 curMimeType.Empty();
525
526 while ( *pc != _T(':') && *pc != _T('\0') )
527 {
528 curMimeType += *pc++;
529 }
530 }
531 }
532 }
533
534
535 void wxGNOMEIconHandler::LoadMimeFilesFromDir(const wxString& dirbase, wxMimeTypesManagerImpl *manager)
536 {
537 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
538 _T("base directory shouldn't end with a slash") );
539
540 wxString dirname = dirbase;
541 dirname << _T("/mime-info");
542
543 if ( !wxDir::Exists(dirname) )
544 return;
545
546 wxDir dir(dirname);
547 if ( !dir.IsOpened() )
548 return;
549
550 // we will concatenate it with filename to get the full path below
551 dirname += _T('/');
552
553 wxString filename;
554 bool cont = dir.GetFirst(&filename, _T("*.mime"), wxDIR_FILES);
555 while ( cont )
556 {
557 LoadMimeTypesFromMimeFile(dirname + filename, manager);
558
559 cont = dir.GetNext(&filename);
560 }
561 }
562
563
564 void wxGNOMEIconHandler::Init()
565 {
566 wxArrayString dirs;
567 dirs.Add(_T("/usr/share"));
568 dirs.Add(_T("/usr/local/share"));
569
570 wxString gnomedir;
571 wxGetHomeDir( &gnomedir );
572 gnomedir += _T("/.gnome");
573 dirs.Add( gnomedir );
574
575 size_t nDirs = dirs.GetCount();
576 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
577 {
578 LoadKeyFilesFromDir(dirs[nDir]);
579 }
580
581 m_inited = TRUE;
582 }
583
584
585 void wxGNOMEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
586 {
587 if ( !m_inited )
588 {
589 Init();
590 }
591
592 wxArrayString dirs;
593 dirs.Add(_T("/usr/share"));
594 dirs.Add(_T("/usr/local/share"));
595
596 wxString gnomedir;
597 wxGetHomeDir( &gnomedir );
598 gnomedir += _T("/.gnome");
599 dirs.Add( gnomedir );
600
601 size_t nDirs = dirs.GetCount();
602 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
603 {
604 LoadMimeFilesFromDir(dirs[nDir], manager);
605 }
606 }
607
608 #if wxUSE_GUI
609 #define WXUNUSED_UNLESS_GUI(p) p
610 #else
611 #define WXUNUSED_UNLESS_GUI(p)
612 #endif
613
614 bool wxGNOMEIconHandler::GetIcon(const wxString& mimetype,
615 wxIcon * WXUNUSED_UNLESS_GUI(icon))
616 {
617 if ( !m_inited )
618 {
619 Init();
620 }
621
622 int index = ms_mimetypes.Index(mimetype);
623 if ( index == wxNOT_FOUND )
624 return FALSE;
625
626 wxString iconname = ms_icons[(size_t)index];
627
628 #if wxUSE_GUI
629 wxLogNull nolog;
630 wxIcon icn;
631 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
632 icn = wxIcon(iconname);
633 else
634 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
635 if ( !icn.Ok() )
636 return FALSE;
637
638 if ( icon )
639 *icon = icn;
640 #else
641 // helpful for testing in console mode
642 wxLogTrace(TRACE_MIME, _T("Found GNOME icon for '%s': '%s'\n"),
643 mimetype.c_str(), iconname.c_str());
644 #endif
645
646 return TRUE;
647 }
648
649 // ----------------------------------------------------------------------------
650 // wxKDEIconHandler
651 // ----------------------------------------------------------------------------
652
653 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
654 // may be found in either of the following locations
655 //
656 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
657 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
658 //
659 // The format of a .kdelnk file is almost the same as the one used by
660 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
661 // value for the entry "Type"
662
663 void wxKDEIconHandler::LoadLinksForMimeSubtype(const wxString& dirbase,
664 const wxString& subdir,
665 const wxString& filename,
666 const wxArrayString& icondirs)
667 {
668 wxFFile file(dirbase + filename);
669 if ( !file.IsOpened() )
670 return;
671
672 // construct mimetype from the directory name and the basename of the
673 // file (it always has .kdelnk extension)
674 wxString mimetype;
675 mimetype << subdir << _T('/') << filename.BeforeLast(_T('.'));
676
677 // these files are small, slurp the entire file at once
678 wxString text;
679 if ( !file.ReadAll(&text) )
680 return;
681
682 int pos;
683 const wxChar *pc;
684
685 // before trying to find an icon, grab mimetype information
686 // (because BFU's machine would hardly have well-edited mime.types but (s)he might
687 // have edited it in control panel...)
688
689 wxString mime_extension, mime_desc;
690
691 pos = wxNOT_FOUND;
692 if (wxGetLocale() != NULL)
693 mime_desc = _T("Comment[") + wxGetLocale()->GetName() + _T("]=");
694 if (pos == wxNOT_FOUND) mime_desc = _T("Comment=");
695 pos = text.Find(mime_desc);
696 if (pos == wxNOT_FOUND) mime_desc = wxEmptyString;
697 else
698 {
699 pc = text.c_str() + pos + mime_desc.Length();
700 mime_desc = wxEmptyString;
701 while ( *pc && *pc != _T('\n') ) mime_desc += *pc++;
702 }
703
704 pos = text.Find(_T("Patterns="));
705 if (pos != wxNOT_FOUND)
706 {
707 wxString exts;
708 pc = text.c_str() + pos + 9;
709 while ( *pc && *pc != _T('\n') ) exts += *pc++;
710 wxStringTokenizer tokenizer(exts, _T(";"));
711 wxString e;
712
713 while (tokenizer.HasMoreTokens())
714 {
715 e = tokenizer.GetNextToken();
716 if (e.Left(2) != _T("*.")) continue; // don't support too difficult patterns
717 mime_extension << e.Mid(2);
718 mime_extension << _T(' ');
719 }
720 mime_extension.RemoveLast();
721 }
722
723 ms_infoTypes.Add(mimetype);
724 ms_infoDescriptions.Add(mime_desc);
725 ms_infoExtensions.Add(mime_extension);
726
727 // ok, now we can take care of icon:
728
729 pos = text.Find(_T("Icon="));
730 if ( pos == wxNOT_FOUND )
731 {
732 // no icon info
733 return;
734 }
735
736 wxString icon;
737
738 pc = text.c_str() + pos + 5; // 5 == strlen("Icon=")
739 while ( *pc && *pc != _T('\n') )
740 {
741 icon += *pc++;
742 }
743
744 if ( !!icon )
745 {
746 // we must check if the file exists because it may be stored
747 // in many locations, at least ~/.kde and $KDEDIR
748 size_t nDir, nDirs = icondirs.GetCount();
749 for ( nDir = 0; nDir < nDirs; nDir++ )
750 if (wxFileExists(icondirs[nDir] + icon))
751 {
752 icon.Prepend(icondirs[nDir]);
753 break;
754 }
755 if (nDir == nDirs) return; //does not exist
756
757 // do we already have this MIME type?
758 int i = ms_mimetypes.Index(mimetype);
759 if ( i == wxNOT_FOUND )
760 {
761 // add it
762 size_t n = ms_mimetypes.Add(mimetype);
763 ms_icons.Insert(icon, n);
764 }
765 else
766 {
767 // replace the old value
768 ms_icons[(size_t)i] = icon;
769 }
770 }
771 }
772
773 void wxKDEIconHandler::LoadLinksForMimeType(const wxString& dirbase,
774 const wxString& subdir,
775 const wxArrayString& icondirs)
776 {
777 wxString dirname = dirbase;
778 dirname += subdir;
779 wxDir dir(dirname);
780 if ( !dir.IsOpened() )
781 return;
782
783 dirname += _T('/');
784
785 wxString filename;
786 bool cont = dir.GetFirst(&filename, _T("*.kdelnk"), wxDIR_FILES);
787 while ( cont )
788 {
789 LoadLinksForMimeSubtype(dirname, subdir, filename, icondirs);
790
791 cont = dir.GetNext(&filename);
792 }
793 }
794
795 void wxKDEIconHandler::LoadLinkFilesFromDir(const wxString& dirbase,
796 const wxArrayString& icondirs)
797 {
798 wxASSERT_MSG( !!dirbase && !wxEndsWithPathSeparator(dirbase),
799 _T("base directory shouldn't end with a slash") );
800
801 wxString dirname = dirbase;
802 dirname << _T("/mimelnk");
803
804 if ( !wxDir::Exists(dirname) )
805 return;
806
807 wxDir dir(dirname);
808 if ( !dir.IsOpened() )
809 return;
810
811 // we will concatenate it with dir name to get the full path below
812 dirname += _T('/');
813
814 wxString subdir;
815 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
816 while ( cont )
817 {
818 LoadLinksForMimeType(dirname, subdir, icondirs);
819
820 cont = dir.GetNext(&subdir);
821 }
822 }
823
824 void wxKDEIconHandler::Init()
825 {
826 wxArrayString dirs;
827 wxArrayString icondirs;
828
829 // settings in ~/.kde have maximal priority
830 dirs.Add(wxGetHomeDir() + _T("/.kde/share"));
831 icondirs.Add(wxGetHomeDir() + _T("/.kde/share/icons/"));
832
833 // the variable KDEDIR is set when KDE is running
834 const char *kdedir = getenv("KDEDIR");
835 if ( kdedir )
836 {
837 dirs.Add(wxString(kdedir) + _T("/share"));
838 icondirs.Add(wxString(kdedir) + _T("/share/icons/"));
839 }
840 else
841 {
842 // try to guess KDEDIR
843 dirs.Add(_T("/usr/share"));
844 dirs.Add(_T("/opt/kde/share"));
845 icondirs.Add(_T("/usr/share/icons/"));
846 icondirs.Add(_T("/usr/X11R6/share/icons/")); // Debian/Corel linux
847 icondirs.Add(_T("/opt/kde/share/icons/"));
848 }
849
850 size_t nDirs = dirs.GetCount();
851 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
852 {
853 LoadLinkFilesFromDir(dirs[nDir], icondirs);
854 }
855
856 m_inited = TRUE;
857 }
858
859 bool wxKDEIconHandler::GetIcon(const wxString& mimetype,
860 wxIcon * WXUNUSED_UNLESS_GUI(icon))
861 {
862 if ( !m_inited )
863 {
864 Init();
865 }
866
867 int index = ms_mimetypes.Index(mimetype);
868 if ( index == wxNOT_FOUND )
869 return FALSE;
870
871 wxString iconname = ms_icons[(size_t)index];
872
873 #if wxUSE_GUI
874 wxLogNull nolog;
875 wxIcon icn;
876 if (iconname.Right(4).MakeUpper() == _T(".XPM"))
877 icn = wxIcon(iconname);
878 else
879 icn = wxIcon(iconname, wxBITMAP_TYPE_ANY);
880
881 if ( !icn.Ok() )
882 return FALSE;
883
884 if ( icon )
885 *icon = icn;
886 #else
887 // helpful for testing in console mode
888 wxLogTrace(TRACE_MIME, _T("Found KDE icon for '%s': '%s'\n"),
889 mimetype.c_str(), iconname.c_str());
890 #endif
891
892 return TRUE;
893 }
894
895
896 void wxKDEIconHandler::GetMimeInfoRecords(wxMimeTypesManagerImpl *manager)
897 {
898 if ( !m_inited ) Init();
899
900 size_t cnt = ms_infoTypes.GetCount();
901 for (unsigned i = 0; i < cnt; i++)
902 manager -> AddMimeTypeInfo(ms_infoTypes[i], ms_infoExtensions[i], ms_infoDescriptions[i]);
903 }
904
905
906 // ----------------------------------------------------------------------------
907 // wxFileTypeImpl (Unix)
908 // ----------------------------------------------------------------------------
909
910 MailCapEntry *
911 wxFileTypeImpl::GetEntry(const wxFileType::MessageParameters& params) const
912 {
913 wxString command;
914 MailCapEntry *entry = m_manager->m_aEntries[m_index[0]];
915 while ( entry != NULL ) {
916 // get the command to run as the test for this entry
917 command = wxFileType::ExpandCommand(entry->GetTestCmd(), params);
918
919 // don't trace the test result if there is no test at all
920 if ( command.IsEmpty() )
921 {
922 // no test at all, ok
923 break;
924 }
925
926 if ( wxSystem(command) == 0 ) {
927 // ok, test passed
928 wxLogTrace(TRACE_MIME,
929 wxT("Test '%s' for mime type '%s' succeeded."),
930 command.c_str(), params.GetMimeType().c_str());
931 break;
932 }
933 else {
934 wxLogTrace(TRACE_MIME,
935 wxT("Test '%s' for mime type '%s' failed."),
936 command.c_str(), params.GetMimeType().c_str());
937 }
938
939 entry = entry->GetNext();
940 }
941
942 return entry;
943 }
944
945 bool wxFileTypeImpl::GetIcon(wxIcon *icon) const
946 {
947 wxArrayString mimetypes;
948 GetMimeTypes(mimetypes);
949
950 ArrayIconHandlers& handlers = m_manager->GetIconHandlers();
951 size_t count = handlers.GetCount();
952 size_t counttypes = mimetypes.GetCount();
953 for ( size_t n = 0; n < count; n++ )
954 {
955 for ( size_t n2 = 0; n2 < counttypes; n2++ )
956 {
957 if ( handlers[n]->GetIcon(mimetypes[n2], icon) )
958 return TRUE;
959 }
960 }
961
962 return FALSE;
963 }
964
965
966 bool
967 wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
968 {
969 mimeTypes.Clear();
970 for (size_t i = 0; i < m_index.GetCount(); i++)
971 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
972 return TRUE;
973 }
974
975
976 bool
977 wxFileTypeImpl::GetExpandedCommand(wxString *expandedCmd,
978 const wxFileType::MessageParameters& params,
979 bool open) const
980 {
981 MailCapEntry *entry = GetEntry(params);
982 if ( entry == NULL ) {
983 // all tests failed...
984 return FALSE;
985 }
986
987 wxString cmd = open ? entry->GetOpenCmd() : entry->GetPrintCmd();
988 if ( cmd.IsEmpty() ) {
989 // may happen, especially for "print"
990 return FALSE;
991 }
992
993 *expandedCmd = wxFileType::ExpandCommand(cmd, params);
994 return TRUE;
995 }
996
997 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
998 {
999 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1000 extensions.Empty();
1001
1002 // one extension in the space or comma delimitid list
1003 wxString strExt;
1004 for ( const wxChar *p = strExtensions; ; p++ ) {
1005 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') ) {
1006 if ( !strExt.IsEmpty() ) {
1007 extensions.Add(strExt);
1008 strExt.Empty();
1009 }
1010 //else: repeated spaces (shouldn't happen, but it's not that
1011 // important if it does happen)
1012
1013 if ( *p == wxT('\0') )
1014 break;
1015 }
1016 else if ( *p == wxT('.') ) {
1017 // remove the dot from extension (but only if it's the first char)
1018 if ( !strExt.IsEmpty() ) {
1019 strExt += wxT('.');
1020 }
1021 //else: no, don't append it
1022 }
1023 else {
1024 strExt += *p;
1025 }
1026 }
1027
1028 return TRUE;
1029 }
1030
1031 // ----------------------------------------------------------------------------
1032 // wxMimeTypesManagerImpl (Unix)
1033 // ----------------------------------------------------------------------------
1034
1035 /* static */
1036 ArrayIconHandlers& wxMimeTypesManagerImpl::GetIconHandlers()
1037 {
1038 if ( ms_iconHandlers.GetCount() == 0 )
1039 {
1040 ms_iconHandlers.Add(&gs_iconHandlerGNOME);
1041 ms_iconHandlers.Add(&gs_iconHandlerKDE);
1042 }
1043
1044 return ms_iconHandlers;
1045 }
1046
1047 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1048 {
1049 m_initialized = FALSE;
1050 }
1051
1052 // read system and user mailcaps and other files
1053 void wxMimeTypesManagerImpl::Initialize()
1054 {
1055 // directories where we look for mailcap and mime.types by default
1056 // (taken from metamail(1) sources)
1057 //
1058 // although RFC 1524 specifies the search path of
1059 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1060 // places - OTOH, the RFC also says that this path can be changed with
1061 // MAILCAPS environment variable (containing the colon separated full
1062 // filenames to try) which is not done yet (TODO?)
1063 static const wxChar *aStandardLocations[] =
1064 {
1065 wxT("/etc"),
1066 wxT("/usr/etc"),
1067 wxT("/usr/local/etc"),
1068 wxT("/etc/mail"),
1069 wxT("/usr/public/lib")
1070 };
1071
1072 // first read the system wide file(s)
1073 size_t n;
1074 for ( n = 0; n < WXSIZEOF(aStandardLocations); n++ ) {
1075 wxString dir = aStandardLocations[n];
1076
1077 wxString file = dir + wxT("/mailcap");
1078 if ( wxFile::Exists(file) ) {
1079 ReadMailcap(file);
1080 }
1081
1082 file = dir + wxT("/mime.types");
1083 if ( wxFile::Exists(file) ) {
1084 ReadMimeTypes(file);
1085 }
1086 }
1087
1088 wxString strHome = wxGetenv(wxT("HOME"));
1089
1090 // and now the users mailcap
1091 wxString strUserMailcap = strHome + wxT("/.mailcap");
1092 if ( wxFile::Exists(strUserMailcap) ) {
1093 ReadMailcap(strUserMailcap);
1094 }
1095
1096 // read the users mime.types
1097 wxString strUserMimeTypes = strHome + wxT("/.mime.types");
1098 if ( wxFile::Exists(strUserMimeTypes) ) {
1099 ReadMimeTypes(strUserMimeTypes);
1100 }
1101
1102 // read KDE/GNOME tables
1103 ArrayIconHandlers& handlers = GetIconHandlers();
1104 size_t count = handlers.GetCount();
1105 for ( size_t hn = 0; hn < count; hn++ )
1106 handlers[hn]->GetMimeInfoRecords(this);
1107 }
1108
1109
1110 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1111 {
1112 size_t cnt = m_aEntries.GetCount();
1113 for (size_t i = 0; i < cnt; i++)
1114 delete m_aEntries[i];
1115 }
1116
1117 wxFileType *
1118 wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
1119 {
1120 wxFAIL_MSG( _T("unimplemented") ); // TODO
1121
1122 return NULL;
1123 }
1124
1125 wxFileType *
1126 wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
1127 {
1128 InitIfNeeded();
1129
1130 wxFileType *fileType = NULL;
1131 size_t count = m_aExtensions.GetCount();
1132 for ( size_t n = 0; n < count; n++ ) {
1133 wxString extensions = m_aExtensions[n];
1134 while ( !extensions.IsEmpty() ) {
1135 wxString field = extensions.BeforeFirst(wxT(' '));
1136 extensions = extensions.AfterFirst(wxT(' '));
1137
1138 // consider extensions as not being case-sensitive
1139 if ( field.IsSameAs(ext, FALSE /* no case */) ) {
1140 // found
1141 if (fileType == NULL) fileType = new wxFileType;
1142 fileType->m_impl->Init(this, n);
1143 // adds this mime type to _list_ of mime types with this extension
1144 }
1145 }
1146 }
1147
1148 return fileType;
1149 }
1150
1151 wxFileType *
1152 wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
1153 {
1154 InitIfNeeded();
1155
1156 // mime types are not case-sensitive
1157 wxString mimetype(mimeType);
1158 mimetype.MakeLower();
1159
1160 // first look for an exact match
1161 int index = m_aTypes.Index(mimetype);
1162 if ( index == wxNOT_FOUND ) {
1163 // then try to find "text/*" as match for "text/plain" (for example)
1164 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
1165 // the whole string - ok.
1166 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
1167
1168 size_t nCount = m_aTypes.Count();
1169 for ( size_t n = 0; n < nCount; n++ ) {
1170 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
1171 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") ) {
1172 index = n;
1173 break;
1174 }
1175 }
1176 }
1177
1178 if ( index != wxNOT_FOUND ) {
1179 wxFileType *fileType = new wxFileType;
1180 fileType->m_impl->Init(this, index);
1181
1182 return fileType;
1183 }
1184 else {
1185 // not found...
1186 return NULL;
1187 }
1188 }
1189
1190 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
1191 {
1192 InitIfNeeded();
1193
1194 wxString extensions;
1195 const wxArrayString& exts = filetype.GetExtensions();
1196 size_t nExts = exts.GetCount();
1197 for ( size_t nExt = 0; nExt < nExts; nExt++ ) {
1198 if ( nExt > 0 ) {
1199 extensions += wxT(' ');
1200 }
1201 extensions += exts[nExt];
1202 }
1203
1204 AddMimeTypeInfo(filetype.GetMimeType(),
1205 extensions,
1206 filetype.GetDescription());
1207
1208 AddMailcapInfo(filetype.GetMimeType(),
1209 filetype.GetOpenCommand(),
1210 filetype.GetPrintCommand(),
1211 wxT(""),
1212 filetype.GetDescription());
1213 }
1214
1215 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
1216 const wxString& strExtensions,
1217 const wxString& strDesc)
1218 {
1219 InitIfNeeded();
1220
1221 int index = m_aTypes.Index(strMimeType);
1222 if ( index == wxNOT_FOUND ) {
1223 // add a new entry
1224 m_aTypes.Add(strMimeType);
1225 m_aEntries.Add(NULL);
1226 m_aExtensions.Add(strExtensions);
1227 m_aDescriptions.Add(strDesc);
1228 }
1229 else {
1230 // modify an existing one
1231 if ( !strDesc.IsEmpty() ) {
1232 m_aDescriptions[index] = strDesc; // replace old value
1233 }
1234 m_aExtensions[index] += ' ' + strExtensions;
1235 }
1236 }
1237
1238 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
1239 const wxString& strOpenCmd,
1240 const wxString& strPrintCmd,
1241 const wxString& strTest,
1242 const wxString& strDesc)
1243 {
1244 InitIfNeeded();
1245
1246 MailCapEntry *entry = new MailCapEntry(strOpenCmd, strPrintCmd, strTest);
1247
1248 int nIndex = m_aTypes.Index(strType);
1249 if ( nIndex == wxNOT_FOUND ) {
1250 // new file type
1251 m_aTypes.Add(strType);
1252
1253 m_aEntries.Add(entry);
1254 m_aExtensions.Add(wxT(""));
1255 m_aDescriptions.Add(strDesc);
1256 }
1257 else {
1258 // always append the entry in the tail of the list - info added with
1259 // this function can only come from AddFallbacks()
1260 MailCapEntry *entryOld = m_aEntries[nIndex];
1261 if ( entryOld )
1262 entry->Append(entryOld);
1263 else
1264 m_aEntries[nIndex] = entry;
1265 }
1266 }
1267
1268 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
1269 {
1270 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
1271 strFileName.c_str());
1272
1273 wxTextFile file(strFileName);
1274 if ( !file.Open() )
1275 return FALSE;
1276
1277 // the information we extract
1278 wxString strMimeType, strDesc, strExtensions;
1279
1280 size_t nLineCount = file.GetLineCount();
1281 const wxChar *pc = NULL;
1282 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1283 if ( pc == NULL ) {
1284 // now we're at the start of the line
1285 pc = file[nLine].c_str();
1286 }
1287 else {
1288 // we didn't finish with the previous line yet
1289 nLine--;
1290 }
1291
1292 // skip whitespace
1293 while ( wxIsspace(*pc) )
1294 pc++;
1295
1296 // comment or blank line?
1297 if ( *pc == wxT('#') || !*pc ) {
1298 // skip the whole line
1299 pc = NULL;
1300 continue;
1301 }
1302
1303 // detect file format
1304 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
1305 if ( pEqualSign == NULL ) {
1306 // brief format
1307 // ------------
1308
1309 // first field is mime type
1310 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ ) {
1311 strMimeType += *pc;
1312 }
1313
1314 // skip whitespace
1315 while ( wxIsspace(*pc) )
1316 pc++;
1317
1318 // take all the rest of the string
1319 strExtensions = pc;
1320
1321 // no description...
1322 strDesc.Empty();
1323 }
1324 else {
1325 // expanded format
1326 // ---------------
1327
1328 // the string on the left of '=' is the field name
1329 wxString strLHS(pc, pEqualSign - pc);
1330
1331 // eat whitespace
1332 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
1333 ;
1334
1335 const wxChar *pEnd;
1336 if ( *pc == wxT('"') ) {
1337 // the string is quoted and ends at the matching quote
1338 pEnd = wxStrchr(++pc, wxT('"'));
1339 if ( pEnd == NULL ) {
1340 wxLogWarning(_("Mime.types file %s, line %d: unterminated "
1341 "quoted string."),
1342 strFileName.c_str(), nLine + 1);
1343 }
1344 }
1345 else {
1346 // unquoted string ends at the first space or at the end of
1347 // line
1348 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
1349 ;
1350 }
1351
1352 // now we have the RHS (field value)
1353 wxString strRHS(pc, pEnd - pc);
1354
1355 // check what follows this entry
1356 if ( *pEnd == wxT('"') ) {
1357 // skip this quote
1358 pEnd++;
1359 }
1360
1361 for ( pc = pEnd; wxIsspace(*pc); pc++ )
1362 ;
1363
1364 // if there is something left, it may be either a '\\' to continue
1365 // the line or the next field of the same entry
1366 bool entryEnded = *pc == wxT('\0'),
1367 nextFieldOnSameLine = FALSE;
1368 if ( !entryEnded ) {
1369 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
1370 }
1371
1372 // now see what we got
1373 if ( strLHS == wxT("type") ) {
1374 strMimeType = strRHS;
1375 }
1376 else if ( strLHS == wxT("desc") ) {
1377 strDesc = strRHS;
1378 }
1379 else if ( strLHS == wxT("exts") ) {
1380 strExtensions = strRHS;
1381 }
1382 else {
1383 wxLogWarning(_("Unknown field in file %s, line %d: '%s'."),
1384 strFileName.c_str(), nLine + 1, strLHS.c_str());
1385 }
1386
1387 if ( !entryEnded ) {
1388 if ( !nextFieldOnSameLine )
1389 pc = NULL;
1390 //else: don't reset it
1391
1392 // as we don't reset strMimeType, the next field in this entry
1393 // will be interpreted correctly.
1394
1395 continue;
1396 }
1397 }
1398
1399 // depending on the format (Mosaic or Netscape) either space or comma
1400 // is used to separate the extensions
1401 strExtensions.Replace(wxT(","), wxT(" "));
1402
1403 // also deal with the leading dot
1404 if ( !strExtensions.IsEmpty() && strExtensions[0u] == wxT('.') )
1405 {
1406 strExtensions.erase(0, 1);
1407 }
1408
1409 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
1410
1411 // finished with this line
1412 pc = NULL;
1413 }
1414
1415 // check our data integriry
1416 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1417 m_aTypes.Count() == m_aExtensions.Count() &&
1418 m_aTypes.Count() == m_aDescriptions.Count() );
1419
1420 return TRUE;
1421 }
1422
1423 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
1424 bool fallback)
1425 {
1426 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
1427 strFileName.c_str());
1428
1429 wxTextFile file(strFileName);
1430 if ( !file.Open() )
1431 return FALSE;
1432
1433 // see the comments near the end of function for the reason we need these
1434 // variables (search for the next occurence of them)
1435 // indices of MIME types (in m_aTypes) we already found in this file
1436 wxArrayInt aEntryIndices;
1437 // aLastIndices[n] is the index of last element in
1438 // m_aEntries[aEntryIndices[n]] from this file
1439 wxArrayInt aLastIndices;
1440
1441 size_t nLineCount = file.GetLineCount();
1442 for ( size_t nLine = 0; nLine < nLineCount; nLine++ ) {
1443 // now we're at the start of the line
1444 const wxChar *pc = file[nLine].c_str();
1445
1446 // skip whitespace
1447 while ( wxIsspace(*pc) )
1448 pc++;
1449
1450 // comment or empty string?
1451 if ( *pc == wxT('#') || *pc == wxT('\0') )
1452 continue;
1453
1454 // no, do parse
1455
1456 // what field are we currently in? The first 2 are fixed and there may
1457 // be an arbitrary number of other fields -- currently, we are not
1458 // interested in any of them, but we should parse them as well...
1459 enum
1460 {
1461 Field_Type,
1462 Field_OpenCmd,
1463 Field_Other
1464 } currentToken = Field_Type;
1465
1466 // the flags and field values on the current line
1467 bool needsterminal = FALSE,
1468 copiousoutput = FALSE;
1469 wxString strType,
1470 strOpenCmd,
1471 strPrintCmd,
1472 strTest,
1473 strDesc,
1474 curField; // accumulator
1475 bool cont = TRUE;
1476 while ( cont ) {
1477 switch ( *pc ) {
1478 case wxT('\\'):
1479 // interpret the next character literally (notice that
1480 // backslash can be used for line continuation)
1481 if ( *++pc == wxT('\0') ) {
1482 // fetch the next line if there is one
1483 if ( nLine == nLineCount - 1 ) {
1484 // something is wrong, bail out
1485 cont = FALSE;
1486
1487 wxLogDebug(wxT("Mailcap file %s, line %d: "
1488 "'\\' on the end of the last line "
1489 "ignored."),
1490 strFileName.c_str(),
1491 nLine + 1);
1492 }
1493 else {
1494 // pass to the beginning of the next line
1495 pc = file[++nLine].c_str();
1496
1497 // skip pc++ at the end of the loop
1498 continue;
1499 }
1500 }
1501 else {
1502 // just a normal character
1503 curField += *pc;
1504 }
1505 break;
1506
1507 case wxT('\0'):
1508 cont = FALSE; // end of line reached, exit the loop
1509
1510 // fall through
1511
1512 case wxT(';'):
1513 // store this field and start looking for the next one
1514
1515 // trim whitespaces from both sides
1516 curField.Trim(TRUE).Trim(FALSE);
1517
1518 switch ( currentToken ) {
1519 case Field_Type:
1520 strType = curField;
1521 if ( strType.empty() ) {
1522 // I don't think that this is a valid mailcap
1523 // entry, but try to interpret it somehow
1524 strType = _T('*');
1525 }
1526
1527 if ( strType.Find(wxT('/')) == wxNOT_FOUND ) {
1528 // we interpret "type" as "type/*"
1529 strType += wxT("/*");
1530 }
1531
1532 currentToken = Field_OpenCmd;
1533 break;
1534
1535 case Field_OpenCmd:
1536 strOpenCmd = curField;
1537
1538 currentToken = Field_Other;
1539 break;
1540
1541 case Field_Other:
1542 if ( !curField.empty() ) {
1543 // "good" mailcap entry?
1544 bool ok = TRUE;
1545
1546 // is this something of the form foo=bar?
1547 const wxChar *pEq = wxStrchr(curField, wxT('='));
1548 if ( pEq != NULL ) {
1549 wxString lhs = curField.BeforeFirst(wxT('=')),
1550 rhs = curField.AfterFirst(wxT('='));
1551
1552 lhs.Trim(TRUE); // from right
1553 rhs.Trim(FALSE); // from left
1554
1555 if ( lhs == wxT("print") )
1556 strPrintCmd = rhs;
1557 else if ( lhs == wxT("test") )
1558 strTest = rhs;
1559 else if ( lhs == wxT("description") ) {
1560 // it might be quoted
1561 if ( rhs[0u] == wxT('"') &&
1562 rhs.Last() == wxT('"') ) {
1563 strDesc = wxString(rhs.c_str() + 1,
1564 rhs.Len() - 2);
1565 }
1566 else {
1567 strDesc = rhs;
1568 }
1569 }
1570 else if ( lhs == wxT("compose") ||
1571 lhs == wxT("composetyped") ||
1572 lhs == wxT("notes") ||
1573 lhs == wxT("edit") )
1574 ; // ignore
1575 else
1576 ok = FALSE;
1577
1578 }
1579 else {
1580 // no, it's a simple flag
1581 if ( curField == wxT("needsterminal") )
1582 needsterminal = TRUE;
1583 else if ( curField == wxT("copiousoutput")) {
1584 // copiousoutput impies that the
1585 // viewer is a console program
1586 needsterminal =
1587 copiousoutput = TRUE;
1588 }
1589 else {
1590 // unknown flag
1591 ok = FALSE;
1592 }
1593 }
1594
1595 if ( !ok )
1596 {
1597 if ( !IsKnownUnimportantField(curField) )
1598 {
1599 // don't flood the user with error
1600 // messages if we don't understand
1601 // something in his mailcap, but give
1602 // them in debug mode because this might
1603 // be useful for the programmer
1604 wxLogDebug
1605 (
1606 wxT("Mailcap file %s, line %d: "
1607 "unknown field '%s' for the "
1608 "MIME type '%s' ignored."),
1609 strFileName.c_str(),
1610 nLine + 1,
1611 curField.c_str(),
1612 strType.c_str()
1613 );
1614 }
1615 }
1616 }
1617 //else: the field is empty, ignore silently
1618
1619 // it already has this value
1620 //currentToken = Field_Other;
1621 break;
1622
1623 default:
1624 wxFAIL_MSG(wxT("unknown field type in mailcap"));
1625 }
1626
1627 // next token starts immediately after ';'
1628 curField.Empty();
1629 break;
1630
1631 default:
1632 curField += *pc;
1633 }
1634
1635 // continue in the same line
1636 pc++;
1637 }
1638
1639 // check that we really read something reasonable
1640 if ( currentToken == Field_Type || currentToken == Field_OpenCmd ) {
1641 wxLogWarning(_("Mailcap file %s, line %d: incomplete entry "
1642 "ignored."),
1643 strFileName.c_str(), nLine + 1);
1644 }
1645 else {
1646 // support for flags:
1647 // 1. create an xterm for 'needsterminal'
1648 // 2. append "| $PAGER" for 'copiousoutput'
1649 //
1650 // Note that the RFC says that having both needsterminal and
1651 // copiousoutput is probably a mistake, so it seems that running
1652 // programs with copiousoutput inside an xterm as it is done now
1653 // is a bad idea (FIXME)
1654 if ( copiousoutput ) {
1655 const wxChar *p = wxGetenv(_T("PAGER"));
1656 strOpenCmd << _T(" | ") << (p ? p : _T("more"));
1657 }
1658
1659 if ( needsterminal ) {
1660 strOpenCmd.Printf(_T("xterm -e sh -c '%s'"),
1661 strOpenCmd.c_str());
1662 }
1663
1664 MailCapEntry *entry = new MailCapEntry(strOpenCmd,
1665 strPrintCmd,
1666 strTest);
1667
1668 // NB: because of complications below (we must get entries priority
1669 // right), we can't use AddMailcapInfo() here, unfortunately.
1670 strType.MakeLower();
1671 int nIndex = m_aTypes.Index(strType);
1672 if ( nIndex == wxNOT_FOUND ) {
1673 // new file type
1674 m_aTypes.Add(strType);
1675
1676 m_aEntries.Add(entry);
1677 m_aExtensions.Add(wxT(""));
1678 m_aDescriptions.Add(strDesc);
1679 }
1680 else {
1681 // modify the existing entry: the entries in one and the same
1682 // file are read in top-to-bottom order, i.e. the entries read
1683 // first should be tried before the entries below. However,
1684 // the files read later should override the settings in the
1685 // files read before (except if fallback is TRUE), thus we
1686 // Insert() the new entry to the list if it has already
1687 // occured in _this_ file, but Prepend() it if it occured in
1688 // some of the previous ones and Append() to it in the
1689 // fallback case
1690
1691 if ( fallback ) {
1692 // 'fallback' parameter prevents the entries from this
1693 // file from overriding the other ones - always append
1694 MailCapEntry *entryOld = m_aEntries[nIndex];
1695 if ( entryOld )
1696 entry->Append(entryOld);
1697 else
1698 m_aEntries[nIndex] = entry;
1699 }
1700 else {
1701 int entryIndex = aEntryIndices.Index(nIndex);
1702 if ( entryIndex == wxNOT_FOUND ) {
1703 // first time in this file
1704 aEntryIndices.Add(nIndex);
1705 aLastIndices.Add(0);
1706
1707 entry->Prepend(m_aEntries[nIndex]);
1708 m_aEntries[nIndex] = entry;
1709 }
1710 else {
1711 // not the first time in _this_ file
1712 size_t nEntryIndex = (size_t)entryIndex;
1713 MailCapEntry *entryOld = m_aEntries[nIndex];
1714 if ( entryOld )
1715 entry->Insert(entryOld, aLastIndices[nEntryIndex]);
1716 else
1717 m_aEntries[nIndex] = entry;
1718
1719 // the indices were shifted by 1
1720 aLastIndices[nEntryIndex]++;
1721 }
1722 }
1723
1724 if ( !strDesc.IsEmpty() ) {
1725 // replace the old one - what else can we do??
1726 m_aDescriptions[nIndex] = strDesc;
1727 }
1728 }
1729 }
1730
1731 // check our data integriry
1732 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
1733 m_aTypes.Count() == m_aExtensions.Count() &&
1734 m_aTypes.Count() == m_aDescriptions.Count() );
1735 }
1736
1737 return TRUE;
1738 }
1739
1740 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
1741 {
1742 InitIfNeeded();
1743
1744 mimetypes.Empty();
1745
1746 wxString type;
1747 size_t count = m_aTypes.GetCount();
1748 for ( size_t n = 0; n < count; n++ )
1749 {
1750 // don't return template types from here (i.e. anything containg '*')
1751 type = m_aTypes[n];
1752 if ( type.Find(_T('*')) == wxNOT_FOUND )
1753 {
1754 mimetypes.Add(type);
1755 }
1756 }
1757
1758 return mimetypes.GetCount();
1759 }
1760
1761 // ----------------------------------------------------------------------------
1762 // writing to MIME type files
1763 // ----------------------------------------------------------------------------
1764
1765 bool wxFileTypeImpl::Unassociate()
1766 {
1767 wxFAIL_MSG( _T("unimplemented") ); // TODO
1768
1769 return FALSE;
1770 }
1771
1772 // ----------------------------------------------------------------------------
1773 // private functions
1774 // ----------------------------------------------------------------------------
1775
1776 static bool IsKnownUnimportantField(const wxString& fieldAll)
1777 {
1778 static const wxChar *knownFields[] =
1779 {
1780 _T("x-mozilla-flags"),
1781 _T("nametemplate"),
1782 _T("textualnewlines"),
1783 };
1784
1785 wxString field = fieldAll.BeforeFirst(_T('='));
1786 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
1787 {
1788 if ( field.CmpNoCase(knownFields[n]) == 0 )
1789 return TRUE;
1790 }
1791
1792 return FALSE;
1793 }
1794
1795 #endif
1796 // wxUSE_FILE && wxUSE_TEXTFILE
1797