Task 536: .PFC File Format

Task 536: .PFC File Format

File Format Specifications for .PFC

The .PFC file format is a proprietary binary database used by AOL Desktop Software for storing user data offline, known as the Personal Filing Cabinet (PFC). It is a structured format for local storage of emails, bookmarks, contacts, preferences, and other data. The format is not officially documented by AOL, but reverse-engineered details are available from sources like the Library of Congress format description, based on work by Franz v. Gordon in 2002. The format is unitary, binary, structured, and symbolic, with compression (zlib for emails in AOL 7.0+) and occasional encryption. MacOS and Windows versions differ slightly, but the following focuses on the Windows version.

  1. List of all the properties of this file format intrinsic to its file system:
  • Magic Number (File Signifier): The file starts with the 8-byte header "AOLVM100" (hex: 41 4F 4C 56 4D 31 30 30), identifying it as a PFC file.
  • Header Data: Additional header information following the magic number (exact details not fully documented, but includes version and configuration data).
  • Index Table: A structured table following the header, containing entries that point to all data objects in the file. Each entry starts with "RS" (hex: 52 53 00 00), and includes:
  • Pointer to the object's location in the file (address offset).
  • Content type identifier (coded value indicating type, e.g., folder, email, bookmark, address book, signature, buddy preferences).
  • Flags (status indicators, e.g., open/closed folders, object state).
  • Timestamps (creation/modification dates for the object).
  • Data Sections: Referenced by the index table, these contain the actual content. Properties common to all objects include:
  • Object size (implied by pointers and lengths).
  • Compression indicator (zlib for email bodies in AOL 7.0+).
  • Encryption indicator (for some received emails in AOL 7.0).
  • Folder Properties (system or user folders):
  • Folder name.
  • Subfolder pointers.
  • Open/closed state.
  • Email Message Properties (specific to email objects):
  • Prefix: "RS\0\0" (hex: 52 53 5c 30 5c 30).
  • Subrecord start marker: "AOLH" (hex: 41 4F 4C 48).
  • Subrecord end marker: "AOLF" (hex: 41 4F 4C 46).
  • Date/time timestamp (primary).
  • To field (recipient address).
  • CC field (carbon copy addresses).
  • BCC field (blind carbon copy addresses).
  • Subject field.
  • Reply-to field.
  • Recipient field (additional details).
  • Secondary date/time timestamp.
  • Flag (1 byte at offset 0x16): 0 = read, 1 = has attachment, 2 = self-written, 3 = has embedded picture.
  • Text formatting preference (HTML or plain text, introduced in AOL 8.0/9.0).
  • Message body (compressed with zlib in later versions).
  • Attachment reference: Name, length, storage location (attachments are 64-bit encoded and stored separately).
  • Bookmark/Favorites Properties:
  • URL.
  • Title or description.
  • Timestamp.
  • Address Book Properties:
  • Contact name.
  • Email address.
  • Phone number.
  • Company info.
  • Other Properties (e.g., Away Messages, Newsgroup Postings, Signatures, Buddy Info):
  • Text content.
  • Preferences (e.g., user language, icons).
  • Timestamp.
  • General File Properties:
  • Total file size limit for emails (25MB including attachments).
  • Username association (file named after username, no extension or .pfc for older versions).
  • Local storage path (in the Organize folder).
  1. Two direct download links for files of format .PFC:

I was unable to find public direct download links for .PFC files, as they typically contain personal user data (e.g., emails, contacts) and are not shared publicly to avoid privacy issues. Instead, here are links to related resources that include sample data or tools for handling .PFC files:

  1. Ghost blog embedded HTML JavaScript for drag and drop .PFC file to dump properties:
PFC File Dumper
Drag and drop .PFC file here


  

This HTML page can be embedded in a Ghost blog post. It allows drag and drop of a .PFC file and dumps the properties to the screen using JSON.stringify. The parsing is simplified based on the reverse-engineered spec (offsets and lengths are placeholders as the full byte-level spec is not public; zlib decompression not implemented for simplicity).

  1. Python class for opening, decoding, reading, writing, and printing .PFC properties:
