Friday, September 26, 2008

Determining If a File Is Cut or Copied to the Clipboard

I received a question on the Microsoft Forums today regarding whether or not a file is cut or copied to the clipboard. Using the System.Windows.Forms.Clipboard class, we can retrieve the data that is on the Windows Clipboard, however, only a small subset of this information is exposed easily through the managed methods on the clipboard. Enter the IDataObject interface.

You can access an object implementing this interface from the Clipboard.GetData() static method. The resulting implementation can return you a list of Formats included in the clipboard. Using these format strings, you can retrieve all of the information on the Clipboard. For example, take the following code:



   1:  IDataObject d = Clipboard.GetDataObject();

   2:  foreach (string format in d.GetFormats())

   3:      Console.WriteLine("Format: {0},\n .NET Data Type {1}\n\n", format, d.GetData(format).GetType().FullName);

   4:  Console.ReadLine();



Right clicking on a file in Explorer and copying a file, and then running the code above gives me the following output in the console:

Format: Shell IDList Array,
.NET Data Type System.IO.MemoryStream


Format: FileDrop,
.NET Data Type System.String[]


Format: FileNameW,
.NET Data Type System.String[]


Format: FileName,
.NET Data Type System.String[]


Format: Preferred DropEffect,
.NET Data Type System.IO.MemoryStream


Format: Shell Object Offsets,
.NET Data Type System.IO.MemoryStream

Playing around with these values can give you some interesting results. For instance, the format "Preferred DropEffect" is actually a MemoryStream mapped to an integer value. Converting it from a MemoryStream to an Int32, you can determine if a file was cut or copied to the clipboard in explorer:



   1:  static DragDropEffects GetCurrentDropEffect()

   2:  {

   3:      string format = "Preferred DropEffect";

   4:   

   5:      Object obj = Clipboard.GetData(format);

   6:   

   7:      if (obj != null)

   8:      {

   9:          MemoryStream ms = (MemoryStream)obj;

  10:          BinaryReader br = new BinaryReader(ms);

  11:          return (DragDropEffects)br.ReadInt32();

  12:      }

  13:      return DragDropEffects.None;

  14:  }



Check it out for yourself. Here's a little console app to display the results:



   1:  [STAThread]

   2:  static void Main(string[] args)

   3:  {

   4:      Console.WriteLine("Perform a file operation.");

   5:      do

   6:      {

   7:          Console.Clear();

   8:          DragDropEffects currentEffect = GetCurrentDropEffect();

   9:          string cutOrCopy = "None";

  10:          switch (currentEffect)

  11:          {

  12:              case DragDropEffects.Copy | DragDropEffects.Link:

  13:                  cutOrCopy = "Copy";

  14:                  break;

  15:              case DragDropEffects.Move:

  16:                  cutOrCopy = "Cut";

  17:                  break;

  18:          }

  19:          Console.WriteLine("Last operation was: {0}", cutOrCopy);

  20:          Console.WriteLine("Perform a function and press enter or type \"exit\" to quit");

  21:      } while (Console.ReadLine() != "exit");

  22:  }



That's all I got for today!

Monday, September 22, 2008

Beware of the Settings Designer

Visual Studio 2008 and 2005 both provide a very simple method of creating default settings for an application. While there is a place for the settings designer, you should be aware of a couple of the "gotchas" that come along with the settings designer, so you don't make a serious security blunder.

.NET settings that are entered using the settings designer create two files: Settings.settings and Settings.designer.cs. Settings.settings is used for viewing the settings at design time, while Settings.Designer.cs is actually compiled into the DLL.

In the settings designer, it's possible to add a connection string by setting the type to "(Connection string)". I believe this is a mistake on the part of Microsoft. While it's extremely convenient to add a connection string via the Connection Properties window that can be used to specify the connection string, it has the added effect of luring unsuspecting developers into actually using it, despite the warning that pops up when you indicate to save the password (manually typing a connection string will not yield this warning), and then compiling the connection string into the DLL. Once the value of the connection string is entered into the settings designer, the IDE automatically updates the Settings.Designer.cs file, as well as the app.config, and it puts this connection string in both locations.

Creating a connection string in the settings designer yielded the following code in the Settings.Designer.cs file:

[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.ConnectionString)]
[global::System.Configuration.DefaultSettingValueAttribute("Data Source=localhost;Initial Catalog=myDatabase;User ID=myUserName;Password=myPa" +
"ssword;")]
public string MyConnectionString {
get {
return ((string)(this["MyConnectionString"]));
}
}


And this code is then compiled into the assembly, and available for viewing in any disassembler program. I was easily able to view my connection string in Reflector by opening the property in Reflector:

.property instance string MyConnectionString
{
.get instance string SecurityNightmare.Properties.Settings::get_MyConnectionString()
.custom instance void [mscorlib]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor()
.custom instance void [System]System.Configuration.SpecialSettingAttribute::.ctor(valuetype [System]System.Configuration.SpecialSetting) = { int32(0) }
.custom instance void [System]System.Configuration.ApplicationScopedSettingAttribute::.ctor()
.custom instance void [System]System.Configuration.DefaultSettingValueAttribute::.ctor(string) = { string('Data Source=localhost;Initial Catalog=myDatabase;User ID=myUserName;Password=myPassword;') }
}


So, that being said, avoid adding connection strings within the settings.designer. It's much safer to add them manually to the app.config.