commit bbf9f1151f17993e986f5fb40579e66aa8b6d581 Author: Gurkengewuerz Date: Mon Jul 3 00:32:04 2017 +0200 init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2353a94 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +*.db +*.sqlite* +settings.json + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Manifest created by build +MANIFEST.MF + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +nbproject/ +dist/ +build/ +/bin/ + +# Files created by the bot inside resources folder +resources/bannedUsers.bin +resources/botlogin.txt +resources/phantombot.db +resources/stacktrace.txt +resources/stdio.txt +resources/web/default.html +resources/web/default.txt +resources/scripts/* + +# Do not include the class files for GenerateTwitterTokens +genTwitterTokens/classes/generatetwittertokens + +# IntelliJ Project files +.idea +out/ +*.iml + +# Netbeans Project files +nbproject + +# Eclipse Project files diff --git a/src/de/gurkengewuerz/termbin/Config.java b/src/de/gurkengewuerz/termbin/Config.java new file mode 100644 index 0000000..5bf1c85 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Config.java @@ -0,0 +1,148 @@ +package de.gurkengewuerz.termbin; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class Config extends JSONObject { + private File file; + private static List ban = new ArrayList<>(); + private static List whitelist = new ArrayList<>(); + + public Config(File file) throws IOException { + this.file = file; + + if (!file.exists()) { + file.createNewFile(); + } + + if (!file.isFile()) { + throw new FileNotFoundException(file.getAbsolutePath() + " not found"); + } + + if (!file.canRead() || !file.canWrite()) { + throw new AccessDeniedException(file.getAbsolutePath() + " is not accessable"); + } + + this.put("log", "termvin.log"); + this.put("database", "db.sqlite3"); + this.put("domain", "http://localhost/"); + + JSONObject uploadserver = new JSONObject(); + uploadserver.put("port", 8888); + uploadserver.put("bind", "0.0.0.0"); + this.put("uploadserver", uploadserver); + + JSONObject dataserver = new JSONObject(); + dataserver.put("port", 8080); + this.put("dataserver", dataserver); + + JSONObject apiserver = new JSONObject(); + apiserver.put("port", 9090); + this.put("apiserver", apiserver); + + JSONArray ban_list = new JSONArray(); + ban_list.put("125.38.39.40"); + ban_list.put("27.46.74.43"); + ban_list.put("210.35.171.4"); + this.put("ban_list", ban_list); + + JSONArray white_list = new JSONArray(); + white_list.put("127.0.0.1"); + white_list.put("192.168.1.36"); + this.put("white_list", white_list); + } + + public void save() { + try { + FileWriter fw = new FileWriter(file.getAbsolutePath()); + fw.write(this.toString(4)); + fw.close(); + loadRestData(); + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + } + } + + public void load() { + try { + String content = new String(Files.readAllBytes(file.toPath()), "UTF-8"); + if (content.isEmpty()) { + save(); + return; + } + JSONTokener jt = new JSONTokener(content); + if (jt.nextClean() != 123) { + throw jt.syntaxError("A JSONObject text must begin with '{'"); + } else { + while (jt.more()) { + char c = jt.nextClean(); + switch (c) { + case '\u0000': + throw jt.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + jt.back(); + String key = jt.nextValue().toString(); + c = jt.nextClean(); + if (c != 58) { + throw jt.syntaxError("Expected a ':' after a key"); + } + + this.remove(key); + this.putOnce(key, jt.nextValue()); + switch (jt.nextClean()) { + case ',': + case ';': + if (jt.nextClean() == 125) { + return; + } + + jt.back(); + break; + case '}': + loadRestData(); + return; + default: + throw jt.syntaxError("Expected a ',' or '}'"); + } + } + } + } + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + } + } + + private void loadRestData() { + JSONArray json_whitelist = getJSONArray("white_list"); + for (int i = 0; i < json_whitelist.length(); i++) { + whitelist.add(json_whitelist.getString(i)); + } + + JSONArray json_ban = getJSONArray("ban_list"); + for (int i = 0; i < json_ban.length(); i++) { + if (whitelist.contains(json_ban.getString(i))) continue; + ban.add(json_ban.getString(i)); + } + } + + public boolean isBanned(String ipv4) { + return ban.contains(ipv4); + } +} diff --git a/src/de/gurkengewuerz/termbin/Database.java b/src/de/gurkengewuerz/termbin/Database.java new file mode 100644 index 0000000..0fbda8b --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Database.java @@ -0,0 +1,61 @@ +package de.gurkengewuerz.termbin; + +import java.sql.*; + +/** + * Created by gurkengewuerz.de on 08.04.2017. + */ +public class Database { + + private String sUrl = null; + private int iTimeout = 30; + private Connection conn = null; + private Statement statement = null; + + + public Database(String sUrlToLoad) throws Exception { + setUrl(sUrlToLoad); + setConnection(); + setStatement(); + } + + private void setUrl(String sUrlVar) { + sUrl = sUrlVar; + } + + private void setConnection() throws Exception { + conn = DriverManager.getConnection("jdbc:sqlite:" + sUrl); + } + + + public Connection getConnection() { + return conn; + } + + private void setStatement() throws Exception { + if (conn == null) { + setConnection(); + } + statement = conn.createStatement(); + statement.setQueryTimeout(iTimeout); // set timeout to 30 sec. + } + + public PreparedStatement getPreparedStatement(String sql) throws SQLException { + return conn.prepareStatement(sql); + } + + public void executeUpdate(String instruction) throws SQLException { + statement.executeUpdate(instruction); + } + + public ResultSet executeQuery(String instruction) throws SQLException { + return statement.executeQuery(instruction); + } + + public void closeConnection() { + try { + conn.close(); + } catch (Exception ignore) { + } + } +} diff --git a/src/de/gurkengewuerz/termbin/Server/APIHandler.java b/src/de/gurkengewuerz/termbin/Server/APIHandler.java new file mode 100644 index 0000000..7c6d5f5 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Server/APIHandler.java @@ -0,0 +1,111 @@ +package de.gurkengewuerz.termbin.Server; + +import de.gurkengewuerz.termbin.Termbin; +import de.gurkengewuerz.termbin.Utils.ImageUtils; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.json.JSONArray; +import org.json.JSONObject; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class APIHandler extends AbstractHandler { + + @Override + public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s); + + if(Termbin.getConfig().isBanned(request.getRemoteAddr())) { + request.setHandled(true); + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s + " closed BANNED"); + return; + } + + JSONObject returnObject = null; + JSONArray returnArray = null; + + request.setCharacterEncoding("UTF-8"); + httpServletResponse.setCharacterEncoding("UTF-8"); + + if (s.equals("/")) { // Describe yourself + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + returnObject = new JSONObject(); + returnObject.put("self", "/"); + returnObject.put("upload", "/upload/"); + } else if (s.startsWith("/upload")) { + returnObject = new JSONObject(); + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + boolean breaked = false; + byte[] buff = new byte[1024]; + while (true) { + int n = httpServletRequest.getInputStream().read(buff); + if (n < 0) break; + baos.write(buff, 0, n); + if (baos.size() > 1024 * 1024 * 2) {// 2 MB + breaked = true; + returnObject.put("error", "File too big"); + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s + " closed FILE TOO BIG"); + break; + } + } + if (!breaked) { + byte[] data = baos.toByteArray(); + + if (data.length > 3) { + String dataString = new String(data, "UTF-8"); + + Termbin.FileType ft = Termbin.FileType.TXT; + + if (ImageUtils.isValidPNG(data)) + ft = Termbin.FileType.PNG; + else if (ImageUtils.isValidJPEG(data)) + ft = Termbin.FileType.JPG; + else if (ImageUtils.isValidGIF(data)) + ft = Termbin.FileType.GIF; + + try { + String uploadID = Termbin.upload(request.getRemoteAddr(), dataString, data, ft); + returnObject.put("key", uploadID); + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s + " closed SUCCESSFULL"); + } catch (SQLException e) { + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s + " closed SERVER ERROR"); + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + returnObject.put("error", "Server error"); + } + } else { + returnObject.put("error", "data is empty"); + Logger.getLogger(getClass().getName()).log(Level.INFO, "API Request by " + request.getRemoteAddr() + "@" + s + " closed EMPTY"); + } + } + } else { + httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); + } + + httpServletResponse.setContentType("application/json; charset=utf-8"); + + PrintWriter out = httpServletResponse.getWriter(); + if (returnObject != null) { + out.write(returnObject.toString()); + } else if (returnArray != null) { + out.write(returnArray.toString()); + } else { + returnObject = new JSONObject(); + returnObject.put("error", "not found"); + out.write(returnObject.toString()); + } + + request.setHandled(true); + } +} diff --git a/src/de/gurkengewuerz/termbin/Server/DataHandler.java b/src/de/gurkengewuerz/termbin/Server/DataHandler.java new file mode 100644 index 0000000..c9057e5 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Server/DataHandler.java @@ -0,0 +1,64 @@ +package de.gurkengewuerz.termbin.Server; + +import de.gurkengewuerz.termbin.Termbin; +import de.gurkengewuerz.termbin.Utils.SQLInjectionEscaper; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class DataHandler extends AbstractHandler { + + @Override + public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { + Logger.getLogger(getClass().getName()).log(Level.INFO, "Request by " + request.getRemoteAddr() + "@" + s); + + if (Termbin.getConfig().isBanned(request.getRemoteAddr())) { + request.setHandled(true); + Logger.getLogger(getClass().getName()).log(Level.INFO, "Request by " + request.getRemoteAddr() + "@" + s + " closed BANNED"); + return; + } + + request.setCharacterEncoding("UTF-8"); + httpServletResponse.setCharacterEncoding("UTF-8"); + try { + ResultSet rs = Termbin.getDatabase().executeQuery("SELECT * FROM data WHERE uniqueid = '" + SQLInjectionEscaper.escapeString(s.substring(1), false) + "' LIMIT 1;"); + + boolean found = false; + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + while (rs.next()) { + found = true; + httpServletResponse.setContentType(rs.getString("filetype")); + if (rs.getString("filetype").equals("text/plain")) { + httpServletResponse.getOutputStream().write(rs.getString("text").getBytes("UTF-8")); + } else { + httpServletResponse.setContentLength(rs.getBytes("rawData").length); + httpServletResponse.getOutputStream().write(rs.getBytes("rawData")); + } + } + + if (!found) { + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND); + httpServletResponse.getOutputStream().write("".getBytes("UTF-8")); + } + + } catch (SQLException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + httpServletResponse.getOutputStream().write("".getBytes("UTF-8")); + + } + + request.setHandled(true); + } +} diff --git a/src/de/gurkengewuerz/termbin/Server/UploadServer.java b/src/de/gurkengewuerz/termbin/Server/UploadServer.java new file mode 100644 index 0000000..1b3b183 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Server/UploadServer.java @@ -0,0 +1,131 @@ +package de.gurkengewuerz.termbin.Server; + +import de.gurkengewuerz.termbin.Termbin; +import de.gurkengewuerz.termbin.Utils.ImageUtils; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.*; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class UploadServer { + + public UploadServer(String ipv4, int port) throws IOException { + Logger.getLogger("Main").log(Level.INFO, "Starting..."); + int clientNumber = 1; + + ServerSocket listener = new ServerSocket(port); + if (!ipv4.isEmpty() && !ipv4.equals("0.0.0.0")) { + listener.bind(new InetSocketAddress(ipv4, port)); + } + Logger.getLogger(getClass().getName()).log(Level.INFO, "Started. Waiting for Clients."); + + try { + while (true) { + Socket socketOfServer = listener.accept(); + new ServiceThread(socketOfServer, clientNumber++).start(); + } + } finally { + listener.close(); + } + } + + private class ServiceThread extends Thread { + + private Socket socketOfServer; + private String client; + private String clientIP; + + public ServiceThread(Socket socketOfServer, int clientNumber) { + this.socketOfServer = socketOfServer; + this.clientIP = socketOfServer.getInetAddress().toString().substring(1); + this.client = "client#" + clientNumber + "@" + clientIP + ":" + socketOfServer.getPort(); + try { + this.socketOfServer.setSoTimeout(2000); + } catch (SocketException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + } + Logger.getLogger(getClass().getName()).log(Level.INFO, "opened " + client + " at " + socketOfServer.getLocalAddress() + ":" + socketOfServer.getLocalPort()); + + if (Termbin.getConfig().isBanned(clientIP)) { + Logger.getLogger(getClass().getName()).log(Level.INFO, "closed " + client + " with error banned"); + try { + socketOfServer.close(); + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + } + } + } + + @Override + public void run() { + try { + if (socketOfServer.isClosed()) return; + BufferedWriter os = new BufferedWriter(new OutputStreamWriter(socketOfServer.getOutputStream())); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + byte[] buff = new byte[1024]; + while (true) { + int n = socketOfServer.getInputStream().read(buff); + if (n < 0) break; + baos.write(buff, 0, n); + if (baos.size() > 1024 * 1024 * 2) {// 2 MB + Logger.getLogger(getClass().getName()).log(Level.INFO, "closed " + client + " with file to big"); + os.write("File to big!"); + os.newLine(); + os.flush(); + socketOfServer.close(); + return; + } + } + } catch (SocketTimeoutException e) { + Logger.getLogger(getClass().getName()).log(Level.INFO, "closed " + client + " with error invalid data"); + os.write("Invalid data"); + os.newLine(); + os.flush(); + socketOfServer.close(); + return; + } + + byte[] data = baos.toByteArray(); + if (data.length > 3) { + String dataString = new String(data, "UTF-8"); + + Termbin.FileType ft = Termbin.FileType.TXT; + + if (ImageUtils.isValidPNG(data)) + ft = Termbin.FileType.PNG; + else if (ImageUtils.isValidJPEG(data)) + ft = Termbin.FileType.JPG; + else if (ImageUtils.isValidGIF(data)) + ft = Termbin.FileType.GIF; + + try { + String uploadID = Termbin.upload(clientIP, dataString, data, ft); + os.write(Termbin.getConfig().get("domain") + uploadID); + Logger.getLogger(getClass().getName()).log(Level.INFO, "closed " + client + " successfully with ID#" + uploadID); + } catch (SQLException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + os.write("Server Error"); + } + } else { + os.write("Invalid data"); + Logger.getLogger(getClass().getName()).log(Level.INFO, "closed " + client + " with error invalid data"); + } + os.newLine(); + os.flush(); + socketOfServer.close(); + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e); + } + } + } +} diff --git a/src/de/gurkengewuerz/termbin/Termbin.java b/src/de/gurkengewuerz/termbin/Termbin.java new file mode 100644 index 0000000..3e3aaf9 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Termbin.java @@ -0,0 +1,158 @@ +package de.gurkengewuerz.termbin; + +import de.gurkengewuerz.termbin.Server.APIHandler; +import de.gurkengewuerz.termbin.Server.DataHandler; +import de.gurkengewuerz.termbin.Server.UploadServer; +import de.gurkengewuerz.termbin.Utils.HashUtils; +import org.eclipse.jetty.server.Server; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class Termbin { + + private static Database db; + private static Config conf; + + public static void main(String args[]) { + File f = new File("." + File.separator + "settings.json"); + + try { + conf = new Config(f); + } catch (IOException e) { + Logger.getLogger(Termbin.class.getName()).log(Level.SEVERE, null, e); + System.exit(1); + } + conf.load(); + + try { + db = new Database(conf.getString("database")); + createDatabase(); + } catch (Exception e) { + Logger.getLogger(Termbin.class.getName()).log(Level.SEVERE, null, e); + } + + Runnable uploadServerTask = () -> { + try { + JSONObject uploadServerConf = conf.getJSONObject("uploadserver"); + new UploadServer(uploadServerConf.getString("bind"), uploadServerConf.getInt("port")); + } catch (IOException e) { + Logger.getLogger(Termbin.class.getName()).log(Level.SEVERE, null, e); + } + }; + Thread uploadServerThread = new Thread(uploadServerTask); + uploadServerThread.start(); + + + Runnable dataServerTask = () -> { + try { + JSONObject dataServerConf = conf.getJSONObject("dataserver"); + Server dataServer = new Server(dataServerConf.getInt("port")); + dataServer.setHandler(new DataHandler()); + + dataServer.start(); + dataServer.join(); + } catch (Exception e) { + Logger.getLogger(Termbin.class.getName()).log(Level.SEVERE, null, e); + } + }; + Thread dataServerThread = new Thread(dataServerTask); + dataServerThread.start(); + + + Runnable apiServerTask = () -> { + try { + JSONObject apiServerConf = conf.getJSONObject("apiserver"); + Server apiServer = new Server(apiServerConf.getInt("port")); + apiServer.setHandler(new APIHandler()); + + apiServer.start(); + apiServer.join(); + } catch (Exception e) { + Logger.getLogger(Termbin.class.getName()).log(Level.SEVERE, null, e); + } + }; + Thread apiServerThread = new Thread(apiServerTask); + apiServerThread.start(); + + // TODO: Check if all threads started successfully + // TODO: log to file + // TODO: Arguments (Settings file) + // TODO: print start values (all ports etc.) + // TODO: Config Max lifetime => Check insert time on request + // TODO: Maven + } + + public static Database getDatabase() { + return db; + } + + + public static Config getConfig() { + return conf; + } + + public static String upload(String ip, String text, byte[] rawData, FileType fileType) throws SQLException { + String answerID = HashUtils.getSha256(System.currentTimeMillis() + "").substring(0, 8); + PreparedStatement ps = getDatabase().getPreparedStatement("INSERT INTO data (uniqueid, timestamp, fromClient, filetype, text, rawData) VALUES (?, ?, ?, ?, ?, ?);"); + ps.setString(1, answerID); + ps.setInt(2, (int) (System.currentTimeMillis() / 1000)); + ps.setString(3, ip); + ps.setString(4, fileType.toString()); + if (fileType.equals(FileType.TXT)) { + ps.setString(5, text); + ps.setBytes(6, null); + } else { + ps.setString(5, null); + ps.setBytes(6, rawData); + } + + ps.execute(); + return answerID; + } + + public enum FileType { + TXT, + PNG, + JPG, + GIF; + + @Override + public String toString() { + switch (this) { + case TXT: + return "text/plain"; + case PNG: + return "image/png"; + case JPG: + return "image/jpeg"; + case GIF: + return "image/gif"; + default: + throw new IllegalArgumentException(); + } + } + } + + private static void createDatabase() throws SQLException { + getDatabase().executeUpdate( + "CREATE TABLE IF NOT EXISTS data (" + + " id INTEGER PRIMARY KEY AUTOINCREMENT," + + " uniqueid char(255) NOT NULL UNIQUE," + + " text text NULL," + + " rawData blob NULL," + + " timestamp float NOT NULL," + + " fromClient char(255) NOT NULL," + + " filetype char(255) NOT NULL" + + ");" + ); + } +} diff --git a/src/de/gurkengewuerz/termbin/Utils/HashUtils.java b/src/de/gurkengewuerz/termbin/Utils/HashUtils.java new file mode 100644 index 0000000..af2bfc5 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Utils/HashUtils.java @@ -0,0 +1,24 @@ +package de.gurkengewuerz.termbin.Utils; + +import java.security.MessageDigest; + +/** + * Created by gurkengewuerz.de on 02.07.2017. + */ +public class HashUtils { + public static String getSha256(String value) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(value.getBytes()); + return bytesToHex(md.digest()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); + return result.toString(); + } +} diff --git a/src/de/gurkengewuerz/termbin/Utils/ImageUtils.java b/src/de/gurkengewuerz/termbin/Utils/ImageUtils.java new file mode 100644 index 0000000..c71d17e --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Utils/ImageUtils.java @@ -0,0 +1,78 @@ +package de.gurkengewuerz.termbin.Utils; + +import java.util.Arrays; + +public class ImageUtils { + + static byte[] PNG_HEADER = hexStringToByteArray("89504e470d0a1a0a"); + /** + * Check if the image is a PNG. The first eight bytes of a PNG file always + * contain the following (decimal) values: 137 80 78 71 13 10 26 10 / Hex: + * 89 50 4e 47 0d 0a 1a 0a + */ + public static boolean isValidPNG(byte[] is) { + try { + byte[] b = Arrays.copyOfRange(is, 0, 8); + if (Arrays.equals(b, PNG_HEADER)) { + return true; + } + } catch (Exception e) { + //Ignore + return false; + } + return false; + } + + /** + * Check if the image is a JPEG. JPEG image files begin with FF D8 and end + * with FF D9 + */ + public static boolean isValidJPEG(byte[] is) { + try { + // check first 2 bytes: + byte[] b = Arrays.copyOfRange(is, 0, 2); + if ((b[0]&0xff) != 0xff || (b[1]&0xff) != 0xd8) { + return false; + } + // check last 2 bytes: + b = Arrays.copyOfRange(is, is.length - 2, is.length); + if ((b[0]&0xff) != 0xff || (b[1]&0xff) != 0xd9) { + return false; + } + } catch (Exception e) { + // Ignore + return false; + } + return true; + } + + /** Check if the image is a valid GIF. GIF files start with GIF and 87a or 89a. + * http://www.onicos.com/staff/iz/formats/gif.html + */ + public static boolean isValidGIF(byte[] is) { + try { + byte[] b = Arrays.copyOfRange(is, 0, 6); + //check 1st 3 bytes + if(b[0]!='G' || b[1]!='I' || b[2]!='F') { + return false; + } + if(b[3]!='8' || !(b[4]=='7' || b[4]=='9') || b[5]!='a') { + return false; + } + } catch(Exception e) { + // Ignore + return false; + } + return true; + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } +} diff --git a/src/de/gurkengewuerz/termbin/Utils/SQLInjectionEscaper.java b/src/de/gurkengewuerz/termbin/Utils/SQLInjectionEscaper.java new file mode 100644 index 0000000..9838c97 --- /dev/null +++ b/src/de/gurkengewuerz/termbin/Utils/SQLInjectionEscaper.java @@ -0,0 +1,71 @@ +package de.gurkengewuerz.termbin.Utils; + +public class SQLInjectionEscaper { + + public static String escapeString(String x, boolean escapeDoubleQuotes) { + StringBuilder sBuilder = new StringBuilder(x.length() * 11 / 10); + + int stringLength = x.length(); + + for (int i = 0; i < stringLength; ++i) { + char c = x.charAt(i); + + switch (c) { + case 0: /* Must be escaped for 'mysql' */ + sBuilder.append('\\'); + sBuilder.append('0'); + + break; + + case '\n': /* Must be escaped for logs */ + sBuilder.append('\\'); + sBuilder.append('n'); + + break; + + case '\r': + sBuilder.append('\\'); + sBuilder.append('r'); + + break; + + case '\\': + sBuilder.append('\\'); + sBuilder.append('\\'); + + break; + + case '\'': + sBuilder.append('\\'); + sBuilder.append('\''); + + break; + + case '"': /* Better safe than sorry */ + if (escapeDoubleQuotes) { + sBuilder.append('\\'); + } + + sBuilder.append('"'); + + break; + + case '\032': /* This gives problems on Win32 */ + sBuilder.append('\\'); + sBuilder.append('Z'); + + break; + + case '\u00a5': + case '\u20a9': + // escape characters interpreted as backslash by mysql + // fall through + + default: + sBuilder.append(c); + } + } + + return sBuilder.toString(); + } +}