[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/PELock/StringEncrypt-JavaScript/main/src/StringEncrypt.js [Back]  [Original]

import { readFile } from 'node:fs/promises';
import querystring from 'node:querystring';
import { inflateSync } from 'node:zlib';
import { Command } from './Command.js';
import { ErrorCode } from './ErrorCode.js';
import { Language } from './Language.js';
import { NewLine } from './NewLine.js';

/**
 * Stateful HTTP client: pass the activation key to the constructor, configure other options via
 * setters, then {@link StringEncrypt#send}.
 *
 * POST field `code` (activation key) is the value given at construction (empty string = demo).
 *
 * For {@link Command.Encrypt} with {@link StringEncrypt#setCompression}`(true)`, the API returns
 * `source` as base64-encoded gzip of the decryptor text. {@link StringEncrypt#send} decodes that
 * automatically so `source` is always plain text on success (unless
 * {@link StringEncrypt#setDecompressEncryptSource}`(false)`).
 */
export class StringEncrypt {
  static DEFAULT_API_URL = 'https://www.stringencrypt.com/api.php';

  /** @type {boolean} */
  #preferCurl = false;

  /** When true (default), decompress `source` after successful encrypt+compression responses. */
  #decompressEncryptSource = true;

  /** @type {string | null} */
  #command = null;

  /** Activation code sent as `code` (empty = demo). */
  #apiKey = '';

  //  encrypt 

  /** @type {string} */
  #label = 'Label';

  /** @type {string | null} */
  #inputString = null;

  /** @type {Buffer | null} */
  #inputBytes = null;

  /** @type {boolean} */
  #compression = false;

  /** @type {string} */
  #language = Language.Php;

  /** `false`, or highlight mode string such as `geshi` / `js` when supported server-side. */
  #highlight = false;

  /** @type {number} */
  #cmdMin = 1;

  /** @type {number} */
  #cmdMax = 3;

  /** @type {boolean} */
  #local = false;

  /** @type {boolean} */
  #unicode = true;

  /** @type {string} */
  #langLocale = 'en_US.utf8';

  /** @type {string} */
  #ansiEncoding = 'WINDOWS-1250';

  /** @type {string} */
  #newLines = NewLine.Lf;

  /** @type {string | null} */
  #template = null;

  /** @type {boolean} */
  #returnTemplate = false;

  /** @type {boolean} */
  #includeTags = false;

  /** @type {boolean} */
  #includeExample = false;

  /** When true, server emits a trace comment block above the encrypted array (hashes, VM JSON). */
  #includeDebugComments = false;

  /**
   * @param {string} [apiKey]
   * @param {boolean} [preferCurl] Ignored; the JS client always uses `fetch` (kept for parity with the PHP SDK).
   */
  constructor(apiKey = '', preferCurl = false) {
    this.#apiKey = apiKey;
    this.#preferCurl = preferCurl;
  }

  /**
   * Activation status and limits for the activation key passed to the constructor.
   *
   * @returns {Promise} Parsed JSON on success, or false on transport/parse failure.
   */
  async isDemo() {
    const previousCommand = this.#command;
    this.setCommand(Command.IsDemo);
    const result = await this.send();
    this.#command = previousCommand;
    return result;
  }

  /**
   * Encrypt raw file contents (binary). Uses other encrypt options already set on this client.
   *
   * @param {string} filePath
   * @param {string} label
   * @returns {Promise}
   */
  async encryptFileContents(filePath, label) {
    let raw;
    try {
      raw = await readFile(filePath);
    } catch {
      return false;
    }
    if (!raw.length) {
      return false;
    }

    const saved = {
      command: this.#command,
      inputString: this.#inputString,
      inputBytes: this.#inputBytes,
      label: this.#label,
    };

    this.setCommand(Command.Encrypt).setBytes(raw).setLabel(label);
    const result = await this.send();

    this.#command = saved.command;
    this.#inputString = saved.inputString;
    this.#inputBytes = saved.inputBytes;
    this.#label = saved.label;

    return result;
  }

  /**
   * Encrypt a UTF-8 string. Uses other encrypt options already set on this client (language,
   * compression, cmd range, etc.).
   *
   * @param {string} string
   * @param {string} label
   * @returns {Promise}
   */
  async encryptString(string, label) {
    const saved = {
      command: this.#command,
      inputString: this.#inputString,
      inputBytes: this.#inputBytes,
      label: this.#label,
    };

    this.setCommand(Command.Encrypt).setString(string).setLabel(label);
    const result = await this.send();

    this.#command = saved.command;
    this.#inputString = saved.inputString;
    this.#inputBytes = saved.inputBytes;
    this.#label = saved.label;

    return result;
  }

  getPreferCurl() {
    return this.#preferCurl;
  }

  /** @param {boolean} preferCurl */
  setPreferCurl(preferCurl) {
    this.#preferCurl = preferCurl;
    return this;
  }

  getDecompressEncryptSource() {
    return this.#decompressEncryptSource;
  }

  /**
   * Disable automatic gzip+base64 decoding of `source` for encrypt+compression responses
   * if you need the raw payload from the API.
   *
   * @param {boolean} decompressEncryptSource
   */
  setDecompressEncryptSource(decompressEncryptSource) {
    this.#decompressEncryptSource = decompressEncryptSource;
    return this;
  }

  getCommand() {
    return this.#command;
  }

  /** @param {string} command One of {@link Command} values. */
  setCommand(command) {
    this.#command = command;
    return this;
  }

  getLabel() {
    return this.#label;
  }

  /** @param {string} label */
  setLabel(label) {
    this.#label = label;
    return this;
  }

  /** UTF-8 text input; clears raw bytes input. */
  setString(string) {
    this.#inputString = string;
    this.#inputBytes = null;
    return this;
  }

  /** Raw binary input; clears string input. */
  setBytes(bytes) {
    this.#inputBytes = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
    this.#inputString = null;
    return this;
  }

  getCompression() {
    return this.#compression;
  }

  /** @param {boolean} compression */
  setCompression(compression) {
    this.#compression = compression;
    return this;
  }

  getLanguage() {
    return this.#language;
  }

  /** @param {string} language One of {@link Language} values. */
  setLanguage(language) {
    this.#language = language;
    return this;
  }

  getHighlight() {
    return this.#highlight;
  }

  /** @param {string | boolean} highlight */
  setHighlight(highlight) {
    this.#highlight = highlight;
    return this;
  }

  getCmdMin() {
    return this.#cmdMin;
  }

  /** @param {number} cmdMin */
  setCmdMin(cmdMin) {
    this.#cmdMin = cmdMin;
    return this;
  }

  getCmdMax() {
    return this.#cmdMax;
  }

  /** @param {number} cmdMax */
  setCmdMax(cmdMax) {
    this.#cmdMax = cmdMax;
    return this;
  }

  getLocal() {
    return this.#local;
  }

  /** @param {boolean} local */
  setLocal(local) {
    this.#local = local;
    return this;
  }

  getUnicode() {
    return this.#unicode;
  }

  /** @param {boolean} unicode */
  setUnicode(unicode) {
    this.#unicode = unicode;
    return this;
  }

  getLangLocale() {
    return this.#langLocale;
  }

  /** @param {string} langLocale */
  setLangLocale(langLocale) {
    this.#langLocale = langLocale;
    return this;
  }

  getAnsiEncoding() {
    return this.#ansiEncoding;
  }

  /** @param {string} ansiEncoding */
  setAnsiEncoding(ansiEncoding) {
    this.#ansiEncoding = ansiEncoding;
    return this;
  }

  getNewLines() {
    return this.#newLines;
  }

  /** @param {string} newLines One of {@link NewLine} values. */
  setNewLines(newLines) {
    this.#newLines = newLines;
    return this;
  }

  getTemplate() {
    return this.#template;
  }

  /** @param {string | null} template */
  setTemplate(template) {
    this.#template = template;
    return this;
  }

  getReturnTemplate() {
    return this.#returnTemplate;
  }

  /** @param {boolean} returnTemplate */
  setReturnTemplate(returnTemplate) {
    this.#returnTemplate = returnTemplate;
    return this;
  }

  getIncludeTags() {
    return this.#includeTags;
  }

  /** @param {boolean} includeTags */
  setIncludeTags(includeTags) {
    this.#includeTags = includeTags;
    return this;
  }

  getIncludeExample() {
    return this.#includeExample;
  }

  /** @param {boolean} includeExample */
  setIncludeExample(includeExample) {
    this.#includeExample = includeExample;
    return this;
  }

  getIncludeDebugComments() {
    return this.#includeDebugComments;
  }

  /** @param {boolean} includeDebugComments */
  setIncludeDebugComments(includeDebugComments) {
    this.#includeDebugComments = includeDebugComments;
    return this;
  }

  /**
   * Reset request fields to defaults (reuse the same client for another call).
   */
  reset() {
    this.#command = null;
    this.#label = '$label';
    this.#inputString = null;
    this.#inputBytes = null;
    this.#compression = false;
    this.#language = Language.Php;
    this.#highlight = false;
    this.#cmdMin = 1;
    this.#cmdMax = 3;
    this.#local = false;
    this.#unicode = true;
    this.#langLocale = 'en_US.utf8';
    this.#ansiEncoding = 'WINDOWS-1250';
    this.#newLines = NewLine.Lf;
    this.#template = null;
    this.#returnTemplate = false;
    this.#includeTags = false;
    this.#includeExample = false;
    this.#includeDebugComments = false;
    return this;
  }

  /**
   * Build the POST body the client would send (for debugging and tests).
   *
   * @returns {Record}
   */
  toRequestArray() {
    if (this.#command === null) {
      throw new Error('Command must be set (use setCommand()).');
    }
    if (this.#command === Command.Info) {
      return this.#buildInfoParams();
    }
    if (this.#command === Command.IsDemo) {
      return this.#buildIsDemoParams();
    }
    if (this.#command === Command.Encrypt) {
      return this.#buildEncryptParams();
    }
    throw new Error('Unknown command.');
  }

  /**
   * Send the request and return decoded JSON, or false on transport / JSON failure.
   *
   * @returns {Promise}
   */
  async send() {
    const params = this.#formEncodeParams(this.toRequestArray());

    const raw = await this.#post(StringEncrypt.DEFAULT_API_URL, params);

    if (raw === false || raw === '') {
      return false;
    }

    let decoded;
    try {
      decoded = JSON.parse(raw);
    } catch {
      return false;
    }
    if (decoded === null || typeof decoded !== 'object' || Array.isArray(decoded)) {
      return false;
    }

    return this.#applyDecryptorSourceDecompression(/** @type {Record} */ (decoded));
  }

  /**
   * Replace `source` with plain text when the request used compression (API returns
   * base64(gzip(decryptor source))).
   *
   * @param {Record} response
   */
  #applyDecryptorSourceDecompression(response) {
    if (this.#command !== Command.Encrypt || !this.#compression || !this.#decompressEncryptSource) {
      return response;
    }

    if (response.error !== ErrorCode.SUCCESS) {
      return response;
    }

    const src = response.source;
    if (typeof src !== 'string') {
      return response;
    }

    let binary;
    try {
      binary = Buffer.from(src, 'base64');
    } catch {
      return response;
    }

    let plain;
    try {
      plain = inflateSync(binary);
    } catch {
      return response;
    }

    return { ...response, source: plain.toString('utf8') };
  }

  /** @returns {Record} */
  #buildInfoParams() {
    return {
      command: Command.Info,
      code: this.#apiKey,
    };
  }

  /** @returns {Record} */
  #buildIsDemoParams() {
    return {
      command: Command.IsDemo,
      code: this.#apiKey,
    };
  }

  /** @returns {Record} */
  #buildEncryptParams() {
    /** @type {Record} */
    const p = {
      command: Command.Encrypt,
      code: this.#apiKey,
      label: this.#label,
      compression: this.#compression,
      lang: this.#language,
      cmd_min: this.#cmdMin,
      cmd_max: this.#cmdMax,
      local: this.#local,
      unicode: this.#unicode,
      lang_locale: this.#langLocale,
      ansi_encoding: this.#ansiEncoding,
      new_lines: this.#newLines,
      return_template: this.#returnTemplate,
      include_tags: this.#includeTags,
      include_example: this.#includeExample,
      include_debug_comments: this.#includeDebugComments,
    };

    if (this.#inputString !== null) {
      p.string = this.#inputString;
    } else if (this.#inputBytes !== null) {
      p.bytes = this.#inputBytes;
    }

    if (this.#highlight !== false) {
      p.highlight = this.#highlight;
    }

    if (this.#template !== null) {
      p.template = this.#template;
    }

    return p;
  }

  /**
   * Match PHP `http_build_query`: booleans become "0"/"1"; `Buffer` values (e.g. `bytes`) become
   * a byte-per-character string so `querystring.stringify` percent-encodes like PHP.
   *
   * @param {Record} data
   */
  #formEncodeParams(data) {
    /** @type {Record} */
    const flat = {};
    for (const [key, value] of Object.entries(data)) {
      if (typeof value === 'boolean') {
        flat[key] = value ? 1 : 0;
      } else if (Buffer.isBuffer(value)) {
        flat[key] = value.toString('latin1');
      } else {
        flat[key] = value;
      }
    }
    return querystring.stringify(flat);
  }

  /**
   * @param {string} url
   * @param {string} body
   * @returns {Promise}
   */
  async #post(url, body) {
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'User-Agent': 'pelock/stringencrypt (+https://www.stringencrypt.com)',
        },
        body,
      });
      const text = await res.text();
      return text;
    } catch {
      return false;
    }
  }
}

Web Proxy Viewer  |  New URL  |  Original Page