1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/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: mimetype.cpp 63971 2010-04-14 00:10:21Z VZ $
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence (part of wxExtra library)
10 /////////////////////////////////////////////////////////////////////////////
11
12 // known bugs; there may be others!! chris elliott, biol75@york.ac.uk 27 Mar 01
13
14 // 1) .mailcap and .mimetypes can be either in a netscape or metamail format
15 // and entries may get confused during writing (I've tried to fix this; please let me know
16 // any files that fail)
17 // 2) KDE and Gnome do not yet fully support international read/write
18 // 3) Gnome key lines like open.latex."LaTeX this file"=latex %f will have odd results
19 // 4) writing to files comments out the existing data; I hope this avoids losing
20 // any data which we could not read, and data which we did not store like test=
21 // 5) results from reading files with multiple entries (especially matches with type/* )
22 // may (or may not) work for getXXX commands
23 // 6) Loading the png icons in Gnome doesn't work for me...
24 // 7) In Gnome, if keys.mime exists but keys.users does not, there is
25 // an error message in debug mode, but the file is still written OK
26 // 8) Deleting entries is only allowed from the user file; sytem wide entries
27 // will be preserved during unassociate
28 // 9) KDE does not yet handle multiple actions; Netscape mode never will
29
30 // TODO: this file is a mess, we need to split it and review everything (VZ)
31
32 // for compilers that support precompilation, includes "wx.h".
33 #include "wx/wxprec.h"
34
35 #ifdef __BORLANDC__
36 #pragma hdrstop
37 #endif
38
39 #if wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
40
41 #include "wx/unix/mimetype.h"
42
43 #ifndef WX_PRECOMP
44 #include "wx/dynarray.h"
45 #include "wx/string.h"
46 #include "wx/intl.h"
47 #include "wx/log.h"
48 #include "wx/utils.h"
49 #endif
50
51 #include "wx/file.h"
52 #include "wx/confbase.h"
53
54 #include "wx/ffile.h"
55 #include "wx/textfile.h"
56 #include "wx/dir.h"
57 #include "wx/tokenzr.h"
58 #include "wx/iconloc.h"
59 #include "wx/filename.h"
60 #include "wx/app.h"
61 #include "wx/apptrait.h"
62
63 #if wxUSE_LIBGNOMEVFS
64 // Not GUI dependent
65 #include "wx/gtk/gnome/gvfs.h"
66 #endif
67
68 // other standard headers
69 #include <ctype.h>
70
71 // this class is a wxTextFile specialization for dealing with files storing
72 // various MIME-related information
73 //
74 // it should be used instead of wxTextFile even if none of its additional
75 // methods are used just because it handles files with mixed encodings (often
76 // the case for MIME files which contain strings for different languages)
77 // correctly, see OnRead()
78 class wxMimeTextFile : public wxTextFile
79 {
80 public:
81 // constructors
wxMimeTextFile()82 wxMimeTextFile () : wxTextFile () { }
wxMimeTextFile(const wxString & strFile)83 wxMimeTextFile(const wxString& strFile) : wxTextFile(strFile) { }
84
pIndexOf(const wxString & sSearch,bool bIncludeComments=false,int iStart=0)85 int pIndexOf(const wxString& sSearch,
86 bool bIncludeComments = false,
87 int iStart = 0)
88 {
89 wxString sTest = sSearch;
90 sTest.MakeLower();
91 for(size_t i = iStart; i < GetLineCount(); i++)
92 {
93 wxString sLine = GetLine(i).Trim(false);
94 if(bIncludeComments || ! sLine.StartsWith(wxT("#")))
95 {
96 sLine.MakeLower();
97 if(sLine.StartsWith(sTest))
98 return (int)i;
99 }
100 }
101 return wxNOT_FOUND;
102 }
103
CommentLine(int nIndex)104 bool CommentLine(int nIndex)
105 {
106 if (nIndex < 0)
107 return false;
108 if (nIndex >= (int)GetLineCount() )
109 return false;
110
111 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
112 return true;
113 }
114
CommentLine(const wxString & sTest)115 bool CommentLine(const wxString & sTest)
116 {
117 int nIndex = pIndexOf(sTest);
118 if (nIndex < 0)
119 return false;
120 if (nIndex >= (int)GetLineCount() )
121 return false;
122
123 GetLine(nIndex) = GetLine(nIndex).Prepend(wxT("#"));
124 return true;
125 }
126
GetVerb(size_t i)127 wxString GetVerb(size_t i)
128 {
129 if (i > GetLineCount() )
130 return wxEmptyString;
131
132 wxString sTmp = GetLine(i).BeforeFirst(wxT('='));
133 return sTmp;
134 }
135
GetCmd(size_t i)136 wxString GetCmd(size_t i)
137 {
138 if (i > GetLineCount() )
139 return wxEmptyString;
140
141 wxString sTmp = GetLine(i).AfterFirst(wxT('='));
142 return sTmp;
143 }
144
145 // Get line number of line starting with sSearch followed by = and return
146 // the string after the =
GetCmd(const wxString & sSearch,wxString & cmd)147 int GetCmd(const wxString& sSearch, wxString &cmd)
148 {
149 const size_t len = sSearch.Len();
150
151 for(size_t i = 0; i < GetLineCount(); i++)
152 {
153 wxString& sLine = GetLine(i);
154 sLine.Trim(false);
155 if (sLine.StartsWith(wxT("#")))
156 continue;
157 if (sLine.Len() <= len)
158 continue;
159
160 if (sLine[len] != '=')
161 continue;
162
163 if (sLine.Left(len).CmpNoCase(sSearch))
164 continue;
165
166 cmd = sLine.Mid(len + 1);
167 return (int)i;
168 }
169
170 return wxNOT_FOUND;
171 }
172
173 protected:
174 // we override this virtual method because we want to always use UTF-8
175 // conversion allowing for invalid characters as MIME information files
176 // often contain lines in different encodings and can't be read using any
177 // single conversion in Unicode build, so we just try to read what we can
178 // suing the most common encoding (UTF-8 is almost ubiquitous nowadays) and
179 // ignore the rest
OnRead(const wxMBConv & WXUNUSED (conv))180 virtual bool OnRead(const wxMBConv& WXUNUSED(conv))
181 {
182 return wxTextFile::OnRead(
183 #if wxUSE_WCHAR_T
184 wxMBConvUTF8(wxMBConvUTF8::MAP_INVALID_UTF8_TO_PUA)
185 #else
186 wxMBConv()
187 #endif // wxUSE_WCHAR_T/!wxUSE_WCHAR_T
188 );
189 }
190 };
191
192 // in case we're compiling in non-GUI mode
193 class WXDLLEXPORT wxIcon;
194
195 // ----------------------------------------------------------------------------
196 // constants
197 // ----------------------------------------------------------------------------
198
199 // MIME code tracing mask
200 #define TRACE_MIME wxT("mime")
201
202 // give trace messages about the results of mailcap tests
203 #define TRACE_MIME_TEST wxT("mimetest")
204
205 // ----------------------------------------------------------------------------
206 // private functions
207 // ----------------------------------------------------------------------------
208
209 // there are some fields which we don't understand but for which we don't give
210 // warnings as we know that they're not important - this function is used to
211 // test for them
212 static bool IsKnownUnimportantField(const wxString& field);
213
214 // ----------------------------------------------------------------------------
215 // private classes
216 // ----------------------------------------------------------------------------
217
218
219 // This class uses both mailcap and mime.types to gather information about file
220 // types.
221 //
222 // The information about mailcap file was extracted from metamail(1) sources
223 // and documentation and subsequently revised when I found the RFC 1524
224 // describing it.
225 //
226 // Format of mailcap file: spaces are ignored, each line is either a comment
227 // (starts with '#') or a line of the form <field1>;<field2>;...;<fieldN>.
228 // A backslash can be used to quote semicolons and newlines (and, in fact,
229 // anything else including itself).
230 //
231 // The first field is always the MIME type in the form of type/subtype (see RFC
232 // 822) where subtype may be '*' meaning "any". Following metamail, we accept
233 // "type" which means the same as "type/*", although I'm not sure whether this
234 // is standard.
235 //
236 // The second field is always the command to run. It is subject to
237 // parameter/filename expansion described below.
238 //
239 // All the following fields are optional and may not be present at all. If
240 // they're present they may appear in any order, although each of them should
241 // appear only once. The optional fields are the following:
242 // * notes=xxx is an uninterpreted string which is silently ignored
243 // * test=xxx is the command to be used to determine whether this mailcap line
244 // applies to our data or not. The RHS of this field goes through the
245 // parameter/filename expansion (as the 2nd field) and the resulting string
246 // is executed. The line applies only if the command succeeds, i.e. returns 0
247 // exit code.
248 // * print=xxx is the command to be used to print (and not view) the data of
249 // this type (parameter/filename expansion is done here too)
250 // * edit=xxx is the command to open/edit the data of this type
251 // * needsterminal means that a new interactive console must be created for
252 // the viewer
253 // * copiousoutput means that the viewer doesn't interact with the user but
254 // produces (possibly) a lof of lines of output on stdout (i.e. "cat" is a
255 // good example), thus it might be a good idea to use some kind of paging
256 // mechanism.
257 // * textualnewlines means not to perform CR/LF translation (not honored)
258 // * compose and composetyped fields are used to determine the program to be
259 // called to create a new message pert in the specified format (unused).
260 //
261 // Parameter/filename expansion:
262 // * %s is replaced with the (full) file name
263 // * %t is replaced with MIME type/subtype of the entry
264 // * for multipart type only %n is replaced with the nnumber of parts and %F is
265 // replaced by an array of (content-type, temporary file name) pairs for all
266 // message parts (TODO)
267 // * %{parameter} is replaced with the value of parameter taken from
268 // Content-type header line of the message.
269 //
270 //
271 // There are 2 possible formats for mime.types file, one entry per line (used
272 // for global mime.types and called Mosaic format) and "expanded" format where
273 // an entry takes multiple lines (used for users mime.types and called
274 // Netscape format).
275 //
276 // For both formats spaces are ignored and lines starting with a '#' are
277 // comments. Each record has one of two following forms:
278 // a) for "brief" format:
279 // <mime type> <space separated list of extensions>
280 // b) for "expanded" format:
281 // type=<mime type> BACKSLASH
282 // desc="<description>" BACKSLASH
283 // exts="<comma separated list of extensions>"
284 //
285 // (where BACKSLASH is a literal '\\' which we can't put here because cpp
286 // misinterprets it)
287 //
288 // We try to autodetect the format of mime.types: if a non-comment line starts
289 // with "type=" we assume the second format, otherwise the first one.
290
291 // there may be more than one entry for one and the same mime type, to
292 // choose the right one we have to run the command specified in the test
293 // field on our data.
294
295 // ----------------------------------------------------------------------------
296 // wxGNOME
297 // ----------------------------------------------------------------------------
298
299 // GNOME stores the info we're interested in in several locations:
300 // 1. xxx.keys files under /usr/share/mime-info
301 // 2. xxx.keys files under ~/.gnome/mime-info
302 //
303 // Update (Chris Elliott): apparently there may be an optional "[lang]" prefix
304 // just before the field name.
305
306
LoadGnomeDataFromKeyFile(const wxString & filename,const wxArrayString & dirs)307 void wxMimeTypesManagerImpl::LoadGnomeDataFromKeyFile(const wxString& filename,
308 const wxArrayString& dirs)
309 {
310 wxMimeTextFile textfile(filename);
311 if ( !textfile.Open() )
312 return;
313
314 wxLogTrace(TRACE_MIME, wxT("--- Opened Gnome file %s ---"),
315 filename.c_str());
316
317 wxArrayString search_dirs( dirs );
318
319 // values for the entry being parsed
320 wxString curMimeType, curIconFile;
321 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
322
323 wxArrayString strExtensions;
324 wxString strDesc;
325
326 const wxChar *pc;
327 size_t nLineCount = textfile.GetLineCount();
328 size_t nLine = 0;
329 while ( nLine < nLineCount )
330 {
331 pc = textfile[nLine].c_str();
332 if ( *pc != wxT('#') )
333 {
334
335 wxLogTrace(TRACE_MIME, wxT("--- Reading from Gnome file %s '%s' ---"),
336 filename.c_str(), pc);
337
338 // trim trailing space and tab
339 while ((*pc == wxT(' ')) || (*pc == wxT('\t')))
340 pc++;
341
342 wxString sTmp(pc);
343 int equal_pos = sTmp.Find( wxT('=') );
344 if (equal_pos > 0)
345 {
346 wxString left_of_equal = sTmp.Left( equal_pos );
347 const wxChar *right_of_equal = pc;
348 right_of_equal += equal_pos+1;
349
350 if (left_of_equal == wxT("icon_filename"))
351 {
352 // GNOME 2:
353 curIconFile = right_of_equal;
354
355 wxFileName newFile( curIconFile );
356 if (newFile.IsRelative() || newFile.FileExists())
357 {
358 size_t nDirs = search_dirs.GetCount();
359
360 for (size_t nDir = 0; nDir < nDirs; nDir++)
361 {
362 newFile.SetPath( search_dirs[nDir] );
363 newFile.AppendDir( wxT("pixmaps") );
364 newFile.AppendDir( wxT("document-icons") );
365 newFile.SetExt( wxT("png") );
366 if (newFile.FileExists())
367 {
368 curIconFile = newFile.GetFullPath();
369 // reorder search_dirs for speedup (fewer
370 // calls to FileExist() required)
371 if (nDir != 0)
372 {
373 const wxString &tmp = search_dirs[nDir];
374 search_dirs.RemoveAt( nDir );
375 search_dirs.Insert( tmp, 0 );
376 }
377 break;
378 }
379 }
380 }
381 }
382 else if (left_of_equal == wxT("open"))
383 {
384 sTmp = right_of_equal;
385 sTmp.Replace( wxT("%f"), wxT("%s") );
386 sTmp.Prepend( wxT("open=") );
387 entry->Add(sTmp);
388 }
389 else if (left_of_equal == wxT("view"))
390 {
391 sTmp = right_of_equal;
392 sTmp.Replace( wxT("%f"), wxT("%s") );
393 sTmp.Prepend( wxT("view=") );
394 entry->Add(sTmp);
395 }
396 else if (left_of_equal == wxT("print"))
397 {
398 sTmp = right_of_equal;
399 sTmp.Replace( wxT("%f"), wxT("%s") );
400 sTmp.Prepend( wxT("print=") );
401 entry->Add(sTmp);
402 }
403 else if (left_of_equal == wxT("description"))
404 {
405 strDesc = right_of_equal;
406 }
407 else if (left_of_equal == wxT("short_list_application_ids_for_novice_user_level"))
408 {
409 sTmp = right_of_equal;
410 if (sTmp.Contains( wxT(",") ))
411 sTmp = sTmp.BeforeFirst( wxT(',') );
412 sTmp.Prepend( wxT("open=") );
413 sTmp.Append( wxT(" %s") );
414 entry->Add(sTmp);
415 }
416
417 } // emd of has an equals sign
418 else
419 {
420 // not a comment and not an equals sign
421 if (sTmp.Contains(wxT('/')))
422 {
423 // this is the start of the new mimetype
424 // overwrite any existing data
425 if (! curMimeType.empty())
426 {
427 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc );
428
429 // now get ready for next bit
430 entry = new wxMimeTypeCommands;
431 }
432
433 curMimeType = sTmp.BeforeFirst(wxT(':'));
434 }
435 }
436 } // end of not a comment
437
438 // ignore blank lines
439 nLine++;
440 } // end of while, save any data
441
442 if ( curMimeType.empty() )
443 delete entry;
444 else
445 AddToMimeData( curMimeType, curIconFile, entry, strExtensions, strDesc);
446 }
447
LoadGnomeMimeTypesFromMimeFile(const wxString & filename)448 void wxMimeTypesManagerImpl::LoadGnomeMimeTypesFromMimeFile(const wxString& filename)
449 {
450 wxMimeTextFile textfile(filename);
451 if ( !textfile.Open() )
452 return;
453
454 wxLogTrace(TRACE_MIME,
455 wxT("--- Opened Gnome file %s ---"),
456 filename.c_str());
457
458 // values for the entry being parsed
459 wxString curMimeType, curExtList;
460
461 const wxChar *pc;
462 size_t nLineCount = textfile.GetLineCount();
463 for ( size_t nLine = 0; /* nothing */; nLine++ )
464 {
465 if ( nLine < nLineCount )
466 {
467 pc = textfile[nLine].c_str();
468 if ( *pc == wxT('#') )
469 {
470 // skip comments
471 continue;
472 }
473 }
474 else
475 {
476 // so that we will fall into the "if" below
477 pc = NULL;
478 }
479
480 if ( !pc || !*pc )
481 {
482 // end of the entry
483 if ( !curMimeType.empty() && !curExtList.empty() )
484 {
485 wxLogTrace(TRACE_MIME,
486 wxT("--- At end of Gnome file finding mimetype %s ---"),
487 curMimeType.c_str());
488
489 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
490 }
491
492 if ( !pc )
493 {
494 // the end: this can only happen if nLine == nLineCount
495 break;
496 }
497
498 curExtList.Empty();
499
500 continue;
501 }
502
503 // what do we have here?
504 if ( *pc == wxT('\t') )
505 {
506 // this is a field=value ling
507 pc++; // skip leading TAB
508
509 static const int lenField = 5; // strlen("ext: ")
510 if ( wxStrncmp(pc, wxT("ext: "), lenField) == 0 )
511 {
512 // skip it and take everything left until the end of line
513 curExtList = pc + lenField;
514 }
515 //else: some other field, we don't care
516 }
517 else
518 {
519 // this is the start of the new section
520 wxLogTrace(TRACE_MIME,
521 wxT("--- In Gnome file finding mimetype %s ---"),
522 curMimeType.c_str());
523
524 if (! curMimeType.empty())
525 AddMimeTypeInfo(curMimeType, curExtList, wxEmptyString);
526
527 curMimeType.Empty();
528
529 while ( *pc != wxT(':') && *pc != wxT('\0') )
530 {
531 curMimeType += *pc++;
532 }
533 }
534 }
535 }
536
537
LoadGnomeMimeFilesFromDir(const wxString & dirbase,const wxArrayString & dirs)538 void wxMimeTypesManagerImpl::LoadGnomeMimeFilesFromDir(
539 const wxString& dirbase, const wxArrayString& dirs)
540 {
541 wxASSERT_MSG( !dirbase.empty() && !wxEndsWithPathSeparator(dirbase),
542 wxT("base directory shouldn't end with a slash") );
543
544 wxString dirname = dirbase;
545 dirname << wxT("/mime-info");
546
547 // Don't complain if we don't have permissions to read - it confuses users
548 wxLogNull logNull;
549
550 if ( !wxDir::Exists(dirname) )
551 return;
552
553 wxDir dir(dirname);
554 if ( !dir.IsOpened() )
555 return;
556
557 // we will concatenate it with filename to get the full path below
558 dirname += wxT('/');
559
560 wxString filename;
561 bool cont;
562
563 cont = dir.GetFirst(&filename, wxT("*.mime"), wxDIR_FILES);
564 while ( cont )
565 {
566 LoadGnomeMimeTypesFromMimeFile(dirname + filename);
567
568 cont = dir.GetNext(&filename);
569 }
570
571 cont = dir.GetFirst(&filename, wxT("*.keys"), wxDIR_FILES);
572 while ( cont )
573 {
574 LoadGnomeDataFromKeyFile(dirname + filename, dirs);
575
576 cont = dir.GetNext(&filename);
577 }
578
579 // FIXME: Hack alert: We scan all icons and deduce the
580 // mime-type from the file name.
581 dirname = dirbase;
582 dirname << wxT("/pixmaps/document-icons");
583
584 // these are always empty in this file
585 wxArrayString strExtensions;
586 wxString strDesc;
587
588 if ( !wxDir::Exists(dirname) )
589 {
590 // Just test for default GPE dir also
591 dirname = wxT("/usr/share/gpe/pixmaps/default/filemanager/document-icons");
592
593 if ( !wxDir::Exists(dirname) )
594 return;
595 }
596
597 wxDir dir2( dirname );
598
599 cont = dir2.GetFirst(&filename, wxT("gnome-*.png"), wxDIR_FILES);
600 while ( cont )
601 {
602 wxString mimeType = filename;
603 mimeType.Remove( 0, 6 ); // remove "gnome-"
604 mimeType.Remove( mimeType.Len() - 4, 4 ); // remove ".png"
605 int pos = mimeType.Find( wxT("-") );
606 if (pos != wxNOT_FOUND)
607 {
608 mimeType.SetChar( pos, wxT('/') );
609 wxString iconFile = dirname;
610 iconFile << wxT("/");
611 iconFile << filename;
612 AddToMimeData( mimeType, iconFile, NULL, strExtensions, strDesc, true );
613 }
614
615 cont = dir2.GetNext(&filename);
616 }
617 }
618
GetGnomeMimeInfo(const wxString & sExtraDir)619 void wxMimeTypesManagerImpl::GetGnomeMimeInfo(const wxString& sExtraDir)
620 {
621 wxArrayString dirs;
622
623 wxString gnomedir = wxGetenv( wxT("GNOMEDIR") );
624 if (!gnomedir.empty())
625 {
626 gnomedir << wxT("/share");
627 dirs.Add( gnomedir );
628 }
629
630 dirs.Add(wxT("/usr/share"));
631 dirs.Add(wxT("/usr/local/share"));
632
633 gnomedir = wxGetHomeDir();
634 gnomedir << wxT("/.gnome");
635 dirs.Add( gnomedir );
636
637 if (!sExtraDir.empty())
638 dirs.Add( sExtraDir );
639
640 size_t nDirs = dirs.GetCount();
641 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
642 {
643 LoadGnomeMimeFilesFromDir(dirs[nDir], dirs);
644 }
645 }
646
647 // ----------------------------------------------------------------------------
648 // KDE
649 // ----------------------------------------------------------------------------
650
651
652 // KDE stores the icon info in its .kdelnk files. The file for mimetype/subtype
653 // may be found in either of the following locations
654 //
655 // 1. $KDEDIR/share/mimelnk/mimetype/subtype.kdelnk
656 // 2. ~/.kde/share/mimelnk/mimetype/subtype.kdelnk
657 //
658 // The format of a .kdelnk file is almost the same as the one used by
659 // wxFileConfig, i.e. there are groups, comments and entries. The icon is the
660 // value for the entry "Type"
661
662 // kde writing; see http://webcvs.kde.org/cgi-bin/cvsweb.cgi/~checkout~/kdelibs/kio/DESKTOP_ENTRY_STANDARD
663 // for now write to .kdelnk but should eventually do .desktop instead (in preference??)
664
CheckKDEDirsExist(const wxString & sOK,const wxString & sTest)665 bool wxMimeTypesManagerImpl::CheckKDEDirsExist( const wxString &sOK, const wxString &sTest )
666 {
667 if (sTest.empty())
668 {
669 return wxDir::Exists(sOK);
670 }
671 else
672 {
673 wxString sStart = sOK + wxT("/") + sTest.BeforeFirst(wxT('/'));
674 if (!wxDir::Exists(sStart))
675 wxMkdir(sStart);
676 wxString sEnd = sTest.AfterFirst(wxT('/'));
677 return CheckKDEDirsExist(sStart, sEnd);
678 }
679 }
680
WriteKDEMimeFile(int index,bool delete_index)681 bool wxMimeTypesManagerImpl::WriteKDEMimeFile(int index, bool delete_index)
682 {
683 wxMimeTextFile appoutfile, mimeoutfile;
684 wxString sHome = wxGetHomeDir();
685 wxString sTmp = wxT(".kde/share/mimelnk/");
686 wxString sMime = m_aTypes[index];
687 CheckKDEDirsExist(sHome, sTmp + sMime.BeforeFirst(wxT('/')) );
688 sTmp = sHome + wxT('/') + sTmp + sMime + wxT(".kdelnk");
689
690 bool bTemp;
691 bool bMimeExists = mimeoutfile.Open(sTmp);
692 if (!bMimeExists)
693 {
694 bTemp = mimeoutfile.Create(sTmp);
695 // some unknown error eg out of disk space
696 if (!bTemp)
697 return false;
698 }
699
700 sTmp = wxT(".kde/share/applnk/");
701 CheckKDEDirsExist(sHome, sTmp + sMime.AfterFirst(wxT('/')) );
702 sTmp = sHome + wxT('/') + sTmp + sMime.AfterFirst(wxT('/')) + wxT(".kdelnk");
703
704 bool bAppExists;
705 bAppExists = appoutfile.Open(sTmp);
706 if (!bAppExists)
707 {
708 bTemp = appoutfile.Create(sTmp);
709 // some unknown error eg out of disk space
710 if (!bTemp)
711 return false;
712 }
713
714 // fixed data; write if new file
715 if (!bMimeExists)
716 {
717 mimeoutfile.AddLine(wxT("#KDE Config File"));
718 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
719 mimeoutfile.AddLine(wxT("Version=1.0"));
720 mimeoutfile.AddLine(wxT("Type=MimeType"));
721 mimeoutfile.AddLine(wxT("MimeType=") + sMime);
722 }
723
724 if (!bAppExists)
725 {
726 mimeoutfile.AddLine(wxT("#KDE Config File"));
727 mimeoutfile.AddLine(wxT("[KDE Desktop Entry]"));
728 appoutfile.AddLine(wxT("Version=1.0"));
729 appoutfile.AddLine(wxT("Type=Application"));
730 appoutfile.AddLine(wxT("MimeType=") + sMime + wxT(';'));
731 }
732
733 // variable data
734 // ignore locale
735 mimeoutfile.CommentLine(wxT("Comment="));
736 if (!delete_index)
737 mimeoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
738 appoutfile.CommentLine(wxT("Name="));
739 if (!delete_index)
740 appoutfile.AddLine(wxT("Comment=") + m_aDescriptions[index]);
741
742 sTmp = m_aIcons[index];
743 // we can either give the full path, or the shortfilename if its in
744 // one of the directories we search
745 mimeoutfile.CommentLine(wxT("Icon=") );
746 if (!delete_index)
747 mimeoutfile.AddLine(wxT("Icon=") + sTmp );
748 appoutfile.CommentLine(wxT("Icon=") );
749 if (!delete_index)
750 appoutfile.AddLine(wxT("Icon=") + sTmp );
751
752 sTmp = wxT(" ") + m_aExtensions[index];
753
754 wxStringTokenizer tokenizer(sTmp, wxT(" "));
755 sTmp = wxT("Patterns=");
756 mimeoutfile.CommentLine(sTmp);
757 while ( tokenizer.HasMoreTokens() )
758 {
759 // holds an extension; need to change it to *.ext;
760 wxString e = wxT("*.") + tokenizer.GetNextToken() + wxT(";");
761 sTmp += e;
762 }
763
764 if (!delete_index)
765 mimeoutfile.AddLine(sTmp);
766
767 wxMimeTypeCommands * entries = m_aEntries[index];
768 // if we don't find open just have an empty string ... FIX this
769 sTmp = entries->GetCommandForVerb(wxT("open"));
770 sTmp.Replace( wxT("%s"), wxT("%f") );
771
772 mimeoutfile.CommentLine(wxT("DefaultApp=") );
773 if (!delete_index)
774 mimeoutfile.AddLine(wxT("DefaultApp=") + sTmp);
775
776 sTmp.Replace( wxT("%f"), wxT("") );
777 appoutfile.CommentLine(wxT("Exec="));
778 if (!delete_index)
779 appoutfile.AddLine(wxT("Exec=") + sTmp);
780
781 if (entries->GetCount() > 1)
782 {
783 //other actions as well as open
784 }
785
786 bTemp = false;
787 if (mimeoutfile.Write())
788 bTemp = true;
789 mimeoutfile.Close();
790 if (appoutfile.Write())
791 bTemp = true;
792 appoutfile.Close();
793
794 return bTemp;
795 }
796
LoadKDELinksForMimeSubtype(const wxString & dirbase,const wxString & subdir,const wxString & filename,const wxArrayString & icondirs)797 void wxMimeTypesManagerImpl::LoadKDELinksForMimeSubtype(const wxString& dirbase,
798 const wxString& subdir,
799 const wxString& filename,
800 const wxArrayString& icondirs)
801 {
802 wxFileName fullname(dirbase, filename);
803 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"),
804 fullname.GetFullPath().c_str());
805
806 wxMimeTextFile file;
807 if ( !file.Open(fullname.GetFullPath()) )
808 return;
809
810 wxMimeTypeCommands * entry = new wxMimeTypeCommands;
811 wxArrayString sExts;
812 wxString mimetype, mime_desc, strIcon;
813
814
815 int nIndex = file.GetCmd( wxT("MimeType"), mimetype );
816 if (nIndex == wxNOT_FOUND)
817 {
818 // construct mimetype from the directory name and the basename of the
819 // file (it always has .kdelnk extension)
820 mimetype << subdir << wxT('/') << filename.BeforeLast( wxT('.') );
821 }
822
823 // first find the description string: it is the value in either "Comment="
824 // line or "Comment[<locale_name>]=" one
825 nIndex = wxNOT_FOUND;
826
827 wxString comment;
828
829 #if wxUSE_INTL
830 wxLocale *locale = wxGetLocale();
831 if ( locale )
832 {
833 // try "Comment[locale name]" first
834 comment << wxT("Comment[") + locale->GetName() + wxT("]");
835 nIndex = file.GetCmd(comment, mime_desc);
836 }
837 #endif
838
839 if ( nIndex == wxNOT_FOUND )
840 file.GetCmd(wxT("Comment"), mime_desc);
841
842 //else: no description
843
844 // next find the extensions
845 wxString mime_extension;
846
847 wxString exts;
848 nIndex = file.GetCmd(wxT("Patterns"), exts);
849 if ( nIndex != wxNOT_FOUND )
850 {
851 wxStringTokenizer tokenizer(exts, wxT(";"));
852 while ( tokenizer.HasMoreTokens() )
853 {
854 wxString e = tokenizer.GetNextToken();
855
856 // don't support too difficult patterns
857 if ( e.Left(2) != wxT("*.") )
858 continue;
859
860 if ( !mime_extension.empty() )
861 {
862 // separate from the previous ext
863 mime_extension << wxT(' ');
864 }
865
866 mime_extension << e.Mid(2);
867 }
868 }
869
870 sExts.Add(mime_extension);
871
872 // ok, now we can take care of icon:
873
874 nIndex = file.GetCmd(wxT("Icon"), strIcon);
875 if ( nIndex != wxNOT_FOUND )
876 {
877 wxLogTrace(TRACE_MIME, wxT(" icon %s"), strIcon.c_str());
878
879 // it could be the real path, but more often a short name
880 if (!wxFileExists(strIcon))
881 {
882 // icon is just the short name
883 if ( !strIcon.empty() )
884 {
885 // we must check if the file exists because it may be stored
886 // in many locations, at least ~/.kde and $KDEDIR
887 size_t nDir, nDirs = icondirs.GetCount();
888 for ( nDir = 0; nDir < nDirs; nDir++ )
889 {
890 wxFileName fnameIcon( strIcon );
891 wxFileName fname( icondirs[nDir], fnameIcon.GetName() );
892 fname.SetExt( wxT("png") );
893 if (fname.FileExists())
894 {
895 strIcon = fname.GetFullPath();
896 wxLogTrace(TRACE_MIME, wxT(" iconfile %s"), strIcon.c_str());
897 break;
898 }
899 }
900 }
901 }
902 }
903
904 // now look for lines which know about the application
905 // exec= or DefaultApp=
906
907 wxString sTmp;
908 nIndex = file.GetCmd(wxT("DefaultApp"), sTmp);
909
910 if ( nIndex == wxNOT_FOUND )
911 {
912 // no entry try exec
913 nIndex = file.GetCmd(wxT("Exec"), sTmp);
914 }
915
916 if ( nIndex != wxNOT_FOUND )
917 {
918 // we expect %f; others including %F and %U and %u are possible
919 if (0 == sTmp.Replace( wxT("%f"), wxT("%s") ))
920 sTmp += wxT(" %s");
921 entry->AddOrReplaceVerb(wxString(wxT("open")), sTmp );
922 }
923
924 AddToMimeData(mimetype, strIcon, entry, sExts, mime_desc);
925 }
926
LoadKDELinksForMimeType(const wxString & dirbase,const wxString & subdir,const wxArrayString & icondirs)927 void wxMimeTypesManagerImpl::LoadKDELinksForMimeType(const wxString& dirbase,
928 const wxString& subdir,
929 const wxArrayString& icondirs)
930 {
931 wxFileName dirname(dirbase, wxEmptyString);
932 dirname.AppendDir(subdir);
933
934 // Don't complain if we don't have permissions to read - it confuses users
935 wxLogNull logNull;
936
937 wxDir dir(dirname.GetPath());
938 if(! dir.IsOpened())
939 return;
940
941 wxLogTrace(TRACE_MIME, wxT("--- Loading from KDE directory %s ---"),
942 dirname.GetPath().c_str());
943
944 wxString filename;
945 bool cont = dir.GetFirst(&filename, wxT("*.kdelnk"), wxDIR_FILES);
946 while(cont) {
947 LoadKDELinksForMimeSubtype(dirname.GetPath(), subdir,
948 filename, icondirs);
949 cont = dir.GetNext(&filename);
950 }
951
952 // new standard for Gnome and KDE
953 cont = dir.GetFirst(&filename, wxT("*.desktop"), wxDIR_FILES);
954 while(cont) {
955 LoadKDELinksForMimeSubtype(dirname.GetPath(), subdir,
956 filename, icondirs);
957 cont = dir.GetNext(&filename);
958 }
959 }
960
LoadKDELinkFilesFromDir(const wxString & dirname,const wxArrayString & icondirs)961 void wxMimeTypesManagerImpl::LoadKDELinkFilesFromDir(const wxString& dirname,
962 const wxArrayString& icondirs)
963 {
964 // Don't complain if we don't have permissions to read - it confuses users
965 wxLogNull logNull;
966
967 if(! wxDir::Exists(dirname))
968 return;
969
970 wxDir dir(dirname);
971 if ( !dir.IsOpened() )
972 return;
973
974 wxString subdir;
975 bool cont = dir.GetFirst(&subdir, wxEmptyString, wxDIR_DIRS);
976 while ( cont )
977 {
978 LoadKDELinksForMimeType(dirname, subdir, icondirs);
979
980 cont = dir.GetNext(&subdir);
981 }
982 }
983
984 // Read a KDE .desktop file of type 'Application'
LoadKDEApp(const wxString & filename)985 void wxMimeTypesManagerImpl::LoadKDEApp(const wxString& filename)
986 {
987 wxLogTrace(TRACE_MIME, wxT("loading KDE file %s"), filename.c_str());
988
989 wxMimeTextFile file;
990 if ( !file.Open(filename) )
991 return;
992
993 // Here, only type 'Application' should be considered.
994 wxString type;
995 int nIndex = file.GetCmd( wxT("Type"), type);
996 if (nIndex != wxNOT_FOUND &&
997 type.CmpNoCase(wxT("application")))
998 return;
999
1000 // The hidden entry specifies a file to be ignored.
1001 wxString hidden;
1002 nIndex = file.GetCmd( wxT("Hidden"), hidden);
1003 if (nIndex != wxNOT_FOUND && !hidden.CmpNoCase(wxT("true")))
1004 return;
1005
1006 // Semicolon separated list of mime types handled by the application.
1007 wxString mimetypes;
1008 nIndex = file.GetCmd( wxT("MimeType"), mimetypes );
1009 if (nIndex == wxNOT_FOUND)
1010 return;
1011
1012 // Name of the application
1013 wxString nameapp;
1014 nIndex = wxNOT_FOUND;
1015 #if wxUSE_INTL // try "Name[locale name]" first
1016 wxLocale *locale = wxGetLocale();
1017 if ( locale )
1018 nIndex = file.GetCmd(_T("Name[")+locale->GetName()+_T("]"), nameapp);
1019 #endif // wxUSE_INTL
1020 if(nIndex == wxNOT_FOUND)
1021 nIndex = file.GetCmd( wxT("Name"), nameapp);
1022
1023 // Icon of the application.
1024 wxString icon;
1025 wxString nameicon, namemini;
1026 nIndex = wxNOT_FOUND;
1027 #if wxUSE_INTL // try "Icon[locale name]" first
1028 if ( locale )
1029 nIndex = file.GetCmd(_T("Icon[")+locale->GetName()+_T("]"), icon);
1030 #endif // wxUSE_INTL
1031 if(nIndex == wxNOT_FOUND)
1032 nIndex = file.GetCmd( wxT("Icon"), icon);
1033 if(nIndex != wxNOT_FOUND) {
1034 nameicon = wxString(wxT("--icon ")) + icon;
1035 namemini = wxString(wxT("--miniicon ")) + icon;
1036 }
1037
1038 // Replace some of the field code in the 'Exec' entry.
1039 // TODO: deal with %d, %D, %n, %N, %k and %v (but last one is deprecated)
1040 wxString sCmd;
1041 nIndex = file.GetCmd( wxT("Exec"), sCmd );
1042 if (nIndex == wxNOT_FOUND)
1043 return;
1044 // we expect %f; others including %F and %U and %u are possible
1045 sCmd.Replace(wxT("%F"), wxT("%f"));
1046 sCmd.Replace(wxT("%U"), wxT("%f"));
1047 sCmd.Replace(wxT("%u"), wxT("%f"));
1048 if (0 == sCmd.Replace ( wxT("%f"), wxT("%s") ))
1049 sCmd = sCmd + wxT(" %s");
1050 sCmd.Replace(wxT("%c"), nameapp);
1051 sCmd.Replace(wxT("%i"), nameicon);
1052 sCmd.Replace(wxT("%m"), namemini);
1053
1054 wxStringTokenizer tokenizer(mimetypes, _T(";"));
1055 while(tokenizer.HasMoreTokens()) {
1056 wxString mimetype = tokenizer.GetNextToken().Lower();
1057 int nIndex = m_aTypes.Index(mimetype);
1058 if(nIndex != wxNOT_FOUND) { // is this a known MIME type?
1059 wxMimeTypeCommands* entry = m_aEntries[nIndex];
1060 entry->AddOrReplaceVerb(wxT("open"), sCmd);
1061 }
1062 }
1063 }
1064
LoadKDEAppsFilesFromDir(const wxString & dirname)1065 void wxMimeTypesManagerImpl::LoadKDEAppsFilesFromDir(const wxString& dirname)
1066 {
1067 // Don't complain if we don't have permissions to read - it confuses users
1068 wxLogNull logNull;
1069
1070 if(! wxDir::Exists(dirname))
1071 return;
1072 wxDir dir(dirname);
1073 if ( !dir.IsOpened() )
1074 return;
1075
1076 wxString filename;
1077 // Look into .desktop files
1078 bool cont = dir.GetFirst(&filename, _T("*.desktop"), wxDIR_FILES);
1079 while(cont) {
1080 wxFileName p(dirname, filename);
1081 LoadKDEApp( p.GetFullPath() );
1082 cont = dir.GetNext(&filename);
1083 }
1084 // Look recursively into subdirs
1085 cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
1086 while(cont) {
1087 wxFileName p(dirname, wxEmptyString);
1088 p.AppendDir(filename);
1089 LoadKDEAppsFilesFromDir( p.GetPath() );
1090 cont = dir.GetNext(&filename);
1091 }
1092 }
1093
1094 // Return base KDE directories.
1095 // 1) Environment variable $KDEHOME, or "~/.kde" if not set.
1096 // 2) List of directories in colon separated environment variable $KDEDIRS.
1097 // 3) Environment variable $KDEDIR in case $KDEDIRS is not set.
1098 // Notice at least the local kde directory is added to the list. If it is the
1099 // only one, use later the application 'kde-config' to get additional paths.
GetKDEBaseDirs(wxArrayString & basedirs)1100 static void GetKDEBaseDirs(wxArrayString& basedirs)
1101 {
1102 wxString env = wxGetenv( wxT("KDEHOME") );
1103 if(env.IsEmpty())
1104 env = wxGetHomeDir() + wxT("/.kde");
1105 basedirs.Add(env);
1106
1107 env = wxGetenv( wxT("KDEDIRS") );
1108 if(env.IsEmpty()) {
1109 env = wxGetenv( wxT("KDEDIR") );
1110 if(! env.IsEmpty())
1111 basedirs.Add(env);
1112 } else {
1113 wxStringTokenizer tokenizer(env, wxT(":"));
1114 while(tokenizer.HasMoreTokens())
1115 basedirs.Add( tokenizer.GetNextToken() );
1116 }
1117 }
1118
ReadPathFromKDEConfig(const wxString & request)1119 static wxString ReadPathFromKDEConfig(const wxString& request)
1120 {
1121 wxString str;
1122 wxArrayString output;
1123 if(wxExecute(wxT("kde-config --path ")+request, output) == 0 &&
1124 output.Count() > 0)
1125 str = output.Item(0);
1126 return str;
1127 }
1128
1129 // Try to find the "Theme" entry in the configuration file, provided it exists.
GetKDEThemeInFile(const wxFileName & filename)1130 static wxString GetKDEThemeInFile(const wxFileName& filename)
1131 {
1132 wxString theme;
1133 wxMimeTextFile config;
1134 if ( filename.FileExists() && config.Open(filename.GetFullPath()) )
1135 {
1136 size_t cnt = config.GetLineCount();
1137 for ( size_t i = 0; i < cnt; i++ )
1138 {
1139 if ( config[i].StartsWith(wxT("Theme="), &theme) )
1140 break;
1141 }
1142 }
1143
1144 return theme;
1145 }
1146
1147 // Try to find a file "kdeglobals" in one of the directories and read the
1148 // "Theme" entry there.
GetKDETheme(const wxArrayString & basedirs)1149 static wxString GetKDETheme(const wxArrayString& basedirs)
1150 {
1151 wxString theme;
1152 for(size_t i = 0; i < basedirs.Count(); i++) {
1153 wxFileName filename(basedirs.Item(i), wxEmptyString);
1154 filename.AppendDir( wxT("share") );
1155 filename.AppendDir( wxT("config") );
1156 filename.SetName( wxT("kdeglobals") );
1157 theme = GetKDEThemeInFile(filename);
1158 if(! theme.IsEmpty())
1159 return theme;
1160 }
1161 // If $KDEDIRS and $KDEDIR were set, we try nothing more. Otherwise, we
1162 // try to get the configuration file with 'kde-config'.
1163 if(basedirs.Count() > 1)
1164 return theme;
1165 wxString paths = ReadPathFromKDEConfig(wxT("config"));
1166 if(! paths.IsEmpty()) {
1167 wxStringTokenizer tokenizer(paths, wxT(":"));
1168 while( tokenizer.HasMoreTokens() ) {
1169 wxFileName filename(tokenizer.GetNextToken(), wxT("kdeglobals"));
1170 theme = GetKDEThemeInFile(filename);
1171 if(! theme.IsEmpty())
1172 return theme;
1173 }
1174 }
1175 return theme;
1176 }
1177
1178 // Get list of directories of icons.
GetKDEIconDirs(const wxArrayString & basedirs,wxArrayString & icondirs)1179 static void GetKDEIconDirs(const wxArrayString& basedirs,
1180 wxArrayString& icondirs)
1181 {
1182 wxString theme = GetKDETheme(basedirs);
1183 if(theme.IsEmpty())
1184 theme = wxT("default.kde");
1185
1186 for(size_t i = 0; i < basedirs.Count(); i++) {
1187 wxFileName dirname(basedirs.Item(i), wxEmptyString);
1188 dirname.AppendDir( wxT("share") );
1189 dirname.AppendDir( wxT("icons") );
1190 dirname.AppendDir(theme);
1191 dirname.AppendDir( wxT("32x32") );
1192 dirname.AppendDir( wxT("mimetypes") );
1193 if( wxDir::Exists( dirname.GetPath() ) )
1194 icondirs.Add( dirname.GetPath() );
1195 }
1196
1197 // If $KDEDIRS and $KDEDIR were not set, use 'kde-config'
1198 if(basedirs.Count() > 1)
1199 return;
1200 wxString paths = ReadPathFromKDEConfig(wxT("icon"));
1201 if(! paths.IsEmpty()) {
1202 wxStringTokenizer tokenizer(paths, wxT(":"));
1203 while( tokenizer.HasMoreTokens() ) {
1204 wxFileName dirname(tokenizer.GetNextToken(), wxEmptyString);
1205 dirname.AppendDir(theme);
1206 dirname.AppendDir( wxT("32x32") );
1207 dirname.AppendDir( wxT("mimetypes") );
1208 if(icondirs.Index(dirname.GetPath()) == wxNOT_FOUND &&
1209 wxDir::Exists( dirname.GetPath() ) )
1210 icondirs.Add( dirname.GetPath() );
1211 }
1212 }
1213 }
1214
1215 // Get list of directories of mime types.
GetKDEMimeDirs(const wxArrayString & basedirs,wxArrayString & mimedirs)1216 static void GetKDEMimeDirs(const wxArrayString& basedirs,
1217 wxArrayString& mimedirs)
1218 {
1219 for(size_t i = 0; i < basedirs.Count(); i++) {
1220 wxFileName dirname(basedirs.Item(i), wxEmptyString);
1221 dirname.AppendDir( wxT("share") );
1222 dirname.AppendDir( wxT("mimelnk") );
1223 if( wxDir::Exists( dirname.GetPath() ) )
1224 mimedirs.Add( dirname.GetPath() );
1225 }
1226
1227 // If $KDEDIRS and $KDEDIR were not set, use 'kde-config'
1228 if(basedirs.Count() > 1)
1229 return;
1230 wxString paths = ReadPathFromKDEConfig(wxT("mime"));
1231 if(! paths.IsEmpty()) {
1232 wxStringTokenizer tokenizer(paths, wxT(":"));
1233 while( tokenizer.HasMoreTokens() ) {
1234 wxFileName p(tokenizer.GetNextToken(), wxEmptyString);
1235 wxString dirname = p.GetPath(); // To remove possible trailing '/'
1236 if(mimedirs.Index(dirname) == wxNOT_FOUND &&
1237 wxDir::Exists(dirname) )
1238 mimedirs.Add(dirname);
1239 }
1240 }
1241 }
1242
1243 // Get list of directories of application desktop files.
GetKDEAppsDirs(const wxArrayString & basedirs,wxArrayString & appsdirs)1244 static void GetKDEAppsDirs(const wxArrayString& basedirs,
1245 wxArrayString& appsdirs)
1246 {
1247 for(size_t i = 0; i < basedirs.Count(); i++) {
1248 wxFileName dirname(basedirs.Item(i), wxEmptyString);
1249 dirname.AppendDir( wxT("share") );
1250 dirname.AppendDir( wxT("applnk") );
1251 if( wxDir::Exists( dirname.GetPath() ) )
1252 appsdirs.Add( dirname.GetPath() );
1253 }
1254
1255 // If $KDEDIRS and $KDEDIR were not set, use 'kde-config'
1256 if(basedirs.Count() > 1)
1257 return;
1258 wxString paths = ReadPathFromKDEConfig(wxT("apps"));
1259 if(! paths.IsEmpty()) {
1260 wxStringTokenizer tokenizer(paths, wxT(":"));
1261 while( tokenizer.HasMoreTokens() ) {
1262 wxFileName p(tokenizer.GetNextToken(), wxEmptyString);
1263 wxString dirname = p.GetPath(); // To remove possible trailing '/'
1264 if(appsdirs.Index(dirname) == wxNOT_FOUND &&
1265 wxDir::Exists(dirname) )
1266 appsdirs.Add(dirname);
1267 }
1268 }
1269 paths = ReadPathFromKDEConfig(wxT("xdgdata-apps"));
1270 if(! paths.IsEmpty()) {
1271 wxStringTokenizer tokenizer(paths, wxT(":"));
1272 while( tokenizer.HasMoreTokens() ) {
1273 wxFileName p(tokenizer.GetNextToken(), wxEmptyString);
1274 wxString dirname = p.GetPath(); // To remove possible trailing '/'
1275 if(appsdirs.Index(dirname) == wxNOT_FOUND &&
1276 wxDir::Exists(dirname) )
1277 appsdirs.Add(dirname);
1278 }
1279 }
1280 }
1281
1282 // Fill database with all mime types.
GetKDEMimeInfo(const wxString & sExtraDir)1283 void wxMimeTypesManagerImpl::GetKDEMimeInfo(const wxString& sExtraDir)
1284 {
1285 wxArrayString basedirs;
1286 GetKDEBaseDirs(basedirs);
1287
1288 wxArrayString icondirs;
1289 GetKDEIconDirs(basedirs, icondirs);
1290 wxArrayString mimedirs;
1291 GetKDEMimeDirs(basedirs, mimedirs);
1292 wxArrayString appsdirs;
1293 GetKDEAppsDirs(basedirs, appsdirs);
1294
1295 if(! sExtraDir.IsEmpty()) {
1296 icondirs.Add(sExtraDir + wxT("/icons"));
1297 mimedirs.Add(sExtraDir + wxT("/mimelnk"));
1298 appsdirs.Add(sExtraDir + wxT("/applnk"));
1299 }
1300
1301 // Load mime types
1302 size_t nDirs = mimedirs.GetCount(), nDir;
1303 for(nDir = 0; nDir < nDirs; nDir++)
1304 LoadKDELinkFilesFromDir(mimedirs[nDir], icondirs);
1305
1306 // Load application files and associate them to corresponding mime types.
1307 nDirs = appsdirs.GetCount();
1308 for(nDir = 0; nDir < nDirs; nDir++)
1309 LoadKDEAppsFilesFromDir(appsdirs[nDir]);
1310 }
1311
1312 // ----------------------------------------------------------------------------
1313 // wxFileTypeImpl (Unix)
1314 // ----------------------------------------------------------------------------
1315
GetExpandedCommand(const wxString & verb,const wxFileType::MessageParameters & params) const1316 wxString wxFileTypeImpl::GetExpandedCommand(const wxString & verb, const wxFileType::MessageParameters& params) const
1317 {
1318 wxString sTmp;
1319 size_t i = 0;
1320 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1321 {
1322 sTmp = m_manager->GetCommand( verb, m_index[i] );
1323 i++;
1324 }
1325
1326 return wxFileType::ExpandCommand(sTmp, params);
1327 }
1328
GetIcon(wxIconLocation * iconLoc) const1329 bool wxFileTypeImpl::GetIcon(wxIconLocation *iconLoc) const
1330 {
1331 wxString sTmp;
1332 size_t i = 0;
1333 while ( (i < m_index.GetCount() ) && sTmp.empty() )
1334 {
1335 sTmp = m_manager->m_aIcons[m_index[i]];
1336 i++;
1337 }
1338
1339 if ( sTmp.empty() )
1340 return false;
1341
1342 if ( iconLoc )
1343 {
1344 iconLoc->SetFileName(sTmp);
1345 }
1346
1347 return true;
1348 }
1349
GetMimeTypes(wxArrayString & mimeTypes) const1350 bool wxFileTypeImpl::GetMimeTypes(wxArrayString& mimeTypes) const
1351 {
1352 mimeTypes.Clear();
1353 size_t nCount = m_index.GetCount();
1354 for (size_t i = 0; i < nCount; i++)
1355 mimeTypes.Add(m_manager->m_aTypes[m_index[i]]);
1356
1357 return true;
1358 }
1359
GetAllCommands(wxArrayString * verbs,wxArrayString * commands,const wxFileType::MessageParameters & params) const1360 size_t wxFileTypeImpl::GetAllCommands(wxArrayString *verbs,
1361 wxArrayString *commands,
1362 const wxFileType::MessageParameters& params) const
1363 {
1364 wxString vrb, cmd, sTmp;
1365 size_t count = 0;
1366 wxMimeTypeCommands * sPairs;
1367
1368 // verbs and commands have been cleared already in mimecmn.cpp...
1369 // if we find no entries in the exact match, try the inexact match
1370 for (size_t n = 0; ((count == 0) && (n < m_index.GetCount())); n++)
1371 {
1372 // list of verb = command pairs for this mimetype
1373 sPairs = m_manager->m_aEntries [m_index[n]];
1374 size_t i;
1375 for ( i = 0; i < sPairs->GetCount(); i++ )
1376 {
1377 vrb = sPairs->GetVerb(i);
1378 // some gnome entries have "." inside
1379 vrb = vrb.AfterLast(wxT('.'));
1380 cmd = sPairs->GetCmd(i);
1381 if (! cmd.empty() )
1382 {
1383 cmd = wxFileType::ExpandCommand(cmd, params);
1384 count++;
1385 if ( vrb.IsSameAs(wxT("open")))
1386 {
1387 if ( verbs )
1388 verbs->Insert(vrb, 0u);
1389 if ( commands )
1390 commands ->Insert(cmd, 0u);
1391 }
1392 else
1393 {
1394 if ( verbs )
1395 verbs->Add(vrb);
1396 if ( commands )
1397 commands->Add(cmd);
1398 }
1399 }
1400 }
1401 }
1402
1403 return count;
1404 }
1405
GetExtensions(wxArrayString & extensions)1406 bool wxFileTypeImpl::GetExtensions(wxArrayString& extensions)
1407 {
1408 wxString strExtensions = m_manager->GetExtension(m_index[0]);
1409 extensions.Empty();
1410
1411 // one extension in the space or comma-delimited list
1412 wxString strExt;
1413 for ( const wxChar *p = strExtensions; /* nothing */; p++ )
1414 {
1415 if ( *p == wxT(' ') || *p == wxT(',') || *p == wxT('\0') )
1416 {
1417 if ( !strExt.empty() )
1418 {
1419 extensions.Add(strExt);
1420 strExt.Empty();
1421 }
1422 //else: repeated spaces
1423 // (shouldn't happen, but it's not that important if it does happen)
1424
1425 if ( *p == wxT('\0') )
1426 break;
1427 }
1428 else if ( *p == wxT('.') )
1429 {
1430 // remove the dot from extension (but only if it's the first char)
1431 if ( !strExt.empty() )
1432 {
1433 strExt += wxT('.');
1434 }
1435 //else: no, don't append it
1436 }
1437 else
1438 {
1439 strExt += *p;
1440 }
1441 }
1442
1443 return true;
1444 }
1445
1446 // set an arbitrary command:
1447 // could adjust the code to ask confirmation if it already exists and
1448 // overwriteprompt is true, but this is currently ignored as *Associate* has
1449 // no overwrite prompt
1450 bool
SetCommand(const wxString & cmd,const wxString & verb,bool WXUNUSED (overwriteprompt))1451 wxFileTypeImpl::SetCommand(const wxString& cmd,
1452 const wxString& verb,
1453 bool WXUNUSED(overwriteprompt))
1454 {
1455 wxArrayString strExtensions;
1456 wxString strDesc, strIcon;
1457
1458 wxArrayString strTypes;
1459 GetMimeTypes(strTypes);
1460 if ( strTypes.IsEmpty() )
1461 return false;
1462
1463 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1464 entry->Add(verb + wxT("=") + cmd + wxT(" %s "));
1465
1466 bool ok = true;
1467 size_t nCount = strTypes.GetCount();
1468 for ( size_t i = 0; i < nCount; i++ )
1469 {
1470 if (!m_manager->DoAssociation(strTypes[i], strIcon, entry, strExtensions, strDesc))
1471 ok = false;
1472 }
1473
1474 return ok;
1475 }
1476
1477 // ignore index on the grounds that we only have one icon in a Unix file
SetDefaultIcon(const wxString & strIcon,int WXUNUSED (index))1478 bool wxFileTypeImpl::SetDefaultIcon(const wxString& strIcon, int WXUNUSED(index))
1479 {
1480 if (strIcon.empty())
1481 return false;
1482
1483 wxArrayString strExtensions;
1484 wxString strDesc;
1485
1486 wxArrayString strTypes;
1487 GetMimeTypes(strTypes);
1488 if ( strTypes.IsEmpty() )
1489 return false;
1490
1491 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
1492 bool ok = true;
1493 size_t nCount = strTypes.GetCount();
1494 for ( size_t i = 0; i < nCount; i++ )
1495 {
1496 if ( !m_manager->DoAssociation
1497 (
1498 strTypes[i],
1499 strIcon,
1500 entry,
1501 strExtensions,
1502 strDesc
1503 ) )
1504 {
1505 ok = false;
1506 }
1507 }
1508
1509 return ok;
1510 }
1511
1512 // ----------------------------------------------------------------------------
1513 // wxMimeTypesManagerImpl (Unix)
1514 // ----------------------------------------------------------------------------
1515
wxMimeTypesManagerImpl()1516 wxMimeTypesManagerImpl::wxMimeTypesManagerImpl()
1517 {
1518 m_initialized = false;
1519 m_mailcapStylesInited = 0;
1520 }
1521
InitIfNeeded()1522 void wxMimeTypesManagerImpl::InitIfNeeded()
1523 {
1524 if ( !m_initialized )
1525 {
1526 // set the flag first to prevent recursion
1527 m_initialized = true;
1528
1529 int mailcapStyles = wxMAILCAP_ALL;
1530 if ( wxAppTraits * const traits = wxTheApp ? wxTheApp->GetTraits()
1531 : NULL )
1532 {
1533 wxString wm = traits->GetDesktopEnvironment();
1534
1535 if ( wm == wxT("KDE") )
1536 mailcapStyles = wxMAILCAP_KDE;
1537 else if ( wm == wxT("GNOME") )
1538 mailcapStyles = wxMAILCAP_GNOME;
1539 //else: unknown, use the default
1540 }
1541
1542 Initialize(mailcapStyles);
1543 }
1544 }
1545
1546 // read system and user mailcaps and other files
Initialize(int mailcapStyles,const wxString & sExtraDir)1547 void wxMimeTypesManagerImpl::Initialize(int mailcapStyles,
1548 const wxString& sExtraDir)
1549 {
1550 // read mimecap amd mime.types
1551 if ( (mailcapStyles & wxMAILCAP_NETSCAPE) ||
1552 (mailcapStyles & wxMAILCAP_STANDARD) )
1553 GetMimeInfo(sExtraDir);
1554
1555 // read GNOME tables
1556 if (mailcapStyles & wxMAILCAP_GNOME)
1557 GetGnomeMimeInfo(sExtraDir);
1558
1559 // read KDE tables which are never installed on OpenVMS
1560 #ifndef __VMS
1561 if (mailcapStyles & wxMAILCAP_KDE)
1562 GetKDEMimeInfo(sExtraDir);
1563 #endif
1564
1565 // Load desktop files for Gnome, and then override them with the Gnome defaults.
1566 // We will override them one desktop file at a time, rather
1567 // than one mime type at a time, but it should be a reasonable
1568 // heuristic.
1569 if (mailcapStyles & wxMAILCAP_GNOME)
1570 {
1571 wxString xdgDataHome = wxGetenv(wxT("XDG_DATA_HOME"));
1572 if ( xdgDataHome.empty() )
1573 xdgDataHome = wxGetHomeDir() + wxT("/.local/share");
1574 wxString xdgDataDirs = wxGetenv(wxT("XDG_DATA_DIRS"));
1575 if ( xdgDataDirs.empty() )
1576 xdgDataDirs = wxT("/usr/local/share:/usr/share:/usr/share/gnome");
1577 wxArrayString dirs;
1578
1579 wxStringTokenizer tokenizer(xdgDataDirs, wxT(":"));
1580 while ( tokenizer.HasMoreTokens() )
1581 {
1582 wxString p = tokenizer.GetNextToken();
1583 dirs.Add(p);
1584 }
1585 dirs.insert(dirs.begin(), xdgDataHome);
1586
1587 wxString defaultsList;
1588 size_t i;
1589 for (i = 0; i < dirs.GetCount(); i++)
1590 {
1591 wxString f(dirs[i] + wxT("/applications/defaults.list"));
1592 if (wxFileExists(f))
1593 {
1594 defaultsList = f;
1595 break;
1596 }
1597 }
1598
1599 // Load application files and associate them to corresponding mime types.
1600 size_t nDirs = dirs.GetCount();
1601 for (size_t nDir = 0; nDir < nDirs; nDir++)
1602 {
1603 wxString dirStr(dirs[nDir] + wxT("/applications"));
1604 LoadKDEAppsFilesFromDir(dirStr);
1605 }
1606
1607 if (!defaultsList.IsEmpty())
1608 {
1609 wxArrayString deskTopFilesSeen;
1610
1611 wxMimeTextFile textfile(defaultsList);
1612 if ( textfile.Open() )
1613 {
1614 int nIndex = textfile.pIndexOf( wxT("[Default Applications]") );
1615 if (nIndex != wxNOT_FOUND)
1616 {
1617 for (i = nIndex+1; i < textfile.GetLineCount(); i++)
1618 {
1619 if (textfile[i].Find(wxT("=")) != wxNOT_FOUND)
1620 {
1621 wxString mimeType = textfile.GetVerb(i);
1622 wxString desktopFile = textfile.GetCmd(i);
1623
1624 if (deskTopFilesSeen.Index(desktopFile) == wxNOT_FOUND)
1625 {
1626 deskTopFilesSeen.Add(desktopFile);
1627 size_t j;
1628 for (j = 0; j < dirs.GetCount(); j++)
1629 {
1630 wxString desktopPath(dirs[j] + wxT("/applications/") + desktopFile);
1631 if (wxFileExists(desktopPath))
1632 {
1633 LoadKDEApp(desktopPath);
1634 }
1635 }
1636 }
1637 }
1638 }
1639 }
1640 }
1641 }
1642 }
1643
1644 m_mailcapStylesInited |= mailcapStyles;
1645 }
1646
1647 // clear data so you can read another group of WM files
ClearData()1648 void wxMimeTypesManagerImpl::ClearData()
1649 {
1650 m_aTypes.Clear();
1651 m_aIcons.Clear();
1652 m_aExtensions.Clear();
1653 m_aDescriptions.Clear();
1654
1655 WX_CLEAR_ARRAY(m_aEntries);
1656 m_aEntries.Empty();
1657
1658 m_mailcapStylesInited = 0;
1659 }
1660
~wxMimeTypesManagerImpl()1661 wxMimeTypesManagerImpl::~wxMimeTypesManagerImpl()
1662 {
1663 ClearData();
1664 }
1665
GetMimeInfo(const wxString & sExtraDir)1666 void wxMimeTypesManagerImpl::GetMimeInfo(const wxString& sExtraDir)
1667 {
1668 // read this for netscape or Metamail formats
1669
1670 // directories where we look for mailcap and mime.types by default
1671 // used by netscape and pine and other mailers, using 2 different formats!
1672
1673 // (taken from metamail(1) sources)
1674 //
1675 // although RFC 1524 specifies the search path of
1676 // /etc/:/usr/etc:/usr/local/etc only, it doesn't hurt to search in more
1677 // places - OTOH, the RFC also says that this path can be changed with
1678 // MAILCAPS environment variable (containing the colon separated full
1679 // filenames to try) which is not done yet (TODO?)
1680
1681 wxString strHome = wxGetenv(wxT("HOME"));
1682
1683 wxArrayString dirs;
1684 dirs.Add( strHome + wxT("/.") );
1685 dirs.Add( wxT("/etc/") );
1686 dirs.Add( wxT("/usr/etc/") );
1687 dirs.Add( wxT("/usr/local/etc/") );
1688 dirs.Add( wxT("/etc/mail/") );
1689 dirs.Add( wxT("/usr/public/lib/") );
1690 if (!sExtraDir.empty())
1691 dirs.Add( sExtraDir + wxT("/") );
1692
1693 wxString file;
1694 size_t nDirs = dirs.GetCount();
1695 for ( size_t nDir = 0; nDir < nDirs; nDir++ )
1696 {
1697 file = dirs[nDir];
1698 file += wxT("mailcap");
1699 if ( wxFile::Exists(file) )
1700 {
1701 ReadMailcap(file);
1702 }
1703
1704 file = dirs[nDir];
1705 file += wxT("mime.types");
1706 if ( wxFile::Exists(file) )
1707 ReadMimeTypes(file);
1708 }
1709 }
1710
WriteToMimeTypes(int index,bool delete_index)1711 bool wxMimeTypesManagerImpl::WriteToMimeTypes(int index, bool delete_index)
1712 {
1713 // check we have the right manager
1714 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD) )
1715 return false;
1716
1717 bool bTemp;
1718 wxString strHome = wxGetenv(wxT("HOME"));
1719
1720 // and now the users mailcap
1721 wxString strUserMailcap = strHome + wxT("/.mime.types");
1722
1723 wxMimeTextFile file;
1724 if ( wxFile::Exists(strUserMailcap) )
1725 {
1726 bTemp = file.Open(strUserMailcap);
1727 }
1728 else
1729 {
1730 if (delete_index)
1731 return false;
1732
1733 bTemp = file.Create(strUserMailcap);
1734 }
1735
1736 if (bTemp)
1737 {
1738 int nIndex;
1739 // test for netscape's header and return false if its found
1740 nIndex = file.pIndexOf(wxT("#--Netscape"));
1741 if (nIndex != wxNOT_FOUND)
1742 {
1743 wxFAIL_MSG(wxT("Error in .mime.types\nTrying to mix Netscape and Metamail formats\nFile not modified"));
1744 return false;
1745 }
1746
1747 // write it in alternative format
1748 // get rid of unwanted entries
1749 wxString strType = m_aTypes[index];
1750 nIndex = file.pIndexOf(strType);
1751
1752 // get rid of all the unwanted entries...
1753 if (nIndex != wxNOT_FOUND)
1754 file.CommentLine(nIndex);
1755
1756 if (!delete_index)
1757 {
1758 // add the new entries in
1759 wxString sTmp = strType.Append( wxT(' '), 40 - strType.Len() );
1760 sTmp += m_aExtensions[index];
1761 file.AddLine(sTmp);
1762 }
1763
1764 bTemp = file.Write();
1765 file.Close();
1766 }
1767
1768 return bTemp;
1769 }
1770
WriteToNSMimeTypes(int index,bool delete_index)1771 bool wxMimeTypesManagerImpl::WriteToNSMimeTypes(int index, bool delete_index)
1772 {
1773 //check we have the right managers
1774 if (! ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) )
1775 return false;
1776
1777 bool bTemp;
1778 wxString strHome = wxGetenv(wxT("HOME"));
1779
1780 // and now the users mailcap
1781 wxString strUserMailcap = strHome + wxT("/.mime.types");
1782
1783 wxMimeTextFile file;
1784 if ( wxFile::Exists(strUserMailcap) )
1785 {
1786 bTemp = file.Open(strUserMailcap);
1787 }
1788 else
1789 {
1790 if (delete_index)
1791 return false;
1792
1793 bTemp = file.Create(strUserMailcap);
1794 }
1795
1796 if (bTemp)
1797 {
1798 // write it in the format that Netscape uses
1799 int nIndex;
1800 // test for netscape's header and insert if required...
1801 // this is a comment so use true
1802 nIndex = file.pIndexOf(wxT("#--Netscape"), true);
1803 if (nIndex == wxNOT_FOUND)
1804 {
1805 // either empty file or metamail format
1806 // at present we can't cope with mixed formats, so exit to preseve
1807 // metamail entreies
1808 if (file.GetLineCount() > 0)
1809 {
1810 wxFAIL_MSG(wxT(".mime.types File not in Netscape format\nNo entries written to\n.mime.types or to .mailcap"));
1811 return false;
1812 }
1813
1814 file.InsertLine(wxT( "#--Netscape Communications Corporation MIME Information" ), 0);
1815 nIndex = 0;
1816 }
1817
1818 wxString strType = wxT("type=") + m_aTypes[index];
1819 nIndex = file.pIndexOf(strType);
1820
1821 // get rid of all the unwanted entries...
1822 if (nIndex != wxNOT_FOUND)
1823 {
1824 wxString sOld = file[nIndex];
1825 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1826 {
1827 file.CommentLine(nIndex);
1828 sOld = file[nIndex];
1829
1830 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mime.types line '%d %s' ---"), nIndex, sOld.c_str());
1831
1832 nIndex++;
1833 }
1834
1835 if (nIndex < (int) file.GetLineCount())
1836 file.CommentLine(nIndex);
1837 }
1838 else
1839 nIndex = (int) file.GetLineCount();
1840
1841 wxString sTmp = strType + wxT(" \\");
1842 if (!delete_index)
1843 file.InsertLine(sTmp, nIndex);
1844
1845 if ( ! m_aDescriptions.Item(index).empty() )
1846 {
1847 sTmp = wxT("desc=\"") + m_aDescriptions[index]+ wxT("\" \\"); //.trim ??
1848 if (!delete_index)
1849 {
1850 nIndex++;
1851 file.InsertLine(sTmp, nIndex);
1852 }
1853 }
1854
1855 wxString sExts = m_aExtensions.Item(index);
1856 sTmp = wxT("exts=\"") + sExts.Trim(false).Trim() + wxT("\"");
1857 if (!delete_index)
1858 {
1859 nIndex++;
1860 file.InsertLine(sTmp, nIndex);
1861 }
1862
1863 bTemp = file.Write();
1864 file.Close();
1865 }
1866
1867 return bTemp;
1868 }
1869
WriteToMailCap(int index,bool delete_index)1870 bool wxMimeTypesManagerImpl::WriteToMailCap(int index, bool delete_index)
1871 {
1872 //check we have the right managers
1873 if ( !( ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE) ||
1874 ( m_mailcapStylesInited & wxMAILCAP_STANDARD) ) )
1875 return false;
1876
1877 bool bTemp = false;
1878 wxString strHome = wxGetenv(wxT("HOME"));
1879
1880 // and now the users mailcap
1881 wxString strUserMailcap = strHome + wxT("/.mailcap");
1882
1883 wxMimeTextFile file;
1884 if ( wxFile::Exists(strUserMailcap) )
1885 {
1886 bTemp = file.Open(strUserMailcap);
1887 }
1888 else
1889 {
1890 if (delete_index)
1891 return false;
1892
1893 bTemp = file.Create(strUserMailcap);
1894 }
1895
1896 if (bTemp)
1897 {
1898 // now got a file we can write to ....
1899 wxMimeTypeCommands * entries = m_aEntries[index];
1900 size_t iOpen;
1901 wxString sCmd = entries->GetCommandForVerb(wxT("open"), &iOpen);
1902 wxString sTmp;
1903
1904 sTmp = m_aTypes[index];
1905 wxString sOld;
1906 int nIndex = file.pIndexOf(sTmp);
1907
1908 // get rid of all the unwanted entries...
1909 if (nIndex == wxNOT_FOUND)
1910 {
1911 nIndex = (int) file.GetLineCount();
1912 }
1913 else
1914 {
1915 sOld = file[nIndex];
1916 wxLogTrace(TRACE_MIME, wxT("--- Deleting from mailcap line '%d' ---"), nIndex);
1917
1918 while ( (sOld.Contains(wxT("\\"))) && (nIndex < (int) file.GetLineCount()) )
1919 {
1920 file.CommentLine(nIndex);
1921 if (nIndex < (int) file.GetLineCount())
1922 sOld = sOld + file[nIndex];
1923 }
1924
1925 if (nIndex < (int)
1926 file.GetLineCount()) file.CommentLine(nIndex);
1927 }
1928
1929 sTmp += wxT(";") + sCmd; //includes wxT(" %s ");
1930
1931 // write it in the format that Netscape uses (default)
1932 if (! ( m_mailcapStylesInited & wxMAILCAP_STANDARD ) )
1933 {
1934 if (! delete_index)
1935 file.InsertLine(sTmp, nIndex);
1936 nIndex++;
1937 }
1938 else
1939 {
1940 // write extended format
1941
1942 // TODO - FIX this code:
1943 // ii) lost entries
1944 // sOld holds all the entries, but our data store only has some
1945 // eg test= is not stored
1946
1947 // so far we have written the mimetype and command out
1948 wxStringTokenizer sT(sOld, wxT(";\\"));
1949 if (sT.CountTokens() > 2)
1950 {
1951 // first one mimetype; second one command, rest unknown...
1952 wxString s;
1953 s = sT.GetNextToken();
1954 s = sT.GetNextToken();
1955
1956 // first unknown
1957 s = sT.GetNextToken();
1958 while ( ! s.empty() )
1959 {
1960 bool bKnownToken = false;
1961 if (s.Contains(wxT("description=")))
1962 bKnownToken = true;
1963 if (s.Contains(wxT("x11-bitmap=")))
1964 bKnownToken = true;
1965
1966 size_t i;
1967 size_t nCount = entries->GetCount();
1968 for (i=0; i < nCount; i++)
1969 {
1970 if (s.Contains(entries->GetVerb(i)))
1971 bKnownToken = true;
1972 }
1973
1974 if (!bKnownToken)
1975 {
1976 sTmp += wxT("; \\");
1977 file.InsertLine(sTmp, nIndex);
1978 sTmp = s;
1979 }
1980
1981 s = sT.GetNextToken();
1982 }
1983 }
1984
1985 if (! m_aDescriptions[index].empty() )
1986 {
1987 sTmp += wxT("; \\");
1988 file.InsertLine(sTmp, nIndex);
1989 nIndex++;
1990 sTmp = wxT(" description=\"") + m_aDescriptions[index] + wxT("\"");
1991 }
1992
1993 if (! m_aIcons[index].empty() )
1994 {
1995 sTmp += wxT("; \\");
1996 file.InsertLine(sTmp, nIndex);
1997 nIndex++;
1998 sTmp = wxT(" x11-bitmap=\"") + m_aIcons[index] + wxT("\"");
1999 }
2000
2001 if ( entries->GetCount() > 1 )
2002 {
2003 size_t i;
2004 for (i=0; i < entries->GetCount(); i++)
2005 if ( i != iOpen )
2006 {
2007 sTmp += wxT("; \\");
2008 file.InsertLine(sTmp, nIndex);
2009 nIndex++;
2010 sTmp = wxT(" ") + entries->GetVerbCmd(i);
2011 }
2012 }
2013
2014 file.InsertLine(sTmp, nIndex);
2015 nIndex++;
2016 }
2017
2018 bTemp = file.Write();
2019 file.Close();
2020 }
2021
2022 return bTemp;
2023 }
2024
Associate(const wxFileTypeInfo & ftInfo)2025 wxFileType * wxMimeTypesManagerImpl::Associate(const wxFileTypeInfo& ftInfo)
2026 {
2027 InitIfNeeded();
2028
2029 wxString strType = ftInfo.GetMimeType();
2030 wxString strDesc = ftInfo.GetDescription();
2031 wxString strIcon = ftInfo.GetIconFile();
2032
2033 wxMimeTypeCommands *entry = new wxMimeTypeCommands();
2034
2035 if ( ! ftInfo.GetOpenCommand().empty())
2036 entry->Add(wxT("open=") + ftInfo.GetOpenCommand() + wxT(" %s "));
2037 if ( ! ftInfo.GetPrintCommand().empty())
2038 entry->Add(wxT("print=") + ftInfo.GetPrintCommand() + wxT(" %s "));
2039
2040 // now find where these extensions are in the data store and remove them
2041 wxArrayString sA_Exts = ftInfo.GetExtensions();
2042 wxString sExt, sExtStore;
2043 size_t i, nIndex;
2044 size_t nExtCount = sA_Exts.GetCount();
2045 for (i=0; i < nExtCount; i++)
2046 {
2047 sExt = sA_Exts.Item(i);
2048
2049 // clean up to just a space before and after
2050 sExt.Trim().Trim(false);
2051 sExt = wxT(' ') + sExt + wxT(' ');
2052 size_t nCount = m_aExtensions.GetCount();
2053 for (nIndex = 0; nIndex < nCount; nIndex++)
2054 {
2055 sExtStore = m_aExtensions.Item(nIndex);
2056 if (sExtStore.Replace(sExt, wxT(" ") ) > 0)
2057 m_aExtensions.Item(nIndex) = sExtStore;
2058 }
2059 }
2060
2061 if ( !DoAssociation(strType, strIcon, entry, sA_Exts, strDesc) )
2062 return NULL;
2063
2064 return GetFileTypeFromMimeType(strType);
2065 }
2066
DoAssociation(const wxString & strType,const wxString & strIcon,wxMimeTypeCommands * entry,const wxArrayString & strExtensions,const wxString & strDesc)2067 bool wxMimeTypesManagerImpl::DoAssociation(const wxString& strType,
2068 const wxString& strIcon,
2069 wxMimeTypeCommands *entry,
2070 const wxArrayString& strExtensions,
2071 const wxString& strDesc)
2072 {
2073 int nIndex = AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
2074
2075 if ( nIndex == wxNOT_FOUND )
2076 return false;
2077
2078 return WriteMimeInfo(nIndex, false);
2079 }
2080
WriteMimeInfo(int nIndex,bool delete_mime)2081 bool wxMimeTypesManagerImpl::WriteMimeInfo(int nIndex, bool delete_mime )
2082 {
2083 bool ok = true;
2084
2085 if ( m_mailcapStylesInited & wxMAILCAP_STANDARD )
2086 {
2087 // write in metamail format;
2088 if (WriteToMimeTypes(nIndex, delete_mime) )
2089 if ( WriteToMailCap(nIndex, delete_mime) )
2090 ok = false;
2091 }
2092
2093 if ( m_mailcapStylesInited & wxMAILCAP_NETSCAPE )
2094 {
2095 // write in netsacpe format;
2096 if (WriteToNSMimeTypes(nIndex, delete_mime) )
2097 if ( WriteToMailCap(nIndex, delete_mime) )
2098 ok = false;
2099 }
2100
2101 // Don't write GNOME files here as this is not
2102 // allowed and simply doesn't work
2103
2104 if (m_mailcapStylesInited & wxMAILCAP_KDE)
2105 {
2106 // write in KDE format;
2107 if (WriteKDEMimeFile(nIndex, delete_mime) )
2108 ok = false;
2109 }
2110
2111 return ok;
2112 }
2113
AddToMimeData(const wxString & strType,const wxString & strIcon,wxMimeTypeCommands * entry,const wxArrayString & strExtensions,const wxString & strDesc,bool replaceExisting)2114 int wxMimeTypesManagerImpl::AddToMimeData(const wxString& strType,
2115 const wxString& strIcon,
2116 wxMimeTypeCommands *entry,
2117 const wxArrayString& strExtensions,
2118 const wxString& strDesc,
2119 bool replaceExisting)
2120 {
2121 InitIfNeeded();
2122
2123 // ensure mimetype is always lower case
2124 wxString mimeType = strType.Lower();
2125
2126 // is this a known MIME type?
2127 int nIndex = m_aTypes.Index(mimeType);
2128 if ( nIndex == wxNOT_FOUND )
2129 {
2130 // new file type
2131 m_aTypes.Add(mimeType);
2132 m_aIcons.Add(strIcon);
2133 m_aEntries.Add(entry ? entry : new wxMimeTypeCommands);
2134
2135 // change nIndex so we can use it below to add the extensions
2136 m_aExtensions.Add(wxEmptyString);
2137 nIndex = m_aExtensions.size() - 1;
2138
2139 m_aDescriptions.Add(strDesc);
2140 }
2141 else // yes, we already have it
2142 {
2143 if ( replaceExisting )
2144 {
2145 // if new description change it
2146 if ( !strDesc.empty())
2147 m_aDescriptions[nIndex] = strDesc;
2148
2149 // if new icon change it
2150 if ( !strIcon.empty())
2151 m_aIcons[nIndex] = strIcon;
2152
2153 if ( entry )
2154 {
2155 delete m_aEntries[nIndex];
2156 m_aEntries[nIndex] = entry;
2157 }
2158 }
2159 else // add data we don't already have ...
2160 {
2161 // if new description add only if none
2162 if ( m_aDescriptions[nIndex].empty() )
2163 m_aDescriptions[nIndex] = strDesc;
2164
2165 // if new icon and no existing icon
2166 if ( m_aIcons[nIndex].empty() )
2167 m_aIcons[nIndex] = strIcon;
2168
2169 // add any new entries...
2170 if ( entry )
2171 {
2172 wxMimeTypeCommands *entryOld = m_aEntries[nIndex];
2173
2174 size_t count = entry->GetCount();
2175 for ( size_t i = 0; i < count; i++ )
2176 {
2177 const wxString& verb = entry->GetVerb(i);
2178 if ( !entryOld->HasVerb(verb) )
2179 {
2180 entryOld->AddOrReplaceVerb(verb, entry->GetCmd(i));
2181 }
2182 }
2183
2184 // as we don't store it anywhere, it won't be deleted later as
2185 // usual -- do it immediately instead
2186 delete entry;
2187 }
2188 }
2189 }
2190
2191 // always add the extensions to this mimetype
2192 wxString& exts = m_aExtensions[nIndex];
2193
2194 // add all extensions we don't have yet
2195 wxString ext;
2196 size_t count = strExtensions.GetCount();
2197 for ( size_t i = 0; i < count; i++ )
2198 {
2199 ext = strExtensions[i];
2200 ext += wxT(' ');
2201
2202 if ( exts.Find(ext) == wxNOT_FOUND )
2203 {
2204 exts += ext;
2205 }
2206 }
2207
2208 // check data integrity
2209 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2210 m_aTypes.Count() == m_aExtensions.Count() &&
2211 m_aTypes.Count() == m_aIcons.Count() &&
2212 m_aTypes.Count() == m_aDescriptions.Count() );
2213
2214 return nIndex;
2215 }
2216
GetFileTypeFromExtension(const wxString & ext)2217 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromExtension(const wxString& ext)
2218 {
2219 if (ext.empty() )
2220 return NULL;
2221
2222 InitIfNeeded();
2223
2224 size_t count = m_aExtensions.GetCount();
2225 for ( size_t n = 0; n < count; n++ )
2226 {
2227 wxStringTokenizer tk(m_aExtensions[n], wxT(' '));
2228
2229 while ( tk.HasMoreTokens() )
2230 {
2231 // consider extensions as not being case-sensitive
2232 if ( tk.GetNextToken().IsSameAs(ext, false /* no case */) )
2233 {
2234 // found
2235 wxFileType *fileType = new wxFileType;
2236 fileType->m_impl->Init(this, n);
2237
2238 return fileType;
2239 }
2240 }
2241 }
2242
2243 return NULL;
2244 }
2245
GetFileTypeFromMimeType(const wxString & mimeType)2246 wxFileType * wxMimeTypesManagerImpl::GetFileTypeFromMimeType(const wxString& mimeType)
2247 {
2248 InitIfNeeded();
2249
2250 wxFileType * fileType = NULL;
2251 // mime types are not case-sensitive
2252 wxString mimetype(mimeType);
2253 mimetype.MakeLower();
2254
2255 // first look for an exact match
2256 int index = m_aTypes.Index(mimetype);
2257 if ( index != wxNOT_FOUND )
2258 {
2259 fileType = new wxFileType;
2260 fileType->m_impl->Init(this, index);
2261 }
2262
2263 // then try to find "text/*" as match for "text/plain" (for example)
2264 // NB: if mimeType doesn't contain '/' at all, BeforeFirst() will return
2265 // the whole string - ok.
2266
2267 index = wxNOT_FOUND;
2268 wxString strCategory = mimetype.BeforeFirst(wxT('/'));
2269
2270 size_t nCount = m_aTypes.Count();
2271 for ( size_t n = 0; n < nCount; n++ )
2272 {
2273 if ( (m_aTypes[n].BeforeFirst(wxT('/')) == strCategory ) &&
2274 m_aTypes[n].AfterFirst(wxT('/')) == wxT("*") )
2275 {
2276 index = n;
2277 break;
2278 }
2279 }
2280
2281 if ( index != wxNOT_FOUND )
2282 {
2283 // don't throw away fileType that was already found
2284 if (!fileType)
2285 fileType = new wxFileType;
2286 fileType->m_impl->Init(this, index);
2287 }
2288
2289 return fileType;
2290 }
2291
GetCommand(const wxString & verb,size_t nIndex) const2292 wxString wxMimeTypesManagerImpl::GetCommand(const wxString & verb, size_t nIndex) const
2293 {
2294 wxString command, testcmd, sV, sTmp;
2295 sV = verb + wxT("=");
2296
2297 // list of verb = command pairs for this mimetype
2298 wxMimeTypeCommands * sPairs = m_aEntries [nIndex];
2299
2300 size_t i;
2301 size_t nCount = sPairs->GetCount();
2302 for ( i = 0; i < nCount; i++ )
2303 {
2304 sTmp = sPairs->GetVerbCmd (i);
2305 if ( sTmp.Contains(sV) )
2306 command = sTmp.AfterFirst(wxT('='));
2307 }
2308
2309 return command;
2310 }
2311
AddFallback(const wxFileTypeInfo & filetype)2312 void wxMimeTypesManagerImpl::AddFallback(const wxFileTypeInfo& filetype)
2313 {
2314 InitIfNeeded();
2315
2316 wxString extensions;
2317 const wxArrayString& exts = filetype.GetExtensions();
2318 size_t nExts = exts.GetCount();
2319 for ( size_t nExt = 0; nExt < nExts; nExt++ )
2320 {
2321 if ( nExt > 0 )
2322 extensions += wxT(' ');
2323
2324 extensions += exts[nExt];
2325 }
2326
2327 AddMimeTypeInfo(filetype.GetMimeType(),
2328 extensions,
2329 filetype.GetDescription());
2330
2331 AddMailcapInfo(filetype.GetMimeType(),
2332 filetype.GetOpenCommand(),
2333 filetype.GetPrintCommand(),
2334 wxT(""),
2335 filetype.GetDescription());
2336 }
2337
AddMimeTypeInfo(const wxString & strMimeType,const wxString & strExtensions,const wxString & strDesc)2338 void wxMimeTypesManagerImpl::AddMimeTypeInfo(const wxString& strMimeType,
2339 const wxString& strExtensions,
2340 const wxString& strDesc)
2341 {
2342 // reading mailcap may find image/* , while
2343 // reading mime.types finds image/gif and no match is made
2344 // this means all the get functions don't work fix this
2345 wxString strIcon;
2346 wxString sTmp = strExtensions;
2347
2348 wxArrayString sExts;
2349 sTmp.Trim().Trim(false);
2350
2351 while (!sTmp.empty())
2352 {
2353 sExts.Add(sTmp.AfterLast(wxT(' ')));
2354 sTmp = sTmp.BeforeLast(wxT(' '));
2355 }
2356
2357 AddToMimeData(strMimeType, strIcon, NULL, sExts, strDesc, true);
2358 }
2359
AddMailcapInfo(const wxString & strType,const wxString & strOpenCmd,const wxString & strPrintCmd,const wxString & strTest,const wxString & strDesc)2360 void wxMimeTypesManagerImpl::AddMailcapInfo(const wxString& strType,
2361 const wxString& strOpenCmd,
2362 const wxString& strPrintCmd,
2363 const wxString& strTest,
2364 const wxString& strDesc)
2365 {
2366 InitIfNeeded();
2367
2368 wxMimeTypeCommands *entry = new wxMimeTypeCommands;
2369 entry->Add(wxT("open=") + strOpenCmd);
2370 entry->Add(wxT("print=") + strPrintCmd);
2371 entry->Add(wxT("test=") + strTest);
2372
2373 wxString strIcon;
2374 wxArrayString strExtensions;
2375
2376 AddToMimeData(strType, strIcon, entry, strExtensions, strDesc, true);
2377 }
2378
ReadMimeTypes(const wxString & strFileName)2379 bool wxMimeTypesManagerImpl::ReadMimeTypes(const wxString& strFileName)
2380 {
2381 wxLogTrace(TRACE_MIME, wxT("--- Parsing mime.types file '%s' ---"),
2382 strFileName.c_str());
2383
2384 wxMimeTextFile file(strFileName);
2385 if ( !file.Open() )
2386 return false;
2387
2388 // the information we extract
2389 wxString strMimeType, strDesc, strExtensions;
2390
2391 size_t nLineCount = file.GetLineCount();
2392 const wxChar *pc = NULL;
2393 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2394 {
2395 if ( pc == NULL )
2396 {
2397 // now we're at the start of the line
2398 pc = file[nLine].c_str();
2399 }
2400 else
2401 {
2402 // we didn't finish with the previous line yet
2403 nLine--;
2404 }
2405
2406 // skip whitespace
2407 while ( wxIsspace(*pc) )
2408 pc++;
2409
2410 // comment or blank line?
2411 if ( *pc == wxT('#') || !*pc )
2412 {
2413 // skip the whole line
2414 pc = NULL;
2415 continue;
2416 }
2417
2418 // detect file format
2419 const wxChar *pEqualSign = wxStrchr(pc, wxT('='));
2420 if ( pEqualSign == NULL )
2421 {
2422 // brief format
2423 // ------------
2424
2425 // first field is mime type
2426 for ( strMimeType.Empty(); !wxIsspace(*pc) && *pc != wxT('\0'); pc++ )
2427 {
2428 strMimeType += *pc;
2429 }
2430
2431 // skip whitespace
2432 while ( wxIsspace(*pc) )
2433 pc++;
2434
2435 // take all the rest of the string
2436 strExtensions = pc;
2437
2438 // no description...
2439 strDesc.Empty();
2440 }
2441 else
2442 {
2443 // expanded format
2444 // ---------------
2445
2446 // the string on the left of '=' is the field name
2447 wxString strLHS(pc, pEqualSign - pc);
2448
2449 // eat whitespace
2450 for ( pc = pEqualSign + 1; wxIsspace(*pc); pc++ )
2451 ;
2452
2453 const wxChar *pEnd;
2454 if ( *pc == wxT('"') )
2455 {
2456 // the string is quoted and ends at the matching quote
2457 pEnd = wxStrchr(++pc, wxT('"'));
2458 if ( pEnd == NULL )
2459 {
2460 wxLogWarning(wxT("Mime.types file %s, line %lu: unterminated quoted string."),
2461 strFileName.c_str(), nLine + 1L);
2462 }
2463 }
2464 else
2465 {
2466 // unquoted string ends at the first space or at the end of
2467 // line
2468 for ( pEnd = pc; *pEnd && !wxIsspace(*pEnd); pEnd++ )
2469 ;
2470 }
2471
2472 // now we have the RHS (field value)
2473 wxString strRHS(pc, pEnd - pc);
2474
2475 // check what follows this entry
2476 if ( *pEnd == wxT('"') )
2477 {
2478 // skip this quote
2479 pEnd++;
2480 }
2481
2482 for ( pc = pEnd; wxIsspace(*pc); pc++ )
2483 ;
2484
2485 // if there is something left, it may be either a '\\' to continue
2486 // the line or the next field of the same entry
2487 bool entryEnded = *pc == wxT('\0');
2488 bool nextFieldOnSameLine = false;
2489 if ( !entryEnded )
2490 {
2491 nextFieldOnSameLine = ((*pc != wxT('\\')) || (pc[1] != wxT('\0')));
2492 }
2493
2494 // now see what we got
2495 if ( strLHS == wxT("type") )
2496 {
2497 strMimeType = strRHS;
2498 }
2499 else if ( strLHS.StartsWith(wxT("desc")) )
2500 {
2501 strDesc = strRHS;
2502 }
2503 else if ( strLHS == wxT("exts") )
2504 {
2505 strExtensions = strRHS;
2506 }
2507 else if ( strLHS == wxT("icon") )
2508 {
2509 // this one is simply ignored: it usually refers to Netscape
2510 // built in icons which are useless for us anyhow
2511 }
2512 else if ( !strLHS.StartsWith(wxT("x-")) )
2513 {
2514 // we suppose that all fields starting with "X-" are
2515 // unregistered extensions according to the standard practice,
2516 // but it may be worth telling the user about other junk in
2517 // his mime.types file
2518 wxLogWarning(wxT("Unknown field in file %s, line %lu: '%s'."),
2519 strFileName.c_str(), nLine + 1L, strLHS.c_str());
2520 }
2521
2522 if ( !entryEnded )
2523 {
2524 if ( !nextFieldOnSameLine )
2525 pc = NULL;
2526 //else: don't reset it
2527
2528 // as we don't reset strMimeType, the next field in this entry
2529 // will be interpreted correctly.
2530
2531 continue;
2532 }
2533 }
2534
2535 // depending on the format (Mosaic or Netscape) either space or comma
2536 // is used to separate the extensions
2537 strExtensions.Replace(wxT(","), wxT(" "));
2538
2539 // also deal with the leading dot
2540 if ( !strExtensions.empty() && strExtensions[0u] == wxT('.') )
2541 {
2542 strExtensions.erase(0, 1);
2543 }
2544
2545 wxLogTrace(TRACE_MIME, wxT("mime.types: '%s' => '%s' (%s)"),
2546 strExtensions.c_str(),
2547 strMimeType.c_str(),
2548 strDesc.c_str());
2549
2550 AddMimeTypeInfo(strMimeType, strExtensions, strDesc);
2551
2552 // finished with this line
2553 pc = NULL;
2554 }
2555
2556 return true;
2557 }
2558
2559 // ----------------------------------------------------------------------------
2560 // UNIX mailcap files parsing
2561 // ----------------------------------------------------------------------------
2562
2563 // the data for a single MIME type
2564 struct MailcapLineData
2565 {
2566 // field values
2567 wxString type,
2568 cmdOpen,
2569 test,
2570 icon,
2571 desc;
2572
2573 wxArrayString verbs,
2574 commands;
2575
2576 // flags
2577 bool testfailed,
2578 needsterminal,
2579 copiousoutput;
2580
MailcapLineDataMailcapLineData2581 MailcapLineData() { testfailed = needsterminal = copiousoutput = false; }
2582 };
2583
2584 // process a non-standard (i.e. not the first or second one) mailcap field
2585 bool
ProcessOtherMailcapField(MailcapLineData & data,const wxString & curField)2586 wxMimeTypesManagerImpl::ProcessOtherMailcapField(MailcapLineData& data,
2587 const wxString& curField)
2588 {
2589 if ( curField.empty() )
2590 {
2591 // we don't care
2592 return true;
2593 }
2594
2595 // is this something of the form foo=bar?
2596 const wxChar *pEq = wxStrchr(curField, wxT('='));
2597 if ( pEq != NULL )
2598 {
2599 // split "LHS = RHS" in 2
2600 wxString lhs = curField.BeforeFirst(wxT('=')),
2601 rhs = curField.AfterFirst(wxT('='));
2602
2603 lhs.Trim(true); // from right
2604 rhs.Trim(false); // from left
2605
2606 // it might be quoted
2607 if ( !rhs.empty() && rhs[0u] == wxT('"') && rhs.Last() == wxT('"') )
2608 {
2609 rhs = rhs.Mid(1, rhs.length() - 2);
2610 }
2611
2612 // is it a command verb or something else?
2613 if ( lhs == wxT("test") )
2614 {
2615 if ( wxSystem(rhs) == 0 )
2616 {
2617 // ok, test passed
2618 wxLogTrace(TRACE_MIME_TEST,
2619 wxT("Test '%s' for mime type '%s' succeeded."),
2620 rhs.c_str(), data.type.c_str());
2621 }
2622 else
2623 {
2624 wxLogTrace(TRACE_MIME_TEST,
2625 wxT("Test '%s' for mime type '%s' failed, skipping."),
2626 rhs.c_str(), data.type.c_str());
2627
2628 data.testfailed = true;
2629 }
2630 }
2631 else if ( lhs == wxT("desc") )
2632 {
2633 data.desc = rhs;
2634 }
2635 else if ( lhs == wxT("x11-bitmap") )
2636 {
2637 data.icon = rhs;
2638 }
2639 else if ( lhs == wxT("notes") )
2640 {
2641 // ignore
2642 }
2643 else // not a (recognized) special case, must be a verb (e.g. "print")
2644 {
2645 data.verbs.Add(lhs);
2646 data.commands.Add(rhs);
2647 }
2648 }
2649 else // '=' not found
2650 {
2651 // so it must be a simple flag
2652 if ( curField == wxT("needsterminal") )
2653 {
2654 data.needsterminal = true;
2655 }
2656 else if ( curField == wxT("copiousoutput"))
2657 {
2658 // copiousoutput impies that the viewer is a console program
2659 data.needsterminal =
2660 data.copiousoutput = true;
2661 }
2662 else if ( !IsKnownUnimportantField(curField) )
2663 {
2664 return false;
2665 }
2666 }
2667
2668 return true;
2669 }
2670
ReadMailcap(const wxString & strFileName,bool fallback)2671 bool wxMimeTypesManagerImpl::ReadMailcap(const wxString& strFileName,
2672 bool fallback)
2673 {
2674 wxLogTrace(TRACE_MIME, wxT("--- Parsing mailcap file '%s' ---"),
2675 strFileName.c_str());
2676
2677 wxMimeTextFile file(strFileName);
2678 if ( !file.Open() )
2679 return false;
2680
2681 // indices of MIME types (in m_aTypes) we already found in this file
2682 //
2683 // (see the comments near the end of function for the reason we need this)
2684 wxArrayInt aIndicesSeenHere;
2685
2686 // accumulator for the current field
2687 wxString curField;
2688 curField.reserve(1024);
2689
2690 const wxChar *pPagerEnv = wxGetenv(wxT("PAGER"));
2691
2692 const wxArrayString empty_extensions_list;
2693
2694 size_t nLineCount = file.GetLineCount();
2695 for ( size_t nLine = 0; nLine < nLineCount; nLine++ )
2696 {
2697 // now we're at the start of the line
2698 const wxChar *pc = file[nLine].c_str();
2699
2700 // skip whitespace
2701 while ( wxIsspace(*pc) )
2702 pc++;
2703
2704 // comment or empty string?
2705 if ( *pc == wxT('#') || *pc == wxT('\0') )
2706 continue;
2707
2708 // no, do parse
2709 // ------------
2710
2711 // what field are we currently in? The first 2 are fixed and there may
2712 // be an arbitrary number of other fields parsed by
2713 // ProcessOtherMailcapField()
2714 //
2715 // the first field is the MIME type
2716 enum
2717 {
2718 Field_Type,
2719 Field_OpenCmd,
2720 Field_Other
2721 }
2722 currentToken = Field_Type;
2723
2724 // the flags and field values on the current line
2725 MailcapLineData data;
2726
2727 bool cont = true;
2728 while ( cont )
2729 {
2730 switch ( *pc )
2731 {
2732 case wxT('\\'):
2733 // interpret the next character literally (notice that
2734 // backslash can be used for line continuation)
2735 if ( *++pc == wxT('\0') )
2736 {
2737 // fetch the next line if there is one
2738 if ( nLine == nLineCount - 1 )
2739 {
2740 // something is wrong, bail out
2741 cont = false;
2742
2743 wxLogDebug(wxT("Mailcap file %s, line %lu: '\\' on the end of the last line ignored."),
2744 strFileName.c_str(),
2745 nLine + 1L);
2746 }
2747 else
2748 {
2749 // pass to the beginning of the next line
2750 pc = file[++nLine].c_str();
2751
2752 // skip pc++ at the end of the loop
2753 continue;
2754 }
2755 }
2756 else
2757 {
2758 // just a normal character
2759 curField += *pc;
2760 }
2761 break;
2762
2763 case wxT('\0'):
2764 cont = false; // end of line reached, exit the loop
2765
2766 // fall through to still process this field
2767
2768 case wxT(';'):
2769 // trim whitespaces from both sides
2770 curField.Trim(true).Trim(false);
2771
2772 switch ( currentToken )
2773 {
2774 case Field_Type:
2775 data.type = curField.Lower();
2776 if ( data.type.empty() )
2777 {
2778 // I don't think that this is a valid mailcap
2779 // entry, but try to interpret it somehow
2780 data.type = wxT('*');
2781 }
2782
2783 if ( data.type.Find(wxT('/')) == wxNOT_FOUND )
2784 {
2785 // we interpret "type" as "type/*"
2786 data.type += wxT("/*");
2787 }
2788
2789 currentToken = Field_OpenCmd;
2790 break;
2791
2792 case Field_OpenCmd:
2793 data.cmdOpen = curField;
2794
2795 currentToken = Field_Other;
2796 break;
2797
2798 case Field_Other:
2799 if ( !ProcessOtherMailcapField(data, curField) )
2800 {
2801 // don't flood the user with error messages if
2802 // we don't understand something in his
2803 // mailcap, but give them in debug mode because
2804 // this might be useful for the programmer
2805 wxLogDebug
2806 (
2807 wxT("Mailcap file %s, line %lu: unknown field '%s' for the MIME type '%s' ignored."),
2808 strFileName.c_str(),
2809 nLine + 1L,
2810 curField.c_str(),
2811 data.type.c_str()
2812 );
2813 }
2814 else if ( data.testfailed )
2815 {
2816 // skip this entry entirely
2817 cont = false;
2818 }
2819
2820 // it already has this value
2821 //currentToken = Field_Other;
2822 break;
2823
2824 default:
2825 wxFAIL_MSG(wxT("unknown field type in mailcap"));
2826 }
2827
2828 // next token starts immediately after ';'
2829 curField.Empty();
2830 break;
2831
2832 default:
2833 curField += *pc;
2834 }
2835
2836 // continue in the same line
2837 pc++;
2838 }
2839
2840 // we read the entire entry, check what have we got
2841 // ------------------------------------------------
2842
2843 // check that we really read something reasonable
2844 if ( currentToken < Field_Other )
2845 {
2846 wxLogWarning(wxT("Mailcap file %s, line %lu: incomplete entry ignored."),
2847 strFileName.c_str(), nLine + 1L);
2848
2849 continue;
2850 }
2851
2852 // if the test command failed, it's as if the entry were not there at all
2853 if ( data.testfailed )
2854 {
2855 continue;
2856 }
2857
2858 // support for flags:
2859 // 1. create an xterm for 'needsterminal'
2860 // 2. append "| $PAGER" for 'copiousoutput'
2861 //
2862 // Note that the RFC says that having both needsterminal and
2863 // copiousoutput is probably a mistake, so it seems that running
2864 // programs with copiousoutput inside an xterm as it is done now
2865 // is a bad idea (FIXME)
2866 if ( data.copiousoutput )
2867 {
2868 data.cmdOpen << wxT(" | ") << (pPagerEnv ? pPagerEnv : wxT("more"));
2869 }
2870
2871 if ( data.needsterminal )
2872 {
2873 data.cmdOpen.insert(0, wxT("xterm -e sh -c '"));
2874 data.cmdOpen.append(wxT("'"));
2875 }
2876
2877 if ( !data.cmdOpen.empty() )
2878 {
2879 data.verbs.Insert(wxT("open"), 0);
2880 data.commands.Insert(data.cmdOpen, 0);
2881 }
2882
2883 // we have to decide whether the new entry should replace any entries
2884 // for the same MIME type we had previously found or not
2885 bool overwrite;
2886
2887 // the fall back entries have the lowest priority, by definition
2888 if ( fallback )
2889 {
2890 overwrite = false;
2891 }
2892 else
2893 {
2894 // have we seen this one before?
2895 int nIndex = m_aTypes.Index(data.type);
2896
2897 // and if we have, was it in this file? if not, we should
2898 // overwrite the previously seen one
2899 overwrite = nIndex == wxNOT_FOUND ||
2900 aIndicesSeenHere.Index(nIndex) == wxNOT_FOUND;
2901 }
2902
2903 wxLogTrace(TRACE_MIME, wxT("mailcap %s: %s [%s]"),
2904 data.type.c_str(), data.cmdOpen.c_str(),
2905 overwrite ? wxT("replace") : wxT("add"));
2906
2907 int n = AddToMimeData
2908 (
2909 data.type,
2910 data.icon,
2911 new wxMimeTypeCommands(data.verbs, data.commands),
2912 empty_extensions_list,
2913 data.desc,
2914 overwrite
2915 );
2916
2917 if ( overwrite )
2918 {
2919 aIndicesSeenHere.Add(n);
2920 }
2921 }
2922
2923 return true;
2924 }
2925
EnumAllFileTypes(wxArrayString & mimetypes)2926 size_t wxMimeTypesManagerImpl::EnumAllFileTypes(wxArrayString& mimetypes)
2927 {
2928 InitIfNeeded();
2929
2930 mimetypes.Empty();
2931
2932 size_t count = m_aTypes.GetCount();
2933 for ( size_t n = 0; n < count; n++ )
2934 {
2935 // don't return template types from here (i.e. anything containg '*')
2936 const wxString &type = m_aTypes[n];
2937 if ( type.Find(wxT('*')) == wxNOT_FOUND )
2938 {
2939 mimetypes.Add(type);
2940 }
2941 }
2942
2943 return mimetypes.GetCount();
2944 }
2945
2946 // ----------------------------------------------------------------------------
2947 // writing to MIME type files
2948 // ----------------------------------------------------------------------------
2949
Unassociate(wxFileType * ft)2950 bool wxMimeTypesManagerImpl::Unassociate(wxFileType *ft)
2951 {
2952 InitIfNeeded();
2953
2954 wxArrayString sMimeTypes;
2955 ft->GetMimeTypes(sMimeTypes);
2956
2957 size_t i;
2958 size_t nCount = sMimeTypes.GetCount();
2959 for (i = 0; i < nCount; i ++)
2960 {
2961 const wxString &sMime = sMimeTypes.Item(i);
2962 int nIndex = m_aTypes.Index(sMime);
2963 if ( nIndex == wxNOT_FOUND)
2964 {
2965 // error if we get here ??
2966 return false;
2967 }
2968 else
2969 {
2970 WriteMimeInfo(nIndex, true);
2971 m_aTypes.RemoveAt(nIndex);
2972 m_aEntries.RemoveAt(nIndex);
2973 m_aExtensions.RemoveAt(nIndex);
2974 m_aDescriptions.RemoveAt(nIndex);
2975 m_aIcons.RemoveAt(nIndex);
2976 }
2977 }
2978 // check data integrity
2979 wxASSERT( m_aTypes.Count() == m_aEntries.Count() &&
2980 m_aTypes.Count() == m_aExtensions.Count() &&
2981 m_aTypes.Count() == m_aIcons.Count() &&
2982 m_aTypes.Count() == m_aDescriptions.Count() );
2983
2984 return true;
2985 }
2986
2987 // ----------------------------------------------------------------------------
2988 // private functions
2989 // ----------------------------------------------------------------------------
2990
IsKnownUnimportantField(const wxString & fieldAll)2991 static bool IsKnownUnimportantField(const wxString& fieldAll)
2992 {
2993 static const wxChar * const knownFields[] =
2994 {
2995 wxT("x-mozilla-flags"),
2996 wxT("nametemplate"),
2997 wxT("textualnewlines"),
2998 };
2999
3000 wxString field = fieldAll.BeforeFirst(wxT('='));
3001 for ( size_t n = 0; n < WXSIZEOF(knownFields); n++ )
3002 {
3003 if ( field.CmpNoCase(knownFields[n]) == 0 )
3004 return true;
3005 }
3006
3007 return false;
3008 }
3009
3010 #endif
3011 // wxUSE_MIMETYPE && wxUSE_FILE && wxUSE_TEXTFILE
3012