Refactor markup/csv: don't read all to memory (#29760)
(cherry picked from commit e79a807a8461a73bd66146d816f635b66e198c89)
This commit is contained in:
parent
2da0628f18
commit
d413a8fcac
2 changed files with 58 additions and 15 deletions
|
@ -77,29 +77,62 @@ func writeField(w io.Writer, element, class, field string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render implements markup.Renderer
|
// Render implements markup.Renderer
|
||||||
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
tmpBlock := bufio.NewWriter(output)
|
tmpBlock := bufio.NewWriter(output)
|
||||||
|
maxSize := setting.UI.CSV.MaxFileSize
|
||||||
|
|
||||||
// FIXME: don't read all to memory
|
if maxSize == 0 {
|
||||||
rawBytes, err := io.ReadAll(input)
|
return r.tableRender(ctx, input, tmpBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < int64(len(rawBytes)) {
|
if int64(len(rawBytes)) <= maxSize {
|
||||||
if _, err := tmpBlock.WriteString("<pre>"); err != nil {
|
return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock)
|
||||||
return err
|
}
|
||||||
}
|
return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock)
|
||||||
if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error {
|
||||||
if _, err := tmpBlock.WriteString("</pre>"); err != nil {
|
_, err := tmpBlock.WriteString("<pre>")
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
return tmpBlock.Flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes))
|
scan := bufio.NewScanner(input)
|
||||||
|
scan.Split(bufio.ScanRunes)
|
||||||
|
for scan.Scan() {
|
||||||
|
switch scan.Text() {
|
||||||
|
case `&`:
|
||||||
|
_, err = tmpBlock.WriteString("&")
|
||||||
|
case `'`:
|
||||||
|
_, err = tmpBlock.WriteString("'") // "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||||
|
case `<`:
|
||||||
|
_, err = tmpBlock.WriteString("<")
|
||||||
|
case `>`:
|
||||||
|
_, err = tmpBlock.WriteString(">")
|
||||||
|
case `"`:
|
||||||
|
_, err = tmpBlock.WriteString(""") // """ is shorter than """.
|
||||||
|
default:
|
||||||
|
_, err = tmpBlock.Write(scan.Bytes())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tmpBlock.WriteString("</pre>")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tmpBlock.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock *bufio.Writer) error {
|
||||||
|
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -29,4 +31,12 @@ func TestRenderCSV(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, v, buf.String())
|
assert.EqualValues(t, v, buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("fallbackRender", func(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := render.fallbackRender(strings.NewReader("1,<a>\n2,<b>"), bufio.NewWriter(&buf))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
want := "<pre>1,<a>\n2,<b></pre>"
|
||||||
|
assert.Equal(t, want, buf.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue