Add option for validating content-type header

For systems implementing to the OWASP security guidelines[1] (of which all
should ideally) we should be checking the content-type header all times
that we parse a request as JSON.

This commit adds an option for parsing content-type, and sets a default
of "must get content-type".  Ideally this would not be a breaking
change, but given the number of guides and scripts that omit the content
type, it seems worthwhile to add a trapdoor, such that people can opt
into their own model on how they would like to see this checking work.

Tested:
```
curl --insecure -H "Content-Type: application/json" -X POST -D headers.txt https://${bmc}/redfish/v1/SessionService/Sessions -d    '{"UserName":"root", "Password":"0penBmc"}'
```

Succeeds.

Removing Content-Type argument causes bmc to return
Base.1.13.0.UnrecognizedRequestBody.

[1] cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html

Change-Id: Iaa47dd563b40036ff2fc2cacb70d941fd8853038
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
index 2665b2e..9bf968a 100644
--- a/include/openbmc_dbus_rest.hpp
+++ b/include/openbmc_dbus_rest.hpp
@@ -20,6 +20,7 @@
 #include "http_request.hpp"
 #include "http_response.hpp"
 #include "logging.hpp"
+#include "parsing.hpp"
 #include "routing.hpp"
 #include "str_utility.hpp"
 
@@ -80,6 +81,7 @@
 const constexpr char* badReqMsg = "400 Bad Request";
 const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed";
 const constexpr char* forbiddenMsg = "403 Forbidden";
+const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type";
 const constexpr char* methodFailedMsg = "500 Method Call Failed";
 const constexpr char* methodOutputFailedMsg = "500 Method Output Error";
 const constexpr char* notFoundDesc =
@@ -87,6 +89,8 @@
 const constexpr char* propNotFoundDesc =
     "The specified property cannot be found";
 const constexpr char* noJsonDesc = "No JSON object could be decoded";
+const constexpr char* invalidContentType =
+    "Content-type header is missing or invalid";
 const constexpr char* methodNotFoundDesc =
     "The specified method cannot be found";
 const constexpr char* methodNotAllowedDesc = "Method not allowed";
@@ -1538,10 +1542,17 @@
 {
     BMCWEB_LOG_DEBUG << "handleAction on path: " << objectPath << " and method "
                      << methodName;
-    nlohmann::json requestDbusData =
-        nlohmann::json::parse(req.body, nullptr, false);
+    nlohmann::json requestDbusData;
 
-    if (requestDbusData.is_discarded())
+    JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+    if (ret == JsonParseResult::BadContentType)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::unsupported_media_type,
+                         invalidContentType, unsupportedMediaMsg);
+        return;
+    }
+    if (ret != JsonParseResult::Success)
     {
         setErrorResponse(asyncResp->res,
                          boost::beast::http::status::bad_request, noJsonDesc,
@@ -1838,11 +1849,18 @@
                          forbiddenResDesc, forbiddenMsg);
         return;
     }
+    nlohmann::json requestDbusData;
 
-    nlohmann::json requestDbusData =
-        nlohmann::json::parse(req.body, nullptr, false);
+    JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+    if (ret == JsonParseResult::BadContentType)
+    {
+        setErrorResponse(asyncResp->res,
+                         boost::beast::http::status::unsupported_media_type,
+                         invalidContentType, unsupportedMediaMsg);
+        return;
+    }
 
-    if (requestDbusData.is_discarded())
+    if (ret != JsonParseResult::Success)
     {
         setErrorResponse(asyncResp->res,
                          boost::beast::http::status::bad_request, noJsonDesc,
@@ -2376,14 +2394,23 @@
             return;
         }
 
-        nlohmann::json requestDbusData =
-            nlohmann::json::parse(req.body, nullptr, false);
-
-        if (requestDbusData.is_discarded())
+        nlohmann::json requestDbusData;
+        JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
+        if (ret == JsonParseResult::BadContentType)
         {
-            asyncResp->res.result(boost::beast::http::status::bad_request);
+            setErrorResponse(asyncResp->res,
+                             boost::beast::http::status::unsupported_media_type,
+                             invalidContentType, unsupportedMediaMsg);
             return;
         }
+        if (ret != JsonParseResult::Success)
+        {
+            setErrorResponse(asyncResp->res,
+                             boost::beast::http::status::bad_request,
+                             noJsonDesc, badReqMsg);
+            return;
+        }
+
         if (!requestDbusData.is_array())
         {
             asyncResp->res.result(boost::beast::http::status::bad_request);