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.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
38 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates;
39 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState;
40 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice;
41 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
42 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider;
43 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound;
44 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists;
45 import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList;
46 import org.openhab.core.thing.Thing;
47 import org.osgi.service.http.HttpService;
48 import org.osgi.service.http.NamespaceException;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
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, "UTF8");
92 servletUrl = "/" + servletUrlWithoutRoot;
94 httpService.registerServlet(servletUrl, this, null, httpService.createDefaultHttpContext());
95 } catch (UnsupportedEncodingException | NamespaceException | ServletException e) {
96 throw new IllegalStateException(e.getMessage());
100 private Connection reCreateConnection() {
101 Connection oldConnection = connectionToInitialize;
102 if (oldConnection == null) {
103 oldConnection = account.findConnection();
105 return new Connection(oldConnection, this.gson);
108 public void dispose() {
109 httpService.unregister(servletUrl);
113 protected void doPut(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
114 throws ServletException, IOException {
115 doVerb("PUT", req, resp);
119 protected void doDelete(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
120 throws ServletException, IOException {
121 doVerb("DELETE", req, resp);
125 protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
126 throws ServletException, IOException {
127 doVerb("POST", req, resp);
130 void doVerb(String verb, @Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
137 String requestUri = req.getRequestURI();
138 if (requestUri == null) {
141 String baseUrl = requestUri.substring(servletUrl.length());
142 String uri = baseUrl;
143 String queryString = req.getQueryString();
144 if (queryString != null && queryString.length() > 0) {
145 uri += "?" + queryString;
148 Connection connection = this.account.findConnection();
149 if (connection != null && uri.equals("/changedomain")) {
150 Map<String, String[]> map = req.getParameterMap();
151 String[] domainArray = map.get("domain");
152 if (domainArray == null) {
153 logger.warn("Could not determine domain");
156 String domain = domainArray[0];
157 String loginData = connection.serializeLoginData();
158 Connection newConnection = new Connection(null, this.gson);
159 if (newConnection.tryRestoreLogin(loginData, domain)) {
160 account.setConnection(newConnection);
162 resp.sendRedirect(servletUrl);
165 if (uri.startsWith(PROXY_URI_PART)) {
166 // handle proxy request
168 if (connection == null) {
169 returnError(resp, "Account not online");
172 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
173 + uri.substring(PROXY_URI_PART.length());
175 String postData = null;
176 if (verb == "POST" || verb == "PUT") {
177 postData = req.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
180 this.handleProxyRequest(connection, resp, verb, getUrl, null, postData, true, connection.getAmazonSite());
184 // handle post of login page
185 connection = this.connectionToInitialize;
186 if (connection == null) {
187 returnError(resp, "Connection not in initialize mode.");
191 resp.addHeader("content-type", "text/html;charset=UTF-8");
193 Map<String, String[]> map = req.getParameterMap();
194 StringBuilder postDataBuilder = new StringBuilder();
195 for (String name : map.keySet()) {
196 if (postDataBuilder.length() > 0) {
197 postDataBuilder.append('&');
200 postDataBuilder.append(name);
201 postDataBuilder.append('=');
203 if (name.equals("failedSignInCount")) {
206 String[] strings = map.get(name);
207 if (strings != null && strings.length > 0 && strings[0] != null) {
211 postDataBuilder.append(URLEncoder.encode(value, StandardCharsets.UTF_8.name()));
214 uri = req.getRequestURI();
215 if (uri == null || !uri.startsWith(servletUrl)) {
216 returnError(resp, "Invalid request uri '" + uri + "'");
219 String relativeUrl = uri.substring(servletUrl.length()).replace(FORWARD_URI_PART, "/");
221 String site = connection.getAmazonSite();
222 if (relativeUrl.startsWith("/ap/signin")) {
225 String postUrl = "https://www." + site + relativeUrl;
226 queryString = req.getQueryString();
227 if (queryString != null && queryString.length() > 0) {
228 postUrl += "?" + queryString;
230 String referer = "https://www." + site;
231 String postData = postDataBuilder.toString();
232 handleProxyRequest(connection, resp, "POST", postUrl, referer, postData, false, site);
236 protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException {
243 String requestUri = req.getRequestURI();
244 if (requestUri == null) {
247 String baseUrl = requestUri.substring(servletUrl.length());
248 String uri = baseUrl;
249 String queryString = req.getQueryString();
250 if (queryString != null && queryString.length() > 0) {
251 uri += "?" + queryString;
253 logger.debug("doGet {}", uri);
255 Connection connection = this.connectionToInitialize;
256 if (uri.startsWith(FORWARD_URI_PART) && connection != null) {
257 String getUrl = "https://www." + connection.getAmazonSite() + "/"
258 + uri.substring(FORWARD_URI_PART.length());
260 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
264 connection = this.account.findConnection();
265 if (uri.startsWith(PROXY_URI_PART)) {
266 // handle proxy request
268 if (connection == null) {
269 returnError(resp, "Account not online");
272 String getUrl = "https://alexa." + connection.getAmazonSite() + "/"
273 + uri.substring(PROXY_URI_PART.length());
275 this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite());
279 if (connection != null && connection.verifyLogin()) {
281 if (baseUrl.equals("/logout") || baseUrl.equals("/logout/")) {
282 this.connectionToInitialize = reCreateConnection();
283 this.account.setConnection(null);
284 resp.sendRedirect(this.servletUrl);
288 if (baseUrl.equals("/newdevice") || baseUrl.equals("/newdevice/")) {
289 this.connectionToInitialize = new Connection(null, this.gson);
290 this.account.setConnection(null);
291 resp.sendRedirect(this.servletUrl);
295 if (baseUrl.equals("/devices") || baseUrl.equals("/devices/")) {
296 handleDevices(resp, connection);
299 if (baseUrl.equals("/changeDomain") || baseUrl.equals("/changeDomain/")) {
300 handleChangeDomain(resp, connection);
303 if (baseUrl.equals("/ids") || baseUrl.equals("/ids/")) {
304 String serialNumber = getQueryMap(queryString).get("serialNumber");
305 Device device = account.findDeviceJson(serialNumber);
306 if (device != null) {
307 Thing thing = account.findThingBySerialNumber(device.serialNumber);
308 handleIds(resp, connection, device, thing);
312 // return hint that everything is ok
313 handleDefaultPageResult(resp, "The Account is logged in.", connection);
316 connection = this.connectionToInitialize;
317 if (connection == null) {
318 connection = this.reCreateConnection();
319 this.connectionToInitialize = connection;
322 if (!uri.equals("/")) {
323 String newUri = req.getServletPath() + "/";
324 resp.sendRedirect(newUri);
328 String html = connection.getLoginPage();
329 returnHtml(connection, resp, html, "amazon.com");
330 } catch (URISyntaxException | InterruptedException e) {
331 logger.warn("get failed with uri syntax error", e);
335 public Map<String, String> getQueryMap(@Nullable String query) {
336 Map<String, String> map = new HashMap<>();
338 String[] params = query.split("&");
339 for (String param : params) {
340 String[] elements = param.split("=");
341 if (elements.length == 2) {
342 String name = elements[0];
345 value = URLDecoder.decode(elements[1], "UTF8");
346 } catch (UnsupportedEncodingException e) {
347 logger.info("Unsupported encoding", e);
349 map.put(name, value);
356 private void handleChangeDomain(HttpServletResponse resp, Connection connection) {
357 StringBuilder html = createPageStart("Change Domain");
358 html.append("<form action='");
359 html.append(servletUrl);
360 html.append("/changedomain' method='post'>\nDomain:\n<input type='text' name='domain' value='");
361 html.append(connection.getAmazonSite());
362 html.append("'>\n<br>\n<input type=\"submit\" value=\"Submit\">\n</form>");
364 createPageEndAndSent(resp, html);
367 private void handleDefaultPageResult(HttpServletResponse resp, String message, Connection connection)
369 StringBuilder html = createPageStart("");
370 html.append(StringEscapeUtils.escapeHtml(message));
372 html.append(" <a href='" + servletUrl + "/logout' >");
373 html.append(StringEscapeUtils.escapeHtml("Logout"));
376 html.append(" | <a href='" + servletUrl + "/newdevice' >");
377 html.append(StringEscapeUtils.escapeHtml("Logout and create new device id"));
380 html.append("<br>Customer Id: ");
381 html.append(StringEscapeUtils.escapeHtml(connection.getCustomerId()));
383 html.append("<br>Customer Name: ");
384 html.append(StringEscapeUtils.escapeHtml(connection.getCustomerName()));
386 html.append("<br>App name: ");
387 html.append(StringEscapeUtils.escapeHtml(connection.getDeviceName()));
389 html.append("<br>Connected to: ");
390 html.append(StringEscapeUtils.escapeHtml(connection.getAlexaServer()));
392 html.append(" <a href='");
393 html.append(servletUrl);
394 html.append("/changeDomain'>Change</a>");
397 html.append("<br><a href='/#!/settings/things/" + BINDING_ID + ":"
398 + URLEncoder.encode(THING_TYPE_ACCOUNT.getId(), "UTF8") + ":" + URLEncoder.encode(id, "UTF8") + "'>");
399 html.append(StringEscapeUtils.escapeHtml("Check Thing in Main UI"));
400 html.append("</a><br><br>");
404 "<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>");
405 for (Device device : this.account.getLastKnownDevices()) {
407 html.append("<tr><td>");
408 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.accountName)));
409 html.append("</td><td>");
410 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.serialNumber)));
411 html.append("</td><td>");
412 html.append(StringEscapeUtils.escapeHtml(device.online ? "Online" : "Offline"));
413 html.append("</td><td>");
414 Thing accountHandler = account.findThingBySerialNumber(device.serialNumber);
415 if (accountHandler != null) {
416 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
417 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
418 + StringEscapeUtils.escapeHtml(accountHandler.getLabel()) + "</a>");
420 html.append("<a href='" + servletUrl + "/ids/?serialNumber="
421 + URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
422 + StringEscapeUtils.escapeHtml("Not defined") + "</a>");
424 html.append("</td><td>");
425 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceFamily)));
426 html.append("</td><td>");
427 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceType)));
428 html.append("</td><td>");
429 html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceOwnerCustomerId)));
430 html.append("</td>");
431 html.append("</tr>");
433 html.append("</table>");
434 createPageEndAndSent(resp, html);
437 private void handleDevices(HttpServletResponse resp, Connection connection)
438 throws IOException, URISyntaxException, InterruptedException {
439 returnHtml(connection, resp,
440 "<html>" + StringEscapeUtils.escapeHtml(connection.getDeviceListJson()) + "</html>");
443 private String nullReplacement(@Nullable String text) {
450 StringBuilder createPageStart(String title) {
451 StringBuilder html = new StringBuilder();
452 html.append("<html><head><title>"
453 + StringEscapeUtils.escapeHtml(BINDING_NAME + " - " + this.account.getThing().getLabel()));
454 if (!title.isEmpty()) {
456 html.append(StringEscapeUtils.escapeHtml(title));
458 html.append("</title><head><body>");
459 html.append("<h1>" + StringEscapeUtils.escapeHtml(BINDING_NAME + " - " + this.account.getThing().getLabel()));
460 if (!title.isEmpty()) {
462 html.append(StringEscapeUtils.escapeHtml(title));
464 html.append("</h1>");
468 private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) {
469 // account overview link
470 html.append("<br><a href='" + servletUrl + "/../' >");
471 html.append(StringEscapeUtils.escapeHtml("Account overview"));
472 html.append("</a><br>");
474 html.append("</body></html>");
475 resp.addHeader("content-type", "text/html;charset=UTF-8");
477 resp.getWriter().write(html.toString());
478 } catch (IOException e) {
479 logger.warn("return html failed with IO error", e);
483 private void handleIds(HttpServletResponse resp, Connection connection, Device device, @Nullable Thing thing)
484 throws IOException, URISyntaxException {
487 html = createPageStart("Channel Options - " + thing.getLabel());
489 html = createPageStart("Device Information - No thing defined");
491 renderBluetoothMacChannel(connection, device, html);
492 renderAmazonMusicPlaylistIdChannel(connection, device, html);
493 renderPlayAlarmSoundChannel(connection, device, html);
494 renderMusicProviderIdChannel(connection, html);
495 renderCapabilities(connection, device, html);
496 createPageEndAndSent(resp, html);
499 private void renderCapabilities(Connection connection, Device device, StringBuilder html) {
500 html.append("<h2>Capabilities</h2>");
501 html.append("<table><tr><th align='left'>Name</th></tr>");
502 String[] capabilities = device.capabilities;
503 if (capabilities != null) {
504 for (String capability : capabilities) {
505 html.append("<tr><td>");
506 html.append(StringEscapeUtils.escapeHtml(capability));
507 html.append("</td></tr>");
510 html.append("</table>");
513 private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) {
514 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_MUSIC_PROVIDER_ID) + "</h2>");
515 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
516 List<JsonMusicProvider> musicProviders = connection.getMusicProviders();
517 for (JsonMusicProvider musicProvider : musicProviders) {
518 List<String> properties = musicProvider.supportedProperties;
519 String providerId = musicProvider.id;
520 String displayName = musicProvider.displayName;
521 if (properties != null && properties.contains("Alexa.Music.PlaySearchPhrase") && providerId != null
522 && !providerId.isEmpty() && "AVAILABLE".equals(musicProvider.availability) && displayName != null
523 && !displayName.isEmpty()) {
524 html.append("<tr><td>");
525 html.append(StringEscapeUtils.escapeHtml(displayName));
526 html.append("</td><td>");
527 html.append(StringEscapeUtils.escapeHtml(providerId));
528 html.append("</td></tr>");
531 html.append("</table>");
534 private void renderPlayAlarmSoundChannel(Connection connection, Device device, StringBuilder html) {
535 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_PLAY_ALARM_SOUND) + "</h2>");
536 JsonNotificationSound[] notificationSounds = null;
537 String errorMessage = "No notifications sounds found";
539 notificationSounds = connection.getNotificationSounds(device);
540 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
541 | InterruptedException e) {
542 errorMessage = e.getLocalizedMessage();
544 if (notificationSounds != null) {
545 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
546 for (JsonNotificationSound notificationSound : notificationSounds) {
547 if (notificationSound.folder == null && notificationSound.providerId != null
548 && notificationSound.id != null && notificationSound.displayName != null) {
549 String providerSoundId = notificationSound.providerId + ":" + notificationSound.id;
551 html.append("<tr><td>");
552 html.append(StringEscapeUtils.escapeHtml(notificationSound.displayName));
553 html.append("</td><td>");
554 html.append(StringEscapeUtils.escapeHtml(providerSoundId));
555 html.append("</td></tr>");
558 html.append("</table>");
560 html.append(StringEscapeUtils.escapeHtml(errorMessage));
564 private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device device, StringBuilder html) {
565 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID) + "</h2>");
567 JsonPlaylists playLists = null;
568 String errorMessage = "No playlists found";
570 playLists = connection.getPlaylists(device);
571 } catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
572 | InterruptedException e) {
573 errorMessage = e.getLocalizedMessage();
576 if (playLists != null) {
577 Map<String, PlayList @Nullable []> playlistMap = playLists.playlists;
578 if (playlistMap != null && !playlistMap.isEmpty()) {
579 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
581 for (PlayList[] innerLists : playlistMap.values()) {
583 if (innerLists != null && innerLists.length > 0) {
584 PlayList playList = innerLists[0];
585 if (playList != null && playList.playlistId != null && playList.title != null) {
586 html.append("<tr><td>");
587 html.append(StringEscapeUtils.escapeHtml(nullReplacement(playList.title)));
588 html.append("</td><td>");
589 html.append(StringEscapeUtils.escapeHtml(nullReplacement(playList.playlistId)));
590 html.append("</td></tr>");
595 html.append("</table>");
597 html.append(StringEscapeUtils.escapeHtml(errorMessage));
602 private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) {
603 html.append("<h2>" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_BLUETOOTH_MAC) + "</h2>");
604 JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates();
605 if (bluetoothStates == null) {
608 BluetoothState[] innerStates = bluetoothStates.bluetoothStates;
609 if (innerStates == null) {
612 for (BluetoothState state : innerStates) {
616 String stateDeviceSerialNumber = state.deviceSerialNumber;
617 if ((stateDeviceSerialNumber == null && device.serialNumber == null)
618 || (stateDeviceSerialNumber != null && stateDeviceSerialNumber.equals(device.serialNumber))) {
619 PairedDevice[] pairedDeviceList = state.pairedDeviceList;
620 if (pairedDeviceList != null && pairedDeviceList.length > 0) {
621 html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
622 for (PairedDevice pairedDevice : pairedDeviceList) {
623 html.append("<tr><td>");
624 html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.friendlyName)));
625 html.append("</td><td>");
626 html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.address)));
627 html.append("</td></tr>");
629 html.append("</table>");
631 html.append(StringEscapeUtils.escapeHtml("No bluetooth devices paired"));
637 void handleProxyRequest(Connection connection, HttpServletResponse resp, String verb, String url,
638 @Nullable String referer, @Nullable String postData, boolean json, String site) throws IOException {
639 HttpsURLConnection urlConnection;
641 Map<String, String> headers = null;
642 if (referer != null) {
643 headers = new HashMap<>();
644 headers.put("Referer", referer);
647 urlConnection = connection.makeRequest(verb, url, postData, json, false, headers, 0);
648 if (urlConnection.getResponseCode() == 302) {
650 String location = urlConnection.getHeaderField("location");
651 if (location.contains("/ap/maplanding")) {
653 connection.registerConnectionAsApp(location);
654 account.setConnection(connection);
655 handleDefaultPageResult(resp, "Login succeeded", connection);
656 this.connectionToInitialize = null;
658 } catch (URISyntaxException | ConnectionException e) {
660 "Login to '" + connection.getAmazonSite() + "' failed: " + e.getLocalizedMessage());
661 this.connectionToInitialize = null;
666 String startString = "https://www." + connection.getAmazonSite() + "/";
667 String newLocation = null;
668 if (location.startsWith(startString) && connection.getIsLoggedIn()) {
669 newLocation = servletUrl + PROXY_URI_PART + location.substring(startString.length());
670 } else if (location.startsWith(startString)) {
671 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
674 if (location.startsWith(startString)) {
675 newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length());
678 if (newLocation != null) {
679 logger.debug("Redirect mapped from {} to {}", location, newLocation);
681 resp.sendRedirect(newLocation);
684 returnError(resp, "Invalid redirect to '" + location + "'");
688 } catch (URISyntaxException | ConnectionException | InterruptedException e) {
689 returnError(resp, e.getLocalizedMessage());
692 String response = connection.convertStream(urlConnection);
693 returnHtml(connection, resp, response, site);
696 private void returnHtml(Connection connection, HttpServletResponse resp, String html) {
697 returnHtml(connection, resp, html, connection.getAmazonSite());
700 private void returnHtml(Connection connection, HttpServletResponse resp, String html, String amazonSite) {
701 String resultHtml = html.replace("action=\"/", "action=\"" + servletUrl + "/")
702 .replace("action=\"/", "action=\"" + servletUrl + "/")
703 .replace("https://www." + amazonSite + "/", servletUrl + "/")
704 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
705 .replace("https://www." + amazonSite + "/", servletUrl + "/")
706 .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/")
707 .replace("http://www." + amazonSite + "/", servletUrl + "/")
708 .replace("http://www." + amazonSite + "/", servletUrl + "/");
710 resp.addHeader("content-type", "text/html;charset=UTF-8");
712 resp.getWriter().write(resultHtml);
713 } catch (IOException e) {
714 logger.warn("return html failed with IO error", e);
718 void returnError(HttpServletResponse resp, @Nullable String errorMessage) {
720 String message = errorMessage != null ? errorMessage : "null";
721 resp.getWriter().write("<html>" + StringEscapeUtils.escapeHtml(message) + "<br><a href='" + servletUrl
722 + "'>Try again</a></html>");
723 } catch (IOException e) {
724 logger.info("Returning error message failed", e);