/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Implements support for shapefile access.
 * Authors:  Steve Lime and Frank Warmerdam
 *
 * Note:
 * This code is entirely based on the previous work of Frank Warmerdam. It is
 * essentially shapelib 1.1.5. However, there were enough changes that it was
 * incorporated into the MapServer source to avoid confusion. Relicensed with
 * permission of Frank Warmerdam (shapelib author). See the README
 * for licence details.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#define NEED_IGNORE_RET_VAL

#include <limits.h>
#include <assert.h>
#include <stdbool.h>
#include "mapserver.h"
#include "mapows.h"

#include <cpl_conv.h>
#include <ogr_srs_api.h>

/* Only use this macro on 32-bit integers! */
#define SWAP_FOUR_BYTES(data) CPL_SWAP32(data)

#define ByteCopy(a, b, c) memcpy(b, a, c)

#ifdef __BYTE_ORDER__
/* GCC/clang predefined macro */
#define bBigEndian (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#elif defined(_MSC_VER)
/* MSVC doesn't support the C99 trick below, but all Microsoft
   platforms are little-endian */
#define bBigEndian false
#else
/* generic check */
#define bBigEndian                                                             \
  (((union {                                                                   \
     int in;                                                                   \
     char out;                                                                 \
   }){1})                                                                      \
       .out)
#endif

/* SHX reading */
static int msSHXLoadAll(SHPHandle psSHP);
static int msSHXReadOffset(SHPHandle psSHP, int hEntity);
static int msSHXReadSize(SHPHandle psSHP, int hEntity);

/************************************************************************/
/*                              SwapWord()                              */
/*                                                                      */
/*      Swap a 2, 4 or 8 byte word.                                     */
/************************************************************************/
static void SwapWord(int length, void *wordP) {
  int i;
  uchar temp;

  for (i = 0; i < length / 2; i++) {
    temp = ((uchar *)wordP)[i];
    ((uchar *)wordP)[i] = ((uchar *)wordP)[length - i - 1];
    ((uchar *)wordP)[length - i - 1] = temp;
  }
}

/************************************************************************/
/*                             SfRealloc()                              */
/*                                                                      */
/*      A realloc cover function that will access a NULL pointer as     */
/*      a valid input.                                                  */
/************************************************************************/
static void *SfRealloc(void *pMem, int nNewSize) {
  return realloc(pMem, nNewSize);
}

/************************************************************************/
/*                          writeHeader()                               */
/*                                                                      */
/*      Write out a header for the .shp and .shx files as well as the */
/*  contents of the index (.shx) file.        */
/************************************************************************/
static void writeHeader(SHPHandle psSHP) {
  uchar abyHeader[100];
  int i;
  ms_int32 i32;
  double dValue;
  ms_int32 *panSHX;

  /* -------------------------------------------------------------------- */
  /*      Prepare header block for .shp file.                             */
  /* -------------------------------------------------------------------- */
  for (i = 0; i < 100; i++)
    abyHeader[i] = 0;

  abyHeader[2] = 0x27; /* magic cookie */
  abyHeader[3] = 0x0a;

  i32 = psSHP->nFileSize / 2; /* file size */
  ByteCopy(&i32, abyHeader + 24, 4);
  if (!bBigEndian)
    SwapWord(4, abyHeader + 24);

  i32 = 1000; /* version */
  ByteCopy(&i32, abyHeader + 28, 4);
  if (bBigEndian)
    SwapWord(4, abyHeader + 28);

  i32 = psSHP->nShapeType; /* shape type */
  ByteCopy(&i32, abyHeader + 32, 4);
  if (bBigEndian)
    SwapWord(4, abyHeader + 32);

  dValue = psSHP->adBoundsMin[0]; /* set bounds */
  ByteCopy(&dValue, abyHeader + 36, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 36);

  dValue = psSHP->adBoundsMin[1];
  ByteCopy(&dValue, abyHeader + 44, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 44);

  dValue = psSHP->adBoundsMax[0];
  ByteCopy(&dValue, abyHeader + 52, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 52);

  dValue = psSHP->adBoundsMax[1];
  ByteCopy(&dValue, abyHeader + 60, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 60);

  dValue = psSHP->adBoundsMin[2]; /* z */
  ByteCopy(&dValue, abyHeader + 68, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 68);

  dValue = psSHP->adBoundsMax[2];
  ByteCopy(&dValue, abyHeader + 76, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 76);

  dValue = psSHP->adBoundsMin[3]; /* m */
  ByteCopy(&dValue, abyHeader + 84, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 84);

  dValue = psSHP->adBoundsMax[3];
  ByteCopy(&dValue, abyHeader + 92, 8);
  if (bBigEndian)
    SwapWord(8, abyHeader + 92);

  /* -------------------------------------------------------------------- */
  /*      Write .shp file header.                                         */
  /* -------------------------------------------------------------------- */
  VSIFSeekL(psSHP->fpSHP, 0, 0);
  VSIFWriteL(abyHeader, 100, 1, psSHP->fpSHP);

  /* -------------------------------------------------------------------- */
  /*      Prepare, and write .shx file header.                            */
  /* -------------------------------------------------------------------- */
  i32 = (psSHP->nRecords * 2 * sizeof(ms_int32) + 100) / 2; /* file size */
  ByteCopy(&i32, abyHeader + 24, 4);
  if (!bBigEndian)
    SwapWord(4, abyHeader + 24);

  VSIFSeekL(psSHP->fpSHX, 0, 0);
  VSIFWriteL(abyHeader, 100, 1, psSHP->fpSHX);

  /* -------------------------------------------------------------------- */
  /*      Write out the .shx contents.                                    */
  /* -------------------------------------------------------------------- */
  panSHX = (ms_int32 *)msSmallMalloc(sizeof(ms_int32) * 2 * psSHP->nRecords);

  for (i = 0; i < psSHP->nRecords; i++) {
    panSHX[i * 2] = psSHP->panRecOffset[i] / 2;
    panSHX[i * 2 + 1] = psSHP->panRecSize[i] / 2;
    if (!bBigEndian) {
      *(panSHX + i * 2) = SWAP_FOUR_BYTES(*(panSHX + i * 2));
      *(panSHX + i * 2 + 1) = SWAP_FOUR_BYTES(*(panSHX + i * 2 + 1));
    }
  }

  VSIFWriteL(panSHX, sizeof(ms_int32) * 2, psSHP->nRecords, psSHP->fpSHX);

  free(panSHX);
}

SHPHandle msSHPOpenVirtualFile(VSILFILE *fpSHP, VSILFILE *fpSHX) {
  /* -------------------------------------------------------------------- */
  /*  Initialize the info structure.              */
  /* -------------------------------------------------------------------- */
  SHPHandle psSHP = (SHPHandle)msSmallMalloc(sizeof(SHPInfo));

  psSHP->bUpdated = MS_FALSE;

  psSHP->pabyRec = NULL;
  psSHP->panParts = NULL;
  psSHP->nBufSize = psSHP->nPartMax = 0;

  psSHP->fpSHP = fpSHP;
  psSHP->fpSHX = fpSHX;

  /* -------------------------------------------------------------------- */
  /*   Read the file size from the SHP file.            */
  /* -------------------------------------------------------------------- */
  uchar *pabyBuf = (uchar *)msSmallMalloc(100);
  if (1 != VSIFReadL(pabyBuf, 100, 1, psSHP->fpSHP)) {
    VSIFCloseL(psSHP->fpSHP);
    VSIFCloseL(psSHP->fpSHX);
    free(psSHP);
    free(pabyBuf);
    return (NULL);
  }

  if (!bBigEndian)
    SwapWord(4, pabyBuf + 24);
  memcpy(&psSHP->nFileSize, pabyBuf + 24, 4);
  if (psSHP->nFileSize < 0 || psSHP->nFileSize > INT_MAX / 2) {
    msDebug("Invalid / unsupported nFileSize = %d value. Got it from actual "
            "file length",
            psSHP->nFileSize);
    VSIFSeekL(psSHP->fpSHP, 0, SEEK_END);
    vsi_l_offset nSize = VSIFTellL(psSHP->fpSHP);
    if (nSize > (vsi_l_offset)INT_MAX) {
      msDebug("Actual .shp size is larger than 2 GB. Not supported. "
              "Invalidating nFileSize");
      psSHP->nFileSize = 0;
    } else
      psSHP->nFileSize = (int)nSize;
  } else {
    psSHP->nFileSize *= 2;
  }

  /* -------------------------------------------------------------------- */
  /*  Read SHX file Header info                                           */
  /* -------------------------------------------------------------------- */
  if (1 != VSIFReadL(pabyBuf, 100, 1, psSHP->fpSHX)) {
    msSetError(MS_SHPERR, "Corrupted .shx file", "msSHPOpen()");
    VSIFCloseL(psSHP->fpSHP);
    VSIFCloseL(psSHP->fpSHX);
    free(psSHP);
    free(pabyBuf);
    return (NULL);
  }

  if (pabyBuf[0] != 0 || pabyBuf[1] != 0 || pabyBuf[2] != 0x27 ||
      (pabyBuf[3] != 0x0a && pabyBuf[3] != 0x0d)) {
    msSetError(MS_SHPERR, "Corrupted .shx file", "msSHPOpen()");
    VSIFCloseL(psSHP->fpSHP);
    VSIFCloseL(psSHP->fpSHX);
    free(psSHP);
    free(pabyBuf);

    return (NULL);
  }

  int nSHXHalfFileSize;
  if (!bBigEndian)
    SwapWord(4, pabyBuf + 24);
  memcpy(&nSHXHalfFileSize, pabyBuf + 24, 4);
  if (nSHXHalfFileSize >= 50)
    psSHP->nRecords = (nSHXHalfFileSize - 50) / 4; // (nSHXFileSize - 100) / 8
  else
    psSHP->nRecords = -1;

  if (psSHP->nRecords < 0 || psSHP->nRecords > 256000000) {
    msSetError(MS_SHPERR, "Corrupted .shx file : nRecords = %d.", "msSHPOpen()",
               psSHP->nRecords);
    VSIFCloseL(psSHP->fpSHP);
    VSIFCloseL(psSHP->fpSHX);
    free(psSHP);
    free(pabyBuf);
    return (NULL);
  }

  psSHP->nShapeType = pabyBuf[32];

  if (bBigEndian)
    SwapWord(8, pabyBuf + 36);
  double dValue;
  memcpy(&dValue, pabyBuf + 36, 8);
  psSHP->adBoundsMin[0] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 44);
  memcpy(&dValue, pabyBuf + 44, 8);
  psSHP->adBoundsMin[1] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 52);
  memcpy(&dValue, pabyBuf + 52, 8);
  psSHP->adBoundsMax[0] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 60);
  memcpy(&dValue, pabyBuf + 60, 8);
  psSHP->adBoundsMax[1] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 68); /* z */
  memcpy(&dValue, pabyBuf + 68, 8);
  psSHP->adBoundsMin[2] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 76);
  memcpy(&dValue, pabyBuf + 76, 8);
  psSHP->adBoundsMax[2] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 84); /* m */
  memcpy(&dValue, pabyBuf + 84, 8);
  psSHP->adBoundsMin[3] = dValue;

  if (bBigEndian)
    SwapWord(8, pabyBuf + 92);
  memcpy(&dValue, pabyBuf + 92, 8);
  psSHP->adBoundsMax[3] = dValue;
  free(pabyBuf);

  /* -------------------------------------------------------------------- */
  /*  Read the .shx file to get the offsets to each record in       */
  /*  the .shp file.                  */
  /* -------------------------------------------------------------------- */
  psSHP->nMaxRecords = psSHP->nRecords;

  /* Our in-memory cache of offset information */
  psSHP->panRecOffset = (int *)malloc(sizeof(int) * psSHP->nMaxRecords);
  /* Our in-memory cache of size information */
  psSHP->panRecSize = (int *)malloc(sizeof(int) * psSHP->nMaxRecords);
  /* The completeness information for our in-memory cache */
  psSHP->panRecLoaded =
      msAllocBitArray(1 + (psSHP->nMaxRecords / SHX_BUFFER_PAGE));
  /* Is our in-memory cache completely populated? */
  psSHP->panRecAllLoaded = 0;

  /* malloc failed? clean up and shut down */
  if (psSHP->panRecOffset == NULL || psSHP->panRecSize == NULL ||
      psSHP->panRecLoaded == NULL) {
    free(psSHP->panRecOffset);
    free(psSHP->panRecSize);
    free(psSHP->panRecLoaded);
    VSIFCloseL(psSHP->fpSHP);
    VSIFCloseL(psSHP->fpSHX);
    free(psSHP);
    msSetError(MS_MEMERR, "Out of memory", "msSHPOpen()");
    return (NULL);
  }

  return (psSHP);
}

/************************************************************************/
/*                              msSHPOpen()                             */
/*                                                                      */
/*      Open the .shp and .shx files based on the basename of the       */
/*      files or either file name.                                      */
/************************************************************************/
SHPHandle msSHPOpen(const char *pszLayer, const char *pszAccess) {
  char *pszFullname, *pszBasename;

  int i;

  /* -------------------------------------------------------------------- */
  /*      Ensure the access string is one of the legal ones.  We          */
  /*      ensure the result string indicates binary to avoid common       */
  /*      problems on Windows.                                            */
  /* -------------------------------------------------------------------- */
  if (strcmp(pszAccess, "rb+") == 0 || strcmp(pszAccess, "r+b") == 0 ||
      strcmp(pszAccess, "r+") == 0)
    pszAccess = "r+b";
  else
    pszAccess = "rb";

  /* -------------------------------------------------------------------- */
  /*  Compute the base (layer) name.  If there is any extension     */
  /*  on the passed in filename we will strip it off.         */
  /* -------------------------------------------------------------------- */
  pszBasename = (char *)msSmallMalloc(strlen(pszLayer) + 5);
  strcpy(pszBasename, pszLayer);
  for (i = strlen(pszBasename) - 1;
       i > 0 && pszBasename[i] != '.' && pszBasename[i] != '/' &&
       pszBasename[i] != '\\';
       i--) {
  }

  if (pszBasename[i] == '.')
    pszBasename[i] = '\0';

  /* -------------------------------------------------------------------- */
  /*  Open the .shp and .shx files.  Note that files pulled from      */
  /*  a PC to Unix with upper case filenames won't work!        */
  /* -------------------------------------------------------------------- */
  pszFullname = (char *)msSmallMalloc(strlen(pszBasename) + 5);
  sprintf(pszFullname, "%s.shp", pszBasename);
  VSILFILE *fpSHP = VSIFOpenL(pszFullname, pszAccess);
  if (fpSHP == NULL) {
    sprintf(pszFullname, "%s.SHP", pszBasename);
    fpSHP = VSIFOpenL(pszFullname, pszAccess);
  }
  if (fpSHP == NULL) {
    msFree(pszBasename);
    msFree(pszFullname);
    return (NULL);
  }

  sprintf(pszFullname, "%s.shx", pszBasename);
  VSILFILE *fpSHX = VSIFOpenL(pszFullname, pszAccess);
  if (fpSHX == NULL) {
    sprintf(pszFullname, "%s.SHX", pszBasename);
    fpSHX = VSIFOpenL(pszFullname, pszAccess);
  }
  if (fpSHX == NULL) {
    VSIFCloseL(fpSHP);
    msFree(pszBasename);
    msFree(pszFullname);
    return (NULL);
  }

  free(pszFullname);
  free(pszBasename);

  return msSHPOpenVirtualFile(fpSHP, fpSHX);
}

