Cherry-pick UpdateService Support for MultipartHttpPushUri

Upstream commit info here:
https://github.com/openbmc/bmcweb/commit/0ed80c8ce93a38eca6951ffad5c3143a3a720053

Since a service support the MultipartHttpPushUri property within the
UpdateService resource, so add a new MultipartHttpPushUri property,
This property shall contain a URI used to perform a Redfish
Specification-defined Multipart HTTP or HTTPS POST of a software image
for the purpose of installing software contained within the image, and
use the requirements of a multipart/form-data to request body for HTTP
push software update.

Tested:
curl -k -H "X-Auth-Token: $token" -H "Content-Type:
multipart/form-data"
-F
'UpdateParameters={"Targets":["/redfish/v1/Managers/bmc"],
"@Redfish.OperationApplyTime":"Immediate"};type=application/json'
-F
'UpdateFile=@obmc-phosphor-image.static.mtd.tar;type=application/
octet-stream'
https://${bmc}/redfish/v1/UpdateService/update
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
  "@odata.type": "#Task.v1_4_3.Task",
  "Id": "0",
  "TaskState": "Running",
  "TaskStatus": "OK"
}

Passed the validator:

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: Ie1ea0594337efdb073270aba6918389c4381a2b3
(cherry picked from commit 0ed80c8ce93a38eca6951ffad5c3143a3a720053)

