GUACAMOLE-422: Change handshake to ignore order of opcodes.
diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h
index adc4c4e..137553a 100644
--- a/src/libguac/guacamole/user.h
+++ b/src/libguac/guacamole/user.h
@@ -506,6 +506,79 @@
 };
 
 /**
+ * Handler for Guacamole protocol opcode specific to the handshake that
+ * happens between client and server at the beginning of the connection.  The
+ * handler will be invoked when the matching opcode is received during the
+ * handshake process.
+ * 
+ * @param user
+ *     The user that initiated the handshake.
+ * 
+ * @param parser
+ *     The parser allocated for parsing the data provided by the client.
+ * 
+ * @param timeout
+ *     The timeout, in microseconds, for parsing the value.
+ * 
+ * @return
+ *     Zero if the handshake instruction is successfully parsed; otherwise
+ *     false.
+ */
+typedef int __guac_handshake_handler(guac_user* user, int argc, char** argv);
+
+/**
+ * Structure that maps opcodes received during the handshake phase of the
+ * connection to callback functions used when those opcodes are received.
+ */
+typedef struct __guac_handshake_mapping {
+    
+    /**
+     * The instruction opcode which maps to the handler.
+     */
+    char* opcode;
+    
+    /**
+     * The handler function used when specified opcode is received.
+     */
+    __guac_handshake_handler* handler;
+    
+} __guac_handshake_mapping;
+
+/**
+ * Internal handler function that is called when the size instruction is
+ * received during the handshake process.
+ */
+__guac_handshake_handler __guac_handshake_size_handler;
+
+/**
+ * Internal handler function that is called when the audio instruction is
+ * received during the handshake process, specifying the audio mimetypes
+ * available to the client.
+ */
+__guac_handshake_handler __guac_handshake_audio_handler;
+
+/**
+ * Internal handler function that is called when the video instruction is
+ * received during the handshake process, specifying the video mimetypes
+ * available to the client.
+ */
+__guac_handshake_handler __guac_handshake_video_handler;
+
+/**
+ * Internal handler function that is called when the image instruction is
+ * received during the handshake process, specifying the image mimetypes
+ * available to the client.
+ */
+__guac_handshake_handler __guac_handshake_image_handler;
+
+/**
+ * Internal handler function that is called when the timezone instruction is
+ * received during the handshake process, specifying the timezone of the
+ * client.
+ */
+__guac_handshake_handler __guac_handshake_timezone_handler;
+
+/**
  * Allocates a new, blank user, not associated with any specific client or
  * socket.
  *
diff --git a/src/libguac/user-handshake.c b/src/libguac/user-handshake.c
index e514392..a9cf003 100644
--- a/src/libguac/user-handshake.c
+++ b/src/libguac/user-handshake.c
@@ -171,6 +171,80 @@
 
 }
 
+/* Guacamole handshake handler functions. */
+
+int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) {
+    
+    /* Validate size of instruction. */
+    if (argc < 2) {
+        guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" "
+                "instruction lacked required arguments.");
+        return 1;
+    }
+    
+    /* Parse optimal screen dimensions from size instruction */
+    user->info.optimal_width  = atoi(argv[0]);
+    user->info.optimal_height = atoi(argv[1]);
+
+    /* If DPI given, set the user resolution */
+    if (argc >= 3)
+        user->info.optimal_resolution = atoi(argv[2]);
+
+    /* Otherwise, use a safe default for rough backwards compatibility */
+    else
+        user->info.optimal_resolution = 96;
+    
+    return 0;
+    
+}
+
+int __guac_handshake_audio_handler(guac_user* user, int argc, char** argv) {
+    
+    /* Store audio mimetypes */
+    user->info.audio_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
+    
+    return 0;
+    
+}
+
+int __guac_handshake_video_handler(guac_user* user, int argc, char** argv) {
+    
+    /* Store video mimetypes */
+    user->info.video_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
+    
+    return 0;
+    
+}
+
+int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
+    
+    /* Store image mimetypes */
+    user->info.image_mimetypes = (const char**) guac_copy_mimetypes(argv, argc);
+    
+    return 0;
+    
+}
+
+int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
+    
+    /* Store timezone, if present */
+    if (argc > 0 && strcmp(argv[0], ""))
+        user->info.timezone = (const char*) strdup(argv[0]);
+    
+    return 0;
+    
+}
+
+/* Guacamole handshake handler mappings. */
+__guac_handshake_mapping __guac_handshake_map[] = {
+    {"size",     __guac_handshake_size_handler},
+    {"audio",    __guac_handshake_audio_handler},
+    {"video",    __guac_handshake_video_handler},
+    {"image",    __guac_handshake_image_handler},
+    {"timezone", __guac_handshake_timezone_handler},
+    {NULL,       NULL}
+};
+
 /**
  * The thread which handles all user input, calling event handlers for received
  * instructions.
@@ -305,122 +379,53 @@
 
     guac_parser* parser = guac_parser_alloc();
 
-    /* Get optimal screen size */
-    if (guac_parser_expect(parser, socket, usec_timeout, "size")) {
-
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"size\"");
-
-        guac_parser_free(parser);
-        return 1;
-    }
-
-    /* Validate content of size instruction */
-    if (parser->argc < 2) {
-        guac_user_log(user, GUAC_LOG_ERROR, "Received \"size\" "
-                "instruction lacked required arguments.");
-        guac_parser_free(parser);
-        return 1;
-    }
-
-    /* Parse optimal screen dimensions from size instruction */
-    user->info.optimal_width  = atoi(parser->argv[0]);
-    user->info.optimal_height = atoi(parser->argv[1]);
-
-    /* If DPI given, set the user resolution */
-    if (parser->argc >= 3)
-        user->info.optimal_resolution = atoi(parser->argv[2]);
-
-    /* Otherwise, use a safe default for rough backwards compatibility */
-    else
-        user->info.optimal_resolution = 96;
-
-    /* Get supported audio formats */
-    if (guac_parser_expect(parser, socket, usec_timeout, "audio")) {
-
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"audio\"");
-
-        guac_parser_free(parser);
-        return 1;
-    }
-
-    /* Store audio mimetypes */
-    char** audio_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
-    user->info.audio_mimetypes = (const char**) audio_mimetypes;
-
-    /* Get supported video formats */
-    if (guac_parser_expect(parser, socket, usec_timeout, "video")) {
-
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"video\"");
-
-        guac_parser_free(parser);
-        return 1;
-    }
-
-    /* Store video mimetypes */
-    char** video_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
-    user->info.video_mimetypes = (const char**) video_mimetypes;
-
-    /* Get supported image formats */
-    if (guac_parser_expect(parser, socket, usec_timeout, "image")) {
-
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"image\"");
-
-        guac_parser_free(parser);
-        return 1;
-    }
-
-    /* Store image mimetypes */
-    char** image_mimetypes = guac_copy_mimetypes(parser->argv, parser->argc);
-    user->info.image_mimetypes = (const char**) image_mimetypes;
-    
-    /* Get client timezone */
-    if (guac_parser_expect(parser, socket, usec_timeout, "timezone")) {
+    /* Handle each of the opcodes. */
+    while (1) {
+        if (guac_parser_read(parser, socket, usec_timeout)) {
+            guac_user_log_handshake_failure(user);
+            guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
+                    "Error while reading opcode instruction.");
+            guac_parser_free(parser);
+            return 1;
+        }
         
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"timezone\"");
+        /* If we receive the connect opcode, we're done. */
+        if (strcmp(parser->opcode, "connect") == 0)
+            break;
         
