diff --git a/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoData.java b/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoData.java index 0e2fe7c904..25799db815 100644 --- a/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoData.java +++ b/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoData.java @@ -265,11 +265,33 @@ public void run() throws IOException { phonePrefixMap = combineMultipleTimes(phonePrefixMap); PrintWriter printWriter = new PrintWriter(new BufferedOutputStream(outputStream)); for (Map.Entry mapping : phonePrefixMap.entrySet()) { - printWriter.printf("%s|%s%s", mapping.getKey(), mapping.getValue(), outputLineSeparator); + printWriter.printf("%s|%s%s", + escapeHtml(mapping.getKey()), + escapeHtml(mapping.getValue()), + outputLineSeparator); } printWriter.flush(); } + private static String escapeHtml(String input) { + if (input == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + switch (c) { + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '&': sb.append("&"); break; + case '"': sb.append("""); break; + case '\'': sb.append("'"); break; + default: sb.append(c); + } + } + return sb.toString(); + } + public static void main(String[] args) { if (args.length != 2) { LOGGER.severe("usage: java -jar combine-geodata.jar /path/to/input /path/to/output"); diff --git a/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoDataServlet.java b/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoDataServlet.java index 6a5889065f..46d54f33bc 100644 --- a/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoDataServlet.java +++ b/tools/java/data/src/com/google/i18n/phonenumbers/CombineGeoDataServlet.java @@ -29,18 +29,47 @@ * A servlet that invokes the geocoding data combination tool. */ public class CombineGeoDataServlet extends HttpServlet { + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, javax.servlet.ServletException { + req.setAttribute("csrf_token", getOrGenerateCsrfToken(req)); + req.getRequestDispatcher("index.jsp").forward(req, resp); + } + @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (!isTokenValid(req)) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid or missing CSRF token."); + return; + } + resp.setContentType("text/html;charset=UTF-8"); String input = req.getParameter("geodata"); + if (input == null) { + input = ""; + } resp.getOutputStream().print(""); resp.getOutputStream().print( ""); resp.getOutputStream().print(""); CombineGeoData combineGeoData = new CombineGeoData( - new ByteArrayInputStream(input.getBytes()), resp.getOutputStream(), "
"); + new ByteArrayInputStream(input.getBytes("UTF-8")), resp.getOutputStream(), "
"); combineGeoData.run(); resp.getOutputStream().print(""); resp.getOutputStream().flush(); } + + protected String getOrGenerateCsrfToken(HttpServletRequest req) { + String csrfToken = (String) req.getSession().getAttribute("csrf_token"); + if (csrfToken == null) { + csrfToken = java.util.UUID.randomUUID().toString(); + req.getSession().setAttribute("csrf_token", csrfToken); + } + return csrfToken; + } + + protected boolean isTokenValid(HttpServletRequest req) { + String sessionToken = (String) req.getSession().getAttribute("csrf_token"); + String requestToken = req.getParameter("csrf_token"); + return sessionToken != null && sessionToken.equals(requestToken); + } } diff --git a/tools/java/data/test/com/google/i18n/phonenumbers/CombineGeoDataServletTest.java b/tools/java/data/test/com/google/i18n/phonenumbers/CombineGeoDataServletTest.java new file mode 100644 index 0000000000..08fd23edeb --- /dev/null +++ b/tools/java/data/test/com/google/i18n/phonenumbers/CombineGeoDataServletTest.java @@ -0,0 +1,109 @@ +package com.google.i18n.phonenumbers; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class CombineGeoDataServletTest { + + private CombineGeoDataServlet servlet; + private Map sessionAttributes; + private Map requestAttributes; + + @Before + public void setUp() { + servlet = new CombineGeoDataServlet(); + sessionAttributes = new HashMap<>(); + requestAttributes = new HashMap<>(); + } + + @Test + public void testGetOrGenerateCsrfToken_NewSession() { + HttpServletRequest mockRequest = createMockRequest(); + + String token = servlet.getOrGenerateCsrfToken(mockRequest); + + assertNotNull(token); + assertEquals(token, sessionAttributes.get("csrf_token")); + } + + @Test + public void testGetOrGenerateCsrfToken_ExistingSession() { + String existingToken = UUID.randomUUID().toString(); + sessionAttributes.put("csrf_token", existingToken); + HttpServletRequest mockRequest = createMockRequest(); + + String token = servlet.getOrGenerateCsrfToken(mockRequest); + + assertEquals(existingToken, token); + } + + @Test + public void testIsTokenValid_NoSessionToken() { + HttpServletRequest mockRequest = createMockRequest(); + // No token in session + + assertFalse(servlet.isTokenValid(mockRequest)); + } + + @Test + public void testIsTokenValid_ValidToken() { + String token = UUID.randomUUID().toString(); + sessionAttributes.put("csrf_token", token); + HttpServletRequest mockRequest = createMockRequest(token); + + assertTrue(servlet.isTokenValid(mockRequest)); + } + + private HttpServletRequest createMockRequest() { + return createMockRequest(null); + } + + private HttpServletRequest createMockRequest(String requestToken) { + return (HttpServletRequest) Proxy.newProxyInstance( + HttpServletRequest.class.getClassLoader(), + new Class[] { HttpServletRequest.class }, + (proxy, method, args) -> { + if (method.getName().equals("getSession")) { + return createMockSession(); + } else if (method.getName().equals("setAttribute")) { + requestAttributes.put((String) args[0], args[1]); + return null; + } else if (method.getName().equals("getAttribute")) { + return requestAttributes.get(args[0]); + } else if (method.getName().equals("getParameter")) { + if ("csrf_token".equals(args[0])) { + return requestToken; + } + return null; + } + return null; + }); + } + + private HttpSession createMockSession() { + return (HttpSession) Proxy.newProxyInstance( + HttpSession.class.getClassLoader(), + new Class[] { HttpSession.class }, + (proxy, method, args) -> { + if (method.getName().equals("getAttribute")) { + return sessionAttributes.get(args[0]); + } else if (method.getName().equals("setAttribute")) { + sessionAttributes.put((String) args[0], args[1]); + return null; + } + return null; + }); + } +} diff --git a/tools/java/data/webapp/WEB-INF/web.xml b/tools/java/data/webapp/WEB-INF/web.xml index 911a1c2643..3656125682 100644 --- a/tools/java/data/webapp/WEB-INF/web.xml +++ b/tools/java/data/webapp/WEB-INF/web.xml @@ -12,7 +12,11 @@ CombineGeoDataServlet /combine + + CombineGeoDataServlet + /index.html + - index.html + index.jsp diff --git a/tools/java/data/webapp/index.html b/tools/java/data/webapp/index.jsp similarity index 80% rename from tools/java/data/webapp/index.html rename to tools/java/data/webapp/index.jsp index 5916f54274..a83e4582d0 100644 --- a/tools/java/data/webapp/index.html +++ b/tools/java/data/webapp/index.jsp @@ -1,8 +1,10 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+ ">

Geocoding Prefix Reducer

Please paste your geocoding data below. Each line should be in the format