import {
  createExecutorFumaDb
} from "./chunk-I3X6242Q.js";
import {
  OAUTH2_DEFAULT_TIMEOUT_MS,
  assertSupportedOAuthEndpointUrl
} from "./chunk-EIHWBY6T.js";
import "./chunk-7D4SUZUM.js";

// src/hosted-http-client.ts
import { Effect, Layer, Schema } from "effect";
import { FetchHttpClient } from "effect/unstable/http";
var HostedOutboundRequestBlocked = class extends Schema.TaggedErrorClass()(
  "HostedOutboundRequestBlocked",
  {
    url: Schema.String,
    reason: Schema.String
  }
) {
};
var parseIpv4 = (hostname) => {
  const parts = hostname.split(".");
  if (parts.length !== 4) return null;
  const parsed = [];
  for (const part of parts) {
    if (!/^\d+$/.test(part)) return null;
    const value = Number(part);
    if (!Number.isInteger(value) || value < 0 || value > 255) return null;
    parsed.push(value);
  }
  return parsed;
};
var parseIpv4MappedIpv6 = (hostname) => {
  const prefix = "::ffff:";
  if (!hostname.startsWith(prefix)) return null;
  const embedded = hostname.slice(prefix.length);
  const dotted = parseIpv4(embedded);
  if (dotted) return dotted;
  const parts = embedded.split(":");
  if (parts.length !== 2) return null;
  const words = parts.map((part) => Number.parseInt(part, 16));
  if (words.some(
    (word, index) => parts[index] === "" || !/^[0-9a-f]+$/i.test(parts[index]) || !Number.isInteger(word) || word < 0 || word > 65535
  )) {
    return null;
  }
  const [high, low] = words;
  return [high >> 8, high & 255, low >> 8, low & 255];
};
var isPrivateIpv4 = ([a, b]) => a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
var isBlockedMetadataHostname = (hostname) => {
  const normalized = hostname.toLowerCase();
  return normalized === "metadata.google.internal" || normalized === "metadata" || normalized === "instance-data" || normalized === "169.254.169.254";
};
var isLocalOrPrivateHostname = (hostname) => {
  const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
  if (normalized === "localhost" || normalized.endsWith(".localhost")) return true;
  const ipv4 = parseIpv4(normalized);
  if (ipv4) return isPrivateIpv4(ipv4);
  const mappedIpv4 = parseIpv4MappedIpv6(normalized);
  if (mappedIpv4) return isPrivateIpv4(mappedIpv4);
  return normalized === "::1" || normalized.startsWith("fe80:") || normalized.startsWith("fc") || normalized.startsWith("fd");
};
var validateHostedOutboundUrl = (value, options = {}) => Effect.gen(function* () {
  const url = yield* Effect.try({
    try: () => new URL(value),
    catch: () => new HostedOutboundRequestBlocked({
      url: value,
      reason: "URL is invalid"
    })
  });
  if (url.protocol !== "http:" && url.protocol !== "https:") {
    return yield* new HostedOutboundRequestBlocked({
      url: value,
      reason: "Only HTTP and HTTPS outbound requests are allowed"
    });
  }
  if (isBlockedMetadataHostname(url.hostname)) {
    return yield* new HostedOutboundRequestBlocked({
      url: value,
      reason: "Metadata service addresses are not allowed"
    });
  }
  if (!options.allowLocalNetwork && isLocalOrPrivateHostname(url.hostname)) {
    return yield* new HostedOutboundRequestBlocked({
      url: value,
      reason: "Local and private network addresses are not allowed"
    });
  }
});
var CREDENTIAL_HEADERS = ["authorization", "proxy-authorization", "cookie"];
var stripCredentialHeaders = (init) => {
  const headers = new Headers(init?.headers);
  for (const name of CREDENTIAL_HEADERS) headers.delete(name);
  return { ...init, headers };
};
var guardFetch = (underlying, options) => (async (input, init) => {
  const maxRedirects = options.maxRedirects ?? 10;
  let current = input;
  let currentInit = init;
  for (let redirects = 0; redirects <= maxRedirects; redirects++) {
    const url = current instanceof Request ? current.url : String(current);
    Effect.runSync(validateHostedOutboundUrl(url, options));
    const response = await underlying(current, {
      ...currentInit,
      redirect: "manual"
    });
    if (response.status >= 300 && response.status < 400 && response.headers.has("location") && redirects < maxRedirects) {
      const next = new URL(response.headers.get("location"), url);
      if (next.origin !== new URL(url).origin) {
        currentInit = stripCredentialHeaders(currentInit);
      }
      current = next.toString();
      continue;
    }
    return response;
  }
  return await underlying(current, { ...currentInit, redirect: "manual" });
});
var makeHostedHttpClientLayer = (options = {}) => FetchHttpClient.layer.pipe(
  Layer.provide(
    options.fetch ? Layer.succeed(FetchHttpClient.Fetch)(guardFetch(options.fetch, options)) : Layer.effect(
      FetchHttpClient.Fetch,
      Effect.map(
        Effect.service(FetchHttpClient.Fetch),
        (underlying) => guardFetch(underlying, options)
      )
    )
  )
);
export {
  HostedOutboundRequestBlocked,
  OAUTH2_DEFAULT_TIMEOUT_MS,
  assertSupportedOAuthEndpointUrl,
  createExecutorFumaDb,
  makeHostedHttpClientLayer
};
//# sourceMappingURL=host-internal.js.map