From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <d.csapak@proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by lists.proxmox.com (Postfix) with ESMTPS id A7DC4687B7
 for <pbs-devel@lists.proxmox.com>; Wed,  9 Mar 2022 15:16:48 +0100 (CET)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
 by firstgate.proxmox.com (Proxmox) with ESMTP id 9F063B01
 for <pbs-devel@lists.proxmox.com>; Wed,  9 Mar 2022 15:16:48 +0100 (CET)
Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com
 [94.136.29.106])
 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
 key-exchange X25519 server-signature RSA-PSS (2048 bits))
 (No client certificate requested)
 by firstgate.proxmox.com (Proxmox) with ESMTPS id F0176AF8
 for <pbs-devel@lists.proxmox.com>; Wed,  9 Mar 2022 15:16:47 +0100 (CET)
Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1])
 by proxmox-new.maurer-it.com (Proxmox) with ESMTP id C034642079
 for <pbs-devel@lists.proxmox.com>; Wed,  9 Mar 2022 15:16:47 +0100 (CET)
From: Dominik Csapak <d.csapak@proxmox.com>
To: pbs-devel@lists.proxmox.com
Date: Wed,  9 Mar 2022 15:16:46 +0100
Message-Id: <20220309141646.1027526-1-d.csapak@proxmox.com>
X-Mailer: git-send-email 2.30.2
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-SPAM-LEVEL: Spam detection results:  0
 AWL 0.153 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
 T_SCC_BODY_TEXT_LINE    -0.01 -
Subject: [pbs-devel] [RFC PATCH proxmox-backup] ui: implement quoted strings
 in parsePropertyString
X-BeenThere: pbs-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox Backup Server development discussion
 <pbs-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pbs-devel/>
List-Post: <mailto:pbs-devel@lists.proxmox.com>
List-Help: <mailto:pbs-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel>, 
 <mailto:pbs-devel-request@lists.proxmox.com?subject=subscribe>
X-List-Received-Date: Wed, 09 Mar 2022 14:16:48 -0000

like we do in our rust propertystring parser.
code is heavily inspired by the rust code.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
if we want to go the js route and not use wasm + rust code for this

i sent it as rfc for pbs (since there we have that property string
implementation), but we actually want this to live in wt, and use it in
pve and pbs both when pve gains support for these.

@thomas, the code is my attempt to write the parser as close to the
rust code as it was sensible, but i'm sure there are some js specific
improvements to be done here. i'll look over it again tomorrow
with fresh eyes and mind, but if you see some things, don't hold back ;)

 www/Utils.js | 100 +++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 82 insertions(+), 18 deletions(-)

diff --git a/www/Utils.js b/www/Utils.js
index 36a94211..87809364 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -62,32 +62,96 @@ Ext.define('PBS.Utils', {
 	return path.indexOf(PBS.Utils.dataStorePrefix) === 0;
     },
 
+    parseQuotedString: function(value) {
+	let data = "";
+	let was_backslash = false;
+	if (value[0] !== '"') {
+	    throw "not a quoted string";
+	}
+	value = value.slice(1);
+	for (let i = 0; ; i++) {
+	    if (i === value.length) {
+		throw "invalid quoted string";
+	    }
+	    if (was_backslash) {
+		was_backslash = false;
+		switch (value[i]) {
+		    case '"': data += '"'; break;
+		    case '\\': data += '\\'; break;
+		    case 'n': data += '\n'; break;
+		    default:
+			throw "unsupported escape sequence";
+		}
+	    } else {
+		switch (value[i]) {
+		    case '"': return [data, i+1];
+		    case '\\': was_backslash = true; break;
+		    default:
+			data += value[i];
+		}
+	    }
+	}
+    },
+
     parsePropertyString: function(value, defaultKey) {
-	var res = {},
-	    error;
+	let res = {};
 
 	if (typeof value !== 'string' || value === '') {
 	    return res;
 	}
 
-	Ext.Array.each(value.split(','), function(p) {
-	    var kv = p.split('=', 2);
-	    if (Ext.isDefined(kv[1])) {
-		res[kv[0]] = kv[1];
-	    } else if (Ext.isDefined(defaultKey)) {
-		if (Ext.isDefined(res[defaultKey])) {
-		    error = 'defaultKey may be only defined once in propertyString';
-		    return false; // break
+	try {
+	    while (value.length > 0) {
+		let key, current;
+		if (value[0] !== '"') {
+		    let idx = value.search(/[,=]/);
+		    if (idx !== -1 && value[idx] === '=') {
+			key = value.slice(0, idx);
+			value = value.slice(idx + 1);
+			if (Ext.isDefined(res[key])) {
+			    throw `duplicate key ${key} found`;
+			}
+		    }
+
+		    if (value[0] !== '"') {
+			let next_idx = value.search(/,/);
+			if (next_idx === -1) {
+			    current = value;
+			    value = "";
+			} else {
+			    current = value.slice(0, next_idx);
+			    value = value.slice(next_idx + 1);
+			}
+		    }
 		}
-		res[defaultKey] = kv[0];
-	    } else {
-		error = 'invalid propertyString, not a key=value pair and no defaultKey defined';
-		return false; // break
-	    }
-	    return true;
-	});
 
-	if (error !== undefined) {
+		if (key === undefined) {
+		    if (Ext.isDefined(defaultKey)) {
+			if (Ext.isDefined(res[defaultKey])) {
+			    throw 'defaultKey may be only defined once in propertyString';
+			}
+			key = defaultKey;
+		    } else {
+			throw "value without key and no defaultKey";
+		    }
+		}
+		if (current === undefined) {
+		    let [val, idx] = PVE.Parser.parseQuotedString(value);
+		    current = val;
+		    value = value.slice(idx + 1);
+		}
+
+		res[key] = current;
+
+		if (value.length > 0) {
+		    if (value[0] === ',') {
+			value = value.slice(1);
+		    } else {
+			throw "garbage after value";
+		    }
+		}
+	    }
+	} catch (error) {
 	    console.error(error);
 	    return null;
 	}
-- 
2.30.2