/
githubmirror
/
drawio
Обзор
Документация
Войти
/
githubmirror
/
drawio
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
src/main/server/java/com/mxgraph/online/ExportProxyServlet.java
240 строк
6 KB
David Benson
31.1.8 release
07 авг 2026, 02:43
07 авг 2026, 02:43
a1f615b
Код
Авторство
О чём код?
/** * Copyright (c) 2020-2025, JGraph Holdings Ltd * Copyright (c) 2020-2025, draw.io AG */ package com.mxgraph.online; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.mxgraph.online.Utils.SizeLimitExceededException; /** * Servlet implementation ExportProxyServlet */ @SuppressWarnings("serial") public class ExportProxyServlet extends HttpServlet { // EXPORT_URL is the only remaining backend service. PlantUML is parsed // locally by the native converter (js/plantuml/drawio-plantuml.min.js) // and EMF images are converted locally by js/diagramly/emf/emf-svg.js, // so PLANTUML_URL and EMF_CONVERT_URL were removed. Out-of-range service // ids still clamp to 0 below, so legacy /service/1/* and /service/2/* // callers now reach EXPORT_URL instead of failing. private final String[] supportedServices = {"EXPORT_URL"}; private void doRequest(String method, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { int serviceId = 0; String proxyPath = ""; String queryString = ""; try { if (request.getQueryString() != null) { queryString = "?" + request.getQueryString(); } if (request.getPathInfo() != null) // /{serviceId}/* { String[] pathParts = request.getPathInfo().split("/"); if (pathParts.length > 1) { serviceId = Integer.parseInt(pathParts[1]); } if (pathParts.length > 2) { proxyPath = String.join("/", Arrays.copyOfRange(pathParts, 2, pathParts.length)); } if (serviceId < 0 || serviceId >= supportedServices.length) { serviceId = 0; } } } catch (Exception e) { // Ignore and use 0 serviceId = 0; } String exportUrl = System.getenv(supportedServices[serviceId]); if (exportUrl == null || exportUrl.isEmpty() || (!exportUrl.startsWith("http://") && !exportUrl.startsWith("https://"))) { throw new Exception(supportedServices[serviceId] + " not set or invalid"); } else if (!exportUrl.endsWith("/")) // There are other non-trivial cases, admins should configure these URLs carefully { exportUrl += "/"; } // Defence in depth: a spec-compliant servlet container already // canonicalises the request path, but reject any residual or // encoded traversal in the forwarded path, and confirm the // resolved URL still points under the configured base URL, so it // cannot escape to other endpoints on the backend. if (containsTraversal(proxyPath)) { throw new Exception("Invalid proxy path"); } if (!new URL(exportUrl + proxyPath).toURI().normalize().toString() .startsWith(exportUrl)) { throw new Exception("Proxy path escapes the configured base URL"); } URL url = new URL(exportUrl + proxyPath + queryString); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod(method); //Copy request headers to export server Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); Enumeration<String> headers = request.getHeaders(headerName); while (headers.hasMoreElements()) { String headerValue = headers.nextElement(); con.addRequestProperty(headerName, headerValue); } } if ("POST".equals(method)) { // Send post request con.setDoOutput(true); OutputStream params = con.getOutputStream(); Utils.copyRestricted(request.getInputStream(), params); params.flush(); params.close(); } int responseCode = con.getResponseCode(); //Copy response code response.setStatus(responseCode); //Copy response headers Map<String, List<String>> map = con.getHeaderFields(); for (Map.Entry<String, List<String>> entry : map.entrySet()) { String key = entry.getKey(); if (key != null) { for (String val : entry.getValue()) { response.addHeader(entry.getKey(), val); } } } //Copy response OutputStream out = response.getOutputStream(); //Error if (responseCode >= 400) { Utils.copy(con.getErrorStream(), out); } else //Success { Utils.copy(con.getInputStream(), out); } out.flush(); out.close(); } catch (SizeLimitExceededException e) { response.setStatus(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE); throw e; } catch (Exception e) { response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(); } } /** * Returns true if the forwarded proxy path contains a path-traversal * sequence - a literal "." or ".." segment, an encoded dot/slash * ("%2e", "%2f", "%5c") that a downstream server might decode into one, * or a backslash. Used as defence in depth on top of the servlet * container's own path canonicalisation. */ private boolean containsTraversal(String proxyPath) { if (proxyPath == null || proxyPath.isEmpty()) { return false; } String lower = proxyPath.toLowerCase(); if (lower.contains("%2e") || lower.contains("%2f") || lower.contains("%5c") || proxyPath.contains("\\")) { return true; } for (String segment : proxyPath.split("/")) { if (segment.equals("..") || segment.equals(".")) { return true; } } return false; } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doRequest("GET", request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doRequest("POST", request, response); } }