-        guac_parser_free(parser);
-        return 1;
-        
-    }
-    
-    /* Check number of timezone arguments */
-    if (parser->argc < 1) {
-        guac_user_log(user, GUAC_LOG_ERROR, "Received \"timezone\" instruction "
-                "lacked required arguments.");
-        guac_parser_free(parser);
-        return 1;
-    }
-    
-    /* Store timezone, if present */
-    char* timezone = parser->argv[0];
-    if (!strcmp(timezone, ""))
-        user->info.timezone = (const char*) timezone;
+        /* Loop available opcodes and run handler if/when match found. */
+        __guac_handshake_mapping* current = __guac_handshake_map;
+        while (current->opcode != NULL) {
+            
+            /* Check if loop opcode matches parsed opcode. */
+            if (strcmp(parser->opcode, current->opcode) == 0) {
+                
+                    /* If calling the handler fails, log it and return. */
+                    if (current->handler(user, parser->argc, parser->argv)) {
+                
+                        guac_user_log_handshake_failure(user);
+                        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
+                                "Error handling handling opcode during handshake.");
+                        guac_user_log(user, GUAC_LOG_DEBUG, "Failed opcode: %s",
+                                current->opcode);
 
-    /* Get args from connect instruction */
-    if (guac_parser_expect(parser, socket, usec_timeout, "connect")) {
+                        guac_parser_free(parser);
+                        return 1;
+                    }
+                    
+                    /* If calling the handler has succeeded, log it and break. */
+                    else {
+                        guac_user_log(user, GUAC_LOG_DEBUG,
+                                "Successfully processed instruction: \"%s\"",
+                                current->opcode);
+                        break;
+                    }
 
-        /* Log error */
-        guac_user_log_handshake_failure(user);
-        guac_user_log_guac_error(user, GUAC_LOG_DEBUG,
-                "Error reading \"connect\"");
-
-        guac_parser_free(parser);
-        return 1;
+            }
+            
+            /* Move to next opcode. */
+            current++;
+        }
     }
 
     /* Acknowledge connection availability */
@@ -448,16 +453,15 @@
                 "users remain)", user->user_id, client->connected_users);
 
     }
-
-    /* Free mimetype lists */
-    guac_free_mimetypes(audio_mimetypes);
-    guac_free_mimetypes(video_mimetypes);
-    guac_free_mimetypes(image_mimetypes);
     
-    /* Free timezone */
-    if (timezone != NULL)
-        free(timezone);
-
+    /* Free mimetype character arrays. */
+    guac_free_mimetypes((char **) user->info.audio_mimetypes);
+    guac_free_mimetypes((char **) user->info.image_mimetypes);
+    guac_free_mimetypes((char **) user->info.video_mimetypes);
+    
+    /* Free timezone info. */
+    free((char *) user->info.timezone);
+    
     guac_parser_free(parser);
 
     /* Successful disconnect */