// EnumDirItems.cpp

#include "StdAfx.h"

#include "../../../Common/Wildcard.h"

#include "../../../Windows/FileDir.h"
#include "../../../Windows/FileIO.h"
#include "../../../Windows/FileName.h"

#if defined(_WIN32) && !defined(UNDER_CE)
#define _USE_SECURITY_CODE
#include "../../../Windows/SecurityUtils.h"
#endif

#include "EnumDirItems.h"

using namespace NWindows;
using namespace NFile;
using namespace NName;

void AddDirFileInfo(int phyParent, int logParent, int secureIndex,
    const NFind::CFileInfo &fi, CObjectVector<CDirItem> &dirItems)
{
  CDirItem di;
  di.Size = fi.Size;
  di.CTime = fi.CTime;
  di.ATime = fi.ATime;
  di.MTime = fi.MTime;
  di.Attrib = fi.Attrib;
  di.IsAltStream = fi.IsAltStream;
  di.PhyParent = phyParent;
  di.LogParent = logParent;
  di.SecureIndex = secureIndex;
  di.Name = fs2us(fi.Name);
  #if defined(_WIN32) && !defined(UNDER_CE)
  // di.ShortName = fs2us(fi.ShortName);
  #endif
  dirItems.Add(di);
}

UString CDirItems::GetPrefixesPath(const CIntVector &parents, int index, const UString &name) const
{
  UString path;
  unsigned len = name.Len();
  int i;
  for (i = index; i >= 0; i = parents[i])
    len += Prefixes[i].Len();
  unsigned totalLen = len;
  wchar_t *p = path.GetBuffer(len);
  p[len] = 0;
  len -= name.Len();
  memcpy(p + len, (const wchar_t *)name, name.Len() * sizeof(wchar_t));
  for (i = index; i >= 0; i = parents[i])
  {
    const UString &s = Prefixes[i];
    len -= s.Len();
    memcpy(p + len, (const wchar_t *)s, s.Len() * sizeof(wchar_t));
  }
  path.ReleaseBuffer(totalLen);
  return path;
}

UString CDirItems::GetPhyPath(unsigned index) const
{
  const CDirItem &di = Items[index];
  return GetPrefixesPath(PhyParents, di.PhyParent, di.Name);
}

UString CDirItems::GetLogPath(unsigned index) const
{
  const CDirItem &di = Items[index];
  return GetPrefixesPath(LogParents, di.LogParent, di.Name);
}

void CDirItems::ReserveDown()
{
  Prefixes.ReserveDown();
  PhyParents.ReserveDown();
  LogParents.ReserveDown();
  Items.ReserveDown();
}

unsigned CDirItems::AddPrefix(int phyParent, int logParent, const UString &prefix)
{
  PhyParents.Add(phyParent);
  LogParents.Add(logParent);
  return Prefixes.Add(prefix);
}

void CDirItems::DeleteLastPrefix()
{
  PhyParents.DeleteBack();
  LogParents.DeleteBack();
  Prefixes.DeleteBack();
}

bool InitLocalPrivileges();

CDirItems::CDirItems():
    SymLinks(false),
    TotalSize(0)
    #ifdef _USE_SECURITY_CODE
    , ReadSecure(false)
    #endif
{
  #ifdef _USE_SECURITY_CODE
  _saclEnabled = InitLocalPrivileges();
  #endif
}

#ifdef _USE_SECURITY_CODE

