String preg_quote(const String& str,
                  const String& delimiter /* = null_string */) {
  const char* in_str = str.data();
  const char* in_str_end = in_str + str.size();

  /* Nothing to do if we got an empty string */
  if (in_str == in_str_end) {
    return str;
  }

  char delim_char = 0;      /* Delimiter character to be quoted */
  bool quote_delim = false; /* Whether to quote additional delim char */
  if (!delimiter.empty()) {
    delim_char = delimiter.charAt(0);
    quote_delim = true;
  }

  /* Allocate enough memory so that even if each character
     is quoted, we won't run out of room */
  static_assert(
    (StringData::MaxSize * 4 + 1) < std::numeric_limits<int64_t>::max()
  );
  String ret(4 * str.size() + 1, ReserveString);
  char* out_str = ret.mutableData();

  /* Go through the string and quote necessary characters */
  const char* p;
  char* q;
  for (p = in_str, q = out_str; p != in_str_end; p++) {
    char c = *p;
    switch (c) {
    case '.': case '\\': case '+': case '*': case '?':
    case '[': case '^':  case ']': case '$': case '(':
    case ')': case '{':  case '}': case '=': case '!':
    case '>': case '<':  case '|': case ':': case '-':
    case '#':
      *q++ = '\\';
      *q++ = c;
      break;

    case '\0':
      *q++ = '\\';
      *q++ = '0';
      *q++ = '0';
      *q++ = '0';
      break;

    default:
      if (quote_delim && c == delim_char)
        *q++ = '\\';
      *q++ = c;
      break;
    }
  }
  *q = '\0';

  return ret.setSize(q - out_str);
}


security fixes

https://hhvm.com/blog/2021/02/25/security-update.html

  const std::string getPat() const {
    return pat;
  }

In-memory file operations (ie: using fopen on a data URI) did not properly restrict negative seeking, allowing for the reading of memory prior to the in-memory buffer. This issue affects HHVM versions prior to 4.56.3, all versions between 4.57.0 and 4.80.1, all versions between 4.81.0 and 4.93.1, and versions 4.94.0, 4.95.0, 4.96.0, 4.97.0, 4.98.0.


static void exif_thumbnail_extract(image_info_type *ImageInfo,
                                   char *offset, size_t length) {
  if (ImageInfo->Thumbnail.data) {
    raise_warning("Multiple possible thumbnails");
    return; /* Should not happen */
  }
  if (!ImageInfo->read_thumbnail) {
    return; /* ignore this call */
  }
  /* according to exif2.1, the thumbnail is not supposed to be greater
     than 64K */
  if (ImageInfo->Thumbnail.size >= 65536 ||
      ImageInfo->Thumbnail.size <= 0 ||
      ImageInfo->Thumbnail.offset <= 0) {
    raise_warning("Illegal thumbnail size/offset");
    return;
  }
  /* Check to make sure we are not going to go past the ExifLength */
  if ((ImageInfo->Thumbnail.offset + ImageInfo->Thumbnail.size) > length) {
    raise_warning("Thumbnail goes IFD boundary or end of file reached");
    return;
  }
  PHP_STRNDUP(ImageInfo->Thumbnail.data, offset + ImageInfo->Thumbnail.offset,
              ImageInfo->Thumbnail.size);
  exif_thumbnail_build(ImageInfo);
}


void PCRECache::insert(
  Accessor& accessor,
  const StringData* regex,
  TempKeyCache& keyCache,
  const pcre_cache_entry* ent
) {
  switch (m_kind) {
    case CacheKind::Static:
      {
        assertx(m_staticCache.load());
        // Clear the cache if we haven't refreshed it in a while
        if (time(nullptr) > m_expire) {
          clearStatic();
        }
        auto const cache = m_staticCache.load(std::memory_order_acquire);
        auto const key =
          regex->isStatic() ||
          (regex->isUncounted() && regex->uncountedIncRef()) ?
          regex : StringData::MakeUncounted(regex->slice());
        auto pair = cache->insert(StaticCachePair(key, ent));
        if (pair.second) {
          // Inserted, container owns the pointer
          accessor = ent;
        } else {
          // Not inserted, caller needs to own the pointer
          if (regex->isUncounted()) StringData::ReleaseUncounted(key);
          accessor = EntryPtr(ent);
        }
      }
      break;
    case CacheKind::Lru:
    case CacheKind::Scalable:
      {
        if (!keyCache) {
          keyCache.reset(new LRUCacheKey(regex->data(), regex->size()));
        }
        // Pointer ownership is shared between container and caller
        accessor = EntryPtr(ent);
        if (m_kind == CacheKind::Lru) {
          m_lruCache->insert(*keyCache, accessor.entryPtr());
        } else {
          m_scalableCache->insert(*keyCache, accessor.entryPtr());
        }
      }
      break;
  }
}


