mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-03 16:01:11 +00:00 
			
		
		
		
	These changes are quite independent and trivial, so I don't want to open too many PRs. * https://github.com/go-gitea/gitea/pull/29882#discussion_r1529607091 * the `f.Close` should be called properly * the error message could be more meaningful (https://github.com/go-gitea/gitea/pull/29882#pullrequestreview-1942557935) * https://github.com/go-gitea/gitea/pull/29859#pullrequestreview-1942324716 * the new translation strings don't take arguments * https://github.com/go-gitea/gitea/pull/28710#discussion_r1443778807 * stale for long time * #28140 * a form was forgotten to be changed to work with backend code (cherry picked from commit 226231ea27d4f2b0f09fa4efb39501507613b284) Conflicts: templates/repo/issue/view_content/pull.tmpl discarded because unexplained templates/status/404.tmpl implemented differently in Forgejo
		
			
				
	
	
		
			181 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2018 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package markup
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"html"
 | 
						|
	"io"
 | 
						|
	"regexp"
 | 
						|
	"strconv"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/csv"
 | 
						|
	"code.gitea.io/gitea/modules/markup"
 | 
						|
	"code.gitea.io/gitea/modules/setting"
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	markup.RegisterRenderer(Renderer{})
 | 
						|
}
 | 
						|
 | 
						|
// Renderer implements markup.Renderer for csv files
 | 
						|
type Renderer struct{}
 | 
						|
 | 
						|
// Name implements markup.Renderer
 | 
						|
func (Renderer) Name() string {
 | 
						|
	return "csv"
 | 
						|
}
 | 
						|
 | 
						|
// Extensions implements markup.Renderer
 | 
						|
func (Renderer) Extensions() []string {
 | 
						|
	return []string{".csv", ".tsv"}
 | 
						|
}
 | 
						|
 | 
						|
// SanitizerRules implements markup.Renderer
 | 
						|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
 | 
						|
	return []setting.MarkupSanitizerRule{
 | 
						|
		{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)},
 | 
						|
		{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
 | 
						|
		{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func writeField(w io.Writer, element, class, field string) error {
 | 
						|
	if _, err := io.WriteString(w, "<"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := io.WriteString(w, element); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if len(class) > 0 {
 | 
						|
		if _, err := io.WriteString(w, " class=\""); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if _, err := io.WriteString(w, class); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if _, err := io.WriteString(w, "\""); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if _, err := io.WriteString(w, ">"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := io.WriteString(w, html.EscapeString(field)); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := io.WriteString(w, "</"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err := io.WriteString(w, element); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	_, err := io.WriteString(w, ">")
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// Render implements markup.Renderer
 | 
						|
func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
 | 
						|
	tmpBlock := bufio.NewWriter(output)
 | 
						|
	maxSize := setting.UI.CSV.MaxFileSize
 | 
						|
 | 
						|
	if maxSize == 0 {
 | 
						|
		return r.tableRender(ctx, input, tmpBlock)
 | 
						|
	}
 | 
						|
 | 
						|
	rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1))
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if int64(len(rawBytes)) <= maxSize {
 | 
						|
		return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock)
 | 
						|
	}
 | 
						|
	return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock)
 | 
						|
}
 | 
						|
 | 
						|
func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error {
 | 
						|
	_, err := tmpBlock.WriteString("<pre>")
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if err = scan.Err(); err != nil {
 | 
						|
		return fmt.Errorf("fallbackRender scan: %w", 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 {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	row := 1
 | 
						|
	for {
 | 
						|
		fields, err := rd.Read()
 | 
						|
		if err == io.EOF {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if _, err := tmpBlock.WriteString("<tr>"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		element := "td"
 | 
						|
		if row == 1 {
 | 
						|
			element = "th"
 | 
						|
		}
 | 
						|
		if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row)); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		for _, field := range fields {
 | 
						|
			if err := writeField(tmpBlock, element, "", field); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if _, err := tmpBlock.WriteString("</tr>"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		row++
 | 
						|
	}
 | 
						|
	if _, err = tmpBlock.WriteString("</table>"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return tmpBlock.Flush()
 | 
						|
}
 |