//
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//

#include <grpc/support/port_platform.h>

#include "src/core/ext/filters/http/client/http_client_filter.h"

#include <algorithm>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"

#include <grpc/grpc.h>
#include <grpc/status.h>

#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/channel/channel_stack.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/promise/latch.h"
#include "src/core/lib/promise/map.h"
#include "src/core/lib/promise/pipe.h"
#include "src/core/lib/promise/poll.h"
#include "src/core/lib/promise/race.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/slice/percent_encoding.h"
#include "src/core/lib/transport/status_conversion.h"
#include "src/core/lib/transport/transport_fwd.h"
#include "src/core/lib/transport/transport_impl.h"

namespace grpc_core {

const grpc_channel_filter HttpClientFilter::kFilter =
    MakePromiseBasedFilter<HttpClientFilter, FilterEndpoint::kClient,
                           kFilterExaminesServerInitialMetadata>("http-client");

namespace {
absl::Status CheckServerMetadata(ServerMetadata* b) {
  if (auto* status = b->get_pointer(HttpStatusMetadata())) {
    // If both gRPC status and HTTP status are provided in the response, we
    // should prefer the gRPC status code, as mentioned in
    // https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
    //
    const grpc_status_code* grpc_status = b->get_pointer(GrpcStatusMetadata());
    if (grpc_status != nullptr || *status == 200) {
      b->Remove(HttpStatusMetadata());
    } else {
      return absl::Status(
          static_cast<absl::StatusCode>(
              grpc_http2_status_to_grpc_status(*status)),
          absl::StrCat("Received http2 header with status: ", *status));
    }
  }

  if (Slice* grpc_message = b->get_pointer(GrpcMessageMetadata())) {
    *grpc_message = PermissivePercentDecodeSlice(std::move(*grpc_message));
  }

  b->Remove(ContentTypeMetadata());
  return absl::OkStatus();
}

HttpSchemeMetadata::ValueType SchemeFromArgs(const ChannelArgs& args) {
  HttpSchemeMetadata::ValueType scheme = HttpSchemeMetadata::Parse(
      args.GetString(GRPC_ARG_HTTP2_SCHEME).value_or(""),
      [](absl::string_view, const Slice&) {});
  if (scheme == HttpSchemeMetadata::kInvalid) return HttpSchemeMetadata::kHttp;
  return scheme;
}

Slice UserAgentFromArgs(const ChannelArgs& args, const char* transport_name) {
  std::vector<std::string> fields;
  auto add = [&fields](absl::string_view x) {
    if (!x.empty()) fields.push_back(std::string(x));
  };

  add(args.GetString(GRPC_ARG_PRIMARY_USER_AGENT_STRING).value_or(""));
  add(absl::StrFormat("grpc-c/%s (%s; %s)", grpc_version_string(),
                      GPR_PLATFORM_STRING, transport_name));
  add(args.GetString(GRPC_ARG_SECONDARY_USER_AGENT_STRING).value_or(""));

  return Slice::FromCopiedString(absl::StrJoin(fields, " "));
}
}  // namespace

ArenaPromise<ServerMetadataHandle> HttpClientFilter::MakeCallPromise(
    CallArgs call_args, NextPromiseFactory next_promise_factory) {
  auto& md = call_args.client_initial_metadata;
  if (test_only_use_put_requests_) {
    md->Set(HttpMethodMetadata(), HttpMethodMetadata::kPut);
  } else {
    md->Set(HttpMethodMetadata(), HttpMethodMetadata::kPost);
  }
  md->Set(HttpSchemeMetadata(), scheme_);
  md->Set(TeMetadata(), TeMetadata::kTrailers);
  md->Set(ContentTypeMetadata(), ContentTypeMetadata::kApplicationGrpc);
  md->Set(UserAgentMetadata(), user_agent_.Ref());

  auto* initial_metadata_err =
      GetContext<Arena>()->New<Latch<ServerMetadataHandle>>();

  call_args.server_initial_metadata->InterceptAndMap(
      [initial_metadata_err](
          ServerMetadataHandle md) -> absl::optional<ServerMetadataHandle> {
        auto r = CheckServerMetadata(md.get());
        if (!r.ok()) {
          initial_metadata_err->Set(ServerMetadataFromStatus(r));
          return absl::nullopt;
        }
        return std::move(md);
      });

  return Race(initial_metadata_err->Wait(),
              Map(next_promise_factory(std::move(call_args)),
                  [](ServerMetadataHandle md) -> ServerMetadataHandle {
                    auto r = CheckServerMetadata(md.get());
                    if (!r.ok()) return ServerMetadataFromStatus(r);
                    return md;
                  }));
}

HttpClientFilter::HttpClientFilter(HttpSchemeMetadata::ValueType scheme,
                                   Slice user_agent,
                                   bool test_only_use_put_requests)
    : scheme_(scheme),
      user_agent_(std::move(user_agent)),
      test_only_use_put_requests_(test_only_use_put_requests) {}

absl::StatusOr<HttpClientFilter> HttpClientFilter::Create(
    const ChannelArgs& args, ChannelFilter::Args) {
  auto* transport = args.GetObject<grpc_transport>();
  if (transport == nullptr) {
    return absl::InvalidArgumentError("HttpClientFilter needs a transport");
  }
  return HttpClientFilter(
      SchemeFromArgs(args), UserAgentFromArgs(args, transport->vtable->name),
      args.GetInt(GRPC_ARG_TEST_ONLY_USE_PUT_REQUESTS).value_or(false));
}

}  // namespace grpc_core
