GUACAMOLE-1174: Merge support for Kubernetes "exec" API call.

diff --git a/src/protocols/kubernetes/kubernetes.c b/src/protocols/kubernetes/kubernetes.c
index 1998d13..a0ed7ea 100644
--- a/src/protocols/kubernetes/kubernetes.c
+++ b/src/protocols/kubernetes/kubernetes.c
@@ -213,10 +213,11 @@
     }
 
     /* Generate endpoint for attachment URL */
-    if (guac_kubernetes_endpoint_attach(endpoint_path, sizeof(endpoint_path),
+    if (guac_kubernetes_endpoint_uri(endpoint_path, sizeof(endpoint_path),
                 settings->kubernetes_namespace,
                 settings->kubernetes_pod,
-                settings->kubernetes_container)) {
+                settings->kubernetes_container,
+                settings->exec_command)) {
         guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR,
                 "Unable to generate path for Kubernetes API endpoint: "
                 "Resulting path too long");
diff --git a/src/protocols/kubernetes/settings.c b/src/protocols/kubernetes/settings.c
index 3421b1e..1d437bc 100644
--- a/src/protocols/kubernetes/settings.c
+++ b/src/protocols/kubernetes/settings.c
@@ -31,6 +31,7 @@
     "namespace",
     "pod",
     "container",
+    "exec-command",
     "use-ssl",
     "client-cert",
     "client-key",
@@ -87,6 +88,11 @@
     IDX_CONTAINER,
 
     /**
+     * The command used by exec call. If omitted, attach call will be used.
+     */
+    IDX_EXEC_COMMAND,
+
+    /**
      * Whether SSL/TLS should be used. If omitted, SSL/TLS will not be used.
      */
     IDX_USE_SSL,
@@ -275,6 +281,11 @@
         guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
                 IDX_CONTAINER, NULL);
 
+    /* Read exec command (optional) */
+    settings->exec_command =
+        guac_user_parse_args_string(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
+                IDX_EXEC_COMMAND, NULL);
+
     /* Parse whether SSL should be used */
     settings->use_ssl =
         guac_user_parse_args_boolean(user, GUAC_KUBERNETES_CLIENT_ARGS, argv,
@@ -406,6 +417,9 @@
     free(settings->kubernetes_pod);
     free(settings->kubernetes_container);
 
+    /* Free Kubernetes exec command */
+    free(settings->exec_command);
+
     /* Free SSL/TLS details */
     free(settings->client_cert);
     free(settings->client_key);
diff --git a/src/protocols/kubernetes/settings.h b/src/protocols/kubernetes/settings.h
index eef4973..1ad5805 100644
--- a/src/protocols/kubernetes/settings.h
+++ b/src/protocols/kubernetes/settings.h
@@ -98,6 +98,12 @@
     char* kubernetes_container;
 
     /**
+     * The command to generate api endpoint for call exec. 
+     * If omitted call attach will be used.
+     */
+    char* exec_command;
+
+    /**
      * Whether SSL/TLS should be used.
      */
     bool use_ssl;
diff --git a/src/protocols/kubernetes/url.c b/src/protocols/kubernetes/url.c
index 78c116e..4bca015 100644
--- a/src/protocols/kubernetes/url.c
+++ b/src/protocols/kubernetes/url.c
@@ -89,15 +89,56 @@
 
 }
 
