1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.newsclub.net.unix;
19
20 import java.io.IOException;
21 import java.nio.charset.StandardCharsets;
22 import java.nio.file.FileAlreadyExistsException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.Objects;
27 import java.util.Random;
28
29 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
30
31 abstract class TempFileUtil {
32 static final TempFileUtil INSTANCE = newInstance();
33
34 private TempFileUtil() {
35 }
36
37 private static TempFileUtil newInstance() {
38 if ("Astral".equals(System.getProperty("os.name", ""))) {
39 return new MaxLengthInstance(14);
40 }
41 return new StandardInstance();
42 }
43
44 public static TempFileUtil getInstance() {
45 return INSTANCE;
46 }
47
48 public abstract Path newPathForUnixDomainSocket(boolean delete) throws IOException;
49
50 private static final class StandardInstance extends TempFileUtil {
51 @Override
52 public Path newPathForUnixDomainSocket(boolean delete) throws IOException {
53 Path p = Files.createTempFile("jux", ".sock");
54 if (!Files.deleteIfExists(p) && Files.exists(p)) {
55 throw new IOException("Could not delete temporary file that we just created: " + p);
56 }
57 return p;
58 }
59 }
60
61 @SuppressFBWarnings("PREDICTABLE_RANDOM")
62 private static final class MaxLengthInstance extends TempFileUtil {
63 private final int maxLength;
64 private final Random random = new Random();
65 private final byte[] bytesPrefix = "/tmp/".getBytes(StandardCharsets.US_ASCII);
66 private final byte[] bytesSuffix = ".j".getBytes(StandardCharsets.US_ASCII);
67 private final byte[] validCharacters = {
68 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
69 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
70 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
71 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
72 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
73 '_', '-',
74 };
75
76 public MaxLengthInstance(int maxLength) {
77 super();
78 this.maxLength = maxLength;
79 }
80
81 @Override
82 public Path newPathForUnixDomainSocket(boolean delete) throws IOException {
83 Path p = null;
84
85 while (!Thread.interrupted()) {
86 p = randomPath();
87 if (Files.exists(p)) {
88 continue;
89 }
90 try {
91 Files.createFile(p);
92 break;
93 } catch (FileAlreadyExistsException e) {
94 continue;
95 }
96 }
97 Objects.requireNonNull(p);
98 if (delete && !Files.deleteIfExists(p) && Files.exists(p)) {
99 throw new IOException("Could not delete temporary file that we just created: " + p);
100 }
101 return p;
102 }
103
104 @SuppressFBWarnings("PATH_TRAVERSAL_IN")
105 private Path randomPath() {
106 byte[] bytes = new byte[maxLength];
107 random.nextBytes(bytes);
108 System.arraycopy(bytesPrefix, 0, bytes, 0, bytesPrefix.length);
109 System.arraycopy(bytesSuffix, 0, bytes, bytes.length - bytesSuffix.length,
110 bytesSuffix.length);
111
112 for (int i = bytesPrefix.length, n = bytes.length - bytesSuffix.length; i < n; i++) {
113 bytes[i] = validCharacters[Math.abs(bytes[i]) % validCharacters.length];
114 }
115 return Paths.get(new String(bytes, StandardCharsets.US_ASCII));
116 }
117 }
118 }