Make it possible to load and save configuration files. Also, when
connecting, the options are saved as default settings. This patch
fixes SF bugs 3481470 and 3499216. 



git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4950 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/vncviewer/parameters.cxx b/vncviewer/parameters.cxx
index 7b60b62..4c5a3dd 100644
--- a/vncviewer/parameters.cxx
+++ b/vncviewer/parameters.cxx
@@ -1,5 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
  * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright 2012 Samuel Mannehed <samuel@cendio.se> for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,10 +22,33 @@
 #include <config.h>
 #endif
 
+#ifdef HAVE_GNUTLS
+#include <rfb/CSecurityTLS.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#include <tchar.h>
+#endif
+
 #include "parameters.h"
 
+#include <os/os.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+#include <rfb/SecurityClient.h>
+
+#include <FL/fl_utf8.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
 using namespace rfb;
 
+static LogWriter vlog("Parameters");
+
+
 IntParameter pointerEventInterval("PointerEventInterval",
                                   "Time in milliseconds to rate-limit"
                                   " successive pointer events", 0);
@@ -107,3 +131,556 @@
                                    "to the server when in full screen mode.",
                                    true);
 
+const char* IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0";
+
+VoidParameter* parameterArray[] = {
+#ifdef HAVE_GNUTLS
+  &CSecurityTLS::x509ca,
+  &CSecurityTLS::x509crl,
+#endif // HAVE_GNUTLS
+  &SecurityClient::secTypes,
+  &dotWhenNoCursor,
+  &autoSelect,
+  &fullColour,
+  &lowColourLevel,
+  &preferredEncoding,
+  &customCompressLevel,
+  &compressLevel,
+  &noJpeg,
+  &qualityLevel,
+#ifdef HAVE_FLTK_FULLSCREEN
+  &fullScreen,
+#ifdef HAVE_FLTK_FULLSCREEN_SCREENS
+  &fullScreenAllMonitors,
+#endif // HAVE_FLTK_FULLSCREEN_SCREENS
+#endif // HAVE_FLTK_FULLSCREEN
+  &desktopSize,
+  &remoteResize,
+  &viewOnly,
+  &shared,
+  &acceptClipboard,
+  &sendClipboard,
+  &sendPrimary,
+  &menuKey,
+  &fullscreenSystemKeys
+};
+
+// Encoding Table
+static struct {
+  const char first;
+  const char second;
+} replaceMap[] = {'\n', 'n',
+                  '\r', 'r'};
+
+bool encodeValue(const char* val, char* dest, size_t destSize) {
+
+  bool normalCharacter = true;
+  size_t pos = 0;
+
+  for (int i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
+    
+    // Check for sequences which will need encoding
+    if (val[i] == '\\') {
+
+      strncpy(dest+pos, "\\\\", 2);
+      pos++;
+      if (pos >= destSize) {
+	vlog.error("Encoding backslash: The size of the buffer dest is to small, "
+		   "it needs to be more than %d bytes bigger.", (destSize - 1 - i));
+	return false;
+      }
+
+    } else {
+
+      for (int j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++)
+
+	if (val[i] == replaceMap[j].first) {
+	  dest[pos] = '\\';
+	  pos++;
+	  if (pos >= destSize) {
+	    vlog.error("Encoding escape sequence: The size of the buffer dest is to small, "
+		       "it needs to be more than %d bytes bigger.", (destSize - 1 - i));
+	    return false;
+	  }
+
+	  dest[pos] = replaceMap[j].second;
+	  normalCharacter = false;
+	  break;
+	}
+
+      if (normalCharacter) {
+	dest[pos] = val[i];
+      }
+    }
+    normalCharacter = true; // Reset for next loop
+
+    pos++;
+    if (pos >= destSize) {
+      vlog.error("Encoding normal character: The size of the buffer dest is to small, "
+		 "it needs to be more than %d bytes bigger.", (destSize - 1 - i));
+      return false;
+    }
+
+  }
+
+  dest[pos] = '\0';
+  return true;
+}
+
+
+bool decodeValue(const char* val, char* dest, size_t destSize) {
+
+  size_t pos = 0;
+  bool escapedCharacter = false;
+  
+  for (int i = 0; (val[i] != '\0') && (i < (destSize - 1)); i++) {
+    
+    // Check for escape sequences
+    if (val[i] == '\\') {
+      
+      for (int j = 0; j < sizeof(replaceMap)/sizeof(replaceMap[0]); j++) {
+	if (val[i+1] == replaceMap[j].second) {
+	  dest[pos] = replaceMap[j].first;
+	  escapedCharacter = true;
+	  pos--;
+	  break;
+	}
+      }
+
+      if (!escapedCharacter) {
+	if (val[i+1] == '\\') {
+	  dest[pos] = val[i];
+	  i++;
+	} else {
+	  vlog.error("Unknown escape sequence at character %d", i);
+	  return false;
+	}
+      }
+
+    } else {
+      dest[pos] = val[i];
+    }
+
+    escapedCharacter = false; // Reset for next loop
+    pos++;
+    if (pos >= destSize) {
+      vlog.error("Decoding: The size of the buffer dest is to small, "
+		 "it needs to be 1 byte bigger.");
+      return false;
+    }
+  }
+  
+  dest[pos] = '\0';
+  return true;
+}
+
+
+#ifdef _WIN32
+void setKeyString(const char *_name, const char *_value, HKEY* hKey) {
+  
+  const DWORD buffersize = 256;
+
+  wchar_t name[buffersize];
+  unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
+  if (size >= buffersize) {
+    vlog.error("Could not convert the parameter-name %s to wchar_t* when "
+	       "writing to the Registry, the buffersize is to small.", _name);
+    return;
+  }
+
+  char encodingBuffer[buffersize];
+  if (!encodeValue(_value, encodingBuffer, buffersize)) {
+    vlog.error("Could not encode the parameter-value %s when "
+	       "writing to the Registry.", _value);
+    return;
+  }
+
+  wchar_t value[buffersize];
+  size = fl_utf8towc(encodingBuffer, strlen(encodingBuffer)+1, value, buffersize);
+  if (size >= buffersize) {
+    vlog.error("Could not convert the parameter-value %s to wchar_t* when "
+	       "writing to the Registry, the buffersize is to small.", _value);
+    return;
+  }
+
+  LONG res = RegSetValueExW(*hKey, name, 0, REG_SZ, (BYTE*)&value, (wcslen(value)+1)*2);
+  if (res != ERROR_SUCCESS) {
+    vlog.error("Error(%d) writing %s(REG_SZ) to Registry.", res, _value);
+    return;
+  }
+}
+
+
+void setKeyInt(const char *_name, const int _value, HKEY* hKey) {
+
+  const DWORD buffersize = 256;
+  wchar_t name[buffersize];
+  DWORD value = _value;
+
+  unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
+  if (size >= buffersize) {
+    vlog.error("Could not convert the parameter-name %s to wchar_t* when "
+	       "writing to the Registry, the buffersize is to small.", _name);
+    return;
+  }
+  
+  LONG res = RegSetValueExW(*hKey, name, 0, REG_DWORD, (BYTE*)&value, sizeof(DWORD));
+  if (res != ERROR_SUCCESS) {
+    vlog.error("Error(%d) writing %d(REG_DWORD) to Registry.", res, _value);
+    return;
+  }
+}
+
+
+bool getKeyString(const char* _name, char* dest, size_t destSize, HKEY* hKey) {
+  
+  DWORD buffersize = 256;
+  WCHAR value[destSize];
+  wchar_t name[buffersize];
+
+  unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
+  if (size >= buffersize) {
+    vlog.error("Could not convert the parameter-name %s to wchar_t* when "
+	       "reading from the Registry, the buffersize is to small.", _name);
+    return false;
+  }
+
+  LONG res = RegQueryValueExW(*hKey, name, 0, NULL, (LPBYTE)value, &buffersize);
+  if (res != ERROR_SUCCESS){
+    if (res == ERROR_FILE_NOT_FOUND) {
+      // The value does not exist, defaults will be used.
+    } else {
+      vlog.error("Error(%d) reading %s from Registry.", res, _name);
+    }
+    return false;
+  }
+  
+  char utf8val[destSize];
+  size = fl_utf8fromwc(utf8val, sizeof(utf8val), value, wcslen(value)+1);
+  if (size >= sizeof(utf8val)) {
+    vlog.error("Could not convert the parameter-value for %s to utf8 char* "
+	       "when reading from the Registry, the buffer dest is to small.",
+	       _name);
+    return false;
+  }
+  const char *ret = utf8val;
+  
+  if(decodeValue(ret, dest, destSize))
+    return true;
+  else 
+    return false;
+}
+
+
+bool getKeyInt(const char* _name, int* dest, HKEY* hKey) {
+  
+  const DWORD buffersize = 256;
+  DWORD dwordsize = sizeof(DWORD);
+  DWORD value = 0;
+  wchar_t name[buffersize];
+
+  unsigned size = fl_utf8towc(_name, strlen(_name)+1, name, buffersize);
+  if (size >= buffersize) {
+    vlog.error("Could not convert the parameter-name %s to wchar_t* when "
+	       "reading from the Registry, the buffersize is to small.", _name);
+    return false;
+  }
+
+  LONG res = RegQueryValueExW(*hKey, name, 0, NULL, (LPBYTE)&value, &dwordsize);
+  if (res != ERROR_SUCCESS){
+    if (res == ERROR_FILE_NOT_FOUND) {
+      // The value does not exist, defaults will be used.
+    } else {
+      vlog.error("Error(%d) reading %s from Registry.", res, _name);
+    }
+    return false;
+  }
+
+  *dest = (int)value;
+  return true;
+}
+
+
+void saveToReg(const char* servername) {
+  
+  HKEY hKey;
+    
+  LONG res = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\TigerVNC\\vncviewer", 0, NULL, 
+			     REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL);
+  if (res != ERROR_SUCCESS) {
+    vlog.error("Error(%d) creating key: Software\\TigerVNC\\vncviewer", res);
+    return;
+  }
+
+  setKeyString("ServerName", servername, &hKey);
+
+  for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
+    if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
+      setKeyString(parameterArray[i]->getName(), *(StringParameter*)parameterArray[i], &hKey);
+    } else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
+      setKeyInt(parameterArray[i]->getName(), (int)*(IntParameter*)parameterArray[i], &hKey);
+    } else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
+      setKeyInt(parameterArray[i]->getName(), (int)*(BoolParameter*)parameterArray[i], &hKey);
+    } else {      
+      vlog.info("The parameterArray contains a object of a invalid type at line %d.", i);
+    }
+  }
+
+  res = RegCloseKey(hKey);
+  if (res != ERROR_SUCCESS) {
+    vlog.error("Error(%d) closing key: Software\\TigerVNC\\vncviewer", res);
+  }
+}
+
+
+char* loadFromReg() {
+
+  HKEY hKey;
+
+  LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\TigerVNC\\vncviewer"
+			   , 0, KEY_READ, &hKey);
+  if (res != ERROR_SUCCESS) {
+    if (res == ERROR_FILE_NOT_FOUND) {
+      // The key does not exist, defaults will be used.
+    } else {
+      vlog.error("Error(%d) opening key: Software\\TigerVNC\\vncviewer", res);
+    }
+    return NULL;
+  }
+
+  const size_t buffersize = 256;
+  static char servername[buffersize];
+
+  char servernameBuffer[buffersize];
+  if (getKeyString("ServerName", servernameBuffer, buffersize, &hKey))
+    snprintf(servername, buffersize, "%s", servernameBuffer);
+  
+  int intValue = 0;
+  char stringValue[buffersize];
+  
+  for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
+    if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
+      if (getKeyString(parameterArray[i]->getName(), stringValue, buffersize, &hKey))
+	parameterArray[i]->setParam(stringValue);
+    } else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
+      if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
+	((IntParameter*)parameterArray[i])->setParam(intValue);
+    } else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
+      if (getKeyInt(parameterArray[i]->getName(), &intValue, &hKey))
+	((BoolParameter*)parameterArray[i])->setParam(intValue);
+    } else {      
+      vlog.info("The parameterArray contains a object of a invalid type at line %d.", i);
+    }
+  }
+
+  res = RegCloseKey(hKey);
+  if (res != ERROR_SUCCESS){
+    vlog.error("Error(%d) closing key:  Software\\TigerVNC\\vncviewer", res);
+  }
+  
+  return servername;
+}
+#endif // _WIN32
+
+
+void saveViewerParameters(const char *filename, const char *servername) {
+
+  const size_t buffersize = 256;
+  char filepath[PATH_MAX];
+  char write_error[buffersize*2];
+  char encodingBuffer[buffersize];
+
+  // Write to the registry or a predefined file if no filename was specified.
+  if(filename == NULL) {
+
+#ifdef _WIN32
+    saveToReg(servername);
+    return;
+#endif
+    
+    char* homeDir = NULL;
+    if (getvnchomedir(&homeDir) == -1) {
+      vlog.error("Failed to write configuration file, "
+		 "can't obtain home directory path.");
+      return;
+    }
+
+    snprintf(filepath, sizeof(filepath), "%sdefault.tigervnc", homeDir);
+  } else {
+    snprintf(filepath, sizeof(filepath), "%s", filename);
+  }
+
+  /* Write parameters to file */
+  FILE* f = fopen(filepath, "w+");
+  if (!f) {
+    snprintf(write_error, sizeof(filepath), "Failed to write configuration file, "
+	     "can't open %s", filepath);
+    throw Exception(write_error);
+  }
+  
+  fprintf(f, "%s\r\n", IDENTIFIER_STRING);
+  fprintf(f, "\r\n");
+
+  if (encodeValue(servername, encodingBuffer, buffersize))  
+    fprintf(f, "ServerName=%s\n", encodingBuffer);
+  
+  for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
+    if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
+      if (encodeValue(*(StringParameter*)parameterArray[i], encodingBuffer, buffersize))
+	fprintf(f, "%s=%s\n", ((StringParameter*)parameterArray[i])->getName(), encodingBuffer);
+    } else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
+      fprintf(f, "%s=%d\n", ((IntParameter*)parameterArray[i])->getName(), (int)*(IntParameter*)parameterArray[i]);
+    } else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
+      fprintf(f, "%s=%d\n", ((BoolParameter*)parameterArray[i])->getName(), (int)*(BoolParameter*)parameterArray[i]);
+    } else {      
+      vlog.info("The parameterArray contains a object of a invalid type at line %d.", i);
+    }
+  }
+  fclose(f);
+}
+
+
+char* loadViewerParameters(const char *filename) {
+
+  const size_t buffersize = 256;
+  char filepath[PATH_MAX];
+  char readError[buffersize*2];
+  char line[buffersize];
+  char decodingBuffer[buffersize];
+  char decodedValue[buffersize];
+  static char servername[sizeof(line)];
+
+  // Load from the registry or a predefined file if no filename was specified.
+  if(filename == NULL) {
+
+#ifdef _WIN32
+    return loadFromReg();
+#endif
+
+    char* homeDir = NULL;
+    if (getvnchomedir(&homeDir) == -1)
+      throw Exception("Failed to read configuration file, " 
+			   "can't obtain home directory path.");
+
+    snprintf(filepath, sizeof(filepath), "%sdefault.tigervnc", homeDir);
+  } else {
+    snprintf(filepath, sizeof(filepath), "%s", filename);
+  }
+
+  /* Read parameters from file */
+  FILE* f = fopen(filepath, "r");
+  if (!f) {
+    if (!filename)
+      return NULL; // Use defaults.
+    snprintf(readError, sizeof(readError), "Failed to read configuration file, "
+	     "can't open %s", filepath);
+    throw Exception(readError);
+  }
+  
+  int lineNr = 0;
+  while (!feof(f)) {
+
+    // Read the next line
+    lineNr++;
+    if (!fgets(line, sizeof(line), f)) {
+      if (line[sizeof(line) -1] != '\0') {
+	vlog.error("Could not read the line(%d) in the configuration file,"
+		   "the buffersize is to small.", lineNr);
+	return NULL;
+      }
+      if (feof(f))
+	break;
+
+      snprintf(readError, sizeof(readError), "Failed to read line %d in file %s", 
+	       lineNr, filepath);
+      throw Exception(readError);
+    }
+    
+    // Make sure that the first line of the file has the file identifier string
+    if(lineNr == 1) {
+      if(strncmp(line, IDENTIFIER_STRING, strlen(IDENTIFIER_STRING)) == 0) {
+	continue;
+      } else {
+	snprintf(readError, sizeof(readError), "Line 1 in file %s\n"
+		 "must contain the TigerVNC configurationfile identifier string:\n"
+		 "\"%s\"", filepath, IDENTIFIER_STRING);
+	throw Exception(readError);
+      }
+    }
+    
+    // Skip empty lines and comments
+    if ((line[0] == '\n') || (line[0] == '#') || (line[0] == '\r'))
+      continue;
+
+    int len = strlen(line);
+    if (line[len-1] == '\n') {
+      line[len-1] = '\0';
+      len--;
+    }
+
+    // Find the parameter value
+    char *value = strchr(line, '=');
+    if (value == NULL) {
+      vlog.info("Bad Name/Value pair on line: %d in file: %s", 
+		lineNr, filepath);
+      continue;
+    }
+    *value = '\0'; // line only contains the parameter name below.
+    value++;
+    
+    bool invalidParameterName = true; // Will be set to false below if 
+                                      // the line contains a valid name.
+
+    if (strcasecmp(line, "ServerName") == 0) {
+
+      if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
+	vlog.info("The value of the parameter %s on line %d in file %s is invalid.",
+		  line, lineNr, filepath);
+	continue;
+      }
+      snprintf(servername, sizeof(decodingBuffer), "%s", decodingBuffer);
+      invalidParameterName = false;
+
+    } else {
+    
+      // Find and set the correct parameter
+      for (int i = 0; i < sizeof(parameterArray)/sizeof(VoidParameter*); i++) {
+
+	if (dynamic_cast<StringParameter*>(parameterArray[i]) != NULL) {
+	  if (strcasecmp(line, ((StringParameter*)parameterArray[i])->getName()) == 0) {
+
+	    if(!decodeValue(value, decodingBuffer, sizeof(decodingBuffer))) {
+	      vlog.info("The value of the parameter %s on line %d in file %s is invalid.",
+			line, lineNr, filepath);
+	      continue;
+	    }
+	    ((StringParameter*)parameterArray[i])->setParam(decodingBuffer);
+	    invalidParameterName = false;
+	  }
+
+	} else if (dynamic_cast<IntParameter*>(parameterArray[i]) != NULL) {
+	  if (strcasecmp(line, ((IntParameter*)parameterArray[i])->getName()) == 0) {
+	    ((IntParameter*)parameterArray[i])->setParam(atoi(value));
+	    invalidParameterName = false;
+	  }
+	
+	} else if (dynamic_cast<BoolParameter*>(parameterArray[i]) != NULL) {
+	  if (strcasecmp(line, ((BoolParameter*)parameterArray[i])->getName()) == 0) {
+	    ((BoolParameter*)parameterArray[i])->setParam(atoi(value));
+	    invalidParameterName = false;
+	  }
+
+	} else {      
+	  vlog.info("The parameterArray contains a object of a invalid type at line %d.", lineNr);
+	}
+      }
+    }
+
+    if (invalidParameterName)
+      vlog.info("Invalid parameter name on line: %d in file: %s", 
+		lineNr, filepath);
+  }
+  fclose(f); f=0;
+  
+  return servername;
+}