void CDirItems::AddSecurityItem(const FString &path, int &secureIndex)
{
  secureIndex = -1;

  SECURITY_INFORMATION securInfo =
      DACL_SECURITY_INFORMATION |
      GROUP_SECURITY_INFORMATION |
      OWNER_SECURITY_INFORMATION;
  if (_saclEnabled)
    securInfo |= SACL_SECURITY_INFORMATION;

  DWORD errorCode = 0;
  DWORD secureSize;
  BOOL res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
  if (res)
  {
    if (secureSize == 0)
      return;
    if (secureSize > TempSecureBuf.Size())
      errorCode = ERROR_INVALID_FUNCTION;
  }
  else
  {
    errorCode = GetLastError();
    if (errorCode == ERROR_INSUFFICIENT_BUFFER)
    {
      if (secureSize <= TempSecureBuf.Size())
        errorCode = ERROR_INVALID_FUNCTION;
      else
      {
        TempSecureBuf.Alloc(secureSize);
        res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize);
        if (res)
        {
          if (secureSize != TempSecureBuf.Size())
            errorCode = ERROR_INVALID_FUNCTION;;
        }
        else
          errorCode = GetLastError();
      }
    }
  }
  if (res)
  {
    secureIndex = SecureBlocks.AddUniq(TempSecureBuf, secureSize);
    return;
  }
  if (errorCode == 0)
    errorCode = ERROR_INVALID_FUNCTION;
  AddError(path, errorCode);
}

#endif

void CDirItems::EnumerateDir(int phyParent, int logParent, const FString &phyPrefix)
{
  NFind::CEnumerator enumerator(phyPrefix + FCHAR_ANY_MASK);
  for (;;)
  {
    NFind::CFileInfo fi;
    bool found;
    if (!enumerator.Next(fi, found))
    {
      AddError(phyPrefix);
      return;
    }
    if (!found)
      break;

    int secureIndex = -1;
    #ifdef _USE_SECURITY_CODE
    if (ReadSecure)
      AddSecurityItem(phyPrefix + fi.Name, secureIndex);
    #endif
    
    AddDirFileInfo(phyParent, logParent, secureIndex, fi, Items);
    
    if (fi.IsDir())
    {
      const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
      unsigned parent = AddPrefix(phyParent, logParent, fs2us(name2));
      EnumerateDir(parent, parent, phyPrefix + name2);
    }
  }
}

void CDirItems::EnumerateItems2(
    const FString &phyPrefix,
    const UString &logPrefix,
    const FStringVector &filePaths,
    FStringVector *requestedPaths)
{
  int phyParent = phyPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, fs2us(phyPrefix));
  int logParent = logPrefix.IsEmpty() ? -1 : AddPrefix(-1, -1, logPrefix);

  FOR_VECTOR (i, filePaths)
  {
    const FString &filePath = filePaths[i];
    NFind::CFileInfo fi;
    const FString phyPath = phyPrefix + filePath;
    if (!fi.Find(phyPath))
    {
      AddError(phyPath);
      continue;
    }
    if (requestedPaths)
      requestedPaths->Add(phyPath);

    int delimiter = filePath.ReverseFind(FCHAR_PATH_SEPARATOR);
    FString phyPrefixCur;
    int phyParentCur = phyParent;
    if (delimiter >= 0)
    {
      phyPrefixCur.SetFrom(filePath, delimiter + 1);
      phyParentCur = AddPrefix(phyParent, logParent, fs2us(phyPrefixCur));
    }

    int secureIndex = -1;
    #ifdef _USE_SECURITY_CODE
    if (ReadSecure)
      AddSecurityItem(phyPath, secureIndex);
    #endif

    AddDirFileInfo(phyParentCur, logParent, secureIndex, fi, Items);
    
    if (fi.IsDir())
    {
      const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR;
      unsigned parent = AddPrefix(phyParentCur, logParent, fs2us(name2));
      EnumerateDir(parent, parent, phyPrefix + phyPrefixCur + name2);
    }
  }
  ReserveDown();
}






static HRESULT EnumerateDirItems(
    const NWildcard::CCensorNode &curNode,
    int phyParent, int logParent, const FString &phyPrefix,
    const UStringVector &addArchivePrefix,
    CDirItems &dirItems,
    bool enterToSubFolders,
    IEnumDirItemCallback *callback);