/************************************************************************/
/*                              msSHPClose()                            */
/*                        */
/*  Close the .shp and .shx files.          */
/************************************************************************/
void msSHPClose(SHPHandle psSHP) {
  /* -------------------------------------------------------------------- */
  /*  Update the header if we have modified anything.           */
  /* -------------------------------------------------------------------- */
  if (psSHP->bUpdated)
    writeHeader(psSHP);

  /* -------------------------------------------------------------------- */
  /*      Free all resources, and close files.                            */
  /* -------------------------------------------------------------------- */
  free(psSHP->panRecOffset);
  free(psSHP->panRecSize);
  free(psSHP->panRecLoaded);

  free(psSHP->pabyRec);
  free(psSHP->panParts);

  VSIFCloseL(psSHP->fpSHX);
  VSIFCloseL(psSHP->fpSHP);

  free(psSHP);
}

/************************************************************************/
/*                             msSHPGetInfo()                           */
/*                                                                      */
/*      Fetch general information about the shape file.                 */
/************************************************************************/
void msSHPGetInfo(SHPHandle psSHP, int *pnEntities, int *pnShapeType) {
  if (pnEntities)
    *pnEntities = psSHP->nRecords;

  if (pnShapeType)
    *pnShapeType = psSHP->nShapeType;
}

/************************************************************************/
/*                             msSHPCreate()                            */
/*                                                                      */
/*      Create a new shape file and return a handle to the open         */
/*      shape file with read/write access.                              */
/************************************************************************/
SHPHandle msSHPCreate(const char *pszLayer, int nShapeType) {
  char *pszBasename, *pszFullname;
  int i;
  VSILFILE *fpSHP, *fpSHX;
  uchar abyHeader[100];
  ms_int32 i32;
  double dValue;

  /* -------------------------------------------------------------------- */
  /*  Compute the base (layer) name.  If there is any extension       */
  /*  on the passed in filename we will strip it off.         */
  /* -------------------------------------------------------------------- */
  pszBasename = (char *)msSmallMalloc(strlen(pszLayer) + 5);
  strcpy(pszBasename, pszLayer);
  for (i = strlen(pszBasename) - 1;
       i > 0 && pszBasename[i] != '.' && pszBasename[i] != '/' &&
       pszBasename[i] != '\\';
       i--) {
  }

  if (pszBasename[i] == '.')
    pszBasename[i] = '\0';

  /* -------------------------------------------------------------------- */
  /*      Open the two files so we can write their headers.               */
  /* -------------------------------------------------------------------- */
  pszFullname = (char *)msSmallMalloc(strlen(pszBasename) + 5);
  sprintf(pszFullname, "%s.shp", pszBasename);
  fpSHP = VSIFOpenL(pszFullname, "wb");
  if (fpSHP == NULL) {
    free(pszFullname);
    free(pszBasename);
    return (NULL);
  }

  sprintf(pszFullname, "%s.shx", pszBasename);
  fpSHX = VSIFOpenL(pszFullname, "wb");
  if (fpSHX == NULL) {
    VSIFCloseL(fpSHP);
    free(pszFullname);
    free(pszBasename);
    return (NULL);
  }

  free(pszFullname);
  free(pszBasename);

  /* -------------------------------------------------------------------- */
  /*      Prepare header block for .shp file.                             */
  /* -------------------------------------------------------------------- */
  for (i = 0; i < 100; i++)
    abyHeader[i] = 0;

  abyHeader[2] = 0x27; /* magic cookie */
  abyHeader[3] = 0x0a;

  i32 = 50; /* file size */
  ByteCopy(&i32, abyHeader + 24, 4);
  if (!bBigEndian)
    SwapWord(4, abyHeader + 24);

  i32 = 1000; /* version */
  ByteCopy(&i32, abyHeader + 28, 4);
  if (bBigEndian)
    SwapWord(4, abyHeader + 28);

  i32 = nShapeType; /* shape type */
  ByteCopy(&i32, abyHeader + 32, 4);
  if (bBigEndian)
    SwapWord(4, abyHeader + 32);

  dValue = 0.0; /* set bounds */
  ByteCopy(&dValue, abyHeader + 36, 8);
  ByteCopy(&dValue, abyHeader + 44, 8);
  ByteCopy(&dValue, abyHeader + 52, 8);
  ByteCopy(&dValue, abyHeader + 60, 8);

  /* -------------------------------------------------------------------- */
  /*      Write .shp file header.                                         */
  /* -------------------------------------------------------------------- */
  VSIFWriteL(abyHeader, 100, 1, fpSHP);

  /* -------------------------------------------------------------------- */
  /*      Prepare, and write .shx file header.                            */
  /* -------------------------------------------------------------------- */
  i32 = 50; /* file size */
  ByteCopy(&i32, abyHeader + 24, 4);
  if (!bBigEndian)
    SwapWord(4, abyHeader + 24);

  VSIFWriteL(abyHeader, 100, 1, fpSHX);

  /* -------------------------------------------------------------------- */
  /*      Close the files, and then open them as regular existing files.  */
  /* -------------------------------------------------------------------- */
  VSIFCloseL(fpSHP);
  VSIFCloseL(fpSHX);

  return (msSHPOpen(pszLayer, "rb+"));
}

/************************************************************************/
/*                           writeBounds()                              */
/*                                                                      */
/*      Compute a bounds rectangle for a shape, and set it into the     */
/*      indicated location in the record.                               */
/************************************************************************/
static void writeBounds(uchar *pabyRec, shapeObj *shape, int nVCount) {
  double dXMin, dXMax, dYMin, dYMax;
  int i, j;

  if (nVCount == 0) {
    dXMin = dYMin = dXMax = dYMax = 0.0;
  } else {
    dXMin = dXMax = shape->line[0].point[0].x;
    dYMin = dYMax = shape->line[0].point[0].y;

    for (i = 0; i < shape->numlines; i++) {
      for (j = 0; j < shape->line[i].numpoints; j++) {
        dXMin = MS_MIN(dXMin, shape->line[i].point[j].x);
        dXMax = MS_MAX(dXMax, shape->line[i].point[j].x);
        dYMin = MS_MIN(dYMin, shape->line[i].point[j].y);
        dYMax = MS_MAX(dYMax, shape->line[i].point[j].y);
      }
    }
  }

  if (bBigEndian) {
    SwapWord(8, &dXMin);
    SwapWord(8, &dYMin);
    SwapWord(8, &dXMax);
    SwapWord(8, &dYMax);
  }

  ByteCopy(&dXMin, pabyRec + 0, 8);
  ByteCopy(&dYMin, pabyRec + 8, 8);
  ByteCopy(&dXMax, pabyRec + 16, 8);
  ByteCopy(&dYMax, pabyRec + 24, 8);
}

int msSHPWritePoint(SHPHandle psSHP, pointObj *point) {
  int nRecordOffset, nRecordSize = 0;
  uchar *pabyRec;
  ms_int32 i32, nPoints, nParts;

  if (psSHP->nShapeType != SHP_POINT)
    return (-1);
  if (psSHP->nFileSize == 0)
    return -1;

  psSHP->bUpdated = MS_TRUE;

  /* Fill the SHX buffer if it is not already full. */
  if (!psSHP->panRecAllLoaded)
    msSHXLoadAll(psSHP);

  /* -------------------------------------------------------------------- */
  /*      Add the new entity to the in memory index.                      */
  /* -------------------------------------------------------------------- */
  psSHP->nRecords++;
  if (psSHP->nRecords > psSHP->nMaxRecords) {
    psSHP->nMaxRecords = (int)(psSHP->nMaxRecords * 1.3 + 100);

    psSHP->panRecOffset =
        (int *)SfRealloc(psSHP->panRecOffset, sizeof(int) * psSHP->nMaxRecords);
    psSHP->panRecSize =
        (int *)SfRealloc(psSHP->panRecSize, sizeof(int) * psSHP->nMaxRecords);
  }

  /* -------------------------------------------------------------------- */
  /*      Compute a few things.                                           */
  /* -------------------------------------------------------------------- */
  nPoints = 1;
  nParts = 1;

  /* -------------------------------------------------------------------- */
  /*      Initialize record.                                              */
  /* -------------------------------------------------------------------- */
  psSHP->panRecOffset[psSHP->nRecords - 1] = nRecordOffset = psSHP->nFileSize;

  pabyRec =
      (uchar *)msSmallMalloc(nPoints * 2 * sizeof(double) + nParts * 4 + 128);

  /* -------------------------------------------------------------------- */
  /*      Write vertices for a point.                                     */
  /* -------------------------------------------------------------------- */
  ByteCopy(&(point->x), pabyRec + 12, 8);
  ByteCopy(&(point->y), pabyRec + 20, 8);

  if (bBigEndian) {
    SwapWord(8, pabyRec + 12);
    SwapWord(8, pabyRec + 20);
  }

  nRecordSize = 20;

  /* -------------------------------------------------------------------- */
  /*      Set the shape type, record number, and record size.             */
  /* -------------------------------------------------------------------- */
  i32 = psSHP->nRecords - 1 + 1; /* record # */
  if (!bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec, 4);

  i32 = nRecordSize / 2; /* record size */
  if (!bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec + 4, 4);

  i32 = psSHP->nShapeType; /* shape type */
  if (bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec + 8, 4);

  /* -------------------------------------------------------------------- */
  /*      Write out record.                                               */
  /* -------------------------------------------------------------------- */
  if (VSIFSeekL(psSHP->fpSHP, nRecordOffset, 0) == 0) {
    VSIFWriteL(pabyRec, nRecordSize + 8, 1, psSHP->fpSHP);

    psSHP->panRecSize[psSHP->nRecords - 1] = nRecordSize;
    psSHP->nFileSize += nRecordSize + 8;

    /* -------------------------------------------------------------------- */
    /*  Expand file wide bounds based on this shape.        */
    /* -------------------------------------------------------------------- */
    if (psSHP->nRecords == 1) {
      psSHP->adBoundsMin[0] = psSHP->adBoundsMax[0] = point->x;
      psSHP->adBoundsMin[1] = psSHP->adBoundsMax[1] = point->y;
    } else {
      psSHP->adBoundsMin[0] = MS_MIN(psSHP->adBoundsMin[0], point->x);
      psSHP->adBoundsMin[1] = MS_MIN(psSHP->adBoundsMin[1], point->y);
      psSHP->adBoundsMax[0] = MS_MAX(psSHP->adBoundsMax[0], point->x);
      psSHP->adBoundsMax[1] = MS_MAX(psSHP->adBoundsMax[1], point->y);
    }
  } else {
    psSHP->nRecords--;
  }
  free(pabyRec);
  return (psSHP->nRecords - 1);
}

