1 | package com.renomad.minum.utils; | |
2 | ||
3 | import com.renomad.minum.state.Constants; | |
4 | import com.renomad.minum.logging.ILogger; | |
5 | ||
6 | import java.io.IOException; | |
7 | import java.nio.charset.StandardCharsets; | |
8 | import java.nio.file.Files; | |
9 | import java.nio.file.Path; | |
10 | import java.util.*; | |
11 | import java.util.regex.Pattern; | |
12 | import java.util.stream.Stream; | |
13 | ||
14 | /** | |
15 | * Helper functions for working with files. | |
16 | * <br> | |
17 | * In all these functions, note that it is disallowed to request a path | |
18 | * having certain characters - see {@link #badFilePathPatterns} | |
19 | */ | |
20 | public final class FileUtils { | |
21 | ||
22 | /** | |
23 | * These patterns can be used in path strings to access files higher in | |
24 | * the directory structure. We disallow this, as a security precaution. | |
25 | * <ul> | |
26 | * <li>1st Alternative {@code //} - This prevents going to the root directory | |
27 | * <li>2nd Alternative {@code ..} - prevents going up a directory | |
28 | * <li>3rd Alternative {@code :} - prevents certain special paths, like "C:" or "file://" | |
29 | * </ul> | |
30 | */ | |
31 | public static final Pattern badFilePathPatterns = Pattern.compile("//|\\.\\.|:"); | |
32 | private final ILogger logger; | |
33 | private final IFileReader fileReader; | |
34 | ||
35 | public FileUtils(ILogger logger, Constants constants) { | |
36 | this( | |
37 | logger, | |
38 | new FileReader( | |
39 | LRUCache.getLruCache(constants.maxElementsLruCacheStaticFiles), | |
40 | constants.useCacheForStaticFiles, | |
41 | logger)); | |
42 | } | |
43 | ||
44 | /** | |
45 | * This version of the constructor is mainly for testing | |
46 | */ | |
47 | FileUtils(ILogger logger, IFileReader fileReader) { | |
48 | this.logger = logger; | |
49 | this.fileReader = fileReader; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Write a string to a path on disk. | |
54 | * <p> | |
55 | * Parent directories are made unavailable by searching the path for | |
56 | * bad characters. See {@link #badFilePathPatterns} | |
57 | * </p> | |
58 | */ | |
59 | public void writeString(Path path, String content) { | |
60 |
1
1. writeString : negated conditional → TIMED_OUT |
if (path.toString().isEmpty()) { |
61 | logger.logDebug(() -> "an empty path was provided to writeString"); | |
62 | return; | |
63 | } | |
64 |
1
1. writeString : negated conditional → TIMED_OUT |
if (badFilePathPatterns.matcher(path.toString()).find()) { |
65 | logger.logDebug(() -> String.format("Bad path requested at writeString: %s", path)); | |
66 | return; | |
67 | } | |
68 | try { | |
69 | Files.writeString(path, content); | |
70 | } catch (IOException e) { | |
71 | throw new UtilsException(e); | |
72 | } | |
73 | } | |
74 | ||
75 | /** | |
76 | * Deletes a directory, deleting everything inside it | |
77 | * recursively afterwards. A more dangerous method than | |
78 | * many others, take care. | |
79 | * <p> | |
80 | * Parent directories are made unavailable by searching the path for | |
81 | * bad characters. See {@link #badFilePathPatterns} | |
82 | * </p> | |
83 | */ | |
84 | public void deleteDirectoryRecursivelyIfExists(Path myPath) { | |
85 |
1
1. deleteDirectoryRecursivelyIfExists : negated conditional → KILLED |
if (badFilePathPatterns.matcher(myPath.toString()).find()) { |
86 | logger.logDebug(() -> String.format("Bad path requested at deleteDirectoryRecursivelyIfExists: %s", myPath)); | |
87 | return; | |
88 | } | |
89 |
1
1. deleteDirectoryRecursivelyIfExists : negated conditional → KILLED |
if (!Files.exists(myPath)) { |
90 | logger.logDebug(() -> "system was requested to delete directory: "+myPath+", but it did not exist"); | |
91 | } else { | |
92 |
1
1. deleteDirectoryRecursivelyIfExists : removed call to com/renomad/minum/utils/FileUtils::walkPathDeleting → KILLED |
walkPathDeleting(myPath); |
93 | } | |
94 | } | |
95 | ||
96 | void walkPathDeleting(Path myPath) { | |
97 | try (Stream<Path> walk = Files.walk(myPath)) { | |
98 | ||
99 | final var files = walk.sorted(Comparator.reverseOrder()) | |
100 | .map(Path::toFile).toList(); | |
101 | ||
102 | for(var file: files) { | |
103 | logger.logDebug(() -> "deleting " + file); | |
104 |
1
1. walkPathDeleting : removed call to java/nio/file/Files::delete → KILLED |
Files.delete(file.toPath()); |
105 | } | |
106 | } catch (IOException ex) { | |
107 | throw new UtilsException("Error during deleteDirectoryRecursivelyIfExists: " + ex); | |
108 | } | |
109 | } | |
110 | ||
111 | /** | |
112 | * Creates a directory if it doesn't already exist. | |
113 | * <p> | |
114 | * Parent directories are made unavailable by searching the path for | |
115 | * bad characters. See {@link #badFilePathPatterns} | |
116 | * </p> | |
117 | * <p> | |
118 | * If the directory does exist, the program will simply skip | |
119 | * building it, and mention it in the logs. | |
120 | * </p> | |
121 | */ | |
122 | public void makeDirectory(Path directory) { | |
123 |
1
1. makeDirectory : negated conditional → TIMED_OUT |
if (badFilePathPatterns.matcher(directory.toString()).find()) { |
124 | logger.logDebug(() -> String.format("Bad path requested at makeDirectory: %s", directory)); | |
125 | return; | |
126 | } | |
127 | logger.logDebug(() -> "Creating a directory " + directory); | |
128 | boolean directoryExists = Files.exists(directory); | |
129 | logger.logDebug(() -> "Directory: " + directory + ". Already exists: " + directory); | |
130 |
1
1. makeDirectory : negated conditional → TIMED_OUT |
if (!directoryExists) { |
131 | logger.logDebug(() -> "Creating directory, since it does not already exist: " + directory); | |
132 |
1
1. makeDirectory : removed call to com/renomad/minum/utils/FileUtils::innerCreateDirectory → KILLED |
innerCreateDirectory(directory); |
133 | logger.logDebug(() -> "Directory: " + directory + " created"); | |
134 | } | |
135 | } | |
136 | ||
137 | static void innerCreateDirectory(Path directory) { | |
138 | try { | |
139 | Files.createDirectories(directory); | |
140 | } catch (Exception e) { | |
141 | throw new UtilsException(e); | |
142 | } | |
143 | } | |
144 | ||
145 | /** | |
146 | * Read a binary file, return as a byte array | |
147 | * <p> | |
148 | * If there is an error, this will return an empty byte array. | |
149 | * </p> | |
150 | */ | |
151 | public byte[] readBinaryFile(String path) { | |
152 | try { | |
153 |
1
1. readBinaryFile : replaced return value with null for com/renomad/minum/utils/FileUtils::readBinaryFile → KILLED |
return fileReader.readFile(path); |
154 | } catch (IOException e) { | |
155 | logger.logDebug(() -> String.format("Error while reading file %s, returning empty byte array. %s", path, e)); | |
156 |
1
1. readBinaryFile : replaced return value with null for com/renomad/minum/utils/FileUtils::readBinaryFile → KILLED |
return new byte[0]; |
157 | } | |
158 | } | |
159 | ||
160 | /** | |
161 | * Read a text file from the given path, return as a string. | |
162 | * | |
163 | * <p> | |
164 | * Access is prevented to data in parent directories or using alternate | |
165 | * drives. If the data is read, it will be added to a cache, if | |
166 | * the property {@link Constants#useCacheForStaticFiles} is set to true. The maximum | |
167 | * size of the cache is controlled by | |
168 | * </p> | |
169 | * <p> | |
170 | * If there is an error, this will return an empty string. | |
171 | * </p> | |
172 | */ | |
173 | public String readTextFile(String path) { | |
174 | try { | |
175 |
1
1. readTextFile : replaced return value with "" for com/renomad/minum/utils/FileUtils::readTextFile → KILLED |
return new String(fileReader.readFile(path), StandardCharsets.UTF_8); |
176 | } catch (IOException e) { | |
177 | logger.logDebug(() -> String.format("Error while reading file %s, returning empty string. %s", path, e)); | |
178 | return ""; | |
179 | } | |
180 | } | |
181 | ||
182 | } | |
Mutations | ||
60 |
1.1 |
|
64 |
1.1 |
|
85 |
1.1 |
|
89 |
1.1 |
|
92 |
1.1 |
|
104 |
1.1 |
|
123 |
1.1 |
|
130 |
1.1 |
|
132 |
1.1 |
|
153 |
1.1 |
|
156 |
1.1 |
|
175 |
1.1 |