code.gitea.io/gitea@v1.22.3/modules/markup/csv/csv.go (about) 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package markup 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "html" 11 "io" 12 "regexp" 13 "strconv" 14 15 "code.gitea.io/gitea/modules/csv" 16 "code.gitea.io/gitea/modules/markup" 17 "code.gitea.io/gitea/modules/setting" 18 ) 19 20 func init() { 21 markup.RegisterRenderer(Renderer{}) 22 } 23 24 // Renderer implements markup.Renderer for csv files 25 type Renderer struct{} 26 27 // Name implements markup.Renderer 28 func (Renderer) Name() string { 29 return "csv" 30 } 31 32 // Extensions implements markup.Renderer 33 func (Renderer) Extensions() []string { 34 return []string{".csv", ".tsv"} 35 } 36 37 // SanitizerRules implements markup.Renderer 38 func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { 39 return []setting.MarkupSanitizerRule{ 40 {Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)}, 41 {Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)}, 42 {Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)}, 43 } 44 } 45 46 func writeField(w io.Writer, element, class, field string) error { 47 if _, err := io.WriteString(w, "<"); err != nil { 48 return err 49 } 50 if _, err := io.WriteString(w, element); err != nil { 51 return err 52 } 53 if len(class) > 0 { 54 if _, err := io.WriteString(w, " class=\""); err != nil { 55 return err 56 } 57 if _, err := io.WriteString(w, class); err != nil { 58 return err 59 } 60 if _, err := io.WriteString(w, "\""); err != nil { 61 return err 62 } 63 } 64 if _, err := io.WriteString(w, ">"); err != nil { 65 return err 66 } 67 if _, err := io.WriteString(w, html.EscapeString(field)); err != nil { 68 return err 69 } 70 if _, err := io.WriteString(w, "</"); err != nil { 71 return err 72 } 73 if _, err := io.WriteString(w, element); err != nil { 74 return err 75 } 76 _, err := io.WriteString(w, ">") 77 return err 78 } 79 80 // Render implements markup.Renderer 81 func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 82 tmpBlock := bufio.NewWriter(output) 83 maxSize := setting.UI.CSV.MaxFileSize 84 85 if maxSize == 0 { 86 return r.tableRender(ctx, input, tmpBlock) 87 } 88 89 rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1)) 90 if err != nil { 91 return err 92 } 93 94 if int64(len(rawBytes)) <= maxSize { 95 return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock) 96 } 97 return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock) 98 } 99 100 func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error { 101 _, err := tmpBlock.WriteString("<pre>") 102 if err != nil { 103 return err 104 } 105 106 scan := bufio.NewScanner(input) 107 scan.Split(bufio.ScanRunes) 108 for scan.Scan() { 109 switch scan.Text() { 110 case `&`: 111 _, err = tmpBlock.WriteString("&") 112 case `'`: 113 _, err = tmpBlock.WriteString("'") // "'" is shorter than "'" and apos was not in HTML until HTML5. 114 case `<`: 115 _, err = tmpBlock.WriteString("<") 116 case `>`: 117 _, err = tmpBlock.WriteString(">") 118 case `"`: 119 _, err = tmpBlock.WriteString(""") // """ is shorter than """. 120 default: 121 _, err = tmpBlock.Write(scan.Bytes()) 122 } 123 if err != nil { 124 return err 125 } 126 } 127 if err = scan.Err(); err != nil { 128 return fmt.Errorf("fallbackRender scan: %w", err) 129 } 130 131 _, err = tmpBlock.WriteString("</pre>") 132 if err != nil { 133 return err 134 } 135 return tmpBlock.Flush() 136 } 137 138 func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock *bufio.Writer) error { 139 rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input) 140 if err != nil { 141 return err 142 } 143 144 if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil { 145 return err 146 } 147 row := 1 148 for { 149 fields, err := rd.Read() 150 if err == io.EOF { 151 break 152 } 153 if err != nil { 154 continue 155 } 156 if _, err := tmpBlock.WriteString("<tr>"); err != nil { 157 return err 158 } 159 element := "td" 160 if row == 1 { 161 element = "th" 162 } 163 if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row)); err != nil { 164 return err 165 } 166 for _, field := range fields { 167 if err := writeField(tmpBlock, element, "", field); err != nil { 168 return err 169 } 170 } 171 if _, err := tmpBlock.WriteString("</tr>"); err != nil { 172 return err 173 } 174 175 row++ 176 } 177 if _, err = tmpBlock.WriteString("</table>"); err != nil { 178 return err 179 } 180 return tmpBlock.Flush() 181 }