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