2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.amazonechocontrol.internal;
15 import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
17 import java.io.IOException;
18 import java.net.URISyntaxException;
19 import java.net.URLDecoder;
20 import java.net.URLEncoder;
21 import java.nio.charset.StandardCharsets;
22 import java.util.HashMap;
23 import java.util.Hashtable;
24 import java.util.List;
26 import java.util.stream.Collectors;
28 import javax.net.ssl.HttpsURLConnection;
29 import javax.servlet.ServletException;
30 import javax.servlet.http.HttpServlet;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
37 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates;
38 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState;
39 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice;
40 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
41 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider;
42 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound;
43 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists;
44 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList;
45 import org.openhab.core.thing.Thing;
46 import org.osgi.service.http.HttpService;
47 import org.osgi.service.http.NamespaceException;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.unbescape.html.HtmlEscape;
52 import com.google.gson.Gson;
53 import com.google.gson.JsonSyntaxException;
56 * Provides the following functions
58 * Simple http proxy to forward the login dialog from amazon to the user through the binding
59 * so the user can enter a captcha or other extended login information
60 * --- List of devices ---
61 * Used to get the device information of new devices which are currently not known
63 * Simple possibility for a user to get the ids needed for writing rules
65 * @author Michael Geramb - Initial Contribution
68 public class AccountServlet extends HttpServlet {
70 private static final long serialVersionUID = -1453738923337413163L;
71 private static final String FORWARD_URI_PART = "/FORWARD/";
72 private static final String PROXY_URI_PART = "/PROXY/";
74 private final Logger logger = LoggerFactory.getLogger(AccountServlet.class);
76 private final HttpService httpService;
77 private final String servletUrlWithoutRoot;
78 private final String servletUrl;
79 private final AccountHandler account;
80 private final String id;
81 private @Nullable Connection connectionToInitialize;
82 private final Gson gson;
84 public AccountServlet(HttpService httpService, String id, AccountHandler account, Gson gson) {
85 this.httpService = httpService;
86 this.account = account;
91 servletUrlWithoutRoot = "amazonechocontrol/" + URLEncoder.encode(id, StandardCharsets.UTF_8);
92 servletUrl = "/" + servletUrlWithoutRoot;
94 Hashtable<Object, Object> initParams = new Hashtable<>();
95 initParams.put("servlet-name", servletUrl);
97 httpService.registerServlet(servletUrl, this, initParams, httpService.createDefaultHttpContext());
98 } catch (NamespaceException | ServletException e) {
99 throw new IllegalStateException(e.getMessage());
103 private Connection reCreateConnection() {
104 Connection oldConnection = connectionToInitialize;
105 if (oldConnection == null) {
106 oldConnection = account.findConnection();
108 return new Connection(oldConnection, this.gson);
111 public void dispose() {
112 httpService.unregister(servletUrl);
116 protected void doPut(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
117 throws ServletException, IOException {
118 doVerb("PUT", req, resp);
122 protected void doDelete(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
123 throws ServletException, IOException {
124 doVerb("DELETE", req, resp);
128 protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
129 throws ServletException, IOException {
130 doVerb("POST", req, resp);
133 void doVerb(String verb, @Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
140 String requestUri = req.getRequestURI();
141 if (requestUri == null) {
144 String baseUrl = requestUri.substring(servletUrl.length());
145 String uri = baseUrl;
146 String queryString = req.getQueryString();
147 if (queryString != null && queryString.length() > 0) {
148 uri += "?" + queryString;
151 Connection connection = this.account.findConnection();
152 if (connection != null && "/changedomain".equals(uri)) {
153 Map<String, String[]> map = req.getParameterMap();
154 String[] domainArray = map.get("domain");
155 if (domainArray == null) {
156 logger.warn("Could not determine domain");
159 String domain = domainArray[0];
160 String loginData = connection.serializeLoginData();
161 Connection newConnection = new Connection(null, this.gson);
162 if (newConnection.tryRestoreLogin(loginData, domain)) {
163 account.setConnection(newConnection);
165 resp.sendRedirect(servletUrl);
168 if (uri.startsWith(PROXY_URI_PART)) {
169 // handle proxy request
171 if (connection == null) {
172 returnError(resp, "Account not online");
175 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
176 + uri.substring(PROXY_URI_PART.length());
178 String postData = null;
179 if ("POST".equals(verb) || "PUT".equals(verb)) {
180 postData = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
183 this.handleProxyRequest(connection, resp, verb, getUrl, null, postData, true, connection.getAmazonSite());
187 // handle post of login page
188 connection = this.connectionToInitialize;
189 if (connection == null) {
190 returnError(resp, "Connection not in initialize mode.");
194 resp.addHeader("content-type", "text/html;charset=UTF-8");
196 Map<String, String[]> map = req.getParameterMap();
197 StringBuilder postDataBuilder = new StringBuilder();
198 for (String name : map.keySet()) {
199 if (postDataBuilder.length() > 0) {
200 postDataBuilder.append('&');
203 postDataBuilder.append(name);
204 postDataBuilder.append('=');
206 if ("failedSignInCount".equals(name)) {
209 String[] strings = map.get(name);
210 if (strings != null && strings.length > 0 && strings[0] != null) {
214 postDataBuilder.append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
217 uri = req.getRequestURI();
218 if (uri == null || !uri.startsWith(servletUrl)) {
219 returnError(resp, "Invalid request uri '" + uri + "'");
222 String relativeUrl = uri.substring(servletUrl.length()).replace(FORWARD_URI_PART, "/");
224 String site = connection.getAmazonSite();
225 if (relativeUrl.startsWith("/ap/signin")) {
228 String postUrl = "https://www." + site + relativeUrl;
229 queryString = req.getQueryString();
230 if (queryString != null && queryString.length() > 0) {
231 postUrl += "?" + queryString;
233 String referer = "https://www." + site;
234 String postData = postDataBuilder.toString();
235 handleProxyRequest(connection, resp, "POST", postUrl, referer, postData, false, site);
239 protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
246 String requestUri = req.getRequestURI();
247 if (requestUri == null) {
250 String baseUrl = requestUri.substring(servletUrl.length());
251 String uri = baseUrl;
252 String queryString = req.getQueryString();
253 if (queryString != null && queryString.length() > 0) {
254 uri += "?" + queryString;
256 logger.debug("doGet {}", uri);
258 Connection connection = this.connectionToInitialize;
259 if (uri.startsWith(FORWARD_URI_PART) && connection != null) {
260 String getUrl = "https://www." + connection.getAmazonSite() + "/"
261 + uri.substring(FORWARD_URI_PART.length());
263 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
267 connection = this.account.findConnection();
268 if (uri.startsWith(PROXY_URI_PART)) {
269 // handle proxy request
271 if (connection == null) {
272 returnError(resp, "Account not online");
275 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
276 + uri.substring(PROXY_URI_PART.length());
278 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
282 if (connection != null && connection.verifyLogin()) {
284 if ("/logout".equals(baseUrl) || "/logout/".equals(baseUrl)) {
285 this.connectionToInitialize = reCreateConnection();
286 this.account.setConnection(null);
287 resp.sendRedirect(this.servletUrl);
291 if ("/newdevice".equals(baseUrl) || "/newdevice/".equals(baseUrl)) {
292 this.connectionToInitialize = new Connection(null, this.gson);
293 this.account.setConnection(null);
294 resp.sendRedirect(this.servletUrl);
298 if ("/devices".equals(baseUrl) || "/devices/".equals(baseUrl)) {
299 handleDevices(resp, connection);
302 if ("/changeDomain".equals(baseUrl) || "/changeDomain/".equals(baseUrl)) {
303 handleChangeDomain(resp, connection);
306 if ("/ids".equals(baseUrl) || "/ids/".equals(baseUrl)) {
307 String serialNumber = getQueryMap(queryString).get("serialNumber");
308 Device device = account.findDeviceJson(serialNumber);
309 if (device != null) {
310 Thing thing = account.findThingBySerialNumber(device.serialNumber);
311 handleIds(resp, connection, device, thing);
315 // return hint that everything is ok
316 handleDefaultPageResult(resp, "The Account is logged in.", connection);
319 connection = this.connectionToInitialize;
320 if (connection == null) {
321 connection = this.reCreateConnection();
322 this.connectionToInitialize = connection;
325 if (!"/".equals(uri)) {
326 String newUri = req.getServletPath() + "/";
327 resp.sendRedirect(newUri);
331 String html = connection.getLoginPage();
332 returnHtml(connection, resp, html, "amazon.com");
333 } catch (URISyntaxException | InterruptedException e) {
334 logger.warn("get failed with uri syntax error", e);
338 public Map<String, String> getQueryMap(@Nullable String query) {
339 Map<String, String> map = new HashMap<>();
341 String[] params = query.split("&");
342 for (String param : params) {
343 String[] elements = param.split("=");
344 if (elements.length == 2) {
345 String name = elements[0];
346 String value = URLDecoder.decode(elements[1], StandardCharsets.UTF_8);
347 map.put(name, value);
354 private void handleChangeDomain(HttpServletResponse resp, Connection connection) {
355 StringBuilder html = createPageStart("Change Domain");
356 html.append("<form action='");
357 html.append(servletUrl);
358 html.append("/changedomain' method='post'>\nDomain:\n<input type='text' name='domain' value='");
359 html.append(connection.getAmazonSite());
360 html.append("'>\n<br>\n<input type=\"submit\" value=\"Submit\">\n</form>");
362 createPageEndAndSent(resp, html);
365 private void handleDefaultPageResult(HttpServletResponse resp, String message, Connection connection)
367 StringBuilder html = createPageStart("");
368 html.append(HtmlEscape.escapeHtml4(message));
370 html.append(" <a href='" + servletUrl + "/logout' >");
371 html.append(HtmlEscape.escapeHtml4("Logout"));
374 html.append(" | <a href='" + servletUrl + "/newdevice' >");
375 html.append(HtmlEscape.escapeHtml4("Logout and create new device id"));
378 html.append("<br>Customer Id: ");
379 html.append(HtmlEscape.escapeHtml4(connection.getCustomerId()));
381 html.append("<br>Customer Name: ");
382 html.append(HtmlEscape.escapeHtml4(connection.getCustomerName()));
384 html.append("<br>App name: ");
385 html.append(HtmlEscape.escapeHtml4(connection.getDeviceName()));
387 html.append("<br>Connected to: ");
388 html.append(HtmlEscape.escapeHtml4(connection.getAlexaServer()));
390 html.append(" <a href='");
391 html.append(servletUrl);
392 html.append("/changeDomain'>Change</a>");
395 html.append("<br><a href='/#!/settings/things/" + BINDING_ID + ":"
396 + URLEncoder.encode(THING_TYPE_ACCOUNT.getId(), "UTF8") + ":" + URLEncoder.encode(id, "UTF8") + "'>");
397 html.append(HtmlEscape.escapeHtml4("Check Thing in Main UI"));
398 html.append("</a><br><br>");
402 "<table><tr><th align='left'>Device</th><th align='left'>Serial Number</th><th align='left'>State</th><th align='left'>Thing</th><th align='left'>Family</th><th align='left'>Type</th><th align='left'>Customer Id</th></tr>");
403 for (Device device : this.account.getLastKnownDevices()) {
405 html.append("<tr><td>");
406 html.append(HtmlEscape.escapeHtml4(nullReplacement(device.accountName)));
407 html.append("</td><td>");
408 html.append(HtmlEscape.escapeHtml4(nullReplacement(device.serialNumber)));
409 html.append("</td><td>");
410 html.append(HtmlEscape.escapeHtml4(device.online ? "Online" : "Offline"));
411 html.append("</td><td>");
412 Thing accountHandler = account.findThingBySerialNumber(device.serialNumber);
413 if (accountHandler != null) {
414 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
415 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
416 + HtmlEscape.escapeHtml4(accountHandler.getLabel()) + "</a>");
418 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
419 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>" + HtmlEscape.escapeHtml4("Not defined")
422 html.append("</td><td>");
423 html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceFamily)));
424 html.append("</td><td>");
425 html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceType)));
426 html.append("</td><td>");
427 html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceOwnerCustomerId)));
428 html.append("</td>");
429 html.append("</tr>");
431 html.append("</table>");
432 createPageEndAndSent(resp, html);
435 private void handleDevices(HttpServletResponse resp, Connection connection)
436 throws IOException, URISyntaxException, InterruptedException {
437 returnHtml(connection, resp, "<html>" + HtmlEscape.escapeHtml4(connection.getDeviceListJson()) + "</html>");
440 private String nullReplacement(@Nullable String text) {
447 StringBuilder createPageStart(String title) {
448 StringBuilder html = new StringBuilder();
449 html.append("<html><head><title>"
450 + HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel()));
451 if (!title.isEmpty()) {
453 html.append(HtmlEscape.escapeHtml4(title));
455 html.append("</title><head><body>");
456 html.append("<h1>" + HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel()));
457 if (!title.isEmpty()) {
459 html.append(HtmlEscape.escapeHtml4(title));
461 html.append("</h1>");
465 private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) {
466 // account overview link
467 html.append("<br><a href='" + servletUrl + "/../' >");
468 html.append(HtmlEscape.escapeHtml4("Account overview"));
469 html.append("</a><br>");
471 html.append("</body></html>");
472 resp.addHeader("content-type", "text/html;charset=UTF-8");
474 resp.getWriter().write(html.toString());
475 } catch (IOException e) {
476 logger.warn("return html failed with IO error", e);
480 private void handleIds(HttpServletResponse resp, Connection connection, Device device, @Nullable Thing thing)
481 throws IOException, URISyntaxException {
484 html = createPageStart("Channel Options - " + thing.getLabel());
486 html = createPageStart("Device Information - No thing defined");
488 renderBluetoothMacChannel(connection, device, html);
489 renderAmazonMusicPlaylistIdChannel(connection, device, html);
490 renderPlayAlarmSoundChannel(connection, device, html);
491 renderMusicProviderIdChannel(connection, html);
492 renderCapabilities(connection, device, html);
493 createPageEndAndSent(resp, html);
496 private void renderCapabilities(Connection connection, Device device, StringBuilder html) {
497 html.append("<h2>Capabilities</h2>");
498 html.append("<table><tr><th align='left'>Name</th></tr>");
499 device.getCapabilities().forEach(
500 capability -> html.append("<tr><td>").append(HtmlEscape.escapeHtml4(capability)).append("</td></tr>"));
501 html.append("</table>");
504 private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) {
505 html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_MUSIC_PROVIDER_ID)).append("</h2>");
506 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
507 List<JsonMusicProvider> musicProviders = connection.getMusicProviders();
508 for (JsonMusicProvider musicProvider : musicProviders) {
509 List<String> properties = musicProvider.supportedProperties;
510 String providerId = musicProvider.id;
511 String displayName = musicProvider.displayName;
512 if (properties != null && properties.contains("Alexa.Music.PlaySearchPhrase") && providerId != null
513 && !providerId.isEmpty() && "AVAILABLE".equals(musicProvider.availability) && displayName != null
514 && !displayName.isEmpty()) {
515 html.append("<tr><td>");
516 html.append(HtmlEscape.escapeHtml4(displayName));
517 html.append("</td><td>");
518 html.append(HtmlEscape.escapeHtml4(providerId));
519 html.append("</td></tr>");
522 html.append("</table>");
525 private void renderPlayAlarmSoundChannel(Connection connection, Device device, StringBuilder html) {
526 html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_PLAY_ALARM_SOUND)).append("</h2>");
527 List<JsonNotificationSound> notificationSounds = List.of();
528 String errorMessage = "No notifications sounds found";
530 notificationSounds = connection.getNotificationSounds(device);
531 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
532 | InterruptedException e) {
533 errorMessage = e.getLocalizedMessage();
535 if (!notificationSounds.isEmpty()) {
536 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
537 for (JsonNotificationSound notificationSound : notificationSounds) {
538 if (notificationSound.folder == null && notificationSound.providerId != null
539 && notificationSound.id != null && notificationSound.displayName != null) {
540 String providerSoundId = notificationSound.providerId + ":" + notificationSound.id;
542 html.append("<tr><td>");
543 html.append(HtmlEscape.escapeHtml4(notificationSound.displayName));
544 html.append("</td><td>");
545 html.append(HtmlEscape.escapeHtml4(providerSoundId));
546 html.append("</td></tr>");
549 html.append("</table>");
551 html.append(HtmlEscape.escapeHtml4(errorMessage));
555 private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device device, StringBuilder html) {
556 html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID))
559 JsonPlaylists playLists = null;
560 String errorMessage = "No playlists found";
562 playLists = connection.getPlaylists(device);
563 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
564 | InterruptedException e) {
565 errorMessage = e.getLocalizedMessage();
568 if (playLists != null) {
569 Map<String, PlayList @Nullable []> playlistMap = playLists.playlists;
570 if (playlistMap != null && !playlistMap.isEmpty()) {
571 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
573 for (PlayList[] innerLists : playlistMap.values()) {
575 if (innerLists != null && innerLists.length > 0) {
576 PlayList playList = innerLists[0];
577 if (playList != null && playList.playlistId != null && playList.title != null) {
578 html.append("<tr><td>");
579 html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.title)));
580 html.append("</td><td>");
581 html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.playlistId)));
582 html.append("</td></tr>");
587 html.append("</table>");
589 html.append(HtmlEscape.escapeHtml4(errorMessage));
594 private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) {
595 html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_BLUETOOTH_MAC)).append("</h2>");
596 JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates();
597 if (bluetoothStates == null) {
600 BluetoothState[] innerStates = bluetoothStates.bluetoothStates;
601 if (innerStates == null) {
604 for (BluetoothState state : innerStates) {
608 String stateDeviceSerialNumber = state.deviceSerialNumber;
609 if ((stateDeviceSerialNumber == null && device.serialNumber == null)
610 || (stateDeviceSerialNumber != null && stateDeviceSerialNumber.equals(device.serialNumber))) {
611 List<PairedDevice> pairedDeviceList = state.getPairedDeviceList();
612 if (!pairedDeviceList.isEmpty()) {
613 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
614 for (PairedDevice pairedDevice : pairedDeviceList) {
615 html.append("<tr><td>");
616 html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.friendlyName)));
617 html.append("</td><td>");
618 html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.address)));
619 html.append("</td></tr>");
621 html.append("</table>");
623 html.append(HtmlEscape.escapeHtml4("No bluetooth devices paired"));
629 void handleProxyRequest(Connection connection, HttpServletResponse resp, String verb, String url,
630 @Nullable String referer, @Nullable String postData, boolean json, String site) throws IOException {
631 HttpsURLConnection urlConnection;
633 Map<String, String> headers = null;
634 if (referer != null) {
635 headers = new HashMap<>();
636 headers.put("Referer", referer);
639 urlConnection = connection.makeRequest(verb, url, postData, json, false, headers, 0);
640 if (urlConnection.getResponseCode() == 302) {
642 String location = urlConnection.getHeaderField("location");
643 if (location.contains("/ap/maplanding")) {
645 connection.registerConnectionAsApp(location);
646 account.setConnection(connection);
647 handleDefaultPageResult(resp, "Login succeeded", connection);
648 this.connectionToInitialize = null;
650 } catch (URISyntaxException | ConnectionException e) {
652 "Login to '" + connection.getAmazonSite() + "' failed: " + e.getLocalizedMessage());
653 this.connectionToInitialize = null;
658 String startString = "https://www." + connection.getAmazonSite() + "/";
659 String newLocation = null;
660 if (location.startsWith(startString) && connection.getIsLoggedIn()) {
661 newLocation = servletUrl + PROXY_URI_PART + location.substring(startString.length());
662 } else if (location.startsWith(startString)) {
663 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
666 if (location.startsWith(startString)) {
667 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
670 if (newLocation != null) {
671 logger.debug("Redirect mapped from {} to {}", location, newLocation);
673 resp.sendRedirect(newLocation);
676 returnError(resp, "Invalid redirect to '" + location + "'");
680 } catch (URISyntaxException | ConnectionException | InterruptedException e) {
681 returnError(resp, e.getLocalizedMessage());
684 String response = connection.convertStream(urlConnection);
685 returnHtml(connection, resp, response, site);
688 private void returnHtml(Connection connection, HttpServletResponse resp, String html) {
689 returnHtml(connection, resp, html, connection.getAmazonSite());
692 private void returnHtml(Connection connection, HttpServletResponse resp, String html, String amazonSite) {
693 String resultHtml = html.replace("action=\"/", "action=\"" + servletUrl + "/")
694 .replace("action=\"/", "action=\"" + servletUrl + "/")
695 .replace("https://www." + amazonSite + "/", servletUrl + "/")
696 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
697 .replace("https://www." + amazonSite + "/", servletUrl + "/")
698 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
699 .replace("http://www." + amazonSite + "/", servletUrl + "/")
700 .replace("http://www." + amazonSite + "/", servletUrl + "/");
702 resp.addHeader("content-type", "text/html;charset=UTF-8");
704 resp.getWriter().write(resultHtml);
705 } catch (IOException e) {
706 logger.warn("return html failed with IO error", e);
710 void returnError(HttpServletResponse resp, @Nullable String errorMessage) {
712 String message = errorMessage != null ? errorMessage : "null";
713 resp.getWriter().write("<html>" + HtmlEscape.escapeHtml4(message) + "<br><a href='" + servletUrl
714 + "'>Try again</a></html>");
715 } catch (IOException e) {
716 logger.info("Returning error message failed", e);