static char *php_image_type_to_mime_type(int image_type) {
  switch( image_type) {
  case IMAGE_FILETYPE_GIF:
    return "image/gif";
  case IMAGE_FILETYPE_JPEG:
    return "image/jpeg";
  case IMAGE_FILETYPE_PNG:
    return "image/png";
  case IMAGE_FILETYPE_SWF:
  case IMAGE_FILETYPE_SWC:
    return "application/x-shockwave-flash";
  case IMAGE_FILETYPE_PSD:
    return "image/psd";
  case IMAGE_FILETYPE_BMP:
    return "image/x-ms-bmp";
  case IMAGE_FILETYPE_TIFF_II:
  case IMAGE_FILETYPE_TIFF_MM:
    return "image/tiff";
  case IMAGE_FILETYPE_IFF:
    return "image/iff";
  case IMAGE_FILETYPE_WBMP:
    return "image/vnd.wap.wbmp";
  case IMAGE_FILETYPE_JPC:
    return "application/octet-stream";
  case IMAGE_FILETYPE_JP2:
    return "image/jp2";
  case IMAGE_FILETYPE_XBM:
    return "image/xbm";
  case IMAGE_FILETYPE_ICO:
    return "image/vnd.microsoft.icon";
  default:
  case IMAGE_FILETYPE_UNKNOWN:
    return "application/octet-stream"; /* suppose binary format */
  }
}


bool HHVM_FUNCTION(is_int, const Variant& v) {
  return is_int(v.asTypedValue());
}


Variant HHVM_FUNCTION(imagesx, const Resource& image) {
  gdImagePtr im = get_valid_image_resource(image);
  if (!im) return false;
  return gdImageSX(im);
}


bool MemFile::rewind() {
  assertx(m_len != -1);
  m_cursor = 0;
  setWritePosition(0);
  setReadPosition(0);
  setPosition(0);
  return true;
}


Variant HHVM_FUNCTION(str_word_count,
                      const String& str,
                      int64_t format /* = 0 */,
                      const String& charlist /* = "" */) {
  int str_len = str.size();
  switch (format) {
  case 1:
  case 2:
    if (!str_len) {
      return empty_array();
    }
    break;
  case 0:
    if (!str_len) {
      return 0LL;
    }
    break;
  default:
    raise_invalid_argument_warning("format: %" PRId64, format);
    return false;
  }

  char ch[256];
  const char *char_list = charlist.data();
  if (*char_list) {
    string_charmask(char_list, charlist.size(), ch);
  } else {
    char_list = nullptr;
  }

  int word_count = 0;
  const char *s0 = str.data();
  const char *p = s0;
  const char *e = p + str_len;

  // first character cannot be ' or -, unless explicitly allowed by the user
  if ((*p == '\'' && (!char_list || !ch[(int)'\''])) ||
      (*p == '-' && (!char_list || !ch[(int)'-']))) {
    p++;
  }

  // last character cannot be -, unless explicitly allowed by the user
  if (*(e - 1) == '-' && (!char_list || !ch[(int)'-'])) {
    e--;
  }

  Array ret = Array::CreateDArray();
  while (p < e) {
    const char *s = p;
    while (p < e &&
           (isalpha(*p) || (char_list && ch[(unsigned char)*p]) ||
            *p == '\'' || *p == '-')) {
      p++;
    }
    if (p > s) {
      switch (format) {
      case 1:
        ret.append(String(s, p - s, CopyString));
        break;
      case 2:
        ret.set((int)(s - s0), String(s, p - s, CopyString));
        break;
      default:
        word_count++;
        break;
      }
    }
    p++;
  }

  if (!format) {
    return word_count;
  }
  return ret;
}


    const EntryPtr& entryPtr() const {
      assertx(m_kind == Kind::SmartPtr);
      return m_u.smart_ptr;
    }


void MemFile::unzip() {
  assertx(m_len != -1);
  assertx(!m_malloced);
  assertx(m_cursor == 0);
  int len = m_len;
  char *data = gzdecode(m_data, len);
  if (data == nullptr) {
    raise_fatal_error((std::string("cannot unzip mem stream: ") +
                               getName()).c_str());
  }
  m_data = data;
  m_malloced = true;
  m_len = len;
}


size_t PCRECache::size() const {
  switch (m_kind) {
    case CacheKind::Static:
      return m_staticCache.load(std::memory_order_acquire)->size();
    case CacheKind::Lru:
      return m_lruCache->size();
    case CacheKind::Scalable:
     return m_scalableCache->size();
  }
  always_assert(false);
}


