001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.utils;
017
018import com.jfinal.core.JFinal;
019import com.jfinal.kit.Base64Kit;
020import com.jfinal.kit.HashKit;
021import com.jfinal.kit.LogKit;
022import com.jfinal.kit.PathKit;
023import com.jfinal.upload.UploadFile;
024
025import java.io.*;
026import java.nio.charset.Charset;
027import java.nio.charset.StandardCharsets;
028import java.security.MessageDigest;
029import java.util.Enumeration;
030import java.util.List;
031import java.util.zip.ZipEntry;
032import java.util.zip.ZipFile;
033
034
035public class FileUtil {
036
037    /**
038     * 获取文件后缀
039     *
040     * @param fileName eg: jboot.jpg
041     * @return suffix eg: .jpg
042     */
043    public static String getSuffix(String fileName) {
044        if (fileName != null && fileName.contains(".")) {
045            return fileName.substring(fileName.lastIndexOf("."));
046        }
047        return null;
048    }
049
050
051    public static String removePrefix(String src, String... prefixes) {
052        if (src != null) {
053            for (String prefix : prefixes) {
054                if (src.startsWith(prefix)) {
055                    return src.substring(prefix.length());
056                }
057            }
058        }
059
060        return src;
061    }
062
063
064    public static String removeSuffix(String src, String... suffixes) {
065        if (src != null) {
066            for (String suffix : suffixes) {
067                if (src.endsWith(suffix)) {
068                    return src.substring(0, suffix.length());
069                }
070            }
071        }
072        return src;
073    }
074
075
076    public static String removeRootPath(String src) {
077        return removePrefix(src, PathKit.getWebRootPath());
078    }
079
080
081    public static String readString(File file) {
082        return readString(file, JFinal.me().getConstants().getEncoding());
083    }
084
085
086    public static String readString(File file, String charsetName) {
087        ByteArrayOutputStream baos = null;
088        FileInputStream fis = null;
089        try {
090            fis = new FileInputStream(file);
091            baos = new ByteArrayOutputStream();
092            byte[] buffer = new byte[1024];
093            for (int len = 0; (len = fis.read(buffer)) > 0; ) {
094                baos.write(buffer, 0, len);
095            }
096            return baos.toString(charsetName);
097        } catch (Exception e) {
098            LogKit.error(e.toString(), e);
099        } finally {
100            close(fis, baos);
101        }
102        return null;
103    }
104
105
106    public static void writeString(File file, String content) {
107        writeString(file, content, JFinal.me().getConstants().getEncoding());
108    }
109
110
111    public static void writeString(File file, String content, String charsetName) {
112        writeString(file, content, charsetName, false);
113    }
114
115    public static void writeString(File file, String content, String charsetName, boolean append) {
116        FileOutputStream fos = null;
117        try {
118            ensuresParentExists(file);
119            fos = new FileOutputStream(file, append);
120            fos.write(content.getBytes(charsetName));
121        } catch (Exception e) {
122            LogKit.error(e.toString(), e);
123        } finally {
124            close(fos);
125        }
126    }
127
128    public static void ensuresParentExists(File currentFile) throws IOException {
129        if (!currentFile.getParentFile().exists()
130                && !currentFile.getParentFile().mkdirs()) {
131            throw new IOException("Can not mkdirs for file: " + currentFile.getParentFile());
132        }
133    }
134
135
136    /**
137     * 获取文件的 md5
138     *
139     * @param file
140     * @return
141     */
142    public static String getFileMD5(File file) {
143        return getFileMD5(file, false);
144    }
145
146
147    /**
148     * 获取文件 md5 的 base64 编码
149     *
150     * @param file
151     * @return
152     */
153    public static String getFileMd5Base64(File file) {
154        return getFileMD5(file, true);
155    }
156
157
158    private static String getFileMD5(File file, boolean withBase64) {
159        try (FileInputStream fiStream = new FileInputStream(file)) {
160            MessageDigest digest = MessageDigest.getInstance("MD5");
161            byte[] buffer = new byte[8192];
162            int length;
163            while ((length = fiStream.read(buffer)) != -1) {
164                digest.update(buffer, 0, length);
165            }
166            return withBase64 ? Base64Kit.encode(digest.digest()) : HashKit.toHex(digest.digest());
167        } catch (Exception e) {
168            LogKit.error(e.toString(), e);
169        }
170        return null;
171    }
172
173
174    public static void close(Closeable... closeable) {
175        QuietlyUtil.closeQuietly(closeable);
176    }
177
178
179    public static void unzip(String zipFilePath) throws IOException {
180        String targetPath = zipFilePath.substring(0, zipFilePath.lastIndexOf("."));
181        unzip(zipFilePath, targetPath, true, StandardCharsets.UTF_8);
182    }
183
184
185    public static void unzip(String zipFilePath, String targetPath) throws IOException {
186        unzip(zipFilePath, targetPath, true, StandardCharsets.UTF_8);
187    }
188
189
190    public static void unzip(String zipFilePath, String targetPath, boolean safeUnzip, Charset charset) throws IOException {
191        targetPath = getCanonicalPath(new File(targetPath));
192        ZipFile zipFile = new ZipFile(zipFilePath, charset);
193        try {
194            Enumeration<?> entryEnum = zipFile.entries();
195            while (entryEnum.hasMoreElements()) {
196                OutputStream os = null;
197                InputStream is = null;
198                try {
199                    ZipEntry zipEntry = (ZipEntry) entryEnum.nextElement();
200                    if (!zipEntry.isDirectory()) {
201                        if (safeUnzip && isNotSafeFile(zipEntry.getName())) {
202                            //Unsafe
203                            continue;
204                        }
205
206                        File targetFile = new File(targetPath, zipEntry.getName());
207
208                        ensuresParentExists(targetFile);
209
210                        if (!targetFile.toPath().normalize().startsWith(targetPath)) {
211                            throw new IOException("Bad zip entry");
212                        }
213
214                        os = new BufferedOutputStream(new FileOutputStream(targetFile));
215                        is = zipFile.getInputStream(zipEntry);
216                        byte[] buffer = new byte[4096];
217                        int readLen = 0;
218                        while ((readLen = is.read(buffer, 0, 4096)) > 0) {
219                            os.write(buffer, 0, readLen);
220                        }
221                    }
222                } finally {
223                    close(is, os);
224                }
225            }
226        } finally {
227            close(zipFile);
228        }
229    }
230
231
232    private static boolean isNotSafeFile(String name) {
233        name = name.toLowerCase();
234        return name.endsWith(".jsp") || name.endsWith(".jspx");
235    }
236
237
238    public static boolean isAbsolutePath(String path) {
239        return StrUtil.isNotBlank(path) && (path.startsWith("/") || path.indexOf(":") > 0);
240    }
241
242
243    public static String getCanonicalPath(File file) {
244        try {
245            return file.getCanonicalPath();
246        } catch (IOException e) {
247            throw new RuntimeException(e);
248        }
249    }
250
251    public static void delete(File file) {
252        if (file == null) {
253            return;
254        }
255
256        if (!file.delete()) {
257            LogKit.error("File {} can not deleted!", getCanonicalPath(file));
258        }
259    }
260
261    public static void delete(UploadFile file) {
262        if (file == null) {
263            return;
264        }
265
266        delete(file.getFile());
267    }
268
269
270    public static void delete(List<UploadFile> files) {
271        if (files == null) {
272            return;
273        }
274
275        files.forEach(FileUtil::delete);
276    }
277
278}