This is an automated email from the ASF dual-hosted git repository.

ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b2cf9fd373 Improved webtools data file viewer URL handling (#1707)
b2cf9fd373 is described below

commit b2cf9fd373ee182f35579e50e29e60776750046c
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Mon Aug 24 00:33:05 2026 +0530

    Improved webtools data file viewer URL handling (#1707)
    
    Gate the fetch behind DATAFILE_MAINT, restrict fetched URLs to
    http/https and public addresses, disable redirect following, and stop
    echoing fetched content into exception messages.
    
    Thank you Krishna Uprit for your help.
    
    Co-authored-by: Krishnauprit18 <[email protected]>
---
 .../java/org/apache/ofbiz/base/util/UtilURL.java   | 148 +++++++++++++++++++++
 .../org/apache/ofbiz/base/util/UtilURLTests.java   |  86 ++++++++++++
 .../java/org/apache/ofbiz/datafile/Record.java     |  19 +--
 .../org/apache/ofbiz/datafile/RecordIterator.java  |  12 +-
 framework/security/config/security.properties      |   6 +
 .../ofbiz/webtools/datafile/ViewDataFile.groovy    | 120 +++++++++--------
 6 files changed, 327 insertions(+), 64 deletions(-)

diff --git 
a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilURL.java 
b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilURL.java
index afb18ea321..9144168208 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilURL.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilURL.java
@@ -19,10 +19,15 @@
 package org.apache.ofbiz.base.util;
 
 import java.io.File;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -156,6 +161,149 @@ public final class UtilURL {
         return url;
     }
 
+    /**
+     * Same as {@link #fromUrlString(String)}, except the result is rejected 
unless it is safe to
+     * dereference: an http or https URL whose host does not resolve to a 
loopback, link-local,
+     * private, or otherwise reserved address. Use this instead of {@link 
#fromUrlString(String)}
+     * whenever the URL comes from an untrusted source (e.g. a request 
parameter) and will be
+     * fetched by the server, to avoid server-side request forgery against the 
local file system,
+     * loopback services, and the internal network.
+     *
+     * <p>An optional host allow-list can be configured with the
+     * {@code webtools.datafile.url.allowed.hosts} property in 
security.properties: a
+     * comma-separated list of hostnames/domains. When set, only URLs whose 
host matches an entry
+     * (or is a subdomain of one) are allowed, in addition to the address 
checks above.
+     *
+     * @throws GeneralException if the URL is present but not allowed
+     */
+    public static URL fromCheckedUrlString(String urlString) throws 
GeneralException {
+        if (urlString == null) {
+            return null;
+        }
+        URL url = fromUrlString(urlString);
+        if (url == null) {
+            return null;
+        }
+        checkUrlResourceAllowed(url);
+        return url;
+    }
+
+    /**
+     * Throws {@link GeneralException} unless {@code url} is an http or https 
URL whose host
+     * resolves only to publicly routable addresses (and, when configured, is 
in the
+     * {@code webtools.datafile.url.allowed.hosts} allow-list). See {@link 
#fromCheckedUrlString}.
+     */
+    public static void checkUrlResourceAllowed(URL url) throws 
GeneralException {
+        String protocol = url.getProtocol();
+        if (!"http".equalsIgnoreCase(protocol) && 
!"https".equalsIgnoreCase(protocol)) {
+            throw new GeneralException("URL only supports http/https 
protocols; rejected: " + protocol);
+        }
+        String host = url.getHost();
+        if (UtilValidate.isEmpty(host)) {
+            throw new GeneralException("URL has no host component");
+        }
+
+        String allowedHostsStr = UtilProperties.getPropertyValue("security", 
"webtools.datafile.url.allowed.hosts", "");
+        if (UtilValidate.isNotEmpty(allowedHostsStr)) {
+            String lcHost = host.toLowerCase(Locale.ROOT);
+            boolean hostAllowed = false;
+            for (String entry : allowedHostsStr.split(",")) {
+                String allowedEntry = entry.trim().toLowerCase(Locale.ROOT);
+                if (UtilValidate.isEmpty(allowedEntry)) {
+                    continue;
+                }
+                if (lcHost.equals(allowedEntry) || lcHost.endsWith("." + 
allowedEntry)) {
+                    hostAllowed = true;
+                    break;
+                }
+            }
+            if (!hostAllowed) {
+                throw new GeneralException("URL host is not in the allowed 
list: " + host);
+            }
+        }
+
+        InetAddress[] addresses;
+        try {
+            addresses = InetAddress.getAllByName(host);
+        } catch (UnknownHostException e) {
+            throw new GeneralException("URL host cannot be resolved: " + host);
+        }
+        if (addresses.length == 0) {
+            throw new GeneralException("URL host resolved to no addresses: " + 
host);
+        }
+        for (InetAddress addr : addresses) {
+            checkNotPrivateOrReservedAddress(addr);
+        }
+    }
+
+    /**
+     * Throws {@link GeneralException} if {@code addr} belongs to a private, 
loopback,
+     * link-local, or otherwise reserved IP range (IPv4 and IPv6).
+     */
+    private static void checkNotPrivateOrReservedAddress(InetAddress addr) 
throws GeneralException {
+        if (addr.isLoopbackAddress()) {
+            throw new GeneralException("URL target resolves to a loopback 
address: " + addr.getHostAddress());
+        }
+        if (addr.isLinkLocalAddress()) {
+            throw new GeneralException("URL target resolves to a link-local 
address: " + addr.getHostAddress());
+        }
+        if (addr.isSiteLocalAddress()) {
+            throw new GeneralException("URL target resolves to a private 
(site-local) address: " + addr.getHostAddress());
+        }
+        if (addr.isAnyLocalAddress()) {
+            throw new GeneralException("URL target resolves to a wildcard 
address: " + addr.getHostAddress());
+        }
+        if (addr.isMulticastAddress()) {
+            throw new GeneralException("URL target resolves to a multicast 
address: " + addr.getHostAddress());
+        }
+        byte[] b = addr.getAddress();
+        if (addr instanceof Inet4Address) {
+            int i0 = b[0] & 0xFF;
+            int i1 = b[1] & 0xFF;
+            // 0.0.0.0/8 - "this" network (RFC 1122)
+            if (i0 == 0) {
+                throw new GeneralException("URL target resolves to a reserved 
network address (0.0.0.0/8): " + addr.getHostAddress());
+            }
+            // 100.64.0.0/10 - shared address space / CGNAT (RFC 6598)
+            if (i0 == 100 && i1 >= 64 && i1 <= 127) {
+                throw new GeneralException("URL target resolves to a shared 
address space (CGNAT, 100.64.0.0/10): " + addr.getHostAddress());
+            }
+            // 192.0.0.0/24 - IETF protocol assignments (RFC 6890)
+            if (i0 == 192 && i1 == 0 && (b[2] & 0xFF) == 0) {
+                throw new GeneralException("URL target resolves to an IETF 
reserved address (192.0.0.0/24): " + addr.getHostAddress());
+            }
+            // 198.18.0.0/15 - network benchmarking (RFC 2544)
+            if (i0 == 198 && (i1 == 18 || i1 == 19)) {
+                throw new GeneralException("URL target resolves to a 
benchmarking address (198.18.0.0/15): " + addr.getHostAddress());
+            }
+            // 240.0.0.0/4 - reserved for future use (RFC 1112)
+            if ((i0 & 0xF0) == 240) {
+                throw new GeneralException("URL target resolves to a reserved 
address (240.0.0.0/4): " + addr.getHostAddress());
+            }
+        } else if (addr instanceof Inet6Address) {
+            // fc00::/7 - Unique Local Addresses (ULA), private IPv6 (RFC 4193)
+            if ((b[0] & 0xFE) == 0xFC) {
+                throw new GeneralException("URL target resolves to a 
unique-local (private) IPv6 address: " + addr.getHostAddress());
+            }
+            // ::ffff:0:0/96 - IPv4-mapped IPv6; re-validate the embedded IPv4 
address
+            boolean isIpv4Mapped = true;
+            for (int i = 0; i < 10; i++) {
+                if (b[i] != 0) {
+                    isIpv4Mapped = false;
+                    break;
+                }
+            }
+            if (isIpv4Mapped && (b[10] & 0xFF) == 0xFF && (b[11] & 0xFF) == 
0xFF) {
+                try {
+                    checkNotPrivateOrReservedAddress(
+                            InetAddress.getByAddress(new byte[]{b[12], b[13], 
b[14], b[15]}));
+                } catch (UnknownHostException e) {
+                    throw new GeneralException("URL target contains an invalid 
IPv4-mapped IPv6 address");
+                }
+            }
+        }
+    }
+
     public static URL fromOfbizHomePath(String filename) {
         String ofbizHome = System.getProperty("ofbiz.home");
         if (ofbizHome == null) {
diff --git 
a/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilURLTests.java 
b/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilURLTests.java
new file mode 100644
index 0000000000..ab7414f5a5
--- /dev/null
+++ b/framework/base/src/test/java/org/apache/ofbiz/base/util/UtilURLTests.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ 
*******************************************************************************/
+package org.apache.ofbiz.base.util;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URL;
+
+import org.junit.jupiter.api.Test;
+
+public class UtilURLTests {
+
+    @Test
+    public void rejectsFileScheme() {
+        GeneralException e = assertThrows(GeneralException.class, () ->
+                UtilURL.fromCheckedUrlString("file:///etc/passwd"));
+        assertTrue(e.getMessage().contains("http/https"));
+    }
+
+    @Test
+    public void rejectsLoopbackAddress() {
+        GeneralException e = assertThrows(GeneralException.class, () ->
+                
UtilURL.fromCheckedUrlString("http://127.0.0.1:8080/internal";));
+        assertTrue(e.getMessage().contains("loopback"));
+    }
+
+    @Test
+    public void rejectsLoopbackHostname() {
+        GeneralException e = assertThrows(GeneralException.class, () ->
+                
UtilURL.fromCheckedUrlString("http://localhost:8080/internal";));
+        assertTrue(e.getMessage().contains("loopback"));
+    }
+
+    @Test
+    public void rejectsLinkLocalCloudMetadataAddress() {
+        GeneralException e = assertThrows(GeneralException.class, () ->
+                
UtilURL.fromCheckedUrlString("http://169.254.169.254/latest/meta-data/";));
+        assertTrue(e.getMessage().contains("link-local"));
+    }
+
+    @Test
+    public void rejectsPrivateAddress() {
+        GeneralException e = assertThrows(GeneralException.class, () ->
+                UtilURL.fromCheckedUrlString("http://10.0.0.5/internal";));
+        assertTrue(e.getMessage().contains("private"));
+    }
+
+    @Test
+    public void rejectsFtpScheme() {
+        assertThrows(GeneralException.class, () -> 
UtilURL.fromCheckedUrlString("ftp://example.com/file";));
+    }
+
+    @Test
+    public void allowsPubliclyRoutableHttpsHost() throws Exception {
+        URL url = 
UtilURL.fromCheckedUrlString("https://93.184.216.34/index.html";);
+        assertTrue(url != null && "93.184.216.34".equals(url.getHost()));
+    }
+
+    @Test
+    public void returnsNullForNullInput() throws Exception {
+        assertNull(UtilURL.fromCheckedUrlString(null));
+    }
+
+    @Test
+    public void returnsNullForUnparsableInput() throws Exception {
+        assertNull(UtilURL.fromCheckedUrlString("not a url at all ::"));
+    }
+}
diff --git 
a/framework/datafile/src/main/java/org/apache/ofbiz/datafile/Record.java 
b/framework/datafile/src/main/java/org/apache/ofbiz/datafile/Record.java
index 20bbba49ec..8c7ab88861 100644
--- a/framework/datafile/src/main/java/org/apache/ofbiz/datafile/Record.java
+++ b/framework/datafile/src/main/java/org/apache/ofbiz/datafile/Record.java
@@ -604,13 +604,15 @@ public class Record implements Serializable {
             try {
                 record.setString(modelField.getName(), strVal);
             } catch (java.text.ParseException e) {
+                // Note: deliberately not including strVal here; it is 
unvalidated content read from
+                // the target file/URL and this message can reach the 
requesting user and the log.
                 throw new DataFileException(
-                        "Could not parse field " + modelField.getName() + ", 
format string \"" + modelField.getFormat() + "\" with value " + strVal
-                                + " on line " + lineNum, e);
+                        "Could not parse field " + modelField.getName() + ", 
format string \"" + modelField.getFormat()
+                                + "\" on line " + lineNum, e);
             } catch (java.lang.NumberFormatException e) {
                 throw new DataFileException(
-                        "Number not valid for field " + modelField.getName() + 
", format string \"" + modelField.getFormat() + "\" with value "
-                                + strVal + " on line " + lineNum, e);
+                        "Number not valid for field " + modelField.getName() + 
", format string \"" + modelField.getFormat()
+                                + "\" on line " + lineNum, e);
             }
         }
         return record;
@@ -674,13 +676,14 @@ public class Record implements Serializable {
                 }
                 record.setString(modelField.getName(), strVal);
             } catch (java.text.ParseException e) {
+                // Note: deliberately not including strVal here; see 
createRecord above.
                 throw new DataFileException(
-                        "Could not parse field " + modelField.getName() + ", 
format string \"" + modelField.getFormat() + "\" with value " + strVal
-                                + " on line " + lineNum, e);
+                        "Could not parse field " + modelField.getName() + ", 
format string \"" + modelField.getFormat()
+                                + "\" on line " + lineNum, e);
             } catch (java.lang.NumberFormatException e) {
                 throw new DataFileException(
-                        "Number not valid for field " + modelField.getName() + 
", format string \"" + modelField.getFormat() + "\" with value "
-                                + strVal + " on line " + lineNum, e);
+                        "Number not valid for field " + modelField.getName() + 
", format string \"" + modelField.getFormat()
+                                + "\" on line " + lineNum, e);
             }
         }
         return record;
diff --git 
a/framework/datafile/src/main/java/org/apache/ofbiz/datafile/RecordIterator.java
 
b/framework/datafile/src/main/java/org/apache/ofbiz/datafile/RecordIterator.java
index a1ec2cb3d3..46bc8a3b68 100644
--- 
a/framework/datafile/src/main/java/org/apache/ofbiz/datafile/RecordIterator.java
+++ 
b/framework/datafile/src/main/java/org/apache/ofbiz/datafile/RecordIterator.java
@@ -22,7 +22,9 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
 import java.net.URL;
+import java.net.URLConnection;
 import java.nio.charset.Charset;
 import java.util.Stack;
 
@@ -53,7 +55,15 @@ public class RecordIterator {
 
         InputStream urlStream = null;
         try {
-            urlStream = fileUrl.openStream();
+            // Don't follow redirects: the caller may have already validated 
fileUrl's host as safe
+            // to fetch from (see UtilURL.fromCheckedUrlString), and a 
redirect could point
+            // anywhere, bypassing that check.
+            URLConnection connection = fileUrl.openConnection();
+            if (connection instanceof HttpURLConnection) {
+                HttpURLConnection httpConnection = (HttpURLConnection) 
connection;
+                httpConnection.setInstanceFollowRedirects(false);
+            }
+            urlStream = connection.getInputStream();
         } catch (IOException e) {
             throw new DataFileException("Error open URL: " + 
fileUrl.toString(), e);
         }
diff --git a/framework/security/config/security.properties 
b/framework/security/config/security.properties
index 234c06531b..2b202d7fca 100644
--- a/framework/security/config/security.properties
+++ b/framework/security/config/security.properties
@@ -198,6 +198,12 @@ content.data.url.resource.connect.timeout=10000
 # -- Read timeout in milliseconds for URL_RESOURCE fetches. Default: 30000 (30 
s).
 content.data.url.resource.read.timeout=30000
 
+# -- Allowed hosts for URLs fetched by the webtools data file viewer/loader 
(comma-separated host
+# -- names). Both exact matches and subdomain matches are supported, as above. 
Leave empty to skip
+# -- host filtering and apply only the scheme (http/https only) and IP-range 
checks; loopback,
+# -- link-local, private and other reserved addresses are always blocked 
regardless of this setting.
+webtools.datafile.url.allowed.hosts=
+
 # -- Maximum response body size in bytes for URL_RESOURCE fetches. Default: 
10485760 (10 MB).
 # -- Responses advertising a larger Content-Length are refused before the 
connection body is read;
 # -- the returned InputStream is additionally capped at this size regardless 
of the declared length.
diff --git 
a/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/datafile/ViewDataFile.groovy
 
b/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/datafile/ViewDataFile.groovy
index ed9b28709e..258f46b793 100644
--- 
a/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/datafile/ViewDataFile.groovy
+++ 
b/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/datafile/ViewDataFile.groovy
@@ -19,6 +19,7 @@
 package org.apache.ofbiz.webtools.datafile
 
 import org.apache.ofbiz.base.util.Debug
+import org.apache.ofbiz.base.util.GeneralException
 import org.apache.ofbiz.base.util.UtilProperties
 import org.apache.ofbiz.base.util.UtilURL
 import org.apache.ofbiz.datafile.DataFile
@@ -28,78 +29,87 @@ import org.apache.ofbiz.datafile.ModelDataFileReader
 uiLabelMap = UtilProperties.getResourceBundleMap('WebtoolsUiLabels', locale)
 messages = []
 
-dataFileSave = request.getParameter('DATAFILE_SAVE')
+// This screen builds URLs from raw request parameters and dereferences them; 
that fetch must
+// never happen for a principal who lacks DATAFILE_MAINT, so the whole thing 
is gated here,
+// ahead of any parameter handling, rather than left to the screen/template 
permission checks
+// that only gate rendering after the fetch already ran.
+if (security.hasPermission('DATAFILE_MAINT', session)) {
+    dataFileSave = request.getParameter('DATAFILE_SAVE')
 
-entityXmlFileSave = request.getParameter('ENTITYXML_FILE_SAVE')
+    entityXmlFileSave = request.getParameter('ENTITYXML_FILE_SAVE')
 
-dataFileLoc = request.getParameter('DATAFILE_LOCATION')
-definitionLoc = request.getParameter('DEFINITION_LOCATION')
-definitionName = request.getParameter('DEFINITION_NAME')
-dataFileIsUrl = null != request.getParameter('DATAFILE_IS_URL')
-definitionIsUrl = null != request.getParameter('DEFINITION_IS_URL')
+    dataFileLoc = request.getParameter('DATAFILE_LOCATION')
+    definitionLoc = request.getParameter('DEFINITION_LOCATION')
+    definitionName = request.getParameter('DEFINITION_NAME')
+    dataFileIsUrl = null != request.getParameter('DATAFILE_IS_URL')
+    definitionIsUrl = null != request.getParameter('DEFINITION_IS_URL')
 
-try {
-    dataFileUrl = dataFileIsUrl ? UtilURL.fromUrlString(dataFileLoc) : 
UtilURL.fromFilename(dataFileLoc)
-}
-catch (java.net.MalformedURLException e) {
-    messages.add(e.getMessage())
-}
-
-try {
-    definitionUrl = definitionIsUrl ? UtilURL.fromUrlString(definitionLoc) : 
UtilURL.fromFilename(definitionLoc)
-}
-catch (java.net.MalformedURLException e) {
-    messages.add(e.getMessage())
-}
-
-definitionNames = null
-if (definitionUrl) {
+    dataFileUrl = null
     try {
-        ModelDataFileReader reader = 
ModelDataFileReader.getModelDataFileReader(definitionUrl)
-        if (reader) {
-            definitionNames = 
((Collection)reader.getDataFileNames()).iterator()
-            context.put('definitionNames', definitionNames)
-        }
+        dataFileUrl = dataFileIsUrl ? 
UtilURL.fromCheckedUrlString(dataFileLoc) : UtilURL.fromFilename(dataFileLoc)
     }
-    catch (Exception e) {
+    catch (java.net.MalformedURLException | GeneralException e) {
         messages.add(e.getMessage())
     }
-}
 
-dataFile = null
-if (dataFileUrl && definitionUrl && definitionNames) {
+    definitionUrl = null
     try {
-        dataFile = DataFile.readFile(dataFileUrl, definitionUrl, 
definitionName)
-        context.put('dataFile', dataFile)
+        definitionUrl = definitionIsUrl ? 
UtilURL.fromCheckedUrlString(definitionLoc) : 
UtilURL.fromFilename(definitionLoc)
     }
-    catch (Exception e) {
-        messages.add(e.toString()); Debug.log(e)
+    catch (java.net.MalformedURLException | GeneralException e) {
+        messages.add(e.getMessage())
     }
-}
 
-if (dataFile) {
-    modelDataFile = dataFile.getModelDataFile()
-    context.put('modelDataFile', modelDataFile)
-}
+    definitionNames = null
+    if (definitionUrl) {
+        try {
+            ModelDataFileReader reader = 
ModelDataFileReader.getModelDataFileReader(definitionUrl)
+            if (reader) {
+                definitionNames = 
((Collection)reader.getDataFileNames()).iterator()
+                context.put('definitionNames', definitionNames)
+            }
+        }
+        catch (Exception e) {
+            messages.add(e.getMessage())
+        }
+    }
 
-if (dataFile && dataFileSave) {
-    try {
-        dataFile.writeDataFile(dataFileSave)
-        messages.add(uiLabelMap.WebtoolsDataFileSavedTo + dataFileSave)
+    dataFile = null
+    if (dataFileUrl && definitionUrl && definitionNames) {
+        try {
+            dataFile = DataFile.readFile(dataFileUrl, definitionUrl, 
definitionName)
+            context.put('dataFile', dataFile)
+        }
+        catch (Exception e) {
+            messages.add(e.getMessage())
+            Debug.logError(e, 'Error reading data file', 'ViewDataFile.groovy')
+        }
     }
-    catch (Exception e) {
-        messages.add(e.getMessage())
+
+    if (dataFile) {
+        modelDataFile = dataFile.getModelDataFile()
+        context.put('modelDataFile', modelDataFile)
     }
-}
 
-if (dataFile && entityXmlFileSave) {
-    try {
-        //dataFile.writeDataFile(entityXmlFileSave)
-        DataFile2EntityXml.writeToEntityXml(entityXmlFileSave, dataFile)
-        messages.add(uiLabelMap.WebtoolsDataEntityFileSavedTo + 
entityXmlFileSave)
+    if (dataFile && dataFileSave) {
+        try {
+            dataFile.writeDataFile(dataFileSave)
+            messages.add(uiLabelMap.WebtoolsDataFileSavedTo + dataFileSave)
+        }
+        catch (Exception e) {
+            messages.add(e.getMessage())
+        }
     }
-    catch (Exception e) {
-        messages.add(e.getMessage())
+
+    if (dataFile && entityXmlFileSave) {
+        try {
+            //dataFile.writeDataFile(entityXmlFileSave)
+            DataFile2EntityXml.writeToEntityXml(entityXmlFileSave, dataFile)
+            messages.add(uiLabelMap.WebtoolsDataEntityFileSavedTo + 
entityXmlFileSave)
+        }
+        catch (Exception e) {
+            messages.add(e.getMessage())
+        }
     }
 }
 context.messages = messages

Reply via email to