/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "FuzzingInterface.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIInputStream.h"
#include "nsIStringEnumerator.h"
#include "nsIZipReader.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsString.h"

enum FuzzMethodType {
  eTest = 0,
  eGetEntry,
  eHasEntry,
  eFindEntries,
  eInputStream,
  eOpenInner,
  eLastMethod,
};
static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);

template <typename T>
T jar_get_num(char** buf, size_t* size) {
  if (sizeof(T) > *size) {
    return 0;
  }

  T* iptr = reinterpret_cast<T*>(*buf);
  *buf += sizeof(T);
  *size -= sizeof(T);
  return *iptr;
}

nsAutoCString jar_get_string(char** buf, size_t* size) {
  uint8_t len = jar_get_num<uint8_t>(buf, size);
  if (len > *size) {
    len = static_cast<uint8_t>(*size);
  }
  nsAutoCString str(*buf, len);

  *buf += len;
  *size -= len;
  return str;
}

nsresult FuzzEntries(char** buf, size_t* size, nsIZipReader* aReader,
                     const nsACString& aName) {
  uint8_t iters = jar_get_num<uint8_t>(buf, size);
  nsresult rv;
  for (uint8_t i = 0; i < iters; ++i) {
    nsAutoCString out;
    uint64_t written;
    nsCOMPtr<nsIZipEntry> entry;
    nsCOMPtr<nsIInputStream> stream;

    switch (jar_get_num<uint8_t>(buf, size) % eLastMethod) {
      case eTest: {
        rv = aReader->Test(aName);
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      }
      case eGetEntry: {
        rv = aReader->GetEntry(aName, getter_AddRefs(entry));
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      }
      case eHasEntry: {
        bool has = false;
        rv = aReader->HasEntry(aName, &has);
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      }
      case eInputStream:
        rv = aReader->GetInputStream(aName, getter_AddRefs(stream));
        NS_ENSURE_SUCCESS(rv, rv);
        if (NS_FAILED(rv)) {
          break;
        }
        rv = NS_ReadInputStreamToString(stream, out, -1, &written);
        NS_ENSURE_SUCCESS(rv, rv);
        break;
      default:
        break;
    }
  }
  return NS_OK;
}

nsresult FuzzReader(char** buf, size_t* size, nsIZipReader* aReader) {
  nsresult rv;
  nsAutoCString name;
  nsCOMPtr<nsIZipEntry> entry;
  bool has = false;
  nsAutoCString pattern;
  nsCOMPtr<nsIUTF8StringEnumerator> enumerator;
  nsCOMPtr<nsIInputStream> stream;
  bool hasMore;
  nsAutoCString out;
  uint64_t written;
  nsCOMPtr<nsIZipReader> newReader = do_CreateInstance(kZipReaderCID, &rv);
  switch (jar_get_num<uint8_t>(buf, size) % eLastMethod) {
    case eTest:
      rv = aReader->Test(""_ns);
      NS_ENSURE_SUCCESS(rv, rv);
      break;
    case eGetEntry:
      name = jar_get_string(buf, size);
      rv = aReader->GetEntry(name, getter_AddRefs(entry));
      NS_ENSURE_SUCCESS(rv, rv);
      break;
    case eHasEntry:
      name = jar_get_string(buf, size);
      rv = aReader->HasEntry(name, &has);
      NS_ENSURE_SUCCESS(rv, rv);
      break;
    case eFindEntries:
      pattern = jar_get_string(buf, size);
      rv = aReader->FindEntries(pattern, getter_AddRefs(enumerator));
      NS_ENSURE_SUCCESS(rv, rv);
      while (NS_SUCCEEDED(enumerator->HasMore(&hasMore)) && hasMore) {
        if (NS_FAILED(enumerator->GetNext(name))) {
          break;
        }
        rv = FuzzEntries(buf, size, aReader, name);
        NS_ENSURE_SUCCESS(rv, rv);
      }

      break;
    case eInputStream:
      name = jar_get_string(buf, size);
      rv = aReader->GetInputStream(name, getter_AddRefs(stream));
      NS_ENSURE_SUCCESS(rv, rv);
      rv = NS_ReadInputStreamToString(stream, out, -1, &written);
      NS_ENSURE_SUCCESS(rv, rv);
      break;
    case eOpenInner:
      name = jar_get_string(buf, size);
      rv = newReader->OpenInner(aReader, name);
      NS_ENSURE_SUCCESS(rv, rv);
      rv = FuzzReader(buf, size, newReader);
      NS_ENSURE_SUCCESS(rv, rv);
      break;
    default:
      break;
  }
  return rv;
}

static int FuzzingRunJARParser(const uint8_t* data, size_t size) {
  char* buf = (char*)data;
  nsresult rv;

  nsCOMPtr<nsIURI> uri;
  nsAutoCString jardata = jar_get_string(&buf, &size);

  nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv);
  rv = reader->OpenMemory((void*)jardata.get(), jardata.Length());
  NS_ENSURE_SUCCESS(rv, 0);

#if 0
  // For easily exporting the last test case that triggered a crash.
  FILE * f = fopen("/tmp/input.jar", "wb");
  fwrite((void*)jardata.get(), 1, jardata.Length(), f);
  fclose(f);
#endif

  uint8_t iters = jar_get_num<uint8_t>(&buf, &size);
  for (uint8_t i = 0; i < iters; ++i) {
    rv = FuzzReader(&buf, &size, reader);
    NS_ENSURE_SUCCESS(rv, 0);
  }

  return 0;
}

MOZ_FUZZING_INTERFACE_RAW(nullptr, FuzzingRunJARParser, JARParser);
