Skip to content

Question: Have you ever tried to decode the Thread-Index header field? #1103

@Sicos1977

Description

@Sicos1977

I have a use case where I want to use the Thread-Index header to link messages together (Yes I know there is also the References field but that one is not always filled in my case). I tried decoded this header AQHbJet7Z+efu/5M5UWYnpinBaQePrKfAKzegAAO5bCAAAHygIAAD3LwgAG3uyCAAAECjYAXUgfggASoxyCAAAqegIADX0fwgAFtahCAAAThwIAAAMtwgAAAupCAAAEUEIAAImAggAAHlkCAAC0xcA== with some example code that I found on the internet.

        try
        {
            Raw = threadIndex;

            var bytes = Convert.FromBase64String(threadIndex);

            // Thread index length should be 22 plus extra 5 bytes per reply
            if (bytes.Length < 22 || (bytes.Length - 22) % 5 != 0)
                return;

            Id = new Guid(bytes.Skip(6).Take(16).ToArray());
            var childBlockCount = (bytes.Length - 22) / 5;

            if (childBlockCount == 0)
                Date = DateTime.FromFileTimeUtc(bytes.Skip(1).Take(6).Select(b => (long)b).Aggregate((l1, l2) => (l1 << 8) + l2) << 16).ToLocalTime();
            else
            {
                Date = DateTime.FromFileTimeUtc(bytes.Take(6).Select(b => (long)b).Aggregate((l1, l2) => (l1 << 8) + l2) << 16).ToLocalTime();
                Dates = [Date];

                for (var i = 0; i < childBlockCount; i++)
                {
                    var childTicks = bytes.Skip(22 + i * 5).Take(4).Select(b => (long)b).Aggregate((l1, l2) => (l1 << 8) + l2) << 18;
                    childTicks &= ~((long)1 << 50);
                    Date = Date.AddTicks(childTicks);
                    if (i < childBlockCount - 1) Dates.Add(Date);
                }
            }
        }
        catch 
        {
            // Ignore
        }

With some headers this works but most of the times I just get dated in the year 1830 or in 1601 so something is different.

I also tried following this documentation --> https://learn.microsoft.com/en-us/office/client-developer/outlook/mapi/tracking-conversations?redirectedfrom=MSDN ... but still no luck.

internal ThreadIndex(string threadIndex)
{
    try
    {
        Raw = threadIndex;

        // Decode base64 string to bytes
        var bytes = Convert.FromBase64String(threadIndex);

        // Validate minimum length (22 bytes for the header block)
        if (bytes.Length < 22 || (bytes.Length - 22) % 5 != 0)
            return;

        // Parse the header block
        if (bytes[0] != 1) // Reserved byte must be 1
            return;

        // Extract and compute the date from the FILETIME format in the header
        var headerTicks = bytes.Skip(1).Take(5).Select(b => (long)b).Aggregate((l1, l2) => (l1 << 8) + l2) << 16;
        Date = DateTime.FromFileTimeUtc(headerTicks).ToLocalTime();

        // Extract GUID from the header block
        Id = new Guid(bytes.Skip(6).Take(16).ToArray());

        // Prepare the list of dates
        Dates = new List<DateTime> { Date };

        // Parse child blocks (if present)
        var childBlockCount = (bytes.Length - 22) / 5;
        for (var i = 0; i < childBlockCount; i++)
        {
            // Extract each child block (5 bytes)
            var childBytes = bytes.Skip(22 + i * 5).Take(5).ToArray();

            // Extract the first bit (time encoding strategy)
            var strategyBit = (childBytes[0] & 0b10000000) != 0;

            // Extract the 31 bits for time difference
            var timeDiff = ((childBytes[0] & 0b01111111) << 24)
                         | (childBytes[1] << 16)
                         | (childBytes[2] << 8)
                         | childBytes[3];

            if (strategyBit)
                timeDiff <<= 23; // High-precision, shift low 23 bits
            else
                timeDiff <<= 18; // Low-precision, shift low 18 bits

            // Mask out the 50th bit to handle the encoding properly
            timeDiff &= ~((long)1 << 50);

            // Compute the child date using the time difference
            var childDate = Date.AddTicks(timeDiff);
            Dates.Add(childDate);
        }
    }
    catch
    {
        // Ignore exceptions for invalid input
    }
}

So I was wondering if you ever tried to do something like this with MimeKit?

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionA question about how to do something

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions