Repository: square/okhttp
File: okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt (Kotlin)

Code before:

          if (!buffer.isProbablyUtf8()) {
            logger.log("")
            logger.log("<-- END HTTP (${totalMs}ms, binary ${buffer.size}-byte body omitted)")
            return response
          }

          if (contentLength != 0L) {
            logger.log("")
            logger.log(buffer.clone().readString(charset))
          }

          logger.log(
            buildString {
              append("<-- END HTTP (${totalMs}ms, ${buffer.size}-byte")
              if (gzippedLength != null) append(", $gzippedLength-gzipped-byte")
              append(" body)")
            },
          )
        }
      }

      return response
    }

    private fun sanitizeUrl(url: HttpUrl): String {
      if (queryParamsNameToRedact.isEmpty()) {
        return url.toString()
      }
      val params = ArrayList<Pair<String, String>>()
      for (parameterName in url.queryParameterNames) {
        params.add(
          Pair(
            first = parameterName,
            second = if (parameterName in queryParamsNameToRedact) "██" else url.queryParameter(parameterName) ?: "",
          ),
        )
      }
      val queryParamsBuilder = StringBuilder()
      val totalParam = params.size
      if (totalParam > 0) {
        queryParamsBuilder.append("?")
        params.forEachIndexed { index, param ->
          val suffix = if (index != totalParam - 1) "&" else ""
          queryParamsBuilder.append("${param.first}=${param.second}$suffix")
        }
      }

      return "${url.toString().substringBefore("?")}$queryParamsBuilder"
    }


Review comment (security):
Actually, I think this needs rework.

queryParameterNames is a unique Set.

Maybe something like

```
    private fun sanitizeUrl(url: HttpUrl): String {
      if (queryParamsNameToRedact.isEmpty() || url.querySize == 0) {
        return url.toString()
      }

      val newUrl = url.newBuilder().query(null).apply {
        for (i in 0 until url.querySize) {
          val parameterName = url.queryParameterName(i)
          val newValue = if (parameterName in queryParamsNameToRedact) "██" else url.queryParameterValue(i)

          addEncodedQueryParameter(parameterName, newValue)
        }
      }.toString()
    }
```

Code after:

          if (!buffer.isProbablyUtf8()) {
            logger.log("")
            logger.log("<-- END HTTP (${totalMs}ms, binary ${buffer.size}-byte body omitted)")
            return response
          }

          if (contentLength != 0L) {
            logger.log("")
            logger.log(buffer.clone().readString(charset))
          }

          logger.log(
            buildString {
              append("<-- END HTTP (${totalMs}ms, ${buffer.size}-byte")
              if (gzippedLength != null) append(", $gzippedLength-gzipped-byte")
              append(" body)")
            },
          )
        }
      }

      return response
    }

    internal fun redactUrl(url: HttpUrl): String {
      if (queryParamsNameToRedact.isEmpty() || url.querySize == 0) {
        return url.toString()
      }
      return url.newBuilder().query(null).apply {
        for (i in 0 until url.querySize) {
          val parameterName = url.queryParameterName(i)
          val newValue = if (parameterName in queryParamsNameToRedact) "██" else url.queryParameterValue(i)

          addEncodedQueryParameter(parameterName, newValue)
        }
      }.toString()
    }

    private fun logHeader(
      headers: Headers,
      i: Int,
    ) {
      val value = if (headers.name(i) in headersToRedact) "██" else headers.value(i)
      logger.log(headers.name(i) + ": " + value)
    }

    private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
      val contentEncoding = headers["Content-Encoding"] ?: return false
      return !contentEncoding.equals("identity", ignoreCase = true) &&
        !contentEncoding.equals("gzip", ignoreCase = true)