1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.test.util.http;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.net.URI;
24 import java.nio.channels.SeekableByteChannel;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.Files;
27 import java.nio.file.StandardOpenOption;
28 import java.util.ArrayList;
29 import java.util.Base64;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.TreeMap;
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.atomic.AtomicInteger;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38 import java.util.stream.Collectors;
39
40 import com.google.gson.Gson;
41 import jakarta.servlet.http.HttpServletResponse;
42 import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
43 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
44 import org.eclipse.aether.spi.connector.transport.http.RFC9457.RFC9457Payload;
45 import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
46 import org.eclipse.jetty.compression.server.CompressionConfig;
47 import org.eclipse.jetty.compression.server.CompressionHandler;
48 import org.eclipse.jetty.http.DateGenerator;
49 import org.eclipse.jetty.http.HttpField;
50 import org.eclipse.jetty.http.HttpFields;
51 import org.eclipse.jetty.http.HttpHeader;
52 import org.eclipse.jetty.http.HttpMethod;
53 import org.eclipse.jetty.http.HttpURI;
54 import org.eclipse.jetty.http.pathmap.MatchedResource;
55 import org.eclipse.jetty.http.pathmap.PathMappings;
56 import org.eclipse.jetty.http.pathmap.PathSpec;
57 import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
58 import org.eclipse.jetty.io.ByteBufferPool;
59 import org.eclipse.jetty.io.Content;
60 import org.eclipse.jetty.server.Handler;
61 import org.eclipse.jetty.server.HttpConfiguration;
62 import org.eclipse.jetty.server.HttpConnectionFactory;
63 import org.eclipse.jetty.server.Request;
64 import org.eclipse.jetty.server.Response;
65 import org.eclipse.jetty.server.SecureRequestCustomizer;
66 import org.eclipse.jetty.server.Server;
67 import org.eclipse.jetty.server.ServerConnector;
68 import org.eclipse.jetty.server.SslConnectionFactory;
69 import org.eclipse.jetty.util.Blocker;
70 import org.eclipse.jetty.util.Callback;
71 import org.eclipse.jetty.util.ssl.SslContextFactory;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 public class HttpServer {
76
77 public static class LogEntry {
78
79 private final String method;
80
81 private final String path;
82
83 private final Map<String, String> requestHeaders;
84
85 private Map<String, String> responseHeaders;
86
87 CountDownLatch responseHeadersAvailableSignal = new CountDownLatch(1);
88
89 public LogEntry(String method, String path, Map<String, String> requestHeaders) {
90 this.method = method;
91 this.path = path;
92 this.requestHeaders = requestHeaders;
93 }
94
95 public String getMethod() {
96 return method;
97 }
98
99 public String getPath() {
100 return path;
101 }
102
103 public Map<String, String> getRequestHeaders() {
104 return requestHeaders;
105 }
106
107
108
109
110
111 public Map<String, String> getResponseHeaders() {
112 try {
113 if (!responseHeadersAvailableSignal.await(30, java.util.concurrent.TimeUnit.SECONDS)) {
114 throw new IllegalStateException("Timeout waiting for response headers to be available");
115 }
116 } catch (InterruptedException e) {
117 Thread.currentThread().interrupt();
118 throw new IllegalStateException("Interrupted while waiting for response headers to be available", e);
119 }
120 return responseHeaders;
121 }
122
123 public void setResponseHeaders(Map<String, String> responseHeaders) {
124 this.responseHeaders = responseHeaders;
125 responseHeadersAvailableSignal.countDown();
126 }
127
128 @Override
129 public String toString() {
130 return method + " " + path;
131 }
132 }
133
134 public enum ExpectContinue {
135 FAIL,
136 PROPER,
137 BROKEN
138 }
139
140 public enum ChecksumHeader {
141 NEXUS,
142 XCHECKSUM
143 }
144
145 private static final Logger LOGGER = LoggerFactory.getLogger(HttpServer.class);
146
147 private File repoDir;
148
149 private boolean rangeSupport = true;
150
151 private boolean webDav;
152
153 private ExpectContinue expectContinue = ExpectContinue.PROPER;
154
155 private ChecksumHeader checksumHeader;
156
157 private Server server;
158
159 private ServerConnector httpConnector;
160
161 private ServerConnector httpsConnector;
162
163 private String username;
164
165 private String password;
166
167 private String proxyUsername;
168
169 private String proxyPassword;
170
171 private final AtomicInteger connectionsToClose = new AtomicInteger(0);
172
173 private final AtomicInteger serverErrorsBeforeWorks = new AtomicInteger(0);
174
175 private int serverErrorStatusCode;
176
177 private final List<LogEntry> logEntries = Collections.synchronizedList(new ArrayList<>());
178
179 public String getHost() {
180 return "localhost";
181 }
182
183 public int getHttpPort() {
184 return httpConnector != null ? httpConnector.getLocalPort() : -1;
185 }
186
187 public int getHttpsPort() {
188 return httpsConnector != null ? httpsConnector.getLocalPort() : -1;
189 }
190
191 public String getHttpUrl() {
192 return "http://" + getHost() + ":" + getHttpPort();
193 }
194
195 public String getHttpsUrl() {
196 return "https://" + getHost() + ":" + getHttpsPort();
197 }
198
199 public HttpServer addSslConnector() {
200 return addSslConnector(true, true);
201 }
202
203 public HttpServer addSelfSignedSslConnector() {
204 return addSslConnector(false, true);
205 }
206
207 public HttpServer addSelfSignedSslConnectorHttp2Only() {
208 return addSslConnector(false, false);
209 }
210
211 private HttpServer addSslConnector(boolean needClientAuth, boolean needHttp11) {
212 if (httpsConnector == null) {
213 SslContextFactory.Server ssl = new SslContextFactory.Server();
214 ssl.setNeedClientAuth(needClientAuth);
215 if (!needClientAuth) {
216 ssl.setKeyStorePath(HttpTransporterTest.KEY_STORE_SELF_SIGNED_PATH
217 .toAbsolutePath()
218 .toString());
219 ssl.setKeyStorePassword("server-pwd");
220 ssl.setSniRequired(false);
221 } else {
222 ssl.setKeyStorePath(
223 HttpTransporterTest.KEY_STORE_PATH.toAbsolutePath().toString());
224 ssl.setKeyStorePassword("server-pwd");
225 ssl.setTrustStorePath(
226 HttpTransporterTest.TRUST_STORE_PATH.toAbsolutePath().toString());
227 ssl.setTrustStorePassword("client-pwd");
228 ssl.setSniRequired(false);
229 }
230
231 HttpConfiguration httpsConfig = new HttpConfiguration();
232 SecureRequestCustomizer customizer = new SecureRequestCustomizer();
233 customizer.setSniHostCheck(false);
234 httpsConfig.addCustomizer(customizer);
235
236 HttpConnectionFactory http1 = null;
237 if (needHttp11) {
238 http1 = new HttpConnectionFactory(httpsConfig);
239 }
240
241 HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpsConfig);
242
243 ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
244 alpn.setDefaultProtocol(http1 != null ? http1.getProtocol() : http2.getProtocol());
245
246 SslConnectionFactory tls = new SslConnectionFactory(ssl, alpn.getProtocol());
247 if (http1 != null) {
248 httpsConnector = new ServerConnector(server, tls, alpn, http2, http1);
249 } else {
250 httpsConnector = new ServerConnector(server, tls, alpn, http2);
251 }
252 server.addConnector(httpsConnector);
253 try {
254 httpsConnector.start();
255 } catch (Exception e) {
256 throw new IllegalStateException(e);
257 }
258 }
259 return this;
260 }
261
262 public List<LogEntry> getLogEntries() {
263 return logEntries;
264 }
265
266 public HttpServer setRepoDir(File repoDir) {
267 this.repoDir = repoDir;
268 return this;
269 }
270
271 public HttpServer setRangeSupport(boolean rangeSupport) {
272 this.rangeSupport = rangeSupport;
273 return this;
274 }
275
276 public HttpServer setWebDav(boolean webDav) {
277 this.webDav = webDav;
278 return this;
279 }
280
281 public HttpServer setExpectSupport(ExpectContinue expectContinue) {
282 this.expectContinue = expectContinue;
283 return this;
284 }
285
286 public HttpServer setChecksumHeader(ChecksumHeader checksumHeader) {
287 this.checksumHeader = checksumHeader;
288 return this;
289 }
290
291 public HttpServer setAuthentication(String username, String password) {
292 this.username = username;
293 this.password = password;
294 return this;
295 }
296
297 public HttpServer setProxyAuthentication(String username, String password) {
298 proxyUsername = username;
299 proxyPassword = password;
300 return this;
301 }
302
303 public HttpServer setConnectionsToClose(int connectionsToClose) {
304 this.connectionsToClose.set(connectionsToClose);
305 return this;
306 }
307
308 public HttpServer setServerErrorsBeforeWorks(int serverErrorsBeforeWorks) {
309 return setServerErrorsBeforeWorks(serverErrorsBeforeWorks, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
310 }
311
312 public HttpServer setServerErrorsBeforeWorks(int serverErrorsBeforeWorks, int errorStatusCode) {
313 this.serverErrorsBeforeWorks.set(serverErrorsBeforeWorks);
314 this.serverErrorStatusCode = errorStatusCode;
315 return this;
316 }
317
318 public HttpServer start() throws Exception {
319 if (server != null) {
320 return this;
321 }
322
323 server = new Server();
324 httpConnector = new ServerConnector(server);
325 server.addConnector(httpConnector);
326
327 server.setHandler(new LogHandler(new CompressionEnforcingHandler(new Handler.Sequence(
328 new ConnectionClosingHandler(),
329 new ServerErrorHandler(),
330 new ProxyAuthHandler(),
331 new AuthHandler(),
332 new RedirectHandler(),
333 new RepoHandler(),
334 new RFC9457Handler()))));
335 server.start();
336
337 return this;
338 }
339
340 public void stop() throws Exception {
341 if (server != null) {
342 server.stop();
343 server = null;
344 httpConnector = null;
345 httpsConnector = null;
346 }
347 }
348
349 private class CompressionEnforcingHandler extends CompressionHandler {
350
351 private final PathMappings<CompressionConfig> pathConfigs = new PathMappings<>();
352
353 CompressionEnforcingHandler(Handler handler) {
354 super(handler);
355 this.putConfiguration(
356 "/br/*",
357 CompressionConfig.builder().compressIncludeEncoding("br").build());
358 this.putConfiguration(
359 "/zstd/*",
360 CompressionConfig.builder().compressIncludeEncoding("zstd").build());
361 this.putConfiguration(
362 "/gzip/*",
363 CompressionConfig.builder().compressIncludeEncoding("gzip").build());
364 this.putConfiguration(
365 "/deflate/*",
366 CompressionConfig.builder()
367 .compressIncludeEncoding("deflate")
368 .build());
369 }
370
371 @Override
372 public CompressionConfig putConfiguration(PathSpec pathSpec, CompressionConfig config) {
373
374 return pathConfigs.put(pathSpec, config);
375 }
376
377 @Override
378 public boolean handle(Request request, Response response, Callback callback) throws Exception {
379 Handler next = getHandler();
380 if (next == null) {
381 return false;
382 }
383 String pathInContext = Request.getPathInContext(request);
384 MatchedResource<CompressionConfig> matchedConfig = this.pathConfigs.getMatched(pathInContext);
385 if (matchedConfig == null) {
386 if (LOGGER.isDebugEnabled()) {
387 LOGGER.debug("skipping compression: path {} has no matching compression config", pathInContext);
388 }
389
390 return next.handle(request, response, callback);
391 }
392
393
394
395 super.putConfiguration(PathSpec.from("/*"), matchedConfig.getResource());
396
397 return super.handle(new StripLeadingPathSegmentsRequestWrapper(request, 1), response, callback);
398 }
399 }
400
401 private static class StripLeadingPathSegmentsRequestWrapper extends Request.Wrapper {
402 private final HttpURI modifiedURI;
403
404 StripLeadingPathSegmentsRequestWrapper(Request wrapped, int segmentsToStrip) {
405 super(wrapped);
406 this.modifiedURI = stripPathSegments(wrapped.getHttpURI(), segmentsToStrip);
407 }
408
409 private static HttpURI stripPathSegments(HttpURI originalURI, int segmentsToStrip) {
410 if (segmentsToStrip <= 0) {
411 return originalURI;
412 }
413
414 String originalPath = originalURI.getPath();
415 if (originalPath == null || originalPath.isEmpty()) {
416 return originalURI;
417 }
418
419
420 String[] segments = originalPath.split("/");
421 StringBuilder newPath = new StringBuilder();
422
423
424 int skipCount = 0;
425 for (int i = 0; i < segments.length; i++) {
426 if (segments[i].isEmpty() && i == 0) {
427
428 continue;
429 }
430 if (skipCount < segmentsToStrip) {
431 skipCount++;
432 continue;
433 }
434 newPath.append("/").append(segments[i]);
435 }
436
437
438 if (newPath.isEmpty()) {
439 newPath.append("/");
440 }
441
442
443 return org.eclipse.jetty.http.HttpURI.build(originalURI)
444 .path(newPath.toString())
445 .asImmutable();
446 }
447
448 @Override
449 public HttpURI getHttpURI() {
450 return modifiedURI;
451 }
452 }
453
454 private class ConnectionClosingHandler extends Handler.Abstract {
455
456 @Override
457 public boolean handle(Request request, Response response, Callback callback) throws Exception {
458 if (connectionsToClose.getAndDecrement() > 0) {
459 request.getConnectionMetaData().getConnection().close();
460 }
461 return false;
462 }
463 }
464
465 private class ServerErrorHandler extends Handler.Abstract {
466 @Override
467 public boolean handle(Request request, Response response, Callback callback) throws IOException {
468 if (serverErrorsBeforeWorks.getAndDecrement() > 0) {
469 response.setStatus(serverErrorStatusCode);
470 writeResponseBodyMessage(request, response, "Oops, come back later!");
471 return true;
472 }
473 return false;
474 }
475 }
476
477 private class LogHandler extends Handler.Wrapper {
478
479 LogHandler(Handler handler) {
480 super(handler);
481 }
482
483 @Override
484 public boolean handle(Request req, Response response, Callback callback) throws Exception {
485
486 LOGGER.info(
487 "{} {}{}",
488 req.getMethod(),
489 req.getHttpURI().getDecodedPath(),
490 req.getHttpURI().getQuery() != null ? "?" + req.getHttpURI().getQuery() : "");
491
492 Map<String, String> requestHeaders =
493 toUnmodifiableMap(req.getHeaders());
494 LogEntry logEntry = new LogEntry(req.getMethod(), req.getHttpURI().getPathQuery(), requestHeaders);
495 logEntries.add(logEntry);
496
497 boolean result = super.handle(req, response, callback);
498
499
500 logEntry.setResponseHeaders(toUnmodifiableMap(response.getHeaders()));
501 if (result) {
502 callback.succeeded();
503 }
504 return result;
505 }
506
507 Map<String, String> toUnmodifiableMap(HttpFields headers) {
508 Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
509 for (HttpField header : headers) {
510 map.put(header.getName(), header.getValueList().stream().collect(Collectors.joining(", ")));
511 }
512 return Collections.unmodifiableMap(map);
513 }
514 }
515
516 private static final Pattern SIMPLE_RANGE = Pattern.compile("bytes=([0-9])+-");
517
518 private class RepoHandler extends Handler.Abstract {
519 @Override
520 public boolean handle(Request req, Response response, Callback callback) throws Exception {
521 String path = req.getHttpURI().getDecodedPath().substring(1);
522
523 if (!path.startsWith("repo/")) {
524 return false;
525 }
526
527 if (ExpectContinue.FAIL.equals(expectContinue) && req.getHeaders().get(HttpHeader.EXPECT) != null) {
528 response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
529 writeResponseBodyMessage(req, response, "Expectation was set to fail");
530 return true;
531 }
532
533 File file = new File(repoDir, path.substring(5));
534 if (HttpMethod.GET.is(req.getMethod()) || HttpMethod.HEAD.is(req.getMethod())) {
535 if (!file.isFile() || path.endsWith("/")) {
536 response.setStatus(HttpServletResponse.SC_NOT_FOUND);
537 writeResponseBodyMessage(req, response, "Not found");
538 return true;
539 }
540 long ifUnmodifiedSince = req.getHeaders().getDateField(HttpHeader.IF_UNMODIFIED_SINCE);
541 if (ifUnmodifiedSince != -1L && file.lastModified() > ifUnmodifiedSince) {
542 response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
543 writeResponseBodyMessage(req, response, "Precondition failed");
544 return true;
545 }
546 long offset = 0L;
547 String range = req.getHeaders().get(HttpHeader.RANGE);
548 if (range != null && rangeSupport) {
549 Matcher m = SIMPLE_RANGE.matcher(range);
550 if (m.matches()) {
551 offset = Long.parseLong(m.group(1));
552 if (offset >= file.length()) {
553 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
554 writeResponseBodyMessage(req, response, "Range not satisfiable");
555 return true;
556 }
557 }
558 String encoding = req.getHeaders().get(HttpHeader.ACCEPT_ENCODING);
559 if ((encoding != null && !"identity".equals(encoding)) || ifUnmodifiedSince == -1L) {
560 response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
561 return true;
562 }
563 }
564 response.setStatus((offset > 0L) ? HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK);
565 response.getHeaders().add(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(file.lastModified()));
566 response.getHeaders().add(HttpHeader.CONTENT_LENGTH, Long.toString(file.length() - offset));
567 if (offset > 0L) {
568 response.getHeaders()
569 .add(
570 HttpHeader.CONTENT_RANGE,
571 "bytes " + offset + "-" + (file.length() - 1L) + "/" + file.length());
572 }
573 if (checksumHeader != null) {
574 Map<String, String> checksums = ChecksumAlgorithmHelper.calculate(
575 file, Collections.singletonList(new Sha1ChecksumAlgorithmFactory()));
576 if (checksumHeader == ChecksumHeader.NEXUS) {
577 response.getHeaders().add(HttpHeader.ETAG.asString(), "{SHA1{" + checksums.get("SHA-1") + "}}");
578 } else if (checksumHeader == ChecksumHeader.XCHECKSUM) {
579 response.getHeaders().add("x-checksum-sha1", checksums.get(Sha1ChecksumAlgorithmFactory.NAME));
580 }
581 }
582 if (HttpMethod.HEAD.is(req.getMethod())) {
583 return true;
584 }
585 Content.Source contentSource =
586 Content.Source.from(new ByteBufferPool.Sized(null), file.toPath(), offset, -1);
587 try (Blocker.Callback fileReadCallback = Blocker.callback()) {
588 Content.copy(contentSource, response, fileReadCallback);
589 fileReadCallback.block();
590 }
591 } else if (HttpMethod.PUT.is(req.getMethod())) {
592 if (!webDav) {
593 file.getParentFile().mkdirs();
594 }
595 if (file.getParentFile().exists()) {
596 try (SeekableByteChannel channel = Files.newByteChannel(
597 file.toPath(),
598 StandardOpenOption.CREATE,
599 StandardOpenOption.WRITE,
600 StandardOpenOption.TRUNCATE_EXISTING);
601 Blocker.Callback fileWriteCallback = Blocker.callback()) {
602 Content.copy(req, Content.Sink.from(channel), fileWriteCallback);
603 fileWriteCallback.block();
604 } catch (IOException e) {
605 file.delete();
606 throw e;
607 }
608 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
609 } else {
610 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
611 }
612 } else if (HttpMethod.OPTIONS.is(req.getMethod())) {
613 if (webDav) {
614 response.getHeaders().add("DAV", "1,2");
615 }
616 response.getHeaders().add(HttpHeader.ALLOW, "GET, PUT, HEAD, OPTIONS");
617 response.setStatus(HttpServletResponse.SC_OK);
618 } else if (webDav && "MKCOL".equals(req.getMethod())) {
619 if (file.exists()) {
620 response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
621 } else if (file.mkdir()) {
622 response.setStatus(HttpServletResponse.SC_CREATED);
623 } else {
624 response.setStatus(HttpServletResponse.SC_CONFLICT);
625 }
626 } else {
627 response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
628 }
629 return true;
630 }
631 }
632
633 private void writeResponseBodyMessage(Request request, Response response, String message) throws IOException {
634
635 try (Blocker.Callback callback = Blocker.callback()) {
636 Content.Sink.write(response, false, message, callback);
637 callback.block();
638 }
639 }
640
641 private class RFC9457Handler extends Handler.Abstract {
642 @Override
643 public boolean handle(Request req, Response response, Callback callback) throws Exception {
644 String path = req.getHttpURI().getPath().substring(1);
645
646 if (!path.startsWith("rfc9457/")) {
647 return false;
648 }
649
650 if (HttpMethod.GET.is(req.getMethod())) {
651 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
652 response.getHeaders().add(HttpHeader.CONTENT_TYPE.asString(), "application/problem+json");
653 RFC9457Payload rfc9457Payload;
654 if (path.endsWith("missing_fields.txt")) {
655 rfc9457Payload = new RFC9457Payload(null, null, null, null, null);
656 } else {
657 rfc9457Payload = new RFC9457Payload(
658 URI.create("https://example.com/probs/out-of-credit"),
659 HttpServletResponse.SC_FORBIDDEN,
660 "You do not have enough credit.",
661 "Your current balance is 30, but that costs 50.",
662 URI.create("/account/12345/msgs/abc"));
663 }
664 writeResponseBodyMessage(req, response, buildRFC9457Message(rfc9457Payload));
665 }
666 return true;
667 }
668 }
669
670 private String buildRFC9457Message(RFC9457Payload payload) {
671 return new Gson().toJson(payload, RFC9457Payload.class);
672 }
673
674 private class RedirectHandler extends Handler.Abstract {
675 @Override
676 public boolean handle(Request req, Response response, Callback callback) throws Exception {
677 String path = req.getHttpURI().getPath();
678 if (!path.startsWith("/redirect/")) {
679 return false;
680 }
681 StringBuilder location = new StringBuilder(128);
682 String scheme = Request.getParameters(req).getValue("scheme");
683 location.append(scheme != null ? scheme : req.getHttpURI().getScheme());
684 location.append("://");
685 location.append(Request.getServerName(req));
686 location.append(":");
687 if ("http".equalsIgnoreCase(scheme)) {
688 location.append(getHttpPort());
689 } else if ("https".equalsIgnoreCase(scheme)) {
690 location.append(getHttpsPort());
691 } else {
692 location.append(Request.getServerPort(req));
693 }
694 location.append("/repo").append(path.substring(9));
695 Response.sendRedirect(
696 req, response, callback, HttpServletResponse.SC_MOVED_PERMANENTLY, location.toString(), false);
697 return true;
698 }
699 }
700
701 private class AuthHandler extends Handler.Abstract {
702 @Override
703 public boolean handle(Request request, Response response, Callback callback) throws Exception {
704 if (ExpectContinue.BROKEN.equals(expectContinue)
705 && "100-continue".equalsIgnoreCase(request.getHeaders().get(HttpHeader.EXPECT))) {
706
707 Request.asInputStream(request);
708 }
709
710 if (username != null && password != null) {
711 if (checkBasicAuth(request.getHeaders().get(HttpHeader.AUTHORIZATION), username, password)) {
712 return false;
713 }
714 response.getHeaders().add(HttpHeader.WWW_AUTHENTICATE, "Basic realm=\"Test-Realm\"");
715 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
716 return true;
717 }
718 return false;
719 }
720 }
721
722 private class ProxyAuthHandler extends Handler.Abstract {
723 @Override
724 public boolean handle(Request req, Response response, Callback callback) throws Exception {
725 if (proxyUsername != null && proxyPassword != null) {
726 if (checkBasicAuth(
727 req.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION), proxyUsername, proxyPassword)) {
728 return false;
729 }
730 response.getHeaders().add(HttpHeader.PROXY_AUTHENTICATE, "basic realm=\"Test-Realm\"");
731 response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
732 return true;
733 } else {
734 return false;
735 }
736 }
737 }
738
739 static boolean checkBasicAuth(String credentials, String username, String password) {
740 if (credentials != null) {
741 int space = credentials.indexOf(' ');
742 if (space > 0) {
743 String method = credentials.substring(0, space);
744 if ("basic".equalsIgnoreCase(method)) {
745 credentials = credentials.substring(space + 1);
746 credentials = new String(Base64.getDecoder().decode(credentials), StandardCharsets.ISO_8859_1);
747 int i = credentials.indexOf(':');
748 if (i > 0) {
749 String user = credentials.substring(0, i);
750 String pass = credentials.substring(i + 1);
751 if (username.equals(user) && password.equals(pass)) {
752 return true;
753 }
754 }
755 }
756 }
757 }
758 return false;
759 }
760 }