static HRESULT EnumerateDirItems_Spec(
    const NWildcard::CCensorNode &curNode,
    int phyParent, int logParent, const FString &curFolderName,
    const FString &phyPrefix,
    const UStringVector &addArchivePrefix,
    CDirItems &dirItems,
    bool enterToSubFolders,
    IEnumDirItemCallback *callback)
{
  const FString name2 = curFolderName + FCHAR_PATH_SEPARATOR;
  unsigned parent = dirItems.AddPrefix(phyParent, logParent, fs2us(name2));
  unsigned numItems = dirItems.Items.Size();
  HRESULT res = EnumerateDirItems(
      curNode, parent, parent, phyPrefix + name2,
      addArchivePrefix, dirItems, enterToSubFolders, callback);
  if (numItems == dirItems.Items.Size())
    dirItems.DeleteLastPrefix();
  return res;
}

#ifndef UNDER_CE

static void EnumerateAltStreams(
    const NFind::CFileInfo &fi,
    const NWildcard::CCensorNode &curNode,
    int phyParent, int logParent, const FString &phyPrefix,
    const UStringVector &addArchivePrefix,  // prefix from curNode
    CDirItems &dirItems)
{
  const FString fullPath = phyPrefix + fi.Name;
  NFind::CStreamEnumerator enumerator(fullPath);
  for (;;)
  {
    NFind::CStreamInfo si;
    bool found;
    if (!enumerator.Next(si, found))
    {
      dirItems.AddError(fullPath + FTEXT(":*"), (DWORD)E_FAIL);
      break;
    }
    if (!found)
      break;
    if (si.IsMainStream())
      continue;
    UStringVector addArchivePrefixNew = addArchivePrefix;
    UString reducedName = si.GetReducedName();
    addArchivePrefixNew.Back() += reducedName;
    if (curNode.CheckPathToRoot(false, addArchivePrefixNew, true))
      continue;
    NFind::CFileInfo fi2 = fi;
    fi2.Name += us2fs(reducedName);
    fi2.Size = si.Size;
    fi2.Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
    fi2.IsAltStream = true;
    AddDirFileInfo(phyParent, logParent, -1, fi2, dirItems.Items);
    dirItems.TotalSize += fi2.Size;
  }
}

void CDirItems::SetLinkInfo(CDirItem &dirItem, const NFind::CFileInfo &fi,
    const FString &phyPrefix)
{
  if (!SymLinks || !fi.HasReparsePoint())
    return;
  const FString path = phyPrefix + fi.Name;
  CByteBuffer &buf = dirItem.ReparseData;
  if (NIO::GetReparseData(path, buf))
  {
    CReparseAttr attr;
    if (attr.Parse(buf, buf.Size()))
      return;
  }
  AddError(path);
  buf.Free();
}

#endif

static HRESULT EnumerateForItem(
    NFind::CFileInfo &fi,
    const NWildcard::CCensorNode &curNode,
    int phyParent, int logParent, const FString &phyPrefix,
    const UStringVector &addArchivePrefix,  // prefix from curNode
    CDirItems &dirItems,
    bool enterToSubFolders,
    IEnumDirItemCallback *callback)
{
  const UString name = fs2us(fi.Name);
  bool enterToSubFolders2 = enterToSubFolders;
  UStringVector addArchivePrefixNew = addArchivePrefix;
  addArchivePrefixNew.Add(name);
  {
    UStringVector addArchivePrefixNewTemp(addArchivePrefixNew);
    if (curNode.CheckPathToRoot(false, addArchivePrefixNewTemp, !fi.IsDir()))
      return S_OK;
  }
  int dirItemIndex = -1;
  
  if (curNode.CheckPathToRoot(true, addArchivePrefixNew, !fi.IsDir()))
  {
    int secureIndex = -1;
    #ifdef _USE_SECURITY_CODE
    if (dirItems.ReadSecure)
      dirItems.AddSecurityItem(phyPrefix + fi.Name, secureIndex);
    #endif
    
    dirItemIndex = dirItems.Items.Size();
    AddDirFileInfo(phyParent, logParent, secureIndex, fi, dirItems.Items);
    dirItems.TotalSize += fi.Size;
    if (fi.IsDir())
      enterToSubFolders2 = true;
  }

  #ifndef UNDER_CE
  if (dirItems.ScanAltStreams)
  {
    EnumerateAltStreams(fi, curNode, phyParent, logParent, phyPrefix,
        addArchivePrefixNew, dirItems);
  }

  if (dirItemIndex >= 0)
  {
    CDirItem &dirItem = dirItems.Items[dirItemIndex];
    dirItems.SetLinkInfo(dirItem, fi, phyPrefix);
    if (dirItem.ReparseData.Size() != 0)
      return S_OK;
  }
  #endif
  
  if (!fi.IsDir())
    return S_OK;
  
  const NWildcard::CCensorNode *nextNode = 0;
  if (addArchivePrefix.IsEmpty())
  {
    int index = curNode.FindSubNode(name);
    if (index >= 0)
      nextNode = &curNode.SubNodes[index];
  }
  if (!enterToSubFolders2 && nextNode == 0)
    return S_OK;
  
  addArchivePrefixNew = addArchivePrefix;
  if (nextNode == 0)
  {
    nextNode = &curNode;
    addArchivePrefixNew.Add(name);
  }
  
  return EnumerateDirItems_Spec(
      *nextNode, phyParent, logParent, fi.Name, phyPrefix,
      addArchivePrefixNew,
      dirItems,
      enterToSubFolders2, callback);
}


