javaiojava-iontfsalternate-data-stream

How do I read Windows NTFS's Alternate Data Stream using Java's IO?


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;

    }
}

Solution

  • 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.