ConverterServlet.java 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package com.mxgraph.online;
  2. import java.io.BufferedReader;
  3. import java.io.DataOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import java.io.OutputStream;
  8. import java.net.HttpURLConnection;
  9. import java.net.URI;
  10. import java.net.URL;
  11. import java.util.List;
  12. import java.util.logging.Level;
  13. import java.util.logging.Logger;
  14. import javax.servlet.ServletException;
  15. import javax.servlet.http.HttpServlet;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. import org.apache.commons.fileupload.FileItem;
  19. import org.apache.commons.fileupload.FileUploadException;
  20. import org.apache.commons.fileupload.disk.DiskFileItemFactory;
  21. import org.apache.commons.fileupload.servlet.ServletFileUpload;
  22. import java.nio.file.Path;
  23. import java.nio.file.Paths;
  24. //This servlet is an interface between draw.io and CloudConverter.
  25. //For EMF files, it detect its size and resize the huge images such that max dimension is MAX_DIM
  26. public class ConverterServlet extends HttpServlet
  27. {
  28. private static final long serialVersionUID = -5084595244442555865L;
  29. private static final Logger log = Logger
  30. .getLogger(HttpServlet.class.getName());
  31. private static final int MAX_DIM = 5000;
  32. private static final int MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
  33. private static final double EMF_10thMM2PXL = 26.458;
  34. private static final String API_KEY_FILE_PATH = "/WEB-INF/cloud_convert_api_key"; // Not migrated to new pattern, since will not be used on diagrams.net
  35. private static final String CONVERT_SERVICE_URL = "https://api.cloudconvert.com/convert";
  36. private static final String CRLF = "\r\n";
  37. private static final String TWO_HYPHENS = "--";
  38. private static final String BOUNDARY = "----WebKitFormBoundary6XTanBMjO0kFwa3p"; //FIXME The boundary should not occur inside the file, it is very unlikely but still a possibility
  39. private static String API_KEY = null;
  40. private void readApiKey()
  41. {
  42. if (API_KEY == null)
  43. {
  44. try
  45. {
  46. API_KEY = Utils
  47. .readInputStream(getServletContext()
  48. .getResourceAsStream(API_KEY_FILE_PATH))
  49. .replaceAll("\n", "");
  50. }
  51. catch (IOException e)
  52. {
  53. throw new RuntimeException("Invalid API key file/path");
  54. }
  55. }
  56. }
  57. //Little Indian
  58. private int fromByteArray(byte[] bytes, int start)
  59. {
  60. return ((bytes[start + 3] & 0xFF) << 24) |
  61. ((bytes[start + 2] & 0xFF) << 16) |
  62. ((bytes[start + 1] & 0xFF) << 8 ) |
  63. ((bytes[start] & 0xFF) << 0 );
  64. }
  65. /**
  66. * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
  67. */
  68. protected void doPost(HttpServletRequest request,
  69. HttpServletResponse response) throws ServletException, IOException
  70. {
  71. readApiKey();
  72. String inputformat = null, outputformat = null, fileName = null;
  73. InputStream fileContent = null;
  74. try
  75. {
  76. List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
  77. for (FileItem item : items)
  78. {
  79. if (item.isFormField())
  80. {
  81. String fieldName = item.getFieldName();
  82. if ("inputformat".equals(fieldName))
  83. {
  84. inputformat = item.getString();
  85. }
  86. else if ("outputformat".equals(fieldName))
  87. {
  88. outputformat = item.getString();
  89. }
  90. }
  91. else
  92. {
  93. //We expect only one file
  94. Path file = Paths.get(item.getName());
  95. fileName = file.getFileName().toString();
  96. fileContent = item.getInputStream();
  97. }
  98. }
  99. }
  100. catch (FileUploadException e)
  101. {
  102. throw new ServletException("Cannot parse multipart request.", e);
  103. }
  104. if (inputformat == null || outputformat == null || fileContent == null)
  105. {
  106. response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  107. }
  108. else
  109. {
  110. HttpURLConnection con = null;
  111. try
  112. {
  113. URL obj = new URL(CONVERT_SERVICE_URL);
  114. con = (HttpURLConnection) obj.openConnection();
  115. con.setUseCaches(false);
  116. con.setDoOutput(true);
  117. con.setRequestMethod("POST");
  118. con.setRequestProperty("Connection", "Keep-Alive");
  119. con.setRequestProperty("Cache-Control", "no-cache");
  120. con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
  121. DataOutputStream postRequest = new DataOutputStream(con.getOutputStream());
  122. byte[] data = new byte[10240]; //10 KB buffer
  123. int bytesRead = fileContent.read(data);
  124. int w = 0, h = 0, dpi = 96;
  125. if (inputformat.equals("emf") && bytesRead >= 40)
  126. {
  127. //Read Frame from EMF header (the rectangular inclusive-inclusive dimensions, in .01 millimeter units,
  128. // of a rectangle that surrounds the image stored in the metafile.)
  129. int x0 = fromByteArray(data, 24);
  130. int y0 = fromByteArray(data, 28);
  131. int x1 = fromByteArray(data, 32);
  132. int y1 = fromByteArray(data, 36);
  133. //Approximate dimensions of the image
  134. w = (int) ((x1 - x0) / EMF_10thMM2PXL);
  135. h = (int) ((y1 - y0) / EMF_10thMM2PXL);
  136. }
  137. if (w > MAX_DIM || h > MAX_DIM)
  138. {
  139. dpi = (int) (dpi * Math.min(MAX_DIM / (double) w, MAX_DIM / (double) h));
  140. if (dpi == 0)
  141. {
  142. dpi = 1;
  143. }
  144. }
  145. addParameter("apikey", API_KEY, postRequest);
  146. addParameter("inputformat", inputformat, postRequest);
  147. addParameter("outputformat", outputformat, postRequest);
  148. addParameter("input", "upload", postRequest);
  149. addParameter("wait", "true", postRequest);
  150. addParameter("download", "true", postRequest);
  151. if (dpi != 96)
  152. {
  153. addParameter("converteroptions[density]", Integer.toString(dpi), postRequest);
  154. }
  155. addParameterHeader("file", fileName, postRequest);
  156. int total = 0;
  157. while(bytesRead != -1)
  158. {
  159. postRequest.write(data, 0, bytesRead);
  160. bytesRead = fileContent.read(data);
  161. total += bytesRead;
  162. if (total > MAX_FILE_SIZE)
  163. {
  164. postRequest.close();
  165. throw new Exception("File size exceeds the maximum allowed size of " + MAX_FILE_SIZE + " bytes.");
  166. }
  167. }
  168. postRequest.writeBytes(CRLF + TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + CRLF);
  169. postRequest.flush();
  170. postRequest.close();
  171. InputStream in = con.getInputStream();
  172. response.setStatus(con.getResponseCode());
  173. String contentType = "application/octet-stream";
  174. if ("png".equals(outputformat))
  175. {
  176. contentType = "image/png";
  177. }
  178. else if ("jpg".equals(outputformat))
  179. {
  180. contentType = "image/jpeg";
  181. }
  182. response.setHeader("Content-Type", contentType);
  183. OutputStream out = response.getOutputStream();
  184. bytesRead = in.read(data);
  185. try
  186. {
  187. URI uri = new URI(request.getHeader("referer"));
  188. String domain = uri.getHost();
  189. log.log(Level.CONFIG, "EMF-CONVERT, domain: " + domain + " ,Filename: " +
  190. fileName != null ? fileName : "" + ", size: " + bytesRead);
  191. }
  192. catch (Exception e)
  193. {
  194. e.printStackTrace();
  195. }
  196. while(bytesRead != -1)
  197. {
  198. out.write(data, 0, bytesRead);
  199. bytesRead = in.read(data);
  200. }
  201. in.close();
  202. out.flush();
  203. out.close();
  204. }
  205. catch(Exception e)
  206. {
  207. e.printStackTrace();
  208. if (con != null)
  209. {
  210. try
  211. {
  212. BufferedReader in = new BufferedReader(
  213. new InputStreamReader(con.getErrorStream()));
  214. String inputLine;
  215. while ((inputLine = in.readLine()) != null)
  216. {
  217. System.err.println(inputLine);
  218. }
  219. in.close();
  220. }
  221. catch (Exception e2)
  222. {
  223. // Ignore
  224. }
  225. }
  226. response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  227. }
  228. }
  229. }
  230. private void addParameter(String name, String val, DataOutputStream postRequest) throws IOException {
  231. addParameterHeader(name, null, postRequest);
  232. postRequest.writeBytes(val);
  233. postRequest.writeBytes(CRLF);
  234. }
  235. private void addParameterHeader(String name, String fileName, DataOutputStream postRequest) throws IOException {
  236. postRequest.writeBytes(TWO_HYPHENS + BOUNDARY + CRLF);
  237. postRequest.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" +
  238. (fileName != null? "; filename=\"" + fileName + "\"" + CRLF + "Content-Type: application/octet-stream" : "") + CRLF);
  239. postRequest.writeBytes(CRLF);
  240. }
  241. }