// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/compiler/csharp/csharp_generator.h"

#include <sstream>

#include "absl/strings/ascii.h"
#include "absl/strings/charset.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/code_generator.h"
#include "google/protobuf/compiler/csharp/csharp_helpers.h"
#include "google/protobuf/compiler/csharp/csharp_options.h"
#include "google/protobuf/compiler/csharp/csharp_reflection_class.h"
#include "google/protobuf/compiler/csharp/names.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/io/printer.h"
#include "google/protobuf/io/zero_copy_stream.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace csharp {

namespace {

// Characters that could enable code injection in generated C# code.
constexpr absl::CharSet kBannedNamespaceChars =
    absl::CharSet(";{}()'\"`$#=<>|&[]/\\@\n\r!?*~^%,") |
    absl::CharSet::Char('\0');

// Validates that a csharp_namespace option does not contain characters that
// could allow code injection into generated C# code. Uses a denylist approach
// to allow Unicode identifiers while blocking dangerous characters.
bool IsValidCSharpNamespace(absl::string_view ns, std::string* error) {
  for (char c : ns) {
    if (kBannedNamespaceChars.contains(c)) {
      *error = absl::StrCat(
          "Invalid character in csharp_namespace option: character '",
          std::string(1, c),
          "' is not allowed because it could enable code injection.");
      return false;
    }
  }
  return true;
}

}  // namespace

Generator::Generator() = default;
Generator::~Generator() = default;

uint64_t Generator::GetSupportedFeatures() const {
  return CodeGenerator::Feature::FEATURE_PROTO3_OPTIONAL |
         CodeGenerator::Feature::FEATURE_SUPPORTS_EDITIONS;
}

void GenerateFile(const FileDescriptor* file, io::Printer* printer,
                  const Options* options) {
  ReflectionClassGenerator reflectionClassGenerator(file, options);
  reflectionClassGenerator.Generate(printer);
}

bool Generator::Generate(const FileDescriptor* file,
                         const std::string& parameter,
                         GeneratorContext* generator_context,
                         std::string* error) const {
  std::vector<std::pair<std::string, std::string> > options;
  ParseGeneratorParameter(parameter, &options);

  struct Options cli_options;

  for (size_t i = 0; i < options.size(); i++) {
    if (options[i].first == "file_extension") {
      cli_options.file_extension = options[i].second;
    } else if (options[i].first == "base_namespace") {
      cli_options.base_namespace = options[i].second;
      cli_options.base_namespace_specified = true;
    } else if (options[i].first == "internal_access") {
      cli_options.internal_access = true;
    } else if (options[i].first == "serializable") {
      cli_options.serializable = true;
    } else if (options[i].first == "experimental_strip_nonfunctional_codegen") {
      cli_options.strip_nonfunctional_codegen = true;
    } else {
      *error = absl::StrCat("Unknown generator option: ", options[i].first);
      return false;
    }
  }

  if (file->options().has_csharp_namespace()) {
    if (!IsValidCSharpNamespace(file->options().csharp_namespace(), error)) {
      return false;
    }
  }

  std::string filename_error = "";
  std::string filename = GetOutputFile(file,
      cli_options.file_extension,
      cli_options.base_namespace_specified,
      cli_options.base_namespace,
      &filename_error);

  if (filename.empty()) {
    *error = filename_error;
    return false;
  }
  std::unique_ptr<io::ZeroCopyOutputStream> output(
      generator_context->Open(filename));
  io::Printer printer(output.get(), '$');

  GenerateFile(file, &printer, &cli_options);

  return true;
}

}  // namespace csharp
}  // namespace compiler
}  // namespace protobuf
}  // namespace google
