1 /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 
10 #include "nsOSHelperAppService.h"
11 #include "nsMIMEInfoUnix.h"
12 #ifdef MOZ_WIDGET_GTK
13 #  include "nsGNOMERegistry.h"
14 #endif
15 #include "nsISupports.h"
16 #include "nsString.h"
17 #include "nsReadableUtils.h"
18 #include "nsUnicharUtils.h"
19 #include "nsIFileStreams.h"
20 #include "nsILineInputStream.h"
21 #include "nsIFile.h"
22 #include "nsIProcess.h"
23 #include "nsNetCID.h"
24 #include "nsXPCOM.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsCRT.h"
27 #include "nsDirectoryServiceDefs.h"
28 #include "nsDirectoryServiceUtils.h"
29 #include "ContentHandlerService.h"
30 #include "prenv.h"  // for PR_GetEnv()
31 #include "mozilla/Preferences.h"
32 #include "nsMimeTypes.h"
33 
34 using namespace mozilla;
35 
36 #define LOG(args) MOZ_LOG(mLog, mozilla::LogLevel::Debug, args)
37 #define LOG_ENABLED() MOZ_LOG_TEST(mLog, mozilla::LogLevel::Debug)
38 
39 static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
40                               const nsAString::const_iterator& aEnd_iter);
41 static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter,
42                               nsAString::const_iterator& aMajorTypeStart,
43                               nsAString::const_iterator& aMajorTypeEnd,
44                               nsAString::const_iterator& aMinorTypeStart,
45                               nsAString::const_iterator& aMinorTypeEnd,
46                               const nsAString::const_iterator& aEnd_iter);
47 
48 inline bool IsNetscapeFormat(const nsACString& aBuffer);
49 
~nsOSHelperAppService()50 nsOSHelperAppService::~nsOSHelperAppService() {}
51 
52 /*
53  * Take a command with all the mailcap escapes in it and unescape it
54  * Ideally this needs the mime type, mime type options, and location of the
55  * temporary file, but this last can't be got from here
56  */
57 // static
UnescapeCommand(const nsAString & aEscapedCommand,const nsAString & aMajorType,const nsAString & aMinorType,nsACString & aUnEscapedCommand)58 nsresult nsOSHelperAppService::UnescapeCommand(const nsAString& aEscapedCommand,
59                                                const nsAString& aMajorType,
60                                                const nsAString& aMinorType,
61                                                nsACString& aUnEscapedCommand) {
62   LOG(("-- UnescapeCommand"));
63   LOG(("Command to escape: '%s'\n",
64        NS_LossyConvertUTF16toASCII(aEscapedCommand).get()));
65   //  XXX This function will need to get the mime type and various stuff like
66   //  that being passed in to work properly
67 
68   LOG(
69       ("UnescapeCommand really needs some work -- it should actually do some "
70        "unescaping\n"));
71 
72   CopyUTF16toUTF8(aEscapedCommand, aUnEscapedCommand);
73   LOG(("Escaped command: '%s'\n", PromiseFlatCString(aUnEscapedCommand).get()));
74   return NS_OK;
75 }
76 
77 /* Put aSemicolon_iter at the first non-escaped semicolon after
78  * aStart_iter but before aEnd_iter
79  */
80 
FindSemicolon(nsAString::const_iterator & aSemicolon_iter,const nsAString::const_iterator & aEnd_iter)81 static nsresult FindSemicolon(nsAString::const_iterator& aSemicolon_iter,
82                               const nsAString::const_iterator& aEnd_iter) {
83   bool semicolonFound = false;
84   while (aSemicolon_iter != aEnd_iter && !semicolonFound) {
85     switch (*aSemicolon_iter) {
86       case '\\':
87         aSemicolon_iter.advance(2);
88         break;
89       case ';':
90         semicolonFound = true;
91         break;
92       default:
93         ++aSemicolon_iter;
94         break;
95     }
96   }
97   return NS_OK;
98 }
99 
ParseMIMEType(const nsAString::const_iterator & aStart_iter,nsAString::const_iterator & aMajorTypeStart,nsAString::const_iterator & aMajorTypeEnd,nsAString::const_iterator & aMinorTypeStart,nsAString::const_iterator & aMinorTypeEnd,const nsAString::const_iterator & aEnd_iter)100 static nsresult ParseMIMEType(const nsAString::const_iterator& aStart_iter,
101                               nsAString::const_iterator& aMajorTypeStart,
102                               nsAString::const_iterator& aMajorTypeEnd,
103                               nsAString::const_iterator& aMinorTypeStart,
104                               nsAString::const_iterator& aMinorTypeEnd,
105                               const nsAString::const_iterator& aEnd_iter) {
106   nsAString::const_iterator iter(aStart_iter);
107 
108   // skip leading whitespace
109   while (iter != aEnd_iter && nsCRT::IsAsciiSpace(*iter)) {
110     ++iter;
111   }
112 
113   if (iter == aEnd_iter) {
114     return NS_ERROR_INVALID_ARG;
115   }
116 
117   aMajorTypeStart = iter;
118 
119   // find major/minor separator ('/')
120   while (iter != aEnd_iter && *iter != '/') {
121     ++iter;
122   }
123 
124   if (iter == aEnd_iter) {
125     return NS_ERROR_INVALID_ARG;
126   }
127 
128   aMajorTypeEnd = iter;
129 
130   // skip '/'
131   ++iter;
132 
133   if (iter == aEnd_iter) {
134     return NS_ERROR_INVALID_ARG;
135   }
136 
137   aMinorTypeStart = iter;
138 
139   // find end of minor type, delimited by whitespace or ';'
140   while (iter != aEnd_iter && !nsCRT::IsAsciiSpace(*iter) && *iter != ';') {
141     ++iter;
142   }
143 
144   aMinorTypeEnd = iter;
145 
146   return NS_OK;
147 }
148 
149 // static
GetFileLocation(const char * aPrefName,const char * aEnvVarName,nsAString & aFileLocation)150 nsresult nsOSHelperAppService::GetFileLocation(const char* aPrefName,
151                                                const char* aEnvVarName,
152                                                nsAString& aFileLocation) {
153   LOG(("-- GetFileLocation.  Pref: '%s'  EnvVar: '%s'\n", aPrefName,
154        aEnvVarName));
155   MOZ_ASSERT(aPrefName, "Null pref name passed; don't do that!");
156 
157   aFileLocation.Truncate();
158   /* The lookup order is:
159      1) user pref
160      2) env var
161      3) pref
162   */
163   NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE);
164 
165   /*
166     If we have an env var we should check whether the pref is a user
167     pref.  If we do not, we don't care.
168   */
169   if (Preferences::HasUserValue(aPrefName) &&
170       NS_SUCCEEDED(Preferences::GetString(aPrefName, aFileLocation))) {
171     return NS_OK;
172   }
173 
174   if (aEnvVarName && *aEnvVarName) {
175     char* prefValue = PR_GetEnv(aEnvVarName);
176     if (prefValue && *prefValue) {
177       // the pref is in the system charset and it's a filepath... The
178       // natural way to do the charset conversion is by just initing
179       // an nsIFile with the native path and asking it for the Unicode
180       // version.
181       nsresult rv;
182       nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
183       NS_ENSURE_SUCCESS(rv, rv);
184 
185       rv = file->InitWithNativePath(nsDependentCString(prefValue));
186       NS_ENSURE_SUCCESS(rv, rv);
187 
188       rv = file->GetPath(aFileLocation);
189       NS_ENSURE_SUCCESS(rv, rv);
190 
191       return NS_OK;
192     }
193   }
194 
195   return Preferences::GetString(aPrefName, aFileLocation);
196 }
197 
198 /* Get the mime.types file names from prefs and look up info in them
199    based on extension */
200 // static
LookUpTypeAndDescription(const nsAString & aFileExtension,nsAString & aMajorType,nsAString & aMinorType,nsAString & aDescription,bool aUserData)201 nsresult nsOSHelperAppService::LookUpTypeAndDescription(
202     const nsAString& aFileExtension, nsAString& aMajorType,
203     nsAString& aMinorType, nsAString& aDescription, bool aUserData) {
204   LOG(("-- LookUpTypeAndDescription for extension '%s'\n",
205        NS_LossyConvertUTF16toASCII(aFileExtension).get()));
206   nsAutoString mimeFileName;
207 
208   const char* filenamePref = aUserData ? "helpers.private_mime_types_file"
209                                        : "helpers.global_mime_types_file";
210 
211   nsresult rv = GetFileLocation(filenamePref, nullptr, mimeFileName);
212   if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
213     rv = GetTypeAndDescriptionFromMimetypesFile(
214         mimeFileName, aFileExtension, aMajorType, aMinorType, aDescription);
215   } else {
216     rv = NS_ERROR_NOT_AVAILABLE;
217   }
218 
219   return rv;
220 }
221 
IsNetscapeFormat(const nsACString & aBuffer)222 inline bool IsNetscapeFormat(const nsACString& aBuffer) {
223   return StringBeginsWith(
224              aBuffer,
225              NS_LITERAL_CSTRING(
226                  "#--Netscape Communications Corporation MIME Information")) ||
227          StringBeginsWith(aBuffer,
228                           NS_LITERAL_CSTRING("#--MCOM MIME Information"));
229 }
230 
231 /*
232  * Create a file stream and line input stream for the filename.
233  * Leaves the first line of the file in aBuffer and sets the format to
234  *  true for netscape files and false for normail ones
235  */
236 // static
CreateInputStream(const nsAString & aFilename,nsIFileInputStream ** aFileInputStream,nsILineInputStream ** aLineInputStream,nsACString & aBuffer,bool * aNetscapeFormat,bool * aMore)237 nsresult nsOSHelperAppService::CreateInputStream(
238     const nsAString& aFilename, nsIFileInputStream** aFileInputStream,
239     nsILineInputStream** aLineInputStream, nsACString& aBuffer,
240     bool* aNetscapeFormat, bool* aMore) {
241   LOG(("-- CreateInputStream"));
242   nsresult rv = NS_OK;
243 
244   nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
245   if (NS_FAILED(rv)) return rv;
246   rv = file->InitWithPath(aFilename);
247   if (NS_FAILED(rv)) return rv;
248 
249   nsCOMPtr<nsIFileInputStream> fileStream(
250       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
251   if (NS_FAILED(rv)) return rv;
252   rv = fileStream->Init(file, -1, -1, false);
253   if (NS_FAILED(rv)) return rv;
254 
255   nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
256 
257   if (NS_FAILED(rv)) {
258     LOG(("Interface trouble in stream land!"));
259     return rv;
260   }
261 
262   rv = lineStream->ReadLine(aBuffer, aMore);
263   if (NS_FAILED(rv)) {
264     fileStream->Close();
265     return rv;
266   }
267 
268   *aNetscapeFormat = IsNetscapeFormat(aBuffer);
269 
270   *aFileInputStream = fileStream;
271   NS_ADDREF(*aFileInputStream);
272   *aLineInputStream = lineStream;
273   NS_ADDREF(*aLineInputStream);
274 
275   return NS_OK;
276 }
277 
278 /* Open the file, read the first line, decide what type of file it is,
279    then get info based on extension */
280 // static
GetTypeAndDescriptionFromMimetypesFile(const nsAString & aFilename,const nsAString & aFileExtension,nsAString & aMajorType,nsAString & aMinorType,nsAString & aDescription)281 nsresult nsOSHelperAppService::GetTypeAndDescriptionFromMimetypesFile(
282     const nsAString& aFilename, const nsAString& aFileExtension,
283     nsAString& aMajorType, nsAString& aMinorType, nsAString& aDescription) {
284   LOG(("-- GetTypeAndDescriptionFromMimetypesFile\n"));
285   LOG(("Getting type and description from types file '%s'\n",
286        NS_LossyConvertUTF16toASCII(aFilename).get()));
287   LOG(("Using extension '%s'\n",
288        NS_LossyConvertUTF16toASCII(aFileExtension).get()));
289   nsCOMPtr<nsIFileInputStream> mimeFile;
290   nsCOMPtr<nsILineInputStream> mimeTypes;
291   bool netscapeFormat;
292   nsAutoString buf;
293   nsAutoCString cBuf;
294   bool more = false;
295   nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
296                                   getter_AddRefs(mimeTypes), cBuf,
297                                   &netscapeFormat, &more);
298 
299   if (NS_FAILED(rv)) {
300     return rv;
301   }
302 
303   nsAutoString extensions;
304   nsAutoStringN<101> entry;
305   nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart,
306       minorTypeEnd, descriptionStart, descriptionEnd;
307 
308   do {
309     CopyASCIItoUTF16(cBuf, buf);
310     // read through, building up an entry.  If we finish an entry, check for
311     // a match and return out of the loop if we match
312 
313     // skip comments and empty lines
314     if (!buf.IsEmpty() && buf.First() != '#') {
315       entry.Append(buf);
316       if (entry.Last() == '\\') {
317         entry.Truncate(entry.Length() - 1);
318         entry.Append(char16_t(
319             ' '));  // in case there is no trailing whitespace on this line
320       } else {      // we have a full entry
321         LOG(("Current entry: '%s'\n",
322              NS_LossyConvertUTF16toASCII(entry).get()));
323         if (netscapeFormat) {
324           rv = ParseNetscapeMIMETypesEntry(
325               entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
326               extensions, descriptionStart, descriptionEnd);
327           if (NS_FAILED(rv)) {
328             // We sometimes get things like RealPlayer appending
329             // "normal" entries to "Netscape" .mime.types files.  Try
330             // to handle that.  Bug 106381.
331             LOG(("Bogus entry; trying 'normal' mode\n"));
332             rv = ParseNormalMIMETypesEntry(
333                 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
334                 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
335           }
336         } else {
337           rv = ParseNormalMIMETypesEntry(
338               entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
339               extensions, descriptionStart, descriptionEnd);
340           if (NS_FAILED(rv)) {
341             // We sometimes get things like StarOffice prepending
342             // "normal" entries to "Netscape" .mime.types files.  Try
343             // to handle that.  Bug 136670.
344             LOG(("Bogus entry; trying 'Netscape' mode\n"));
345             rv = ParseNetscapeMIMETypesEntry(
346                 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
347                 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
348           }
349         }
350 
351         if (NS_SUCCEEDED(rv)) {  // entry parses
352           nsAString::const_iterator start, end;
353           extensions.BeginReading(start);
354           extensions.EndReading(end);
355           nsAString::const_iterator iter(start);
356 
357           while (start != end) {
358             FindCharInReadable(',', iter, end);
359             if (Substring(start, iter)
360                     .Equals(aFileExtension,
361                             nsCaseInsensitiveStringComparator)) {
362               // it's a match.  Assign the type and description and run
363               aMajorType.Assign(Substring(majorTypeStart, majorTypeEnd));
364               aMinorType.Assign(Substring(minorTypeStart, minorTypeEnd));
365               aDescription.Assign(Substring(descriptionStart, descriptionEnd));
366               mimeFile->Close();
367               return NS_OK;
368             }
369             if (iter != end) {
370               ++iter;
371             }
372             start = iter;
373           }
374         } else {
375           LOG(("Failed to parse entry: %s\n",
376                NS_LossyConvertUTF16toASCII(entry).get()));
377         }
378         // truncate the entry for the next iteration
379         entry.Truncate();
380       }
381     }
382     if (!more) {
383       rv = NS_ERROR_NOT_AVAILABLE;
384       break;
385     }
386     // read the next line
387     rv = mimeTypes->ReadLine(cBuf, &more);
388   } while (NS_SUCCEEDED(rv));
389 
390   mimeFile->Close();
391   return rv;
392 }
393 
394 /* Get the mime.types file names from prefs and look up info in them
395    based on mimetype  */
396 // static
LookUpExtensionsAndDescription(const nsAString & aMajorType,const nsAString & aMinorType,nsAString & aFileExtensions,nsAString & aDescription)397 nsresult nsOSHelperAppService::LookUpExtensionsAndDescription(
398     const nsAString& aMajorType, const nsAString& aMinorType,
399     nsAString& aFileExtensions, nsAString& aDescription) {
400   LOG(("-- LookUpExtensionsAndDescription for type '%s/%s'\n",
401        NS_LossyConvertUTF16toASCII(aMajorType).get(),
402        NS_LossyConvertUTF16toASCII(aMinorType).get()));
403   nsAutoString mimeFileName;
404 
405   nsresult rv =
406       GetFileLocation("helpers.private_mime_types_file", nullptr, mimeFileName);
407   if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
408     rv = GetExtensionsAndDescriptionFromMimetypesFile(
409         mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription);
410   } else {
411     rv = NS_ERROR_NOT_AVAILABLE;
412   }
413   if (NS_FAILED(rv) || aFileExtensions.IsEmpty()) {
414     rv = GetFileLocation("helpers.global_mime_types_file", nullptr,
415                          mimeFileName);
416     if (NS_SUCCEEDED(rv) && !mimeFileName.IsEmpty()) {
417       rv = GetExtensionsAndDescriptionFromMimetypesFile(
418           mimeFileName, aMajorType, aMinorType, aFileExtensions, aDescription);
419     } else {
420       rv = NS_ERROR_NOT_AVAILABLE;
421     }
422   }
423   return rv;
424 }
425 
426 /* Open the file, read the first line, decide what type of file it is,
427    then get info based on extension */
428 // static
GetExtensionsAndDescriptionFromMimetypesFile(const nsAString & aFilename,const nsAString & aMajorType,const nsAString & aMinorType,nsAString & aFileExtensions,nsAString & aDescription)429 nsresult nsOSHelperAppService::GetExtensionsAndDescriptionFromMimetypesFile(
430     const nsAString& aFilename, const nsAString& aMajorType,
431     const nsAString& aMinorType, nsAString& aFileExtensions,
432     nsAString& aDescription) {
433   LOG(("-- GetExtensionsAndDescriptionFromMimetypesFile\n"));
434   LOG(("Getting extensions and description from types file '%s'\n",
435        NS_LossyConvertUTF16toASCII(aFilename).get()));
436   LOG(("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(),
437        NS_LossyConvertUTF16toASCII(aMinorType).get()));
438   nsCOMPtr<nsIFileInputStream> mimeFile;
439   nsCOMPtr<nsILineInputStream> mimeTypes;
440   bool netscapeFormat;
441   nsAutoCString cBuf;
442   nsAutoString buf;
443   bool more = false;
444   nsresult rv = CreateInputStream(aFilename, getter_AddRefs(mimeFile),
445                                   getter_AddRefs(mimeTypes), cBuf,
446                                   &netscapeFormat, &more);
447   if (NS_FAILED(rv)) {
448     return rv;
449   }
450 
451   nsAutoString extensions;
452   nsAutoStringN<101> entry;
453   nsAString::const_iterator majorTypeStart, majorTypeEnd, minorTypeStart,
454       minorTypeEnd, descriptionStart, descriptionEnd;
455 
456   do {
457     CopyASCIItoUTF16(cBuf, buf);
458     // read through, building up an entry.  If we finish an entry, check for
459     // a match and return out of the loop if we match
460 
461     // skip comments and empty lines
462     if (!buf.IsEmpty() && buf.First() != '#') {
463       entry.Append(buf);
464       if (entry.Last() == '\\') {
465         entry.Truncate(entry.Length() - 1);
466         entry.Append(char16_t(
467             ' '));  // in case there is no trailing whitespace on this line
468       } else {      // we have a full entry
469         LOG(("Current entry: '%s'\n",
470              NS_LossyConvertUTF16toASCII(entry).get()));
471         if (netscapeFormat) {
472           rv = ParseNetscapeMIMETypesEntry(
473               entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
474               extensions, descriptionStart, descriptionEnd);
475 
476           if (NS_FAILED(rv)) {
477             // We sometimes get things like RealPlayer appending
478             // "normal" entries to "Netscape" .mime.types files.  Try
479             // to handle that.  Bug 106381.
480             LOG(("Bogus entry; trying 'normal' mode\n"));
481             rv = ParseNormalMIMETypesEntry(
482                 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
483                 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
484           }
485         } else {
486           rv = ParseNormalMIMETypesEntry(
487               entry, majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd,
488               extensions, descriptionStart, descriptionEnd);
489 
490           if (NS_FAILED(rv)) {
491             // We sometimes get things like StarOffice prepending
492             // "normal" entries to "Netscape" .mime.types files.  Try
493             // to handle that.  Bug 136670.
494             LOG(("Bogus entry; trying 'Netscape' mode\n"));
495             rv = ParseNetscapeMIMETypesEntry(
496                 entry, majorTypeStart, majorTypeEnd, minorTypeStart,
497                 minorTypeEnd, extensions, descriptionStart, descriptionEnd);
498           }
499         }
500 
501         if (NS_SUCCEEDED(rv) &&
502             Substring(majorTypeStart, majorTypeEnd)
503                 .Equals(aMajorType, nsCaseInsensitiveStringComparator) &&
504             Substring(minorTypeStart, minorTypeEnd)
505                 .Equals(aMinorType, nsCaseInsensitiveStringComparator)) {
506           // it's a match
507           aFileExtensions.Assign(extensions);
508           aDescription.Assign(Substring(descriptionStart, descriptionEnd));
509           mimeFile->Close();
510           return NS_OK;
511         }
512         if (NS_FAILED(rv)) {
513           LOG(("Failed to parse entry: %s\n",
514                NS_LossyConvertUTF16toASCII(entry).get()));
515         }
516 
517         entry.Truncate();
518       }
519     }
520     if (!more) {
521       rv = NS_ERROR_NOT_AVAILABLE;
522       break;
523     }
524     // read the next line
525     rv = mimeTypes->ReadLine(cBuf, &more);
526   } while (NS_SUCCEEDED(rv));
527 
528   mimeFile->Close();
529   return rv;
530 }
531 
532 /*
533  * This parses a Netscape format mime.types entry.  There are two
534  * possible formats:
535  *
536  * type=foo/bar; options exts="baz" description="Some type"
537  *
538  * and
539  *
540  * type=foo/bar; options description="Some type" exts="baz"
541  */
542 // static
ParseNetscapeMIMETypesEntry(const nsAString & aEntry,nsAString::const_iterator & aMajorTypeStart,nsAString::const_iterator & aMajorTypeEnd,nsAString::const_iterator & aMinorTypeStart,nsAString::const_iterator & aMinorTypeEnd,nsAString & aExtensions,nsAString::const_iterator & aDescriptionStart,nsAString::const_iterator & aDescriptionEnd)543 nsresult nsOSHelperAppService::ParseNetscapeMIMETypesEntry(
544     const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart,
545     nsAString::const_iterator& aMajorTypeEnd,
546     nsAString::const_iterator& aMinorTypeStart,
547     nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions,
548     nsAString::const_iterator& aDescriptionStart,
549     nsAString::const_iterator& aDescriptionEnd) {
550   LOG(("-- ParseNetscapeMIMETypesEntry\n"));
551   NS_ASSERTION(!aEntry.IsEmpty(),
552                "Empty Netscape MIME types entry being parsed.");
553 
554   nsAString::const_iterator start_iter, end_iter, match_start, match_end;
555 
556   aEntry.BeginReading(start_iter);
557   aEntry.EndReading(end_iter);
558 
559   // skip trailing whitespace
560   do {
561     --end_iter;
562   } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
563   // if we're pointing to a quote, don't advance -- we don't want to
564   // include the quote....
565   if (*end_iter != '"') ++end_iter;
566   match_start = start_iter;
567   match_end = end_iter;
568 
569   // Get the major and minor types
570   // First the major type
571   if (!FindInReadable(NS_LITERAL_STRING("type="), match_start, match_end)) {
572     return NS_ERROR_FAILURE;
573   }
574 
575   match_start = match_end;
576 
577   while (match_end != end_iter && *match_end != '/') {
578     ++match_end;
579   }
580   if (match_end == end_iter) {
581     return NS_ERROR_FAILURE;
582   }
583 
584   aMajorTypeStart = match_start;
585   aMajorTypeEnd = match_end;
586 
587   // now the minor type
588   if (++match_end == end_iter) {
589     return NS_ERROR_FAILURE;
590   }
591 
592   match_start = match_end;
593 
594   while (match_end != end_iter && !nsCRT::IsAsciiSpace(*match_end) &&
595          *match_end != ';') {
596     ++match_end;
597   }
598   if (match_end == end_iter) {
599     return NS_ERROR_FAILURE;
600   }
601 
602   aMinorTypeStart = match_start;
603   aMinorTypeEnd = match_end;
604 
605   // ignore everything up to the end of the mime type from here on
606   start_iter = match_end;
607 
608   // get the extensions
609   match_start = match_end;
610   match_end = end_iter;
611   if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
612     nsAString::const_iterator extStart, extEnd;
613 
614     if (match_end == end_iter ||
615         (*match_end == '"' && ++match_end == end_iter)) {
616       return NS_ERROR_FAILURE;
617     }
618 
619     extStart = match_end;
620     match_start = extStart;
621     match_end = end_iter;
622     if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
623       // exts= before desc=, so we have to find the actual end of the extensions
624       extEnd = match_start;
625       if (extEnd == extStart) {
626         return NS_ERROR_FAILURE;
627       }
628 
629       do {
630         --extEnd;
631       } while (extEnd != extStart && nsCRT::IsAsciiSpace(*extEnd));
632 
633       if (extEnd != extStart && *extEnd == '"') {
634         --extEnd;
635       }
636     } else {
637       // desc= before exts=, so we can use end_iter as the end of the extensions
638       extEnd = end_iter;
639     }
640     aExtensions = Substring(extStart, extEnd);
641   } else {
642     // no extensions
643     aExtensions.Truncate();
644   }
645 
646   // get the description
647   match_start = start_iter;
648   match_end = end_iter;
649   if (FindInReadable(NS_LITERAL_STRING("desc=\""), match_start, match_end)) {
650     aDescriptionStart = match_end;
651     match_start = aDescriptionStart;
652     match_end = end_iter;
653     if (FindInReadable(NS_LITERAL_STRING("exts="), match_start, match_end)) {
654       // exts= after desc=, so have to find actual end of description
655       aDescriptionEnd = match_start;
656       if (aDescriptionEnd == aDescriptionStart) {
657         return NS_ERROR_FAILURE;
658       }
659 
660       do {
661         --aDescriptionEnd;
662       } while (aDescriptionEnd != aDescriptionStart &&
663                nsCRT::IsAsciiSpace(*aDescriptionEnd));
664     } else {
665       // desc= after exts=, so use end_iter for the description end
666       aDescriptionEnd = end_iter;
667     }
668   } else {
669     // no description
670     aDescriptionStart = start_iter;
671     aDescriptionEnd = start_iter;
672   }
673 
674   return NS_OK;
675 }
676 
677 /*
678  * This parses a normal format mime.types entry.  The format is:
679  *
680  * major/minor    ext1 ext2 ext3
681  */
682 // static
ParseNormalMIMETypesEntry(const nsAString & aEntry,nsAString::const_iterator & aMajorTypeStart,nsAString::const_iterator & aMajorTypeEnd,nsAString::const_iterator & aMinorTypeStart,nsAString::const_iterator & aMinorTypeEnd,nsAString & aExtensions,nsAString::const_iterator & aDescriptionStart,nsAString::const_iterator & aDescriptionEnd)683 nsresult nsOSHelperAppService::ParseNormalMIMETypesEntry(
684     const nsAString& aEntry, nsAString::const_iterator& aMajorTypeStart,
685     nsAString::const_iterator& aMajorTypeEnd,
686     nsAString::const_iterator& aMinorTypeStart,
687     nsAString::const_iterator& aMinorTypeEnd, nsAString& aExtensions,
688     nsAString::const_iterator& aDescriptionStart,
689     nsAString::const_iterator& aDescriptionEnd) {
690   LOG(("-- ParseNormalMIMETypesEntry\n"));
691   NS_ASSERTION(!aEntry.IsEmpty(),
692                "Empty Normal MIME types entry being parsed.");
693 
694   nsAString::const_iterator start_iter, end_iter, iter;
695 
696   aEntry.BeginReading(start_iter);
697   aEntry.EndReading(end_iter);
698 
699   // no description
700   aDescriptionStart = start_iter;
701   aDescriptionEnd = start_iter;
702 
703   // skip leading whitespace
704   while (start_iter != end_iter && nsCRT::IsAsciiSpace(*start_iter)) {
705     ++start_iter;
706   }
707   if (start_iter == end_iter) {
708     return NS_ERROR_FAILURE;
709   }
710   // skip trailing whitespace
711   do {
712     --end_iter;
713   } while (end_iter != start_iter && nsCRT::IsAsciiSpace(*end_iter));
714 
715   ++end_iter;  // point to first whitespace char (or to end of string)
716   iter = start_iter;
717 
718   // get the major type
719   if (!FindCharInReadable('/', iter, end_iter)) return NS_ERROR_FAILURE;
720 
721   nsAString::const_iterator equals_sign_iter(start_iter);
722   if (FindCharInReadable('=', equals_sign_iter, iter))
723     return NS_ERROR_FAILURE;  // see bug 136670
724 
725   aMajorTypeStart = start_iter;
726   aMajorTypeEnd = iter;
727 
728   // get the minor type
729   if (++iter == end_iter) {
730     return NS_ERROR_FAILURE;
731   }
732   start_iter = iter;
733 
734   while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
735     ++iter;
736   }
737   aMinorTypeStart = start_iter;
738   aMinorTypeEnd = iter;
739 
740   // get the extensions
741   aExtensions.Truncate();
742   while (iter != end_iter) {
743     while (iter != end_iter && nsCRT::IsAsciiSpace(*iter)) {
744       ++iter;
745     }
746 
747     start_iter = iter;
748     while (iter != end_iter && !nsCRT::IsAsciiSpace(*iter)) {
749       ++iter;
750     }
751     aExtensions.Append(Substring(start_iter, iter));
752     if (iter != end_iter) {  // not the last extension
753       aExtensions.Append(char16_t(','));
754     }
755   }
756 
757   return NS_OK;
758 }
759 
760 // static
LookUpHandlerAndDescription(const nsAString & aMajorType,const nsAString & aMinorType,nsAString & aHandler,nsAString & aDescription,nsAString & aMozillaFlags)761 nsresult nsOSHelperAppService::LookUpHandlerAndDescription(
762     const nsAString& aMajorType, const nsAString& aMinorType,
763     nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags) {
764   // The mailcap lookup is two-pass to handle the case of mailcap files
765   // that have something like:
766   //
767   // text/*; emacs %s
768   // text/rtf; soffice %s
769   //
770   // in that order.  We want to pick up "soffice" for text/rtf in such cases
771   nsresult rv = DoLookUpHandlerAndDescription(
772       aMajorType, aMinorType, aHandler, aDescription, aMozillaFlags, true);
773   if (NS_FAILED(rv)) {
774     rv = DoLookUpHandlerAndDescription(aMajorType, aMinorType, aHandler,
775                                        aDescription, aMozillaFlags, false);
776   }
777 
778   // maybe we have an entry for "aMajorType/*"?
779   if (NS_FAILED(rv)) {
780     rv = DoLookUpHandlerAndDescription(aMajorType, NS_LITERAL_STRING("*"),
781                                        aHandler, aDescription, aMozillaFlags,
782                                        true);
783   }
784 
785   if (NS_FAILED(rv)) {
786     rv = DoLookUpHandlerAndDescription(aMajorType, NS_LITERAL_STRING("*"),
787                                        aHandler, aDescription, aMozillaFlags,
788                                        false);
789   }
790 
791   return rv;
792 }
793 
794 // static
DoLookUpHandlerAndDescription(const nsAString & aMajorType,const nsAString & aMinorType,nsAString & aHandler,nsAString & aDescription,nsAString & aMozillaFlags,bool aUserData)795 nsresult nsOSHelperAppService::DoLookUpHandlerAndDescription(
796     const nsAString& aMajorType, const nsAString& aMinorType,
797     nsAString& aHandler, nsAString& aDescription, nsAString& aMozillaFlags,
798     bool aUserData) {
799   LOG(("-- LookUpHandlerAndDescription for type '%s/%s'\n",
800        NS_LossyConvertUTF16toASCII(aMajorType).get(),
801        NS_LossyConvertUTF16toASCII(aMinorType).get()));
802   nsAutoString mailcapFileName;
803 
804   const char* filenamePref = aUserData ? "helpers.private_mailcap_file"
805                                        : "helpers.global_mailcap_file";
806   const char* filenameEnvVar = aUserData ? "PERSONAL_MAILCAP" : "MAILCAP";
807 
808   nsresult rv = GetFileLocation(filenamePref, filenameEnvVar, mailcapFileName);
809   if (NS_SUCCEEDED(rv) && !mailcapFileName.IsEmpty()) {
810     rv = GetHandlerAndDescriptionFromMailcapFile(mailcapFileName, aMajorType,
811                                                  aMinorType, aHandler,
812                                                  aDescription, aMozillaFlags);
813   } else {
814     rv = NS_ERROR_NOT_AVAILABLE;
815   }
816 
817   return rv;
818 }
819 
820 // static
GetHandlerAndDescriptionFromMailcapFile(const nsAString & aFilename,const nsAString & aMajorType,const nsAString & aMinorType,nsAString & aHandler,nsAString & aDescription,nsAString & aMozillaFlags)821 nsresult nsOSHelperAppService::GetHandlerAndDescriptionFromMailcapFile(
822     const nsAString& aFilename, const nsAString& aMajorType,
823     const nsAString& aMinorType, nsAString& aHandler, nsAString& aDescription,
824     nsAString& aMozillaFlags) {
825   LOG(("-- GetHandlerAndDescriptionFromMailcapFile\n"));
826   LOG(("Getting handler and description from mailcap file '%s'\n",
827        NS_LossyConvertUTF16toASCII(aFilename).get()));
828   LOG(("Using type '%s/%s'\n", NS_LossyConvertUTF16toASCII(aMajorType).get(),
829        NS_LossyConvertUTF16toASCII(aMinorType).get()));
830 
831   nsresult rv = NS_OK;
832   bool more = false;
833 
834   nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
835   if (NS_FAILED(rv)) return rv;
836   rv = file->InitWithPath(aFilename);
837   if (NS_FAILED(rv)) return rv;
838 
839   nsCOMPtr<nsIFileInputStream> mailcapFile(
840       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
841   if (NS_FAILED(rv)) return rv;
842   rv = mailcapFile->Init(file, -1, -1, false);
843   if (NS_FAILED(rv)) return rv;
844 
845   nsCOMPtr<nsILineInputStream> mailcap(do_QueryInterface(mailcapFile, &rv));
846 
847   if (NS_FAILED(rv)) {
848     LOG(("Interface trouble in stream land!"));
849     return rv;
850   }
851 
852   nsAutoStringN<129> entry;
853   nsAutoStringN<81> buffer;
854   nsAutoCStringN<81> cBuffer;
855   rv = mailcap->ReadLine(cBuffer, &more);
856   if (NS_FAILED(rv)) {
857     mailcapFile->Close();
858     return rv;
859   }
860 
861   do {  // return on end-of-file in the loop
862 
863     CopyASCIItoUTF16(cBuffer, buffer);
864     if (!buffer.IsEmpty() && buffer.First() != '#') {
865       entry.Append(buffer);
866       if (entry.Last() == '\\') {  // entry continues on next line
867         entry.Truncate(entry.Length() - 1);
868         entry.Append(char16_t(
869             ' '));  // in case there is no trailing whitespace on this line
870       } else {      // we have a full entry in entry.  Check it for the type
871         LOG(("Current entry: '%s'\n",
872              NS_LossyConvertUTF16toASCII(entry).get()));
873 
874         nsAString::const_iterator semicolon_iter, start_iter, end_iter,
875             majorTypeStart, majorTypeEnd, minorTypeStart, minorTypeEnd;
876         entry.BeginReading(start_iter);
877         entry.EndReading(end_iter);
878         semicolon_iter = start_iter;
879         FindSemicolon(semicolon_iter, end_iter);
880         if (semicolon_iter !=
881             end_iter) {  // we have something resembling a valid entry
882           rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
883                              minorTypeStart, minorTypeEnd, semicolon_iter);
884           if (NS_SUCCEEDED(rv) &&
885               Substring(majorTypeStart, majorTypeEnd)
886                   .Equals(aMajorType, nsCaseInsensitiveStringComparator) &&
887               Substring(minorTypeStart, minorTypeEnd)
888                   .Equals(aMinorType, nsCaseInsensitiveStringComparator)) {
889             // we have a match
890             bool match = true;
891             ++semicolon_iter;  // point at the first char past the semicolon
892             start_iter = semicolon_iter;  // handler string starts here
893             FindSemicolon(semicolon_iter, end_iter);
894             while (start_iter != semicolon_iter &&
895                    nsCRT::IsAsciiSpace(*start_iter)) {
896               ++start_iter;
897             }
898 
899             LOG(("The real handler is: '%s'\n",
900                  NS_LossyConvertUTF16toASCII(
901                      Substring(start_iter, semicolon_iter))
902                      .get()));
903 
904             // XXX ugly hack.  Just grab the executable name
905             nsAString::const_iterator end_handler_iter = semicolon_iter;
906             nsAString::const_iterator end_executable_iter = start_iter;
907             while (end_executable_iter != end_handler_iter &&
908                    !nsCRT::IsAsciiSpace(*end_executable_iter)) {
909               ++end_executable_iter;
910             }
911             // XXX End ugly hack
912 
913             aHandler = Substring(start_iter, end_executable_iter);
914 
915             nsAString::const_iterator start_option_iter, end_optionname_iter,
916                 equal_sign_iter;
917             bool equalSignFound;
918             while (match && semicolon_iter != end_iter &&
919                    ++semicolon_iter !=
920                        end_iter) {  // there are options left and we still match
921               start_option_iter = semicolon_iter;
922               // skip over leading whitespace
923               while (start_option_iter != end_iter &&
924                      nsCRT::IsAsciiSpace(*start_option_iter)) {
925                 ++start_option_iter;
926               }
927               if (start_option_iter == end_iter) {  // nothing actually here
928                 break;
929               }
930               semicolon_iter = start_option_iter;
931               FindSemicolon(semicolon_iter, end_iter);
932               equal_sign_iter = start_option_iter;
933               equalSignFound = false;
934               while (equal_sign_iter != semicolon_iter && !equalSignFound) {
935                 switch (*equal_sign_iter) {
936                   case '\\':
937                     equal_sign_iter.advance(2);
938                     break;
939                   case '=':
940                     equalSignFound = true;
941                     break;
942                   default:
943                     ++equal_sign_iter;
944                     break;
945                 }
946               }
947               end_optionname_iter = start_option_iter;
948               // find end of option name
949               while (end_optionname_iter != equal_sign_iter &&
950                      !nsCRT::IsAsciiSpace(*end_optionname_iter)) {
951                 ++end_optionname_iter;
952               }
953               nsDependentSubstring optionName(start_option_iter,
954                                               end_optionname_iter);
955               if (equalSignFound) {
956                 // This is an option that has a name and value
957                 if (optionName.EqualsLiteral("description")) {
958                   aDescription = Substring(++equal_sign_iter, semicolon_iter);
959                 } else if (optionName.EqualsLiteral("x-mozilla-flags")) {
960                   aMozillaFlags = Substring(++equal_sign_iter, semicolon_iter);
961                 } else if (optionName.EqualsLiteral("test")) {
962                   nsAutoCString testCommand;
963                   rv = UnescapeCommand(
964                       Substring(++equal_sign_iter, semicolon_iter), aMajorType,
965                       aMinorType, testCommand);
966                   if (NS_FAILED(rv)) continue;
967                   nsCOMPtr<nsIProcess> process =
968                       do_CreateInstance(NS_PROCESS_CONTRACTID, &rv);
969                   if (NS_FAILED(rv)) continue;
970                   nsCOMPtr<nsIFile> file(
971                       do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
972                   if (NS_FAILED(rv)) continue;
973                   rv = file->InitWithNativePath(NS_LITERAL_CSTRING("/bin/sh"));
974                   if (NS_FAILED(rv)) continue;
975                   rv = process->Init(file);
976                   if (NS_FAILED(rv)) continue;
977                   const char* args[] = {"-c", testCommand.get()};
978                   LOG(("Running Test: %s\n", testCommand.get()));
979                   rv = process->Run(true, args, 2);
980                   if (NS_FAILED(rv)) continue;
981                   int32_t exitValue;
982                   rv = process->GetExitValue(&exitValue);
983                   if (NS_FAILED(rv)) continue;
984                   LOG(("Exit code: %d\n", exitValue));
985                   if (exitValue) {
986                     match = false;
987                   }
988                 }
989               } else {
990                 // This is an option that just has a name but no value (eg
991                 // "copiousoutput")
992                 if (optionName.EqualsLiteral("needsterminal")) {
993                   match = false;
994                 }
995               }
996             }
997 
998             if (match) {  // we did not fail any test clauses; all is good
999               // get out of here
1000               mailcapFile->Close();
1001               return NS_OK;
1002             }
1003             // pretend that this match never happened
1004             aDescription.Truncate();
1005             aMozillaFlags.Truncate();
1006             aHandler.Truncate();
1007           }
1008         }
1009         // zero out the entry for the next cycle
1010         entry.Truncate();
1011       }
1012     }
1013     if (!more) {
1014       rv = NS_ERROR_NOT_AVAILABLE;
1015       break;
1016     }
1017     rv = mailcap->ReadLine(cBuffer, &more);
1018   } while (NS_SUCCEEDED(rv));
1019   mailcapFile->Close();
1020   return rv;
1021 }
1022 
OSProtocolHandlerExists(const char * aProtocolScheme,bool * aHandlerExists)1023 nsresult nsOSHelperAppService::OSProtocolHandlerExists(
1024     const char* aProtocolScheme, bool* aHandlerExists) {
1025   nsresult rv = NS_OK;
1026 
1027   if (!XRE_IsContentProcess()) {
1028 #ifdef MOZ_WIDGET_GTK
1029     // Check the GNOME registry for a protocol handler
1030     *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
1031 #else
1032     *aHandlerExists = false;
1033 #endif
1034   } else {
1035     *aHandlerExists = false;
1036     nsCOMPtr<nsIHandlerService> handlerSvc =
1037         do_GetService(NS_HANDLERSERVICE_CONTRACTID, &rv);
1038     if (NS_SUCCEEDED(rv) && handlerSvc) {
1039       rv = handlerSvc->ExistsForProtocolOS(nsCString(aProtocolScheme),
1040                                            aHandlerExists);
1041     }
1042   }
1043 
1044   return rv;
1045 }
1046 
GetApplicationDescription(const nsACString & aScheme,nsAString & _retval)1047 NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
1048     const nsACString& aScheme, nsAString& _retval) {
1049 #ifdef MOZ_WIDGET_GTK
1050   nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
1051   return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
1052 #else
1053   return NS_ERROR_NOT_AVAILABLE;
1054 #endif
1055 }
1056 
IsCurrentAppOSDefaultForProtocol(const nsACString & aScheme,bool * _retval)1057 NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol(
1058     const nsACString& aScheme, bool* _retval) {
1059   *_retval = false;
1060   return NS_OK;
1061 }
1062 
GetFileTokenForPath(const char16_t * platformAppPath,nsIFile ** aFile)1063 nsresult nsOSHelperAppService::GetFileTokenForPath(
1064     const char16_t* platformAppPath, nsIFile** aFile) {
1065   LOG(("-- nsOSHelperAppService::GetFileTokenForPath: '%s'\n",
1066        NS_LossyConvertUTF16toASCII(platformAppPath).get()));
1067   if (!*platformAppPath) {  // empty filename--return error
1068     NS_WARNING("Empty filename passed in.");
1069     return NS_ERROR_INVALID_ARG;
1070   }
1071 
1072   // first check if the base class implementation finds anything
1073   nsresult rv =
1074       nsExternalHelperAppService::GetFileTokenForPath(platformAppPath, aFile);
1075   if (NS_SUCCEEDED(rv)) return rv;
1076   // If the reason for failure was that the file doesn't exist, return too
1077   // (because it means the path was absolute, and so that we shouldn't search in
1078   // the path)
1079   if (rv == NS_ERROR_FILE_NOT_FOUND) return rv;
1080 
1081   // If we get here, we really should have a relative path.
1082   NS_ASSERTION(*platformAppPath != char16_t('/'), "Unexpected absolute path");
1083 
1084   nsCOMPtr<nsIFile> localFile(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
1085 
1086   if (!localFile) return NS_ERROR_NOT_INITIALIZED;
1087 
1088   bool exists = false;
1089   // ugly hack.  Walk the PATH variable...
1090   char* unixpath = PR_GetEnv("PATH");
1091   nsAutoCString path(unixpath);
1092 
1093   const char* start_iter = path.BeginReading(start_iter);
1094   const char* colon_iter = start_iter;
1095   const char* end_iter = path.EndReading(end_iter);
1096 
1097   while (start_iter != end_iter && !exists) {
1098     while (colon_iter != end_iter && *colon_iter != ':') {
1099       ++colon_iter;
1100     }
1101     localFile->InitWithNativePath(Substring(start_iter, colon_iter));
1102     rv = localFile->AppendRelativePath(nsDependentString(platformAppPath));
1103     // Failing AppendRelativePath is a bad thing - it should basically always
1104     // succeed given a relative path. Show a warning if it does fail.
1105     // To prevent infinite loops when it does fail, return at this point.
1106     NS_ENSURE_SUCCESS(rv, rv);
1107     localFile->Exists(&exists);
1108     if (!exists) {
1109       if (colon_iter == end_iter) {
1110         break;
1111       }
1112       ++colon_iter;
1113       start_iter = colon_iter;
1114     }
1115   }
1116 
1117   if (exists) {
1118     rv = NS_OK;
1119   } else {
1120     rv = NS_ERROR_NOT_AVAILABLE;
1121   }
1122 
1123   *aFile = localFile;
1124   NS_IF_ADDREF(*aFile);
1125 
1126   return rv;
1127 }
1128 
GetFromExtension(const nsCString & aFileExt)1129 already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension(
1130     const nsCString& aFileExt) {
1131   // if the extension is empty, return immediately
1132   if (aFileExt.IsEmpty()) return nullptr;
1133 
1134   LOG(("Here we do an extension lookup for '%s'\n", aFileExt.get()));
1135 
1136   nsAutoString majorType, minorType, mime_types_description,
1137       mailcap_description, handler, mozillaFlags;
1138 
1139   nsresult rv =
1140       LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType,
1141                                minorType, mime_types_description, true);
1142 
1143   if (NS_FAILED(rv) || majorType.IsEmpty()) {
1144 #ifdef MOZ_WIDGET_GTK
1145     LOG(("Looking in GNOME registry\n"));
1146     RefPtr<nsMIMEInfoBase> gnomeInfo =
1147         nsGNOMERegistry::GetFromExtension(aFileExt);
1148     if (gnomeInfo) {
1149       LOG(("Got MIMEInfo from GNOME registry\n"));
1150       return gnomeInfo.forget();
1151     }
1152 #endif
1153 
1154     rv = LookUpTypeAndDescription(NS_ConvertUTF8toUTF16(aFileExt), majorType,
1155                                   minorType, mime_types_description, false);
1156   }
1157 
1158   if (NS_FAILED(rv)) return nullptr;
1159 
1160   NS_LossyConvertUTF16toASCII asciiMajorType(majorType);
1161   NS_LossyConvertUTF16toASCII asciiMinorType(minorType);
1162 
1163   LOG(
1164       ("Type/Description results:  majorType='%s', minorType='%s', "
1165        "description='%s'\n",
1166        asciiMajorType.get(), asciiMinorType.get(),
1167        NS_LossyConvertUTF16toASCII(mime_types_description).get()));
1168 
1169   if (majorType.IsEmpty() && minorType.IsEmpty()) {
1170     // we didn't get a type mapping, so we can't do anything useful
1171     return nullptr;
1172   }
1173 
1174   nsAutoCString mimeType(asciiMajorType + NS_LITERAL_CSTRING("/") +
1175                          asciiMinorType);
1176   RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimeType);
1177 
1178   mimeInfo->AppendExtension(aFileExt);
1179   rv = LookUpHandlerAndDescription(majorType, minorType, handler,
1180                                    mailcap_description, mozillaFlags);
1181   LOG(
1182       ("Handler/Description results:  handler='%s', description='%s', "
1183        "mozillaFlags='%s'\n",
1184        NS_LossyConvertUTF16toASCII(handler).get(),
1185        NS_LossyConvertUTF16toASCII(mailcap_description).get(),
1186        NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
1187   mailcap_description.Trim(" \t\"");
1188   mozillaFlags.Trim(" \t");
1189   if (!mime_types_description.IsEmpty()) {
1190     mimeInfo->SetDescription(mime_types_description);
1191   } else {
1192     mimeInfo->SetDescription(mailcap_description);
1193   }
1194 
1195   if (NS_SUCCEEDED(rv) && handler.IsEmpty()) {
1196     rv = NS_ERROR_NOT_AVAILABLE;
1197   }
1198 
1199   if (NS_SUCCEEDED(rv)) {
1200     nsCOMPtr<nsIFile> handlerFile;
1201     rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
1202 
1203     if (NS_SUCCEEDED(rv)) {
1204       mimeInfo->SetDefaultApplication(handlerFile);
1205       mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
1206       mimeInfo->SetDefaultDescription(handler);
1207     }
1208   }
1209 
1210   if (NS_FAILED(rv)) {
1211     mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1212   }
1213 
1214   return mimeInfo.forget();
1215 }
1216 
GetFromType(const nsCString & aMIMEType)1217 already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType(
1218     const nsCString& aMIMEType) {
1219   // if the type is empty, return immediately
1220   if (aMIMEType.IsEmpty()) return nullptr;
1221 
1222   LOG(("Here we do a mimetype lookup for '%s'\n", aMIMEType.get()));
1223 
1224   // extract the major and minor types
1225   NS_ConvertASCIItoUTF16 mimeType(aMIMEType);
1226   nsAString::const_iterator start_iter, end_iter, majorTypeStart, majorTypeEnd,
1227       minorTypeStart, minorTypeEnd;
1228 
1229   mimeType.BeginReading(start_iter);
1230   mimeType.EndReading(end_iter);
1231 
1232   // XXX FIXME: add typeOptions parsing in here
1233   nsresult rv = ParseMIMEType(start_iter, majorTypeStart, majorTypeEnd,
1234                               minorTypeStart, minorTypeEnd, end_iter);
1235 
1236   if (NS_FAILED(rv)) {
1237     return nullptr;
1238   }
1239 
1240   nsDependentSubstring majorType(majorTypeStart, majorTypeEnd);
1241   nsDependentSubstring minorType(minorTypeStart, minorTypeEnd);
1242 
1243   // First check the user's private mailcap file
1244   nsAutoString mailcap_description, handler, mozillaFlags;
1245   DoLookUpHandlerAndDescription(majorType, minorType, handler,
1246                                 mailcap_description, mozillaFlags, true);
1247 
1248   LOG(("Private Handler/Description results:  handler='%s', description='%s'\n",
1249        NS_LossyConvertUTF16toASCII(handler).get(),
1250        NS_LossyConvertUTF16toASCII(mailcap_description).get()));
1251 
1252   // Now look up our extensions
1253   nsAutoString extensions, mime_types_description;
1254   LookUpExtensionsAndDescription(majorType, minorType, extensions,
1255                                  mime_types_description);
1256 
1257 #ifdef MOZ_WIDGET_GTK
1258   if (handler.IsEmpty()) {
1259     RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
1260     if (gnomeInfo) {
1261       LOG(
1262           ("Got MIMEInfo from GNOME registry without extensions; setting them "
1263            "to %s\n",
1264            NS_LossyConvertUTF16toASCII(extensions).get()));
1265 
1266       NS_ASSERTION(!gnomeInfo->HasExtensions(), "How'd that happen?");
1267       gnomeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
1268       return gnomeInfo.forget();
1269     }
1270   }
1271 #endif
1272 
1273   if (handler.IsEmpty()) {
1274     DoLookUpHandlerAndDescription(majorType, minorType, handler,
1275                                   mailcap_description, mozillaFlags, false);
1276   }
1277 
1278   if (handler.IsEmpty()) {
1279     DoLookUpHandlerAndDescription(majorType, NS_LITERAL_STRING("*"), handler,
1280                                   mailcap_description, mozillaFlags, true);
1281   }
1282 
1283   if (handler.IsEmpty()) {
1284     DoLookUpHandlerAndDescription(majorType, NS_LITERAL_STRING("*"), handler,
1285                                   mailcap_description, mozillaFlags, false);
1286   }
1287 
1288   LOG(
1289       ("Handler/Description results:  handler='%s', description='%s', "
1290        "mozillaFlags='%s'\n",
1291        NS_LossyConvertUTF16toASCII(handler).get(),
1292        NS_LossyConvertUTF16toASCII(mailcap_description).get(),
1293        NS_LossyConvertUTF16toASCII(mozillaFlags).get()));
1294 
1295   mailcap_description.Trim(" \t\"");
1296   mozillaFlags.Trim(" \t");
1297 
1298   if (handler.IsEmpty() && extensions.IsEmpty() &&
1299       mailcap_description.IsEmpty() && mime_types_description.IsEmpty()) {
1300     // No real useful info
1301     return nullptr;
1302   }
1303 
1304   RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
1305 
1306   mimeInfo->SetFileExtensions(NS_ConvertUTF16toUTF8(extensions));
1307   if (!mime_types_description.IsEmpty()) {
1308     mimeInfo->SetDescription(mime_types_description);
1309   } else {
1310     mimeInfo->SetDescription(mailcap_description);
1311   }
1312 
1313   rv = NS_ERROR_NOT_AVAILABLE;
1314   nsCOMPtr<nsIFile> handlerFile;
1315   if (!handler.IsEmpty()) {
1316     rv = GetFileTokenForPath(handler.get(), getter_AddRefs(handlerFile));
1317   }
1318 
1319   if (NS_SUCCEEDED(rv)) {
1320     mimeInfo->SetDefaultApplication(handlerFile);
1321     mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
1322     mimeInfo->SetDefaultDescription(handler);
1323   } else {
1324     mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
1325   }
1326 
1327   return mimeInfo.forget();
1328 }
1329 
GetMIMEInfoFromOS(const nsACString & aType,const nsACString & aFileExt,bool * aFound,nsIMIMEInfo ** aMIMEInfo)1330 nsresult nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aType,
1331                                                  const nsACString& aFileExt,
1332                                                  bool* aFound,
1333                                                  nsIMIMEInfo** aMIMEInfo) {
1334   *aFound = true;
1335   RefPtr<nsMIMEInfoBase> retval;
1336   // Fallback to lookup by extension when generic 'application/octet-stream'
1337   // content type is received.
1338   if (!aType.EqualsLiteral(APPLICATION_OCTET_STREAM)) {
1339     retval = GetFromType(PromiseFlatCString(aType));
1340   }
1341   bool hasDefault = false;
1342   if (retval) retval->GetHasDefaultHandler(&hasDefault);
1343   if (!retval || !hasDefault) {
1344     RefPtr<nsMIMEInfoBase> miByExt =
1345         GetFromExtension(PromiseFlatCString(aFileExt));
1346     // If we had no extension match, but a type match, use that
1347     if (!miByExt && retval) {
1348       retval.forget(aMIMEInfo);
1349       return NS_OK;
1350     }
1351     // If we had an extension match but no type match, set the mimetype and use
1352     // it
1353     if (!retval && miByExt) {
1354       if (!aType.IsEmpty()) miByExt->SetMIMEType(aType);
1355       miByExt.swap(retval);
1356 
1357       retval.forget(aMIMEInfo);
1358       return NS_OK;
1359     }
1360     // If we got nothing, make a new mimeinfo
1361     if (!retval) {
1362       *aFound = false;
1363       retval = new nsMIMEInfoUnix(aType);
1364       if (retval) {
1365         if (!aFileExt.IsEmpty()) retval->AppendExtension(aFileExt);
1366       }
1367 
1368       retval.forget(aMIMEInfo);
1369       return NS_OK;
1370     }
1371 
1372     // Copy the attributes of retval (mimeinfo from type) onto miByExt, to
1373     // return it
1374     // but reset to just collected mDefaultAppDescription (from ext)
1375     nsAutoString byExtDefault;
1376     miByExt->GetDefaultDescription(byExtDefault);
1377     retval->SetDefaultDescription(byExtDefault);
1378     retval->CopyBasicDataTo(miByExt);
1379 
1380     miByExt.swap(retval);
1381   }
1382   retval.forget(aMIMEInfo);
1383   return NS_OK;
1384 }
1385 
1386 NS_IMETHODIMP
GetProtocolHandlerInfoFromOS(const nsACString & aScheme,bool * found,nsIHandlerInfo ** _retval)1387 nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme,
1388                                                    bool* found,
1389                                                    nsIHandlerInfo** _retval) {
1390   NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
1391 
1392   nsresult rv =
1393       OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(), found);
1394   if (NS_FAILED(rv)) return rv;
1395 
1396   nsMIMEInfoUnix* handlerInfo =
1397       new nsMIMEInfoUnix(aScheme, nsMIMEInfoBase::eProtocolInfo);
1398   NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
1399   NS_ADDREF(*_retval = handlerInfo);
1400 
1401   if (!*found) {
1402     // Code that calls this requires an object regardless if the OS has
1403     // something for us, so we return the empty object.
1404     return NS_OK;
1405   }
1406 
1407   nsAutoString desc;
1408   GetApplicationDescription(aScheme, desc);
1409   handlerInfo->SetDefaultDescription(desc);
1410 
1411   return NS_OK;
1412 }
1413