import struct
import zlib
import os

class PfcParser:
    def __init__(self, filename):
        self.filename = filename
        self.data = None
        self.properties = {}

    def read(self):
        with open(self.filename, 'rb') as f:
            self.data = f.read()

        position = 0

        # Magic number
        self.properties['magic'] = self.data[position:position+8].decode('ascii')
        position += 8
        if self.properties['magic'] != 'AOLVM100':
            raise ValueError('Invalid PFC file')

        # Skip additional header (placeholder)
        position += 100

        # Index Table (simplified)
        self.properties['indexEntries'] = []
        while position < len(self.data):
            entry_start = self.data[position:position+4]
            if entry_start != b'RS\x00\x00':
                break
            position += 4
            pointer = struct.unpack('<I', self.data[position:position+4])[0]
            position += 4
            content_type = struct.unpack('<I', self.data[position:position+4])[0]
            position += 4
            flags = self.data[position]
            position += 1
            timestamp = struct.unpack('<I', self.data[position:position+4])[0]
            position += 4
            self.properties['indexEntries'].append({
                'pointer': pointer,
                'content_type': content_type,
                'flags': flags,
                'timestamp': timestamp
            })

            # Skip variable part (placeholder)
            position += 20

        # Parse example email (placeholder, assuming first entry is email)
        if self.properties['indexEntries']:
            position = self.properties['indexEntries'][0]['pointer']
            prefix = self.data[position:position+5]
            position += 5
            if prefix == b'RS\x00\x00':
                self.properties['email'] = {}
                self.properties['email']['start'] = self.data[position:position+4].decode('ascii')
                position += 4
                self.properties['email']['date'] = struct.unpack('<I', self.data[position:position+4])[0]
                position += 4
                self.properties['email']['to'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['cc'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['bcc'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['subject'] = self.data[position:position+100].rstrip(b'\x00').decode('ascii')
                position += 100
                self.properties['email']['replyTo'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['recipient'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['secondaryDate'] = struct.unpack('<I', self.data[position:position+4])[0]
                position += 4
                position += 0x16 - 8  # Adjust to flag
                self.properties['email']['flag'] = self.data[position]
                position += 1
                self.properties['email']['textFormat'] = self.data[position]
                position += 1
                self.properties['email']['attachmentName'] = self.data[position:position+50].rstrip(b'\x00').decode('ascii')
                position += 50
                self.properties['email']['attachmentLength'] = struct.unpack('<I', self.data[position:position+4])[0]
                position += 4
                self.properties['email']['end'] = self.data[position:position+4].decode('ascii')
                # Note: Message body would be zlib.decompress(self.data[position:...]) if compressed

    def print_properties(self):
        print(self.properties)

    def write(self, new_filename=None):
        # Simplified write (reconstruct from properties, placeholder for full implementation)
        if not new_filename:
            new_filename = self.filename + '.new'
        with open(new_filename, 'wb') as f:
            f.write(b'AOLVM100')
            # Write header placeholder
            f.write(b'\x00' * 100)
            for entry in self.properties.get('indexEntries', []):
                f.write(b'RS\x00\x00')
                f.write(struct.pack('<I', entry['pointer']))
                f.write(struct.pack('<I', entry['content_type']))
                f.write(bytes([entry['flags']]))
                f.write(struct.pack('<I', entry['timestamp']))
                # Skip variable
                f.write(b'\x00' * 20)
            if 'email' in self.properties:
                # Write example email data (placeholder)
                f.write(b'RS\x00\x00')
                f.write(b'AOLH')
                f.write(struct.pack('<I', self.properties['email']['date']))
                f.write(self.properties['email']['to'].encode('ascii').ljust(50, b'\x00'))
                # Similar for other fields...
                # Note: Full write would require recalculating pointers and compressing

# Example usage
# parser = PfcParser('example.pfc')
# parser.read()
# parser.print_properties()
# parser.write()

This class reads the file, parses the properties (simplified offsets, no full zlib or encryption handling), prints them to console, and writes a new file (basic reconstruction).

  1. Java class for opening, decoding, reading, writing, and printing .PFC properties:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class PfcParser {
    private String filename;
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();

    public PfcParser(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            data = fis.readAllBytes();
        }

        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
        int position = 0;

        // Magic
        byte[] magicBytes = new byte[8];
        buffer.position(position);
        buffer.get(magicBytes);
        properties.put("magic", new String(magicBytes));
        position += 8;
        if (!properties.get("magic").equals("AOLVM100")) {
            throw new IOException("Invalid PFC file");
        }

        // Skip header placeholder
        position += 100;

        // Index Table
        ArrayList<Map<String, Object>> indexEntries = new ArrayList<>();
        while (position + 4 < data.length) {
            byte[] entryStart = new byte[4];
            buffer.position(position);
            buffer.get(entryStart);
            if (!new String(entryStart).equals("RS")) break;
            position += 4;
            int pointer = buffer.getInt(position);
            position += 4;
            int contentType = buffer.getInt(position);
            position += 4;
            byte flags = buffer.get(position);
            position += 1;
            int timestamp = buffer.get(position);
            position += 4;
            Map<String, Object> entry = new HashMap<>();
            entry.put("pointer", pointer);
            entry.put("contentType", contentType);
            entry.put("flags", flags);
            entry.put("timestamp", timestamp);
            indexEntries.add(entry);
            position += 20; // Placeholder
        }
        properties.put("indexEntries", indexEntries);

        // Example email
        if (!indexEntries.isEmpty()) {
            position = (int) indexEntries.get(0).get("pointer");
            byte[] prefix = new byte[5];
            buffer.position(position);
            buffer.get(prefix);
            position += 5;
            if (new String(prefix).equals("RS")) {
                Map<String, Object> email = new HashMap<>();
                byte[] start = new byte[4];
                buffer.get(start);
                email.put("start", new String(start));
                position += 4;
                email.put("date", buffer.getInt(position));
                position += 4;
                // For strings, read until \0 (simplified fixed length)
                byte[] toBytes = new byte[50];
                buffer.get(toBytes);
                email.put("to", new String(toBytes).trim());
                position += 50;
                // Similar for cc, bcc, subject, etc... (omitted for brevity)
                properties.put("email", email);
            }
        }
    }

    public void printProperties() {
        System.out.println(properties);
    }

    public void write(String newFilename) throws IOException {
        if (newFilename == null) newFilename = filename + ".new";
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            fos.write("AOLVM100".getBytes());
            // Write header placeholder
            byte[] header = new byte[100];
            fos.write(header);
            @SuppressWarnings("unchecked")
            ArrayList<Map<String, Object>> entries = (ArrayList<Map<String, Object>>) properties.get("indexEntries");
            for (Map<String, Object> entry : entries) {
                fos.write("RS".getBytes());
                fos.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) entry.get("pointer")).array());
                // Similar for other fields...
            }
            // Write email data (placeholder)
        }
    }

    // Example usage
    // public static void main(String[] args) throws IOException {
    //     PfcParser parser = new PfcParser("example.pfc");
    //     parser.read();
    //     parser.printProperties();
    //     parser.write(null);
    // }
}

