001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.http; 019 020import java.io.IOException; 021import java.io.PrintWriter; 022import java.net.HttpURLConnection; 023import java.net.URI; 024import java.net.URL; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.SortedSet; 032import java.util.TreeSet; 033import java.util.concurrent.CountDownLatch; 034import java.util.concurrent.Executor; 035import java.util.concurrent.Executors; 036import javax.servlet.Filter; 037import javax.servlet.FilterChain; 038import javax.servlet.FilterConfig; 039import javax.servlet.ServletContext; 040import javax.servlet.ServletException; 041import javax.servlet.ServletRequest; 042import javax.servlet.ServletResponse; 043import javax.servlet.http.HttpServlet; 044import javax.servlet.http.HttpServletRequest; 045import javax.servlet.http.HttpServletRequestWrapper; 046import javax.servlet.http.HttpServletResponse; 047import org.apache.hadoop.conf.Configuration; 048import org.apache.hadoop.fs.CommonConfigurationKeys; 049import org.apache.hadoop.hbase.HBaseClassTestRule; 050import org.apache.hadoop.hbase.http.HttpServer.QuotingInputFilter.RequestQuoter; 051import org.apache.hadoop.hbase.http.resource.JerseyResource; 052import org.apache.hadoop.hbase.testclassification.MiscTests; 053import org.apache.hadoop.hbase.testclassification.SmallTests; 054import org.apache.hadoop.net.NetUtils; 055import org.apache.hadoop.security.Groups; 056import org.apache.hadoop.security.ShellBasedUnixGroupsMapping; 057import org.apache.hadoop.security.UserGroupInformation; 058import org.apache.hadoop.security.authorize.AccessControlList; 059import org.junit.AfterClass; 060import org.junit.Assert; 061import org.junit.BeforeClass; 062import org.junit.ClassRule; 063import org.junit.Ignore; 064import org.junit.Test; 065import org.junit.experimental.categories.Category; 066import org.mockito.Mockito; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector; 071import org.apache.hbase.thirdparty.org.eclipse.jetty.util.ajax.JSON; 072 073@Category({MiscTests.class, SmallTests.class}) 074public class TestHttpServer extends HttpServerFunctionalTest { 075 @ClassRule 076 public static final HBaseClassTestRule CLASS_RULE = 077 HBaseClassTestRule.forClass(TestHttpServer.class); 078 079 private static final Logger LOG = LoggerFactory.getLogger(TestHttpServer.class); 080 private static HttpServer server; 081 private static URL baseUrl; 082 // jetty 9.4.x needs this many threads to start, even in the small. 083 static final int MAX_THREADS = 16; 084 085 @SuppressWarnings("serial") 086 public static class EchoMapServlet extends HttpServlet { 087 @Override 088 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 089 PrintWriter out = response.getWriter(); 090 Map<String, String[]> params = request.getParameterMap(); 091 SortedSet<String> keys = new TreeSet<>(params.keySet()); 092 for(String key: keys) { 093 out.print(key); 094 out.print(':'); 095 String[] values = params.get(key); 096 if (values.length > 0) { 097 out.print(values[0]); 098 for(int i=1; i < values.length; ++i) { 099 out.print(','); 100 out.print(values[i]); 101 } 102 } 103 out.print('\n'); 104 } 105 out.close(); 106 } 107 } 108 109 @SuppressWarnings("serial") 110 public static class EchoServlet extends HttpServlet { 111 @Override 112 public void doGet(HttpServletRequest request, HttpServletResponse response) 113 throws IOException { 114 PrintWriter out = response.getWriter(); 115 SortedSet<String> sortedKeys = new TreeSet<>(); 116 Enumeration<String> keys = request.getParameterNames(); 117 while(keys.hasMoreElements()) { 118 sortedKeys.add(keys.nextElement()); 119 } 120 for(String key: sortedKeys) { 121 out.print(key); 122 out.print(':'); 123 out.print(request.getParameter(key)); 124 out.print('\n'); 125 } 126 out.close(); 127 } 128 } 129 130 @SuppressWarnings("serial") 131 public static class LongHeaderServlet extends HttpServlet { 132 @Override 133 public void doGet(HttpServletRequest request, HttpServletResponse response) { 134 Assert.assertEquals(63 * 1024, request.getHeader("longheader").length()); 135 response.setStatus(HttpServletResponse.SC_OK); 136 } 137 } 138 139 @SuppressWarnings("serial") 140 public static class HtmlContentServlet extends HttpServlet { 141 @Override 142 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 143 response.setContentType("text/html"); 144 PrintWriter out = response.getWriter(); 145 out.print("hello world"); 146 out.close(); 147 } 148 } 149 150 @BeforeClass public static void setup() throws Exception { 151 Configuration conf = new Configuration(); 152 conf.setInt(HttpServer.HTTP_MAX_THREADS, MAX_THREADS); 153 server = createTestServer(conf); 154 server.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 155 server.addUnprivilegedServlet("echomap", "/echomap", EchoMapServlet.class); 156 server.addUnprivilegedServlet("htmlcontent", "/htmlcontent", HtmlContentServlet.class); 157 server.addUnprivilegedServlet("longheader", "/longheader", LongHeaderServlet.class); 158 server.addJerseyResourcePackage( 159 JerseyResource.class.getPackage().getName(), "/jersey/*"); 160 server.start(); 161 baseUrl = getServerURL(server); 162 LOG.info("HTTP server started: "+ baseUrl); 163 } 164 165 @AfterClass public static void cleanup() throws Exception { 166 server.stop(); 167 } 168 169 /** 170 * Test the maximum number of threads cannot be exceeded. 171 */ 172 @Test 173 public void testMaxThreads() throws Exception { 174 int clientThreads = MAX_THREADS * 10; 175 Executor executor = Executors.newFixedThreadPool(clientThreads); 176 // Run many clients to make server reach its maximum number of threads 177 final CountDownLatch ready = new CountDownLatch(clientThreads); 178 final CountDownLatch start = new CountDownLatch(1); 179 for (int i = 0; i < clientThreads; i++) { 180 executor.execute(() -> { 181 ready.countDown(); 182 try { 183 start.await(); 184 assertEquals("a:b\nc:d\n", 185 readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 186 int serverThreads = server.webServer.getThreadPool().getThreads(); 187 assertTrue("More threads are started than expected, Server Threads count: " 188 + serverThreads, serverThreads <= MAX_THREADS); 189 LOG.info("Number of threads = " + serverThreads + 190 " which is less or equal than the max = " + MAX_THREADS); 191 } catch (Exception e) { 192 // do nothing 193 } 194 }); 195 } 196 // Start the client threads when they are all ready 197 ready.await(); 198 start.countDown(); 199 } 200 201 @Test public void testEcho() throws Exception { 202 assertEquals("a:b\nc:d\n", 203 readOutput(new URL(baseUrl, "/echo?a=b&c=d"))); 204 assertEquals("a:b\nc<:d\ne:>\n", 205 readOutput(new URL(baseUrl, "/echo?a=b&c<=d&e=>"))); 206 } 207 208 /** Test the echo map servlet that uses getParameterMap. */ 209 @Test public void testEchoMap() throws Exception { 210 assertEquals("a:b\nc:d\n", 211 readOutput(new URL(baseUrl, "/echomap?a=b&c=d"))); 212 assertEquals("a:b,>\nc<:d\n", 213 readOutput(new URL(baseUrl, "/echomap?a=b&c<=d&a=>"))); 214 } 215 216 /** 217 * Test that verifies headers can be up to 64K long. 218 * The test adds a 63K header leaving 1K for other headers. 219 * This is because the header buffer setting is for ALL headers, 220 * names and values included. */ 221 @Test public void testLongHeader() throws Exception { 222 URL url = new URL(baseUrl, "/longheader"); 223 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 224 StringBuilder sb = new StringBuilder(); 225 for (int i = 0 ; i < 63 * 1024; i++) { 226 sb.append("a"); 227 } 228 conn.setRequestProperty("longheader", sb.toString()); 229 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 230 } 231 232 @Test 233 public void testContentTypes() throws Exception { 234 // Static CSS files should have text/css 235 URL cssUrl = new URL(baseUrl, "/static/test.css"); 236 HttpURLConnection conn = (HttpURLConnection)cssUrl.openConnection(); 237 conn.connect(); 238 assertEquals(200, conn.getResponseCode()); 239 assertEquals("text/css", conn.getContentType()); 240 241 // Servlets should have text/plain with proper encoding by default 242 URL servletUrl = new URL(baseUrl, "/echo?a=b"); 243 conn = (HttpURLConnection)servletUrl.openConnection(); 244 conn.connect(); 245 assertEquals(200, conn.getResponseCode()); 246 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 247 248 // We should ignore parameters for mime types - ie a parameter 249 // ending in .css should not change mime type 250 servletUrl = new URL(baseUrl, "/echo?a=b.css"); 251 conn = (HttpURLConnection)servletUrl.openConnection(); 252 conn.connect(); 253 assertEquals(200, conn.getResponseCode()); 254 assertEquals("text/plain;charset=utf-8", conn.getContentType()); 255 256 // Servlets that specify text/html should get that content type 257 servletUrl = new URL(baseUrl, "/htmlcontent"); 258 conn = (HttpURLConnection)servletUrl.openConnection(); 259 conn.connect(); 260 assertEquals(200, conn.getResponseCode()); 261 assertEquals("text/html;charset=utf-8", conn.getContentType()); 262 263 // JSPs should default to text/html with utf8 264 // JSPs do not work from unit tests 265 // servletUrl = new URL(baseUrl, "/testjsp.jsp"); 266 // conn = (HttpURLConnection)servletUrl.openConnection(); 267 // conn.connect(); 268 // assertEquals(200, conn.getResponseCode()); 269 // assertEquals("text/html; charset=utf-8", conn.getContentType()); 270 } 271 272 /** 273 * Dummy filter that mimics as an authentication filter. Obtains user identity 274 * from the request parameter user.name. Wraps around the request so that 275 * request.getRemoteUser() returns the user identity. 276 * 277 */ 278 public static class DummyServletFilter implements Filter { 279 @Override 280 public void destroy() { } 281 282 @Override 283 public void doFilter(ServletRequest request, ServletResponse response, 284 FilterChain filterChain) throws IOException, ServletException { 285 final String userName = request.getParameter("user.name"); 286 ServletRequest requestModified = 287 new HttpServletRequestWrapper((HttpServletRequest) request) { 288 @Override 289 public String getRemoteUser() { 290 return userName; 291 } 292 }; 293 filterChain.doFilter(requestModified, response); 294 } 295 296 @Override 297 public void init(FilterConfig arg0) { } 298 } 299 300 /** 301 * FilterInitializer that initialized the DummyFilter. 302 * 303 */ 304 public static class DummyFilterInitializer extends FilterInitializer { 305 public DummyFilterInitializer() { 306 } 307 308 @Override 309 public void initFilter(FilterContainer container, Configuration conf) { 310 container.addFilter("DummyFilter", DummyServletFilter.class.getName(), null); 311 } 312 } 313 314 /** 315 * Access a URL and get the corresponding return Http status code. The URL 316 * will be accessed as the passed user, by sending user.name request 317 * parameter. 318 * 319 * @param urlstring The url to access 320 * @param userName The user to perform access as 321 * @return The HTTP response code 322 * @throws IOException if there is a problem communicating with the server 323 */ 324 private static int getHttpStatusCode(String urlstring, String userName) throws IOException { 325 URL url = new URL(urlstring + "?user.name=" + userName); 326 System.out.println("Accessing " + url + " as user " + userName); 327 HttpURLConnection connection = (HttpURLConnection)url.openConnection(); 328 connection.connect(); 329 return connection.getResponseCode(); 330 } 331 332 /** 333 * Custom user->group mapping service. 334 */ 335 public static class MyGroupsProvider extends ShellBasedUnixGroupsMapping { 336 static Map<String, List<String>> mapping = new HashMap<>(); 337 338 static void clearMapping() { 339 mapping.clear(); 340 } 341 342 @Override 343 public List<String> getGroups(String user) { 344 return mapping.get(user); 345 } 346 } 347 348 /** 349 * Verify the access for /logs, /stacks, /conf, /logLevel and /metrics 350 * servlets, when authentication filters are set, but authorization is not 351 * enabled. 352 */ 353 @Test 354 @Ignore 355 public void testDisabledAuthorizationOfDefaultServlets() throws Exception { 356 Configuration conf = new Configuration(); 357 358 // Authorization is disabled by default 359 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, 360 DummyFilterInitializer.class.getName()); 361 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 362 MyGroupsProvider.class.getName()); 363 Groups.getUserToGroupsMappingService(conf); 364 MyGroupsProvider.clearMapping(); 365 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 366 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 367 368 HttpServer myServer = new HttpServer.Builder().setName("test") 369 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).build(); 370 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 371 myServer.start(); 372 String serverURL = "http://" + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 373 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 374 for (String user : new String[] { "userA", "userB" }) { 375 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL 376 + servlet, user)); 377 } 378 } 379 myServer.stop(); 380 } 381 382 /** 383 * Verify the administrator access for /logs, /stacks, /conf, /logLevel and 384 * /metrics servlets. 385 */ 386 @Test 387 @Ignore 388 public void testAuthorizationOfDefaultServlets() throws Exception { 389 Configuration conf = new Configuration(); 390 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, 391 true); 392 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, 393 true); 394 conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, 395 DummyFilterInitializer.class.getName()); 396 397 conf.set(CommonConfigurationKeys.HADOOP_SECURITY_GROUP_MAPPING, 398 MyGroupsProvider.class.getName()); 399 Groups.getUserToGroupsMappingService(conf); 400 MyGroupsProvider.clearMapping(); 401 MyGroupsProvider.mapping.put("userA", Collections.singletonList("groupA")); 402 MyGroupsProvider.mapping.put("userB", Collections.singletonList("groupB")); 403 MyGroupsProvider.mapping.put("userC", Collections.singletonList("groupC")); 404 MyGroupsProvider.mapping.put("userD", Collections.singletonList("groupD")); 405 MyGroupsProvider.mapping.put("userE", Collections.singletonList("groupE")); 406 407 HttpServer myServer = new HttpServer.Builder().setName("test") 408 .addEndpoint(new URI("http://localhost:0")).setFindPort(true).setConf(conf) 409 .setACL(new AccessControlList("userA,userB groupC,groupD")).build(); 410 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 411 myServer.start(); 412 413 String serverURL = "http://" 414 + NetUtils.getHostPortString(myServer.getConnectorAddress(0)) + "/"; 415 for (String servlet : new String[] { "conf", "logs", "stacks", "logLevel", "metrics" }) { 416 for (String user : new String[] { "userA", "userB", "userC", "userD" }) { 417 assertEquals(HttpURLConnection.HTTP_OK, getHttpStatusCode(serverURL 418 + servlet, user)); 419 } 420 assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, getHttpStatusCode( 421 serverURL + servlet, "userE")); 422 } 423 myServer.stop(); 424 } 425 426 @Test 427 public void testRequestQuoterWithNull() { 428 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 429 Mockito.doReturn(null).when(request).getParameterValues("dummy"); 430 RequestQuoter requestQuoter = new RequestQuoter(request); 431 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 432 Assert.assertNull("It should return null " 433 + "when there are no values for the parameter", parameterValues); 434 } 435 436 @Test 437 public void testRequestQuoterWithNotNull() { 438 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 439 String[] values = new String[] { "abc", "def" }; 440 Mockito.doReturn(values).when(request).getParameterValues("dummy"); 441 RequestQuoter requestQuoter = new RequestQuoter(request); 442 String[] parameterValues = requestQuoter.getParameterValues("dummy"); 443 Assert.assertTrue("It should return Parameter Values", Arrays.equals( 444 values, parameterValues)); 445 } 446 447 @SuppressWarnings("unchecked") 448 private static Map<String, Object> parse(String jsonString) { 449 return (Map<String, Object>)JSON.parse(jsonString); 450 } 451 452 @Test public void testJersey() throws Exception { 453 LOG.info("BEGIN testJersey()"); 454 final String js = readOutput(new URL(baseUrl, "/jersey/foo?op=bar")); 455 final Map<String, Object> m = parse(js); 456 LOG.info("m=" + m); 457 assertEquals("foo", m.get(JerseyResource.PATH)); 458 assertEquals("bar", m.get(JerseyResource.OP)); 459 LOG.info("END testJersey()"); 460 } 461 462 @Test 463 public void testHasAdministratorAccess() throws Exception { 464 Configuration conf = new Configuration(); 465 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false); 466 ServletContext context = Mockito.mock(ServletContext.class); 467 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 468 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(null); 469 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 470 Mockito.when(request.getRemoteUser()).thenReturn(null); 471 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 472 473 //authorization OFF 474 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 475 476 //authorization ON & user NULL 477 response = Mockito.mock(HttpServletResponse.class); 478 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 479 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 480 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED), 481 Mockito.anyString()); 482 483 //authorization ON & user NOT NULL & ACLs NULL 484 response = Mockito.mock(HttpServletResponse.class); 485 Mockito.when(request.getRemoteUser()).thenReturn("foo"); 486 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 487 488 //authorization ON & user NOT NULL & ACLs NOT NULL & user not in ACLs 489 response = Mockito.mock(HttpServletResponse.class); 490 AccessControlList acls = Mockito.mock(AccessControlList.class); 491 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); 492 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 493 Assert.assertFalse(HttpServer.hasAdministratorAccess(context, request, response)); 494 Mockito.verify(response).sendError(Mockito.eq(HttpServletResponse.SC_FORBIDDEN), 495 Mockito.anyString()); 496 497 //authorization ON & user NOT NULL & ACLs NOT NULL & user in in ACLs 498 response = Mockito.mock(HttpServletResponse.class); 499 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(true); 500 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 501 Assert.assertTrue(HttpServer.hasAdministratorAccess(context, request, response)); 502 503 } 504 505 @Test 506 public void testRequiresAuthorizationAccess() throws Exception { 507 Configuration conf = new Configuration(); 508 ServletContext context = Mockito.mock(ServletContext.class); 509 Mockito.when(context.getAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE)).thenReturn(conf); 510 HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 511 HttpServletResponse response = Mockito.mock(HttpServletResponse.class); 512 513 //requires admin access to instrumentation, FALSE by default 514 Assert.assertTrue(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 515 516 //requires admin access to instrumentation, TRUE 517 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, true); 518 conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, true); 519 AccessControlList acls = Mockito.mock(AccessControlList.class); 520 Mockito.when(acls.isUserAllowed(Mockito.<UserGroupInformation>any())).thenReturn(false); 521 Mockito.when(context.getAttribute(HttpServer.ADMINS_ACL)).thenReturn(acls); 522 Assert.assertFalse(HttpServer.isInstrumentationAccessAllowed(context, request, response)); 523 } 524 525 @Test 526 public void testBindAddress() throws Exception { 527 checkBindAddress("localhost", 0, false).stop(); 528 // hang onto this one for a bit more testing 529 HttpServer myServer = checkBindAddress("localhost", 0, false); 530 HttpServer myServer2 = null; 531 try { 532 int port = myServer.getConnectorAddress(0).getPort(); 533 // it's already in use, true = expect a higher port 534 myServer2 = checkBindAddress("localhost", port, true); 535 // try to reuse the port 536 port = myServer2.getConnectorAddress(0).getPort(); 537 myServer2.stop(); 538 assertNull(myServer2.getConnectorAddress(0)); // not bound 539 myServer2.openListeners(); 540 assertEquals(port, myServer2.getConnectorAddress(0).getPort()); // expect same port 541 } finally { 542 myServer.stop(); 543 if (myServer2 != null) { 544 myServer2.stop(); 545 } 546 } 547 } 548 549 private HttpServer checkBindAddress(String host, int port, boolean findPort) 550 throws Exception { 551 HttpServer server = createServer(host, port); 552 try { 553 // not bound, ephemeral should return requested port (0 for ephemeral) 554 ServerConnector listener = server.getServerConnectors().get(0); 555 556 assertEquals(port, listener.getPort()); 557 // verify hostname is what was given 558 server.openListeners(); 559 assertEquals(host, server.getConnectorAddress(0).getHostName()); 560 561 int boundPort = server.getConnectorAddress(0).getPort(); 562 if (port == 0) { 563 assertTrue(boundPort != 0); // ephemeral should now return bound port 564 } else if (findPort) { 565 assertTrue(boundPort > port); 566 // allow a little wiggle room to prevent random test failures if 567 // some consecutive ports are already in use 568 assertTrue(boundPort - port < 8); 569 } 570 } catch (Exception e) { 571 server.stop(); 572 throw e; 573 } 574 return server; 575 } 576 577 @Test 578 public void testXFrameHeaderSameOrigin() throws Exception { 579 Configuration conf = new Configuration(); 580 conf.set("hbase.http.filter.xframeoptions.mode", "SAMEORIGIN"); 581 582 HttpServer myServer = new HttpServer.Builder().setName("test") 583 .addEndpoint(new URI("http://localhost:0")) 584 .setFindPort(true).setConf(conf).build(); 585 myServer.setAttribute(HttpServer.CONF_CONTEXT_ATTRIBUTE, conf); 586 myServer.addUnprivilegedServlet("echo", "/echo", EchoServlet.class); 587 myServer.start(); 588 589 String serverURL = "http://" 590 + NetUtils.getHostPortString(myServer.getConnectorAddress(0)); 591 URL url = new URL(new URL(serverURL), "/echo?a=b&c=d"); 592 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 593 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 594 assertEquals("SAMEORIGIN", conn.getHeaderField("X-Frame-Options")); 595 myServer.stop(); 596 } 597 598 @Test 599 public void testNoCacheHeader() throws Exception { 600 URL url = new URL(baseUrl, "/echo?a=b&c=d"); 601 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 602 assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); 603 assertEquals("no-cache", conn.getHeaderField("Cache-Control")); 604 assertEquals("no-cache", conn.getHeaderField("Pragma")); 605 assertNotNull(conn.getHeaderField("Expires")); 606 assertNotNull(conn.getHeaderField("Date")); 607 assertEquals(conn.getHeaderField("Expires"), conn.getHeaderField("Date")); 608 assertEquals("DENY", conn.getHeaderField("X-Frame-Options")); 609 } 610 611 @Test 612 public void testHttpMethods() throws Exception { 613 // HTTP TRACE method should be disabled for security 614 // See https://www.owasp.org/index.php/Cross_Site_Tracing 615 URL url = new URL(baseUrl, "/echo?a=b"); 616 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 617 conn.setRequestMethod("TRACE"); 618 conn.connect(); 619 assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); 620 } 621}