| @@ -7,6 +7,7 @@ import ( |
| 7 | 7 | "path/filepath" |
| 8 | 8 | "strings" |
| 9 | 9 | |
| 10 | "gitbay.org/gitbay/internal/policy" |
| 10 | 11 | "gitbay.org/gitbay/internal/store" |
| 11 | 12 | ) |
| 12 | 13 | |
| @@ -14,17 +15,17 @@ import ( |
| 14 | 15 | // command the CLI runs; who may do it is the command's decision, and the |
| 15 | 16 | // pages only show the forms to those it will accept. |
| 16 | 17 | |
| 17 | | // milestoneCreateArgs builds the argv tail for create: the title, and the |
| 18 | | // description and due date when given. |
| 19 | | func milestoneCreateArgs(r *http.Request, head []string) []string { |
| 20 | | argv := append(head, strings.TrimSpace(r.FormValue("title"))) |
| 18 | // milestoneCreateArgs builds the create argv: the flags, then the |
| 19 | // positionals after "--" so a title starting with "-" is not a flag. |
| 20 | func milestoneCreateArgs(r *http.Request, head []string, target string) []string { |
| 21 | argv := head |
| 21 | 22 | if d := strings.TrimSpace(r.FormValue("description")); d != "" { |
| 22 | 23 | argv = append(argv, "--description", d) |
| 23 | 24 | } |
| 24 | 25 | if d := strings.TrimSpace(r.FormValue("due")); d != "" { |
| 25 | 26 | argv = append(argv, "--due", d) |
| 26 | 27 | } |
| 27 | | return argv |
| 28 | return append(argv, "--", target, strings.TrimSpace(r.FormValue("title"))) |
| 28 | 29 | } |
| 29 | 30 | |
| 30 | 31 | func (s *Server) milestoneSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| @@ -42,7 +43,7 @@ func (s *Server) milestoneSubmit(w http.ResponseWriter, r *http.Request, u store |
| 42 | 43 | case "reopen": |
| 43 | 44 | argv = []string{"milestone", "reopen", repo, title} |
| 44 | 45 | default: |
| 45 | | argv = milestoneCreateArgs(r, []string{"milestone", "create", repo}) |
| 46 | argv = milestoneCreateArgs(r, []string{"milestone", "create"}, repo) |
| 46 | 47 | } |
| 47 | 48 | _, msg, code := s.runControlCode(u, argv) |
| 48 | 49 | s.done(w, r, code, msg, back) |
| @@ -61,7 +62,7 @@ func (s *Server) orgLabelSubmit(w http.ResponseWriter, r *http.Request, u store. |
| 61 | 62 | back(w, r, "name the label") |
| 62 | 63 | return |
| 63 | 64 | } |
| 64 | | argv := []string{"org", "label", "set", org, name, "--color", strings.TrimSpace(r.FormValue("color"))} |
| 65 | argv := []string{"org", "label", "set", "--color", strings.TrimSpace(r.FormValue("color")), "--", org, name} |
| 65 | 66 | if r.FormValue("action") == "remove" { |
| 66 | 67 | if ok, msg := confirmed(r, name); !ok { |
| 67 | 68 | back(w, r, msg) |
| @@ -88,7 +89,7 @@ func (s *Server) orgMilestoneSubmit(w http.ResponseWriter, r *http.Request, u st |
| 88 | 89 | case "reopen": |
| 89 | 90 | argv = []string{"org", "milestone", "reopen", org, title} |
| 90 | 91 | default: |
| 91 | | argv = milestoneCreateArgs(r, []string{"org", "milestone", "create", org}) |
| 92 | argv = milestoneCreateArgs(r, []string{"org", "milestone", "create"}, org) |
| 92 | 93 | } |
| 93 | 94 | _, msg, code := s.runControlCode(u, argv) |
| 94 | 95 | s.done(w, r, code, msg, back) |
| @@ -103,7 +104,35 @@ func (s *Server) orgMilestoneSubmit(w http.ResponseWriter, r *http.Request, u st |
| 103 | 104 | func (s *Server) releaseAssetSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| 104 | 105 | repo := r.PathValue("owner") + "/" + r.PathValue("repo") |
| 105 | 106 | back := func(w http.ResponseWriter, r *http.Request, msg string) { s.backTo(w, r, "releases", msg) } |
| 107 | // Authorise before reading a byte: a body nobody may upload is never |
| 108 | // spooled to disk. |
| 109 | rp, err := s.st.RepoByPath(repo) |
| 110 | if err == nil { |
| 111 | grant, _ := s.st.AccessRole(rp.ID, u.ID) |
| 112 | if !policyCanRead(u, rp, grant) { |
| 113 | err = store.ErrNotFound |
| 114 | } else if !policy.CanWrite(u, rp, grant) { |
| 115 | back(w, r, "you need write access to change releases") |
| 116 | return |
| 117 | } else if rp.Settings.Archived { |
| 118 | back(w, r, rp.Path()+" is archived and read-only; unarchive it first") |
| 119 | return |
| 120 | } |
| 121 | } |
| 122 | if err != nil { |
| 123 | s.notFound(w, r) |
| 124 | return |
| 125 | } |
| 106 | 126 | limit := s.cfg.Limits.MaxAssetBytes |
| 127 | if r.ContentLength > limit+1<<20 { |
| 128 | back(w, r, fmt.Sprintf("asset exceeds max_asset_bytes (%d)", limit)) |
| 129 | return |
| 130 | } |
| 131 | if _, busy := s.uploads.LoadOrStore(u.ID, struct{}{}); busy { |
| 132 | back(w, r, "another upload of yours is still running; wait for it to finish") |
| 133 | return |
| 134 | } |
| 135 | defer s.uploads.Delete(u.ID) |
| 107 | 136 | r.Body = http.MaxBytesReader(w, r.Body, limit+1<<20) |
| 108 | 137 | if err := r.ParseMultipartForm(1 << 20); err != nil && !errors.Is(err, http.ErrNotMultipart) { |
| 109 | 138 | var tooBig *http.MaxBytesError |