diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java index 1147f601c48..9e98dc8b263 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractBoatCertificatesPanel.java @@ -48,6 +48,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.Notification; @@ -58,7 +59,6 @@ import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite; import com.sap.sse.gwt.client.dialog.DialogUtils; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.security.shared.HasPermissions.DefaultActions; import com.sap.sse.security.shared.dto.SecuredDTO; import com.sap.sse.security.ui.client.UserService; @@ -255,9 +255,9 @@ public abstract class AbstractBoatCertificatesPanel extends SimplePanel { private void formSubmitComplete(SubmitCompleteEvent e) { try { - final JSONObject json = (JSONObject) JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContent(e)); + final JSONObject json = (JSONObject) JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContentFromHtml(e.getResults())); if (json.get(ORCCertificateUploadConstants.CERTIFICATES) != null) { - sailingServiceWrite.getORCCertificates(e.getResults(), new AsyncCallback>() { + sailingServiceWrite.getORCCertificates(json.toString(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { busyIndicator.setBusy(false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java index 51722d4ba4e..5026e638233 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java @@ -23,10 +23,10 @@ import com.sap.sailing.gwt.ui.adminconsole.resulthandling.SensorDataImportResult import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; public abstract class AbstractFileImportWidget extends Composite { @@ -89,7 +89,7 @@ public abstract class AbstractFileImportWidget extends Composite { @UiHandler("formPanelUi") void onFileImportComplete(SubmitCompleteEvent event) { - SensorDataImportResponse importResponse = SensorDataImportResponse.parse(FileUploadUtil.getApplicationJsonContent(event)); + SensorDataImportResponse importResponse = SensorDataImportResponse.parse(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); if (importResponse == null) { Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java index eacd64e7673..51898873145 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportPanel.java @@ -33,11 +33,11 @@ import com.sap.sailing.gwt.ui.adminconsole.resulthandling.ExpeditionDataImportRe import com.sap.sailing.gwt.ui.client.Displayer; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; /** * The UI form to upload data for expedition all in one import. @@ -150,7 +150,7 @@ public class ExpeditionAllInOneImportPanel extends Composite { formPanel.addSubmitCompleteHandler(event -> { validation.run(); busyIndicator.setBusy(false); - final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(FileUploadUtil.getApplicationJsonContent(event)); + final ExpeditionDataImportResponse response = ExpeditionDataImportResponse.parse(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); if (response == null) { Notification.notify(StringMessages.INSTANCE.unexpectedErrorDuringFileImport(), NotificationType.ERROR); } else if (response.hasEventId()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index 277e1328d8c..d4a38bff79e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -69,13 +69,13 @@ import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; import com.sap.sse.gwt.adminconsole.FilterablePanelProvider; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.panels.AbstractFilterablePanel; import com.sap.sse.security.shared.dto.UserDTO; import com.sap.sse.security.ui.client.UserService; @@ -477,7 +477,7 @@ public class WindPanel extends FormPanel implements FilterablePanelProvider raceEntries = windImportResult.getRaceEntries(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java index b744eca3196..99f9b6c0692 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/AbstractMediaUploadPopup.java @@ -41,12 +41,12 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.home.shared.SharedHomeResources; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.fileupload.FileUploadConstants; +import com.sap.sse.common.fileupload.FileUploadUtil; import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.common.media.MediaType; import com.sap.sse.common.media.MimeType; import com.sap.sse.gwt.client.Notification; import com.sap.sse.gwt.client.Notification.NotificationType; -import com.sap.sse.gwt.client.fileupload.FileUploadUtil; import com.sap.sse.gwt.client.media.ImageDTO; import com.sap.sse.gwt.client.media.VideoDTO; import com.sap.sse.gwt.client.panels.HorizontalFlowPanel; @@ -288,7 +288,7 @@ public abstract class AbstractMediaUploadPopup extends DialogBox { // we can get the result text here (see the FormPanel documentation for // further explanation). progressOverlay.setVisible(false); - JSONValue resultJsonValue = JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContent(event)); + JSONValue resultJsonValue = JSONParser.parseStrict(FileUploadUtil.getApplicationJsonContentFromHtml(event.getResults())); JSONArray resultJson = resultJsonValue.isArray(); boolean uploadSuccessful = false; boolean fileSkipped = false; diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java index e904d47edf1..294232424e7 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/AbstractJsonHttpServlet.java @@ -1,17 +1,59 @@ package com.sap.sailing.server.gateway; +import java.io.IOException; + import javax.servlet.http.HttpServletResponse; +import org.json.simple.JSONAware; + +import com.sap.sse.common.fileupload.FileUploadUtil; + /** - * Base servlet for json exports - * @author Frank + * Base servlet for JSON exports. If implementing subclasses use {@link #setJsonResponseHeader(HttpServletResponse)}, + * they are assumed to write their response to the servlet response's {@link HttpServletResponse#getOutputStream() + * output stream} directly. Alternatively, e.g., when used as the target of a file upload form that technically expects + * HTML output, the output can be set to {@code text/html}, and a special encoding of a JSON response will be used so it + * can be embedded robustly in an HTML document's {@code body} tag and be extracted again by the client receiving the + * event about submission completion. + * + * @author Frank Mittag + * @author Axel Uhl */ @SuppressWarnings("serial") public abstract class AbstractJsonHttpServlet extends SailingServerHttpServlet { + /** + * By invoking this method you declare that you will write the JSON document + * straight to the output stream. + */ protected void setJsonResponseHeader(HttpServletResponse resp) { + setBasicPropertiesInResponseHeader(resp); + resp.setContentType("application/json"); + } + + /** + * By invoking this method you declare that you assume being the target of a form submission + * and will encode your JSON into an HTML document's {@code body} tag, using the + * {@link FileUploadUtil} class. + */ + protected void setJsonEncodedInHtmlResponseHeader(HttpServletResponse resp) { + setBasicPropertiesInResponseHeader(resp); + resp.setContentType("text/html"); + } + + private void setBasicPropertiesInResponseHeader(HttpServletResponse resp) { // to allow access to the json document directly from a client side javascript resp.setHeader("Access-Control-Allow-Origin", "*"); - resp.setContentType("application/json"); resp.setCharacterEncoding("UTF-8"); } + + /** + * Encodes the stringified {@code json} object into an HTML document's {@code body} element, writing the output into + * {@code resp}'s {@link HttpServletResponse#getWriter() writer}. The encoding is done using + * {@link FileUploadUtil#getHtmlWithEmbeddedJsonContent(String)} which the client can + * then also use to {@link FileUploadUtil#getEmbeddedJsonContentFromHtml(String, java.util.function.Function) + * extract} the pure JSON string from the HTML carrier document again. + */ + protected void writeJsonIntoHtmlResponse(HttpServletResponse resp, JSONAware json) throws IOException { + resp.getWriter().write(FileUploadUtil.getHtmlWithEmbeddedJsonContent(json.toJSONString())); + } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java index d3a2f1d62eb..17a7f339ec7 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/AbstractFileUploadServlet.java @@ -15,9 +15,14 @@ import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import com.sap.sailing.server.gateway.AbstractJsonHttpServlet; +import com.sap.sse.common.fileupload.FileUploadUtil; /** - * Abstract servlet handling multipart-mime file upload coming from an upload form + * Abstract servlet handling multipart-mime file upload coming from an upload form. The response content type is set to + * "text/html" by default (see {@link #setJsonEncodedInHtmlResponseHeader(HttpServletResponse)}). If your response is a + * JSON document, encode it as an HTML document {@code body} using + * {@link FileUploadUtil#getHtmlWithEmbeddedJsonContent(String)}. A client can then use the submit complete event + * response and decode it using {@link FileUploadUtil#getApplicationJsonContentFromHtml(String)}. * * @author Fredrik Teschke * @author Axel Uhl @@ -56,7 +61,7 @@ public abstract class AbstractFileUploadServlet extends AbstractJsonHttpServlet // Conversely, trying to use text/html as content encoding leads some browsers---especially on mobile devices--- // to do ugly things to the content returned, such as replacing digit sequences by a corresponding element // that allows the user to dial that number with the phone app... - setJsonResponseHeader(resp); + setJsonEncodedInHtmlResponseHeader(resp); process(items, req, resp); } catch (FileUploadException e) { throw new IOException("Could not parse request"); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 940d2326b9e..b7ec14b6087 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -105,7 +105,6 @@ public class FileUploadServlet extends AbstractFileUploadServlet { } resultList.add(result); } - - resultList.writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, resultList); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java index 44b4d84f26a..dd134a180b9 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/orc/ORCCertificateImportServlet.java @@ -101,7 +101,6 @@ public class ORCCertificateImportServlet extends AbstractFileUploadServlet { private void writeResponse(Iterable certificates, HttpServletResponse resp) throws IOException { // Use text/html to prevent browsers from wrapping the response body, // see "Handling File Upload Responses in GWT" at http://www.artofsolving.com/node/50 - resp.setContentType("text/html;charset=UTF-8"); final JSONObject jsonResponse = new JSONObject(); final ORCCertificateJsonSerializer certificateSerializer = new ORCCertificateJsonSerializer(); final JSONArray certificatesAsJson = new JSONArray(); @@ -110,7 +109,7 @@ public class ORCCertificateImportServlet extends AbstractFileUploadServlet { } jsonResponse.put(ORCCertificateUploadConstants.CERTIFICATES, certificatesAsJson); jsonResponse.put(ORCCertificateUploadConstants.STATUS, "OK"); - jsonResponse.writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, jsonResponse); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java index 5751505cc83..a145dba38b1 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImportServlet.java @@ -130,7 +130,7 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet { importerResult = new ImporterResult(t, Collections.emptyList()); logger.log(Level.SEVERE, t.getMessage()); } finally { - this.toJSON(importerResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, toJSON(importerResult)); } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java index 652acec62e5..51377f35008 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/SensorDataImportServlet.java @@ -51,7 +51,7 @@ public class SensorDataImportServlet extends AbstractFileUploadServlet { } catch (Exception e) { importResult.add(e); } finally { - ImportResultSerializer.serializeImportResult(importResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, ImportResultSerializer.serializeImportResult(importResult)); } } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java index d27c7106256..952bd4f6e1f 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/TrackFilesImportServlet.java @@ -62,7 +62,7 @@ public class TrackFilesImportServlet extends AbstractFileUploadServlet { } catch (Exception e) { importResult.add(e); } finally { - ImportResultSerializer.serializeImportResult(importResult).writeJSONString(resp.getWriter()); + writeJsonIntoHtmlResponse(resp, ImportResultSerializer.serializeImportResult(importResult)); } } } diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index bbfb35c328c..aff6361e1b9 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -33,6 +33,9 @@ both, mark and race, specify one. This way, redundant and excessive mark fixes can be avoided, don't consume extra memory and don't waste replication bandwidth. See also bug 5919. +
  • Fixed file upload for Chrome browsers after Chrome made an incompatible change, appending + a combination of <div> tags after the already <pre>-enclosed + form submission completion payload.
  • March 2024