PHPDeserialize.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /**
  2. * @author Jarmo van Lenthe [github.com/jarmovanlenthe]
  3. * @copyright Jarmo van Lenthe
  4. * @license Apache-2.0
  5. */
  6. import Operation from "../Operation.mjs";
  7. import OperationError from "../errors/OperationError.mjs";
  8. /**
  9. * PHP Deserialize operation
  10. */
  11. class PHPDeserialize extends Operation {
  12. /**
  13. * PHPDeserialize constructor
  14. */
  15. constructor() {
  16. super();
  17. this.name = "PHP Deserialize";
  18. this.module = "Default";
  19. this.description = "Deserializes PHP serialized data, outputting keyed arrays as JSON.<br><br>This function does not support <code>object</code> tags.<br><br>Example:<br><code>a:2:{s:1:&quot;a&quot;;i:10;i:0;a:1:{s:2:&quot;ab&quot;;b:1;}}</code><br>becomes<br><code>{&quot;a&quot;: 10,0: {&quot;ab&quot;: true}}</code><br><br><u>Output valid JSON:</u> JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.";
  20. this.infoURL = "http://www.phpinternalsbook.com/classes_objects/serialization.html";
  21. this.inputType = "string";
  22. this.outputType = "string";
  23. this.args = [
  24. {
  25. "name": "Output valid JSON",
  26. "type": "boolean",
  27. "value": true
  28. }
  29. ];
  30. }
  31. /**
  32. * @param {string} input
  33. * @param {Object[]} args
  34. * @returns {string}
  35. */
  36. run(input, args) {
  37. /**
  38. * Recursive method for deserializing.
  39. * @returns {*}
  40. */
  41. function handleInput() {
  42. /**
  43. * Read `length` characters from the input, shifting them out the input.
  44. * @param length
  45. * @returns {string}
  46. */
  47. function read(length) {
  48. let result = "";
  49. for (let idx = 0; idx < length; idx++) {
  50. const char = inputPart.shift();
  51. if (char === undefined) {
  52. throw new OperationError("End of input reached before end of script");
  53. }
  54. result += char;
  55. }
  56. return result;
  57. }
  58. /**
  59. * Read characters from the input until `until` is found.
  60. * @param until
  61. * @returns {string}
  62. */
  63. function readUntil(until) {
  64. let result = "";
  65. for (;;) {
  66. const char = read(1);
  67. if (char === until) {
  68. break;
  69. } else {
  70. result += char;
  71. }
  72. }
  73. return result;
  74. }
  75. /**
  76. * Read characters from the input that must be equal to `expect`
  77. * @param expect
  78. * @returns {string}
  79. */
  80. function expect(expect) {
  81. const result = read(expect.length);
  82. if (result !== expect) {
  83. throw new OperationError("Unexpected input found");
  84. }
  85. return result;
  86. }
  87. /**
  88. * Helper function to handle deserialized arrays.
  89. * @returns {Array}
  90. */
  91. function handleArray() {
  92. const items = parseInt(readUntil(":"), 10) * 2;
  93. expect("{");
  94. const result = [];
  95. let isKey = true;
  96. let lastItem = null;
  97. for (let idx = 0; idx < items; idx++) {
  98. const item = handleInput();
  99. if (isKey) {
  100. lastItem = item;
  101. isKey = false;
  102. } else {
  103. const numberCheck = lastItem.match(/[0-9]+/);
  104. if (args[0] && numberCheck && numberCheck[0].length === lastItem.length) {
  105. result.push('"' + lastItem + '": ' + item);
  106. } else {
  107. result.push(lastItem + ": " + item);
  108. }
  109. isKey = true;
  110. }
  111. }
  112. expect("}");
  113. return result;
  114. }
  115. const kind = read(1).toLowerCase();
  116. switch (kind) {
  117. case "n":
  118. expect(";");
  119. return "null";
  120. case "i":
  121. case "d":
  122. case "b": {
  123. expect(":");
  124. const data = readUntil(";");
  125. if (kind === "b") {
  126. return (parseInt(data, 10) !== 0);
  127. }
  128. return data;
  129. }
  130. case "a":
  131. expect(":");
  132. return "{" + handleArray() + "}";
  133. case "s": {
  134. expect(":");
  135. const length = readUntil(":");
  136. expect("\"");
  137. const value = read(length);
  138. expect('";');
  139. if (args[0]) {
  140. return '"' + value.replace(/"/g, '\\"') + '"'; // lgtm [js/incomplete-sanitization]
  141. } else {
  142. return '"' + value + '"';
  143. }
  144. }
  145. default:
  146. throw new OperationError("Unknown type: " + kind);
  147. }
  148. }
  149. const inputPart = input.split("");
  150. return handleInput();
  151. }
  152. }
  153. export default PHPDeserialize;