diff --git a/api/api.go b/api/api.go index 8dedef7..745d661 100644 --- a/api/api.go +++ b/api/api.go @@ -40,7 +40,7 @@ func API(s store.Store, port int) { mux.HandleFunc("POST /contests/{id}/problems/new", apiCtx.createProblem) mux.HandleFunc("GET /contests/{id}/new-problem", apiCtx.createProblemView) mux.HandleFunc("GET /contests/{contestID}/problems/{problemID}", apiCtx.getProblemByID) - mux.HandleFunc("GET /contests/{contestID}/problems/{problemID}/pdf", apiCtx.getProblemStatementByID) + // mux.HandleFunc("GET /contests/{contestID}/problems/{problemID}/pdf", apiCtx.getProblemStatementByID) mux.HandleFunc("POST /contests/{contestID}/problems/{problemID}/submit", apiCtx.submitProgram) // mux.HandleFunc("GET /contests/{constestID}/problems/{problemID}/submit/{submitID}", auth.RequireAuth(apiCtx.getSubmissionByID)) diff --git a/api/problem.go b/api/problem.go index 39cfcc5..c090c6c 100644 --- a/api/problem.go +++ b/api/problem.go @@ -2,11 +2,17 @@ package api import ( "fmt" + "html/template" "io" "log" "net/http" "strconv" + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" + + "lidsol.org/papeador/security" "lidsol.org/papeador/store" ) @@ -69,6 +75,8 @@ func (api *ApiContext) createProblem(w http.ResponseWriter, r *http.Request) { return } + in.Description = []byte(r.FormValue("description")) + err = r.ParseMultipartForm(8 << 20) if err != nil { http.Error(w, "Los archivos deben ser, como máximo, de 8 MiB", http.StatusBadRequest) @@ -76,14 +84,6 @@ func (api *ApiContext) createProblem(w http.ResponseWriter, r *http.Request) { return } - description, err := getRequestFileContents(r, "description") - if err != nil { - http.Error(w, "Error al leer el enunciado", http.StatusInternalServerError) - log.Println("Error", err) - return - } - in.Description = description - inputs, err := getFileGroup(r, "inputs") if err != nil { http.Error(w, "Error al leer los archivos de entrada", http.StatusInternalServerError) @@ -186,6 +186,8 @@ func (api *ApiContext) getProblemByID(w http.ResponseWriter, r *http.Request) { } problem.SampleInputStr = string(problem.SampleInput) problem.SampleOutStr = string(problem.SampleOut) + problem.Description = security.SanitizeHtml(mdToHTML(problem.Description)) + problem.DescriptionHTML = template.HTML(string(problem.Description)) type pageInfo struct { store.Problem @@ -205,31 +207,16 @@ func (api *ApiContext) getProblemByID(w http.ResponseWriter, r *http.Request) { templates.ExecuteTemplate(w, "problem.html", &info) } -func (api *ApiContext) getProblemStatementByID(w http.ResponseWriter, r *http.Request) { - contestIDStr := r.PathValue("contestID") - contestID, err := strconv.Atoi(contestIDStr) - if err != nil { - http.Error(w, "Error en ruta", http.StatusBadRequest) - log.Println("Error", err) - return - } +func mdToHTML(md []byte) []byte { + // create markdown parser with extensions + extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.MathJax + p := parser.NewWithExtensions(extensions) + doc := p.Parse([]byte(md)) - problemIDStr := r.PathValue("problemID") - problemID, err := strconv.Atoi(problemIDStr) - if err != nil { - http.Error(w, "Error en ruta", http.StatusBadRequest) - log.Println("Error", err) - return - } - - problem, err := api.Store.GetProblemByIDs(r.Context(), contestID, problemID) - if err != nil { - http.Error(w, "Error al buscar problema", http.StatusBadRequest) - log.Println("Error", err) - return - } + // create HTML renderer with extensions + htmlFlags := html.CommonFlags | html.HrefTargetBlank + opts := html.RendererOptions{Flags: htmlFlags} + renderer := html.NewRenderer(opts) - w.Header().Set("Content-Type", "application/pdf") - w.WriteHeader(http.StatusOK) - w.Write(problem.Description) + return markdown.Render(doc, renderer) } diff --git a/go.mod b/go.mod index 05e2be6..94c6189 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/containers/podman/v5 v5.6.0 github.com/cristalhq/jwt/v5 v5.4.0 github.com/docker/docker v28.3.2+incompatible + github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab + github.com/microcosm-cc/bluemonday v1.0.27 golang.org/x/crypto v0.43.0 modernc.org/sqlite v1.38.2 ) @@ -21,6 +23,7 @@ require ( github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect @@ -58,6 +61,7 @@ require ( github.com/google/go-containerregistry v0.20.3 // indirect github.com/google/go-intervals v0.0.2 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/schema v1.4.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 0d7cbdd..35e1eb3 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -139,6 +141,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc= +github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -159,6 +163,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= @@ -202,6 +208,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU= diff --git a/schema.sql b/schema.sql index dc89191..8f84b54 100644 --- a/schema.sql +++ b/schema.sql @@ -43,7 +43,7 @@ create table if not exists problem ( creator_id integer not null, problem_name text not null, base_score integer not null, - description blob not null, + description text not null, constraint fk_creator foreign key (creator_id) references user(user_id) diff --git a/security/ensure.go b/security/ensure.go index 2f38adb..14b1eac 100644 --- a/security/ensure.go +++ b/security/ensure.go @@ -5,6 +5,8 @@ import ( "regexp" "strings" "errors" + + "github.com/microcosm-cc/bluemonday" ) var ( @@ -58,3 +60,7 @@ func ValidateEmail(email string) (string, error) { return strings.ToLower(email), nil } + +func SanitizeHtml(html []byte) []byte { + return bluemonday.UGCPolicy().SanitizeBytes(html); +} diff --git a/store/store.go b/store/store.go index c7ea8bb..08c7e00 100644 --- a/store/store.go +++ b/store/store.go @@ -3,6 +3,7 @@ package store import ( "context" "errors" + "html/template" ) var ( @@ -34,6 +35,7 @@ type Problem struct { ProblemName string `json:"problem_name"` BaseScore string `json:"base_score"` Description []byte `json:"description"` + DescriptionHTML template.HTML `json:"description_str"` SampleInput []byte `json:"sample_input"` SampleOut []byte `json:"sample_out"` SampleInputStr string `json:"sample_input_str"` diff --git a/templates/createProblem.html b/templates/createProblem.html index 623b3a3..7958856 100644 --- a/templates/createProblem.html +++ b/templates/createProblem.html @@ -7,6 +7,12 @@ + +

Añade un nuevo problema

@@ -18,9 +24,21 @@

Añade un nuevo problema

Limite de tiempo: -
- Enunciado: -
+
+
+ +
+ + + + +
Entrada: diff --git a/templates/problem.html b/templates/problem.html index 5411761..f8d38fa 100644 --- a/templates/problem.html +++ b/templates/problem.html @@ -48,7 +48,8 @@

{{.ProblemName}}

Enunciado del Problema
- + + {{.DescriptionHTML}}
@@ -83,6 +84,7 @@
Output
+