149 lines
5.0 KiB
Java
Executable File
149 lines
5.0 KiB
Java
Executable File
package com.xiaoleilu.loServer.action;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.URLDecoder;
|
|
import java.net.URLEncoder;
|
|
import java.nio.file.Files;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.Locale;
|
|
import java.util.regex.Pattern;
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
import cn.hutool.core.io.FileUtil;
|
|
import cn.hutool.core.util.ReUtil;
|
|
import cn.hutool.core.util.StrUtil;
|
|
import com.hazelcast.core.HazelcastInstance;
|
|
import com.hazelcast.util.StringUtil;
|
|
import com.xiaoleilu.loServer.ServerSetting;
|
|
import com.xiaoleilu.loServer.handler.Request;
|
|
import com.xiaoleilu.loServer.handler.Response;
|
|
|
|
import io.moquette.spi.IMessagesStore;
|
|
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import static cn.hutool.core.date.DatePattern.HTTP_DATETIME_PATTERN;
|
|
|
|
/**
|
|
* 默认的主页Action,当访问主页且没有定义主页Action时,调用此Action
|
|
*
|
|
* @author Looly
|
|
*
|
|
*/
|
|
public class FileAction extends Action {
|
|
private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(FileAction.class);
|
|
|
|
@Override
|
|
public boolean action(Request request, Response response) {
|
|
response.setHeader("Access-Control-Allow-Origin", "*");
|
|
if (false == Request.METHOD_GET.equalsIgnoreCase(request.getMethod())) {
|
|
response.sendError(HttpResponseStatus.METHOD_NOT_ALLOWED, "Please use GET method to request file!");
|
|
return true;
|
|
}
|
|
|
|
if(ServerSetting.isRootAvailable() == false){
|
|
response.sendError(HttpResponseStatus.NOT_FOUND, "404 Root dir not avaliable!");
|
|
return true;
|
|
}
|
|
|
|
File file = null;
|
|
try {
|
|
file = getFileByPath(request.getPath());
|
|
} catch (Exception e) {
|
|
response.sendError(HttpResponseStatus.NOT_FOUND, "404 File not found!");
|
|
return true;
|
|
}
|
|
|
|
// 隐藏文件或不存在,跳过
|
|
if (file == null || file.isHidden() || !file.exists()) {
|
|
response.sendError(HttpResponseStatus.NOT_FOUND, "404 File not found!");
|
|
return true;
|
|
}
|
|
|
|
// 非文件,跳过
|
|
if (!file.isFile()) {
|
|
response.sendError(HttpResponseStatus.FORBIDDEN, "403 Forbidden!");
|
|
return true;
|
|
}
|
|
|
|
Logger.debug("Client [{}] get file [{}]", request.getIp(), file.getPath());
|
|
|
|
// Cache Validation
|
|
String ifModifiedSince = request.getHeader(HttpHeaderNames.IF_MODIFIED_SINCE.toString());
|
|
if (StrUtil.isNotBlank(ifModifiedSince)) {
|
|
Date ifModifiedSinceDate = null;
|
|
try {
|
|
ifModifiedSinceDate = DateUtil.parse(ifModifiedSince, HTTP_DATE_FORMATER);
|
|
} catch (Exception e) {
|
|
Logger.warn("If-Modified-Since header parse error: {}", e.getMessage());
|
|
}
|
|
if(ifModifiedSinceDate != null) {
|
|
// 只对比到秒一级别
|
|
long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
|
|
long fileLastModifiedSeconds = file.lastModified() / 1000;
|
|
if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
|
|
Logger.debug("File {} not modified.", file.getPath());
|
|
response.sendNotModified();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
response.setContent(file);
|
|
if (isAudioOrVideoFile(file.getName())) {
|
|
response.setHeader("Accept-Ranges", "bytes");
|
|
} else {
|
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean isAudioOrVideoFile(String name) {
|
|
try {
|
|
if (StringUtil.isNullOrEmpty(name)) return false;
|
|
|
|
String mimeType = Files.probeContentType(new File(name).toPath());
|
|
return mimeType != null && (mimeType.startsWith("audio/") || mimeType.startsWith("video/"));
|
|
} catch (IOException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
|
|
private static final SimpleDateFormat HTTP_DATE_FORMATER = new SimpleDateFormat(HTTP_DATETIME_PATTERN, Locale.US);
|
|
|
|
|
|
/**
|
|
* 通过URL中的path获得文件的绝对路径
|
|
*
|
|
* @param httpPath Http请求的Path
|
|
* @return 文件绝对路径
|
|
*/
|
|
public static File getFileByPath(String httpPath) {
|
|
// Decode the path.
|
|
try {
|
|
httpPath = URLDecoder.decode(httpPath, "UTF-8");
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new Error(e);
|
|
}
|
|
|
|
if (httpPath.isEmpty() || httpPath.charAt(0) != '/') {
|
|
return null;
|
|
}
|
|
|
|
// 路径安全检查
|
|
String path = httpPath.substring(0, httpPath.lastIndexOf("/"));
|
|
if (path.contains("/.") || path.contains("./") || ReUtil.isMatch(INSECURE_URI, path)) {
|
|
return null;
|
|
}
|
|
|
|
// 转换为绝对路径
|
|
return FileUtil.file(ServerSetting.getRoot(), httpPath);
|
|
}
|
|
}
|