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