1
0

BxDolServiceCallCodec.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * Encode/decode stored service-call blobs.
  7. * New writes are JSON. Old PHP-serialized arrays still decode.
  8. * Objects are never instantiated (allowed_classes => false + plain-data check).
  9. *
  10. * Writers of these blobs (inventory, 18 Aug 2026):
  11. * - BxDolService::getSerializedService — used by cron transient jobs, background jobs,
  12. * live updates (comments/timeline), search-extended field values, ads form values_src.
  13. * - DB tables: sys_cron_jobs.service_call, background jobs.service_call,
  14. * sys_alerts_handlers.service_call, studio widget cnt_notices, live-update service_call.
  15. * - Studio/admin paths write alerts + widgets. A lower-privilege INSERT into those
  16. * columns is still "call any module method". This codec blocks object injection,
  17. * not an authorized service call.
  18. */
  19. class BxDolServiceCallCodec
  20. {
  21. /**
  22. * JSON-encode a service-call array for new writes.
  23. */
  24. public static function encode($mixedModule, $sMethod, $aParams = array(), $sClass = '')
  25. {
  26. $aService = array(
  27. 'module' => $mixedModule,
  28. 'method' => $sMethod,
  29. );
  30. if (!empty($aParams))
  31. $aService['params'] = $aParams;
  32. if (!empty($sClass))
  33. $aService['class'] = $sClass;
  34. return json_encode($aService, JSON_UNESCAPED_UNICODE);
  35. }
  36. /**
  37. * True for a PHP-serialized array service call or a JSON object with module+method.
  38. */
  39. public static function isEncoded($s)
  40. {
  41. return false !== self::decode($s);
  42. }
  43. /**
  44. * Decode a stored blob. Returns an array or false.
  45. * Never instantiates PHP objects from the payload.
  46. */
  47. public static function decode($s)
  48. {
  49. if (!is_string($s) || $s === '')
  50. return false;
  51. $s = ltrim($s);
  52. if ($s === '')
  53. return false;
  54. if ($s[0] === '{') {
  55. $a = json_decode($s, true);
  56. return self::isServiceArray($a) ? $a : false;
  57. }
  58. // Historic PHP serialize of an array: a:N:{...}
  59. if (!preg_match('/^a:\d+:\{/', $s))
  60. return false;
  61. $a = @unserialize($s, array('allowed_classes' => false));
  62. return self::isServiceArray($a) ? $a : false;
  63. }
  64. /**
  65. * Same decode rules as BxBaseMenuSetAclLevel::getCode() AJAX profile_id.
  66. * Numeric string stays a string/number; otherwise json_decode. Never unserialize.
  67. */
  68. public static function decodeAclProfileId($mixedProfileId)
  69. {
  70. if (!is_string($mixedProfileId) && !is_numeric($mixedProfileId))
  71. return $mixedProfileId;
  72. $mixedProfileId = urldecode((string)$mixedProfileId);
  73. if (!is_numeric($mixedProfileId))
  74. $mixedProfileId = json_decode($mixedProfileId, true);
  75. return $mixedProfileId;
  76. }
  77. protected static function isServiceArray($a)
  78. {
  79. if (!is_array($a) || !isset($a['module'], $a['method']))
  80. return false;
  81. if (!is_scalar($a['module']) || !is_scalar($a['method']))
  82. return false;
  83. return self::isPlainData($a);
  84. }
  85. protected static function isPlainData($v)
  86. {
  87. if (is_scalar($v) || $v === null)
  88. return true;
  89. if (!is_array($v))
  90. return false;
  91. foreach ($v as $k => $item) {
  92. if (!is_int($k) && !is_string($k))
  93. return false;
  94. if (!self::isPlainData($item))
  95. return false;
  96. }
  97. return true;
  98. }
  99. }