This class reads the file, parses properties (simplified), prints to console, and writes a new file (basic).

  1. JavaScript class for opening, decoding, reading, writing, and printing .PFC properties:
class PfcParser {
  constructor(buffer = null) {
    this.buffer = buffer;
    this.dataView = buffer ? new DataView(buffer) : null;
    this.properties = {};
    this.position = 0;
  }

  read(buffer) {
    this.buffer = buffer;
    this.dataView = new DataView(buffer);
    this.position = 0;

    this.properties.magic = this.readString(8);
    if (this.properties.magic !== 'AOLVM100') {
      throw new Error('Invalid PFC file');
    }

    this.position += 100; // Header placeholder

    this.properties.indexEntries = [];
    while (this.position + 4 < this.buffer.byteLength) {
      const entryStart = this.readString(4);
      if (entryStart !== 'RS\x00\x00') break;
      const pointer = this.dataView.getUint32(this.position, true);
      this.position += 4;
      const contentType = this.dataView.getUint32(this.position, true);
      this.position += 4;
      const flags = this.dataView.getUint8(this.position);
      this.position += 1;
      const timestamp = this.dataView.getUint32(this.position, true);
      this.position += 4;
      this.properties.indexEntries.push({ pointer, contentType, flags, timestamp });
      this.position += 20; // Placeholder
    }

    // Example email parsing (similar to HTML script, omitted for brevity)
  }