-int guac_kubernetes_endpoint_attach(char* buffer, int length,
-        const char* kubernetes_namespace, const char* kubernetes_pod,
-        const char* kubernetes_container) {
+int guac_kubernetes_append_endpoint_param(char* buffer, int length, 
+        const char* param_name, const char* param_value) {
 
+    char escaped_param_value[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
+
+    /* Escape value */
+    if (guac_kubernetes_escape_url_component(escaped_param_value,
+                    sizeof(escaped_param_value), param_value))
+            return 1;
+    
+    char* str = buffer;
+
+    int str_len = 0;
+    int qmark = 0;
+
+    while (*str != '\0') {
+
+        /* Look for a question mark */
+        if (*str=='?') qmark = 1;
+
+        /* Compute the buffer string length */
+        str_len++;
+
+        /* Verify the buffer null terminated */
+        if (str_len >= length) return 1;
+
+        /* Next character */
+        str++;
+    }
+
+    /* Determine the parameter delimiter */
+    char delimiter = '?';
+    if (qmark) delimiter = '&';
+
+    /* Write the parameter to the buffer */
     int written;
+    written = snprintf(buffer + str_len, length - str_len,
+            "%c%s=%s", delimiter, param_name, escaped_param_value);
+
+    /* The parameter was successfully added if it was written to the given
+     * buffer without truncation */
+    return (written < 0 || written >= length);
+}
+
+int guac_kubernetes_endpoint_uri(char* buffer, int length,
+        const char* kubernetes_namespace, const char* kubernetes_pod,
+        const char* kubernetes_container, const char* exec_command) {
 
     char escaped_namespace[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
     char escaped_pod[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
-    char escaped_container[GUAC_KUBERNETES_MAX_ENDPOINT_LENGTH];
 
     /* Escape Kubernetes namespace */
     if (guac_kubernetes_escape_url_component(escaped_namespace,
@@ -109,29 +150,38 @@
                 sizeof(escaped_pod), kubernetes_pod))
         return 1;
 
-    /* Generate attachment endpoint URL */
-    if (kubernetes_container != NULL) {
+    /* Determine the call type */
+    char* call = "attach";
+    if (exec_command != NULL)
+        call = "exec";
 
-        /* Escape container name */
-        if (guac_kubernetes_escape_url_component(escaped_container,
-                    sizeof(escaped_container), kubernetes_container))
-            return 1;
+    int written;
 
-        written = snprintf(buffer, length,
-                "/api/v1/namespaces/%s/pods/%s/attach"
-                "?container=%s&stdin=true&stdout=true&tty=true",
-                escaped_namespace, escaped_pod, escaped_container);
-    }
-    else {
-        written = snprintf(buffer, length,
-                "/api/v1/namespaces/%s/pods/%s/attach"
-                "?stdin=true&stdout=true&tty=true",
-                escaped_namespace, escaped_pod);
-    }
+    /* Generate the endpoint path and write to the buffer */
+    written = snprintf(buffer, length,
+        "/api/v1/namespaces/%s/pods/%s/%s", escaped_namespace, escaped_pod, call);
 
-    /* Endpoint URL was successfully generated if it was written to the given
+    /* Operation successful if the endpoint path was written to the given
      * buffer without truncation */
-    return !(written < length - 1);
+    if (written < 0 || written >= length)
+        return 1;
 
+    /* Append exec command parameter */
+    if (exec_command != NULL) {
+        if (guac_kubernetes_append_endpoint_param(buffer,
+                    length, "command", exec_command))
+            return 1;
+    }
+
+    /* Append kubernetes container parameter */
+    if (kubernetes_container != NULL) {
+        if (guac_kubernetes_append_endpoint_param(buffer,
+                    length, "container", kubernetes_container))
+            return 1;
+    }
+
+    /* Append stdin, stdout and tty parameters */
+    return (guac_kubernetes_append_endpoint_param(buffer, length, "stdin", "true"))
+        || (guac_kubernetes_append_endpoint_param(buffer, length, "stdout", "true"))
+        || (guac_kubernetes_append_endpoint_param(buffer, length, "tty", "true"));
 }
-
diff --git a/src/protocols/kubernetes/url.h b/src/protocols/kubernetes/url.h
index 285baa2..96e5098 100644
--- a/src/protocols/kubernetes/url.h
+++ b/src/protocols/kubernetes/url.h
@@ -50,6 +50,31 @@
         const char* str);
 
 /**
+ * Append the parameter to the endpoint path.
+ * Value within the path will be URL-escaped as necessary.
+ *
+ * @param buffer
+ *     The buffer which should receive the parameter. It could contain the endpoint path.
+ *     The parameter will be written to the end of the buffer.
+ *
+ * @param length
+ *     The number of bytes available in the given buffer.
+ *
+ * @param param_name
+ *     The name of the parameter.
+ *
+ * @param param_value
+ *     The value of the parameter.
+ *
+ * @return
+ *     Zero if the parameter was successfully attached to the buffer,
+ *     non-zero if insufficient space exists within the buffer or
+ *     buffer not null terminated.
+ */
+int guac_kubernetes_append_endpoint_param(char* buffer, int length,
+        const char* param_name, const char* param_value);
+
+/**
  * Generates the full path to the Kubernetes API endpoint which handles
  * attaching to running containers within specific pods. Values within the path
  * will be URL-escaped as necessary.
@@ -72,14 +97,18 @@
  * @param kubernetes_container
  *     The name of the container to attach to, or NULL to arbitrarily attach
  *     to the first container in the pod.
+ *  
+ * @param exec_command
+ *     The command used to run a new process and attach to it,
+ *     instead of the main container process.
  *
  * @return
  *     Zero if the endpoint path was successfully written to the provided
  *     buffer, non-zero if insufficient space exists within the buffer.
  */
-int guac_kubernetes_endpoint_attach(char* buffer, int length,
+int guac_kubernetes_endpoint_uri(char* buffer, int length,
         const char* kubernetes_namespace, const char* kubernetes_pod,
-        const char* kubernetes_container);
+        const char* kubernetes_container, const char* exec_command);
 
 #endif