2 * Copyright (c) 2010-2020 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.io.UnsupportedEncodingException;
19 import java.net.URISyntaxException;
20 import java.net.URLDecoder;
21 import java.net.URLEncoder;
22 import java.nio.charset.StandardCharsets;
23 import java.util.HashMap;
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.apache.commons.lang.StringEscapeUtils;
35 import org.apache.commons.lang.StringUtils;
36 import org.eclipse.jdt.annotation.NonNullByDefault;
37 import org.eclipse.jdt.annotation.Nullable;
38 import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
39 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates;
40 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState;
41 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice;
42 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
43 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider;
44 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound;
45 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists;
46 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList;
47 import org.openhab.core.thing.Thing;
48 import org.osgi.service.http.HttpService;
49 import org.osgi.service.http.NamespaceException;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
53 import com.google.gson.Gson;
54 import com.google.gson.JsonSyntaxException;
57 * Provides the following functions
59 * Simple http proxy to forward the login dialog from amazon to the user through the binding
60 * so the user can enter a captcha or other extended login information
61 * --- List of devices ---
62 * Used to get the device information of new devices which are currently not known
64 * Simple possibility for a user to get the ids needed for writing rules
66 * @author Michael Geramb - Initial Contribution
69 public class AccountServlet extends HttpServlet {
71 private static final long serialVersionUID = -1453738923337413163L;
72 private static final String FORWARD_URI_PART = "/FORWARD/";
73 private static final String PROXY_URI_PART = "/PROXY/";
75 private final Logger logger = LoggerFactory.getLogger(AccountServlet.class);
77 private final HttpService httpService;
78 private final String servletUrlWithoutRoot;
79 private final String servletUrl;
80 private final AccountHandler account;
81 private final String id;
82 private @Nullable Connection connectionToInitialize;
83 private final Gson gson;
85 public AccountServlet(HttpService httpService, String id, AccountHandler account, Gson gson) {
86 this.httpService = httpService;
87 this.account = account;
92 servletUrlWithoutRoot = "amazonechocontrol/" + URLEncoder.encode(id, "UTF8");
93 servletUrl = "/" + servletUrlWithoutRoot;
95 httpService.registerServlet(servletUrl, this, null, httpService.createDefaultHttpContext());
96 } catch (UnsupportedEncodingException | NamespaceException | ServletException e) {
97 throw new IllegalStateException(e.getMessage());
101 private Connection reCreateConnection() {
102 Connection oldConnection = connectionToInitialize;
103 if (oldConnection == null) {
104 oldConnection = account.findConnection();
106 return new Connection(oldConnection, this.gson);
109 public void dispose() {
110 httpService.unregister(servletUrl);
114 protected void doPut(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
115 throws ServletException, IOException {
116 doVerb("PUT", req, resp);
120 protected void doDelete(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
121 throws ServletException, IOException {
122 doVerb("DELETE", req, resp);
126 protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
127 throws ServletException, IOException {
128 doVerb("POST", req, resp);
131 void doVerb(String verb, @Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
132 throws ServletException, IOException {
139 String baseUrl = req.getRequestURI().substring(servletUrl.length());
140 String uri = baseUrl;
141 String queryString = req.getQueryString();
142 if (queryString != null && queryString.length() > 0) {
143 uri += "?" + queryString;
146 Connection connection = this.account.findConnection();
147 if (connection != null && uri.equals("/changedomain")) {
148 Map<String, String[]> map = req.getParameterMap();
149 String domain = map.get("domain")[0];
150 String loginData = connection.serializeLoginData();
151 Connection newConnection = new Connection(null, this.gson);
152 if (newConnection.tryRestoreLogin(loginData, domain)) {
153 account.setConnection(newConnection);
155 resp.sendRedirect(servletUrl);
158 if (uri.startsWith(PROXY_URI_PART)) {
159 // handle proxy request
161 if (connection == null) {
162 returnError(resp, "Account not online");
165 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
166 + uri.substring(PROXY_URI_PART.length());
168 String postData = null;
169 if (verb == "POST" || verb == "PUT") {
170 postData = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
173 this.handleProxyRequest(connection, resp, verb, getUrl, null, postData, true, connection.getAmazonSite());
177 // handle post of login page
178 connection = this.connectionToInitialize;
179 if (connection == null) {
180 returnError(resp, "Connection not in initialize mode.");
184 resp.addHeader("content-type", "text/html;charset=UTF-8");
186 Map<String, String[]> map = req.getParameterMap();
187 StringBuilder postDataBuilder = new StringBuilder();
188 for (String name : map.keySet()) {
189 if (postDataBuilder.length() > 0) {
190 postDataBuilder.append('&');
193 postDataBuilder.append(name);
194 postDataBuilder.append('=');
195 String value = map.get(name)[0];
196 if (name.equals("failedSignInCount")) {
199 postDataBuilder.append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
202 uri = req.getRequestURI();
203 if (!uri.startsWith(servletUrl)) {
204 returnError(resp, "Invalid request uri '" + uri + "'");
207 String relativeUrl = uri.substring(servletUrl.length()).replace(FORWARD_URI_PART, "/");
209 String site = connection.getAmazonSite();
210 if (relativeUrl.startsWith("/ap/signin")) {
213 String postUrl = "https://www." + site + relativeUrl;
214 queryString = req.getQueryString();
215 if (queryString != null && queryString.length() > 0) {
216 postUrl += "?" + queryString;
218 String referer = "https://www." + site;
219 String postData = postDataBuilder.toString();
220 handleProxyRequest(connection, resp, "POST", postUrl, referer, postData, false, site);
224 protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
225 throws ServletException, IOException {
232 String baseUrl = req.getRequestURI().substring(servletUrl.length());
233 String uri = baseUrl;
234 String queryString = req.getQueryString();
235 if (queryString != null && queryString.length() > 0) {
236 uri += "?" + queryString;
238 logger.debug("doGet {}", uri);
240 Connection connection = this.connectionToInitialize;
241 if (uri.startsWith(FORWARD_URI_PART) && connection != null) {
242 String getUrl = "https://www." + connection.getAmazonSite() + "/"
243 + uri.substring(FORWARD_URI_PART.length());
245 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
249 connection = this.account.findConnection();
250 if (uri.startsWith(PROXY_URI_PART)) {
251 // handle proxy request
253 if (connection == null) {
254 returnError(resp, "Account not online");
257 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
258 + uri.substring(PROXY_URI_PART.length());
260 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
264 if (connection != null && connection.verifyLogin()) {
266 if (baseUrl.equals("/logout") || baseUrl.equals("/logout/")) {
267 this.connectionToInitialize = reCreateConnection();
268 this.account.setConnection(null);
269 resp.sendRedirect(this.servletUrl);
273 if (baseUrl.equals("/newdevice") || baseUrl.equals("/newdevice/")) {
274 this.connectionToInitialize = new Connection(null, this.gson);
275 this.account.setConnection(null);
276 resp.sendRedirect(this.servletUrl);
280 if (baseUrl.equals("/devices") || baseUrl.equals("/devices/")) {
281 handleDevices(resp, connection);
284 if (baseUrl.equals("/changeDomain") || baseUrl.equals("/changeDomain/")) {
285 handleChangeDomain(resp, connection);
288 if (baseUrl.equals("/ids") || baseUrl.equals("/ids/")) {
289 String serialNumber = getQueryMap(queryString).get("serialNumber");
290 Device device = account.findDeviceJson(serialNumber);
291 if (device != null) {
292 Thing thing = account.findThingBySerialNumber(device.serialNumber);
293 handleIds(resp, connection, device, thing);
297 // return hint that everything is ok
298 handleDefaultPageResult(resp, "The Account is logged in.", connection);
301 connection = this.connectionToInitialize;
302 if (connection == null) {
303 connection = this.reCreateConnection();
304 this.connectionToInitialize = connection;
307 if (!uri.equals("/")) {
308 String newUri = req.getServletPath() + "/";
309 resp.sendRedirect(newUri);
313 String html = connection.getLoginPage();
314 returnHtml(connection, resp, html, "amazon.com");
315 } catch (URISyntaxException e) {
316 logger.warn("get failed with uri syntax error", e);
320 public Map<String, String> getQueryMap(@Nullable String query) {
321 Map<String, String> map = new HashMap<>();
323 String[] params = query.split("&");
324 for (String param : params) {
325 String[] elements = param.split("=");
326 if (elements.length == 2) {
327 String name = elements[0];
330 value = URLDecoder.decode(elements[1], "UTF8");
331 } catch (UnsupportedEncodingException e) {
332 logger.info("Unsupported encoding", e);
334 map.put(name, value);
341 private void handleChangeDomain(HttpServletResponse resp, Connection connection) {
342 StringBuilder html = createPageStart("Change Domain");
343 html.append("<form action='");
344 html.append(servletUrl);
345 html.append("/changedomain' method='post'>\nDomain:\n<input type='text' name='domain' value='");
346 html.append(connection.getAmazonSite());
347 html.append("'>\n<br>\n<input type=\"submit\" value=\"Submit\">\n</form>");
349 createPageEndAndSent(resp, html);
352 private void handleDefaultPageResult(HttpServletResponse resp, String message, Connection connection)
354 StringBuilder html = createPageStart("");
355 html.append(StringEscapeUtils.escapeHtml(message));
357 html.append(" <a href='" + servletUrl + "/logout' >");
358 html.append(StringEscapeUtils.escapeHtml("Logout"));
361 html.append(" | <a href='" + servletUrl + "/newdevice' >");
362 html.append(StringEscapeUtils.escapeHtml("Logout and create new device id"));
365 html.append("<br>Customer Id: ");
366 html.append(StringEscapeUtils.escapeHtml(connection.getCustomerId()));
368 html.append("<br>Customer Name: ");
369 html.append(StringEscapeUtils.escapeHtml(connection.getCustomerName()));
371 html.append("<br>App name: ");
372 html.append(StringEscapeUtils.escapeHtml(connection.getDeviceName()));
374 html.append("<br>Connected to: ");
375 html.append(StringEscapeUtils.escapeHtml(connection.getAlexaServer()));
377 html.append(" <a href='");
378 html.append(servletUrl);
379 html.append("/changeDomain'>Change</a>");
382 html.append("<br><a href='/paperui/index.html#/configuration/things/view/" + BINDING_ID + ":"
383 + URLEncoder.encode(THING_TYPE_ACCOUNT.getId(), "UTF8") + ":" + URLEncoder.encode(id, "UTF8") + "'>");
384 html.append(StringEscapeUtils.escapeHtml("Check Thing in Paper UI"));
385 html.append("</a><br><br>");
389 "<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>");
390 for (Device device : this.account.getLastKnownDevices()) {
392 html.append("<tr><td>");
393 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.accountName)));
394 html.append("</td><td>");
395 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.serialNumber)));
396 html.append("</td><td>");
397 html.append(StringEscapeUtils.escapeHtml(device.online ? "Online" : "Offline"));
398 html.append("</td><td>");
399 Thing accountHandler = account.findThingBySerialNumber(device.serialNumber);
400 if (accountHandler != null) {
401 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
402 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
403 + StringEscapeUtils.escapeHtml(accountHandler.getLabel()) + "</a>");
405 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
406 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
407 + StringEscapeUtils.escapeHtml("Not defined") + "</a>");
409 html.append("</td><td>");
410 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceFamily)));
411 html.append("</td><td>");
412 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceType)));
413 html.append("</td><td>");
414 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceOwnerCustomerId)));
415 html.append("</td>");
416 html.append("</tr>");
418 html.append("</table>");
419 createPageEndAndSent(resp, html);
422 private void handleDevices(HttpServletResponse resp, Connection connection) throws IOException, URISyntaxException {
423 returnHtml(connection, resp,
424 "<html>" + StringEscapeUtils.escapeHtml(connection.getDeviceListJson()) + "</html>");
427 private String nullReplacement(@Nullable String text) {
434 StringBuilder createPageStart(String title) {
435 StringBuilder html = new StringBuilder();
436 html.append("<html><head><title>"
437 + StringEscapeUtils.escapeHtml(BINDING_NAME + " - " + this.account.getThing().getLabel()));
438 if (StringUtils.isNotEmpty(title)) {
440 html.append(StringEscapeUtils.escapeHtml(title));
442 html.append("</title><head><body>");
443 html.append("<h1>" + StringEscapeUtils.escapeHtml(BINDING_NAME + " - " + this.account.getThing().getLabel()));
444 if (StringUtils.isNotEmpty(title)) {
446 html.append(StringEscapeUtils.escapeHtml(title));
448 html.append("</h1>");
452 private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) {
453 // account overview link
454 html.append("<br><a href='" + servletUrl + "/../' >");
455 html.append(StringEscapeUtils.escapeHtml("Account overview"));
456 html.append("</a><br>");
458 html.append("</body></html>");
459 resp.addHeader("content-type", "text/html;charset=UTF-8");
461 resp.getWriter().write(html.toString());
462 } catch (IOException e) {
463 logger.warn("return html failed with IO error", e);
467 private void handleIds(HttpServletResponse resp, Connection connection, Device device, @Nullable Thing thing)
468 throws IOException, URISyntaxException {
471 html = createPageStart("Channel Options - " + thing.getLabel());
473 html = createPageStart("Device Information - No thing defined");
475 renderBluetoothMacChannel(connection, device, html);
476 renderAmazonMusicPlaylistIdChannel(connection, device, html);
477 renderPlayAlarmSoundChannel(connection, device, html);
478 renderMusicProviderIdChannel(connection, html);
479 renderCapabilities(connection, device, html);
480 createPageEndAndSent(resp, html);
483 private void renderCapabilities(Connection connection, Device device, StringBuilder html) {
484 html.append("<h2>Capabilities</h2>");
485 html.append("<table><tr><th align='left'>Name</th></tr>");
486 String[] capabilities = device.capabilities;
487 if (capabilities != null) {
488 for (String capability : capabilities) {
489 html.append("<tr><td>");
490 html.append(StringEscapeUtils.escapeHtml(capability));
491 html.append("</td></tr>");
494 html.append("</table>");
497 private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) {
498 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_MUSIC_PROVIDER_ID) + "</h2>");
499 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
500 List<JsonMusicProvider> musicProviders = connection.getMusicProviders();
501 for (JsonMusicProvider musicProvider : musicProviders) {
502 List<String> properties = musicProvider.supportedProperties;
503 String providerId = musicProvider.id;
504 String displayName = musicProvider.displayName;
505 if (properties != null && properties.contains("Alexa.Music.PlaySearchPhrase")
506 && StringUtils.isNotEmpty(providerId) && StringUtils.equals(musicProvider.availability, "AVAILABLE")
507 && StringUtils.isNotEmpty(displayName)) {
508 html.append("<tr><td>");
509 html.append(StringEscapeUtils.escapeHtml(displayName));
510 html.append("</td><td>");
511 html.append(StringEscapeUtils.escapeHtml(providerId));
512 html.append("</td></tr>");
515 html.append("</table>");
518 private void renderPlayAlarmSoundChannel(Connection connection, Device device, StringBuilder html) {
519 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_PLAY_ALARM_SOUND) + "</h2>");
520 JsonNotificationSound[] notificationSounds = null;
521 String errorMessage = "No notifications sounds found";
523 notificationSounds = connection.getNotificationSounds(device);
524 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException e) {
525 errorMessage = e.getLocalizedMessage();
527 if (notificationSounds != null) {
528 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
529 for (JsonNotificationSound notificationSound : notificationSounds) {
530 if (notificationSound.folder == null && notificationSound.providerId != null
531 && notificationSound.id != null && notificationSound.displayName != null) {
532 String providerSoundId = notificationSound.providerId + ":" + notificationSound.id;
534 html.append("<tr><td>");
535 html.append(StringEscapeUtils.escapeHtml(notificationSound.displayName));
536 html.append("</td><td>");
537 html.append(StringEscapeUtils.escapeHtml(providerSoundId));
538 html.append("</td></tr>");
541 html.append("</table>");
543 html.append(StringEscapeUtils.escapeHtml(errorMessage));
547 private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device device, StringBuilder html) {
548 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID) + "</h2>");
550 JsonPlaylists playLists = null;
551 String errorMessage = "No playlists found";
553 playLists = connection.getPlaylists(device);
554 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException e) {
555 errorMessage = e.getLocalizedMessage();
558 if (playLists != null) {
559 Map<String, PlayList @Nullable []> playlistMap = playLists.playlists;
560 if (playlistMap != null && !playlistMap.isEmpty()) {
561 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
563 for (PlayList[] innerLists : playlistMap.values()) {
565 if (innerLists != null && innerLists.length > 0) {
566 PlayList playList = innerLists[0];
567 if (playList != null && playList.playlistId != null && playList.title != null) {
568 html.append("<tr><td>");
569 html.append(StringEscapeUtils.escapeHtml(nullReplacement(playList.title)));
570 html.append("</td><td>");
571 html.append(StringEscapeUtils.escapeHtml(nullReplacement(playList.playlistId)));
572 html.append("</td></tr>");
577 html.append("</table>");
579 html.append(StringEscapeUtils.escapeHtml(errorMessage));
584 private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) {
585 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_BLUETOOTH_MAC) + "</h2>");
586 JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates();
587 if (bluetoothStates == null) {
590 BluetoothState[] innerStates = bluetoothStates.bluetoothStates;
591 if (innerStates == null) {
594 for (BluetoothState state : innerStates) {
598 if ((state.deviceSerialNumber == null && device.serialNumber == null)
599 || (state.deviceSerialNumber != null && state.deviceSerialNumber.equals(device.serialNumber))) {
600 PairedDevice[] pairedDeviceList = state.pairedDeviceList;
601 if (pairedDeviceList != null && pairedDeviceList.length > 0) {
602 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
603 for (PairedDevice pairedDevice : pairedDeviceList) {
604 html.append("<tr><td>");
605 html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.friendlyName)));
606 html.append("</td><td>");
607 html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.address)));
608 html.append("</td></tr>");
610 html.append("</table>");
612 html.append(StringEscapeUtils.escapeHtml("No bluetooth devices paired"));
618 void handleProxyRequest(Connection connection, HttpServletResponse resp, String verb, String url,
619 @Nullable String referer, @Nullable String postData, boolean json, String site) throws IOException {
620 HttpsURLConnection urlConnection;
622 Map<String, String> headers = null;
623 if (referer != null) {
624 headers = new HashMap<>();
625 headers.put("Referer", referer);
628 urlConnection = connection.makeRequest(verb, url, postData, json, false, headers, 0);
629 if (urlConnection.getResponseCode() == 302) {
631 String location = urlConnection.getHeaderField("location");
632 if (location.contains("/ap/maplanding")) {
634 connection.registerConnectionAsApp(location);
635 account.setConnection(connection);
636 handleDefaultPageResult(resp, "Login succeeded", connection);
637 this.connectionToInitialize = null;
639 } catch (URISyntaxException | ConnectionException e) {
641 "Login to '" + connection.getAmazonSite() + "' failed: " + e.getLocalizedMessage());
642 this.connectionToInitialize = null;
647 String startString = "https://www." + connection.getAmazonSite() + "/";
648 String newLocation = null;
649 if (location.startsWith(startString) && connection.getIsLoggedIn()) {
650 newLocation = servletUrl + PROXY_URI_PART + location.substring(startString.length());
651 } else if (location.startsWith(startString)) {
652 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
655 if (location.startsWith(startString)) {
656 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
659 if (newLocation != null) {
660 logger.debug("Redirect mapped from {} to {}", location, newLocation);
662 resp.sendRedirect(newLocation);
665 returnError(resp, "Invalid redirect to '" + location + "'");
669 } catch (URISyntaxException | ConnectionException e) {
670 returnError(resp, e.getLocalizedMessage());
673 String response = connection.convertStream(urlConnection);
674 returnHtml(connection, resp, response, site);
677 private void returnHtml(Connection connection, HttpServletResponse resp, String html) {
678 returnHtml(connection, resp, html, connection.getAmazonSite());
681 private void returnHtml(Connection connection, HttpServletResponse resp, String html, String amazonSite) {
682 String resultHtml = html.replace("action=\"/", "action=\"" + servletUrl + "/")
683 .replace("action=\"/", "action=\"" + servletUrl + "/")
684 .replace("https://www." + amazonSite + "/", servletUrl + "/")
685 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
686 .replace("https://www." + amazonSite + "/", servletUrl + "/")
687 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
688 .replace("http://www." + amazonSite + "/", servletUrl + "/")
689 .replace("http://www." + amazonSite + "/", servletUrl + "/");
691 resp.addHeader("content-type", "text/html;charset=UTF-8");
693 resp.getWriter().write(resultHtml);
694 } catch (IOException e) {
695 logger.warn("return html failed with IO error", e);
699 void returnError(HttpServletResponse resp, @Nullable String errorMessage) {
701 String message = errorMessage != null ? errorMessage : "null";
702 resp.getWriter().write("<html>" + StringEscapeUtils.escapeHtml(message) + "<br><a href='" + servletUrl
703 + "'>Try again</a></html>");
704 } catch (IOException e) {
705 logger.info("Returning error message failed", e);