1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.crypto;
19
20 import java.io.BufferedInputStream;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.StandardCopyOption;
27 import java.nio.file.attribute.PosixFileAttributes;
28 import java.util.Objects;
29 import java.util.Properties;
30 import java.util.UUID;
31
32 import org.apache.commons.crypto.utils.Utils;
33
34
35
36
37
38
39 final class NativeCodeLoader {
40
41 private static final String SIMPLE_NAME = NativeCodeLoader.class.getSimpleName();
42
43 private static final String NATIVE_LIBNAME = "commons-crypto";
44
45 private static final String NATIVE_LIBNAME_ALT = "lib" + NATIVE_LIBNAME + ".jnilib";
46
47
48
49
50 private static final int EOF = -1;
51
52 private static final Throwable libraryLoadingError;
53
54 private static final boolean libraryLoaded;
55
56 static {
57 debug("%s static init start", SIMPLE_NAME);
58 libraryLoadingError = loadLibrary();
59 libraryLoaded = libraryLoadingError == null;
60 debug("%s libraryLoaded = %s, libraryLoadingError = %s", SIMPLE_NAME, libraryLoaded, libraryLoadingError);
61 debug("%s static init end", SIMPLE_NAME);
62 }
63
64
65
66
67
68
69
70
71
72
73
74
75
76 @SuppressWarnings("resource")
77 private static BufferedInputStream buffer(final InputStream inputStream) {
78
79
80 Objects.requireNonNull(inputStream, "inputStream");
81 return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream
82 : new BufferedInputStream(inputStream);
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96 @SuppressWarnings("resource")
97 private static boolean contentsEquals(final InputStream input1, final InputStream input2) throws IOException {
98 if (input1 == input2) {
99 return true;
100 }
101 if (input1 == null ^ input2 == null) {
102 return false;
103 }
104 final BufferedInputStream bufferedInput1 = buffer(input1);
105 final BufferedInputStream bufferedInput2 = buffer(input2);
106 int ch = bufferedInput1.read();
107 while (EOF != ch) {
108 final int ch2 = bufferedInput2.read();
109 if (ch != ch2) {
110 return false;
111 }
112 ch = bufferedInput1.read();
113 }
114 return bufferedInput2.read() == EOF;
115 }
116
117
118
119
120
121
122
123 private static void debug(final String format, final Object... args) {
124
125 if (isDebug()) {
126 System.out.println(String.format(format, args));
127 if (args != null && args.length > 0 && args[0] instanceof Throwable) {
128 ((Throwable) args[0]).printStackTrace(System.out);
129 }
130 }
131 }
132
133
134
135
136
137
138
139
140
141
142 private static File extractLibraryFile(final String libFolderForCurrentOS, final String libraryFileName,
143 final String targetFolder) {
144 final String nativeLibraryFilePath = libFolderForCurrentOS + File.separator + libraryFileName;
145
146
147
148 final UUID uuid = UUID.randomUUID();
149 final String extractedLibFileName = String.format("commons-crypto-%s-%s", uuid, libraryFileName);
150 final File extractedLibFile = new File(targetFolder, extractedLibFileName);
151 debug("Extracting '%s' to '%s'...", nativeLibraryFilePath, extractedLibFile);
152 try (InputStream inputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
153 if (inputStream == null) {
154 debug("Resource not found: %s", nativeLibraryFilePath);
155 return null;
156 }
157
158 final Path path;
159 try {
160 path = extractedLibFile.toPath();
161 final long byteCount = Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
162 if (isDebug()) {
163 debug("Extracted '%s' to '%s': %,d bytes [%s]", nativeLibraryFilePath, extractedLibFile, byteCount,
164 Files.isExecutable(path) ? "X+" : "X-");
165 final PosixFileAttributes attributes = Files.readAttributes(path, PosixFileAttributes.class);
166 if (attributes != null) {
167 debug("Attributes '%s': %s %s %s", extractedLibFile, attributes.permissions(),
168 attributes.owner(), attributes.group());
169 }
170 }
171 } finally {
172
173 debug("Delete on exit: %s", extractedLibFile);
174 extractedLibFile.deleteOnExit();
175 }
176
177
178 if (!extractedLibFile.setReadable(true) || !extractedLibFile.setExecutable(true)
179 || !extractedLibFile.setWritable(true, true)) {
180 throw new IllegalStateException("Invalid path for library path " + extractedLibFile);
181 }
182
183
184
185 try (InputStream nativeInputStream = NativeCodeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
186 try (InputStream extractedLibIn = Files.newInputStream(path)) {
187 debug("Validating '%s'...", extractedLibFile);
188 if (!contentsEquals(nativeInputStream, extractedLibIn)) {
189 throw new IllegalStateException(String.format("Failed to write a native library file %s to %s",
190 nativeLibraryFilePath, extractedLibFile));
191 }
192 }
193 }
194 return extractedLibFile;
195 } catch (final IOException e) {
196 debug("Ignoring %s", e);
197 return null;
198 }
199 }
200
201
202
203
204
205
206 private static File findNativeLibrary() {
207
208 final Properties props = Utils.getDefaultProperties();
209
210
211 String nativeLibraryPath = props.getProperty(Crypto.LIB_PATH_KEY);
212 String nativeLibraryName = props.getProperty(Crypto.LIB_NAME_KEY, System.mapLibraryName(NATIVE_LIBNAME));
213
214 debug("%s nativeLibraryPath %s = %s", SIMPLE_NAME, Crypto.LIB_PATH_KEY, nativeLibraryPath);
215 debug("%s nativeLibraryName %s = %s", SIMPLE_NAME, Crypto.LIB_NAME_KEY, nativeLibraryName);
216
217 if (nativeLibraryPath != null) {
218 final File nativeLib = new File(nativeLibraryPath, nativeLibraryName);
219 final boolean exists = nativeLib.exists();
220 debug("%s nativeLib %s exists = %s", SIMPLE_NAME, nativeLib, exists);
221 if (exists) {
222 return nativeLib;
223 }
224 }
225
226
227 nativeLibraryPath = "/org/apache/commons/crypto/native/" + OsInfo.getNativeLibFolderPathForCurrentOS();
228 debug("%s nativeLibraryPath = %s", SIMPLE_NAME, nativeLibraryPath);
229 final String resource = nativeLibraryPath + File.separator + nativeLibraryName;
230 boolean hasNativeLib = hasResource(resource);
231 debug("%s resource %s exists = %s", SIMPLE_NAME, resource, hasNativeLib);
232 if (!hasNativeLib) {
233 final String altName = NATIVE_LIBNAME_ALT;
234 if (OsInfo.getOSName().equals("Mac") && hasResource(nativeLibraryPath + File.separator + altName)) {
235
236 nativeLibraryName = altName;
237 hasNativeLib = true;
238 }
239 }
240
241 if (!hasNativeLib) {
242 final String errorMessage = String.format("No native library is found for os.name=%s and os.arch=%s", OsInfo.getOSName(), OsInfo.getArchName());
243 throw new IllegalStateException(errorMessage);
244 }
245
246
247
248 final String tempFolder = new File(props.getProperty(Crypto.LIB_TEMPDIR_KEY, System.getProperty("java.io.tmpdir"))).getAbsolutePath();
249
250
251 return extractLibraryFile(nativeLibraryPath, nativeLibraryName, tempFolder);
252 }
253
254
255
256
257
258
259 static Throwable getLoadingError() {
260 return libraryLoadingError;
261 }
262
263
264
265
266
267
268
269 private static boolean hasResource(final String path) {
270 return NativeCodeLoader.class.getResource(path) != null;
271 }
272
273 private static boolean isDebug() {
274 return Boolean.getBoolean(Crypto.CONF_PREFIX + "debug");
275 }
276
277
278
279
280
281
282 static boolean isNativeCodeLoaded() {
283 return libraryLoaded;
284 }
285
286
287
288
289
290
291 static Throwable loadLibrary() {
292 try {
293 final File nativeLibFile = findNativeLibrary();
294 if (nativeLibFile != null) {
295
296 final String absolutePath = nativeLibFile.getAbsolutePath();
297 debug("%s System.load('%s')", SIMPLE_NAME, absolutePath);
298 System.load(absolutePath);
299 } else {
300
301 final String libname = NATIVE_LIBNAME;
302 debug("%s System.loadLibrary('%s')", SIMPLE_NAME, libname);
303 System.loadLibrary(libname);
304 }
305 return null;
306 } catch (final Exception | UnsatisfiedLinkError t) {
307 return t;
308 }
309 }
310
311
312
313
314 private NativeCodeLoader() {
315 }
316 }