22
22
23
23
import java .io .CharArrayWriter ;
24
24
import java .io .File ;
25
+ import java .io .FileInputStream ;
25
26
import java .io .FileOutputStream ;
26
27
import java .io .IOException ;
27
28
import java .io .InputStream ;
28
29
import java .io .PrintWriter ;
29
30
import java .nio .channels .Channels ;
30
31
import java .nio .channels .ReadableByteChannel ;
32
+ import java .security .DigestInputStream ;
33
+ import java .security .MessageDigest ;
34
+ import java .security .NoSuchAlgorithmException ;
31
35
import java .util .ArrayList ;
32
36
import java .util .Arrays ;
33
37
import java .util .Collection ;
@@ -67,14 +71,25 @@ public class StubLoader {
67
71
private static volatile Throwable failureCause = null ;
68
72
private static volatile boolean loaded = false ;
69
73
private static final File jffiExtractDir ;
74
+ private static final String jffiExtractName ;
75
+
76
+ private static final String JFFI_EXTRACT_DIR = "jffi.extract.dir" ;
77
+ private static final String JFFI_EXTRACT_NAME = "jffi.extract.name" ;
70
78
71
79
static {
72
- String extractDir = System .getProperty ("jffi.extract.dir" );
80
+ String extractDir = System .getProperty (JFFI_EXTRACT_DIR );
73
81
if (extractDir != null ) {
74
82
jffiExtractDir = new File (extractDir );
75
83
} else {
76
84
jffiExtractDir = null ;
77
85
}
86
+
87
+ String extractName = System .getProperty (JFFI_EXTRACT_NAME );
88
+ if (extractName != null ) {
89
+ jffiExtractName = extractName ;
90
+ } else {
91
+ jffiExtractName = null ;
92
+ }
78
93
}
79
94
80
95
public static final boolean isLoaded () {
@@ -286,19 +301,27 @@ static void load() {
286
301
try {
287
302
loadFromJar (jffiExtractDir );
288
303
return ;
304
+ } catch (SecurityException se ) {
305
+ throw se ;
289
306
} catch (Throwable t1 ) {
290
- throw new UnsatisfiedLinkError ("could not load jffi library from " + jffiExtractDir );
307
+ UnsatisfiedLinkError ule = new UnsatisfiedLinkError ("could not load jffi library from " + jffiExtractDir );
308
+ ule .initCause (t1 );
309
+ throw ule ;
291
310
}
292
311
}
293
312
294
313
// try default tmp location with failover to current directory
295
314
try {
296
315
loadFromJar (null );
297
316
return ;
317
+ } catch (SecurityException se ) {
318
+ throw se ;
298
319
} catch (Throwable t ) {
299
- try {
320
+ try {
300
321
loadFromJar (new File (System .getProperty ("user.dir" )));
301
- }catch (Throwable t1 ){
322
+ } catch (SecurityException se ) {
323
+ throw se ;
324
+ } catch (Throwable t1 ){
302
325
errors .add (t1 );
303
326
}
304
327
}
@@ -384,7 +407,7 @@ private static boolean loadFromBootPath(String libName, String bootPath, Collect
384
407
return false ;
385
408
}
386
409
387
- private static String dlExtension () {
410
+ static String dlExtension () {
388
411
switch (getOS ()) {
389
412
case WINDOWS :
390
413
return "dll" ;
@@ -396,44 +419,120 @@ private static String dlExtension() {
396
419
}
397
420
398
421
private static void loadFromJar (File tmpDirFile ) throws IOException , LinkageError {
399
- InputStream is = getStubLibraryStream ();
400
422
File dstFile ;
401
423
402
- // Install the stub library to a temporary location
403
- try {
424
+ // Install the stub library
425
+ String jffiExtractName = StubLoader . jffiExtractName ;
404
426
405
- // Create tempfile.
406
- dstFile = null == tmpDirFile ? File .createTempFile ("jffi" , "." + dlExtension ()):
407
- File .createTempFile ("jffi" , "." + dlExtension (), tmpDirFile );
408
- dstFile .deleteOnExit ();
427
+ try (InputStream sourceIS = getStubLibraryStream ()) {
428
+ dstFile = calculateExtractPath (tmpDirFile , jffiExtractName );
409
429
410
- // Write the library to the tempfile
411
- FileOutputStream os = new FileOutputStream (dstFile );
412
- try {
413
- ReadableByteChannel srcChannel = Channels .newChannel (is );
414
-
415
- for (long pos = 0 ; is .available () > 0 ; ) {
416
- pos += os .getChannel ().transferFrom (srcChannel , pos , Math .max (4096 , is .available ()));
417
- }
418
- } finally {
419
- os .close ();
430
+ if (jffiExtractName != null && dstFile .exists ()) {
431
+ // unpacking to a specific name and that file exists, verify it
432
+ verifyExistingLibrary (dstFile , sourceIS );
433
+ } else {
434
+ unpackLibrary (dstFile , sourceIS );
420
435
}
421
436
} catch (IOException ioe ) {
422
437
// If we get here it means we are unable to write the stub library to the system default temp location.
423
438
throw tempReadonlyError (ioe );
424
- } finally {
425
- is .close ();
426
439
}
427
440
428
441
try {
429
442
System .load (dstFile .getAbsolutePath ());
430
- dstFile .delete ();
443
+ if ( null == jffiExtractName ) dstFile .delete ();
431
444
} catch (UnsatisfiedLinkError ule ) {
432
445
// If we get here it means the file wrote to temp ok but can't be loaded from there.
433
446
throw tempLoadError (ule );
434
447
}
435
448
}
436
449
450
+ private static void unpackLibrary (File dstFile , InputStream sourceIS ) throws IOException {
451
+ // Write the library to the tempfile
452
+ try (FileOutputStream os = new FileOutputStream (dstFile )) {
453
+ ReadableByteChannel srcChannel = Channels .newChannel (sourceIS );
454
+
455
+ for (long pos = 0 ; sourceIS .available () > 0 ; ) {
456
+ pos += os .getChannel ().transferFrom (srcChannel , pos , Math .max (4096 , sourceIS .available ()));
457
+ }
458
+ }
459
+ }
460
+
461
+ private static void verifyExistingLibrary (File dstFile , InputStream sourceIS ) throws IOException {
462
+ int sourceSize = sourceIS .available ();
463
+
464
+ try (FileInputStream targetIS = new FileInputStream (dstFile )) {
465
+ // perform minimal verification of the found file
466
+ int targetSize = targetIS .available ();
467
+ if (targetSize != sourceSize ) throw sizeMismatchError (dstFile , sourceSize , targetSize );
468
+
469
+ // compare sha-256 for existing file
470
+ MessageDigest sourceMD = MessageDigest .getInstance ("SHA-256" );
471
+ MessageDigest targetMD = MessageDigest .getInstance ("SHA-256" );
472
+ DigestInputStream sourceDIS = new DigestInputStream (sourceIS , sourceMD );
473
+ DigestInputStream targetDIS = new DigestInputStream (targetIS , targetMD );
474
+ byte [] buf = new byte [8192 ];
475
+ while (sourceIS .available () > 0 ) {
476
+ sourceDIS .read (buf );
477
+ targetDIS .read (buf );
478
+ }
479
+ byte [] sourceDigest = sourceMD .digest ();
480
+ byte [] targetDigest = targetMD .digest ();
481
+
482
+ if (!Arrays .equals (sourceDigest , targetDigest )) throw digestMismatchError (dstFile );
483
+ } catch (NoSuchAlgorithmException nsae ) {
484
+ throw new IOException (nsae );
485
+ }
486
+ }
487
+
488
+ private static SecurityException sizeMismatchError (File dstFile , int sourceSize , int targetSize ) {
489
+ return new SecurityException ("file size mismatch: " + dstFile + " (" + targetSize + ") does not match packaged library (" + sourceSize + ")" );
490
+ }
491
+
492
+ private static SecurityException digestMismatchError (File dstFile ) {
493
+ return new SecurityException ("digest mismatch: " + dstFile + " does not match packaged library" );
494
+ }
495
+
496
+ static File calculateExtractPath (File tmpDirFile , String jffiExtractName ) throws IOException {
497
+ if (jffiExtractName == null ) return calculateExtractPath (tmpDirFile );
498
+
499
+ File dstFile ;
500
+
501
+ // allow empty name to mean "jffi-#.#"
502
+ if (jffiExtractName .isEmpty ()) {
503
+ jffiExtractName = "jffi-" + VERSION_MAJOR + "." + VERSION_MINOR ;
504
+ }
505
+
506
+ // add extension if necessary
507
+ if (!jffiExtractName .endsWith (dlExtension ())) {
508
+ jffiExtractName = jffiExtractName + "." + dlExtension ();
509
+ }
510
+
511
+ // use tmpdir if no dir was specified
512
+ if (null == tmpDirFile ) {
513
+ dstFile = new File (TMPDIR , jffiExtractName );
514
+ } else {
515
+ dstFile = new File (tmpDirFile , jffiExtractName );
516
+ }
517
+
518
+ return dstFile ;
519
+ }
520
+
521
+ static File calculateExtractPath (File tmpDirFile ) throws IOException {
522
+ File dstFile ;
523
+
524
+ // create tempfile
525
+ if (null == tmpDirFile ) {
526
+ dstFile = File .createTempFile ("jffi" , "." + dlExtension ());
527
+ } else {
528
+ dstFile = File .createTempFile ("jffi" , "." + dlExtension (), tmpDirFile );
529
+ }
530
+
531
+ dstFile .deleteOnExit ();
532
+
533
+ return dstFile ;
534
+ }
535
+
437
536
private static IOException tempReadonlyError (IOException ioe ) {
438
537
return new IOException (
439
538
TMPDIR_WRITE_ERROR + " " +
0 commit comments