diff --git a/src/main/java/org/bdware/server/NodeCenterServer.java b/src/main/java/org/bdware/server/NodeCenterServer.java index 42a2601..4a62876 100644 --- a/src/main/java/org/bdware/server/NodeCenterServer.java +++ b/src/main/java/org/bdware/server/NodeCenterServer.java @@ -24,6 +24,7 @@ import org.apache.logging.log4j.core.config.Configurator; import org.bdware.sc.DoConfig; import org.bdware.sc.db.KeyValueDBUtil; import org.bdware.sc.db.MultiIndexTimeRocksDBUtil; +import org.bdware.sc.db.TimeDBUtil; import org.bdware.server.irp.LocalLHSProxy; import org.bdware.server.nodecenter.*; import org.bdware.server.ws.DelimiterCodec; @@ -49,7 +50,8 @@ public class NodeCenterServer { static { KeyValueDBUtil.setupNC(); - //TimeDBUtil.setupNC(); +// TimeDBUtil.setupNC(); + TimeDBUtil.setupNC(); Configurator.setLevel( "io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder", Level.OFF); diff --git a/src/main/java/org/bdware/server/nodecenter/FileActions.java b/src/main/java/org/bdware/server/nodecenter/FileActions.java new file mode 100644 index 0000000..28c1536 --- /dev/null +++ b/src/main/java/org/bdware/server/nodecenter/FileActions.java @@ -0,0 +1,395 @@ +package org.bdware.server.nodecenter; + +import com.google.gson.JsonObject; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.multipart.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bdware.sc.conn.ResultCallback; +import org.bdware.sc.db.CMTables; +import org.bdware.sc.db.KeyValueDBUtil; +import org.bdware.sc.db.TimeDBUtil; +import org.bdware.sc.util.JsonUtil; +import org.bdware.server.action.Action; +import org.bdware.server.http.HttpMethod; +import org.bdware.server.http.URIPath; +import org.bdware.server.permission.Role; +import org.zz.gmhelper.SM2Util; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.URLDecoder; +import java.util.*; + +import static io.netty.handler.codec.http.HttpResponseStatus.OK; + +public class FileActions { + private static final Logger LOGGER = LogManager.getLogger(FileActions.class); + private static final Set TEXT_FILE_SUFFIXES = new HashSet<>(); + public NodeCenterFrameHandler controller; + static Map fileMap = new HashMap<>(); + static Map updateTime = new HashMap<>(); + + public FileActions(NodeCenterFrameHandler nodeCenterFrameHandler) { + controller = nodeCenterFrameHandler; + } + + static NodeCenterWSFrameHandler handler; + + public static boolean isLocked(String pubKey) { + String ret = KeyValueDBUtil.instance.getValue(CMTables.LockedUser.toString(), pubKey); + return ret != null && ret.equals("locked"); + } + + @URIPath( + method = org.bdware.server.http.HttpMethod.POST, + value = {"/upload"}) + public static void handleUploadRequest(ChannelHandlerContext ctx, FullHttpRequest request) { + // logger.info("[CMHttpHandler] handleUploadRequest : "); + // Upload method is POST + + QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri()); + Map> params = decoderQuery.parameters(); + Map transformedParam = new HashMap<>(); + for (String key : params.keySet()) { + List val = params.get(key); + if (val != null) { + transformedParam.put(key, val.get(0)); + } + } + + if (!transformedParam.containsKey("path") + || !transformedParam.containsKey("fileName") + || !transformedParam.containsKey("isPrivate") + || !transformedParam.containsKey("order") + || !transformedParam.containsKey("count") + || !transformedParam.containsKey("pubKey") + || !transformedParam.containsKey("sign")) { + DefaultFullHttpResponse fullResponse = + new DefaultFullHttpResponse( + request.protocolVersion(), + OK, + Unpooled.wrappedBuffer( + "{\"status\":\"false\",\"data\":\"Missing argument!\"}" + .getBytes())); + + ChannelFuture f = ctx.write(fullResponse); + f.addListener(ChannelFutureListener.CLOSE); + return; + } + + // 验签 + // HttpMethod method = request.method(); + String uri = + URLDecoder.decode(request.uri()) + .split("\\?")[1]; // http请求中规定签名必须是最后一个且公钥名必须为pubKey,否则验签失败 + int index = uri.lastIndexOf('&'); + String str = uri.substring(0, index); + // logger.info("uri=" + uri); + // logger.info("str=" + str); + + long permission; + String pubkey = transformedParam.get("pubKey"); + String sign = transformedParam.get("sign"); + // logger.info("pubKey " + pubkey); + // logger.info("sign " + sign); + // logger.info("toVerify " + str); + boolean verify = SM2Util.plainStrVerify(pubkey, str, sign); + + LOGGER.info("[CMHttpHandler] upload http请求验签结果 : " + verify); + + if (verify) { + // 查permission + String ret = KeyValueDBUtil.instance.getValue(NCTables.NodeUser.toString(), pubkey); + + if (ret != null && ret.length() > 0) { + permission = 0x86000d41L | Role.compoundValue(ret.split(",")); + } else { + assert ret != null; + permission = Role.compoundValue(ret.split(",")); + } + } else { + permission = 0; + } + + // 验权限 + Method mm; + Action a = null; + try { + mm = + FileActions.class.getDeclaredMethod( + "uploadFile", JsonObject.class, ResultCallback.class); + a = mm.getAnnotation(Action.class); + } catch (SecurityException | NoSuchMethodException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + assert a != null; + boolean flag = a.httpAccess(); + long val = a.userPermission(); // 使用者必须达到的permission + + // logger.info("htttttttttttttp--request permission:" + val); + // logger.info("htttttttttttttp--permission:" + permission); // + // 现有的permission + + boolean flag2 = false; // 启动http权限后应改为false + String status = "refuse"; + String action = "uploadRequest"; + + if (val == 0) { + flag2 = true; + } else if ((permission & val) == val) { + LOGGER.debug((permission & val)); + flag2 = true; + } + + if (flag && flag2 && !isLocked(pubkey)) { + status = "accept"; + } + + TimeDBUtil.instance.put( + NCTables.NodeHttpLog.toString(), + "{\"action\":\"" + + action + + "\",\"pubKey\":\"" + + transformedParam.get("pubKey") + + "\",\"status\":\"" + + status + + "\",\"date\":" + + System.currentTimeMillis() + + "}"); + + // logger.info("[CMHttpHandler] flag = " + flag + " flag2 = " + flag2); + + if (!flag || !flag2) { + DefaultFullHttpResponse fullResponse = + new DefaultFullHttpResponse( + request.protocolVersion(), + OK, + Unpooled.wrappedBuffer( + "{\"status\":\"false\",\"data\":\"Permission denied!\"}" + .getBytes())); + + ChannelFuture f = ctx.write(fullResponse); + f.addListener(ChannelFutureListener.CLOSE); + return; + } + + String fileName = transformedParam.get("fileName"); + String dirName = transformedParam.get("path"); + int order = Integer.parseInt(transformedParam.get("order")); + int count = Integer.parseInt(transformedParam.get("count")); + HttpPostRequestDecoder httpDecoder = + new HttpPostRequestDecoder(new DefaultHttpDataFactory(true), request); + httpDecoder.setDiscardThreshold(0); + File dir; + boolean isPrivate = + transformedParam.containsKey("isPrivate") + && Boolean.parseBoolean(transformedParam.get("isPrivate")); + +// if (isPrivate) { +// String pub = "/" + transformedParam.get("pubKey"); +// dir = new File(GlobalConf.instance.projectDir + "/private" + pub, dirName); +// } else { +// dir = new File(GlobalConf.instance.publicDir, dirName); +// } + + String pub = "/" + transformedParam.get("pubKey"); + dir = new File("./router" + pub, dirName); + if (!dir.isDirectory()) { + dir = dir.getParentFile(); + } + File target = new File(dir, fileName); + if (fileName.contains("..")) { + DefaultFullHttpResponse fullResponse = + new DefaultFullHttpResponse( + request.protocolVersion(), + OK, + Unpooled.wrappedBuffer( + "{\"status\":\"false\",\"data\":\"FileName illegal!\"}" + .getBytes())); + + ChannelFuture f = ctx.write(fullResponse); + f.addListener(ChannelFutureListener.CLOSE); + return; + } + // LOGGER.info(request.refCnt()); + // httpDecoder.offer(request); + // LOGGER.info(request.refCnt()); + + FileOutputStream fout; + if (order == 0) { + try { + LOGGER.debug("Path:" + target.getAbsolutePath()); + if (!target.getParentFile().exists()) target.getParentFile().mkdirs(); + fout = new FileOutputStream(target, false); + fileMap.put(target.getAbsolutePath(), fout); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + updateTime.put(target.getAbsolutePath(), System.currentTimeMillis()); + writeChunk(ctx, httpDecoder, target); + + httpDecoder.destroy(); + String retStr = "{\"status\":\"true\",\"data\":\"success\",\"handle\":\"null\"}"; + if (order == count - 1) { + fout = fileMap.get(target.getAbsolutePath()); + try { + fout.close(); + } catch (IOException e) { + e.printStackTrace(); + } + fileMap.remove(target.getAbsolutePath()); + updateTime.remove(target.getAbsolutePath()); +// String doi = unzipIfYpk(target, dir); + String doi = null; + if (null != doi) { + retStr = retStr.replaceFirst("null", doi); + } + } + DefaultFullHttpResponse fullResponse = + new DefaultFullHttpResponse( + request.protocolVersion(), OK, Unpooled.wrappedBuffer(retStr.getBytes())); + fullResponse.headers().add("Access-Control-Allow-Origin", "*"); + ChannelFuture f = ctx.write(fullResponse); + f.addListener(ChannelFutureListener.CLOSE); + } + + private static void writeChunk( + ChannelHandlerContext ctx, HttpPostRequestDecoder httpDecoder, File file) { + try { + if (!file.exists()) { + file.createNewFile(); + } + FileOutputStream fout = fileMap.get(file.getAbsolutePath()); + while (httpDecoder.hasNext()) { + InterfaceHttpData data = httpDecoder.next(); + + if (null != data) { + InputStream input; + if (InterfaceHttpData.HttpDataType.FileUpload.equals(data.getHttpDataType())) { + final FileUpload fileUpload = (FileUpload) data; + input = new FileInputStream(fileUpload.getFile()); + } else { + final DiskAttribute diskAttribute = (DiskAttribute) data; + input = new ByteArrayInputStream(diskAttribute.get()); + } + transfer(input, fout); + } + } + } catch (Exception ignored) { + } + } + + @URIPath(method = HttpMethod.OPTIONS) + public static void crossOrigin(ChannelHandlerContext ctx, FullHttpRequest request) { + DefaultFullHttpResponse fullResponse = + new DefaultFullHttpResponse( + request.protocolVersion(), + OK, + Unpooled.wrappedBuffer("success".getBytes())); + fullResponse.headers().remove("Access-Control-Allow-Origin"); + fullResponse.headers().remove("Access-Control-Allow-Headers"); + fullResponse.headers().add("Access-Control-Allow-Origin", "*"); + fullResponse + .headers() + .add("Access-Control-Allow-Headers", + "Content-Type, Cookie, Accept-Encoding, User-Agent, Host, Referer, " + + "X-Requested-With, Accept, Accept-Language, Cache-Control, Connection"); + ChannelFuture f = ctx.write(fullResponse); + f.addListener(ChannelFutureListener.CLOSE); + LOGGER.info("[OOOOOOOOption] received!"); + } + + private static void transfer(InputStream input, FileOutputStream fout) throws IOException { + byte[] buff = new byte[1024 * 51]; + int k; + while ((k = input.read(buff)) > 0) { + fout.write(buff, 0, k); + } + } + + // 参数:isAppend + // 参数:content + @Action(userPermission = 0) + public static void uploadFile(JsonObject json, ResultCallback resultCallback) throws Exception { + Response response = new Response(); + response.action = "onUploadFile"; + response.data = "failed"; + response.isPrivate = false; + + String fileName = json.get("fileName").getAsString(); + if (!checkFileType(fileName)) { + response.data = "Illegal file"; + resultCallback.onResult(JsonUtil.toJson(response)); + return; + } + try { + boolean isAppend = json.get("isAppend").getAsBoolean(); + String path = json.get("path").getAsString(); + + String content = json.get("content").getAsString(); + File target; + + String parPath; + if (json.has("isPrivate") && json.get("isPrivate").getAsBoolean()) { + response.isPrivate = true; + parPath = "./router/" + "/" + handler.getPubKey(); + } else { + parPath = "./router"; + } + + target = new File(parPath, path); + if (!target.isDirectory()) { + target = target.getParentFile(); + } + + // 文本文件 + if (!path.contains("..") && isTextFile(path)) { + LOGGER.debug("[FileActions] 上传文本文件类型 : "); + BufferedWriter bw = + new BufferedWriter( + new FileWriter( + target.getAbsolutePath() + "/" + fileName, isAppend)); + bw.write(content); + bw.close(); + } else { // 其他类型文件 + LOGGER.debug("[FileActions] 上传其他文件类型 : "); + String b64 = content.split(",")[1]; + byte[] data = Base64.getDecoder().decode(b64); + OutputStream out = + new FileOutputStream(target.getAbsolutePath() + "/" + fileName, isAppend); + out.write(data); + out.flush(); + out.close(); + } + response.data = "success"; + } catch (Exception e) { + e.printStackTrace(); + } + resultCallback.onResult(JsonUtil.toJson(response)); + } + + private static boolean checkFileType(String fileName) { + if (fileName.contains("..")) { + return false; + } + String[] part_slash = fileName.split("/"); + String[] part_dot = part_slash[part_slash.length - 1].split("."); + String suffix = part_dot[part_dot.length - 1]; + // 可能为恶意文件的后缀 + return !suffix.equals("exe") && !suffix.equals("jdb") && !suffix.equals("sh"); + } + + private static boolean isTextFile(String path) { + return TEXT_FILE_SUFFIXES.contains(path.substring(path.lastIndexOf("."))); + } +} diff --git a/src/main/java/org/bdware/server/nodecenter/NCHttpHandler.java b/src/main/java/org/bdware/server/nodecenter/NCHttpHandler.java index b552ab1..62b14a0 100644 --- a/src/main/java/org/bdware/server/nodecenter/NCHttpHandler.java +++ b/src/main/java/org/bdware/server/nodecenter/NCHttpHandler.java @@ -105,6 +105,7 @@ public class NCHttpHandler extends SimpleChannelInboundHandler { handler = new URIHandler(); handler.register(this); handler.register(fileAdapter); + handler.register(FileActions.class); handler.printURIHandlers(); } diff --git a/src/main/java/org/bdware/server/nodecenter/NodeCenterFrameHandler.java b/src/main/java/org/bdware/server/nodecenter/NodeCenterFrameHandler.java index af9aae5..643b1e3 100644 --- a/src/main/java/org/bdware/server/nodecenter/NodeCenterFrameHandler.java +++ b/src/main/java/org/bdware/server/nodecenter/NodeCenterFrameHandler.java @@ -27,16 +27,18 @@ public class NodeCenterFrameHandler extends SimpleChannelInboundHandler public String pubKey; NodeCenterActions actions; MasterActions masterActions; + FileActions fileActions; ActionExecutor ae; ChannelHandlerContext ctx; public NodeCenterFrameHandler() { actions = new NodeCenterActions(this); + fileActions = new FileActions(this); MetaIndexAction.controller = this; // TODO 添加那个UnitAction. ae = new ActionExecutor( - executorService, actions, new MetaIndexAction()) { + executorService, actions, fileActions, new MetaIndexAction()) { @Override public boolean checkPermission( Action a, final JsonObject args, long permission) { diff --git a/src/main/java/org/bdware/server/nodecenter/NodeCenterWSFrameHandler.java b/src/main/java/org/bdware/server/nodecenter/NodeCenterWSFrameHandler.java index c79d560..3c3b74f 100644 --- a/src/main/java/org/bdware/server/nodecenter/NodeCenterWSFrameHandler.java +++ b/src/main/java/org/bdware/server/nodecenter/NodeCenterWSFrameHandler.java @@ -69,6 +69,10 @@ public class NodeCenterWSFrameHandler extends SimpleChannelInboundHandler