Google-Bug-Id: 290981635
Change-Id: Idadc06f6ba485523453e67d23a93d65e779ffc7a
Signed-off-by: Edward Lee <edwarddl@google.com>
diff --git a/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch b/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch
new file mode 100644
index 0000000..3f8dc9e
--- /dev/null
+++ b/recipes-phosphor/interfaces/bmcweb/0001-UpdateService-Support-for-MultipartHttpPushUri.patch
@@ -0,0 +1,317 @@
+From f27a664969be38d9ff58d811bd4c23f920d8199b Mon Sep 17 00:00:00 2001
+From: George Liu <liuxiwei@inspur.com>
+Date: Tue, 12 May 2020 16:06:27 +0800
+Subject: [PATCH] UpdateService: Support for MultipartHttpPushUri
+
+Since a service support the MultipartHttpPushUri property within the
+UpdateService resource, so add a new MultipartHttpPushUri property,
+This property shall contain a URI used to perform a Redfish
+Specification-defined Multipart HTTP or HTTPS POST of a software image
+for the purpose of installing software contained within the image, and
+use the requirements of a multipart/form-data to request body for HTTP
+push software update.
+
+Tested:
+curl -k -H "X-Auth-Token: $token" -H "Content-Type:
+multipart/form-data"
+-F
+'UpdateParameters={"Targets":["/redfish/v1/Managers/bmc"],
+"@Redfish.OperationApplyTime":"Immediate"};type=application/json'
+-F
+'UpdateFile=@obmc-phosphor-image.static.mtd.tar;type=application/
+octet-stream'
+https://${bmc}/redfish/v1/UpdateService/update
+{
+  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
+  "@odata.type": "#Task.v1_4_3.Task",
+  "Id": "0",
+  "TaskState": "Running",
+  "TaskStatus": "OK"
+}
+
+Passed the validator:
+
+Patch Tracking Bug: b/290981635
+Upstream info / review: https://github.com/openbmc/bmcweb/commit/0ed80c8ce93a38eca6951ffad5c3143a3a720053
+Upstream-Status: Accepted
+Justification:
+
+Already submitted upstream but need downstream until rebase.
+
+Signed-off-by: George Liu <liuxiwei@inspur.com>
+Change-Id: Ie1ea0594337efdb073270aba6918389c4381a2b3
+(cherry picked from commit 0ed80c8ce93a38eca6951ffad5c3143a3a720053)
+---
+ redfish-core/lib/update_service.hpp | 214 ++++++++++++++++++++++------
+ 1 file changed, 167 insertions(+), 47 deletions(-)
+
+diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
+index 6d0e1502..cdac33e2 100644
+--- a/redfish-core/lib/update_service.hpp
++++ b/redfish-core/lib/update_service.hpp
+@@ -20,6 +20,7 @@
+ #include "app.hpp"
+ #include "dbus_utility.hpp"
+ #include "other_software_service.hpp"
++#include "multipart_parser.hpp"
+ #include "query.hpp"
+ #include "registries/privilege_registry.hpp"
+ #include "task.hpp"
+@@ -33,6 +34,7 @@
+ #include <sdbusplus/unpack_properties.hpp>
+ 
+ #include <array>
++#include <filesystem>
+ #include <string_view>
+ 
+ namespace redfish
+@@ -528,6 +530,150 @@ inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app)
+         });
+ }
+ 
++inline void uploadImageFile(crow::Response& res, std::string_view body)
++{
++    std::filesystem::path filepath(
++        "/tmp/images/" +
++        boost::uuids::to_string(boost::uuids::random_generator()()));
++    BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
++    std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
++                                    std::ofstream::trunc);
++    // set the permission of the file to 640
++    std::filesystem::perms permission =
++        std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
++    std::filesystem::permissions(filepath, permission);
++    out << body;
++
++    if (out.bad())
++    {
++        messages::internalError(res);
++        cleanUp();
++    }
++}
++
++inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
++                         const std::string& applyTime)
++{
++    std::string applyTimeNewVal;
++    if (applyTime == "Immediate")
++    {
++        applyTimeNewVal =
++            "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
++    }
++    else if (applyTime == "OnReset")
++    {
++        applyTimeNewVal =
++            "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
++    }
++    else
++    {
++        BMCWEB_LOG_INFO
++            << "ApplyTime value is not in the list of acceptable values";
++        messages::propertyValueNotInList(asyncResp->res, applyTime,
++                                         "ApplyTime");
++        return;
++    }
++
++    // Set the requested image apply time value
++    crow::connections::systemBus->async_method_call(
++        [asyncResp](const boost::system::error_code ec) {
++        if (ec)
++        {
++            BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
++            messages::internalError(asyncResp->res);
++            return;
++        }
++        messages::success(asyncResp->res);
++        },
++        "xyz.openbmc_project.Settings",
++        "/xyz/openbmc_project/software/apply_time",
++        "org.freedesktop.DBus.Properties", "Set",
++        "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
++        dbus::utility::DbusVariantType{applyTimeNewVal});
++}
++
++inline void
++    updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
++                           const MultipartParser& parser)
++{
++    const std::string* uploadData = nullptr;
++    std::optional<std::string> applyTime = "OnReset";
++    bool targetFound = false;
++    for (const FormPart& formpart : parser.mime_fields)
++    {
++        boost::beast::http::fields::const_iterator it =
++            formpart.fields.find("Content-Disposition");
++        if (it == formpart.fields.end())
++        {
++            BMCWEB_LOG_ERROR << "Couldn't find Content-Disposition";
++            return;
++        }
++        BMCWEB_LOG_INFO << "Parsing value " << it->value();
++
++        // The construction parameters of param_list must start with `;`
++        size_t index = it->value().find(';');
++        if (index == std::string::npos)
++        {
++            continue;
++        }
++
++        for (auto const& param :
++             boost::beast::http::param_list{it->value().substr(index)})
++        {
++            if (param.first != "name" || param.second.empty())
++            {
++                continue;
++            }
++
++            if (param.second == "UpdateParameters")
++            {
++                std::vector<std::string> targets;
++                nlohmann::json content =
++                    nlohmann::json::parse(formpart.content);
++                if (!json_util::readJson(content, asyncResp->res, "Targets",
++                                         targets, "@Redfish.OperationApplyTime",
++                                         applyTime))
++                {
++                    return;
++                }
++                if (targets.size() != 1)
++                {
++                    messages::propertyValueFormatError(asyncResp->res,
++                                                       "Targets", "");
++                    return;
++                }
++                if (targets[0] != "/redfish/v1/Managers/bmc")
++                {
++                    messages::propertyValueNotInList(asyncResp->res,
++                                                     "Targets/0", targets[0]);
++                    return;
++                }
++                targetFound = true;
++            }
++            else if (param.second == "UpdateFile")
++            {
++                uploadData = &(formpart.content);
++            }
++        }
++    }
++
++    if (uploadData == nullptr)
++    {
++        BMCWEB_LOG_ERROR << "Upload data is NULL";
++        messages::propertyMissing(asyncResp->res, "UpdateFile");
++        return;
++    }
++    if (!targetFound)
++    {
++        messages::propertyMissing(asyncResp->res, "targets");
++        return;
++    }
++
++    setApplyTime(asyncResp, *applyTime);
++
++    uploadImageFile(asyncResp->res, *uploadData);
++}
++
+ inline void
+     handleUpdateServicePost(App& app, const crow::Request& req,
+                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+@@ -541,15 +687,23 @@ inline void
+     // Setup callback for when new software detected
+     monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
+ 
+-    std::string filepath(
+-        "/tmp/images/" +
+-        boost::uuids::to_string(boost::uuids::random_generator()()));
+-    BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
+-    std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
+-                                    std::ofstream::trunc);
+-    out << req.body();
+-    out.close();
+-    BMCWEB_LOG_DEBUG << "file upload complete!!";
++    MultipartParser parser;
++    ParserError ec = parser.parse(req);
++    if (ec == ParserError::ERROR_BOUNDARY_FORMAT)
++    {
++        // If the request didnt' contain boundary information, assume it was a
++        // POST binary payload.
++        uploadImageFile(asyncResp->res, req.body());
++        return;
++    }
++    if (ec != ParserError::PARSER_SUCCESS)
++    {
++        // handle error
++        BMCWEB_LOG_ERROR << "MIME parse failed, ec : " << static_cast<int>(ec);
++        messages::internalError(asyncResp->res);
++        return;
++    }
++    updateMultipartContext(asyncResp, parser);
+ }
+ 
+ inline void requestRoutesUpdateService(App& app)
+@@ -564,7 +718,7 @@ inline void requestRoutesUpdateService(App& app)
+             return;
+         }
+         asyncResp->res.jsonValue["@odata.type"] =
+-            "#UpdateService.v1_5_0.UpdateService";
++            "#UpdateService.v1_11_1.UpdateService";
+         asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
+         asyncResp->res.jsonValue["Id"] = "UpdateService";
+         asyncResp->res.jsonValue["Description"] = "Service for Software Update";
+@@ -581,6 +735,8 @@ inline void requestRoutesUpdateService(App& app)
+ 
+         asyncResp->res.jsonValue["HttpPushUri"] =
+             "/redfish/v1/UpdateService/update";
++        asyncResp->res.jsonValue["MultipartHttpPushUri"] =
++            "/redfish/v1/UpdateService/update";
+ 
+         // UpdateService cannot be disabled
+         asyncResp->res.jsonValue["ServiceEnabled"] = true;
+@@ -668,43 +824,7 @@ inline void requestRoutesUpdateService(App& app)
+ 
+                 if (applyTime)
+                 {
+-                    std::string applyTimeNewVal;
+-                    if (applyTime == "Immediate")
+-                    {
+-                        applyTimeNewVal =
+-                            "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
+-                    }
+-                    else if (applyTime == "OnReset")
+-                    {
+-                        applyTimeNewVal =
+-                            "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
+-                    }
+-                    else
+-                    {
+-                        BMCWEB_LOG_INFO
+-                            << "ApplyTime value is not in the list of acceptable values";
+-                        messages::propertyValueNotInList(
+-                            asyncResp->res, *applyTime, "ApplyTime");
+-                        return;
+-                    }
+-
+-                    // Set the requested image apply time value
+-                    crow::connections::systemBus->async_method_call(
+-                        [asyncResp](const boost::system::error_code& ec) {
+-                        if (ec)
+-                        {
+-                            BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
+-                            messages::internalError(asyncResp->res);
+-                            return;
+-                        }
+-                        messages::success(asyncResp->res);
+-                        },
+-                        "xyz.openbmc_project.Settings",
+-                        "/xyz/openbmc_project/software/apply_time",
+-                        "org.freedesktop.DBus.Properties", "Set",
+-                        "xyz.openbmc_project.Software.ApplyTime",
+-                        "RequestedApplyTime",
+-                        dbus::utility::DbusVariantType{applyTimeNewVal});
++                    setApplyTime(asyncResp, *applyTime);
+                 }
+             }
+         }
+-- 
+2.41.0.255.g8b1d071c50-goog
+
diff --git a/recipes-phosphor/interfaces/bmcweb_%.bbappend b/recipes-phosphor/interfaces/bmcweb_%.bbappend
index ebb0655..23f711f 100644
--- a/recipes-phosphor/interfaces/bmcweb_%.bbappend
+++ b/recipes-phosphor/interfaces/bmcweb_%.bbappend
@@ -99,6 +99,7 @@
   file://0001-Expose-Systems-in-ComputerSystemCollection-by-Item.S.patch \
   file://0001-Add-Multi-Host-Power-Control-Support.patch \
   file://0001-Add-Multi-Host-Support-for-HostLogger.patch \
+  file://0001-UpdateService-Support-for-MultipartHttpPushUri.patch \
 "
 multi_host_patches:gbmcfork = ""
 SRC_URI:append:gbmc = "${multi_host_patches}"