/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.api.semconv.http;

import static io.opentelemetry.instrumentation.api.semconv.http.HeaderParsingHelper.notFound;
import static io.opentelemetry.instrumentation.api.semconv.http.HeaderParsingHelper.setPort;

import io.opentelemetry.instrumentation.api.semconv.network.internal.AddressAndPortExtractor;
import java.util.Locale;

final class ForwardedHostAddressAndPortExtractor<REQUEST>
    implements AddressAndPortExtractor<REQUEST> {

  private final HttpCommonAttributesGetter<REQUEST, ?> getter;

  ForwardedHostAddressAndPortExtractor(HttpCommonAttributesGetter<REQUEST, ?> getter) {
    this.getter = getter;
  }

  @Override
  public void extract(AddressPortSink sink, REQUEST request) {
    // try Forwarded
    for (String forwarded : getter.getHttpRequestHeader(request, "forwarded")) {
      if (extractFromForwardedHeader(sink, forwarded)) {
        return;
      }
    }

    // try X-Forwarded-Host
    for (String forwardedHost : getter.getHttpRequestHeader(request, "x-forwarded-host")) {
      if (extractHost(sink, forwardedHost, 0, forwardedHost.length())) {
        return;
      }
    }

    // try :authority (HTTP 2.0 pseudo-header)
    for (String host : getter.getHttpRequestHeader(request, ":authority")) {
      if (extractHost(sink, host, 0, host.length())) {
        return;
      }
    }

    // try Host
    for (String host : getter.getHttpRequestHeader(request, "host")) {
      if (extractHost(sink, host, 0, host.length())) {
        return;
      }
    }
  }

  private static boolean extractFromForwardedHeader(AddressPortSink sink, String forwarded) {
    int start = forwarded.toLowerCase(Locale.ROOT).indexOf("host=");
    if (start < 0) {
      return false;
    }
    start += "host=".length(); // start is now the index after host=
    if (start >= forwarded.length() - 1) { // the value after host= must not be empty
      return false;
    }
    // find the end of the `host=<address>` section
    int end = forwarded.indexOf(';', start);
    if (end < 0) {
      end = forwarded.length();
    }
    return extractHost(sink, forwarded, start, end);
  }

  private static boolean extractHost(AddressPortSink sink, String host, int start, int end) {
    if (start >= end) {
      return false;
    }

    // skip quotes
    if (host.charAt(start) == '"') {
      // try to find the end of the quote
      int quoteEnd = host.indexOf('"', start + 1);
      if (notFound(quoteEnd, end)) {
        // malformed header value
        return false;
      }
      return extractHost(sink, host, start + 1, quoteEnd);
    }

    int hostHeaderSeparator = host.indexOf(':', start);
    if (notFound(hostHeaderSeparator, end)) {
      sink.setAddress(host.substring(start, end));
    } else {
      sink.setAddress(host.substring(start, hostHeaderSeparator));
      setPort(sink, host, hostHeaderSeparator + 1, end);
    }

    return true;
  }
}
