lloyd.io is the personal website of Lloyd Hilaiel, a software engineer who works for Team Ozlo and lives in Denver.

All the stuff you'll find here is available under a CC BY-SA 3.0 license (use it and change it, just don't lie about who wrote it). Icons on this site are commercially available from steedicons.com. Fonts used are available in Google's Web Font directory, and I'm using Ubuntu and Lekton. Finally, Jekyll is used for site rendering.

Finally, Atul, Pascal, and Stephen inspired the site's design. And in case you're interested, this site's code is available on github.

Would the real "HTML5" File please stand up?
2009-09-11 00:00:00 -0700

In fiddling around with HTML5 desktop sourced drag and drop, present in Safari Version 4.0.3 (6531.9), I’m faced with the interesting challenge of understanding when I can trust that a drop is really a drop – that a File is the result of user interaction. For a little context, here’s a bit of code cobbled up by Gordon Durand that’ll let us capture desktop sourced drops in the latest snow leopard:

<html>
<head>
<script>

function dodragenter(event)
{
  document.getElementById("output").textContent = "Drop it!  I dare you!";
}

function dodragleave(event)
{
  document.getElementById("output").textContent = "";
}

function dodrop(event)
{
  var files = event.dataTransfer.files;
  var uris = event.dataTransfer.getData("text/uri-list").split("\n");
  var msg;
  msg = "File Count: " + files.length + "\n";

  for (var i = 0; i < files.length; i++) {
    msg += (" File " + i + ":\n");
    msg += ("\tfileData.fileName: " + files[i].fileName + ", fileData.fileSize: " + files[i].fileSize + "\n");
    msg += ("\turi: " + uris[i] + "\n");
    msg += "\n";
  }
  document.getElementById("output").textContent = msg;
}

</script>
</head>
<body>

<div id="output" style="min-height: 100px; white-space: pre; border: 1px solid black;"
     ondragenter="event.stopPropagation(); event.preventDefault(); dodragenter(event);"
     ondragover="event.stopPropagation(); event.preventDefault();"
     ondrop="event.stopPropagation(); event.preventDefault(); dodrop(event);"
     ondragleave="dodragleave(event)">

</body>
</html>

What’s a file look like?

So the first question is, in what ways can we introspect a javascript object? Here’s an interesting start:

// examine a supposed File object and return its vitals
function getObjectInfo(o)
{
  var msg;
  msg = "typeof(o):     " + typeof o + "\n";
  msg += "o.toString():  " + o.toString() + "\n";
  msg += "String(o):     " + String(o) + "\n";
  if (o) msg += "o.constructor: " + o.constructor + "\n";
  if (o) msg += "o.prototype:   " + o.prototype + "\n";
  if (o)  msg += "o members:     ";
  var comma = "";
  for (var m in o) { msg += comma + m; comma = ", "; }

  msg += "\n\ncontents: (" + o.fileName + " is " + o.fileSize + " bytes)\n";

  return "<pre>"+msg+"</pre>>";
}

That is, we’re checking .constructor, .prototype and the members of this magical file object. Here’s what a “real” file object looks like, one caught by a drag:

typeof(o):     object
o.toString():  [object File]
String(o):     [object File]
o.constructor: [object FileConstructor]
o.prototype:   undefined
o members:     fileSize, fileName

contents: (silly_hat.jpg is 1380 bytes)

Hrm, can we fake that? Here’s a first try:

var fakeFile = {
  fileSize: 1234,
  fileName: "/etc/passwd"
};
fakeFile.constructor = File;
fakeFile.toString = function() { return "[object File]"; }

the fakeFile variable ends up looking like this:

typeof(o):     object
o.toString():  [object File]
String(o):     [object File]
o.constructor: [object FileConstructor]
o.prototype:   undefined
o members:     fileSize, fileName, constructor, toString

contents: (/etc/passwd is 1234 bytes)

The only telltale here is the fact that I’ve overridden constructor and the toString functions to make a plain ol' object instance built from a literal look a lil' bit more like one of these mystical files. So I, personally, can’t figure out how to get rid of this constructor or toString function. So I’m concluding here that we’ve got a reasonable way to filter out wholly synthetic fake File objects.

Now what about a different tactic? overwriting an existing File object? We’ll take something read (the first object), and overwrite the fileSize and fileName members. And that doesn’t work. we see that the specification of fileName and fileSize is actually read only. Here’s the idl from WebKit –

module html {

    interface [
        GenerateConstructor
    ] File {
        readonly attribute DOMString fileName;
        readonly attribute unsigned long long fileSize;
    };
}

Testing for an authentic File

This is deeply tied to Safari 4.0.3, and subject to break in the future, but for now this is the (slightly redundant) test that I come up with to verify that we’re really talking about a File that was attained as a result of a drag.

function isRealFile(f)
{
  var hasFileName = false, hasFileSize = false, numMembers = 0,
      mutableMembers = false;

  for (var m in f) {
      numMembers++;
      if (m === 'fileName') hasFileName = true;
      else if (m === 'fileSize') hasFileSize = true;
      var before = f[m];
      f[m] = "__IMutedYou__";
      if (before !== f[m]) mutableMembers = true;
  }


  if (typeof(f) !== 'object' ||
      !f.toString || f.toString.constructor !== Function ||
      f.toString() !== '[object File]' ||
      f.constructor != File ||
      f.protype != undefined ||
      numMembers != 2 ||
      !hasFileName ||
      !hasFileSize ||
      mutableMembers)
  {
      return false;
  }

  return true;
}

Cobbling together an Attack?

The ultimate question is once we have one of these magic File objects (resulting from a user drop), what can we do with it that’s interesting? In 15 minutes of looking it seems like all we can really do is display the size, uri, and filename… Could that be correct?

The point here is that it’s very interesting that we can now accept drops from the desktop, but what will we be able to do with them and what will the security model be?

Personally, I’m grateful to see progress in this area, but am a little curious about the utility of these first steps, and the strategy for the next ones…

lloyd