int msSHPWriteShape(SHPHandle psSHP, shapeObj *shape) {
  int nRecordOffset, i, j, k, nRecordSize = 0;
  uchar *pabyRec;
  int nShapeType;

  ms_int32 i32, nPoints, nParts;

  if (psSHP->nFileSize == 0)
    return -1;

  psSHP->bUpdated = MS_TRUE;

  /* Fill the SHX buffer if it is not already full. */
  if (!psSHP->panRecAllLoaded)
    msSHXLoadAll(psSHP);

  /* -------------------------------------------------------------------- */
  /*      Add the new entity to the in memory index.                      */
  /* -------------------------------------------------------------------- */
  psSHP->nRecords++;
  if (psSHP->nRecords > psSHP->nMaxRecords) {
    psSHP->nMaxRecords = (int)(psSHP->nMaxRecords * 1.3 + 100);

    psSHP->panRecOffset =
        (int *)SfRealloc(psSHP->panRecOffset, sizeof(int) * psSHP->nMaxRecords);
    psSHP->panRecSize =
        (int *)SfRealloc(psSHP->panRecSize, sizeof(int) * psSHP->nMaxRecords);
  }

  /* -------------------------------------------------------------------- */
  /*      Compute a few things.                                           */
  /* -------------------------------------------------------------------- */
  nPoints = 0;
  for (i = 0; i < shape->numlines; i++)
    nPoints += shape->line[i].numpoints;

  nParts = shape->numlines;

  /* -------------------------------------------------------------------- */
  /*      Initialize record.                                              */
  /* -------------------------------------------------------------------- */
  psSHP->panRecOffset[psSHP->nRecords - 1] = nRecordOffset = psSHP->nFileSize;

  pabyRec =
      (uchar *)msSmallMalloc(nPoints * 4 * sizeof(double) + nParts * 8 + 128);
  nShapeType = psSHP->nShapeType;

  if (shape->type == MS_SHAPE_NULL) {
    nShapeType = 0;
    nRecordSize = 12;
  }
  /* -------------------------------------------------------------------- */
  /*  Write vertices for a Polygon or Arc.            */
  /* -------------------------------------------------------------------- */
  else if (psSHP->nShapeType == SHP_POLYGON || psSHP->nShapeType == SHP_ARC ||
           psSHP->nShapeType == SHP_POLYGONM || psSHP->nShapeType == SHP_ARCM ||
           psSHP->nShapeType == SHP_ARCZ || psSHP->nShapeType == SHP_POLYGONZ) {
    ms_int32 t_nParts, t_nPoints, partSize;

    t_nParts = nParts;
    t_nPoints = nPoints;

    writeBounds(pabyRec + 12, shape, t_nPoints);

    if (bBigEndian) {
      nPoints = SWAP_FOUR_BYTES(nPoints);
      nParts = SWAP_FOUR_BYTES(nParts);
    }

    ByteCopy(&nPoints, pabyRec + 40 + 8, 4);
    ByteCopy(&nParts, pabyRec + 36 + 8, 4);

    partSize = 0; /* first part always starts at 0 */
    ByteCopy(&partSize, pabyRec + 44 + 8 + 4 * 0, 4);
    if (bBigEndian)
      SwapWord(4, pabyRec + 44 + 8 + 4 * 0);

    for (i = 1; i < t_nParts; i++) {
      partSize += shape->line[i - 1].numpoints;
      ByteCopy(&partSize, pabyRec + 44 + 8 + 4 * i, 4);
      if (bBigEndian)
        SwapWord(4, pabyRec + 44 + 8 + 4 * i);
    }

    k = 0; /* overall point counter */
    for (i = 0; i < shape->numlines; i++) {
      for (j = 0; j < shape->line[i].numpoints; j++) {
        ByteCopy(&(shape->line[i].point[j].x),
                 pabyRec + 44 + 4 * t_nParts + 8 + k * 16, 8);
        ByteCopy(&(shape->line[i].point[j].y),
                 pabyRec + 44 + 4 * t_nParts + 8 + k * 16 + 8, 8);

        if (bBigEndian) {
          SwapWord(8, pabyRec + 44 + 4 * t_nParts + 8 + k * 16);
          SwapWord(8, pabyRec + 44 + 4 * t_nParts + 8 + k * 16 + 8);
        }

        k++;
      }
    }

    nRecordSize = 44 + 4 * t_nParts + 16 * t_nPoints;

    /* -------------------------------------------------------------------- */
    /*      measured shape : polygon and arc.                               */
    /* -------------------------------------------------------------------- */
    if (psSHP->nShapeType == SHP_POLYGONM || psSHP->nShapeType == SHP_ARCM) {
      const double dfMMin = shape->line[0].point[0].m;
      const double dfMMax =
          shape->line[shape->numlines - 1]
              .point[shape->line[shape->numlines - 1].numpoints - 1]
              .m;

      nRecordSize = 44 + 4 * t_nParts + 8 + (t_nPoints * 16);

      ByteCopy(&(dfMMin), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      ByteCopy(&(dfMMax), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      for (i = 0; i < shape->numlines; i++) {
        for (j = 0; j < shape->line[i].numpoints; j++) {
          ByteCopy(&(shape->line[i].point[j].m), pabyRec + nRecordSize, 8);
          if (bBigEndian)
            SwapWord(8, pabyRec + nRecordSize);
          nRecordSize += 8;
        }
      }
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon. Arc with Z                                             */
    /* -------------------------------------------------------------------- */
    if (psSHP->nShapeType == SHP_POLYGONZ || psSHP->nShapeType == SHP_ARCZ ||
        psSHP->nShapeType == SHP_POLYGONM || psSHP->nShapeType == SHP_ARCM) {
      const double dfMMin = shape->line[0].point[0].z;
      const double dfMMax =
          shape->line[shape->numlines - 1]
              .point[shape->line[shape->numlines - 1].numpoints - 1]
              .z;

      nRecordSize = 44 + 4 * t_nParts + 8 + (t_nPoints * 16);

      ByteCopy(&(dfMMin), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      ByteCopy(&(dfMMax), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      for (i = 0; i < shape->numlines; i++) {
        for (j = 0; j < shape->line[i].numpoints; j++) {
          ByteCopy(&(shape->line[i].point[j].z), pabyRec + nRecordSize, 8);
          if (bBigEndian)
            SwapWord(8, pabyRec + nRecordSize);
          nRecordSize += 8;
        }
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*  Write vertices for a MultiPoint.                                    */
  /* -------------------------------------------------------------------- */
  else if (psSHP->nShapeType == SHP_MULTIPOINT ||
           psSHP->nShapeType == SHP_MULTIPOINTM ||
           psSHP->nShapeType == SHP_MULTIPOINTZ) {
    ms_int32 t_nPoints;

    t_nPoints = nPoints;

    writeBounds(pabyRec + 12, shape, nPoints);

    if (bBigEndian)
      nPoints = SWAP_FOUR_BYTES(nPoints);
    ByteCopy(&nPoints, pabyRec + 44, 4);

    for (i = 0; i < shape->line[0].numpoints; i++) {
      ByteCopy(&(shape->line[0].point[i].x), pabyRec + 48 + i * 16, 8);
      ByteCopy(&(shape->line[0].point[i].y), pabyRec + 48 + i * 16 + 8, 8);

      if (bBigEndian) {
        SwapWord(8, pabyRec + 48 + i * 16);
        SwapWord(8, pabyRec + 48 + i * 16 + 8);
      }
    }

    nRecordSize = 40 + 16 * t_nPoints;

    if (psSHP->nShapeType == SHP_MULTIPOINTM) {
      const double dfMMin = shape->line[0].point[0].m;
      const double dfMMax =
          shape->line[0].point[shape->line[0].numpoints - 1].m;

      ByteCopy(&(dfMMin), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      ByteCopy(&(dfMMax), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      for (i = 0; i < shape->line[0].numpoints; i++) {
        ByteCopy(&(shape->line[0].point[i].m), pabyRec + nRecordSize, 8);
        if (bBigEndian)
          SwapWord(8, pabyRec + nRecordSize);
        nRecordSize += 8;
      }
    }

    if (psSHP->nShapeType == SHP_MULTIPOINTZ) {
      const double dfMMin = shape->line[0].point[0].z;
      const double dfMMax =
          shape->line[0].point[shape->line[0].numpoints - 1].z;

      ByteCopy(&(dfMMin), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      ByteCopy(&(dfMMax), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;

      for (i = 0; i < shape->line[0].numpoints; i++) {
        ByteCopy(&(shape->line[0].point[i].z), pabyRec + nRecordSize, 8);
        if (bBigEndian)
          SwapWord(8, pabyRec + nRecordSize);
        nRecordSize += 8;
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Write vertices for a point.                                     */
  /* -------------------------------------------------------------------- */
  else if (psSHP->nShapeType == SHP_POINT || psSHP->nShapeType == SHP_POINTM ||
           psSHP->nShapeType == SHP_POINTZ) {
    ByteCopy(&(shape->line[0].point[0].x), pabyRec + 12, 8);
    ByteCopy(&(shape->line[0].point[0].y), pabyRec + 20, 8);

    if (bBigEndian) {
      SwapWord(8, pabyRec + 12);
      SwapWord(8, pabyRec + 20);
    }

    nRecordSize = 20;

    if (psSHP->nShapeType == SHP_POINTM) {
      ByteCopy(&(shape->line[0].point[0].m), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;
    }

    if (psSHP->nShapeType == SHP_POINTZ) {
      ByteCopy(&(shape->line[0].point[0].z), pabyRec + nRecordSize, 8);
      if (bBigEndian)
        SwapWord(8, pabyRec + nRecordSize);
      nRecordSize += 8;
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Set the shape type, record number, and record size.             */
  /* -------------------------------------------------------------------- */
  i32 = psSHP->nRecords - 1 + 1; /* record # */
  if (!bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec, 4);

  i32 = nRecordSize / 2; /* record size */
  if (!bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec + 4, 4);

  i32 = nShapeType; /* shape type */
  if (bBigEndian)
    i32 = SWAP_FOUR_BYTES(i32);
  ByteCopy(&i32, pabyRec + 8, 4);

  /* -------------------------------------------------------------------- */
  /*      Write out record.                                               */
  /* -------------------------------------------------------------------- */
  if (VSIFSeekL(psSHP->fpSHP, nRecordOffset, 0) == 0) {
    VSIFWriteL(pabyRec, nRecordSize + 8, 1, psSHP->fpSHP);

    psSHP->panRecSize[psSHP->nRecords - 1] = nRecordSize;
    psSHP->nFileSize += nRecordSize + 8;

    /* -------------------------------------------------------------------- */
    /*  Expand file wide bounds based on this shape.        */
    /* -------------------------------------------------------------------- */
    if (psSHP->nRecords == 1) {
      psSHP->adBoundsMin[0] = psSHP->adBoundsMax[0] = shape->line[0].point[0].x;
      psSHP->adBoundsMin[1] = psSHP->adBoundsMax[1] = shape->line[0].point[0].y;
      psSHP->adBoundsMin[2] = psSHP->adBoundsMax[2] = shape->line[0].point[0].z;
      psSHP->adBoundsMin[3] = psSHP->adBoundsMax[3] = shape->line[0].point[0].m;
    }

    for (i = 0; i < shape->numlines; i++) {
      for (j = 0; j < shape->line[i].numpoints; j++) {
        psSHP->adBoundsMin[0] =
            MS_MIN(psSHP->adBoundsMin[0], shape->line[i].point[j].x);
        psSHP->adBoundsMin[1] =
            MS_MIN(psSHP->adBoundsMin[1], shape->line[i].point[j].y);
        psSHP->adBoundsMin[2] =
            MS_MIN(psSHP->adBoundsMin[2], shape->line[i].point[j].z);
        psSHP->adBoundsMin[3] =
            MS_MIN(psSHP->adBoundsMin[3], shape->line[i].point[j].m);
        psSHP->adBoundsMax[0] =
            MS_MAX(psSHP->adBoundsMax[0], shape->line[i].point[j].x);
        psSHP->adBoundsMax[1] =
            MS_MAX(psSHP->adBoundsMax[1], shape->line[i].point[j].y);
        psSHP->adBoundsMax[2] =
            MS_MAX(psSHP->adBoundsMax[2], shape->line[i].point[j].z);
        psSHP->adBoundsMax[3] =
            MS_MAX(psSHP->adBoundsMax[3], shape->line[i].point[j].m);
      }
    }

  } else {
    psSHP->nRecords--;
    /* there was an error writing the record */
  }
  free(pabyRec);
  return (psSHP->nRecords - 1);
}

/*
 ** msSHPReadAllocateBuffer() - Ensure our record buffer is large enough.
 */
static uchar *msSHPReadAllocateBuffer(SHPHandle psSHP, int hEntity,
                                      const char *pszCallingFunction) {

  int nEntitySize = msSHXReadSize(psSHP, hEntity);
  if (nEntitySize <= 0 || nEntitySize > INT_MAX - 8) {
    msSetError(MS_MEMERR,
               "Out of memory. Cannot allocate %d bytes. Probably broken "
               "shapefile at feature %d",
               pszCallingFunction, nEntitySize, hEntity);
    return NULL;
  }
  nEntitySize += 8;
  /* -------------------------------------------------------------------- */
  /*      Ensure our record buffer is large enough.                       */
  /* -------------------------------------------------------------------- */

#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
  /* when running with libFuzzer, allocate a new buffer for every
     call, to allow AddressSanitizer to detect memory errors */
  free(psSHP->pabyRec);
  psSHP->pabyRec = NULL;
  psSHP->nBufSize = 0;
#endif

  if (nEntitySize > psSHP->nBufSize) {
    uchar *pabyRec = (uchar *)SfRealloc(psSHP->pabyRec, nEntitySize);
    if (pabyRec == NULL) {
      msSetError(MS_MEMERR,
                 "Out of memory. Cannot allocate %d bytes. Probably broken "
                 "shapefile at feature %d",
                 pszCallingFunction, nEntitySize, hEntity);
      return NULL;
    }
    psSHP->pabyRec = pabyRec;
    psSHP->nBufSize = nEntitySize;
  }
  return psSHP->pabyRec;
}

/*
** msSHPReadPoint() - Reads a single point from a POINT shape file.
*/
int msSHPReadPoint(SHPHandle psSHP, int hEntity, pointObj *point) {
  int nEntitySize;

  /* -------------------------------------------------------------------- */
  /*      Only valid for point shapefiles                                 */
  /* -------------------------------------------------------------------- */
  if (psSHP->nShapeType != SHP_POINT) {
    msSetError(MS_SHPERR, "msSHPReadPoint only operates on point shapefiles.",
               "msSHPReadPoint()");
    return (MS_FAILURE);
  }

  /* -------------------------------------------------------------------- */
  /*      Validate the record/entity number.                              */
  /* -------------------------------------------------------------------- */
  if (hEntity < 0 || hEntity >= psSHP->nRecords) {
    msSetError(MS_SHPERR, "Record index out of bounds.", "msSHPReadPoint()");
    return (MS_FAILURE);
  }

  nEntitySize = msSHXReadSize(psSHP, hEntity) + 8;

  if (nEntitySize == 12) {
    msSetError(MS_SHPERR, "NULL feature encountered.", "msSHPReadPoint()");
    return (MS_FAILURE);
  } else if (nEntitySize < 28) {
    msSetError(MS_SHPERR,
               "Corrupted feature encountered.  hEntity=%d, nEntitySize=%d",
               "msSHPReadPoint()", hEntity, nEntitySize);
    return (MS_FAILURE);
  }

  uchar *pabyRec = msSHPReadAllocateBuffer(psSHP, hEntity, "msSHPReadPoint()");
  if (pabyRec == NULL) {
    return MS_FAILURE;
  }

  /* -------------------------------------------------------------------- */
  /*      Read the record.                                                */
  /* -------------------------------------------------------------------- */
  const int offset = msSHXReadOffset(psSHP, hEntity);
  if (offset <= 0 || 0 != VSIFSeekL(psSHP->fpSHP, offset, 0)) {
    msSetError(MS_IOERR, "failed to seek offset", "msSHPReadPoint()");
    return (MS_FAILURE);
  }
  if (1 != VSIFReadL(pabyRec, nEntitySize, 1, psSHP->fpSHP)) {
    msSetError(MS_IOERR, "failed to fread record", "msSHPReadPoint()");
    return (MS_FAILURE);
  }

  memcpy(&(point->x), pabyRec + 12, 8);
  memcpy(&(point->y), pabyRec + 20, 8);

  if (bBigEndian) {
    SwapWord(8, &(point->x));
    SwapWord(8, &(point->y));
  }

  return (MS_SUCCESS);
}

/*
** msSHXLoadPage()
**
** The SHX tells us what the byte offsets of the shapes in the SHP file are.
** We read the SHX file in ~8K pages and store those pages in memory for
** successive accesses during the reading cycle (first bounds are read,
** then entire shapes). Each time we read a page, we mark it as read.
*/
static bool msSHXLoadPage(SHPHandle psSHP, int shxBufferPage) {
  int i;

  /* Each SHX record is 8 bytes long (two ints), hence our buffer size. */
  char buffer[SHX_BUFFER_PAGE * 8];

  /*  Validate the page number. */
  if (shxBufferPage < 0)
    return (MS_FAILURE);

  const int nShapesToCache =
      shxBufferPage < psSHP->nRecords / SHX_BUFFER_PAGE
          ? SHX_BUFFER_PAGE
          : psSHP->nRecords - shxBufferPage * SHX_BUFFER_PAGE;

  if (0 !=
      VSIFSeekL(psSHP->fpSHX, 100 + shxBufferPage * SHX_BUFFER_PAGE * 8, 0)) {
    memset(psSHP->panRecOffset + shxBufferPage * SHX_BUFFER_PAGE, 0,
           nShapesToCache * sizeof(psSHP->panRecOffset[0]));
    memset(psSHP->panRecSize + shxBufferPage * SHX_BUFFER_PAGE, 0,
           nShapesToCache * sizeof(psSHP->panRecSize[0]));
    msSetBit(psSHP->panRecLoaded, shxBufferPage, 1);
    msSetError(MS_IOERR, "failed to seek offset", "msSHXLoadPage()");
    return false;
  }

  if ((size_t)nShapesToCache !=
      VSIFReadL(buffer, 8, nShapesToCache, psSHP->fpSHX)) {
    memset(psSHP->panRecOffset + shxBufferPage * SHX_BUFFER_PAGE, 0,
           nShapesToCache * sizeof(psSHP->panRecOffset[0]));
    memset(psSHP->panRecSize + shxBufferPage * SHX_BUFFER_PAGE, 0,
           nShapesToCache * sizeof(psSHP->panRecSize[0]));
    msSetBit(psSHP->panRecLoaded, shxBufferPage, 1);
    msSetError(MS_IOERR, "failed to fread SHX record", "msSHXLoadPage()");
    return false;
  }

  /* Copy the buffer contents out into the working arrays. */
  for (i = 0; i < nShapesToCache; i++) {
    int tmpOffset, tmpSize;

    memcpy(&tmpOffset, (buffer + (8 * i)), 4);
    memcpy(&tmpSize, (buffer + (8 * i) + 4), 4);

    /* SHX uses big endian numbers for the offsets, so we have to flip them */
    /* if we are a little endian machine. */
    if (!bBigEndian) {
      tmpOffset = SWAP_FOUR_BYTES(tmpOffset);
      tmpSize = SWAP_FOUR_BYTES(tmpSize);
    }

    /* SHX stores the offsets in 2 byte units, so we double them to get */
    /* an offset in bytes. */
    if (tmpOffset > 0 && tmpOffset < INT_MAX / 2)
      tmpOffset = tmpOffset * 2;
    else
      tmpOffset = 0;

    if (tmpSize > 0 && tmpSize < INT_MAX / 2)
      tmpSize = tmpSize * 2;
    else
      tmpSize = 0;

    /* Write the answer into the working arrays on the SHPHandle */
    psSHP->panRecOffset[shxBufferPage * SHX_BUFFER_PAGE + i] = tmpOffset;
    psSHP->panRecSize[shxBufferPage * SHX_BUFFER_PAGE + i] = tmpSize;
  }

  msSetBit(psSHP->panRecLoaded, shxBufferPage, 1);

  return (MS_SUCCESS);
}

static int msSHXLoadAll(SHPHandle psSHP) {

  int i;
  uchar *pabyBuf;

  pabyBuf = (uchar *)malloc(8 * psSHP->nRecords);
  if (pabyBuf == NULL) {
    msSetError(MS_IOERR, "failed to allocate memory for SHX buffer",
               "msSHXLoadAll()");
    return MS_FAILURE;
  }
  if ((size_t)psSHP->nRecords !=
      VSIFReadL(pabyBuf, 8, psSHP->nRecords, psSHP->fpSHX)) {
    msSetError(MS_IOERR, "failed to read shx records", "msSHXLoadAll()");
    free(pabyBuf);
    return MS_FAILURE;
  }
  for (i = 0; i < psSHP->nRecords; i++) {
    ms_int32 nOffset, nLength;

    memcpy(&nOffset, pabyBuf + i * 8, 4);
    memcpy(&nLength, pabyBuf + i * 8 + 4, 4);

    if (!bBigEndian) {
      nOffset = SWAP_FOUR_BYTES(nOffset);
      nLength = SWAP_FOUR_BYTES(nLength);
    }

    /* SHX stores the offsets in 2 byte units, so we double them to get */
    /* an offset in bytes. */
    if (nOffset > 0 && nOffset < INT_MAX / 2)
      nOffset = nOffset * 2;
    else
      nOffset = 0;

    if (nLength > 0 && nLength < INT_MAX / 2)
      nLength = nLength * 2;
    else
      nLength = 0;

    psSHP->panRecOffset[i] = nOffset;
    psSHP->panRecSize[i] = nLength;
  }
  free(pabyBuf);
  psSHP->panRecAllLoaded = 1;

  return (MS_SUCCESS);
}

static int msSHXReadOffset(SHPHandle psSHP, int hEntity) {

  int shxBufferPage = hEntity / SHX_BUFFER_PAGE;

  /*  Validate the record/entity number. */
  if (hEntity < 0 || hEntity >= psSHP->nRecords)
    return 0;

  if (!(psSHP->panRecAllLoaded ||
        msGetBit(psSHP->panRecLoaded, shxBufferPage))) {
    msSHXLoadPage(psSHP, shxBufferPage);
  }

  return psSHP->panRecOffset[hEntity];
}

static int msSHXReadSize(SHPHandle psSHP, int hEntity) {

  int shxBufferPage = hEntity / SHX_BUFFER_PAGE;

  /*  Validate the record/entity number. */
  if (hEntity < 0 || hEntity >= psSHP->nRecords)
    return 0;

  if (!(psSHP->panRecAllLoaded ||
        msGetBit(psSHP->panRecLoaded, shxBufferPage))) {
    msSHXLoadPage(psSHP, shxBufferPage);
  }

  return psSHP->panRecSize[hEntity];
}

static void ReadRect(rectObj *r, const uchar *src) {
  memcpy(&r->minx, src, 8);
  memcpy(&r->miny, src + 8, 8);
  memcpy(&r->maxx, src + 16, 8);
  memcpy(&r->maxy, src + 24, 8);

  if (bBigEndian) {
    SwapWord(8, &r->minx);
    SwapWord(8, &r->miny);
    SwapWord(8, &r->maxx);
    SwapWord(8, &r->maxy);
  }
}

/*
** msSHPReadShape() - Reads the vertices for one shape from a shape file.
*/
void msSHPReadShape(SHPHandle psSHP, int hEntity, shapeObj *shape) {
  int i, j, k;
  int nEntitySize, nRequiredSize;

  msInitShape(shape); /* initialize the shape */

  /* -------------------------------------------------------------------- */
  /*      Validate the record/entity number.                              */
  /* -------------------------------------------------------------------- */
  if (hEntity < 0 || hEntity >= psSHP->nRecords)
    return;

  nEntitySize = msSHXReadSize(psSHP, hEntity);
  if (nEntitySize < 4 || nEntitySize > INT_MAX - 8) {
    shape->type = MS_SHAPE_NULL;
    msSetError(MS_SHPERR,
               "Corrupted feature encountered.  hEntity = %d, nEntitySize=%d",
               "msSHPReadShape()", hEntity, nEntitySize);
    return;
  }

  nEntitySize += 8;
  if (nEntitySize == 12) {
    shape->type = MS_SHAPE_NULL;
    return;
  }

  uchar *pabyRec = msSHPReadAllocateBuffer(psSHP, hEntity, "msSHPReadShape()");
  if (pabyRec == NULL) {
    shape->type = MS_SHAPE_NULL;
    return;
  }

  /* -------------------------------------------------------------------- */
  /*      Read the record.                                                */
  /* -------------------------------------------------------------------- */
  const int offset = msSHXReadOffset(psSHP, hEntity);
  if (offset <= 0 || 0 != VSIFSeekL(psSHP->fpSHP, offset, 0)) {
    msSetError(MS_IOERR, "failed to seek offset", "msSHPReadShape()");
    shape->type = MS_SHAPE_NULL;
    return;
  }
  if (1 != VSIFReadL(pabyRec, nEntitySize, 1, psSHP->fpSHP)) {
    msSetError(MS_IOERR, "failed to fread record", "msSHPReadPoint()");
    shape->type = MS_SHAPE_NULL;
    return;
  }

  /* -------------------------------------------------------------------- */
  /*  Extract vertices for a Polygon or Arc.            */
  /* -------------------------------------------------------------------- */
  if (psSHP->nShapeType == SHP_POLYGON || psSHP->nShapeType == SHP_ARC ||
      psSHP->nShapeType == SHP_POLYGONM || psSHP->nShapeType == SHP_ARCM ||
      psSHP->nShapeType == SHP_POLYGONZ || psSHP->nShapeType == SHP_ARCZ) {
    ms_int32 nPoints, nParts;

    if (nEntitySize < 40 + 8 + 4) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR,
                 "Corrupted feature encountered.  hEntity = %d, nEntitySize=%d",
                 "msSHPReadShape()", hEntity, nEntitySize);
      return;
    }

    /* copy the bounding box */
    ReadRect(&shape->bounds, pabyRec + 8 + 4);

    memcpy(&nPoints, pabyRec + 40 + 8, 4);
    memcpy(&nParts, pabyRec + 36 + 8, 4);

    if (bBigEndian) {
      nPoints = SWAP_FOUR_BYTES(nPoints);
      nParts = SWAP_FOUR_BYTES(nParts);
    }

    if (nPoints < 0 || nParts < 0 || nPoints > 50 * 1000 * 1000 ||
        nParts > 10 * 1000 * 1000) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR,
                 "Corrupted feature encountered.  hEntity = %d, nPoints =%d, "
                 "nParts = %d",
                 "msSHPReadShape()", hEntity, nPoints, nParts);
      return;
    }

    /* -------------------------------------------------------------------- */
    /*      Copy out the part array from the record.                        */
    /* -------------------------------------------------------------------- */

#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    /* when running with libFuzzer, allocate a new buffer for every
       call, to allow AddressSanitizer to detect memory errors */
    free(psSHP->panParts);
    psSHP->panParts = NULL;
    psSHP->nPartMax = 0;
#endif

    if (psSHP->nPartMax < nParts) {
      psSHP->panParts = (int *)SfRealloc(psSHP->panParts, nParts * sizeof(int));
      if (psSHP->panParts == NULL) {
        /* Reallocate previous successful size for following features */
        psSHP->panParts = (int *)msSmallMalloc(psSHP->nPartMax * sizeof(int));

        shape->type = MS_SHAPE_NULL;
        msSetError(MS_MEMERR,
                   "Out of memory. Cannot allocate %d bytes. Probably broken "
                   "shapefile at feature %d",
                   "msSHPReadShape()", (int)(nParts * sizeof(int)), hEntity);
        return;
      }
      psSHP->nPartMax = nParts;
    }
    if (psSHP->panParts == NULL) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_MEMERR, "Out of memory", "msSHPReadShape()");
      return;
    }

    /* With the previous checks on nPoints and nParts, */
    /* we should not overflow here and after */
    /* since 50 M * (16 + 8 + 8) = 1 600 MB */
    if (44 + 8 + 4 * nParts + 16 * nPoints > nEntitySize) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR,
                 "Corrupted .shp file : shape %d, nPoints=%d, nParts=%d.",
                 "msSHPReadShape()", hEntity, nPoints, nParts);
      return;
    }

    memcpy(psSHP->panParts, pabyRec + 44 + 8, 4 * nParts);
    if (bBigEndian) {
      for (i = 0; i < nParts; i++) {
        *(psSHP->panParts + i) = SWAP_FOUR_BYTES(*(psSHP->panParts + i));
      }
    }

    /* -------------------------------------------------------------------- */
    /*      Fill the shape structure.                                       */
    /* -------------------------------------------------------------------- */
    shape->line = (lineObj *)malloc(sizeof(lineObj) * nParts);
    MS_CHECK_ALLOC_NO_RET(shape->line, sizeof(lineObj) * nParts);

    shape->numlines = nParts;

    k = 0; /* overall point counter */
    for (i = 0; i < nParts; i++) {
      const ms_int32 end = i == nParts - 1 ? nPoints : psSHP->panParts[i + 1];
      if (psSHP->panParts[i] < 0 || end < 0 || end > nPoints ||
          psSHP->panParts[i] >= end) {
        msSetError(MS_SHPERR,
                   "Corrupted .shp file : shape %d, shape->line[%d].start=%d, "
                   "shape->line[%d].end=%d",
                   "msSHPReadShape()", hEntity, i, psSHP->panParts[i], i, end);
        while (--i >= 0)
          free(shape->line[i].point);
        free(shape->line);
        shape->line = NULL;
        shape->numlines = 0;
        shape->type = MS_SHAPE_NULL;
        return;
      }

      shape->line[i].numpoints = end - psSHP->panParts[i];
      if ((shape->line[i].point = (pointObj *)malloc(
               sizeof(pointObj) * shape->line[i].numpoints)) == NULL) {
        while (--i >= 0)
          free(shape->line[i].point);
        free(shape->line);
        shape->line = NULL;
        shape->numlines = 0;
        shape->type = MS_SHAPE_NULL;
        msSetError(MS_MEMERR, "Out of memory", "msSHPReadShape()");
        return;
      }

      /* nOffset = 44 + 8 + 4*nParts; */
      for (j = 0; j < shape->line[i].numpoints; j++) {
        memcpy(&(shape->line[i].point[j].x),
               pabyRec + 44 + 4 * nParts + 8 + k * 16, 8);
        memcpy(&(shape->line[i].point[j].y),
               pabyRec + 44 + 4 * nParts + 8 + k * 16 + 8, 8);

        if (bBigEndian) {
          SwapWord(8, &(shape->line[i].point[j].x));
          SwapWord(8, &(shape->line[i].point[j].y));
        }

        /* --------------------------------------------------------------------
         */
        /*      Polygon, Arc with Z values. */
        /* --------------------------------------------------------------------
         */
        shape->line[i].point[j].z = 0.0; /* initialize */
        if (psSHP->nShapeType == SHP_POLYGONZ ||
            psSHP->nShapeType == SHP_ARCZ) {
          const int nOffset = 44 + 8 + (4 * nParts) + (16 * nPoints);
          if (nEntitySize >= nOffset + 16 + 8 * nPoints) {
            memcpy(&(shape->line[i].point[j].z), pabyRec + nOffset + 16 + k * 8,
                   8);
            if (bBigEndian)
              SwapWord(8, &(shape->line[i].point[j].z));
          }
        }

        /* --------------------------------------------------------------------
         */
        /*      Measured arc and polygon support. */
        /* --------------------------------------------------------------------
         */
        shape->line[i].point[j].m = 0; /* initialize */
        if (psSHP->nShapeType == SHP_POLYGONM ||
            psSHP->nShapeType == SHP_ARCM) {
          const int nOffset = 44 + 8 + (4 * nParts) + (16 * nPoints);
          if (nEntitySize >= nOffset + 16 + 8 * nPoints) {
            memcpy(&(shape->line[i].point[j].m), pabyRec + nOffset + 16 + k * 8,
                   8);
            if (bBigEndian)
              SwapWord(8, &(shape->line[i].point[j].m));
          }
        }
        k++;
      }
    }

    if (psSHP->nShapeType == SHP_POLYGON || psSHP->nShapeType == SHP_POLYGONZ ||
        psSHP->nShapeType == SHP_POLYGONM)
      shape->type = MS_SHAPE_POLYGON;
    else
      shape->type = MS_SHAPE_LINE;

  }

  /* -------------------------------------------------------------------- */
  /*  Extract a MultiPoint.                                               */
  /* -------------------------------------------------------------------- */
  else if (psSHP->nShapeType == SHP_MULTIPOINT ||
           psSHP->nShapeType == SHP_MULTIPOINTM ||
           psSHP->nShapeType == SHP_MULTIPOINTZ) {
    ms_int32 nPoints;

    if (nEntitySize < 44 + 4) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR,
                 "Corrupted feature encountered.  recSize of feature %d=%d",
                 "msSHPReadShape()", hEntity, msSHXReadSize(psSHP, hEntity));
      return;
    }

    /* copy the bounding box */
    ReadRect(&shape->bounds, pabyRec + 8 + 4);

    memcpy(&nPoints, pabyRec + 44, 4);
    if (bBigEndian)
      nPoints = SWAP_FOUR_BYTES(nPoints);

    /* -------------------------------------------------------------------- */
    /*      Fill the shape structure.                                       */
    /* -------------------------------------------------------------------- */
    if ((shape->line = (lineObj *)malloc(sizeof(lineObj))) == NULL) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_MEMERR, "Out of memory", "msSHPReadShape()");
      return;
    }

    if (nPoints < 0 || nPoints > 50 * 1000 * 1000) {
      free(shape->line);
      shape->line = NULL;
      shape->numlines = 0;
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR, "Corrupted .shp file : shape %d, nPoints=%d.",
                 "msSHPReadShape()", hEntity, nPoints);
      return;
    }

    nRequiredSize = 48 + nPoints * 16;
    if (psSHP->nShapeType == SHP_MULTIPOINTZ ||
        psSHP->nShapeType == SHP_MULTIPOINTM)
      nRequiredSize += 16 + nPoints * 8;
    if (nRequiredSize > nEntitySize) {
      free(shape->line);
      shape->line = NULL;
      shape->numlines = 0;
      shape->type = MS_SHAPE_NULL;
      msSetError(
          MS_SHPERR,
          "Corrupted .shp file : shape %d : nPoints = %d, nEntitySize = %d",
          "msSHPReadShape()", hEntity, nPoints, nEntitySize);
      return;
    }

    shape->numlines = 1;
    shape->line[0].numpoints = nPoints;
    shape->line[0].point = (pointObj *)malloc(nPoints * sizeof(pointObj));
    if (shape->line[0].point == NULL) {
      free(shape->line);
      shape->line = NULL;
      shape->numlines = 0;
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_MEMERR, "Out of memory", "msSHPReadShape()");
      return;
    }

    for (i = 0; i < nPoints; i++) {
      memcpy(&(shape->line[0].point[i].x), pabyRec + 48 + 16 * i, 8);
      memcpy(&(shape->line[0].point[i].y), pabyRec + 48 + 16 * i + 8, 8);

      if (bBigEndian) {
        SwapWord(8, &(shape->line[0].point[i].x));
        SwapWord(8, &(shape->line[0].point[i].y));
      }

      /* -------------------------------------------------------------------- */
      /*      MulipointZ                                                      */
      /* -------------------------------------------------------------------- */
      shape->line[0].point[i].z = 0; /* initialize */
      if (psSHP->nShapeType == SHP_MULTIPOINTZ) {
        const int nOffset = 48 + 16 * nPoints;
        memcpy(&(shape->line[0].point[i].z), pabyRec + nOffset + 16 + i * 8, 8);
        if (bBigEndian)
          SwapWord(8, &(shape->line[0].point[i].z));
      }

      /* -------------------------------------------------------------------- */
      /*      Measured shape : multipont.                                     */
      /* -------------------------------------------------------------------- */
      shape->line[0].point[i].m = 0; /* initialize */
      if (psSHP->nShapeType == SHP_MULTIPOINTM) {
        const int nOffset = 48 + 16 * nPoints;
        memcpy(&(shape->line[0].point[i].m), pabyRec + nOffset + 16 + i * 8, 8);
        if (bBigEndian)
          SwapWord(8, &(shape->line[0].point[i].m));
      }
    }

    shape->type = MS_SHAPE_POINT;
  }

  /* -------------------------------------------------------------------- */
  /*  Extract a Point.                            */
  /* -------------------------------------------------------------------- */
  else if (psSHP->nShapeType == SHP_POINT || psSHP->nShapeType == SHP_POINTM ||
           psSHP->nShapeType == SHP_POINTZ) {

    if (nEntitySize < 20 + 8) {
      shape->type = MS_SHAPE_NULL;
      msSetError(MS_SHPERR,
                 "Corrupted feature encountered.  recSize of feature %d=%d",
                 "msSHPReadShape()", hEntity, msSHXReadSize(psSHP, hEntity));
      return;
    }

    /* -------------------------------------------------------------------- */
    /*      Fill the shape structure.                                       */
    /* -------------------------------------------------------------------- */
    shape->line = (lineObj *)malloc(sizeof(lineObj));
    MS_CHECK_ALLOC_NO_RET(shape->line, sizeof(lineObj));

    shape->numlines = 1;
    shape->line[0].numpoints = 1;
    shape->line[0].point = (pointObj *)msSmallMalloc(sizeof(pointObj));

    memcpy(&(shape->line[0].point[0].x), pabyRec + 12, 8);
    memcpy(&(shape->line[0].point[0].y), pabyRec + 20, 8);

    if (bBigEndian) {
      SwapWord(8, &(shape->line[0].point[0].x));
      SwapWord(8, &(shape->line[0].point[0].y));
    }

    /* -------------------------------------------------------------------- */
    /*      PointZ                                                          */
    /* -------------------------------------------------------------------- */
    shape->line[0].point[0].z = 0; /* initialize */
    if (psSHP->nShapeType == SHP_POINTZ) {
      const int nOffset = 20 + 8;
      if (nEntitySize >= nOffset + 8) {
        memcpy(&(shape->line[0].point[0].z), pabyRec + nOffset, 8);
        if (bBigEndian)
          SwapWord(8, &(shape->line[0].point[0].z));
      }
    }

    /* -------------------------------------------------------------------- */
    /*      Measured support : point.                                       */
    /* -------------------------------------------------------------------- */
    shape->line[0].point[0].m = 0; /* initialize */
    if (psSHP->nShapeType == SHP_POINTM) {
      const int nOffset = 20 + 8;
      if (nEntitySize >= nOffset + 8) {
        memcpy(&(shape->line[0].point[0].m), pabyRec + nOffset, 8);
        if (bBigEndian)
          SwapWord(8, &(shape->line[0].point[0].m));
      }
    }

    /* set the bounding box to the point */
    shape->bounds.minx = shape->bounds.maxx = shape->line[0].point[0].x;
    shape->bounds.miny = shape->bounds.maxy = shape->line[0].point[0].y;

    shape->type = MS_SHAPE_POINT;
  }

  shape->index = hEntity;

  return;
}

int msSHPReadBounds(SHPHandle psSHP, int hEntity, rectObj *padBounds) {
  /* -------------------------------------------------------------------- */
  /*      Validate the record/entity number.                              */
  /* -------------------------------------------------------------------- */
  if (psSHP->nRecords <= 0 || hEntity < -1 || hEntity >= psSHP->nRecords) {
    padBounds->minx = padBounds->miny = padBounds->maxx = padBounds->maxy = 0.0;
    return MS_FAILURE;
  }

  /* -------------------------------------------------------------------- */
  /*  If the entity is -1 we fetch the bounds for the whole file.   */
  /* -------------------------------------------------------------------- */
  if (hEntity == -1) {
    padBounds->minx = psSHP->adBoundsMin[0];
    padBounds->miny = psSHP->adBoundsMin[1];
    padBounds->maxx = psSHP->adBoundsMax[0];
    padBounds->maxy = psSHP->adBoundsMax[1];
  } else {

    if (msSHXReadSize(psSHP, hEntity) <= 4) { /* NULL shape */
      padBounds->minx = padBounds->miny = padBounds->maxx = padBounds->maxy =
          0.0;
      return MS_FAILURE;
    }

    const int offset = msSHXReadOffset(psSHP, hEntity);
    if (offset <= 0 || offset >= INT_MAX - 12 ||
        0 != VSIFSeekL(psSHP->fpSHP, offset + 12, 0)) {
      msSetError(MS_IOERR, "failed to seek offset", "msSHPReadBounds()");
      return (MS_FAILURE);
    }

    if (psSHP->nShapeType != SHP_POINT && psSHP->nShapeType != SHP_POINTZ &&
        psSHP->nShapeType != SHP_POINTM) {
      if (1 != VSIFReadL(padBounds, sizeof(double) * 4, 1, psSHP->fpSHP)) {
        msSetError(MS_IOERR, "failed to fread record", "msSHPReadBounds()");
        return (MS_FAILURE);
      }

      if (bBigEndian) {
        SwapWord(8, &(padBounds->minx));
        SwapWord(8, &(padBounds->miny));
        SwapWord(8, &(padBounds->maxx));
        SwapWord(8, &(padBounds->maxy));
      }

      if (msIsNan(padBounds->minx)) { /* empty shape */
        padBounds->minx = padBounds->miny = padBounds->maxx = padBounds->maxy =
            0.0;
        return MS_FAILURE;
      }
    } else {
      /* -------------------------------------------------------------------- */
      /*      For points we fetch the point, and duplicate it as the          */
      /*      minimum and maximum bound.                                      */
      /* -------------------------------------------------------------------- */
      if (1 != VSIFReadL(padBounds, sizeof(double) * 2, 1, psSHP->fpSHP)) {
        msSetError(MS_IOERR, "failed to fread record", "msSHPReadBounds()");
        return (MS_FAILURE);
      }

      if (bBigEndian) {
        SwapWord(8, &(padBounds->minx));
        SwapWord(8, &(padBounds->miny));
      }

      padBounds->maxx = padBounds->minx;
      padBounds->maxy = padBounds->miny;
    }
  }

  return MS_SUCCESS;
}

int msShapefileOpenHandle(shapefileObj *shpfile, const char *filename,
                          SHPHandle hSHP, DBFHandle hDBF) {
  assert(filename != NULL);
  assert(hSHP != NULL);
  assert(hDBF != NULL);

  /* initialize a few things */
  shpfile->status = NULL;
  shpfile->lastshape = -1;
  shpfile->isopen = MS_FALSE;

  shpfile->hSHP = hSHP;

  strlcpy(shpfile->source, filename, sizeof(shpfile->source));

  /* load some information about this shapefile */
  msSHPGetInfo(shpfile->hSHP, &shpfile->numshapes, &shpfile->type);

  if (shpfile->numshapes < 0 || shpfile->numshapes > 256000000) {
    msSetError(MS_SHPERR, "Corrupted .shp file : numshapes = %d.",
               "msShapefileOpen()", shpfile->numshapes);
    msDBFClose(hDBF);
    msSHPClose(hSHP);
    return -1;
  }

  msSHPReadBounds(shpfile->hSHP, -1, &(shpfile->bounds));

  shpfile->hDBF = hDBF;

  shpfile->isopen = MS_TRUE;
  return (0); /* all o.k. */
}

int msShapefileOpenVirtualFile(shapefileObj *shpfile, const char *filename,
                               VSILFILE *fpSHP, VSILFILE *fpSHX,
                               VSILFILE *fpDBF, int log_failures) {
  assert(filename != NULL);
  assert(fpSHP != NULL);
  assert(fpSHX != NULL);
  assert(fpDBF != NULL);

  /* open the shapefile file (appending ok) and get basic info */
  SHPHandle hSHP = msSHPOpenVirtualFile(fpSHP, fpSHX);
  if (!hSHP) {
    if (log_failures)
      msSetError(MS_IOERR, "(%s)", "msShapefileOpen()", filename);
    VSIFCloseL(fpDBF);
    return (-1);
  }

  DBFHandle hDBF = msDBFOpenVirtualFile(fpDBF);

  if (!hDBF) {
    if (log_failures)
      msSetError(MS_IOERR, "(%s)", "msShapefileOpen()", filename);
    msSHPClose(hSHP);
    return (-1);
  }

  return msShapefileOpenHandle(shpfile, filename, hSHP, hDBF);
}

int msShapefileOpen(shapefileObj *shpfile, const char *mode,
                    const char *filename, int log_failures) {
  int i;
  char *dbfFilename;
  size_t bufferSize = 0;

  if (!filename) {
    if (log_failures)
      msSetError(MS_IOERR, "No (NULL) filename provided.", "msShapefileOpen()");
    return (-1);
  }

  /* open the shapefile file (appending ok) and get basic info */
  SHPHandle hSHP;
  if (!mode)
    hSHP = msSHPOpen(filename, "rb");
  else
    hSHP = msSHPOpen(filename, mode);

  if (!hSHP) {
    if (log_failures)
      msSetError(MS_IOERR, "(%s)", "msShapefileOpen()", filename);
    return (-1);
  }

  bufferSize = strlen(filename) + 5;
  dbfFilename = (char *)msSmallMalloc(bufferSize);
  strcpy(dbfFilename, filename);

  /* clean off any extension the filename might have */
  for (i = strlen(dbfFilename) - 1;
       i > 0 && dbfFilename[i] != '.' && dbfFilename[i] != '/' &&
       dbfFilename[i] != '\\';
       i--) {
  }

  if (dbfFilename[i] == '.')
    dbfFilename[i] = '\0';

  strlcat(dbfFilename, ".dbf", bufferSize);

  DBFHandle hDBF = msDBFOpen(dbfFilename, "rb");

  if (!hDBF) {
    if (log_failures)
      msSetError(MS_IOERR, "(%s)", "msShapefileOpen()", dbfFilename);
    free(dbfFilename);
    msSHPClose(hSHP);
    return (-1);
  }
  free(dbfFilename);

  return msShapefileOpenHandle(shpfile, filename, hSHP, hDBF);
}

/* Creates a new shapefile */
int msShapefileCreate(shapefileObj *shpfile, char *filename, int type) {
  if (type != SHP_POINT && type != SHP_MULTIPOINT && type != SHP_ARC &&
      type != SHP_POLYGON && type != SHP_POINTM && type != SHP_MULTIPOINTM &&
      type != SHP_ARCM && type != SHP_POLYGONM && type != SHP_POINTZ &&
      type != SHP_MULTIPOINTZ && type != SHP_ARCZ && type != SHP_POLYGONZ) {
    msSetError(MS_SHPERR, "Invalid shape type.", "msNewSHPFile()");
    return (-1);
  }

  /* create the spatial portion */
  shpfile->hSHP = msSHPCreate(filename, type);
  if (!shpfile->hSHP) {
    msSetError(MS_IOERR, "(%s)", "msNewSHPFile()", filename);
    return (-1);
  }

  /* retrieve a few things about this shapefile */
  msSHPGetInfo(shpfile->hSHP, &shpfile->numshapes, &shpfile->type);
  msSHPReadBounds(shpfile->hSHP, -1, &(shpfile->bounds));

  /* initialize a few other things */
  shpfile->status = NULL;
  shpfile->lastshape = -1;
  shpfile->isopen = MS_TRUE;

  shpfile->hDBF = NULL; /* XBase file is NOT created here... */
  return (0);
}

void msShapefileClose(shapefileObj *shpfile) {
  if (shpfile && shpfile->isopen == MS_TRUE) { /* Silently return if called with
                                                  NULL shpfile by freeLayer() */
    if (shpfile->hSHP)
      msSHPClose(shpfile->hSHP);
    if (shpfile->hDBF)
      msDBFClose(shpfile->hDBF);
    free(shpfile->status);
    shpfile->isopen = MS_FALSE;
  }
}

/* status array lives in the shpfile, can return MS_SUCCESS/MS_FAILURE/MS_DONE
 */
int msShapefileWhichShapes(shapefileObj *shpfile, rectObj rect, int debug) {
  int i;
  rectObj shaperect;
  char *filename;

  free(shpfile->status);
  shpfile->status = NULL;

  /* rect and shapefile DON'T overlap... */
  if (msRectOverlap(&shpfile->bounds, &rect) != MS_TRUE)
    return (MS_DONE);

  if (msRectContained(&shpfile->bounds, &rect) == MS_TRUE) {
    shpfile->status = msAllocBitArray(shpfile->numshapes);
    if (!shpfile->status) {
      msSetError(MS_MEMERR, NULL, "msShapefileWhichShapes()");
      return (MS_FAILURE);
    }
    msSetAllBits(shpfile->status, shpfile->numshapes, 1);
  } else {

    /* deal with case where sourcename is of the form 'file.shp' */
    char *sourcename =
        msStrdup(shpfile->source); /* shape file source string from map file */
    char *s = strstr(sourcename, ".shp");
    if (s)
      *s = '\0';
    else {
      s = strstr(sourcename, ".SHP");
      if (s)
        *s = '\0';
    }

    filename =
        (char *)malloc(strlen(sourcename) + strlen(MS_INDEX_EXTENSION) + 1);
    MS_CHECK_ALLOC(filename,
                   strlen(sourcename) + strlen(MS_INDEX_EXTENSION) + 1,
                   MS_FAILURE);

    sprintf(filename, "%s%s", sourcename, MS_INDEX_EXTENSION);

    shpfile->status =
        msSearchDiskTree(filename, rect, debug, shpfile->numshapes);
    free(filename);
    free(sourcename);

    if (shpfile->status) { /* index  */
      msFilterTreeSearch(shpfile, shpfile->status, rect);
    } else { /* no index  */
      shpfile->status = msAllocBitArray(shpfile->numshapes);
      if (!shpfile->status) {
        msSetError(MS_MEMERR, NULL, "msShapefileWhichShapes()");
        return (MS_FAILURE);
      }

      for (i = 0; i < shpfile->numshapes; i++) {
        if (msSHPReadBounds(shpfile->hSHP, i, &shaperect) != MS_SUCCESS) {
          if (msSHXReadSize(shpfile->hSHP, i) == 4) { /* handle NULL shape */
            continue;
          }
          return (MS_FAILURE);
        }

        if (msRectOverlap(&shaperect, &rect) == MS_TRUE)
          msSetBit(shpfile->status, i, 1);
      }
    }
  }

  shpfile->lastshape = -1;

  return (MS_SUCCESS); /* success */
}

/* Return the absolute path to the given layer's tileindex file's directory */
void msTileIndexAbsoluteDir(char *tiFileAbsDir, layerObj *layer) {
  char tiFileAbsPath[MS_MAXPATHLEN];
  char *tiFileAbsDirTmp = NULL;

  msBuildPath(tiFileAbsPath, layer->map->mappath,
              layer->tileindex); /* absolute path to tileindex file */
  tiFileAbsDirTmp = msGetPath(tiFileAbsPath); /* tileindex file's directory */
  strlcpy(tiFileAbsDir, tiFileAbsDirTmp, MS_MAXPATHLEN);
  free(tiFileAbsDirTmp);
}

/*
** Build possible paths we might find the tile file at:
**   map dir + shape path + filename?
**   tile dir + shape path + filename?
**   map dir + filename?
**
** Returns
** MS_SUCCESS - found a file
** MS_FAILURE - no file, and map is configured to fail on missing
** MS_DONE - no file, and map is configured to continue on missing
*/
static int msTiledSHPTryOpen(shapefileObj *shpfile, layerObj *layer,
                             char *tiFileAbsDir, const char *filename) {
  char szPath[MS_MAXPATHLEN];
  int ignore_missing = msMapIgnoreMissingData(layer->map);
  int log_failures = MS_TRUE;

  if (ignore_missing == MS_MISSING_DATA_IGNORE)
    log_failures = MS_FALSE;

  if (msShapefileOpen(shpfile, "rb",
                      msBuildPath3(szPath, layer->map->mappath,
                                   layer->map->shapepath, filename),
                      log_failures) == -1) {
    if (msShapefileOpen(
            shpfile, "rb",
            msBuildPath3(szPath, tiFileAbsDir, layer->map->shapepath, filename),
            log_failures) == -1) {
      if (msShapefileOpen(shpfile, "rb",
                          msBuildPath(szPath, layer->map->mappath, filename),
                          log_failures) == -1) {
        if (ignore_missing == MS_MISSING_DATA_FAIL) {
          msSetError(
              MS_IOERR,
              "Unable to open shapefile '%s' for layer '%s' ... fatal error.",
              "msTiledSHPTryOpen()", filename, layer->name);
          return (MS_FAILURE);
        } else if (ignore_missing == MS_MISSING_DATA_LOG) {
          if (layer->debug || layer->map->debug) {
            msDebug("Unable to open shapefile '%s' for layer '%s' ... ignoring "
                    "this missing data.\n",
                    szPath, layer->name);
          }
          return (MS_DONE);
        } else if (ignore_missing == MS_MISSING_DATA_IGNORE) {
          return (MS_DONE);
        } else {
          /* never get here */
          msSetError(MS_IOERR, "msIgnoreMissingData returned unexpected value.",
                     "msTiledSHPTryOpen()");
          return (MS_FAILURE);
        }
      }
    }
  }
  return (MS_SUCCESS);
}

static const char *msTiledSHPLoadEntry(layerObj *layer, int i, char *tilename,
                                       size_t tilenamesize) {
  const char *filename;
  msTiledSHPLayerInfo *tSHP = layer->layerinfo;

  msProjectDestroyReprojector(tSHP->reprojectorFromTileProjToLayerProj);
  tSHP->reprojectorFromTileProjToLayerProj = NULL;

  msFreeProjection(&(tSHP->sTileProj));
  if (layer->tilesrs != NULL) {
    int idx = msDBFGetItemIndex(tSHP->tileshpfile->hDBF, layer->tilesrs);
    const char *pszWKT =
        msDBFReadStringAttribute(tSHP->tileshpfile->hDBF, i, idx);
    IGNORE_RET_VAL(
        msOGCWKT2ProjectionObj(pszWKT, &(tSHP->sTileProj), layer->debug));
  }

  if (!layer->data) /* assume whole filename is in attribute field */
    filename = msDBFReadStringAttribute(tSHP->tileshpfile->hDBF, i,
                                        layer->tileitemindex);
  else {
    snprintf(tilename, tilenamesize, "%s/%s",
             msDBFReadStringAttribute(tSHP->tileshpfile->hDBF, i,
                                      layer->tileitemindex),
             layer->data);
    filename = tilename;
  }

  return filename;
}

static int msTiledSHPOpenFile(layerObj *layer) {
  int i;
  const char *filename;
  char tilename[MS_MAXPATHLEN], szPath[MS_MAXPATHLEN];
  char tiFileAbsDir[MS_MAXPATHLEN];

  msTiledSHPLayerInfo *tSHP = NULL;

  if (layer->layerinfo != NULL) {
    return MS_SUCCESS; // Nothing to do... layer is already opened
  }

  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
    return MS_FAILURE;

  /* allocate space for a shapefileObj using layer->layerinfo  */
  tSHP = (msTiledSHPLayerInfo *)calloc(1, sizeof(msTiledSHPLayerInfo));
  MS_CHECK_ALLOC(tSHP, sizeof(msTiledSHPLayerInfo), MS_FAILURE);
  msInitProjection(&(tSHP->sTileProj));
  msProjectionInheritContextFrom(&(tSHP->sTileProj), &layer->projection);

  tSHP->shpfile = (shapefileObj *)malloc(sizeof(shapefileObj));
  if (tSHP->shpfile == NULL) {
    msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n",
               "msTiledSHPOpenFile()", __FILE__, __LINE__,
               (unsigned int)sizeof(shapefileObj));
    msFreeProjection(&(tSHP->sTileProj));
    free(tSHP);
    return MS_FAILURE;
  }

  tSHP->shpfile->isopen =
      MS_FALSE; /* in case of error: do not try to close the shpfile */
  tSHP->tileshpfile =
      NULL; /* may need this if not using a tile layer, look for malloc later */
  layer->layerinfo = tSHP;

  tSHP->tilelayerindex = msGetLayerIndex(layer->map, layer->tileindex);
  if (tSHP->tilelayerindex !=
      -1) { /* does the tileindex reference another layer */
    int status;
    layerObj *tlp;

    tlp = (GET_LAYER(layer->map, tSHP->tilelayerindex));

    if (tlp->connectiontype != MS_SHAPEFILE) {
      msSetError(MS_SHPERR, "Tileindex layer must be a shapefile.",
                 "msTiledSHPOpenFile()");
      return (MS_FAILURE);
    }

    status = msLayerOpen(tlp);
    if (status != MS_SUCCESS)
      return (MS_FAILURE);

    /* build item list */
    status = msLayerWhichItems(tlp, MS_FALSE, NULL);
    if (status != MS_SUCCESS)
      return (MS_FAILURE);

    tSHP->tileshpfile =
        (shapefileObj *)tlp->layerinfo; /* shapefiles use layerinfo to point to
                                           a shapefileObj */

  } else { /* or reference a shapefile directly */

    /* we need tSHP->tileshpfile if we're not working with a layer */
    tSHP->tileshpfile = (shapefileObj *)malloc(sizeof(shapefileObj));
    if (tSHP->tileshpfile == NULL) {
      msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n",
                 "msTiledSHPOpenFile()", __FILE__, __LINE__,
                 (unsigned int)sizeof(shapefileObj));
      free(tSHP->shpfile);
      msFreeProjection(&(tSHP->sTileProj));
      free(tSHP);
      layer->layerinfo = NULL;
      return MS_FAILURE;
    }

    if (msShapefileOpen(tSHP->tileshpfile, "rb",
                        msBuildPath3(szPath, layer->map->mappath,
                                     layer->map->shapepath, layer->tileindex),
                        MS_TRUE) == -1)
      if (msShapefileOpen(
              tSHP->tileshpfile, "rb",
              msBuildPath(szPath, layer->map->mappath, layer->tileindex),
              MS_TRUE) == -1)
        return (MS_FAILURE);
  }

  if ((layer->tileitemindex =
           msDBFGetItemIndex(tSHP->tileshpfile->hDBF, layer->tileitem)) == -1)
    return (MS_FAILURE);

  if (layer->tilesrs != NULL &&
      msDBFGetItemIndex(tSHP->tileshpfile->hDBF, layer->tilesrs) < 0) {
    msSetError(MS_SHPERR, "Cannot identify TILESRS field.",
               "msTiledSHPOpenFile()");
    return MS_FAILURE;
  }
  if (layer->tilesrs != NULL && layer->projection.numargs == 0) {
    msSetError(MS_SHPERR,
               "A layer with TILESRS set in TILEINDEX `%s' must have a "
               "projection set on itself.",
               "msOGRLayerOpen()", layer->tileindex);
    return MS_FAILURE;
  }

  msTileIndexAbsoluteDir(tiFileAbsDir, layer);

  /* position the source at the FIRST tile to use as a template, this is so the
   * functions that fill the iteminfo array have something to work from */
  for (i = 0; i < tSHP->tileshpfile->numshapes; i++) {
    int try_open;

    filename = msTiledSHPLoadEntry(layer, i, tilename, sizeof(tilename));
    if (strlen(filename) == 0)
      continue; /* check again */

    try_open = msTiledSHPTryOpen(tSHP->shpfile, layer, tiFileAbsDir, filename);
    if (try_open == MS_DONE)
      continue;
    else if (try_open == MS_FAILURE)
      return (MS_FAILURE);

    return (MS_SUCCESS); /* found a template, ok to proceed */
  }

  msSetError(MS_SHPERR,
             "Unable to open a single tile to use as a template in layer %s.",
             "msTiledSHPOpenFile()", layer->name ? layer->name : "(null)");
  return (MS_FAILURE);
}

static int msTiledSHPWhichShapes(layerObj *layer, rectObj rect, int isQuery) {
  int i, status;
  const char *filename;
  char tilename[MS_MAXPATHLEN];
  char tiFileAbsDir[MS_MAXPATHLEN];

  msTiledSHPLayerInfo *tSHP = NULL;

  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
    return MS_FAILURE;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPWhichShapes()");
    return (MS_FAILURE);
  }

  msShapefileClose(tSHP->shpfile); /* close previously opened files */

  tSHP->searchrect = rect; /* save the search extent */

  if (tSHP->tilelayerindex !=
      -1) { /* does the tileindex reference another layer */
    layerObj *tlp;
    shapeObj tshape;

    tlp = (GET_LAYER(layer->map, tSHP->tilelayerindex));
    status = msLayerWhichShapes(tlp, rect, isQuery);
    if (status != MS_SUCCESS)
      return (status); /* could be MS_DONE or MS_FAILURE */

    msTileIndexAbsoluteDir(tiFileAbsDir, layer);

    msInitShape(&tshape);
    while ((status = msLayerNextShape(tlp, &tshape)) == MS_SUCCESS) {
      int try_open;
      rectObj rectTile = rect;

      filename =
          msTiledSHPLoadEntry(layer, tshape.index, tilename, sizeof(tilename));
      if (strlen(filename) == 0)
        continue; /* check again */

      try_open =
          msTiledSHPTryOpen(tSHP->shpfile, layer, tiFileAbsDir, filename);
      if (try_open == MS_DONE)
        continue;
      else if (try_open == MS_FAILURE)
        return (MS_FAILURE);

      if (tSHP->sTileProj.numargs > 0) {
        msProjectRect(&(layer->projection), &(tSHP->sTileProj), &rectTile);
      }

      status = msShapefileWhichShapes(tSHP->shpfile, rectTile, layer->debug);
      if (status == MS_DONE) {
        /* Close and continue to next tile */
        msShapefileClose(tSHP->shpfile);
        continue;
      } else if (status != MS_SUCCESS) {
        msShapefileClose(tSHP->shpfile);
        return (MS_FAILURE);
      }

      /* the layer functions keeps track of this */
      /* tSHP->tileshpfile->lastshape = tshape.index; */
      break;
    }
    return (status); /* if we reach here we either 1) ran out of tiles or 2) had
                        an error reading a tile */

  } else { /* or reference a shapefile directly */
    int try_open;

    status = msShapefileWhichShapes(tSHP->tileshpfile, rect, layer->debug);
    if (status != MS_SUCCESS)
      return (status); /* could be MS_DONE or MS_FAILURE */

    msTileIndexAbsoluteDir(tiFileAbsDir, layer);

    /* position the source at the FIRST shapefile */
    for (i = 0; i < tSHP->tileshpfile->numshapes; i++) {
      rectObj rectTile = rect;
      if (msGetBit(tSHP->tileshpfile->status, i)) {

        filename = msTiledSHPLoadEntry(layer, i, tilename, sizeof(tilename));
        if (strlen(filename) == 0)
          continue; /* check again */

        try_open =
            msTiledSHPTryOpen(tSHP->shpfile, layer, tiFileAbsDir, filename);
        if (try_open == MS_DONE)
          continue;
        else if (try_open == MS_FAILURE)
          return (MS_FAILURE);

        if (tSHP->sTileProj.numargs > 0) {
          msProjectRect(&(layer->projection), &(tSHP->sTileProj), &rectTile);
        }

        status = msShapefileWhichShapes(tSHP->shpfile, rectTile, layer->debug);
        if (status == MS_DONE) {
          /* Close and continue to next tile */
          msShapefileClose(tSHP->shpfile);
          continue;
        } else if (status != MS_SUCCESS) {
          msShapefileClose(tSHP->shpfile);
          return (MS_FAILURE);
        }

        tSHP->tileshpfile->lastshape = i;
        break;
      }
    }

    if (i == tSHP->tileshpfile->numshapes)
      return (MS_DONE); /* no more tiles */
    else
      return (MS_SUCCESS);
  }

  return (MS_FAILURE); /* should *never* get here */
}

static int msTiledSHPNextShape(layerObj *layer, shapeObj *shape) {
  int i, status, filter_passed = MS_FALSE;
  const char *filename;
  char tilename[MS_MAXPATHLEN];
  char tiFileAbsDir[MS_MAXPATHLEN];

  msTiledSHPLayerInfo *tSHP = NULL;

  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
    return MS_FAILURE;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPNextShape()");
    return (MS_FAILURE);
  }

  msTileIndexAbsoluteDir(tiFileAbsDir, layer);

  do {
    i = tSHP->shpfile->lastshape + 1;
    while (i < tSHP->shpfile->numshapes && !msGetBit(tSHP->shpfile->status, i))
      i++; /* next "in" shape */

    if (i ==
        tSHP->shpfile->numshapes) {    /* done with this tile, need a new one */
      msShapefileClose(tSHP->shpfile); /* clean up */

      /* position the source to the NEXT shapefile based on the tileindex */
      if (tSHP->tilelayerindex !=
          -1) { /* does the tileindex reference another layer */
        layerObj *tlp;
        shapeObj tshape;
        int try_open;

        tlp = (GET_LAYER(layer->map, tSHP->tilelayerindex));

        msInitShape(&tshape);
        while ((status = msLayerNextShape(tlp, &tshape)) == MS_SUCCESS) {
          rectObj rectTile = tSHP->searchrect;

          filename = msTiledSHPLoadEntry(layer, tshape.index, tilename,
                                         sizeof(tilename));
          if (strlen(filename) == 0)
            continue; /* check again */

          try_open =
              msTiledSHPTryOpen(tSHP->shpfile, layer, tiFileAbsDir, filename);
          if (try_open == MS_DONE)
            continue;
          else if (try_open == MS_FAILURE)
            return (MS_FAILURE);

          if (tSHP->sTileProj.numargs > 0) {
            msProjectRect(&(layer->projection), &(tSHP->sTileProj), &rectTile);
          }

          status =
              msShapefileWhichShapes(tSHP->shpfile, rectTile, layer->debug);
          if (status == MS_DONE) {
            /* Close and continue to next tile */
            msShapefileClose(tSHP->shpfile);
            continue;
          } else if (status != MS_SUCCESS) {
            msShapefileClose(tSHP->shpfile);
            tSHP->tileshpfile->lastshape = -1;
            return (MS_FAILURE);
          }

          /* the layer functions keeps track of this */
          /* tSHP->tileshpfile->lastshape = tshape.index; */
          break;
        }

        if (status == MS_DONE) {
          tSHP->tileshpfile->lastshape = -1;
          return (MS_DONE); /* no more tiles */
        } else {
          msFreeShape(&tshape);
          continue; /* we've got shapes */
        }

      } else { /* or reference a shapefile directly   */

        for (i = (tSHP->tileshpfile->lastshape + 1);
             i < tSHP->tileshpfile->numshapes; i++) {
          if (msGetBit(tSHP->tileshpfile->status, i)) {
            rectObj rectTile = tSHP->searchrect;
            int try_open;

            filename =
                msTiledSHPLoadEntry(layer, i, tilename, sizeof(tilename));
            if (strlen(filename) == 0)
              continue; /* check again */

            try_open =
                msTiledSHPTryOpen(tSHP->shpfile, layer, tiFileAbsDir, filename);
            if (try_open == MS_DONE)
              continue;
            else if (try_open == MS_FAILURE)
              return (MS_FAILURE);

            if (tSHP->sTileProj.numargs > 0) {
              msProjectRect(&(layer->projection), &(tSHP->sTileProj),
                            &rectTile);
            }

            status =
                msShapefileWhichShapes(tSHP->shpfile, rectTile, layer->debug);
            if (status == MS_DONE) {
              /* Close and continue to next tile */
              msShapefileClose(tSHP->shpfile);
              continue;
            } else if (status != MS_SUCCESS) {
              msShapefileClose(tSHP->shpfile);
              tSHP->tileshpfile->lastshape = -1;
              return (MS_FAILURE);
            }

            tSHP->tileshpfile->lastshape = i;
            break;
          }
        } /* end for loop */

        if (i == tSHP->tileshpfile->numshapes) {
          tSHP->tileshpfile->lastshape = -1;
          return (MS_DONE); /* no more tiles */
        } else
          continue; /* we've got shapes */
      }
    }

    tSHP->shpfile->lastshape = i;

    msSHPReadShape(tSHP->shpfile->hSHP, i, shape);
    if (shape->type == MS_SHAPE_NULL) {
      msFreeShape(shape);
      continue; /* skip NULL shapes */
    }

    if (tSHP->sTileProj.numargs > 0) {
      if (tSHP->reprojectorFromTileProjToLayerProj == NULL) {
        tSHP->reprojectorFromTileProjToLayerProj = msProjectCreateReprojector(
            &(tSHP->sTileProj), &(layer->projection));
      }
      if (tSHP->reprojectorFromTileProjToLayerProj) {
        msProjectShapeEx(tSHP->reprojectorFromTileProjToLayerProj, shape);
      }
    }

    shape->tileindex = tSHP->tileshpfile->lastshape;
    shape->numvalues = layer->numitems;
    shape->values = msDBFGetValueList(tSHP->shpfile->hDBF, i, layer->iteminfo,
                                      layer->numitems);
    if (!shape->values)
      shape->numvalues = 0;

    filter_passed = MS_TRUE; /* By default accept ANY shape */
    if (layer->numitems > 0 && layer->iteminfo) {
      filter_passed = msEvalExpression(layer, shape, &(layer->filter),
                                       layer->filteritemindex);
    }

    if (!filter_passed)
      msFreeShape(shape); /* free's values as well */

  } while (
      !filter_passed); /* Loop until both spatial and attribute filters match */

  return (MS_SUCCESS);
}

