4umi.com/web/javascript/fileread

Read-a-file

Useful Jscript

Windows Internet Explorer allows access to the local filesystem from a webpage using JScript, the Microsoft implementation of ECMAScript, the Javascript standard, by assigning an ActiveXObject() to the Scripting.FileSystemObject. That 's how they phrase it.

Active axe

This page features a script that, as an exercise, reads everything about your system it can get hold off. Start with the name of a local file or browse your drives in a series of select lists. For your peace of mind it is stressed that nothing is changed or written to your file system. That is, except if you choose it, as with the toggling of attributes mentioned below. The use of ActiveX objects in scripts does open doors to very radical commands, so when a script receives user input, special care should be taken to ensure that nothing unexpected will happen.

 An example report.
A drive scan report.

Depending on your settings for ActiveX controls (ToolsInternet Options… → choose tab Security → click button Custom level → scroll to ActiveX controls, select ‘prompt’ or ‘enable’), a security warning may pop up the first time the script is run after the page has loaded, asking for confirmation: “An ActiveX control might be unsafe to interact with other parts of the page. Do you want to allow this?”. The wording is strange, since the interaction is with the file system, not with other parts of the page, but once the allowance has been granted, there is no holding back. Scroll on.

The reader

This form reads a local file from your system and displays its attributes, properties and contents with linenumbers and an icon. In addition, some attributes can be changed by toggling the checkboxes. If the folder containing the file is open in another window, it may need to be refreshed before the change becomes apparent. Some files may even be renamed or deleted, after numerous clicks.

Click ‘Run’ to see the current file content interpreted as HTML code in a new window, or ‘Clear’ to empty the page of the last load.

Enter the path and name of the file you wish to investigate, or start by having the script read your drives.

   
Properties
Actions:

Contents
  1.  

The script

The magic and violence of this file reader comes from a symphony of Javascript functions. They have been incorporated into this page as the text below. Highlight the code, then double-click any item for more detailed information about it.

The getdrives() function builds and returns one big string full of information about each drive that the Enumerator() method has to offer. This string is parsed and used by various other functions, ie. to create the appropriate options in the <select> list of drives. Try it out with this snippet of bookmarklet code, in the addressbar or straightaway with a click:

javascript:window.alert( getdrives() );/**/

Used space is derived from the substraction of the remaining FreeSpace from the TotalSize, which are both numbered in bytes. And as quoted from the MicroSoft website: “The value returned by the AvailableSpace property is typically the same as that returned by the FreeSpace property. Differences may occur between the two on computer systems that support quotas.”

var foldercolor = '#fff390',
 drivetypes = [ 'Unknown', 'Removable', 'Fixed', 'Network', 'CD-ROM', 'RAM Disk' ],
 driveprops = [ 'DriveLetter', 'DriveType', 'ShareName', 'IsReady', 'Path', 'RootFolder',
  'FileSystem', 'SerialNumber', 'VolumeName', 'TotalSize', 'AvailableSpace', 'FreeSpace' ];


function getdrives() {
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  e = new Enumerator(fso.Drives),
  add = function(i) {
   i = driveprops[i];
   var prop = f[i];
   if( ( prop || prop===0 || prop===false ) && ( i!=='AvailableSpace' || prop!==free ) ) {
    if( /(Type)$/.test( i ) ) { prop = drivetypes[ prop ]; }
    if( /(Size|Space)$/.test( i ) ) { prop = bykb( prop, true ); }
    s.push( i.toCamelCase() + ':\t' + ( i.length < 8 ? '\t' : '' ) + prop );
   }
  },
  f, s = [], i, size, free, used;
 for( ; !e.atEnd(); e.moveNext() ) {
  f = e.item();
  if( f.IsReady ) {
   size = f.TotalSize;
   free = f.FreeSpace;
   used = size - free;
   for( i = 0; i < 12; i++ ) { add( i ); }
   s.push( 'Used space:\t' + bykb( used ) + ' (' + ron( used / size * 100, 2 ) + '%)' );
  } else {
   for( i = 0; i < 4; i++ ) { add( i ); }
  }
  s.push( '--------' );
 }
 return s.join( '\n' );
}