char   toByte   () const { return m_str ? m_str->toByte   () : 0;}


explicit String(StringData *data) : m_str(data) { }


  String& operator=(req::ptr<StringData>&& src) {
    m_str = std::move(src);
    return *this;
  }


static req::ptr<MCrypt> get_valid_mcrypt_resource(const Resource& td) {
  auto pm = dyn_cast_or_null<MCrypt>(td);

  if (pm == nullptr || pm->isInvalid()) {
    raise_warning("supplied argument is not a valid MCrypt resource");
    return nullptr;
  }

  return pm;
}


inline StringData* StringData::modifyChar(int offset, char c) {
  assertx(offset >= 0 && offset < size());
  assertx(!hasMultipleRefs());

  auto const sd = isProxy() ? escalate(size()) : this;
  sd->mutableData()[offset] = c;
  sd->m_hash = 0;
  return sd;
}


int spprintf(char **pbuf, size_t max_len, const char *format, ...)
{
  int cc;
  va_list ap;

  va_start(ap, format);
  cc = vspprintf(pbuf, max_len, format, ap);
  va_end(ap);
  return (cc);
}


static void* exif_ifd_make_value(image_info_data *info_data,
                                 int motorola_intel) {
  size_t  byte_count;
  char *value_ptr, *data_ptr;
  size_t  i;

  image_info_value  *info_value;

  byte_count =
    get_php_tiff_bytes_per_format(info_data->format) * info_data->length;
  size_t malloc_size = byte_count > 4 ? byte_count : 4;
  value_ptr = (char *)IM_MALLOC(malloc_size);
  CHECK_ALLOC_R(value_ptr, malloc_size, nullptr);
  memset(value_ptr, 0, 4);
  if (!info_data->length) {
    return value_ptr;
  }
  if (info_data->format == TAG_FMT_UNDEFINED ||
      info_data->format == TAG_FMT_STRING ||
      (byte_count>1 && (info_data->format == TAG_FMT_BYTE ||
                        info_data->format == TAG_FMT_SBYTE))) {
    memmove(value_ptr, info_data->value.s, byte_count);
    return value_ptr;
  } else if (info_data->format == TAG_FMT_BYTE) {
    *value_ptr = info_data->value.u;
    return value_ptr;
  } else if (info_data->format == TAG_FMT_SBYTE) {
    *value_ptr = info_data->value.i;
    return value_ptr;
  } else {
    data_ptr = value_ptr;
    for(i=0; i<info_data->length; i++) {
      if (info_data->length==1) {
        info_value = &info_data->value;
      } else {
        info_value = &info_data->value.list[i];
      }
      switch(info_data->format) {
      case TAG_FMT_USHORT:
        php_ifd_set16u(data_ptr, info_value->u, motorola_intel);
        data_ptr += 2;
        break;
      case TAG_FMT_ULONG:
        php_ifd_set32u(data_ptr, info_value->u, motorola_intel);
        data_ptr += 4;
        break;
      case TAG_FMT_SSHORT:
        php_ifd_set16u(data_ptr, info_value->i, motorola_intel);
        data_ptr += 2;
        break;
      case TAG_FMT_SLONG:
        php_ifd_set32u(data_ptr, info_value->i, motorola_intel);
        data_ptr += 4;
        break;
      case TAG_FMT_URATIONAL:
        php_ifd_set32u(data_ptr,   info_value->sr.num, motorola_intel);
        php_ifd_set32u(data_ptr+4, info_value->sr.den, motorola_intel);
        data_ptr += 8;
        break;
      case TAG_FMT_SRATIONAL:
        php_ifd_set32u(data_ptr,   info_value->ur.num, motorola_intel);
        php_ifd_set32u(data_ptr+4, info_value->ur.den, motorola_intel);
        data_ptr += 8;
        break;
      case TAG_FMT_SINGLE:
        memmove(data_ptr, &info_value->f, 4);
        data_ptr += 4;
        break;
      case TAG_FMT_DOUBLE:
        memmove(data_ptr, &info_value->d, 8);
        data_ptr += 8;
        break;
      }
    }
  }
  return value_ptr;
}


bool HHVM_FUNCTION(imagesetbrush,
    const Resource& image, const Resource& brush) {
  gdImagePtr im = get_valid_image_resource(image);
  if (!im) return false;
  gdImagePtr tile = get_valid_image_resource(brush);
  if (!tile) return false;
  gdImageSetBrush(im, tile);
  unsafe_cast_or_null<Image>(image)->m_brush =
    unsafe_cast_or_null<Image>(brush);
  return true;
}