static int msTiledSHPGetShape(layerObj *layer, shapeObj *shape,
                              resultObj *record) {
  const char *filename;
  char tilename[MS_MAXPATHLEN], szPath[MS_MAXPATHLEN];

  msTiledSHPLayerInfo *tSHP = NULL;
  char tiFileAbsDir[MS_MAXPATHLEN];

  long shapeindex = record->shapeindex;
  int tileindex = record->tileindex;

  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
    return MS_FAILURE;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPGetShape()");
    return (MS_FAILURE);
  }

  msTileIndexAbsoluteDir(tiFileAbsDir, layer);

  if ((tileindex < 0) || (tileindex >= tSHP->tileshpfile->numshapes))
    return (MS_FAILURE); /* invalid tile id */

  if (tileindex !=
      tSHP->tileshpfile->lastshape) { /* correct tile is not currently open so
                                         open the correct tile */
    msShapefileClose(tSHP->shpfile);  /* close current tile */

    filename =
        msTiledSHPLoadEntry(layer, tileindex, tilename, sizeof(tilename));

    /* open the shapefile, since a specific tile was request an error should be
     * generated if that tile does not exist */
    if (strlen(filename) == 0)
      return (MS_FAILURE);
    if (msShapefileOpen(
            tSHP->shpfile, "rb",
            msBuildPath3(szPath, tiFileAbsDir, layer->map->shapepath, filename),
            MS_TRUE) == -1) {
      if (msShapefileOpen(tSHP->shpfile, "rb",
                          msBuildPath3(szPath, layer->map->mappath,
                                       layer->map->shapepath, filename),
                          MS_TRUE) == -1) {
        if (msShapefileOpen(tSHP->shpfile, "rb",
                            msBuildPath(szPath, layer->map->mappath, filename),
                            MS_TRUE) == -1) {
          return (MS_FAILURE);
        }
      }
    }
  }

  if ((shapeindex < 0) || (shapeindex >= tSHP->shpfile->numshapes))
    return (MS_FAILURE);

  msSHPReadShape(tSHP->shpfile->hSHP, shapeindex, shape);
  tSHP->shpfile->lastshape = shapeindex;
  tSHP->tileshpfile->lastshape = tileindex;

  if (tSHP->sTileProj.numargs > 0) {
    if (tSHP->reprojectorFromTileProjToLayerProj == NULL) {
      tSHP->reprojectorFromTileProjToLayerProj =
          msProjectCreateReprojector(&(tSHP->sTileProj), &(layer->projection));
    }
    if (tSHP->reprojectorFromTileProjToLayerProj) {
      msProjectShapeEx(tSHP->reprojectorFromTileProjToLayerProj, shape);
    }
  }

  if (layer->numitems > 0 && layer->iteminfo) {
    shape->numvalues = layer->numitems;
    shape->values = msDBFGetValueList(tSHP->shpfile->hDBF, shapeindex,
                                      layer->iteminfo, layer->numitems);
    if (!shape->values)
      return (MS_FAILURE);
  }

  shape->tileindex = tileindex;

  return (MS_SUCCESS);
}

