<commit_before>package de.gesellix.docker.client

import de.gesellix.docker.client.protocolhandler.DockerContentHandlerFactory
import de.gesellix.docker.client.protocolhandler.DockerURLHandler
import de.gesellix.docker.client.protocolhandler.RawInputStream
import de.gesellix.socketfactory.https.KeyStoreUtil
import groovy.json.JsonBuilder
import org.apache.commons.io.IOUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import javax.net.ssl.*
import java.nio.charset.Charset
import java.util.regex.Pattern

import static de.gesellix.socketfactory.https.KeyStoreUtil.getKEY_STORE_PASSWORD
import static javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm

// Proof-of-concept for https://docs.docker.com/reference/api/docker_remote_api_v1.17/#attach-to-a-container
class LowLevelDockerClient {

  def Logger logger = LoggerFactory.getLogger(LowLevelDockerClient)

  ContentHandlerFactory contentHandlerFactory
  DockerURLHandler dockerURLHandler

  def dockerHost
  URL dockerHostUrl

  def sslSocketFactory

  LowLevelDockerClient() {
    dockerURLHandler = new DockerURLHandler()
    contentHandlerFactory = new DockerContentHandlerFactory()
    dockerHost = "http://127.0.0.1:2375"
    sslSocketFactory = null
  }

  def getDockerBaseUrl() {
    if (!getDockerHostUrl()) {
      dockerURLHandler.dockerHost = getDockerHost()
      dockerHostUrl = dockerURLHandler.getURL()
    }
    return dockerHostUrl
  }

  def get(requestConfig) {
    def config = ensureValidRequestConfig(requestConfig)
    config.method = "GET"
    return request(config)
  }

  def post(requestConfig) {
    def config = ensureValidRequestConfig(requestConfig)
    config.method = "POST"
    return request(config)
  }

  def request(config) {
    config = ensureValidRequestConfig(config)

    HttpURLConnection connection = openConnection(config)
    configureConnection(connection, config)

    // since we listen to a stream we disable the timeout
//    connection.setConnectTimeout(0)
//    connection.setReadTimeout(0)

    if (config.body) {
      byte[] postData
      int postDataLength
      switch (config.contentType) {
        case "application/json":
          def json = new JsonBuilder()
          json config.body
          def bodyAsString = json.toString()
          postData = bodyAsString.getBytes(Charset.forName("UTF-8"))
          postDataLength = postData.length
          break
        default:
          postData = config.body.toString().getBytes(Charset.forName("UTF-8"))
          postDataLength = postData.length
          break
      }

      connection.setDoOutput(true)
      connection.setDoInput(true)
      connection.setInstanceFollowRedirects(false)

      connection.setRequestProperty("Content-Type", config.contentType as String)
      connection.setRequestProperty("charset", "utf-8")
      connection.setRequestProperty("Content-Length", Integer.toString(postDataLength))
      IOUtils.copy(new ByteArrayInputStream(postData), connection.getOutputStream())
    }

    def response = handleResponse(connection, config)
    return response
  }

  def handleResponse(HttpURLConnection connection, config) {
    def response = readHeaders(connection)

    def contentHandler = contentHandlerFactory.createContentHandler(response.mimeType)
    if (contentHandler == null) {
      logger.warn("couldn't find a specific ContentHandler for '${response.contentType}'.")
      if (config.stdout) {
        logger.debug("redirecting to stdout.")
        IOUtils.copy(response.stream as InputStream, config.stdout as OutputStream)
        response.stream = null
      }
    }
    else {
      def content = contentHandler.getContent(connection)

      switch (response.mimeType) {
        case "application/vnd.docker.raw-stream":
          InputStream rawStream = content as RawInputStream
          response.stream = rawStream
          config.stdout = config.stdout ?: System.out
          IOUtils.copy(rawStream as InputStream, config.stdout as OutputStream)
          break
        case "application/json":
        case "text/html":
        case "text/plain":
          if (content instanceof InputStream) {
            if (config.stdout) {
              IOUtils.copy(content as InputStream, config.stdout as OutputStream)
              response.stream = null
            }
            else if (response.contentLength) {
              response.content = IOUtils.toString(content as InputStream)
              response.stream = null
            }
            else {
              response.stream = content as InputStream
            }
          }
          else {
            response.content = content
            response.stream = null
          }
          break
        default:
          logger.warn("unexpected mime type '${response.mimeType}'.")
          if (content instanceof InputStream) {
            logger.debug("passing through via `response.stream`.")
            if (config.stdout) {
              IOUtils.copy(content as InputStream, config.stdout as OutputStream)
              response.stream = null
            }
            else {
              response.stream = content as InputStream
            }
          }
          else {
            logger.debug("passing through via `response.content`.")
            response.content = content
            response.stream = null
          }
          break
      }
    }

    return response
  }