  readString(length) {
    // Similar to HTML script
  }

  printProperties() {
    console.log(this.properties);
  }

  write() {
    // Simplified write, returns new ArrayBuffer
    const newBuffer = new ArrayBuffer(1024); // Placeholder size
    const dv = new DataView(newBuffer);
    let pos = 0;
    const magic = 'AOLVM100';
    for (let i = 0; i < magic.length; i++) {
      dv.setUint8(pos++, magic.charCodeAt(i));
    }
    // Add header, index, etc... (placeholder)
    return newBuffer;
  }
}

// Example usage
// const reader = new FileReader();
// reader.onload = (e) => {
//   const parser = new PfcParser();
//   parser.read(e.target.result);
//   parser.printProperties();
//   const newBuffer = parser.write();
// };
// reader.readAsArrayBuffer(file);

This class can be used in Node.js or browser (with buffer). Reads, parses, prints to console, writes new buffer (basic).

  1. C class (using C++ for class support) for opening, decoding, reading, writing, and printing .PFC properties:
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstring>

class PfcParser {
private:
    std::string filename;
    std::vector<char> data;
    std::map<std::string, std::string> properties; // Simplified to string for print

public:
    PfcParser(const std::string& fn) : filename(fn) {}

    void read() {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot open file");
        }
        file.seekg(0, std::ios::end);
        size_t size = file.tellg();
        file.seekg(0, std::ios::beg);
        data.resize(size);
        file.read(data.data(), size);

        size_t position = 0;

        char magic[9];
        memcpy(magic, data.data() + position, 8);
        magic[8] = '\0';
        properties["magic"] = magic;
        position += 8;
        if (std::string(magic) != "AOLVM100") {
            throw std::runtime_error("Invalid PFC file");
        }

        position += 100; // Header

        // Index Table (simplified)
        std::vector<std::map<std::string, uint32_t>> indexEntries;
        while (position + 4 < data.size()) {
            char entryStart[4];
            memcpy(entryStart, data.data() + position, 4);
            if (memcmp(entryStart, "RS\0\0", 4) != 0) break;
            position += 4;
            uint32_t pointer;
            memcpy(&pointer, data.data() + position, 4);
            position += 4;
            uint32_t contentType;
            memcpy(&contentType, data.data() + position, 4);
            position += 4;
            uint8_t flags = *(data.data() + position);
            position += 1;
            uint32_t timestamp;
            memcpy(&timestamp, data.data() + position, 4);
            position += 4;
            std::map<std::string, uint32_t> entry;
            entry["pointer"] = pointer;
            entry["contentType"] = contentType;
            entry["flags"] = flags;
            entry["timestamp"] = timestamp;
            indexEntries.push_back(entry);
            position += 20;
        }
        // Store as string for print
        properties["indexEntries"] = "Count: " + std::to_string(indexEntries.size());

        // Example email (placeholder, similar)
    }

    void printProperties() {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void write(const std::string& newFilename) {
        std::ofstream file(newFilename, std::ios::binary);
        if (!file) {
            throw std::runtime_error("Cannot write file");
        }
        file.write("AOLVM100", 8);
        char header[100] = {0};
        file.write(header, 100);
        // Add index, etc... (placeholder)
    }
};

// Example usage
// int main() {
//     try {
//         PfcParser parser("example.pfc");
//         parser.read();
//         parser.printProperties();
//         parser.write("example.new.pfc");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }

This C++ class reads the file, parses (simplified), prints to console, and writes a new file (basic). Adjust endianness if needed.