Clean up tftp update to use URL

Similar to transforms we've done elsewhere, we shouldn't be parsing
urls using std::string::find, regex, or anything else, as they don't
handle URL % encoding properly.

Change-Id: I48bb30c0c737c4df2ae73f40fc49c63bac5b658f
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
index 9ab1a3b..9a07d3c 100644
--- a/redfish-core/lib/update_service.hpp
+++ b/redfish-core/lib/update_service.hpp
@@ -433,6 +433,70 @@
         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
 }
 
+struct TftpUrl
+{
+    std::string fwFile;
+    std::string tftpServer;
+};
+
+inline std::optional<TftpUrl>
+    parseTftpUrl(std::string imageURI,
+                 std::optional<std::string> transferProtocol,
+                 crow::Response& res)
+{
+    if (imageURI.find("://") == std::string::npos)
+    {
+        if (imageURI.starts_with("/"))
+        {
+            messages::actionParameterValueTypeError(
+                res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
+            return std::nullopt;
+        }
+        if (!transferProtocol)
+        {
+            messages::actionParameterValueTypeError(
+                res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
+            return std::nullopt;
+        }
+        // OpenBMC currently only supports TFTP
+        if (*transferProtocol != "TFTP")
+        {
+            messages::actionParameterNotSupported(res, "TransferProtocol",
+                                                  *transferProtocol);
+            BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
+                             *transferProtocol);
+            return std::nullopt;
+        }
+        imageURI = "tftp://" + imageURI;
+    }
+
+    boost::system::result<boost::urls::url> url =
+        boost::urls::parse_absolute_uri(imageURI);
+    if (!url)
+    {
+        messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
+                                                "UpdateService.SimpleUpdate");
+
+        return std::nullopt;
+    }
+    url->normalize();
+
+    if (url->scheme() != "tftp")
+    {
+        messages::actionParameterNotSupported(res, "ImageURI", imageURI);
+        return std::nullopt;
+    }
+    std::string path(url->encoded_path());
+    if (path.size() < 2)
+    {
+        messages::actionParameterNotSupported(res, "ImageURI", imageURI);
+        return std::nullopt;
+    }
+    path.erase(0, 1);
+    std::string host(url->encoded_host_and_port());
+    return TftpUrl{path, host};
+}
+
 /**
  * UpdateServiceActionsSimpleUpdate class supports handle POST method for
  * SimpleUpdate action.
@@ -467,60 +531,14 @@
             BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
             return;
         }
-        if (!transferProtocol)
+        std::optional<TftpUrl> ret = parseTftpUrl(imageURI, transferProtocol,
+                                                  asyncResp->res);
+        if (!ret)
         {
-            // Must be option 2
-            // Verify ImageURI has transfer protocol in it
-            size_t separator = imageURI.find(':');
-            if ((separator == std::string::npos) ||
-                ((separator + 1) > imageURI.size()))
-            {
-                messages::actionParameterValueTypeError(
-                    asyncResp->res, imageURI, "ImageURI",
-                    "UpdateService.SimpleUpdate");
-                BMCWEB_LOG_ERROR("ImageURI missing transfer protocol: {}",
-                                 imageURI);
-                return;
-            }
-            transferProtocol = imageURI.substr(0, separator);
-            // Ensure protocol is upper case for a common comparison path
-            // below
-            boost::to_upper(*transferProtocol);
-            BMCWEB_LOG_DEBUG("Encoded transfer protocol {}", *transferProtocol);
-
-            // Adjust imageURI to not have the protocol on it for parsing
-            // below
-            // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
-            imageURI = imageURI.substr(separator + 3);
-            BMCWEB_LOG_DEBUG("Adjusted imageUri {}", imageURI);
-        }
-
-        // OpenBMC currently only supports TFTP
-        if (*transferProtocol != "TFTP")
-        {
-            messages::actionParameterNotSupported(asyncResp->res,
-                                                  "TransferProtocol",
-                                                  "UpdateService.SimpleUpdate");
-            BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
-                             *transferProtocol);
             return;
         }
 
-        // Format should be <IP or Hostname>/<file> for imageURI
-        size_t separator = imageURI.find('/');
-        if ((separator == std::string::npos) ||
-            ((separator + 1) > imageURI.size()))
-        {
-            messages::actionParameterValueTypeError(
-                asyncResp->res, imageURI, "ImageURI",
-                "UpdateService.SimpleUpdate");
-            BMCWEB_LOG_ERROR("Invalid ImageURI: {}", imageURI);
-            return;
-        }
-
-        std::string tftpServer = imageURI.substr(0, separator);
-        std::string fwFile = imageURI.substr(separator + 1);
-        BMCWEB_LOG_DEBUG("Server: {}{}", tftpServer + " File: ", fwFile);
+        BMCWEB_LOG_DEBUG("Server: {} File: {}", ret->tftpServer, ret->fwFile);
 
         // Setup callback for when new software detected
         // Give TFTP 10 minutes to complete
@@ -552,7 +570,7 @@
         },
             "xyz.openbmc_project.Software.Download",
             "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
-            "DownloadViaTFTP", fwFile, tftpServer);
+            "DownloadViaTFTP", ret->fwFile, ret->tftpServer);
 
         BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
     });