| @@ -7,6 +7,7 @@ import ( |
| 7 | "path/filepath" |
7 | "path/filepath" |
| 8 | "strings" |
8 | "strings" |
| 9 | |
9 | |
| |
10 | "gitbay.org/gitbay/internal/policy" |
| 10 | "gitbay.org/gitbay/internal/store" |
11 | "gitbay.org/gitbay/internal/store" |
| 11 | ) |
12 | ) |
| 12 | |
13 | |
| @@ -14,17 +15,17 @@ import ( |
| 14 | // command the CLI runs; who may do it is the command's decision, and the |
15 | // command the CLI runs; who may do it is the command's decision, and the |
| 15 | // pages only show the forms to those it will accept. |
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 | // milestoneCreateArgs builds the create argv: the flags, then the |
| 18 | // description and due date when given. |
19 | // positionals after "--" so a title starting with "-" is not a flag. |
| 19 | func milestoneCreateArgs(r *http.Request, head []string) []string { |
20 | func milestoneCreateArgs(r *http.Request, head []string, target string) []string { |
| 20 | argv := append(head, strings.TrimSpace(r.FormValue("title"))) |
21 | argv := head |
| 21 | if d := strings.TrimSpace(r.FormValue("description")); d != "" { |
22 | if d := strings.TrimSpace(r.FormValue("description")); d != "" { |
| 22 | argv = append(argv, "--description", d) |
23 | argv = append(argv, "--description", d) |
| 23 | } |
24 | } |
| 24 | if d := strings.TrimSpace(r.FormValue("due")); d != "" { |
25 | if d := strings.TrimSpace(r.FormValue("due")); d != "" { |
| 25 | argv = append(argv, "--due", d) |
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 | func (s *Server) milestoneSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
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 | case "reopen": |
43 | case "reopen": |
| 43 | argv = []string{"milestone", "reopen", repo, title} |
44 | argv = []string{"milestone", "reopen", repo, title} |
| 44 | default: |
45 | default: |
| 45 | argv = milestoneCreateArgs(r, []string{"milestone", "create", repo}) |
46 | argv = milestoneCreateArgs(r, []string{"milestone", "create"}, repo) |
| 46 | } |
47 | } |
| 47 | _, msg, code := s.runControlCode(u, argv) |
48 | _, msg, code := s.runControlCode(u, argv) |
| 48 | s.done(w, r, code, msg, back) |
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 | back(w, r, "name the label") |
62 | back(w, r, "name the label") |
| 62 | return |
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 | if r.FormValue("action") == "remove" { |
66 | if r.FormValue("action") == "remove" { |
| 66 | if ok, msg := confirmed(r, name); !ok { |
67 | if ok, msg := confirmed(r, name); !ok { |
| 67 | back(w, r, msg) |
68 | back(w, r, msg) |
| @@ -88,7 +89,7 @@ func (s *Server) orgMilestoneSubmit(w http.ResponseWriter, r *http.Request, u st |
| 88 | case "reopen": |
89 | case "reopen": |
| 89 | argv = []string{"org", "milestone", "reopen", org, title} |
90 | argv = []string{"org", "milestone", "reopen", org, title} |
| 90 | default: |
91 | default: |
| 91 | argv = milestoneCreateArgs(r, []string{"org", "milestone", "create", org}) |
92 | argv = milestoneCreateArgs(r, []string{"org", "milestone", "create"}, org) |
| 92 | } |
93 | } |
| 93 | _, msg, code := s.runControlCode(u, argv) |
94 | _, msg, code := s.runControlCode(u, argv) |
| 94 | s.done(w, r, code, msg, back) |
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 | func (s *Server) releaseAssetSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
104 | func (s *Server) releaseAssetSubmit(w http.ResponseWriter, r *http.Request, u store.User) { |
| 104 | repo := r.PathValue("owner") + "/" + r.PathValue("repo") |
105 | repo := r.PathValue("owner") + "/" + r.PathValue("repo") |
| 105 | back := func(w http.ResponseWriter, r *http.Request, msg string) { s.backTo(w, r, "releases", msg) } |
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 | limit := s.cfg.Limits.MaxAssetBytes |
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 | r.Body = http.MaxBytesReader(w, r.Body, limit+1<<20) |
136 | r.Body = http.MaxBytesReader(w, r.Body, limit+1<<20) |
| 108 | if err := r.ParseMultipartForm(1 << 20); err != nil && !errors.Is(err, http.ErrNotMultipart) { |
137 | if err := r.ParseMultipartForm(1 << 20); err != nil && !errors.Is(err, http.ErrNotMultipart) { |
| 109 | var tooBig *http.MaxBytesError |
138 | var tooBig *http.MaxBytesError |