  def readHeaders(HttpURLConnection connection) {
    def statusLine = connection.headerFields[null]
    logger.info("status: ${statusLine}")

    def headers = connection.headerFields.findAll { key, value ->
      key != null
    }.collectEntries { key, value ->
      [key.toLowerCase(), value]
    }
    logger.debug("headers: ${headers}")

    def contentType = headers['content-type']?.first()
    logger.debug("content-type: ${contentType}")
    def contentLength = headers['content-length']?.first()
    logger.debug("content-length: ${contentLength}")
    def mimeType = getMimeType(contentType)
    logger.debug("mime type: ${mimeType}")

    def response = [
        statusLine   : [
            text: statusLine,
            code: connection.responseCode
        ],
        headers      : headers,
        contentType  : contentType,
        mimeType     : mimeType,
        contentLength: contentLength,
        stream       : connection.inputStream
    ]
    return response
  }

  def ensureValidRequestConfig(config) {
    def validConfig = config
    if (config instanceof String) {
      validConfig = [path: config]
    }
    if (!validConfig?.path) {
      logger.error("bad request config: ${config}")
      throw new IllegalArgumentException("bad request config")
    }
    return validConfig<commit_msg>refactor<commit_after>package de.gesellix.docker.client

import de.gesellix.docker.client.protocolhandler.DockerContentHandlerFactory
import de.gesellix.docker.client.protocolhandler.DockerURLHandler
import de.gesellix.docker.client.protocolhandler.RawInputStream
import de.gesellix.socketfactory.https.KeyStoreUtil
import groovy.json.JsonBuilder
import org.apache.commons.io.IOUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import javax.net.ssl.*
import java.nio.charset.Charset
import java.util.regex.Pattern

import static de.gesellix.socketfactory.https.KeyStoreUtil.getKEY_STORE_PASSWORD
import static javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm

// Proof-of-concept for https://docs.docker.com/reference/api/docker_remote_api_v1.17/#attach-to-a-container
class LowLevelDockerClient {

  def Logger logger = LoggerFactory.getLogger(LowLevelDockerClient)

  ContentHandlerFactory contentHandlerFactory
  DockerURLHandler dockerURLHandler

  def dockerHost
  URL dockerHostUrl

  def sslSocketFactory

  LowLevelDockerClient() {
    dockerURLHandler = new DockerURLHandler()
    contentHandlerFactory = new DockerContentHandlerFactory()
    dockerHost = "http://127.0.0.1:2375"
    sslSocketFactory = null
  }

  def getDockerBaseUrl() {
    if (!getDockerHostUrl()) {
      dockerURLHandler.dockerHost = getDockerHost()
      dockerHostUrl = dockerURLHandler.getURL()
    }
    return dockerHostUrl
  }

  def get(requestConfig) {
    def config = ensureValidRequestConfig(requestConfig)
    config.method = "GET"
    return request(config)
  }

  def post(requestConfig) {
    def config = ensureValidRequestConfig(requestConfig)
    config.method = "POST"
    return request(config)
  }

  def request(config) {
    config = ensureValidRequestConfig(config)

    HttpURLConnection connection = openConnection(config)
    configureConnection(connection, config)

    // since we listen to a stream we disable the timeout
//    connection.setConnectTimeout(0)
//    connection.setReadTimeout(0)

    if (config.body) {
      byte[] postData
      int postDataLength
      switch (config.contentType) {
        case "application/json":
          def json = new JsonBuilder()
          json config.body
          def bodyAsString = json.toString()
          postData = bodyAsString.getBytes(Charset.forName("UTF-8"))
          postDataLength = postData.length
          break
        default:
          postData = config.body.toString().getBytes(Charset.forName("UTF-8"))
          postDataLength = postData.length
          break
      }

      connection.setDoOutput(true)
      connection.setDoInput(true)
      connection.setInstanceFollowRedirects(false)

      connection.setRequestProperty("Content-Type", config.contentType as String)
      connection.setRequestProperty("charset", "utf-8")
      connection.setRequestProperty("Content-Length", Integer.toString(postDataLength))
      IOUtils.copy(new ByteArrayInputStream(postData), connection.getOutputStream())
    }

    def response = handleResponse(connection, config)
    return response
  }

  def handleResponse(HttpURLConnection connection, config) {
    def response = readHeaders(connection)

    def contentHandler = contentHandlerFactory.createContentHandler(response.mimeType)
    if (contentHandler == null) {
      logger.warn("couldn't find a specific ContentHandler for '${response.contentType}'.")
      if (config.stdout) {
        logger.debug("redirecting to stdout.")
        IOUtils.copy(response.stream as InputStream, config.stdout as OutputStream)
        response.stream = null
      }
    }
    else {
      def content = contentHandler.getContent(connection)

      switch (response.mimeType) {
        case "application/vnd.docker.raw-stream":
          InputStream rawStream = content as RawInputStream
          response.stream = rawStream
          config.stdout = config.stdout ?: System.out
          IOUtils.copy(rawStream as InputStream, config.stdout as OutputStream)
          break
        case "application/json":
          consumeResponseBody(response, content, config)
          break
        case "text/html":
          consumeResponseBody(response, content, config)
          break
        case "text/plain":
          consumeResponseBody(response, content, config)
          break
        default:
          logger.warn("unexpected mime type '${response.mimeType}'.")
          if (content instanceof InputStream) {
            logger.debug("passing through via `response.stream`.")
            if (config.stdout) {
              IOUtils.copy(content as InputStream, config.stdout as OutputStream)
              response.stream = null
            }
            else {
              response.stream = content as InputStream
            }
          }
          else {
            logger.debug("passing through via `response.content`.")
            response.content = content
            response.stream = null
          }
          break
      }
    }

    return response
  }

  def readHeaders(HttpURLConnection connection) {
    def statusLine = connection.headerFields[null]
    logger.info("status: ${statusLine}")

    def headers = connection.headerFields.findAll { key, value ->
      key != null
    }.collectEntries { key, value ->
      [key.toLowerCase(), value]
    }
    logger.debug("headers: ${headers}")

    def contentType = headers['content-type']?.first()
    logger.debug("content-type: ${contentType}")
    def contentLength = headers['content-length']?.first()
    logger.debug("content-length: ${contentLength}")
    def mimeType = getMimeType(contentType)
    logger.debug("mime type: ${mimeType}")

    def response = [
        statusLine   : [
            text: statusLine,
            code: connection.responseCode
        ],
        headers      : headers,
        contentType  : contentType,
        mimeType     : mimeType,
        contentLength: contentLength,
        stream       : connection.inputStream
    ]
    return response
  }

  def consumeResponseBody(response, content, config) {
    if (content instanceof InputStream) {
      if (config.stdout) {
        IOUtils.copy(content as InputStream, config.stdout as OutputStream)
        response.stream = null
      }
      else if (response.contentLength) {
        response.content = IOUtils.toString(content as InputStream)
        response.stream = null
      }
      else {
        response.stream = content as InputStream
      }
    }
    else {
      response.content = content
      response.stream = null
    }
  }

  def ensureValidRequestConfig(config) {
    def validConfig = config
    if (config instanceof String) {
      validConfig = [path: config]
    }
    if (!validConfig?.path) {
      logger.error("bad request config: ${config}")
      throw new IllegalArgumentException("bad request config")
    }
    return validConfig
{'fraction_non_alphanumeric': 0.0915918126932705, 'fraction_numerical': 0.0029450743631276688, 'mean_word_length': 2.736726272352132, 'pattern_counts': {'":': 10, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5045107', 'n_tokens_mistral': 3666, 'n_tokens_neox': 3583, 'n_words': 772}