static bool CanUseFsDirect(const NWildcard::CCensorNode &curNode)
{
  FOR_VECTOR (i, curNode.IncludeItems)
  {
    const NWildcard::CItem &item = curNode.IncludeItems[i];
    if (item.Recursive || item.PathParts.Size() != 1)
      return false;
    const UString &name = item.PathParts.Front();
    if (name.IsEmpty())
      return false;
    
    /* Windows doesn't support file name with wildcard.
       but if another system supports file name with wildcard,
       and wildcard mode is disabled, we can ignore wildcard in name */
    /*
    if (!item.WildcardParsing)
      continue;
    */
    if (DoesNameContainWildcard(name))
      return false;
  }
  return true;
}


static HRESULT EnumerateDirItems(
    const NWildcard::CCensorNode &curNode,
    int phyParent, int logParent, const FString &phyPrefix,
    const UStringVector &addArchivePrefix,  // prefix from curNode
    CDirItems &dirItems,
    bool enterToSubFolders,
    IEnumDirItemCallback *callback)
{
  if (!enterToSubFolders)
    if (curNode.NeedCheckSubDirs())
      enterToSubFolders = true;
  if (callback)
    RINOK(callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.Size(), dirItems.TotalSize, fs2us(phyPrefix), true));

  // try direct_names case at first
  if (addArchivePrefix.IsEmpty() && !enterToSubFolders)
  {
    if (CanUseFsDirect(curNode))
    {
      // all names are direct (no wildcards)
      // so we don't need file_system's dir enumerator
      CRecordVector<bool> needEnterVector;
      unsigned i;

      for (i = 0; i < curNode.IncludeItems.Size(); i++)
      {
        const NWildcard::CItem &item = curNode.IncludeItems[i];
        const UString &name = item.PathParts.Front();
        const FString fullPath = phyPrefix + us2fs(name);
        NFind::CFileInfo fi;
        #ifdef _WIN32
        if (phyPrefix.IsEmpty() && item.IsDriveItem())
        {
          fi.SetAsDir();
          fi.Name = fullPath;
        }
        else
        #endif
        if (!fi.Find(fullPath))
        {
          dirItems.AddError(fullPath);
          continue;
        }
        bool isDir = fi.IsDir();
        if (isDir && !item.ForDir || !isDir && !item.ForFile)
        {
          dirItems.AddError(fullPath, (DWORD)E_FAIL);
          continue;
        }
        {
          UStringVector pathParts;
          pathParts.Add(fs2us(fi.Name));
          if (curNode.CheckPathToRoot(false, pathParts, !isDir))
            continue;
        }
        
        int secureIndex = -1;
        #ifdef _USE_SECURITY_CODE
        if (dirItems.ReadSecure)
          dirItems.AddSecurityItem(fullPath, secureIndex);
        #endif

        AddDirFileInfo(phyParent, logParent, secureIndex, fi, dirItems.Items);

        #ifndef UNDER_CE
        {
          CDirItem &dirItem = dirItems.Items.Back();
          dirItems.SetLinkInfo(dirItem, fi, phyPrefix);
          if (dirItem.ReparseData.Size() != 0)
            continue;
        }
        #endif

        dirItems.TotalSize += fi.Size;

        #ifndef UNDER_CE
        if (dirItems.ScanAltStreams)
        {
          UStringVector pathParts;
          pathParts.Add(fs2us(fi.Name));
          EnumerateAltStreams(fi, curNode, phyParent, logParent, phyPrefix,
              pathParts, dirItems);
        }
        #endif

        if (!isDir)
          continue;
        
        UStringVector addArchivePrefixNew;
        const NWildcard::CCensorNode *nextNode = 0;
        int index = curNode.FindSubNode(name);
        if (index >= 0)
        {
          for (int t = needEnterVector.Size(); t <= index; t++)
            needEnterVector.Add(true);
          needEnterVector[index] = false;
          nextNode = &curNode.SubNodes[index];
        }
        else
        {
          nextNode = &curNode;
          addArchivePrefixNew.Add(name); // don't change it to fi.Name. It's for shortnames support
        }

        RINOK(EnumerateDirItems_Spec(*nextNode, phyParent, logParent, fi.Name, phyPrefix,
            addArchivePrefixNew, dirItems, true, callback));
      }
      
      for (i = 0; i < curNode.SubNodes.Size(); i++)
      {
        if (i < needEnterVector.Size())
          if (!needEnterVector[i])
            continue;
        const NWildcard::CCensorNode &nextNode = curNode.SubNodes[i];
        const FString fullPath = phyPrefix + us2fs(nextNode.Name);
        NFind::CFileInfo fi;
        #ifdef _WIN32
        if (phyPrefix.IsEmpty() && NWildcard::IsDriveColonName(nextNode.Name))
        {
          fi.SetAsDir();
          fi.Name = fullPath;
        }
        else
        #endif
        if (!fi.Find(fullPath))
        {
          if (!nextNode.AreThereIncludeItems())
            continue;
          dirItems.AddError(fullPath);
          continue;
        }
        if (!fi.IsDir())
        {
          dirItems.AddError(fullPath, (DWORD)E_FAIL);
          continue;
        }

        RINOK(EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix,
            UStringVector(), dirItems, false, callback));
      }

      return S_OK;
    }
  }

  #ifdef _WIN32
  #ifndef UNDER_CE

  // scan drives, if wildcard is "*:\"

  if (phyPrefix.IsEmpty() && curNode.IncludeItems.Size() > 0)
  {
    unsigned i;
    for (i = 0; i < curNode.IncludeItems.Size(); i++)
    {
      const NWildcard::CItem &item = curNode.IncludeItems[i];
      if (item.PathParts.Size() < 1)
        break;
      const UString &name = item.PathParts.Front();
      if (name.Len() != 2 || name[1] != ':')
        break;
      if (item.PathParts.Size() == 1)
        if (item.ForFile || !item.ForDir)
          break;
      if (NWildcard::IsDriveColonName(name))
        continue;
      if (name[0] != '*' && name[0] != '?')
        break;
    }
    if (i == curNode.IncludeItems.Size())
    {
      FStringVector driveStrings;
      NFind::MyGetLogicalDriveStrings(driveStrings);
      for (i = 0; i < driveStrings.Size(); i++)
      {
        FString driveName = driveStrings[i];
        if (driveName.Len() < 3 || driveName.Back() != '\\')
          return E_FAIL;
        driveName.DeleteBack();
        NFind::CFileInfo fi;
        fi.SetAsDir();
        fi.Name = driveName;

        RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix,
            addArchivePrefix, dirItems, enterToSubFolders, callback));
      }
      return S_OK;
    }
  }
  
  #endif
  #endif

  NFind::CEnumerator enumerator(phyPrefix + FCHAR_ANY_MASK);
  for (unsigned ttt = 0; ; ttt++)
  {
    NFind::CFileInfo fi;
    bool found;
    if (!enumerator.Next(fi, found))
    {
      dirItems.AddError(phyPrefix);
      break;
    }
    if (!found)
      break;

    if (callback && (ttt & 0xFF) == 0xFF)
      RINOK(callback->ScanProgress(dirItems.GetNumFolders(), dirItems.Items.Size(), dirItems.TotalSize, fs2us(phyPrefix), true));

    RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix,
          addArchivePrefix, dirItems, enterToSubFolders, callback));
  }
  return S_OK;
}

