From ba78194636a9a498f6979cc21cd39399f23d658a Mon Sep 17 00:00:00 2001
From: kwolekr <kwolekr@minetest.net>
Date: Sun, 24 Feb 2013 16:00:35 -0500
Subject: [PATCH] Allow any character in formspec strings with escape char

---
 builtin/misc.lua        |   7 +++
 doc/lua_api.txt         |   3 +
 src/guiFormSpecMenu.cpp | 122 ++++++++++++++++++++++------------------
 src/strfnd.h            |  37 ++++++++++++
 src/util/string.h       |  16 ++++++
 5 files changed, 129 insertions(+), 56 deletions(-)

diff --git a/builtin/misc.lua b/builtin/misc.lua
index e018aff85..496435b33 100644
--- a/builtin/misc.lua
+++ b/builtin/misc.lua
@@ -99,3 +99,10 @@ function minetest.setting_get_pos(name)
 	return minetest.string_to_pos(value)
 end
 
+function minetest.formspec_escape(str)
+	str = string.gsub(str, "\\", "\\\\")
+	str = string.gsub(str, "%[", "\\[")
+	str = string.gsub(str, "%]", "\\]")
+	return str
+end
+
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 005d7c010..8246377e2 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -778,6 +778,9 @@ string:trim()
 minetest.pos_to_string({x=X,y=Y,z=Z}) -> "(X,Y,Z)"
 ^ Convert position to a printable string
 minetest.string_to_pos(string) -> position
+^ Same but in reverse
+minetest.formspec_escape(string) -> string
+^ escapes characters like [, ], and \ that can not be used in formspecs
 
 minetest namespace reference
 -----------------------------
diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp
index 120d6629a..1754422d0 100644
--- a/src/guiFormSpecMenu.cpp
+++ b/src/guiFormSpecMenu.cpp
@@ -207,18 +207,18 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 	Strfnd f(m_formspec_string);
 	while(f.atend() == false)
 	{
-		std::string type = trim(f.next("["));
+		std::string type = trim(f.next_esc("["));
 		if(type == "invsize" || type == "size")
 		{
 			v2f invsize;
-			invsize.X = stof(f.next(","));
+			invsize.X = stof(f.next_esc(","));
 			if(type == "size")
 			{
-				invsize.Y = stof(f.next("]"));
+				invsize.Y = stof(f.next_esc("]"));
 			}
 			else{
-				invsize.Y = stof(f.next(";"));
-				f.next("]");
+				invsize.Y = stof(f.next_esc(";"));
+				f.next_esc("]");
 			}
 			infostream<<"Form size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
 
@@ -242,24 +242,24 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		}
 		else if(type == "list")
 		{
-			std::string name = f.next(";");
+			std::string name = f.next_esc(";");
 			InventoryLocation loc;
 			if(name == "context" || name == "current_name")
 				loc = m_current_inventory_location;
 			else
 				loc.deSerialize(name);
-			std::string listname = f.next(";");
+			std::string listname = f.next_esc(";");
 			v2s32 pos = basepos;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = stoi(f.next(","));
-			geom.Y = stoi(f.next(";"));
+			geom.X = stoi(f.next_esc(","));
+			geom.Y = stoi(f.next_esc(";"));
 			infostream<<"list inv="<<name<<", listname="<<listname
 					<<", pos=("<<pos.X<<","<<pos.Y<<")"
 					<<", geom=("<<geom.X<<","<<geom.Y<<")"
 					<<std::endl;
-			std::string start_i_s = f.next("]");
+			std::string start_i_s = f.next_esc("]");
 			s32 start_i = 0;
 			if(start_i_s != "")
 				start_i = stoi(start_i_s);
@@ -270,12 +270,12 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "image")
 		{
 			v2s32 pos = basepos;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = stof(f.next(",")) * (float)imgsize.X;
-			geom.Y = stof(f.next(";")) * (float)imgsize.Y;
-			std::string name = f.next("]");
+			geom.X = stof(f.next_esc(",")) * (float)imgsize.X;
+			geom.Y = stof(f.next_esc(";")) * (float)imgsize.Y;
+			std::string name = f.next_esc("]");
 			infostream<<"image name="<<name
 					<<", pos=("<<pos.X<<","<<pos.Y<<")"
 					<<", geom=("<<geom.X<<","<<geom.Y<<")"
@@ -287,12 +287,12 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "item_image")
 		{
 			v2s32 pos = basepos;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = stof(f.next(",")) * (float)imgsize.X;
-			geom.Y = stof(f.next(";")) * (float)imgsize.Y;
-			std::string name = f.next("]");
+			geom.X = stof(f.next_esc(",")) * (float)imgsize.X;
+			geom.Y = stof(f.next_esc(";")) * (float)imgsize.Y;
+			std::string name = f.next_esc("]");
 			infostream<<"item name="<<name
 					<<", pos=("<<pos.X<<","<<pos.Y<<")"
 					<<", geom=("<<geom.X<<","<<geom.Y<<")"
@@ -304,12 +304,12 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "background")
 		{
 			v2s32 pos = basepos;
-			pos.X += stof(f.next(",")) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X - ((float)spacing.X-(float)imgsize.X)/2;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y - ((float)spacing.Y-(float)imgsize.Y)/2;
 			v2s32 geom;
-			geom.X = stof(f.next(",")) * (float)spacing.X;
-			geom.Y = stof(f.next(";")) * (float)spacing.Y;
-			std::string name = f.next("]");
+			geom.X = stof(f.next_esc(",")) * (float)spacing.X;
+			geom.Y = stof(f.next_esc(";")) * (float)spacing.Y;
+			std::string name = f.next_esc("]");
 			infostream<<"image name="<<name
 					<<", pos=("<<pos.X<<","<<pos.Y<<")"
 					<<", geom=("<<geom.X<<","<<geom.Y<<")"
@@ -320,8 +320,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		}
 		else if(type == "field" || type == "textarea")
 		{
-			std::string fname = f.next(";");
-			std::string flabel = f.next(";");
+			std::string fname = f.next_esc(";");
+			std::string flabel = f.next_esc(";");
 
 			if(fname.find(",") == std::string::npos && flabel.find(",") == std::string::npos)
 			{
@@ -372,14 +372,14 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 
 
 				
-				fname = f.next(";");
-				flabel = f.next(";");
+				fname = f.next_esc(";");
+				flabel = f.next_esc(";");
 				if(bp_set != 2)
 					errorstream<<"WARNING: invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
 				
 			}
 
-			std::string odefault = f.next("]");
+			std::string odefault = f.next_esc("]");
 			std::string fdefault;
 
 			// fdefault may contain a variable reference, which
@@ -389,6 +389,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 			else
 				fdefault = odefault;
 
+			fdefault = unescape_string(fdefault);
+			flabel = unescape_string(flabel);
+
 			FieldSpec spec = FieldSpec(
 				narrow_to_wide(fname.c_str()),
 				narrow_to_wide(flabel.c_str()),
@@ -434,15 +437,17 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "label")
 		{
 			v2s32 pos = padding;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 
 			rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15));
 			
-			std::string flabel = f.next("]");
+			std::string flabel = f.next_esc("]");
 			if(bp_set != 2)
 				errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;
 
+			flabel = unescape_string(flabel);
+
 			FieldSpec spec = FieldSpec(
 				narrow_to_wide(""),
 				narrow_to_wide(flabel.c_str()),
@@ -455,19 +460,21 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "button" || type == "button_exit")
 		{
 			v2s32 pos = padding;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
-			pos.Y += (stof(f.next(";")) * (float)imgsize.Y)/2;
+			geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
+			pos.Y += (stof(f.next_esc(";")) * (float)imgsize.Y)/2;
 
 			rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
 			
-			std::string fname = f.next(";");
-			std::string flabel = f.next("]");
+			std::string fname = f.next_esc(";");
+			std::string flabel = f.next_esc("]");
 			if(bp_set != 2)
 				errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
 
+			flabel = unescape_string(flabel);
+
 			FieldSpec spec = FieldSpec(
 				narrow_to_wide(fname.c_str()),
 				narrow_to_wide(flabel.c_str()),
@@ -483,20 +490,22 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "image_button" || type == "image_button_exit")
 		{
 			v2s32 pos = padding;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
-			geom.Y = (stof(f.next(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+			geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
+			geom.Y = (stof(f.next_esc(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
 
 			rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
 			
-			std::string fimage = f.next(";");
-			std::string fname = f.next(";");
-			std::string flabel = f.next("]");
+			std::string fimage = f.next_esc(";");
+			std::string fname = f.next_esc(";");
+			std::string flabel = f.next_esc("]");
 			if(bp_set != 2)
 				errorstream<<"WARNING: invalid use of image_button without a size[] element"<<std::endl;
 
+			flabel = unescape_string(flabel);
+
 			FieldSpec spec = FieldSpec(
 				narrow_to_wide(fname.c_str()),
 				narrow_to_wide(flabel.c_str()),
@@ -519,15 +528,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else if(type == "item_image_button")
 		{
 			v2s32 pos = padding;
-			pos.X += stof(f.next(",")) * (float)spacing.X;
-			pos.Y += stof(f.next(";")) * (float)spacing.Y;
+			pos.X += stof(f.next_esc(",")) * (float)spacing.X;
+			pos.Y += stof(f.next_esc(";")) * (float)spacing.Y;
 			v2s32 geom;
-			geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
-			geom.Y = (stof(f.next(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+			geom.X = (stof(f.next_esc(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
+			geom.Y = (stof(f.next_esc(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
 			rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);		
-			std::string fimage = f.next(";");
-			std::string fname = f.next(";");
-			std::string flabel = f.next("]");
+			std::string fimage = f.next_esc(";");
+			std::string fname = f.next_esc(";");
+			std::string flabel = f.next_esc("]");
 			if(bp_set != 2)
 				errorstream<<"WARNING: invalid use of item_image_button without a size[] element"<<std::endl;		
 			IItemDefManager *idef = m_gamedef->idef();
@@ -535,6 +544,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 			item.deSerialize(fimage, idef);
 			video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);
 			std::string tooltip = item.getDefinition(idef).description;
+			flabel = unescape_string(flabel);
 			FieldSpec spec = FieldSpec(
 				narrow_to_wide(fname.c_str()),
 				narrow_to_wide(flabel.c_str()),
@@ -556,7 +566,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
 		else
 		{
 			// Ignore others
-			std::string ts = f.next("]");
+			std::string ts = f.next_esc("]");
 			infostream<<"Unknown DrawSpec: type="<<type<<", data=\""<<ts<<"\""
 					<<std::endl;
 		}
diff --git a/src/strfnd.h b/src/strfnd.h
index d28aa73b2..4a72edf3c 100644
--- a/src/strfnd.h
+++ b/src/strfnd.h
@@ -65,6 +65,25 @@ class Strfnd{
 		//std::cout<<"palautus=\""<<palautus<<"\""<<std::endl;
         return palautus;
     }
+    
+    // Returns substr of tek up to the next occurence of plop that isn't escaped with '\'
+    std::string next_esc(std::string plop) {
+		size_t n, realp;
+		
+    	if (p >= tek.size())
+    		return "";
+		
+		realp = p;
+		do {
+			n = tek.find(plop, p);
+			if (n == std::string::npos || plop == "")
+				n = tek.length();
+			p = n + plop.length();
+		} while (n > 0 && tek[n - 1] == '\\');
+		
+		return tek.substr(realp, n - realp);
+    }
+    
 	void skip_over(std::string chars){
 		while(p < tek.size()){
 			bool is = false;
@@ -128,6 +147,24 @@ class WStrfnd{
 		//std::cout<<"palautus=\""<<palautus<<"\""<<std::endl;
         return palautus;
     }
+    
+    std::wstring next_esc(std::wstring plop) {
+		size_t n, realp;
+		
+    	if (p >= tek.size())
+    		return L"";
+		
+		realp = p;
+		do {
+			n = tek.find(plop, p);
+			if (n == std::wstring::npos || plop == L"")
+				n = tek.length();
+			p = n + plop.length();
+		} while (n > 0 && tek[n - 1] == '\\');
+		
+		return tek.substr(realp, n - realp);
+    }
+    
     bool atend(){
         if(p>=tek.size()) return true;
         return false;
diff --git a/src/util/string.h b/src/util/string.h
index 2f0264bd4..6c48adeb3 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -286,6 +286,22 @@ inline std::string wrap_rows(const std::string &from, u32 rowlen)
 	return to;
 }
 
+/*
+	Removes all \\ from a string that had been escaped (FormSpec strings)
+*/
+inline std::string unescape_string(std::string &s)
+{
+	std::string res;
+	
+	for (size_t i = 0; i <= s.length(); i++) {
+		if (s[i] == '\\')
+			i++;
+		res += s[i];
+	}
+	
+	return res;
+}
+
 std::string translatePassword(std::string playername, std::wstring password);
 size_t curl_write_data(char *ptr, size_t size, size_t nmemb, void *userdata);
 u32 readFlagString(std::string str, FlagDesc *flagdesc);
-- 
GitLab