6

I'm trying to have my Java application read all the data in a given path. So files, directories, metadata etc. This also includes one weird thing NTFS has called Alternate Data Stream (ADS).

Apparently it's like a second layer of data in a directory or file. You can open the command prompt and create a file in the ADS using ':', for example:

C:\ADSTest> echo test>:ads.txt

So,

C:\ADSTest> notepad :ads.txt

Should open a notepad that contains the string "test" in it. However, if you did:

C:\ADSTest> dir

You will not be able to see ads.txt. However, if you use the dir option that displays ADS data, you will be able to see it:

C:\ADSTest> dir /r
MM/dd/yyyy hh:mm            .:ads.txt

Now, I am aware that Java IO has the capability to read ADS. How do I know that? Well, Oracle's documentations clearly states so:

If the file attributes supported by your file system implementation aren't sufficient for your needs, you can use the UserDefinedAttributeView to create and track your own file attributes.

Some implementations map this concept to features like NTFS Alternative Data Streams and extended attributes on file systems such as ext3 and ZFS.

Also, a random post on a random forum :D

The data is stored in NTFS Alternate data streams (ADS) which are readable through Java IO (I have tested it).

The problem is, I can't find any pre-written file attribute viewer that can parse ADS, and I don't understand how to write an ADS parser of my own. I'm a beginner programmer so I feel this is way over my head. Would anybody please help me out or point me in the right direction?

Solution

EDIT: With the help of @knosrtum I was able to concoct a method that will return all the parsed ADS information from a given path as an ArrayList of Strings (it can also be easily edited to an ArrayList of files). Here's the code for anyone who might need it:

public class ADSReader {

    public static ArrayList<String> start(Path toParse) {

        String path = toParse.toString();
        ArrayList<String> parsedADS = new ArrayList<>();

        final String command = "cmd.exe /c dir " + path + " /r"; // listing of given Path.

        final Pattern pattern = Pattern.compile(
                "\\s*"                 // any amount of whitespace
                        + "[0123456789,]+\\s*"   // digits (with possible comma), whitespace
                        + "([^:]+:"    // group 1 = file name, then colon,
                        + "[^:]+:"     // then ADS, then colon,
                        + ".+)");      // then everything else.

        try {
            Process process = Runtime.getRuntime().exec(command);
            process.waitFor();
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;

                while ((line = br.readLine()) != null) {
                    Matcher matcher = pattern.matcher(line);
                    if (matcher.matches()) {
                        parsedADS.add((matcher.group(1)));
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int z = 0; z<parsedADS.size(); z++)
            System.out.println(parsedADS.get(z));

        return parsedADS;

    }
}
Pt. Terk
  • 462
  • 4
  • 13

1 Answers1

4

I was able to read the ADS of a file simply by opening the the file with the syntax "file_name:stream_name". So if you've done this:

C:>echo Hidden text > test.txt:hidden

Then you should be able to do this:

package net.snortum.play;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class AdsPlay {
    public static void main(String[] args) {
        new AdsPlay().start();
    }

    private void start() {
        File file = new File("test.txt:hidden");
        try (BufferedReader bf = new BufferedReader( new FileReader(file))) {
            String hidden = bf.readLine();
            System.out.println(hidden);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

If you want to get the ADS data from the dir /r command, I think you just need to execute a shell and capture the output:

package net.snortum.play;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ExecPlay {

    public static void main(String[] args) {
        new ExecPlay().start();
    }

    private void start() {
        String fileName = "not found";
        String ads = "not found";
        final String command = "cmd.exe /c dir /r"; // listing of current directory

        final Pattern pattern = Pattern.compile(
                  "\\s*"                 // any amount of whitespace
                + "[0123456789,]+\\s*"   // digits (with possible comma), whitespace
                + "([^:]+):"             // group 1 = file name, then colon
                + "([^:]+):"             // group 2 = ADS, then colon
                + ".+");                 // everything else

        try {
            Process process = Runtime.getRuntime().exec(command);
            process.waitFor();
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;

                while ((line = br.readLine()) != null) {
                    Matcher matcher = pattern.matcher(line);
                    if (matcher.matches()) {
                        fileName = matcher.group(1);
                        ads = matcher.group(2);
                        break;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(fileName + ", " + ads);

    }
}

Now you can use the first code listing to print the ADS data.

ksnortum
  • 2,809
  • 4
  • 27
  • 36
  • Cool! But, as I stated in my original post, I need this functionality to read all the data in a given path. Think of it like the Windows DIR command if you run `dir /r`. Normally, I would use `DirectoryStream stream = Files.newDirectoryStream(localDir)`, but that doesn't read the ADS. So I need to be able to detect ADS data inside a file stream and place it inside an ArrayList of Paths. How would I have to modify this code to accomodate for that? – Pt. Terk Oct 13 '15 at 06:30
  • Thank you for that edit, but is there any way to turn that into a method that returns an ArrayList of _specifically_ ADS paths or Path class object of an ADS path (ideally implemeted using just the Java IO) so I can insert it in my app's ArrayList that already contains all the normally parsed Paths to be passed on to my filtering and sorting methods? – Pt. Terk Oct 13 '15 at 18:35
  • If you know exactly the ADS name, you can write code using NIO to read it or to test for it. In the above example the ADS name would be "hidden". But if you don't know the exact name, I don't know of a Java IO way to tell if there is _any_ ADS info on a file. I think you'd have to use the the `dir /r` parser I wrote above. – ksnortum Oct 13 '15 at 22:53
  • I've managed to concoct a method to match my needs by tweaking your code a bit. Thank you very much, I've learned a lot of new things! – Pt. Terk Oct 17 '15 at 15:36