xref: /openbmc/bmcweb/http/complete_response_fields.hpp (revision f485bd44a22c27a2346a69d740764ea98b333bd1)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "boost_formatters.hpp"
6 #include "http_body.hpp"
7 #include "http_response.hpp"
8 #include "http_utility.hpp"
9 #include "json_html_serializer.hpp"
10 #include "logging.hpp"
11 #include "security_headers.hpp"
12 
13 #include <boost/beast/http/field.hpp>
14 #include <nlohmann/json.hpp>
15 
16 #include <array>
17 #include <string>
18 #include <string_view>
19 #include <utility>
20 
21 namespace crow
22 {
23 
attemptZstdCompression(Response & res)24 inline bool attemptZstdCompression(Response& res)
25 {
26     using bmcweb::CompressionType;
27     using enum bmcweb::CompressionType;
28     using http_helpers::Encoding;
29     using enum http_helpers::Encoding;
30 
31     std::string& strBody = res.response.body().str();
32     if (strBody.empty())
33     {
34         // No need to compress an empty body
35         return true;
36     }
37     bmcweb::ZstdCompressor zstdCompressor;
38     if (!zstdCompressor.init(strBody.size()))
39     {
40         BMCWEB_LOG_ERROR("Failed to initialize Zstd Compressor");
41         return false;
42     }
43 
44     const uint8_t* dataIn = std::bit_cast<const uint8_t*>(strBody.data());
45     std::span<const uint8_t> spanIn(dataIn, strBody.size());
46     bool more = false;
47     std::optional<std::span<const uint8_t>> compressed =
48         zstdCompressor.compress(spanIn, more);
49     if (!compressed)
50     {
51         BMCWEB_LOG_ERROR("Failed to compress content with zstd.");
52         return false;
53     }
54     const char* dataOut = std::bit_cast<const char*>(compressed->data());
55     strBody = std::string(dataOut, compressed->size());
56 
57     res.addHeader(boost::beast::http::field::content_encoding, "zstd");
58     res.response.body().clientCompressionType = Zstd;
59     res.response.body().compressionType = Zstd;
60     return true;
61 }
62 
handleEncoding(std::string_view acceptEncoding,Response & res)63 inline void handleEncoding(std::string_view acceptEncoding, Response& res)
64 {
65     using bmcweb::CompressionType;
66     using enum bmcweb::CompressionType;
67     using http_helpers::Encoding;
68     using enum http_helpers::Encoding;
69     // If the payload is currently compressed, see if we can avoid
70     // decompressing it by sending it to the client directly
71     switch (res.response.body().compressionType)
72     {
73         case Zstd:
74         {
75             std::array<Encoding, 1> allowedEnc{ZSTD};
76             Encoding encoding =
77                 http_helpers::getPreferredEncoding(acceptEncoding, allowedEnc);
78 
79             if (encoding == ZSTD)
80             {
81                 // If the client supports returning zstd directly, allow that.
82                 BMCWEB_LOG_DEBUG(
83                     "Content is already ztd compressed.  Setting client compression type to Zstd");
84                 res.response.body().clientCompressionType = Zstd;
85             }
86         }
87         break;
88         case Gzip:
89         {
90             std::array<Encoding, 1> allowedEnc{GZIP};
91             Encoding encoding =
92                 http_helpers::getPreferredEncoding(acceptEncoding, allowedEnc);
93             if (encoding != GZIP)
94             {
95                 BMCWEB_LOG_WARNING(
96                     "Unimplemented: Returning gzip payload to client that did not explicitly allow it.");
97             }
98         }
99         break;
100         case Raw:
101         {
102             BMCWEB_LOG_DEBUG(
103                 "Content is raw bytes.  Checking if it can be compressed.");
104 
105             std::array<Encoding, 1> allowedEnc{ZSTD};
106             Encoding encoding =
107                 http_helpers::getPreferredEncoding(acceptEncoding, allowedEnc);
108             if (encoding == ZSTD)
109             {
110                 BMCWEB_LOG_ERROR("Content can be compressed with zstd.");
111                 if (!attemptZstdCompression(res))
112                 {
113                     BMCWEB_LOG_ERROR(
114                         "Failed to compress content with zstd.  Continuing.");
115                 }
116             }
117         }
118         break;
119         default:
120             break;
121     }
122 }
123 
completeResponseFields(std::string_view accepts,std::string_view acceptEncoding,Response & res)124 inline void completeResponseFields(
125     std::string_view accepts, std::string_view acceptEncoding, Response& res)
126 {
127     BMCWEB_LOG_INFO("Response: {}", res.resultInt());
128     addSecurityHeaders(res);
129 
130     res.setResponseEtagAndHandleNotModified();
131     if (res.jsonValue.is_structured())
132     {
133         using http_helpers::ContentType;
134         std::array<ContentType, 3> allowed{ContentType::CBOR, ContentType::JSON,
135                                            ContentType::HTML};
136         ContentType preferred = getPreferredContentType(accepts, allowed);
137 
138         if (preferred == ContentType::HTML)
139         {
140             json_html_util::prettyPrintJson(res);
141         }
142         else if (preferred == ContentType::CBOR)
143         {
144             res.addHeader(boost::beast::http::field::content_type,
145                           "application/cbor");
146             std::string cbor;
147             nlohmann::json::to_cbor(res.jsonValue, cbor);
148             res.write(std::move(cbor));
149         }
150         else
151         {
152             // Technically preferred could also be NoMatch here, but we'd
153             // like to default to something rather than return 400 for
154             // backward compatibility.
155             res.addHeader(boost::beast::http::field::content_type,
156                           "application/json");
157             res.write(res.jsonValue.dump(
158                 2, ' ', true, nlohmann::json::error_handler_t::replace));
159         }
160     }
161 
162     handleEncoding(acceptEncoding, res);
163 }
164 } // namespace crow
165