// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/installer/util/lzma_util.h"

#include "base/file_util.h"
#include "base/string_util.h"

extern "C" {
#include "third_party/lzma_sdk/Archive/7z/7zExtract.h"
#include "third_party/lzma_sdk/Archive/7z/7zIn.h"
#include "third_party/lzma_sdk/7zCrc.h"
}


namespace installer {

typedef struct _CFileInStream {
  ISzInStream InStream;
  HANDLE File;
} CFileInStream;


size_t LzmaReadFile(HANDLE file, void *data, size_t size) {
  if (size == 0)
    return 0;

  size_t processedSize = 0;
  do {
    DWORD processedLoc = 0;
    BOOL res = ReadFile(file, data, (DWORD) size, &processedLoc, NULL);
    data = (void *)((unsigned char *) data + processedLoc);
    size -= processedLoc;
    processedSize += processedLoc;
    if (!res || processedLoc == 0)
      break;
  } while (size > 0);

  return processedSize;
}

SZ_RESULT SzFileSeekImp(void *object, CFileSize pos) {
  CFileInStream *s = (CFileInStream *) object;
  LARGE_INTEGER value;
  value.LowPart = (DWORD) pos;
  value.HighPart = (LONG) ((UInt64) pos >> 32);
  value.LowPart = SetFilePointer(s->File, value.LowPart, &value.HighPart,
                                 FILE_BEGIN);
  if (value.LowPart == 0xFFFFFFFF) {
    if(GetLastError() != NO_ERROR) {
      return SZE_FAIL;
    }
  }
  return SZ_OK;
}

SZ_RESULT SzFileReadImp(void *object, void **buffer,
                        size_t maxRequiredSize, size_t *processedSize) {
  const int kBufferSize = 1 << 12;
  static Byte g_Buffer[kBufferSize];
  if (maxRequiredSize > kBufferSize) {
    maxRequiredSize = kBufferSize;
  }

  CFileInStream *s = (CFileInStream *) object;
  size_t processedSizeLoc;
  processedSizeLoc = LzmaReadFile(s->File, g_Buffer, maxRequiredSize);
  *buffer = g_Buffer;
  if (processedSize != 0) {
    *processedSize = processedSizeLoc;
  }
  return SZ_OK;
}


LzmaUtil::LzmaUtil() : archive_handle_(NULL) {}

LzmaUtil::~LzmaUtil() {
  if (archive_handle_) CloseArchive();
}

DWORD LzmaUtil::OpenArchive(const std::wstring& archivePath) {
  DWORD ret = NO_ERROR;
  archive_handle_ = CreateFile(archivePath.c_str(), GENERIC_READ,
      FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (archive_handle_ == INVALID_HANDLE_VALUE) {
    ret = GetLastError();
  }
  return ret;
}

// Unpacks the archive to the given location.
DWORD LzmaUtil::UnPack(const std::wstring& location) {
  CFileInStream archiveStream;
  ISzAlloc allocImp;
  ISzAlloc allocTempImp;
  CArchiveDatabaseEx db;
  DWORD ret = NO_ERROR;

  archiveStream.File = archive_handle_;
  archiveStream.InStream.Read = SzFileReadImp;
  archiveStream.InStream.Seek = SzFileSeekImp;
  allocImp.Alloc = SzAlloc;
  allocImp.Free = SzFree;
  allocTempImp.Alloc = SzAllocTemp;
  allocTempImp.Free = SzFreeTemp;

  CrcGenerateTable();
  SzArDbExInit(&db);
  if ((ret = SzArchiveOpen(&archiveStream.InStream, &db,
                           &allocImp, &allocTempImp)) != SZ_OK) {
    return ret;
  }

  Byte *outBuffer = 0; // it must be 0 before first call for each new archive
  UInt32 blockIndex = 0xFFFFFFFF; // can have any value if outBuffer = 0
  size_t outBufferSize = 0;  // can have any value if outBuffer = 0
  for (unsigned int i = 0; i < db.Database.NumFiles; i++) {
    DWORD written;
    size_t offset;
    size_t outSizeProcessed;
    CFileItem *f = db.Database.Files + i;

    if ((ret = SzExtract(&archiveStream.InStream, &db, i, &blockIndex,
                         &outBuffer, &outBufferSize, &offset, &outSizeProcessed,
                         &allocImp, &allocTempImp)) != SZ_OK) {
      break;
    }

    // Append location to the file path in archive, to get full path.
    std::wstring wfileName(location);
    file_util::AppendToPath(&wfileName, UTF8ToWide(f->Name));

    // If archive entry is directory create it and move on to the next entry.
    if (f->IsDirectory) {
      file_util::CreateDirectory(wfileName);
      continue;
    }

    HANDLE hFile;
    std::wstring directory = file_util::GetDirectoryFromPath(wfileName);
    file_util::CreateDirectory(directory);

    hFile = CreateFile(wfileName.c_str(), GENERIC_WRITE, 0, NULL,
                       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)  {
      ret = GetLastError();
      break;
    }

    WriteFile(hFile, outBuffer + offset, (DWORD) outSizeProcessed,
              &written, NULL);
    if (written != outSizeProcessed) {
      ret = GetLastError();
      CloseHandle(hFile);
      break;
    }

    if (f->IsLastWriteTimeDefined) {
      if (!SetFileTime(hFile, NULL, NULL,
                       (const FILETIME *)&(f->LastWriteTime))) {
        ret = GetLastError();
        CloseHandle(hFile);
        break;
      }
    }
    if (!CloseHandle(hFile)) {
      ret = GetLastError();
      break;
    }
  }  // for loop

  allocImp.Free(outBuffer);
  SzArDbExFree(&db, allocImp.Free);
  return ret;
}

void LzmaUtil::CloseArchive() {
  CloseHandle(archive_handle_);
  archive_handle_ = NULL;
}

}  // namespace installer