socketio/socket.io-postgres-emitter

Single quote in message results in error "scanner_yyerror"

cyrusmg opened this issue · 2 comments

I have been trying to get postgres-emitter working for our project, but I get following error thrown to socket.io clients:

code: "42601"
file: "scan.l"
length: 94
line: "1176"
name: "error"
position: "104"
routine: "scanner_yyerror"
severity: "ERROR"

The issue seems to be whenever the data sent to clients includes ' single quote. I unfortunately can't change the protocol, so this prevents me from using this library.

Minimal script reproduction below with your connectionString (in typescript project)

import { Pool } from 'pg';
import { Emitter } from '@socket.io/postgres-emitter';

const run = async () => {
  const pool = new Pool({ connectionString: 'postgresql://<your connection string>' });
  await pool.connect();

  const io = new Emitter(pool);

  io.emit('hello world with quote', "'"); // this one throws error
  io.emit('hello world'); // this one gets to the clients correctly
};

run();

It's going to be this line probably, I will have closer look tomorrow:

`NOTIFY "${this.emitter.channel}", '${payload}'`

This is a fix using the patch-package node module - the escapeLiteral and escapeIdentifier functions are verbatim from pg package repo: https://github.com/brianc/node-postgres/blob/684cd09bcecbf5ad5f451fdf608a3e9a9444524e/packages/pg/lib/client.js#L444

diff --git a/node_modules/@socket.io/postgres-emitter/dist/index.js b/node_modules/@socket.io/postgres-emitter/dist/index.js
index 214266f..33bb2af 100644
--- a/node_modules/@socket.io/postgres-emitter/dist/index.js
+++ b/node_modules/@socket.io/postgres-emitter/dist/index.js
@@ -244,6 +244,36 @@ class BroadcastOperator {
         const flags = Object.assign({}, this.flags, { volatile: true });
         return new BroadcastOperator(this.emitter, this.rooms, this.exceptRooms, flags);
     }
+    // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
+    escapeIdentifier(str) {
+      return '"' + str.replace(/"/g, '""') + '"'
+    }
+
+    // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
+    escapeLiteral(str) {
+      var hasBackslash = false
+      var escaped = "'"
+
+      for (var i = 0; i < str.length; i++) {
+        var c = str[i]
+        if (c === "'") {
+          escaped += c + c
+        } else if (c === '\\') {
+          escaped += c + c
+          hasBackslash = true
+        } else {
+          escaped += c
+        }
+      }
+
+      escaped += "'"
+
+      if (hasBackslash === true) {
+        escaped = ' E' + escaped
+      }
+
+      return escaped
+    }
     async publish(document) {
         document.uid = EMITTER_UID;
         try {
@@ -260,7 +290,7 @@ class BroadcastOperator {
                 return await this.publishWithAttachment(document);
             }
             debug("sending event of type %s to channel %s", document.type, this.emitter.channel);
-            await this.emitter.pool.query(`NOTIFY "${this.emitter.channel}", '${payload}'`);
+            await this.emitter.pool.query(`NOTIFY ${this.escapeIdentifier(this.emitter.channel)}, ${this.escapeLiteral(payload)}`);
         }
         catch (err) {
             // @ts-ignore