static void msTiledSHPClose(layerObj *layer) {
  msTiledSHPLayerInfo *tSHP = NULL;

  tSHP = layer->layerinfo;
  if (tSHP) {
    msShapefileClose(tSHP->shpfile);
    free(tSHP->shpfile);

    if (tSHP->tilelayerindex != -1) {
      layerObj *tlp;
      if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
        return;
      tlp = (GET_LAYER(layer->map, tSHP->tilelayerindex));
      msLayerClose(tlp);
    } else {
      msShapefileClose(tSHP->tileshpfile);
      free(tSHP->tileshpfile);
    }

    msProjectDestroyReprojector(tSHP->reprojectorFromTileProjToLayerProj);

    msFreeProjection(&(tSHP->sTileProj));

    free(tSHP);
  }
  layer->layerinfo = NULL;
}
/************************************************************************/
/*                              msTiledSHPClose()                       */
/* Overloaded version of msTiledSHPClose for virtual table architecture */
/************************************************************************/
int msTiledSHPCloseVT(layerObj *layer) {
  msTiledSHPClose(layer);
  return MS_SUCCESS;
}

void msTiledSHPLayerFreeItemInfo(layerObj *layer) {
  if (layer->iteminfo) {
    free(layer->iteminfo);
    layer->iteminfo = NULL;
  }
}

