Repository: yorkie-team/yorkie
Pull request #443 (2023-01-12 08:07:48)

Cozitive attrs escape
<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

#439

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

**Additional documentation**:

<!--
This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation:
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

**Checklist**:
- [x] Added relevant tests or not required
- [ ] Didn't break anything



Patch:
diff --git a/pkg/document/crdt/rht.go b/pkg/document/crdt/rht.go
index b614decf..806b2205 100644
--- a/pkg/document/crdt/rht.go
+++ b/pkg/document/crdt/rht.go
@@ -177,7 +177,7 @@ func (rht *RHT) Marshal() string {
 			sb.WriteString(",")
 		}
 		value := members[k]
-		sb.WriteString(fmt.Sprintf(`"%s":"%s"`, k, value))
+		sb.WriteString(fmt.Sprintf(`"%s":"%s"`, EscapeString(k), EscapeString(value)))
 	}
 	sb.WriteString("}")
 
diff --git a/pkg/document/crdt/rht_pq_map.go b/pkg/document/crdt/rht_pq_map.go
index cf023f45..97dbe508 100644
--- a/pkg/document/crdt/rht_pq_map.go
+++ b/pkg/document/crdt/rht_pq_map.go
@@ -226,7 +226,7 @@ func (rht *RHTPriorityQueueMap) Marshal() string {
 			sb.WriteString(",")
 		}
 		value := members[k]
-		sb.WriteString(fmt.Sprintf(`"%s":%s`, k, value.Marshal()))
+		sb.WriteString(fmt.Sprintf(`"%s":%s`, EscapeString(k), value.Marshal()))
 	}
 	sb.WriteString("}")
 
diff --git a/pkg/document/crdt/strings.go b/pkg/document/crdt/strings.go
index 4ba0ed01..ca9fb251 100644
--- a/pkg/document/crdt/strings.go
+++ b/pkg/document/crdt/strings.go
@@ -32,26 +32,39 @@ func EscapeString(s string) string {
 			continue
 		}
 		switch c {
-		case '\\', '"':
+		case '\\':
 			buf.WriteByte('\\')
-			buf.WriteByte(c)
+			buf.WriteByte('\\')
+		case '"':
+			buf.WriteByte('\\')
+			buf.WriteByte('\\')
+			buf.WriteByte('"')
 		case '\n':
+			buf.WriteByte('\\')
 			buf.WriteByte('\\')
 			buf.WriteByte('n')
 		case '\f':
+			buf.WriteByte('\\')
 			buf.WriteByte('\\')
 			buf.WriteByte('f')
 		case '\b':
+			buf.WriteByte('\\')
 			buf.WriteByte('\\')
 			buf.WriteByte('b')
 		case '\r':
+			buf.WriteByte('\\')
 			buf.WriteByte('\\')
 			buf.WriteByte('r')
 		case '\t':
+			buf.WriteByte('\\')
 			buf.WriteByte('\\')
 			buf.WriteByte('t')
 		default:
-			buf.Write([]byte{'\\', 'u', '0', '0'})
+			buf.WriteByte('\\')
+			buf.WriteByte('\\')
+			buf.WriteByte('u')
+			buf.WriteByte('0')
+			buf.WriteByte('0')
 			buf.WriteByte(hex[c>>4])
 			buf.WriteByte(hex[c&0xF])
 		}


Test patch:
diff --git a/pkg/document/crdt/rht_test.go b/pkg/document/crdt/rht_test.go
new file mode 100644
index 00000000..c422c447
--- /dev/null
+++ b/pkg/document/crdt/rht_test.go
@@ -0,0 +1,23 @@
+package crdt
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMarshal(t *testing.T) {
+	t.Run("marshal test", func(t *testing.T) {
+		key1 := `hello\\\t`
+		value1 := "world\"\f\b"
+		key2 := "hi"
+		value2 := `test\r`
+		expected := `{"hello\\\\\\t":"world\\"\\f\\b","hi":"test\\r"}`
+
+		rht := NewRHT()
+		rht.Set(key1, value1, nil)
+		rht.Set(key2, value2, nil)
+		actual := rht.Marshal()
+		assert.Equal(t, expected, actual)
+	})
+}
diff --git a/pkg/document/crdt/strings_test.go b/pkg/document/crdt/strings_test.go
index 0ae55f88..395ac2cf 100644
--- a/pkg/document/crdt/strings_test.go
+++ b/pkg/document/crdt/strings_test.go
@@ -23,11 +23,38 @@ import (
 )
 
 func TestEscapeString(t *testing.T) {
-	t.Run("escape string", func(t *testing.T) {
-		str := `"hello world"`
-		expected := `\"hello world\"`
+	t.Run("escape normal string", func(t *testing.T) {
+		str := `hello world`
+		expected := `hello world`
 		actual := EscapeString(str)
 		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("escape string with doublequote", func(t *testing.T) {
+		str := `hello world"`
+		expected := `hello world\\"`
+		actual := EscapeString(str)
+		assert.Equal(t, expected, actual)
+	})
 
+	t.Run("escape string with raw control character", func(t *testing.T) {
+		str := "hello world\n"
+		expected := `hello world\\n`
+		actual := EscapeString(str)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("escape string with escaped control character", func(t *testing.T) {
+		str := `hello world\n`
+		expected := `hello world\\n`
+		actual := EscapeString(str)
+		assert.Equal(t, expected, actual)
+	})
+
+	t.Run("escape string with raw unicode character", func(t *testing.T) {
+		str := "hello world\u1234"
+		expected := "hello world\u1234"
+		actual := EscapeString(str)
+		assert.Equal(t, expected, actual)
 	})
 }
diff --git a/pkg/document/document_test.go b/pkg/document/document_test.go
index bd1d5599..2d47c491 100644
--- a/pkg/document/document_test.go
+++ b/pkg/document/document_test.go
@@ -336,7 +336,7 @@ func TestDocument(t *testing.T) {
 			assert.Equal(
 				t,
 				`[0:0:00:0 {} ""][1:2:00:0 {"b":"1"} "Hel"][1:2:00:3 {"b":"1","i":"1"} "lo"]`+
-					`[5:1:00:0 {"list":"true"} "\n"][4:1:00:0 {} " Yorkie"]{1:2:00:5 {} " world"}`,
+					`[5:1:00:0 {"list":"true"} "\\n"][4:1:00:0 {} " Yorkie"]{1:2:00:5 {} " world"}`,
 				text.StructureAsString(),
 			)
 			return nil
@@ -345,7 +345,7 @@ func TestDocument(t *testing.T) {
 		assert.Equal(
 			t,
 			`{"k1":[{"attrs":{"b":"1"},"val":"Hel"},{"attrs":{"b":"1","i":"1"},"val":"lo"},`+
-				`{"attrs":{"list":"true"},"val":"\n"},{"attrs":{},"val":" Yorkie"}]}`,
+				`{"attrs":{"list":"true"},"val":"\\n"},{"attrs":{},"val":" Yorkie"}]}`,
 			doc.Marshal(),
 		)
 	})