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