int msTiledSHPLayerInitItemInfo(layerObj *layer) {
  msTiledSHPLayerInfo *tSHP = NULL;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPLayerInitItemInfo()");
    return MS_FAILURE;
  }

  msTiledSHPLayerFreeItemInfo(layer);
  layer->iteminfo = (int *)msDBFGetItemIndexes(tSHP->shpfile->hDBF,
                                               layer->items, layer->numitems);
  if (!layer->iteminfo)
    return (MS_FAILURE);

  return MS_SUCCESS;
}

static void msSHPPassThroughFieldDefinitions(layerObj *layer, DBFHandle hDBF) {
  int numitems, i;

  numitems = msDBFGetFieldCount(hDBF);

  for (i = 0; i < numitems; i++) {
    char item[16];
    int nWidth = 0, nPrecision = 0;
    char gml_width[32], gml_precision[32];
    DBFFieldType eType;
    const char *gml_type = NULL;

    eType = msDBFGetFieldInfo(hDBF, i, item, &nWidth, &nPrecision);

    gml_width[0] = '\0';
    gml_precision[0] = '\0';

    switch (eType) {
    case FTInteger:
      gml_type = "Integer";
      sprintf(gml_width, "%d", nWidth);
      break;

    case FTDouble:
      gml_type = "Real";
      sprintf(gml_width, "%d", nWidth);
      sprintf(gml_precision, "%d", nPrecision);
      break;

    case FTString:
    default:
      gml_type = "Character";
      sprintf(gml_width, "%d", nWidth);
      break;
    }

    msUpdateGMLFieldMetadata(layer, item, gml_type, gml_width, gml_precision,
                             0);
  }
}

