KittyUtils.cpp
1 #include "KittyUtils.hpp" 2 3 namespace KittyUtils 4 { 5 6 std::vector<uint8_t> randomBytes(std::size_t length) 7 { 8 static std::mutex mtx; 9 std::lock_guard<std::mutex> lock(mtx); 10 11 static std::mt19937 gen{std::random_device{}()}; 12 13 std::uniform_int_distribution<uint16_t> dist(0, 255); 14 15 std::vector<uint8_t> data(length); 16 for (auto &b : data) 17 { 18 b = static_cast<uint8_t>(dist(gen)); 19 } 20 21 return data; 22 } 23 24 std::string randomString(size_t length) 25 { 26 static const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 27 28 static std::mutex mtx; 29 std::lock_guard<std::mutex> lock(mtx); 30 31 static std::default_random_engine rnd(std::random_device{}()); 32 33 std::uniform_int_distribution<std::string::size_type> dist(0, chars.size() - 1); 34 35 std::string str(length, '\0'); 36 for (size_t i = 0; i < length; ++i) 37 str[i] = chars[dist(rnd)]; 38 39 return str; 40 } 41 42 #ifdef __ANDROID__ 43 int Android::getVersion() 44 { 45 static int ver = 0; 46 if (ver > 0) 47 return ver; 48 49 ver = getSystemProperty<int>("ro.build.version.release", 0); 50 return ver; 51 } 52 53 int Android::getSDK() 54 { 55 static int sdk = 0; 56 if (sdk > 0) 57 return sdk; 58 59 sdk = getSystemProperty<int>("ro.build.version.sdk", 0); 60 return sdk; 61 } 62 63 bool Android::is64BitSupported() 64 { 65 static bool once = false; 66 static bool is64 = false; 67 if (!once) 68 { 69 char value[0xff]{}; 70 if (__system_property_get("ro.product.cpu.abilist", value) == 0) 71 __system_property_get("ro.product.cpu.abi", value); 72 73 std::string abi = value; 74 is64 = abi.find("64") != std::string::npos; 75 once = true; 76 } 77 return is64; 78 } 79 80 std::string Android::getAppInternalDataDir(const std::string &packageName) 81 { 82 std::string dir = getAppInternalCacheDir(packageName); 83 if (!dir.empty()) 84 { 85 return Path::fileDirectory(dir); 86 } 87 return dir; 88 } 89 90 std::string Android::getAppInternalFilesDir(const std::string &packageName) 91 { 92 std::string dir = getAppInternalCacheDir(packageName); 93 if (!dir.empty()) 94 { 95 dir = Path::fileDirectory(dir); 96 dir += "/files"; 97 } 98 return dir; 99 } 100 101 std::string Android::getAppInternalCacheDir(const std::string &packageName) 102 { 103 std::string dir = "/data/data/"; 104 dir += packageName; 105 dir += "/cache"; 106 if (access(dir.c_str(), F_OK) == 0) 107 return dir; 108 109 dir = "/data/user/"; 110 dir += std::to_string(getUserId()); 111 dir += "/"; 112 dir += packageName; 113 dir += "/cache"; 114 if (access(dir.c_str(), F_OK) == 0) 115 return dir; 116 117 return std::string(); 118 } 119 #endif 120 121 std::string Path::fileName(const std::string &filePath) 122 { 123 std::string filename; 124 const size_t last_slash_idx = filePath.find_last_of("/\\"); 125 if (std::string::npos != last_slash_idx) 126 filename = filePath.substr(last_slash_idx + 1); 127 return filename; 128 } 129 130 std::string Path::fileDirectory(const std::string &filePath) 131 { 132 std::string directory; 133 const size_t last_slash_idx = filePath.find_last_of("/\\"); 134 if (std::string::npos != last_slash_idx) 135 directory = filePath.substr(0, last_slash_idx); 136 return directory; 137 } 138 139 std::string Path::fileExtension(const std::string &filePath) 140 { 141 std::string ext; 142 const size_t last_slash_idx = filePath.find_last_of("."); 143 if (std::string::npos != last_slash_idx) 144 ext = filePath.substr(last_slash_idx + 1); 145 return ext; 146 } 147 148 bool String::startsWith(const std::string &str, const std::string &prefix, bool sensitive) 149 { 150 if (str.length() < prefix.length()) 151 return false; 152 if (sensitive) 153 { 154 return str.compare(0, prefix.length(), prefix) == 0; 155 } 156 return std::equal(prefix.begin(), prefix.end(), str.begin(), charEqualsIgnoreCase); 157 } 158 159 bool String::contains(const std::string &str, const std::string &substring, bool sensitive) 160 { 161 if (str.length() < substring.length()) 162 return false; 163 if (sensitive) 164 { 165 return str.find(substring) != std::string::npos; 166 } 167 auto it = std::search(str.begin(), str.end(), substring.begin(), substring.end(), charEqualsIgnoreCase); 168 return it != str.end(); 169 } 170 171 bool String::endsWith(const std::string &str, const std::string &suffix, bool sensitive) 172 { 173 if (str.length() < suffix.length()) 174 return false; 175 if (sensitive) 176 { 177 return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; 178 } 179 return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin(), charEqualsIgnoreCase); 180 } 181 182 bool String::startsWith(const std::string &str, const std::vector<std::string> &prefixes, bool sensitive) 183 { 184 for (const auto &prefix : prefixes) 185 { 186 if (startsWith(str, prefix, sensitive)) 187 return true; 188 } 189 return false; 190 } 191 192 bool String::contains(const std::string &str, const std::vector<std::string> &substrings, bool sensitive) 193 { 194 for (const auto &substring : substrings) 195 { 196 if (contains(str, substring, sensitive)) 197 return true; 198 } 199 return false; 200 } 201 202 bool String::endsWith(const std::string &str, const std::vector<std::string> &suffixes, bool sensitive) 203 { 204 for (const auto &suffix : suffixes) 205 { 206 if (endsWith(str, suffix, sensitive)) 207 return true; 208 } 209 return false; 210 } 211 212 void String::trim(std::string &str) 213 { 214 str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end()); 215 } 216 217 bool String::isValidHex(const std::string &hex) 218 { 219 if (hex.empty()) 220 return false; 221 222 const char *data = hex.c_str(); 223 size_t len = hex.length(); 224 size_t i = 0; 225 226 while (i < len && ::isspace(static_cast<unsigned char>(data[i]))) 227 { 228 i++; 229 } 230 231 if (i + 2 <= len && data[i] == '0' && (data[i + 1] == 'x' || data[i + 1] == 'X')) 232 { 233 i += 2; 234 } 235 236 size_t digitCount = 0; 237 238 for (; i < len; ++i) 239 { 240 unsigned char c = static_cast<unsigned char>(data[i]); 241 242 if (::isspace(c)) 243 { 244 continue; 245 } 246 247 if (!::isxdigit(c)) 248 { 249 return false; 250 } 251 252 digitCount++; 253 } 254 255 return (digitCount > 0 && (digitCount % 2 == 0)); 256 } 257 258 bool String::validateHex(std::string &hex) 259 { 260 if (hex.empty()) 261 return false; 262 263 size_t len = hex.length(); 264 size_t startOffset = (len >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) ? 2 : 0; 265 266 size_t actualByteCount = 0; 267 bool needsCleaning = (startOffset > 0); 268 269 for (size_t i = startOffset; i < len; ++i) 270 { 271 unsigned char c = static_cast<unsigned char>(hex[i]); 272 273 if (::isspace(c)) 274 { 275 needsCleaning = true; 276 continue; 277 } 278 279 if (!::isxdigit(c)) 280 return false; 281 282 actualByteCount++; 283 } 284 285 if (actualByteCount == 0 || (actualByteCount % 2 != 0)) 286 return false; 287 288 if (needsCleaning) 289 { 290 std::string cleaned; 291 cleaned.reserve(actualByteCount); 292 for (size_t i = startOffset; i < len; ++i) 293 { 294 unsigned char c = static_cast<unsigned char>(hex[i]); 295 if (!::isspace(c)) 296 { 297 cleaned.push_back(c); 298 } 299 } 300 hex = std::move(cleaned); 301 } 302 303 return true; 304 } 305 306 std::string String::fmt(const char *fmt, ...) 307 { 308 if (!fmt) 309 return ""; 310 311 va_list args; 312 va_start(args, fmt); 313 int size = vsnprintf(nullptr, 0, fmt, args); 314 va_end(args); 315 316 if (size <= 0) 317 return ""; 318 319 std::string str; 320 str.resize(static_cast<size_t>(size)); 321 322 va_start(args, fmt); 323 vsnprintf(&str[0], static_cast<size_t>(size) + 1, fmt, args); 324 va_end(args); 325 326 return str; 327 } 328 329 bool Data::fromHex(std::string in, void *data) 330 { 331 if (in.empty() || !data || !String::validateHex(in)) 332 return false; 333 334 size_t length = in.length(); 335 auto *byteData = reinterpret_cast<uint8_t *>(data); 336 337 auto charToNibble = [](char c) -> uint8_t { 338 if (c >= '0' && c <= '9') 339 return c - '0'; 340 if (c >= 'a' && c <= 'f') 341 return c - 'a' + 10; 342 if (c >= 'A' && c <= 'F') 343 return c - 'A' + 10; 344 return 0; 345 }; 346 347 for (size_t strIndex = 0, dataIndex = 0; strIndex < length; strIndex += 2, ++dataIndex) 348 { 349 byteData[dataIndex] = (charToNibble(in[strIndex]) << 4) | charToNibble(in[strIndex + 1]); 350 } 351 352 return true; 353 } 354 355 std::string Data::toHex(const void *data, const size_t dataLength) 356 { 357 if (!data || dataLength == 0) 358 return ""; 359 360 static const char hexTable[] = "0123456789ABCDEF"; 361 const auto *byteData = reinterpret_cast<const uint8_t *>(data); 362 363 std::string hexString; 364 hexString.resize(dataLength * 2); 365 366 for (size_t i = 0; i < dataLength; ++i) 367 { 368 hexString[i * 2] = hexTable[(byteData[i] >> 4) & 0x0F]; 369 hexString[i * 2 + 1] = hexTable[byteData[i] & 0x0F]; 370 } 371 372 return hexString; 373 } 374 375 namespace Zip 376 { 377 #define KT_MIN_EOCD_SIZE 22 378 #define KT_EOCD_SIGNATURE 0x06054b50 379 #define KT_ZIP64_EOCD_SIGNATURE 0x06064b50 380 #define KT_ZIP64_EOCD_LOCATOR 0x07064b50 381 #define KT_CENTRAL_DIR_SIGNATURE 0x02014b50 382 #define KT_LOCAL_HEADER_SIGNATURE 0x04034b50 383 #define KT_ZIP64_EXTRA_ID 0x0001 384 #define KT_MAX_NAME_LEN 65535 385 #define KT_MAX_EOCD_SEARCH (1024 * 64) 386 #define KT_CENTRAL_DIR_SIZE 46 387 #define KT_LOCAL_HEADER_SIZE 30 388 389 inline bool read16(const uint8_t *base, uint64_t size, uint64_t offset, uint16_t &out) 390 { 391 if (offset + 2 > size) 392 return false; 393 std::memcpy(&out, base + offset, 2); 394 return true; 395 } 396 397 inline bool read32(const uint8_t *base, uint64_t size, uint64_t offset, uint32_t &out) 398 { 399 if (offset + 4 > size) 400 return false; 401 std::memcpy(&out, base + offset, 4); 402 return true; 403 } 404 405 inline bool read64(const uint8_t *base, uint64_t size, uint64_t offset, uint64_t &out) 406 { 407 if (offset + 8 > size) 408 return false; 409 std::memcpy(&out, base + offset, 8); 410 return true; 411 } 412 413 bool findCentralDirectory(const uint8_t *data, uint64_t fileSize, uint64_t *cdOffset, uint64_t *totalEntries) 414 { 415 if (fileSize < KT_MIN_EOCD_SIZE) 416 return false; 417 418 uint64_t searchStart = (fileSize > KT_MAX_EOCD_SEARCH) ? fileSize - KT_MAX_EOCD_SEARCH : 0; 419 420 for (int64_t offset = fileSize - 4; offset >= (int64_t)searchStart; --offset) 421 { 422 uint32_t sig; 423 if (!read32(data, fileSize, offset, sig)) 424 continue; 425 426 if (sig == KT_EOCD_SIGNATURE) 427 { 428 uint16_t entries16; 429 uint32_t cdOff32; 430 431 if (!read16(data, fileSize, offset + 10, entries16)) 432 return false; 433 if (!read32(data, fileSize, offset + 16, cdOff32)) 434 return false; 435 436 if (totalEntries) 437 *totalEntries = entries16; 438 439 if (cdOffset) 440 *cdOffset = cdOff32; 441 442 return true; 443 } 444 445 if (sig == KT_ZIP64_EOCD_LOCATOR) 446 { 447 uint64_t zip64EOCDOffset; 448 if (!read64(data, fileSize, offset + 8, zip64EOCDOffset)) 449 return false; 450 451 uint32_t zip64sig; 452 if (!read32(data, fileSize, zip64EOCDOffset, zip64sig)) 453 return false; 454 455 if (zip64sig != KT_ZIP64_EOCD_SIGNATURE) 456 return false; 457 458 uint64_t entries64; 459 uint64_t cdOff64; 460 461 if (!read64(data, fileSize, zip64EOCDOffset + 24, entries64)) 462 return false; 463 464 if (!read64(data, fileSize, zip64EOCDOffset + 48, cdOff64)) 465 return false; 466 467 if (totalEntries) 468 *totalEntries = entries64; 469 470 if (cdOffset) 471 *cdOffset = cdOff64; 472 473 return true; 474 } 475 } 476 477 return false; 478 } 479 480 std::vector<ZipEntryInfo> listEntriesInZip(const std::string &zipPath) 481 { 482 std::vector<ZipEntryInfo> ents; 483 484 int fd = KT_EINTR_RETRY(open(zipPath.c_str(), O_RDONLY)); 485 if (fd < 0) 486 return ents; 487 488 struct stat st{}; 489 if (fstat(fd, &st) < 0) 490 { 491 KT_EINTR_RETRY(close(fd)); 492 return ents; 493 } 494 495 uint64_t fileSize = st.st_size; 496 if (fileSize < KT_MIN_EOCD_SIZE) 497 { 498 KT_EINTR_RETRY(close(fd)); 499 return ents; 500 } 501 502 void *map = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0); 503 if (!map || map == MAP_FAILED) 504 { 505 KT_EINTR_RETRY(close(fd)); 506 return ents; 507 } 508 509 const uint8_t *data = static_cast<uint8_t *>(map); 510 511 uint64_t cdOffset = 0; 512 uint64_t totalEntries = 0; 513 514 if (!findCentralDirectory(data, fileSize, &cdOffset, &totalEntries)) 515 { 516 munmap(map, fileSize); 517 KT_EINTR_RETRY(close(fd)); 518 return ents; 519 } 520 521 if (cdOffset >= fileSize) 522 { 523 munmap(map, fileSize); 524 KT_EINTR_RETRY(close(fd)); 525 return ents; 526 } 527 528 uint64_t offset = cdOffset; 529 uint64_t parsedEntries = 0; 530 531 while (offset + KT_CENTRAL_DIR_SIZE <= fileSize) 532 { 533 uint32_t sig; 534 if (!read32(data, fileSize, offset, sig)) 535 break; 536 537 if (sig != KT_CENTRAL_DIR_SIGNATURE) 538 break; 539 540 ZipEntryInfo info{}; 541 542 read16(data, fileSize, offset + 10, info.compressionMethod); 543 read16(data, fileSize, offset + 12, info.modTime); 544 read16(data, fileSize, offset + 14, info.modDate); 545 read32(data, fileSize, offset + 16, info.crc32); 546 547 uint32_t compSize32, uncompSize32; 548 read32(data, fileSize, offset + 20, compSize32); 549 read32(data, fileSize, offset + 24, uncompSize32); 550 551 info.compressedSize = compSize32; 552 info.uncompressedSize = uncompSize32; 553 554 uint16_t nameLen, extraLen, commentLen; 555 read16(data, fileSize, offset + 28, nameLen); 556 read16(data, fileSize, offset + 30, extraLen); 557 read16(data, fileSize, offset + 32, commentLen); 558 559 uint32_t localHeaderOffset32; 560 read32(data, fileSize, offset + 42, localHeaderOffset32); 561 562 uint64_t entrySize = KT_CENTRAL_DIR_SIZE + nameLen + extraLen + commentLen; 563 if (offset + entrySize > fileSize) 564 break; 565 566 if (nameLen > KT_MAX_NAME_LEN) 567 break; 568 569 info.fileName.assign(reinterpret_cast<const char *>(data + offset + KT_CENTRAL_DIR_SIZE), nameLen); 570 571 uint64_t localHeaderOffset = localHeaderOffset32; 572 573 // ZIP64 handling 574 if (compSize32 == 0xFFFFFFFF || uncompSize32 == 0xFFFFFFFF || localHeaderOffset32 == 0xFFFFFFFF) 575 { 576 uint64_t extraOffset = offset + KT_CENTRAL_DIR_SIZE + nameLen; 577 uint64_t endExtra = extraOffset + extraLen; 578 579 while (extraOffset + 4 <= endExtra) 580 { 581 uint16_t id, size; 582 read16(data, fileSize, extraOffset, id); 583 read16(data, fileSize, extraOffset + 2, size); 584 585 if (extraOffset + 4 + size > endExtra) 586 break; 587 588 if (id == KT_ZIP64_EXTRA_ID) 589 { 590 uint64_t fieldOffset = extraOffset + 4; 591 592 if (uncompSize32 == 0xFFFFFFFF) 593 { 594 read64(data, fileSize, fieldOffset, info.uncompressedSize); 595 fieldOffset += 8; 596 } 597 598 if (compSize32 == 0xFFFFFFFF) 599 { 600 read64(data, fileSize, fieldOffset, info.compressedSize); 601 fieldOffset += 8; 602 } 603 604 if (localHeaderOffset32 == 0xFFFFFFFF) 605 { 606 read64(data, fileSize, fieldOffset, localHeaderOffset); 607 } 608 609 break; 610 } 611 612 extraOffset += 4 + size; 613 } 614 } 615 616 // Validate local header 617 if (localHeaderOffset + KT_LOCAL_HEADER_SIZE > fileSize) 618 break; 619 620 uint16_t localNameLen, localExtraLen; 621 read16(data, fileSize, localHeaderOffset + 26, localNameLen); 622 read16(data, fileSize, localHeaderOffset + 28, localExtraLen); 623 624 info.dataOffset = localHeaderOffset + KT_LOCAL_HEADER_SIZE + localNameLen + localExtraLen; 625 626 if (info.dataOffset > fileSize) 627 break; 628 629 ents.push_back(std::move(info)); 630 631 offset += entrySize; 632 parsedEntries++; 633 634 if (parsedEntries >= totalEntries) 635 break; 636 } 637 638 munmap(map, fileSize); 639 KT_EINTR_RETRY(close(fd)); 640 641 return ents; 642 } 643 644 bool findEntryInfoByDataOffset(const std::string &zipPath, uint64_t dataOffset, ZipEntryInfo *out) 645 { 646 if (out) 647 *out = {}; 648 649 const auto ents = listEntriesInZip(zipPath); 650 for (const auto &it : ents) 651 { 652 if (it.dataOffset == dataOffset) 653 { 654 if (out) 655 *out = it; 656 657 return true; 658 } 659 } 660 661 return false; 662 } 663 664 bool mmapEntryByDataOffset(const std::string &zipPath, uint64_t dataOffset, ZipEntryMMap *out) 665 { 666 if (out) 667 *out = {}; 668 669 ZipEntryInfo ent{}; 670 if (!findEntryInfoByDataOffset(zipPath, dataOffset, &ent)) 671 return false; 672 673 uint64_t compressedSize = ent.compressedSize; 674 675 int fd = KT_EINTR_RETRY(open(zipPath.c_str(), O_RDONLY)); 676 if (fd < 0) 677 return false; 678 679 struct stat st{}; 680 if (fstat(fd, &st) < 0) 681 { 682 KT_EINTR_RETRY(close(fd)); 683 return false; 684 } 685 686 uint64_t fileSize = st.st_size; 687 688 if (dataOffset >= fileSize || dataOffset + compressedSize > fileSize) 689 { 690 KT_EINTR_RETRY(close(fd)); 691 return false; 692 } 693 694 const size_t pageSize = sysconf(_SC_PAGE_SIZE); 695 uint64_t alignedOffset = dataOffset & ~(uint64_t(pageSize - 1)); 696 uint64_t offsetDiff = dataOffset - alignedOffset; 697 uint64_t mapSize = offsetDiff + compressedSize; 698 699 void *map = mmap(nullptr, mapSize, PROT_READ, MAP_PRIVATE, fd, alignedOffset); 700 701 KT_EINTR_RETRY(close(fd)); 702 703 if (!map || map == MAP_FAILED) 704 return false; 705 706 if (out) 707 { 708 out->mappingBase = map; 709 out->mappingSize = mapSize; 710 out->data = static_cast<uint8_t *>(map) + offsetDiff; 711 out->size = compressedSize; 712 } 713 714 return true; 715 } 716 } // namespace Zip 717 718 } // namespace KittyUtils