| 1 | package tinyclient |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | func TestSocialBodyRendersMarkdownAndMedia(t *testing.T) { |
| 9 | row := map[string]any{ |
| 10 | "kind" : "30023" , |
| 11 | "content" : "# Hello\n\nA **note** with a table:\n\n| one | two |\n| --- | --- |\n| a | b |\n\n" , |
| 12 | "tags" : [][]string{{"imeta" , "url https://cdn.example.test/sun.png" , "m image/png" , "alt sunset" }}, |
| 13 | } |
| 14 | got := string(socialBody(row)) |
| 15 | for _, want := range []string{"<h1>Hello</h1>" , "<strong>note</strong>" , "data-markdown-table" , "<img" , `alt="sunset"` , `loading="lazy"` } { |
| 16 | if !strings.Contains(got, want) { |
| 17 | t.Errorf("social body missing %q: %s" , want, got) |
| 18 | } |
| 19 | } |
| 20 | if strings.Count(got, "sun.png" ) != 1 { |
| 21 | t.Fatalf("media should have one image: %s" , got) |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | func TestSocialBodySupportsMediaTypesAndIgnoresCode(t *testing.T) { |
| 26 | row := map[string]any{ |
| 27 | "kind" : "1" , |
| 28 | "content" : "https://cdn.example.test/clip.mp4\n\nhttps://cdn.example.test/song.mp3\n\n```\nhttps://cdn.example.test/hidden.png\n```" , |
| 29 | "tags" : [][]string{{"imeta" , "url https://cdn.example.test/clip.mp4" , "m video/mp4" }, {"imeta" , "url https://cdn.example.test/song.mp3" , "m audio/mpeg" }, {"imeta" , "url https://cdn.example.test/hidden.png" , "m image/png" }}, |
| 30 | } |
| 31 | got := string(socialBody(row)) |
| 32 | if !strings.Contains(got, `<video controls` ) || !strings.Contains(got, `<audio controls` ) { |
| 33 | t.Fatalf("native media missing: %s" , got) |
| 34 | } |
| 35 | if !strings.Contains(got, "hidden.png" ) || strings.Contains(got, "<iframe" ) { |
| 36 | t.Fatalf("code or iframe leaked into media: %s" , got) |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | func TestSocialNoteIsPlaintext(t *testing.T) { |
| 41 | got := string(socialBody(map[string]any{"kind" : "1" , "content" : "**literal**\nhttps://cdn.example.test/note.png" , "tags" : [][]string{{"imeta" , "url https://cdn.example.test/note.png" , "m image/png" }}})) |
| 42 | if strings.Contains(got, "<strong>" ) || !strings.Contains(got, "**literal**" ) || !strings.Contains(got, "<img" ) { |
| 43 | t.Fatalf("note rendering = %s" , got) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | func TestSocialMediaSkipsInlineAndFencedCode(t *testing.T) { |
| 48 | url := "https://cdn.example.test/hidden.png" |
| 49 | row := map[string]any{"kind" : "30023" , "content" : "``\n\n````\n\n````" , "tags" : [][]string{{"imeta" , "url " + url, "m image/png" }}} |
| 50 | got := string(socialBody(row)) |
| 51 | if strings.Contains(got, "<img" ) || strings.Contains(got, "<figure" ) { |
| 52 | t.Fatalf("code media was embedded: %s" , got) |
| 53 | } |
| 54 | if !strings.Contains(got, "hidden.png" ) { |
| 55 | t.Fatalf("code source missing: %s" , got) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | func TestSocialBodyEscapesUnsafeContent(t *testing.T) { |
| 60 | got := string(socialBody(map[string]any{"content" : `<script>alert(1)</script> [x](javascript:alert(1)) https://cdn.example.test/a.svg` , "tags" : [][]string{{"imeta" , "url https://cdn.example.test/a.svg" , "m image/svg+xml" }}})) |
| 61 | if strings.Contains(got, "<script" ) || strings.Contains(got, `href="javascript:` ) || strings.Contains(got, "<iframe" ) { |
| 62 | t.Fatalf("unsafe social markup: %s" , got) |
| 63 | } |
| 64 | if !strings.Contains(got, "a.svg" ) { |
| 65 | t.Fatalf("safe media link missing: %s" , got) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | func TestSocialPreviewAndMediaURL(t *testing.T) { |
| 70 | if got := socialPreview(map[string]any{"content" : "hello\nworld" }); got != "hello world" { |
| 71 | t.Fatalf("preview = %q" , got) |
| 72 | } |
| 73 | if got := socialMediaURL("javascript:alert(1)" ); got != "" { |
| 74 | t.Fatalf("unsafe URL = %q" , got) |
| 75 | } |
| 76 | if got := socialMediaURL("https://example.test/a?x=1" ); got != "https://example.test/a?x=1" { |
| 77 | t.Fatalf("safe URL = %q" , got) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | func TestSocialArticleMediaWithoutMetadata(t *testing.T) { |
| 82 | got := string(socialBody(map[string]any{"kind" : 30023, "content" : "\n\n[Watch](https://cdn.test/a.mp4)\n\nhttps://cdn.test/a.mp3" })) |
| 83 | for _, want := range []string{`<img src="https://cdn.test/a.jpg"` , `alt="Cover"` , `<video controls` , `<audio controls` } { |
| 84 | if !strings.Contains(got, want) { |
| 85 | t.Errorf("missing %s: %s" , want, got) |
| 86 | } |
| 87 | } |
| 88 | if strings.Contains(got, "!Cover" ) || strings.Contains(got, "<p><figure" ) { |
| 89 | t.Fatal(got) |
| 90 | } |
| 91 | } |
| 92 | func TestSocialNoteImageWithoutMetadataAndInvalidClientMIME(t *testing.T) { |
| 93 | for _, tags := range [][][]string{nil, {{"imeta" , "url https://cdn.test/a.jpg" , "m jpeg" }}} { |
| 94 | got := string(socialBody(map[string]any{"kind" : 1, "content" : "A photo\nhttps://cdn.test/a.jpg" , "tags" : tags})) |
| 95 | if !strings.Contains(got, `<img src="https://cdn.test/a.jpg"` ) { |
| 96 | t.Fatal(got) |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | func TestSocialMediaTokenCannotBeForgedInCode(t *testing.T) { |
| 101 | raw := "https://cdn.test/a.png" |
| 102 | got := string(socialBody(map[string]any{"kind" : 30023, "content" : "`" + socialMediaToken(raw) + "`\n\n" })) |
| 103 | if strings.Count(got, "<img" ) != 1 || !strings.Contains(got, "<code>" +socialMediaToken(raw)+"</code>" ) { |
| 104 | t.Fatal(got) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | func TestSocialMediaReservesValidMetadataDimensions(t *testing.T) { |
| 109 | for _, dimension := range []string{"1170x1613" , "1170.0x1613.0" } { |
| 110 | got := string(socialBody(map[string]any{"kind" : 1, "content" : "https://cdn.test/a.jpg" , "tags" : [][]string{{"imeta" , "url https://cdn.test/a.jpg" , "m image/jpeg" , "dim " + dimension}}})) |
| 111 | if !strings.Contains(got, `width="1170" height="1613"` ) { |
| 112 | t.Fatal(got) |
| 113 | } |
| 114 | } |
| 115 | for _, dimension := range []string{"0x3" , "NaNx3" , "1e90x3" , `1" onload="bad` } { |
| 116 | got := string(socialBody(map[string]any{"kind" : 1, "content" : "https://cdn.test/a.jpg" , "tags" : [][]string{{"imeta" , "url https://cdn.test/a.jpg" , "m image/jpeg" , "dim " + dimension}}})) |
| 117 | if strings.Contains(got, "width=" ) || strings.Contains(got, "onload=" ) { |
| 118 | t.Fatal(got) |
| 119 | } |
| 120 | } |
| 121 | } |