int msTiledSHPLayerGetItems(layerObj *layer) {
  msTiledSHPLayerInfo *tSHP = NULL;
  const char *value;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPLayerGetItems()");
    return MS_FAILURE;
  }

  layer->numitems = msDBFGetFieldCount(tSHP->shpfile->hDBF);
  layer->items = msDBFGetItems(tSHP->shpfile->hDBF);
  if (!layer->items)
    return MS_FAILURE;

  /* -------------------------------------------------------------------- */
  /*      consider populating the field definitions in metadata.          */
  /* -------------------------------------------------------------------- */
  if ((value = msOWSLookupMetadata(&(layer->metadata), "G", "types")) != NULL &&
      strcasecmp(value, "auto") == 0)
    msSHPPassThroughFieldDefinitions(layer, tSHP->shpfile->hDBF);

  return msTiledSHPLayerInitItemInfo(layer);
}

int msTiledSHPLayerGetExtent(layerObj *layer, rectObj *extent) {
  msTiledSHPLayerInfo *tSHP = NULL;

  tSHP = layer->layerinfo;
  if (!tSHP) {
    msSetError(MS_SHPERR, "Tiled shapefile layer has not been opened.",
               "msTiledSHPLayerGetExtent()");
    return MS_FAILURE;
  }

  *extent = tSHP->tileshpfile->bounds;
  return MS_SUCCESS;
}

