github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/charset/escape_test.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package charset
     7  
     8  import (
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  type escapeControlTest struct {
    15  	name   string
    16  	text   string
    17  	status EscapeStatus
    18  	result string
    19  }
    20  
    21  var escapeControlTests = []escapeControlTest{
    22  	{
    23  		name: "<empty>",
    24  	},
    25  	{
    26  		name:   "single line western",
    27  		text:   "single line western",
    28  		result: "single line western",
    29  		status: EscapeStatus{HasLTRScript: true},
    30  	},
    31  	{
    32  		name:   "multi line western",
    33  		text:   "single line western\nmulti line western\n",
    34  		result: "single line western\nmulti line western\n",
    35  		status: EscapeStatus{HasLTRScript: true},
    36  	},
    37  	{
    38  		name:   "multi line western non-breaking space",
    39  		text:   "single line western\nmulti line western\n",
    40  		result: `single line<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>western` + "\n" + `multi line<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>western` + "\n",
    41  		status: EscapeStatus{Escaped: true, HasLTRScript: true, HasSpaces: true},
    42  	},
    43  	{
    44  		name:   "mixed scripts: western + japanese",
    45  		text:   "日属秘ぞしちゅ。Then some western.",
    46  		result: "日属秘ぞしちゅ。Then some western.",
    47  		status: EscapeStatus{HasLTRScript: true},
    48  	},
    49  	{
    50  		name:   "japanese",
    51  		text:   "日属秘ぞしちゅ。",
    52  		result: "日属秘ぞしちゅ。",
    53  		status: EscapeStatus{HasLTRScript: true},
    54  	},
    55  	{
    56  		name:   "hebrew",
    57  		text:   "עד תקופת יוון העתיקה היה העיסוק במתמטיקה תכליתי בלבד: היא שימשה כאוסף של נוסחאות לחישוב קרקע, אוכלוסין וכו'. פריצת הדרך של היוונים, פרט לתרומותיהם הגדולות לידע המתמטי, הייתה בלימוד המתמטיקה כשלעצמה, מתוקף ערכה הרוחני. יחסם של חלק מהיוונים הקדמונים למתמטיקה היה דתי - למשל, הכת שאסף סביבו פיתגורס האמינה כי המתמטיקה היא הבסיס לכל הדברים. היוונים נחשבים ליוצרי מושג ההוכחה המתמטית, וכן לראשונים שעסקו במתמטיקה לשם עצמה, כלומר כתחום מחקרי עיוני ומופשט ולא רק כעזר שימושי. עם זאת, לצדה",
    58  		result: "עד תקופת יוון העתיקה היה העיסוק במתמטיקה תכליתי בלבד: היא שימשה כאוסף של נוסחאות לחישוב קרקע, אוכלוסין וכו'. פריצת הדרך של היוונים, פרט לתרומותיהם הגדולות לידע המתמטי, הייתה בלימוד המתמטיקה כשלעצמה, מתוקף ערכה הרוחני. יחסם של חלק מהיוונים הקדמונים למתמטיקה היה דתי - למשל, הכת שאסף סביבו פיתגורס האמינה כי המתמטיקה היא הבסיס לכל הדברים. היוונים נחשבים ליוצרי מושג ההוכחה המתמטית, וכן לראשונים שעסקו במתמטיקה לשם עצמה, כלומר כתחום מחקרי עיוני ומופשט ולא רק כעזר שימושי. עם זאת, לצדה",
    59  		status: EscapeStatus{HasRTLScript: true},
    60  	},
    61  	{
    62  		name: "more hebrew",
    63  		text: `בתקופה מאוחרת יותר, השתמשו היוונים בשיטת סימון מתקדמת יותר, שבה הוצגו המספרים לפי 22 אותיות האלפבית היווני. לסימון המספרים בין 1 ל-9 נקבעו תשע האותיות הראשונות, בתוספת גרש ( ' ) בצד ימין של האות, למעלה; תשע האותיות הבאות ייצגו את העשרות מ-10 עד 90, והבאות את המאות. לסימון הספרות בין 1000 ל-900,000, השתמשו היוונים באותן אותיות, אך הוסיפו לאותיות את הגרש דווקא מצד שמאל של האותיות, למטה. ממיליון ומעלה, כנראה השתמשו היוונים בשני תגים במקום אחד.
    64  
    65  			המתמטיקאי הבולט הראשון ביוון העתיקה, ויש האומרים בתולדות האנושות, הוא תאלס (624 לפנה"ס - 546 לפנה"ס בקירוב).[1] לא יהיה זה משולל יסוד להניח שהוא האדם הראשון שהוכיח משפט מתמטי, ולא רק גילה אותו. תאלס הוכיח שישרים מקבילים חותכים מצד אחד של שוקי זווית קטעים בעלי יחסים שווים (משפט תאלס הראשון), שהזווית המונחת על קוטר במעגל היא זווית ישרה (משפט תאלס השני), שהקוטר מחלק את המעגל לשני חלקים שווים, ושזוויות הבסיס במשולש שווה-שוקיים שוות זו לזו. מיוחסות לו גם שיטות למדידת גובהן של הפירמידות בעזרת מדידת צילן ולקביעת מיקומה של ספינה הנראית מן החוף.
    66  
    67  			בשנים 582 לפנה"ס עד 496 לפנה"ס, בקירוב, חי מתמטיקאי חשוב במיוחד - פיתגורס. המקורות הראשוניים עליו מועטים, וההיסטוריונים מתקשים להפריד את העובדות משכבת המסתורין והאגדות שנקשרו בו. ידוע שסביבו התקבצה האסכולה הפיתגוראית מעין כת פסבדו-מתמטית שהאמינה ש"הכל מספר", או ליתר דיוק הכל ניתן לכימות, וייחסה למספרים משמעויות מיסטיות. ככל הנראה הפיתגוראים ידעו לבנות את הגופים האפלטוניים, הכירו את הממוצע האריתמטי, הממוצע הגאומטרי והממוצע ההרמוני והגיעו להישגים חשובים נוספים. ניתן לומר שהפיתגוראים גילו את היותו של השורש הריבועי של 2, שהוא גם האלכסון בריבוע שאורך צלעותיו 1, אי רציונלי, אך תגליתם הייתה למעשה רק שהקטעים "חסרי מידה משותפת", ומושג המספר האי רציונלי מאוחר יותר.[2] אזכור ראשון לקיומם של קטעים חסרי מידה משותפת מופיע בדיאלוג "תאיטיטוס" של אפלטון, אך רעיון זה היה מוכר עוד קודם לכן, במאה החמישית לפנה"ס להיפאסוס, בן האסכולה הפיתגוראית, ואולי לפיתגורס עצמו.[3]`,
    68  		result: `בתקופה מאוחרת יותר, השתמשו היוונים בשיטת סימון מתקדמת יותר, שבה הוצגו המספרים לפי 22 אותיות האלפבית היווני. לסימון המספרים בין 1 ל-9 נקבעו תשע האותיות הראשונות, בתוספת גרש ( ' ) בצד ימין של האות, למעלה; תשע האותיות הבאות ייצגו את העשרות מ-10 עד 90, והבאות את המאות. לסימון הספרות בין 1000 ל-900,000, השתמשו היוונים באותן אותיות, אך הוסיפו לאותיות את הגרש דווקא מצד שמאל של האותיות, למטה. ממיליון ומעלה, כנראה השתמשו היוונים בשני תגים במקום אחד.
    69  
    70  			המתמטיקאי הבולט הראשון ביוון העתיקה, ויש האומרים בתולדות האנושות, הוא תאלס (624 לפנה"ס - 546 לפנה"ס בקירוב).[1] לא יהיה זה משולל יסוד להניח שהוא האדם הראשון שהוכיח משפט מתמטי, ולא רק גילה אותו. תאלס הוכיח שישרים מקבילים חותכים מצד אחד של שוקי זווית קטעים בעלי יחסים שווים (משפט תאלס הראשון), שהזווית המונחת על קוטר במעגל היא זווית ישרה (משפט תאלס השני), שהקוטר מחלק את המעגל לשני חלקים שווים, ושזוויות הבסיס במשולש שווה-שוקיים שוות זו לזו. מיוחסות לו גם שיטות למדידת גובהן של הפירמידות בעזרת מדידת צילן ולקביעת מיקומה של ספינה הנראית מן החוף.
    71  
    72  			בשנים 582 לפנה"ס עד 496 לפנה"ס, בקירוב, חי מתמטיקאי חשוב במיוחד - פיתגורס. המקורות הראשוניים עליו מועטים, וההיסטוריונים מתקשים להפריד את העובדות משכבת המסתורין והאגדות שנקשרו בו. ידוע שסביבו התקבצה האסכולה הפיתגוראית מעין כת פסבדו-מתמטית שהאמינה ש"הכל מספר", או ליתר דיוק הכל ניתן לכימות, וייחסה למספרים משמעויות מיסטיות. ככל הנראה הפיתגוראים ידעו לבנות את הגופים האפלטוניים, הכירו את הממוצע האריתמטי, הממוצע הגאומטרי והממוצע ההרמוני והגיעו להישגים חשובים נוספים. ניתן לומר שהפיתגוראים גילו את היותו של השורש הריבועי של 2, שהוא גם האלכסון בריבוע שאורך צלעותיו 1, אי רציונלי, אך תגליתם הייתה למעשה רק שהקטעים "חסרי מידה משותפת", ומושג המספר האי רציונלי מאוחר יותר.[2] אזכור ראשון לקיומם של קטעים חסרי מידה משותפת מופיע בדיאלוג "תאיטיטוס" של אפלטון, אך רעיון זה היה מוכר עוד קודם לכן, במאה החמישית לפנה"ס להיפאסוס, בן האסכולה הפיתגוראית, ואולי לפיתגורס עצמו.[3]`,
    73  		status: EscapeStatus{HasRTLScript: true},
    74  	},
    75  	{
    76  		name: "Mixed RTL+LTR",
    77  		text: `Many computer programs fail to display bidirectional text correctly.
    78  For example, the Hebrew name Sarah (שרה) is spelled: sin (ש) (which appears rightmost),
    79  then resh (ר), and finally heh (ה) (which should appear leftmost).`,
    80  		result: `Many computer programs fail to display bidirectional text correctly.
    81  For example, the Hebrew name Sarah (שרה) is spelled: sin (ש) (which appears rightmost),
    82  then resh (ר), and finally heh (ה) (which should appear leftmost).`,
    83  		status: EscapeStatus{
    84  			HasRTLScript: true,
    85  			HasLTRScript: true,
    86  		},
    87  	},
    88  	{
    89  		name: "Mixed RTL+LTR+BIDI",
    90  		text: `Many computer programs fail to display bidirectional text correctly.
    91  			For example, the Hebrew name Sarah ` + "\u2067" + `שרה` + "\u2066\n" +
    92  			`sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).`,
    93  		result: `Many computer programs fail to display bidirectional text correctly.
    94  			For example, the Hebrew name Sarah <span class="escaped-code-point" data-escaped="[U+2067]"><span class="char">` + "\u2067" + `</span></span>שרה<span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>` + "\n" +
    95  			`sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).`,
    96  		status: EscapeStatus{
    97  			Escaped:      true,
    98  			HasBIDI:      true,
    99  			HasRTLScript: true,
   100  			HasLTRScript: true,
   101  		},
   102  	},
   103  	{
   104  		name:   "Accented characters",
   105  		text:   string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}),
   106  		result: string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba}),
   107  		status: EscapeStatus{HasLTRScript: true},
   108  	},
   109  	{
   110  		name:   "Program",
   111  		text:   "string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})",
   112  		result: "string([]byte{0xc3, 0xa1, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xb3, 0xc3, 0xba})",
   113  		status: EscapeStatus{HasLTRScript: true},
   114  	},
   115  	{
   116  		name:   "CVE testcase",
   117  		text:   "if access_level != \"user\u202E \u2066// Check if admin\u2069 \u2066\" {",
   118  		result: `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {`,
   119  		status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true},
   120  	},
   121  	{
   122  		name: "Mixed testcase with fail",
   123  		text: `Many computer programs fail to display bidirectional text correctly.
   124  			For example, the Hebrew name Sarah ` + "\u2067" + `שרה` + "\u2066\n" +
   125  			`sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).` +
   126  			"\nif access_level != \"user\u202E \u2066// Check if admin\u2069 \u2066\" {\n",
   127  		result: `Many computer programs fail to display bidirectional text correctly.
   128  			For example, the Hebrew name Sarah <span class="escaped-code-point" data-escaped="[U+2067]"><span class="char">` + "\u2067" + `</span></span>שרה<span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>` + "\n" +
   129  			`sin (ש) (which appears rightmost), then resh (ר), and finally heh (ה) (which should appear leftmost).` +
   130  			"\n" + `if access_level != "user<span class="escaped-code-point" data-escaped="[U+202E]"><span class="char">` + "\u202e" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>// Check if admin<span class="escaped-code-point" data-escaped="[U+2069]"><span class="char">` + "\u2069" + `</span></span> <span class="escaped-code-point" data-escaped="[U+2066]"><span class="char">` + "\u2066" + `</span></span>" {` + "\n",
   131  		status: EscapeStatus{Escaped: true, HasBIDI: true, BadBIDI: true, HasLTRScript: true, HasRTLScript: true},
   132  	},
   133  	{
   134  		// UTF-8/16/32 all use the same codepoint for BOM
   135  		// GitBundle could read UTF-16/32 content and convert into UTF-8 internally then render it, so we only process UTF-8 internally
   136  		name:   "UTF BOM",
   137  		text:   "\xef\xbb\xbftest",
   138  		result: "\xef\xbb\xbftest",
   139  		status: EscapeStatus{HasLTRScript: true},
   140  	},
   141  }
   142  
   143  func TestEscapeControlString(t *testing.T) {
   144  	for _, tt := range escapeControlTests {
   145  		t.Run(tt.name, func(t *testing.T) {
   146  			status, result := EscapeControlString(tt.text)
   147  			if !reflect.DeepEqual(status, tt.status) {
   148  				t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
   149  			}
   150  			if result != tt.result {
   151  				t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestEscapeControlBytes(t *testing.T) {
   158  	for _, tt := range escapeControlTests {
   159  		t.Run(tt.name, func(t *testing.T) {
   160  			status, result := EscapeControlBytes([]byte(tt.text))
   161  			if !reflect.DeepEqual(status, tt.status) {
   162  				t.Errorf("EscapeControlBytes() status = %v, wanted= %v", status, tt.status)
   163  			}
   164  			if string(result) != tt.result {
   165  				t.Errorf("EscapeControlBytes()\nresult= %v,\nwanted= %v", result, tt.result)
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  func TestEscapeControlReader(t *testing.T) {
   172  	// lets add some control characters to the tests
   173  	tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
   174  	copy(tests, escapeControlTests)
   175  
   176  	// if there is a BOM, we should keep the BOM
   177  	addPrefix := func(prefix, s string) string {
   178  		if strings.HasPrefix(s, "\xef\xbb\xbf") {
   179  			return s[:3] + prefix + s[3:]
   180  		}
   181  		return prefix + s
   182  	}
   183  	for _, test := range escapeControlTests {
   184  		test.name += " (+Control)"
   185  		test.text = addPrefix("\u001E", test.text)
   186  		test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
   187  		test.status.Escaped = true
   188  		test.status.HasControls = true
   189  		tests = append(tests, test)
   190  	}
   191  
   192  	for _, test := range escapeControlTests {
   193  		test.name += " (+Mark)"
   194  		test.text = addPrefix("\u0300", test.text)
   195  		test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+0300]"><span class="char">`+"\u0300"+`</span></span>`, test.result)
   196  		test.status.Escaped = true
   197  		test.status.HasMarks = true
   198  		tests = append(tests, test)
   199  	}
   200  
   201  	for _, tt := range tests {
   202  		t.Run(tt.name, func(t *testing.T) {
   203  			input := strings.NewReader(tt.text)
   204  			output := &strings.Builder{}
   205  			status, err := EscapeControlReader(input, output)
   206  			result := output.String()
   207  			if err != nil {
   208  				t.Errorf("EscapeControlReader(): err = %v", err)
   209  			}
   210  
   211  			if !reflect.DeepEqual(status, tt.status) {
   212  				t.Errorf("EscapeControlReader() status = %v, wanted= %v", status, tt.status)
   213  			}
   214  			if result != tt.result {
   215  				t.Errorf("EscapeControlReader()\nresult= %v,\nwanted= %v", result, tt.result)
   216  			}
   217  		})
   218  	}
   219  }
   220  
   221  func TestEscapeControlReader_panic(t *testing.T) {
   222  	bs := make([]byte, 0, 20479)
   223  	bs = append(bs, 'A')
   224  	for i := 0; i < 6826; i++ {
   225  		bs = append(bs, []byte("—")...)
   226  	}
   227  	_, _ = EscapeControlBytes(bs)
   228  }