xref: /aosp_15_r20/external/federated-compute/fcp/client/http/http_client_util.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "fcp/client/http/http_client_util.h"
17 
18 #include <algorithm>
19 #include <functional>
20 #include <optional>
21 #include <string>
22 #include <vector>
23 
24 #include "absl/status/status.h"
25 #include "absl/status/statusor.h"
26 #include "absl/strings/ascii.h"
27 #include "absl/strings/str_cat.h"
28 #include "absl/strings/str_format.h"
29 #include "absl/strings/string_view.h"
30 #include "absl/strings/strip.h"
31 #include "absl/strings/substitute.h"
32 #include "fcp/base/monitoring.h"
33 #include "fcp/client/http/http_client.h"
34 #include "fcp/protos/federatedcompute/common.pb.h"
35 // #include "google/rpc/status.pb.h"
36 
37 namespace fcp {
38 namespace client {
39 namespace http {
40 
41 namespace {
42 
43 using ::google::internal::federatedcompute::v1::Status;
44 
ConvertHttpCodeToStatusCode(int code)45 absl::StatusCode ConvertHttpCodeToStatusCode(int code) {
46   switch (code) {
47     case kHttpBadRequest:
48       return absl::StatusCode::kInvalidArgument;
49     case kHttpForbidden:
50       return absl::StatusCode::kPermissionDenied;
51     case kHttpNotFound:
52       return absl::StatusCode::kNotFound;
53     case kHttpConflict:
54       return absl::StatusCode::kAborted;
55     case kHttpTooManyRequests:
56       return absl::StatusCode::kResourceExhausted;
57     case kHttpClientClosedRequest:
58       return absl::StatusCode::kCancelled;
59     case kHttpGatewayTimeout:
60       return absl::StatusCode::kDeadlineExceeded;
61     case kHttpNotImplemented:
62       return absl::StatusCode::kUnimplemented;
63     case kHttpServiceUnavailable:
64       return absl::StatusCode::kUnavailable;
65     case kHttpUnauthorized:
66       return absl::StatusCode::kUnauthenticated;
67     default: {
68       // Importantly this range ensures that we treat not only "200 OK" as OK,
69       // but also other codes such as "201 Created" etc.
70       if (code >= 200 && code < 300) {
71         return absl::StatusCode::kOk;
72       }
73       if (code >= 400 && code < 500) {
74         return absl::StatusCode::kFailedPrecondition;
75       }
76       if (code >= 500 && code < 600) {
77         return absl::StatusCode::kInternal;
78       }
79       return absl::StatusCode::kUnknown;
80     }
81   }
82 }
83 
84 // Converts a `Status` error code into an `absl::StatusCode`
85 // (there is a 1:1 mapping).
ConvertRpcCodeToStatusCode(int code)86 absl::StatusCode ConvertRpcCodeToStatusCode(int code) {
87   switch (code) {
88     case static_cast<int>(absl::StatusCode::kOk):
89       return absl::StatusCode::kOk;
90     case static_cast<int>(absl::StatusCode::kCancelled):
91       return absl::StatusCode::kCancelled;
92     case static_cast<int>(absl::StatusCode::kUnknown):
93       return absl::StatusCode::kUnknown;
94     case static_cast<int>(absl::StatusCode::kInvalidArgument):
95       return absl::StatusCode::kInvalidArgument;
96     case static_cast<int>(absl::StatusCode::kDeadlineExceeded):
97       return absl::StatusCode::kDeadlineExceeded;
98     case static_cast<int>(absl::StatusCode::kNotFound):
99       return absl::StatusCode::kNotFound;
100     case static_cast<int>(absl::StatusCode::kAlreadyExists):
101       return absl::StatusCode::kAlreadyExists;
102     case static_cast<int>(absl::StatusCode::kPermissionDenied):
103       return absl::StatusCode::kPermissionDenied;
104     case static_cast<int>(absl::StatusCode::kResourceExhausted):
105       return absl::StatusCode::kResourceExhausted;
106     case static_cast<int>(absl::StatusCode::kFailedPrecondition):
107       return absl::StatusCode::kFailedPrecondition;
108     case static_cast<int>(absl::StatusCode::kAborted):
109       return absl::StatusCode::kAborted;
110     case static_cast<int>(absl::StatusCode::kOutOfRange):
111       return absl::StatusCode::kOutOfRange;
112     case static_cast<int>(absl::StatusCode::kUnimplemented):
113       return absl::StatusCode::kUnimplemented;
114     case static_cast<int>(absl::StatusCode::kInternal):
115       return absl::StatusCode::kInternal;
116     case static_cast<int>(absl::StatusCode::kUnavailable):
117       return absl::StatusCode::kUnavailable;
118     case static_cast<int>(absl::StatusCode::kDataLoss):
119       return absl::StatusCode::kDataLoss;
120     case static_cast<int>(absl::StatusCode::kUnauthenticated):
121       return absl::StatusCode::kUnauthenticated;
122     default:
123       // This should never be reached, since there should be a 1:1 mapping
124       // between Absl and Google RPC status codes.
125       return absl::StatusCode::kUnknown;
126   }
127 }
128 
PercentEncode(absl::string_view input,std::function<bool (char c)> unencoded_chars)129 absl::StatusOr<std::string> PercentEncode(
130     absl::string_view input, std::function<bool(char c)> unencoded_chars) {
131   std::string result;
132   for (unsigned char c : input) {
133     // We limit URIs only to ASCII characters.
134     if (!absl::ascii_isascii(c)) {
135       return absl::InvalidArgumentError(absl::StrCat(
136           "Encountered unsupported char during URI encoding: ", c));
137     }
138     // The following characters are *not* percent-encoded.
139     if (unencoded_chars(c)) {
140       result.push_back(c);
141       continue;
142     }
143     // Any other character is percent-encoded.
144     result.append(absl::StrFormat("%%%X", c));
145   }
146   return result;
147 }
148 
149 }  // namespace
150 
ConvertHttpCodeToStatus(int code)151 absl::Status ConvertHttpCodeToStatus(int code) {
152   absl::StatusCode status_code = ConvertHttpCodeToStatusCode(code);
153   if (status_code == absl::StatusCode::kOk) {
154     return absl::OkStatus();
155   }
156   std::string error_message =
157       absl::StrCat("Request returned non-OK response (code: ", code, ")");
158   return absl::Status(status_code, error_message);
159 }
160 
ConvertRpcStatusToAbslStatus(Status rpc_status)161 absl::Status ConvertRpcStatusToAbslStatus(Status rpc_status) {
162   return absl::Status(ConvertRpcCodeToStatusCode(rpc_status.code()),
163                       rpc_status.message());
164 }
165 
ConvertAbslStatusToRpcStatus(absl::Status status)166 Status ConvertAbslStatusToRpcStatus(absl::Status status) {
167   Status rpc_status;
168   rpc_status.set_code(static_cast<int>(status.code()));
169   rpc_status.set_message(std::string(status.message()));
170   return rpc_status;
171 }
172 
ConvertMethodToString(HttpRequest::Method method)173 std::string ConvertMethodToString(HttpRequest::Method method) {
174   switch (method) {
175     case HttpRequest::Method::kGet:
176       return "GET";
177     case HttpRequest::Method::kHead:
178       return "HEAD";
179     case HttpRequest::Method::kDelete:
180       return "DELETE";
181     case HttpRequest::Method::kPatch:
182       return "PATCH";
183     case HttpRequest::Method::kPost:
184       return "POST";
185     case HttpRequest::Method::kPut:
186       return "PUT";
187   }
188 }
189 
FindHeader(const HeaderList & headers,absl::string_view needle)190 std::optional<std::string> FindHeader(const HeaderList& headers,
191                                       absl::string_view needle) {
192   // Normalize the needle (since header names are case insensitive, as per RFC
193   // 2616 section 4.2).
194   const std::string normalized_needle = absl::AsciiStrToLower(needle);
195   const auto& header_entry = std::find_if(
196       headers.begin(), headers.end(), [&normalized_needle](const Header& x) {
197         // AsciiStrToLower safely handles non-ASCII data, and comparing
198         // non-ASCII data w/ our needle is safe as well.
199         return absl::AsciiStrToLower(std::get<0>(x)) == normalized_needle;
200       });
201 
202   if (header_entry == headers.end()) {
203     return std::nullopt;
204   }
205   return std::get<1>(*header_entry);
206 }
207 
JoinBaseUriWithSuffix(absl::string_view base_uri,absl::string_view uri_suffix)208 absl::StatusOr<std::string> JoinBaseUriWithSuffix(
209     absl::string_view base_uri, absl::string_view uri_suffix) {
210   if (!uri_suffix.empty() && uri_suffix[0] != '/') {
211     return absl::InvalidArgumentError(
212         "uri_suffix be empty or must have a leading '/'");
213   }
214   // Construct the full URI by joining the base URI we should use with the given
215   // suffix, ensuring that there's always a single '/' in between the two parts.
216   return absl::StrCat(absl::StripSuffix(base_uri, "/"), "/",
217                       absl::StripPrefix(uri_suffix, "/"));
218 }
219 
EncodeUriSinglePathSegment(absl::string_view input)220 absl::StatusOr<std::string> EncodeUriSinglePathSegment(
221     absl::string_view input) {
222   return PercentEncode(input, [](char c) {
223     return absl::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' ||
224            c == '~';
225   });
226 }
227 
EncodeUriMultiplePathSegments(absl::string_view input)228 absl::StatusOr<std::string> EncodeUriMultiplePathSegments(
229     absl::string_view input) {
230   return PercentEncode(input, [](char c) {
231     return absl::ascii_isalnum(c) || c == '-' || c == '_' || c == '.' ||
232            c == '~' || c == '/';
233   });
234 }
235 
CreateByteStreamUploadUriSuffix(absl::string_view resource_name)236 absl::StatusOr<std::string> CreateByteStreamUploadUriSuffix(
237     absl::string_view resource_name) {
238   constexpr absl::string_view pattern = "/upload/v1/media/$0";
239   FCP_ASSIGN_OR_RETURN(std::string encoded_resource_name,
240                        EncodeUriMultiplePathSegments(resource_name));
241   // Construct the URI suffix.
242   return absl::Substitute(pattern, encoded_resource_name);
243 }
244 }  // namespace http
245 }  // namespace client
246 }  // namespace fcp
247