1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package com.sun.star.lib.loader;
25 
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.io.IOException;
32 import java.io.UnsupportedEncodingException;
33 import java.net.MalformedURLException;
34 import java.net.URL;
35 import java.util.StringTokenizer;
36 import java.util.Vector;
37 
38 /**
39  * This class finds a UNO installation on the system.
40  *
41  * <p>A UNO installation can be specified by the user by either setting the
42  * com.sun.star.lib.loader.unopath system property or by setting the
43  * UNO_PATH environment variable to the program directory of a UNO
44  * installation.
45  * Note, that Java 1.3.1 and Java 1.4 don't support environment variables
46  * (System.getenv() throws java.lang.Error) and therefore setting the UNO_PATH
47  * enviroment variable won't work with those Java versions.
48  * If no UNO installation is specified by the user, the default installation
49  * on the system will be returned.</p>
50  *
51  * <p>On the Windows platform the default installation is read from the Windows
52  * Registry.</p>
53  *
54  * <p>On the Unix/Linux platforms the default installation is found from the
55  * PATH environment variable. Note, that for Java 1.3.1 and Java 1.4 the
56  * default installation is found by using the 'which' command, because
57  * environment variables are not supported with those Java versions.
58  * Both methods require that the 'soffice' executable or a symbolic
59  * link is in one of the directories listed in the PATH environment variable.
60  * For older versions than OOo 2.0 the above described methods may fail.
61  * In this case the default installation is taken from the .sversionrc file in
62  * the user's home directory. Note, that the .sversionrc file will be omitted
63  * for OOo 2.0</p>
64  */
65 final class InstallationFinder {
66 
67     private static final String SYSPROP_NAME =
68         "com.sun.star.lib.loader.unopath";
69     private static final String ENVVAR_NAME = "UNO_PATH";
70     private static final String SOFFICE = "soffice"; // Unix/Linux only
71 
InstallationFinder()72     private InstallationFinder() {} // do not instantiate
73 
74     /**
75      * Gets the path of a UNO installation.
76      *
77      * @return the installation path or <code>null</code>, if no installation
78      *         was specified or found, or if an error occured
79      */
getPath()80     public static String getPath() {
81 
82         String path = null;
83 
84         // get the installation path from the Java system property
85         // com.sun.star.lib.loader.unopath
86         // (all platforms)
87         path = getPathFromProperty( SYSPROP_NAME );
88         if ( path == null ) {
89             // get the installation path from the UNO_PATH environment variable
90             // (all platforms, not working for Java 1.3.1 and Java 1.4)
91             path = getPathFromEnvVar( ENVVAR_NAME );
92             if ( path == null ) {
93                 String osname = null;
94                 try {
95                     osname = System.getProperty( "os.name" );
96                 } catch ( SecurityException e ) {
97                     // if a SecurityException was thrown,
98                     // return <code>null</code>
99                     return null;
100                 }
101                 if ( osname != null ) {
102                     if ( osname.startsWith( "Windows" ) ) {
103                         // get the installation path from the Windows Registry
104                         // (Windows platform only)
105                         path = getPathFromWindowsRegistry();
106                     } else {
107                         // get the installation path from the PATH environment
108                         // variable (Unix/Linux platforms only, not working for
109                         // Java 1.3.1 and Java 1.4)
110                         path = getPathFromPathEnvVar();
111                         if ( path == null ) {
112                             // get the installation path from the 'which'
113                             // command (Unix/Linux platforms only)
114                             path = getPathFromWhich();
115                             if ( path == null ) {
116                                 // get the installation path from the
117                                 // .sversionrc file (Unix/Linux platforms only,
118                                 // for older versions than OOo 2.0)
119                                 path = getPathFromSVersionFile();
120                             }
121                         }
122                     }
123                 }
124             }
125         }
126 
127         return path;
128     }
129 
130     /**
131      * Gets the installation path from a Java system property.
132      *
133      * <p>This method is called on all platforms.
134      * The Java system property can be passed into the application by using
135      * the -D flag, e.g.
136      * java -D<property name>=<installation path> -jar application.jar.</p>
137      *
138      * @return the installation path or <code>null</code>, if no installation
139      *         was specified in the Java system property or if an error occured
140      */
getPathFromProperty( String prop )141     private static String getPathFromProperty( String prop ) {
142 
143         String path = null;
144 
145         try {
146             path = System.getProperty( prop );
147         } catch ( SecurityException e ) {
148             // if a SecurityException was thrown, return <code>null</code>
149         }
150 
151         return path;
152     }
153 
154     /**
155      * Gets the installation path from an environment variable.
156      *
157      * <p>This method is called on all platforms.
158      * Note, that in Java 1.3.1 and Java 1.4 System.getenv() throws
159      * java.lang.Error and therefore this method returns null for those
160      * Java versions.</p>
161      *
162      * @return the installation path or <code>null</code>, if no installation
163      *         was specified in the environment variable or if an error occured
164      */
getPathFromEnvVar( String var )165     private static String getPathFromEnvVar( String var ) {
166 
167         String path = null;
168 
169         try {
170             path = System.getenv( var );
171         } catch ( SecurityException e ) {
172             // if a SecurityException was thrown, return <code>null</code>
173         } catch ( java.lang.Error err ) {
174             // System.getenv() throws java.lang.Error in Java 1.3.1 and
175             // Java 1.4
176         }
177 
178         return path;
179     }
180 
181     /**
182      * Gets the installation path from the Windows Registry.
183      *
184      * <p>This method is called on the Windows platform only.</p>
185      *
186      * @return the installation path or <code>null</code>, if no installation
187      *         was found or if an error occured
188      */
getPathFromWindowsRegistry()189     private static String getPathFromWindowsRegistry() {
190 
191         final String SUBKEYNAME = "Software\\OpenOffice\\UNO\\InstallPath";
192         final String SUBKEYNAME64 = "Software\\Wow6432Node\\OpenOffice\\UNO\\InstallPath";
193 
194         String path = null;
195 
196         try {
197             // read the key's default value from HKEY_CURRENT_USER
198             WinRegKey key = new WinRegKey( "HKEY_CURRENT_USER", SUBKEYNAME );
199             path = key.getStringValue( "" ); // default
200         } catch ( WinRegKeyException e ) {
201             try {
202                 // read the key's default value from HKEY_LOCAL_MACHINE
203                 WinRegKey key = new WinRegKey( "HKEY_CURRENT_USER",
204                                                SUBKEYNAME64 );
205                 path = key.getStringValue( "" ); // default
206             } catch ( WinRegKeyException e64 ) {
207                 try {
208                     // read the key's default value from HKEY_LOCAL_MACHINE
209                     WinRegKey key = new WinRegKey( "HKEY_LOCAL_MACHINE",
210                                                    SUBKEYNAME );
211                     path = key.getStringValue( "" ); // default
212                 } catch ( WinRegKeyException we ) {
213                     try {
214                         // read the key's default value from HKEY_LOCAL_MACHINE
215                         WinRegKey key = new WinRegKey( "HKEY_LOCAL_MACHINE",
216                                                        SUBKEYNAME64 );
217                         path = key.getStringValue( "" ); // default
218                     } catch ( WinRegKeyException we64 ) {
219                         System.err.println( "com.sun.star.lib.loader." +
220                                             "InstallationFinder::getPathFromWindowsRegistry: " +
221                                             "reading key from Windows Registry failed: " + we64 );
222                     }
223                 }
224             }
225         }
226 
227         return path;
228     }
229 
230     /**
231      * Gets the installation path from the PATH environment variable.
232      *
233      * <p>This method is called on Unix/Linux platforms only.
234      * An installation is found, if the executable 'soffice' or a symbolic link
235      * is in one of the directories listed in the PATH environment variable.
236      * Note, that in Java 1.3.1 and Java 1.4 System.getenv() throws
237      * java.lang.Error and therefore this method returns null for those
238      * Java versions.</p>
239      *
240      * @return the installation path or <code>null</code>, if no installation
241      *         was found or if an error occured
242      */
getPathFromPathEnvVar()243     private static String getPathFromPathEnvVar() {
244 
245         final String PATH_ENVVAR_NAME = "PATH";
246 
247         String path = null;
248         String str = null;
249 
250         try {
251             str = System.getenv( PATH_ENVVAR_NAME );
252         } catch ( SecurityException e ) {
253             // if a SecurityException was thrown, return <code>null</code>
254             return null;
255         } catch ( java.lang.Error err ) {
256             // System.getenv() throws java.lang.Error in Java 1.3.1 and
257             // Java 1.4
258             return null;
259         }
260 
261         if ( str != null ) {
262             StringTokenizer tokens = new StringTokenizer(
263                 str, File.pathSeparator );
264             while ( tokens.hasMoreTokens() ) {
265                 File file = new File( tokens.nextToken(), SOFFICE );
266                 try {
267                     if ( file.exists() ) {
268                         try {
269                             // resolve symlink
270                             path = file.getCanonicalFile().getParent();
271                             if ( path != null )
272                                 break;
273                         } catch ( IOException e ) {
274                             // if an I/O exception is thrown, ignore this
275                             // path entry and try the next one
276                             System.err.println( "com.sun.star.lib.loader." +
277                                 "InstallationFinder::getPathFromEnvVar: " +
278                                 "bad path: " + e );
279                         }
280                     }
281                 } catch ( SecurityException e ) {
282                     // if a SecurityException was thrown, ignore this path
283                     // entry and try the next one
284                 }
285             }
286         }
287 
288         return path;
289     }
290 
291     /**
292      * Gets the installation path from the 'which' command on Unix/Linux
293      * platforms.
294      *
295      * <p>This method is called on Unix/Linux platforms only.
296      * An installation is found, if the executable 'soffice' or a symbolic link
297      * is in one of the directories listed in the PATH environment variable.</p>
298      *
299      * @return the installation path or <code>null</code>, if no installation
300      *         was found or if an error occured
301      */
getPathFromWhich()302     private static String getPathFromWhich() {
303 
304         final String WHICH = "which";
305 
306         String path = null;
307 
308         // start the which process
309         String[] cmdArray = new String[2];
310         cmdArray[0] = WHICH;
311         cmdArray[1] = SOFFICE;
312         Process proc = null;
313         Runtime rt = Runtime.getRuntime();
314         try {
315             proc = rt.exec( cmdArray );
316         } catch ( SecurityException e ) {
317             return null;
318         } catch ( IOException e ) {
319             // if an I/O exception is thrown, return <code>null</null>
320             System.err.println( "com.sun.star.lib.loader." +
321                 "InstallationFinder::getPathFromWhich: " +
322                 "which command failed: " + e );
323             return null;
324         }
325 
326         // empty standard error stream in a seperate thread
327         StreamGobbler gobbler = new StreamGobbler( proc.getErrorStream() );
328         gobbler.start();
329 
330         // read the which output from standard input stream
331         BufferedReader br = new BufferedReader(
332             new InputStreamReader( proc.getInputStream() ) );
333         String line = null;
334         try {
335             while ( ( line = br.readLine() ) != null ) {
336                 if ( path == null ) {
337                     // get the path from the which output
338                     int index = line.lastIndexOf( SOFFICE );
339                     if ( index != -1 ) {
340                         int end = index + SOFFICE.length();
341                         for ( int i = 0; i <= index; i++ ) {
342                             File file = new File( line.substring( i, end ) );
343                             try {
344                                 if ( file.exists() ) {
345                                     // resolve symlink
346                                     path = file.getCanonicalFile().getParent();
347                                     if ( path != null )
348                                         break;
349                                 }
350                             } catch ( SecurityException e ) {
351                                 return null;
352                             }
353                         }
354                     }
355                 }
356             }
357         } catch ( IOException e ) {
358             // if an I/O exception is thrown, return <code>null</null>
359             System.err.println( "com.sun.star.lib.loader." +
360                                 "InstallationFinder::getPathFromWhich: " +
361                                 "reading which command output failed: " + e );
362             return null;
363         } finally {
364             if ( br != null ) {
365                 try {
366                     br.close();
367                 } catch ( IOException e ) {
368                     // closing standard input stream failed, ignore
369                 }
370             }
371         }
372 
373         try {
374             // wait until the which process has terminated
375             proc.waitFor();
376         } catch ( InterruptedException e ) {
377             // the current thread was interrupted by another thread,
378             // kill the which process
379             proc.destroy();
380             // set the interrupted status
381             Thread.currentThread().interrupt();
382         }
383 
384         return path;
385     }
386 
387     /**
388      * Gets the installation path from the .sverionrc file in the user's home
389      * directory.
390      *
391      * <p>This method is called on Unix/Linux platforms only.
392      * The .sversionrc file is written during setup and will be omitted for
393      * OOo 2.0.</p>
394      *
395      * @return the installation path or <code>null</code>, if no installation
396      *         was found or if an error occured
397      */
getPathFromSVersionFile()398     private static String getPathFromSVersionFile() {
399 
400         final String SVERSION = ".sversionrc"; // Unix/Linux only
401         final String VERSIONS = "[Versions]";
402 
403         String path = null;
404 
405         try {
406             File fSVersion = new File(
407                 System.getProperty( "user.home" ) ,SVERSION );
408             if ( fSVersion.exists() ) {
409                 Vector lines = new Vector();
410                 BufferedReader br = null;
411                 try {
412                     br = new BufferedReader( new InputStreamReader(
413                         new FileInputStream( fSVersion ), "UTF-8" ) );
414                     String line = null;
415                     while ( ( line = br.readLine() ) != null &&
416                             ( line.equals( VERSIONS ) ) != true ) {
417                         // read lines until [Versions] is found
418                     }
419                     while ( ( line = br.readLine() ) != null &&
420                             line.length() != 0 ) {
421                         if ( !line.startsWith( ";" ) )
422                             lines.add( line );
423                     }
424                 } catch ( IOException e ) {
425                     // if an I/O exception is thrown, try to analyze the lines
426                     // read so far
427                     System.err.println( "com.sun.star.lib.loader." +
428                         "InstallationFinder::getPathFromSVersionFile: " +
429                         "reading .sversionrc file failed: " + e );
430                 } finally {
431                     if ( br != null ) {
432                         try {
433                             br.close();
434                         } catch ( IOException e ) {
435                             // closing .sversionrc failed, ignore
436                         }
437                     }
438                 }
439                 for ( int i = lines.size() - 1; i >= 0; i-- ) {
440                     StringTokenizer tokens = new StringTokenizer(
441                         (String)lines.elementAt( i ), "=" );
442                     if ( tokens.countTokens() != 2 )
443                         continue;
444                     String key = tokens.nextToken();
445                     String url = tokens.nextToken();
446                     path = getCanonicalPathFromFileURL( url );
447                     if ( path != null )
448                         break;
449                 }
450             }
451         } catch ( SecurityException e ) {
452             return null;
453         }
454 
455         return path;
456     }
457 
458     /**
459      * Translates an OOo-internal absolute file URL reference (encoded using
460      * UTF-8) into a Java canonical pathname.
461      *
462      * @param oooUrl any URL reference; any fragment part is ignored
463      *
464      * @return if the given URL is a valid absolute, local (that is, the host
465      * part is empty or equal to "localhost", ignoring case) file URL, it is
466      * converted into an absolute canonical pathname; otherwise,
467      * <code>null</code> is returned
468      */
getCanonicalPathFromFileURL( String oooUrl )469     private static String getCanonicalPathFromFileURL( String oooUrl ) {
470 
471         String prefix = "file://";
472         if (oooUrl.length() < prefix.length()
473             || !oooUrl.substring(0, prefix.length()).toLowerCase().equals(
474                 prefix))
475         {
476             return null;
477         }
478         StringBuffer buf = new StringBuffer(prefix);
479         int n = oooUrl.indexOf('/', prefix.length());
480         if (n < 0) {
481             n = oooUrl.length();
482         }
483         String host = oooUrl.substring(prefix.length(), n);
484         if (host.length() != 0 && !host.toLowerCase().equals("localhost")) {
485             return null;
486         }
487         buf.append(host);
488         if (n == oooUrl.length()) {
489             buf.append('/');
490         } else {
491         loop:
492             while (n < oooUrl.length()) {
493                 buf.append('/');
494                 ++n;
495                 int n2 = oooUrl.indexOf('/', n);
496                 if (n2 < 0) {
497                     n2 = oooUrl.length();
498                 }
499                 while (n < n2) {
500                     char c = oooUrl.charAt(n);
501                     switch (c) {
502                     case '%':
503                         byte[] bytes = new byte[(n2 - n) / 3];
504                         int len = 0;
505                         while (oooUrl.length() - n > 2
506                                && oooUrl.charAt(n) == '%')
507                         {
508                             int d1 = Character.digit(oooUrl.charAt(n + 1), 16);
509                             int d2 = Character.digit(oooUrl.charAt(n + 2), 16);
510                             if (d1 < 0 || d2 < 0) {
511                                 break;
512                             }
513                             int d = 16 * d1 + d2;
514                             if (d == '/') {
515                                 return null;
516                             }
517                             bytes[len++] = (byte) d;
518                             n += 3;
519                         }
520                         String s;
521                         try {
522                             s = new String(bytes, 0, len, "UTF-8");
523                         } catch (UnsupportedEncodingException e) {
524                             return null;
525                         }
526                         buf.append(s);
527                         break;
528 
529                     case '#':
530                         break loop;
531 
532                     default:
533                         buf.append(c);
534                         ++n;
535                         break;
536                     }
537                 }
538             }
539         }
540         URL url;
541         try {
542             url = new URL(buf.toString());
543         } catch (MalformedURLException e) {
544             return null;
545         }
546         String path = url.getFile();
547         String fragment = url.getRef();
548         if (fragment != null) {
549             path += '#' + fragment;
550         }
551         String ret = null;
552         File file = new File( path, SOFFICE );
553         try {
554             if ( file.isAbsolute() && file.exists() ) {
555                 try {
556                     // resolve symlink
557                     ret = file.getCanonicalFile().getParent();
558                 } catch ( IOException e ) {
559                     return null;
560                 }
561             }
562         } catch ( SecurityException e ) {
563             return null;
564         }
565 
566         return ret;
567     }
568 
569     /**
570        This class is used for emptying any stream which is passed into it in
571        a separate thread.
572      */
573     private static final class StreamGobbler extends Thread {
574 
575         InputStream m_istream;
576 
StreamGobbler( InputStream istream )577         StreamGobbler( InputStream istream ) {
578             m_istream = istream;
579         }
580 
run()581         public void run() {
582             try {
583                 BufferedReader br = new BufferedReader(
584                     new InputStreamReader( m_istream ) );
585                 // read from input stream
586                 while ( br.readLine() != null ) {
587                     // don't handle line content
588                 }
589                 br.close();
590             } catch ( IOException e ) {
591                 // stop reading from input stream
592             }
593         }
594     }
595 }
596