HRESULT EnumerateItems(
    const NWildcard::CCensor &censor,
    const NWildcard::ECensorPathMode pathMode,
    const UString &addPathPrefix,
    CDirItems &dirItems,
    IEnumDirItemCallback *callback)
{
  FOR_VECTOR (i, censor.Pairs)
  {
    const NWildcard::CPair &pair = censor.Pairs[i];
    int phyParent = pair.Prefix.IsEmpty() ? -1 : dirItems.AddPrefix(-1, -1, pair.Prefix);
    int logParent = -1;
    
    if (pathMode == NWildcard::k_AbsPath)
      logParent = phyParent;
    else
    {
      if (!addPathPrefix.IsEmpty())
        logParent = dirItems.AddPrefix(-1, -1, addPathPrefix);
    }
    
    RINOK(EnumerateDirItems(pair.Head, phyParent, logParent, us2fs(pair.Prefix), UStringVector(),
        dirItems,
        false, // enterToSubFolders
        callback));
  }
  dirItems.ReserveDown();

  #if defined(_WIN32) && !defined(UNDER_CE)
  dirItems.FillFixedReparse();
  #endif

  return S_OK;
}

#if defined(_WIN32) && !defined(UNDER_CE)

void CDirItems::FillFixedReparse()
{
  /* imagex/WIM reduces absolute pathes in links (raparse data),
     if we archive non root folder. We do same thing here */

  if (!SymLinks)
    return;
  
  FOR_VECTOR(i, Items)
  {
    CDirItem &item = Items[i];
    if (item.ReparseData.Size() == 0)
      continue;
    
    CReparseAttr attr;
    if (!attr.Parse(item.ReparseData, item.ReparseData.Size()))
      continue;
    if (attr.IsRelative())
      continue;

    const UString &link = attr.GetPath();
    if (!IsDrivePath(link))
      continue;
    // maybe we need to support networks paths also ?

    FString fullPathF;
    if (!NDir::MyGetFullPathName(us2fs(GetPhyPath(i)), fullPathF))
      continue;
    UString fullPath = fs2us(fullPathF);
    const UString logPath = GetLogPath(i);
    if (logPath.Len() >= fullPath.Len())
      continue;
    if (CompareFileNames(logPath, fullPath.RightPtr(logPath.Len())) != 0)
      continue;
    
    const UString prefix = fullPath.Left(fullPath.Len() - logPath.Len());
    if (prefix.Back() != WCHAR_PATH_SEPARATOR)
      continue;

    unsigned rootPrefixSize = GetRootPrefixSize(prefix);
    if (rootPrefixSize == 0)
      continue;
    if (rootPrefixSize == prefix.Len())
      continue; // simple case: paths are from root

    if (link.Len() <= prefix.Len())
      continue;

    if (CompareFileNames(link.Left(prefix.Len()), prefix) != 0)
      continue;

    UString newLink = prefix.Left(rootPrefixSize);
    newLink += link.Ptr(prefix.Len());

    CByteBuffer data;
    if (!FillLinkData(data, newLink, attr.IsSymLink()))
      continue;
    item.ReparseData2 = data;
  }
}

#endif