4

I am uploading a cab file from a web form, and want to unpack it in memory. I've tried tackling the issue with a CabInfo file, but without success. I do know how to unpack a cab file to my local disk, but do not know how to apply this in memory.

Any assistance would be appreciated.

Adam_G
  • 7,337
  • 20
  • 86
  • 148

4 Answers4

1

If using another library is possible, take a look at this. The description clearly states the library will allow you to extract into memoty.

WIX is an open source project. You could always ask for this feature, ask for a better solution on their forum or simply modify the code for your need.

Vadim

Vadim
  • 2,847
  • 15
  • 18
  • Thanks Vadim. Unfortunately, my company does not allow me to use 3rd-party libraries. – Adam_G Jul 18 '13 at 18:06
  • I can use p/invoke, though, which would allow me to use the c++ API FDICopy() (http://msdn.microsoft.com/en-us/library/bb432270(v=vs.85).aspx). Do you know how I would implement using this code? – Adam_G Jul 18 '13 at 19:58
  • Look at the source of the project I proposed using. Specifically at Extract.hpp. There's a function called ExtractFileW, if msw_TargetDir is "MEMORY", then mb_ExtractToMemory is set to true. This will in turn affect the way FdiWrite will work. When you create an FDIContext, you provide your own write handler, this handler gets the data and if mb_ExtractToMemory is true it will write it to memory. However, it seems you will not be able to perform all this with a single PINVOKE. I would take create my own C++ dll based on the above mentioned code and PINVOKE it. – Vadim Jul 19 '13 at 18:14
  • The magic really happens with FCICreate and FNFCIWRITE. As far as I see, you'll have to write your own FNFCIWRITE (or reuse parts from the project), call FCICreate and only then call FDICOPY. – Vadim Jul 19 '13 at 18:15
  • Thanks Vadim. I had to move past this part of the project for the moment being, but will come back to you soon on this. – Adam_G Jul 19 '13 at 21:18
1

Your initial question seems to indicate that you are allowed to use the Microsoft.Deployment.Compression DLL. If true, the code below should get you close to what you want:

CabEngine engine = new CabEngine();
foreach (ArchiveFileInfo archiveFileInfo in engine.GetFileInfo(fileStream))
{
    Stream stream = engine.Unpack(fileStream, archiveFileInfo.Name);
    byte[] buffer = new byte[stream.Length];
    //The (int) below is a dirty trick for demonstration purposes only; 
    //re-work for production code
    stream.Read(buffer, 0, (int)stream.Length);
}

Later answers from you seem to indicate that you are not allowed to use 3rd party DLLs in which case you will want to use the FDI* API. This link has some code that you will be able to modify from using hard-coded paths to (memory) streams: Extract .cab file in C#

Community
  • 1
  • 1
M Miller
  • 86
  • 3
0

There is a ready to use project that you can download: Cabinet File (*.CAB) Compression and Extraction

According to version history since Sep 2008 "..you can extract directly to memory."

MikroDel
  • 6,705
  • 7
  • 39
  • 74
0

Here is a utility class that should work in memory (it supports x86 or x64 compilation). Here is out you would use it, if we suppose a .CAB file has been uploaded into ASP.NET using the standard upload protocol:

    using (CabFile file = new CabFile(HttpContext.Current.Request.Files[0].InputStream))
    {
        file.EntryExtract += CabEntryExtract;
        file.ExtractEntries();
    }

    static void CabEntryExtract(object sender, CabEntryExtractEventArgs e)
    {
        // e.Entry.Name contains the entry name
        // e.Entry.Data contains a byte[] with the entry data
        // e.Entry.LastWriteTime contains the entry last write time
        // e.Entry.Size contains the entry uncompressed size
    }

And here is the utility and associated classes:

public sealed class CabFile : IDisposable
{
    private IntPtr _hfdi;
    private ERF _erf;
    private GCHandle _erfHandle;
    private byte[] _data;
    private Dictionary<IntPtr, object> _handles = new Dictionary<IntPtr, object>();
    private MemoryStream _currentEntryData;
    private FNALLOC _alloc;
    private FNCLOSE _close;
    private FNFREE _free;
    private FNOPEN _open;
    private FNREAD _read;
    private FNWRITE _write;
    private FNSEEK _seek;
    private FNFDINOTIFY _extract;

    public event EventHandler<CabEntryExtractEventArgs> EntryExtract;

    public CabFile(string filePath)
        : this(GetStream(filePath))
    {
    }

    private static Stream GetStream(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException("filePath");

        return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
    }

    public CabFile(Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException("stream");

        using (MemoryStream data = new MemoryStream())
        {
            stream.CopyTo(data);
            _data = data.ToArray();
        }
        _erf = new ERF();
        _alloc = new FNALLOC(FnAlloc);
        _free = new FNFREE(FnFree);
        _close = new FNCLOSE(FnClose);
        _open = new FNOPEN(FnOpen);
        _read = new FNREAD(FnRead);
        _write = new FNWRITE(FnWrite);
        _seek = new FNSEEK(FnSeek);
        _extract = new FNFDINOTIFY(FnNotifyExtract);

        _erfHandle = GCHandle.Alloc(_erf, GCHandleType.Pinned);
        _hfdi = FDICreate(
            Marshal.GetFunctionPointerForDelegate(_alloc),
            Marshal.GetFunctionPointerForDelegate(_free),
            Marshal.GetFunctionPointerForDelegate(_open),
            Marshal.GetFunctionPointerForDelegate(_read),
            Marshal.GetFunctionPointerForDelegate(_write),
            Marshal.GetFunctionPointerForDelegate(_close),
            Marshal.GetFunctionPointerForDelegate(_seek)
            , -1, _erfHandle.AddrOfPinnedObject());
    }

    public void ExtractEntries()
    {
        FDICopy(_hfdi, string.Empty, string.Empty, 0, Marshal.GetFunctionPointerForDelegate(_extract), IntPtr.Zero, IntPtr.Zero);
    }

    public void Dispose()
    {
        if (_hfdi != IntPtr.Zero)
        {
            FDIDestroy(_hfdi);
            _hfdi = IntPtr.Zero;
        }

        _erfHandle.Free();
    }

    private void OnEntryExtract(CabEntry entry)
    {
        EventHandler<CabEntryExtractEventArgs> handler = EntryExtract;
        if (handler != null)
        {
            handler(this, new CabEntryExtractEventArgs(entry));
        }
    }

    private IntPtr FnAlloc(int cb)
    {
        return Marshal.AllocHGlobal(cb);
    }

    private void FnFree(IntPtr pv)
    {
        Marshal.FreeHGlobal(pv);
    }

    private IntPtr FnOpen(string pszFile, int oflag, int pmode)
    {
        // only used for reading archive
        IntPtr h = new IntPtr(_handles.Count + 1);
        _handles.Add(h, 0);
        return h;
    }

    private int FnRead(IntPtr hf, byte[] pv, int cb)
    {
        // only used for reading archive
        int pos = (int)_handles[hf];
        int left = _data.Length - pos;
        int read = Math.Min(left, cb);
        if (read > 0)
        {
            Array.Copy(_data, pos, pv, 0, read);
            _handles[hf] = pos + read;
        }
        return read;
    }

    private int FnWrite(IntPtr hf, byte[] pv, int cb)
    {
        // only used for writing entries
        _currentEntryData.Write(pv, 0, cb);
        return cb;
    }

    private int FnClose(IntPtr hf)
    {
        object o = _handles[hf];
        CabEntry entry = o as CabEntry;
        if (entry != null)
        {
            entry.Data = _currentEntryData.ToArray();
            _currentEntryData.Dispose();
        }
        _handles.Remove(hf);
        return 0;
    }

    private int FnSeek(IntPtr hf, int dist, SeekOrigin seektype)
    {
        // only used for seeking archive
        int pos;
        switch (seektype)
        {
            case SeekOrigin.Begin:
                pos = dist;
                break;

            case SeekOrigin.Current:
                pos = (int)_handles[hf] + dist;
                break;

            //case SeekOrigin.End:
            default:
                pos = _data.Length + dist;
                break;
        }
        _handles[hf] = pos;
        return pos;
    }

    private IntPtr FnNotifyExtract(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION fdin)
    {
        CabEntry entry;
        switch (fdint)
        {
            case FDINOTIFICATIONTYPE.COPY_FILE:
                entry = new CabEntry(fdin);
                entry._handle = new IntPtr(_handles.Count + 1);
                _handles.Add(entry._handle, entry);
                _currentEntryData = new MemoryStream();
                return entry._handle;

            case FDINOTIFICATIONTYPE.CLOSE_FILE_INFO:
                entry = (CabEntry)_handles[fdin.hf];
                FnClose(fdin.hf);
                OnEntryExtract(entry);
                return new IntPtr(1);

            default:
                return IntPtr.Zero;
        }
    }

    private enum FDINOTIFICATIONTYPE
    {
        CABINET_INFO = 0,
        PARTIAL_FILE = 1,
        COPY_FILE = 2,
        CLOSE_FILE_INFO = 3,
        NEXT_CABINET = 4,
        ENUMERATE = 5,
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct ERF
    {
        public int erfOper;
        public int erfType;
        public int fError;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    internal class FDINOTIFICATION
    {
        public int cb;
        public IntPtr psz1;
        public IntPtr psz2;
        public IntPtr psz3;
        public IntPtr pv;
        public IntPtr hf;
        public ushort date;
        public ushort time;
        public ushort attribs;
        public ushort setID;
        public ushort iCabinet;
        public ushort iFolder;
        public int fdie;
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate IntPtr FNALLOC(int cb);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void FNFREE(IntPtr pv);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private delegate IntPtr FNOPEN(string pszFile, int oflag, int pmode);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int FNREAD(IntPtr hf, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int FNWRITE(IntPtr hf, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int FNCLOSE(IntPtr hf);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate int FNSEEK(IntPtr hf, int dist, SeekOrigin seektype);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate IntPtr FNFDINOTIFY(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION pfdin);

    [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private static extern IntPtr FDICreate(IntPtr pfnalloc, IntPtr pfnfree, IntPtr pfnopen, IntPtr pfnread, IntPtr pfnwriter, IntPtr pfnclose, IntPtr pfnseek, int cpuType, IntPtr perf);

    [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr FDIDestroy(IntPtr hdfi);

    [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private static extern IntPtr FDICopy(IntPtr hdfi, string pszCabinet, string pszCabPath, int flags, IntPtr fnNotify, IntPtr fnDecrypt, IntPtr userData);
}

public sealed class CabEntry
{
    internal IntPtr _handle;

    internal CabEntry(CabFile.FDINOTIFICATION fdin)
    {
        Name = Marshal.PtrToStringAnsi(fdin.psz1);
        Size = fdin.cb;
        LastWriteTime = new DateTime(1980 + GetMask(fdin.date, 9, 15), GetMask(fdin.date, 5, 8), GetMask(fdin.date, 0, 4),
            GetMask(fdin.time, 11, 15), GetMask(fdin.time, 5, 10), 2 * GetMask(fdin.time, 0, 4));
    }

    private static int GetMask(int value, byte startByte, byte endByte)
    {
        int final = 0;
        int v = 1;
        for (byte b = startByte; b <= endByte; b++)
        {
            if ((value & (1 << b)) != 0)
            {
                final += v;
            }
            v = v * 2;
        }
        return final;
    }

    public string Name { get; private set; }
    public int Size { get; private set; }
    public DateTime LastWriteTime { get; private set; }
    public byte[] Data { get; internal set; }
}

public sealed class CabEntryExtractEventArgs : EventArgs
{
    public CabEntryExtractEventArgs(CabEntry entry)
    {
        if (entry == null)
            throw new ArgumentNullException("entry");

        Entry = entry;
    }

    public CabEntry Entry { get; private set; }
}

NOTE: this code allocates big byte[] chunks so it could be optimized to use things like ChunkedMemoryStream (available for example in this library: CodeFluent Runtime Client) instead of byte[] to avoid impacting the LOH (Large Object Heap) too much.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298