Function getfolder() returns the contents of a folder as an array of filenames. Subfolders are listed with a leading space, so they appear on top when the array is sorted later on.

function getfolder( s ) { s = trim( s ) || 'C:';
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  e, f, i, r = [];
 if( fso.FolderExists( s ) ) {
  f = fso.GetFolder( s );
  e = new Enumerator(f.SubFolders);
  for( ; !e.atEnd(); e.moveNext() ) {
   if( ( i = e.item() ) ) { r.push( ' ' + i ); }
  }
  e = new Enumerator(f.files);
  for( ; !e.atEnd(); e.moveNext() ) {
   if( ( i = e.item() ) ) { r.push( '' + i ); }
  }
 }
 return r;
}

The getfile() function is the heart of the machine, where all properties of an object begotten with the fso.GetFile() method are read and displayed on the page. The attributes property is a binary number, the Path property contains the name of the file entered by the user, but corrected for capitalization inaccuracies. The three date properties must be read in a try … catch block as some files, especially on non-writeable disks, do not have every property set and return en error when it is accessed.

The ReadAll() method of the file object made with the fso.OpenTextFile() method, requires the file to be non-empty. Hence the check for a size.

function getfile( form ) {
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  forReading = 1, forWriting = 2, forAppending = 8,
  dd = function( o, s ) {
   try {
    s = f[s] + '';
    o.value = s.replace( /^(\w{3}) (\w+) (\d\d?) ([\d:]+) ([\w+]+) (\d+)$/, '$3 $2 $6 $4' );
   } catch(e) {
    o.value = e.message;
   }
  },
  f, el = form.elements, name = el.file.value, a, i, ico, size, txt;
 if( fso.FileExists( name ) ) {
  f = fso.GetFile( name );
  a = f.attributes;
  size = f.size;
  el.filename.value = f.Path;
  el.filesize.value = bykb( size, true );
  el.filetype.value = f.Type;
  dd( el.created, 'DateCreated' );
  dd( el.modified, 'DateLastModified' );
  dd( el.accessed, 'DateLastAccessed' );
  for( i = 14; i<22; i++ ) { el[i].checked = a&el[i].value; }
  ico = name.toLowerCase().match( /\.(\w+)$/ );
  ico = ico ? 'image/icon/ext/' + ico[1] : 'blank';
  el.filetype.parentNode.style.background = 'url(/' + ico + '.gif) top 4em no-repeat';
  if( size ) {
   f = fso.OpenTextFile( name, forReading );
   txt = f.ReadAll();
   f.close();
  }
  gid( 'ol' ).innerHTML = toli( txt || ' -- Empty. --' );
  el.ren.disabled = /\\(windows|system|temp)\b/i.test( name ) || name.length<3;
  el.del.disabled = el.ren.disabled;
 } else {
  window.alert( 'Sorry, ' + name + ' could not be found.' );
 }
 return false;
}

The checkboxes in the list of attributes that is part of the file report, trigger the setattribute() function. The value of the checkbox is the bit to toggle in the binary number. Four checkboxes are read-only (grayed out) because the corresponding file attributes are read-only too. Only four of the eight attributes are writable by the FileSystemObject at all.

function setattribute( o ) {
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  f, name = o.form.elements.filename.value, n = o.value,
  m = 'The "' + o.parentNode.title.split(/\s/)[0] + '" attribute';
 if( name && fso.FileExists( name ) ) {
  f = fso.GetFile( name );
  if( f.attributes & n ) {
   f.attributes -= n; m += ' has been cleared.';
  } else {
   f.attributes += n; m += ' is set.';
  }
 }else{
  o.checked = !o.checked; m = 'No file specified.';
 }
 window.alert( m );
}

function renamefile( o, s ) {
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  ext = function ( s ) { return s.replace(/^.*\.(\w+)$/,'$1'); },
  f, el = o.form.elements.filename, name = el.value, m, t;
 if( name && fso.FileExists( name ) ) {
  f = fso.GetFile( name );
  t = f.Name;
  s = window.prompt( 'Rename ' + name, s || t );
  if( s ) {
   if( ext( s )!==ext( name ) && !window.confirm(
    'Changing the filename extension may render the file unusable.\n\nDo you wish to proceed?'
   ) ) { renamefile( o, s ); return; }
   f.Name = s;
   el.value = f.Path;
   m = t + ( !fso.FileExists( name ) ? ( ' has been renamed ' + f.Path ) : ' still exists!' );
  }else{
   m = ran( [ '', '', '', 'You can always change your mind.' ] );
  }
 }else{ m = 'No file specified.'; }
 if( m ) { window.alert( m ); }
}

function deletefile( o ) {
 var fso = new ActiveXObject( 'Scripting.FileSystemObject' ),
  f, el = o.form.elements.filename, name = el.value, m;
 if( name && fso.FileExists( name ) ) {
  if( window.confirm( 'Delete ' + name ) ){
   f = fso.GetFile( name );
   f.Delete();
   if( fso.FileExists( name ) ) {
    m = name + ' still exists!';
   }else{
    el.value = '';
    m = name + ' has been deleted.';
   }
  }
 }else{ m = 'No file specified.'; }
 if( m ) { window.alert( m ); }
}

The building and removing of select lists is done in a function baptized scan(), which is called in the onchange event of every such list.

function scan( o ) {
 o = o || this;
 var now = new Date(), i = 0, list, num = 0, par = o.parentNode,
  name = o.options[o.selectedIndex].value;
 while( o.nextSibling ) { par.removeChild( o.nextSibling ); }
 o.form.elements.file.value = name;
 if( name.charAt()===' ' ) {
  if( ( list = getfolder( name ) ) && ( num = list.length ) ) {
   var m, sel = o.cloneNode( false ), p = sel.options;
   list.sort( sortcaseless );
   p[0] = new Option( 'Contents', '', true, true );
   p[0].className = 'sd';
   while( (m = list[i++]) ) {
    p[i] = new Option( m.substring( m.lastIndexOf( '\\' ) + 1 ), m );
    if( m.charAt()===' ' ) {
     p[i].style.backgroundColor = foldercolor;
    }
   }
   sel.onchange = scan;
   par.insertBefore( document.createTextNode( ' \\ ' ) );
   par.insertBefore( sel );
   window.status = 'Found ' + num + ' items in ' + ( new Date() - now ) / 1000 + ' sec.';
  } else {
   window.alert( 'Nothing found! Is "' + name.replace(/[\/\\:]+$/,'') + '" perhaps empty?' );
  }
 }
 return false;
}

The initialization function is called the first time the user touches the <select> list of drives. View the course for even more of the same.

function init() {
 var a, def = 0, i = 0, m, sel = gid( 'drive' ), p = sel.options,
  now = new Date(), s = getdrives();
 now = new Date() - now;
 a = s.match( /Drive .*\t(.*)/g );
 if( a ) {
  a = a.join( '\n' ).split( /\s*Drive .*\t/ );
  while ( ( m = a[ 2 * i++ ] ) ) {
   p[i] = new Option( ' ' + m + ':', ' ' + m + ':\\' );
   p[i].style.backgroundColor = foldercolor;
  }
  sel.onchange = scan;
  gid( 'start' ).onclick = settable; settable( s );
  def = a.indexOf( 'Fixed' ) - 1;
  if( ( m = a[def] ) ) {
   sel.selectedIndex = def; scan( sel );
  }
 } else {
  window.alert( 'Sorry, no drives were found.' );
  restore( sel );
 }
 return false;
}

if( (i = window.location.search.match( /file=([^&]*)/ ) ) && ( i = i[1] ) ) {
 document.forms.f.elements.file.value = decode( i );
}

The last few lines run while the document is loading. Should an nasty error occur and the form submit the page to the server, the name of the file that caused the error will appear in the url of the next page, and so it can easily be copied back to the input box for another attempt.

Reference