001package io.prometheus.metrics.exporter.common;
002
003import io.prometheus.metrics.annotations.StableApi;
004import io.prometheus.metrics.model.registry.PrometheusScrapeRequest;
005import java.io.UnsupportedEncodingException;
006import java.util.ArrayList;
007import java.util.Enumeration;
008import javax.annotation.Nullable;
009
010@StableApi
011public interface PrometheusHttpRequest extends PrometheusScrapeRequest {
012
013  /** See {@code jakarta.servlet.http.HttpServletRequest.getQueryString()} */
014  String getQueryString();
015
016  /** See {@code jakarta.servlet.http.HttpServletRequest.getHeaders(String)} */
017  Enumeration<String> getHeaders(String name);
018
019  /** See {@code jakarta.servlet.http.HttpServletRequest.getMethod()} */
020  String getMethod();
021
022  /** See {@code jakarta.servlet.http.HttpServletRequest.getHeader(String)} */
023  @Nullable
024  default String getHeader(String name) {
025    Enumeration<String> headers = getHeaders(name);
026    if (headers == null || !headers.hasMoreElements()) {
027      return null;
028    } else {
029      return headers.nextElement();
030    }
031  }
032
033  /** See {@code jakarta.servlet.ServletRequest.getParameter(String)} */
034  @Nullable
035  default String getParameter(String name) {
036    String[] values = getParameterValues(name);
037    if (values == null || values.length == 0) {
038      return null;
039    } else {
040      return values[0];
041    }
042  }
043
044  /**
045   * See {@code jakarta.servlet.ServletRequest.getParameterValues(String)}.
046   *
047   * <p>For safety, the default implementation applies two fixed limits: {@code maxQueryStringLength
048   * = 64 * 1024} (65,536 characters) and {@code maxQueryParameterCount = 1024} ({@code &}-separated
049   * parameter pairs). These implementation values are not exposed as runtime configuration.
050   * Requests that exceed either limit or contain invalid percent-encoding are rejected by the
051   * scrape handler with HTTP {@code 400 Bad Request}.
052   */
053  @Override
054  @Nullable
055  // decode with Charset is only available in Java 10+, but we want to support Java 8
056  @SuppressWarnings("JdkObsolete")
057  default String[] getParameterValues(String name) {
058    try {
059      int maxQueryStringLength = 64 * 1024;
060      int maxQueryParameterCount = 1024;
061      ArrayList<String> result = new ArrayList<>();
062      String queryString = getQueryString();
063      if (queryString != null) {
064        if (queryString.length() > maxQueryStringLength) {
065          throw new InvalidQueryParameterException(
066              "Query string too long: "
067                  + queryString.length()
068                  + " characters (max "
069                  + maxQueryStringLength
070                  + ")");
071        }
072        int start = 0;
073        int parameterCount = 0;
074        for (int end = queryString.indexOf('&'); start <= queryString.length(); ) {
075          parameterCount++;
076          if (parameterCount > maxQueryParameterCount) {
077            throw new InvalidQueryParameterException(
078                "Too many query parameters: "
079                    + parameterCount
080                    + " (max "
081                    + maxQueryParameterCount
082                    + ")");
083          }
084          String pair =
085              end == -1 ? queryString.substring(start) : queryString.substring(start, end);
086          int idx = pair.indexOf("=");
087          if (idx != -1
088              && InvalidQueryParameterException.urlDecode(pair.substring(0, idx)).equals(name)) {
089            result.add(InvalidQueryParameterException.urlDecode(pair.substring(idx + 1)));
090          }
091          if (end == -1) {
092            break;
093          }
094          start = end + 1;
095          end = queryString.indexOf('&', start);
096        }
097      }
098      if (result.isEmpty()) {
099        // Servlet API: getParameterValues() returns null if the parameter does not exist.
100        return null;
101      } else {
102        return result.toArray(new String[0]);
103      }
104    } catch (UnsupportedEncodingException e) {
105      // UTF-8 encoding not supported.
106      throw new RuntimeException(e);
107    }
108  }
109}