1 /* 2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntool.utils; 17 18 import com.google.gson.Gson; 19 import com.google.gson.GsonBuilder; 20 import com.ohos.hapsigntool.error.CustomException; 21 import com.ohos.hapsigntool.error.ERROR; 22 import com.ohos.hapsigntool.error.SignToolErrMsg; 23 24 import java.io.BufferedWriter; 25 import java.io.ByteArrayOutputStream; 26 import java.io.Closeable; 27 import java.io.DataOutputStream; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.FileNotFoundException; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.io.OutputStreamWriter; 36 import java.nio.charset.Charset; 37 import java.nio.file.Files; 38 39 /** 40 * Common file operation. 41 * 42 * @since 2021/12/28 43 */ 44 public final class FileUtils { 45 /** 46 * LOGGER. 47 */ 48 private static final LogUtils LOGGER = new LogUtils(FileUtils.class); 49 50 /** 51 * add GSON static. 52 */ 53 public static final Gson GSON = (new GsonBuilder()).disableHtmlEscaping().create(); 54 55 /** 56 * add GSON_PRETTY_PRINT static. 57 */ 58 public static final Gson GSON_PRETTY_PRINT = (new GsonBuilder()).disableHtmlEscaping().setPrettyPrinting().create(); 59 60 /** 61 * File reader block size 62 */ 63 public static final int FILE_BUFFER_BLOCK = 1024 * 1024; 64 65 /** 66 * File end 67 */ 68 public static final int FILE_END = -1; 69 70 /** 71 * Expected split string length 72 */ 73 public static final int SPLIT_LENGTH = 2; 74 75 /** 76 * libs dir path 77 */ 78 public static final String LIBS_PATH_PREFIX = "libs/"; 79 80 /** 81 * abc file suffix 82 */ 83 public static final String ABC_FILE_SUFFIX = ".abc"; 84 85 /** 86 * an file suffix 87 */ 88 public static final String NATIVE_LIB_AN_SUFFIX = ".an"; 89 90 /** 91 * bitmap file name 92 */ 93 public static final String BIT_MAP_FILENAME = ".pages.info"; 94 95 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 96 FileUtils()97 private FileUtils() { 98 } 99 100 /** 101 * Close closeable quietly. 102 * 103 * @param closeable closeable 104 */ close(Closeable closeable)105 public static void close(Closeable closeable) { 106 if (closeable != null) { 107 try { 108 closeable.close(); 109 } catch (IOException exception) { 110 LOGGER.debug(exception.getMessage(), exception); 111 } 112 } 113 } 114 115 /** 116 * Read byte from input file. 117 * 118 * @param file Which file to read 119 * @return byte content 120 * @throws IOException Read failed 121 */ readFile(File file)122 public static byte[] readFile(File file) throws IOException { 123 return read(Files.newInputStream(file.toPath())); 124 } 125 126 /** 127 * Read byte from input stream. 128 * 129 * @param input Input stream 130 * @return File content 131 * @throws IOException Read failed 132 */ read(InputStream input)133 public static byte[] read(InputStream input) throws IOException { 134 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 135 byte[] buffer = new byte[FILE_BUFFER_BLOCK]; 136 int read; 137 while ((read = input.read(buffer)) != FILE_END) { 138 output.write(buffer, 0, read); 139 } 140 return output.toByteArray(); 141 } finally { 142 close(input); 143 } 144 } 145 146 /** 147 * Read byte from input file. 148 * 149 * @param file input file 150 * @param offset offset 151 * @param length length 152 * @return data bytes 153 */ readFileByOffsetAndLength(File file, long offset, long length)154 public static byte[] readFileByOffsetAndLength(File file, long offset, long length) throws IOException { 155 try (FileInputStream input = new FileInputStream(file)) { 156 return readInputByOffsetAndLength(input, offset, length); 157 } 158 } 159 160 /** 161 * Read byte from input stream. 162 * 163 * @param input input stream 164 * @param offset offset 165 * @param length length 166 * @return data bytes 167 * @throws IOException read exception 168 */ readInputByOffsetAndLength(InputStream input, long offset, long length)169 public static byte[] readInputByOffsetAndLength(InputStream input, long offset, long length) throws IOException { 170 input.skip(offset); 171 return readInputByLength(input, length); 172 } 173 174 /** 175 * Read byte from input stream. 176 * 177 * @param input InputStream 178 * @param length length 179 * @return data bytes 180 */ readInputByLength(InputStream input, long length)181 public static byte[] readInputByLength(InputStream input, long length) throws IOException { 182 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { 183 if (length > Integer.MAX_VALUE) { 184 throw new IOException("Size cannot be greater than Integer max value: " + length); 185 } 186 writeInputToOutPut(input, output, length); 187 return output.toByteArray(); 188 } 189 } 190 191 /** 192 * write input to output by length 193 */ writeInputToOutPut(InputStream input, OutputStream output, long length)194 private static void writeInputToOutPut(InputStream input, OutputStream output, long length) throws IOException { 195 byte[] buffer = new byte[FILE_BUFFER_BLOCK]; 196 long hasReadLen = 0L; 197 while (hasReadLen < length) { 198 int readLen = (int) Math.min(length - hasReadLen, FILE_BUFFER_BLOCK); 199 int len = input.read(buffer, 0, readLen); 200 if (len != readLen) { 201 throw new IOException("read" + hasReadLen + "bytes data less than " + length); 202 } 203 output.write(buffer, 0, len); 204 hasReadLen += len; 205 } 206 } 207 208 /** 209 * Out put content to file. 210 * 211 * @param content Which content to out put 212 * @param output File to write 213 * @throws IOException Write failed 214 */ write(byte[] content, File output)215 public static void write(byte[] content, File output) throws IOException { 216 if (output.exists() && !output.canWrite()) { 217 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.FILE_WRITE_FAILED 218 .toString("No permission write to file " + output)); 219 } 220 try (FileOutputStream out = new FileOutputStream(output)) { 221 for (byte con : content) { 222 out.write(con); 223 } 224 } 225 } 226 227 /** 228 * Write data in file to output stream 229 * 230 * @param inFile input file path. 231 * @param out output file path. 232 * @param offset file read offset 233 * @param size file read size 234 * @return true, if write successfully. 235 */ appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size)236 public static boolean appendWriteFileByOffsetToFile(String inFile, FileOutputStream out, long offset, long size) { 237 File inputFile = new File(inFile); 238 try (FileInputStream fis = new FileInputStream(inputFile)) { 239 fis.skip(offset); 240 writeInputToOutPut(fis, out, size); 241 return true; 242 } catch (FileNotFoundException e) { 243 LOGGER.error("Failed to get input stream object."); 244 } catch (IOException e) { 245 LOGGER.error("Failed to read or write data."); 246 } 247 return false; 248 } 249 250 /** 251 * Check file exist or not. 252 * 253 * @param filePath File path 254 * @return Is file exist 255 */ isFileExist(String filePath)256 public static boolean isFileExist(String filePath) { 257 return new File(filePath).exists(); 258 } 259 260 /** 261 * Throw runtime exception if not allowed file type. 262 * 263 * @param filePath file path 264 * @param types Such as "txt" "json" "mp3" 265 */ validFileType(String filePath, String... types)266 public static void validFileType(String filePath, String... types) { 267 String suffix = getSuffix(filePath); 268 ValidateUtils.throwIfNotMatches(!StringUtils.isEmpty(suffix), 269 ERROR.NOT_SUPPORT_ERROR, SignToolErrMsg.NOT_SUPPORT_FILE.toString(filePath)); 270 boolean isMatches = false; 271 for (String type : types) { 272 if (StringUtils.isEmpty(type)) { 273 continue; 274 } 275 if (type.equalsIgnoreCase(suffix)) { 276 isMatches = true; 277 break; 278 } 279 } 280 ValidateUtils.throwIfNotMatches(isMatches, 281 ERROR.NOT_SUPPORT_ERROR, SignToolErrMsg.NOT_SUPPORT_FILE.toString(filePath)); 282 } 283 284 /** 285 * Get suffix of file. 286 * 287 * @param filePath file path 288 * @return file suffix. Such as "txt" "json" "p12" 289 */ getSuffix(String filePath)290 public static String getSuffix(String filePath) { 291 if (StringUtils.isEmpty(filePath)) { 292 return ""; 293 } 294 File file = new File(filePath); 295 String fileName = file.getName(); 296 String[] temps = fileName.split("\\."); 297 if (temps.length < SPLIT_LENGTH) { 298 return ""; 299 } 300 return temps[temps.length - 1]; 301 } 302 303 /** 304 * Write data in file to output stream 305 * 306 * @param file input file path. 307 * @param dos output stream. 308 * @return true, if write successfully. 309 */ writeFileToDos(String file, DataOutputStream dos)310 public static boolean writeFileToDos(String file, DataOutputStream dos) { 311 if (dos == null) { 312 return false; 313 } 314 File src = new File(file); 315 try (FileInputStream fileStream = new FileInputStream(src)) { 316 int temp; 317 byte[] buf = new byte[FILE_BUFFER_BLOCK]; 318 while ((temp = fileStream.read(buf)) > 0) { 319 dos.write(buf, 0, temp); 320 } 321 return true; 322 } catch (FileNotFoundException e) { 323 LOGGER.error("Failed to get input stream object."); 324 } catch (IOException e) { 325 LOGGER.error("Failed to read or write data."); 326 } 327 return false; 328 } 329 330 /** 331 * Write byte array to output stream 332 * 333 * @param data byte array 334 * @param dos output stream 335 * @return true, if write successfully. 336 */ writeByteToDos(byte[] data, DataOutputStream dos)337 public static boolean writeByteToDos(byte[] data, DataOutputStream dos) { 338 if (data == null) { 339 return true; 340 } 341 try { 342 dos.write(data); 343 } catch (IOException e) { 344 LOGGER.error("Failed to write data to output stream."); 345 return false; 346 } 347 return true; 348 } 349 350 /** 351 * Get length of file. 352 * 353 * @param filePath input file path. 354 * @return long value of file length. 355 */ getFileLen(String filePath)356 public static long getFileLen(String filePath) { 357 File file = new File(filePath); 358 if (file.exists() && file.isFile()) { 359 return file.length(); 360 } 361 return -1; 362 } 363 364 /** 365 * Write byte array data to output file. 366 * 367 * @param bytes byte array data. 368 * @param outFile output file path. 369 * @return true, if write successfully. 370 */ writeByteToOutFile(byte[] bytes, String outFile)371 public static boolean writeByteToOutFile(byte[] bytes, String outFile) { 372 try (OutputStream ops = new FileOutputStream(outFile, true)) { 373 return writeByteToOutFile(bytes, ops); 374 } catch (FileNotFoundException e) { 375 LOGGER.error("Failed to get output stream object, outfile: " + outFile); 376 } catch (IOException e) { 377 LOGGER.error("Failed to write data to ops, outfile: " + outFile); 378 } 379 return false; 380 } 381 382 /** 383 * Write byte array data to output file. 384 * 385 * @param bytes byte array data. 386 * @param outFile output file path. 387 * @return true, if write successfully. 388 */ writeByteToOutFile(byte[] bytes, OutputStream outFile)389 public static boolean writeByteToOutFile(byte[] bytes, OutputStream outFile) { 390 try { 391 outFile.write(bytes, 0, bytes.length); 392 outFile.flush(); 393 return true; 394 } catch (FileNotFoundException e) { 395 LOGGER.error("Failed to get output stream object, outfile: " + outFile); 396 } catch (IOException e) { 397 LOGGER.error("Failed to write data to ops, outfile: " + outFile); 398 } 399 return false; 400 } 401 402 /** 403 * Check input file is valid. 404 * 405 * @param file input file. 406 * @throws IOException file is a directory or can't be read. 407 */ isValidFile(File file)408 public static void isValidFile(File file) throws IOException { 409 if (!file.exists()) { 410 throw new FileNotFoundException("File '" + file + "' does not exist"); 411 } 412 413 if (file.isDirectory()) { 414 throw new IOException("File '" + file + "' exists but is a directory"); 415 } 416 417 if (!file.canRead()) { 418 throw new IOException("File '" + file + "' cannot be read"); 419 } 420 } 421 422 /** 423 * Open an input stream of input file safely. 424 * 425 * @param file input file. 426 * @return an input stream of input file 427 * @throws IOException file is a directory or can't be read. 428 */ openInputStream(File file)429 public static FileInputStream openInputStream(File file) throws IOException { 430 isValidFile(file); 431 return new FileInputStream(file); 432 } 433 toByteArray(final InputStream input, final int size)434 private static byte[] toByteArray(final InputStream input, final int size) throws IOException { 435 if (size < 0) { 436 throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); 437 } 438 439 if (size == 0) { 440 return EMPTY_BYTE_ARRAY; 441 } 442 443 final byte[] data = new byte[size]; 444 int offset = 0; 445 int read; 446 447 while (offset < size && (read = input.read(data, offset, size - offset)) != FILE_END) { 448 offset += read; 449 } 450 451 if (offset != size) { 452 throw new IOException("Unexpected read size. current: " + offset + ", expected: " + size); 453 } 454 455 return data; 456 } 457 458 /** 459 * Read file to byte array. 460 * 461 * @param file input file. 462 * @return byte array of file-content. 463 * @throws IOException fill exception 464 */ readFileToByteArray(File file)465 public static byte[] readFileToByteArray(File file) throws IOException { 466 try (InputStream in = openInputStream(file)) { 467 final long fileLength = file.length(); 468 if (fileLength > Integer.MAX_VALUE) { 469 throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + fileLength); 470 } 471 return toByteArray(in, (int) fileLength); 472 } 473 } 474 475 /** 476 * Write a string to file 477 * 478 * @param source String will be written. 479 * @param filePath path to write file. 480 * @param charset a charset 481 * @throws IOException write error. 482 */ writeStringToFile(String source, String filePath, Charset charset)483 public static void writeStringToFile(String source, String filePath, Charset charset) throws IOException { 484 try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( 485 new FileOutputStream(filePath), charset))) { 486 writer.write(source); 487 writer.flush(); 488 } 489 } 490 491 /** 492 * Delete a file quietly 493 * 494 * @param file the file to delete 495 */ deleteFile(File file)496 public static void deleteFile(File file) { 497 if (file != null && file.isFile()) { 498 try { 499 Files.delete(file.toPath()); 500 } catch (IOException e) { 501 LOGGER.warn("delete file '{}' error, error message: {}", file, e.getMessage()); 502 } 503 } 504 } 505 506 /** 507 * regex filename 508 * 509 * @param name filename 510 * @return boolean 511 */ isRunnableFile(String name)512 public static boolean isRunnableFile(String name) { 513 if (StringUtils.isEmpty(name)) { 514 return false; 515 } 516 if (name.endsWith(NATIVE_LIB_AN_SUFFIX) || name.endsWith(ABC_FILE_SUFFIX)) { 517 return true; 518 } 519 if (name.startsWith(LIBS_PATH_PREFIX)) { 520 return true; 521 } 522 return false; 523 } 524 } 525