int msTiledSHPLayerIsOpen(layerObj *layer) {
  if (layer->layerinfo)
    return MS_TRUE;
  else
    return MS_FALSE;
}

int msTiledSHPLayerSupportsCommonFilters(layerObj *layer) {
  (void)layer;
  return MS_TRUE;
}

int msTiledSHPLayerInitializeVirtualTable(layerObj *layer) {
  assert(layer != NULL);
  assert(layer->vtable != NULL);

  layer->vtable->LayerSupportsCommonFilters =
      msTiledSHPLayerSupportsCommonFilters;
  layer->vtable->LayerInitItemInfo = msTiledSHPLayerInitItemInfo;
  layer->vtable->LayerFreeItemInfo = msTiledSHPLayerFreeItemInfo;
  layer->vtable->LayerOpen = msTiledSHPOpenFile;
  layer->vtable->LayerIsOpen = msTiledSHPLayerIsOpen;
  layer->vtable->LayerWhichShapes = msTiledSHPWhichShapes;
  layer->vtable->LayerNextShape = msTiledSHPNextShape;
  /* no special version, use ...GetShape() */
  /* layer->vtable->LayerResultsGetShape = msTiledSHPGetShape; */
  layer->vtable->LayerGetShape = msTiledSHPGetShape;
  layer->vtable->LayerClose = msTiledSHPCloseVT;
  layer->vtable->LayerGetItems = msTiledSHPLayerGetItems;
  layer->vtable->LayerGetExtent = msTiledSHPLayerGetExtent;
  /* layer->vtable->LayerApplyFilterToLayer, use default */
  /* layer->vtable->LayerGetAutoStyle, use default */
  /* layer->vtable->LayerCloseConnection, use default */;
  layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
  /* layer->vtable->LayerCreateItems, use default */
  /* layer->vtable->LayerGetNumFeatures, use default */
  /* layer->vtable->LayerGetAutoProjection, use default*/

  return MS_SUCCESS;
}

/* SHAPEFILE Layer virtual table functions */

void msSHPLayerFreeItemInfo(layerObj *layer) {
  if (layer->iteminfo) {
    free(layer->iteminfo);
    layer->iteminfo = NULL;
  }
}

int msSHPLayerInitItemInfo(layerObj *layer) {
  shapefileObj *shpfile = layer->layerinfo;
  if (!shpfile) {
    msSetError(MS_SHPERR, "Shapefile layer has not been opened.",
               "msSHPLayerInitItemInfo()");
    return MS_FAILURE;
  }

  /* iteminfo needs to be a bit more complex, a list of indexes plus the length
   * of the list */
  msSHPLayerFreeItemInfo(layer);
  layer->iteminfo =
      (int *)msDBFGetItemIndexes(shpfile->hDBF, layer->items, layer->numitems);
  if (!layer->iteminfo) {
    return MS_FAILURE;
  }

  return MS_SUCCESS;
}

int msSHPLayerOpen(layerObj *layer) {
  char szPath[MS_MAXPATHLEN];
  shapefileObj *shpfile;

  if (layer->layerinfo)
    return MS_SUCCESS; /* layer already open */

  if (msCheckParentPointer(layer->map, "map") == MS_FAILURE)
    return MS_FAILURE;

  /* allocate space for a shapefileObj using layer->layerinfo  */
  shpfile = (shapefileObj *)malloc(sizeof(shapefileObj));
  MS_CHECK_ALLOC(shpfile, sizeof(shapefileObj), MS_FAILURE);

  layer->layerinfo = shpfile;

  if (msShapefileOpen(shpfile, "rb",
                      msBuildPath3(szPath, layer->map->mappath,
                                   layer->map->shapepath, layer->data),
                      MS_TRUE) == -1) {
    if (msShapefileOpen(shpfile, "rb",
                        msBuildPath(szPath, layer->map->mappath, layer->data),
                        MS_TRUE) == -1) {
      layer->layerinfo = NULL;
      free(shpfile);
      return MS_FAILURE;
    }
  }

  if (layer->projection.numargs > 0 &&
      EQUAL(layer->projection.args[0], "auto")) {
    const char *pszPRJFilename = CPLResetExtension(szPath, "prj");
    int bOK = MS_FALSE;
    VSILFILE *fp = VSIFOpenL(pszPRJFilename, "rb");
    if (fp != NULL) {
      char szPRJ[2048];
      OGRSpatialReferenceH hSRS;
      int nRead;

      nRead = (int)VSIFReadL(szPRJ, 1, sizeof(szPRJ) - 1, fp);
      szPRJ[nRead] = '\0';
      hSRS = OSRNewSpatialReference(szPRJ);
      if (hSRS != NULL) {
        if (OSRMorphFromESRI(hSRS) == OGRERR_NONE) {
          char *pszWKT = NULL;
          if (OSRExportToWkt(hSRS, &pszWKT) == OGRERR_NONE) {
            if (msOGCWKT2ProjectionObj(pszWKT, &(layer->projection),
                                       layer->debug) == MS_SUCCESS) {
              bOK = MS_TRUE;
            }
          }
          CPLFree(pszWKT);
        }
        OSRDestroySpatialReference(hSRS);
      }
      VSIFCloseL(fp);
    }

    if (bOK != MS_TRUE) {
      if (layer->debug || layer->map->debug) {
        msDebug("Unable to get SRS from shapefile '%s' for layer '%s'.\n",
                szPath, layer->name);
      }
    }
  }

  return MS_SUCCESS;
}

int msSHPLayerIsOpen(layerObj *layer) {
  if (layer->layerinfo)
    return MS_TRUE;
  else
    return MS_FALSE;
}

int msSHPLayerWhichShapes(layerObj *layer, rectObj rect, int isQuery) {
  (void)isQuery;
  int status;
  shapefileObj *shpfile;

  shpfile = layer->layerinfo;

  if (!shpfile) {
    msSetError(MS_SHPERR, "Shapefile layer has not been opened.",
               "msSHPLayerWhichShapes()");
    return MS_FAILURE;
  }

  status = msShapefileWhichShapes(shpfile, rect, layer->debug);
  if (status != MS_SUCCESS) {
    return status;
  }

  return MS_SUCCESS;
}

int msSHPLayerNextShape(layerObj *layer, shapeObj *shape) {
  int i;
  shapefileObj *shpfile;

  shpfile = layer->layerinfo;

  if (!shpfile) {
    msSetError(MS_SHPERR, "Shapefile layer has not been opened.",
               "msSHPLayerNextShape()");
    return MS_FAILURE;
  }

  if (!shpfile->status) {
    /* probably whichShapes didn't overlap */
    return MS_DONE;
  }

  i = msGetNextBit(shpfile->status, shpfile->lastshape + 1, shpfile->numshapes);
  shpfile->lastshape = i;
  if (i == -1)
    return (MS_DONE); /* nothing else to read */

  msSHPReadShape(shpfile->hSHP, i, shape);
  if (shape->type == MS_SHAPE_NULL) {
    msFreeShape(shape);
    return msSHPLayerNextShape(layer, shape); /* skip NULL shapes */
  }
  shape->numvalues = layer->numitems;
  shape->values =
      msDBFGetValueList(shpfile->hDBF, i, layer->iteminfo, layer->numitems);
  if (!shape->values)
    shape->numvalues = 0;

  return MS_SUCCESS;
}

int msSHPLayerGetShape(layerObj *layer, shapeObj *shape, resultObj *record) {
  shapefileObj *shpfile;
  long shapeindex;

  shpfile = layer->layerinfo;

  shapeindex = record->shapeindex;

  if (!shpfile) {
    msSetError(MS_SHPERR, "Shapefile layer has not been opened.",
               "msSHPLayerGetShape()");
    return MS_FAILURE;
  }

  /* msSHPReadShape *should* return success or failure so we don't have to test
   * here */
  if (shapeindex < 0 || shapeindex >= shpfile->numshapes) {
    msSetError(MS_MISCERR, "Invalid feature id.", "msSHPLayerGetShape()");
    return MS_FAILURE;
  }

  msSHPReadShape(shpfile->hSHP, shapeindex, shape);
  if (layer->numitems > 0 && layer->iteminfo) {
    shape->numvalues = layer->numitems;
    shape->values = msDBFGetValueList(shpfile->hDBF, shapeindex,
                                      layer->iteminfo, layer->numitems);
    if (!shape->values)
      return MS_FAILURE;
  }

  shpfile->lastshape = shapeindex;

  return MS_SUCCESS;
}

int msSHPLayerClose(layerObj *layer) {
  shapefileObj *shpfile;
  shpfile = layer->layerinfo;
  if (!shpfile)
    return MS_SUCCESS; /* nothing to do */

  msShapefileClose(shpfile);
  free(layer->layerinfo);
  layer->layerinfo = NULL;

  return MS_SUCCESS;
}

int msSHPLayerGetItems(layerObj *layer) {
  shapefileObj *shpfile;
  const char *value;

  shpfile = layer->layerinfo;

  if (!shpfile) {
    msSetError(MS_SHPERR, "Shapefile layer has not been opened.",
               "msSHPLayerGetItems()");
    return MS_FAILURE;
  }

  layer->numitems = msDBFGetFieldCount(shpfile->hDBF);
  layer->items = msDBFGetItems(shpfile->hDBF);
  if (layer->numitems == 0)
    return MS_SUCCESS; /* No items is a valid case (#3147) */
  if (!layer->items)
    return MS_FAILURE;

  /* -------------------------------------------------------------------- */
  /*      consider populating the field definitions in metadata.          */
  /* -------------------------------------------------------------------- */
  if ((value = msOWSLookupMetadata(&(layer->metadata), "G", "types")) != NULL &&
      strcasecmp(value, "auto") == 0)
    msSHPPassThroughFieldDefinitions(layer, shpfile->hDBF);

  return msLayerInitItemInfo(layer);
}

int msSHPLayerGetExtent(layerObj *layer, rectObj *extent) {
  *extent = ((shapefileObj *)layer->layerinfo)->bounds;
  return MS_SUCCESS;
}

int msSHPLayerSupportsCommonFilters(layerObj *layer) {
  (void)layer;
  return MS_TRUE;
}

int msSHPLayerInitializeVirtualTable(layerObj *layer) {
  assert(layer != NULL);
  assert(layer->vtable != NULL);

  layer->vtable->LayerSupportsCommonFilters = msSHPLayerSupportsCommonFilters;
  layer->vtable->LayerInitItemInfo = msSHPLayerInitItemInfo;
  layer->vtable->LayerFreeItemInfo = msSHPLayerFreeItemInfo;
  layer->vtable->LayerOpen = msSHPLayerOpen;
  layer->vtable->LayerIsOpen = msSHPLayerIsOpen;
  layer->vtable->LayerWhichShapes = msSHPLayerWhichShapes;
  layer->vtable->LayerNextShape = msSHPLayerNextShape;
  layer->vtable->LayerGetShape = msSHPLayerGetShape;
  /* layer->vtable->LayerGetShapeCount, use default */
  layer->vtable->LayerClose = msSHPLayerClose;
  layer->vtable->LayerGetItems = msSHPLayerGetItems;
  layer->vtable->LayerGetExtent = msSHPLayerGetExtent;
  /* layer->vtable->LayerGetAutoStyle, use default */
  /* layer->vtable->LayerCloseConnection, use default */
  layer->vtable->LayerSetTimeFilter = msLayerMakeBackticsTimeFilter;
  /* layer->vtable->LayerTranslateFilter, use default */
  /* layer->vtable->LayerApplyFilterToLayer, use default */
  /* layer->vtable->LayerCreateItems, use default */
  /* layer->vtable->LayerGetNumFeatures, use default */

  return MS_SUCCESS;
}
