1 package server
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9 "os"
10 "path/filepath"
11 "strconv"
12 "strings"
13 "time"
14
15 "codeberg.org/tslocum/sriracha/internal/database"
16 . "codeberg.org/tslocum/sriracha/model"
17 . "codeberg.org/tslocum/sriracha/util"
18 "github.com/gabriel-vasile/mimetype"
19 )
20
21 func (s *Server) loadBannerFormFile(db *database.DB, r *http.Request, b *Banner) ([]byte, error) {
22 if r.PostForm == nil {
23 const maxMemory = 32 << 20
24 err := r.ParseMultipartForm(maxMemory)
25 if err != nil {
26 return nil, err
27 }
28 }
29 if r.MultipartForm == nil || r.MultipartForm.File == nil {
30 return nil, nil
31 }
32 files := r.MultipartForm.File["file"]
33 if len(files) == 0 {
34 return nil, nil
35 } else if len(files) > 1 {
36 return nil, fmt.Errorf("too many files: upload a single file")
37 }
38 fileHeader := files[0]
39
40 formFile, err := fileHeader.Open()
41 if err != nil {
42 return nil, err
43 }
44 defer formFile.Close()
45
46 buf, err := io.ReadAll(formFile)
47 if err != nil {
48 return nil, err
49 }
50
51 if b.Name == "" {
52 b.Name = strings.TrimSpace(fileHeader.Filename)
53 if b.Name == "" {
54 b.Name = fmt.Sprintf("%d", time.Now().Unix())
55 }
56 }
57
58 mimeType := mimetype.Detect(buf).String()
59 ext := MIMEToExt(mimeType)
60 if ext == "" {
61 return nil, fmt.Errorf("unknown file type: upload a JPG, PNG or GIF image")
62 }
63 fileExt := "." + ext
64 if !strings.HasSuffix(b.Name, fileExt) {
65 b.Name = strings.TrimSuffix(b.Name, filepath.Ext(b.Name)) + fileExt
66 }
67
68 imgWidth, imgHeight := s.imageDimensions(bytes.NewReader(buf))
69 if imgWidth == 0 || imgHeight == 0 {
70 return nil, fmt.Errorf("invalid image")
71 }
72 b.Width, b.Height = imgWidth, imgHeight
73
74 return buf, nil
75 }
76
77 func (s *Server) loadBannerForm(db *database.DB, r *http.Request, b *Banner) {
78 b.Name = FormString(r, "name")
79 b.Overboard = FormBool(r, "overboard")
80 b.News = FormBool(r, "news")
81 b.Pages = FormBool(r, "pages")
82
83 b.Boards = nil
84 boards := r.Form["boards"]
85 for _, board := range boards {
86 boardID, err := strconv.Atoi(board)
87 if err != nil || boardID <= 0 {
88 continue
89 }
90 board := db.BoardByID(boardID)
91 if board == nil {
92 continue
93 }
94 b.Boards = append(b.Boards, board)
95 }
96 }
97
98 func (s *Server) serveBanner(data *templateData, db *database.DB, w http.ResponseWriter, r *http.Request) {
99 var err error
100 data.Template = "manage_banner"
101 data.Boards = db.AllBoards()
102 data.Manage.Banner = &Banner{}
103
104 deleteBannerID := PathInt(r, "/sriracha/banner/delete/")
105 if deleteBannerID > 0 {
106 if s.forbidden(w, data, "banner.delete") {
107 return
108 }
109 b := db.BannerByID(deleteBannerID)
110 if b == nil {
111 data.ManageError("Invalid banner.")
112 return
113 }
114
115 bannerPath := filepath.Join(s.config.Root, "banner", b.Name)
116 os.Remove(bannerPath)
117
118 db.DeleteBanner(b.ID)
119 s.refreshBannerCache(db)
120 s.rebuildAll(db, false)
121
122 s.log(db, data.Account, nil, fmt.Sprintf("Deleted banner #%d", b.ID), "")
123
124 data.Redirect(w, r, "/sriracha/banner/")
125 return
126 }
127
128 bannerID, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/sriracha/banner/"))
129 if err == nil && bannerID > 0 {
130 if s.forbidden(w, data, "banner.update") {
131 return
132 }
133 data.Manage.Banner = db.BannerByID(bannerID)
134
135 if data.Manage.Banner != nil && r.Method == http.MethodPost {
136 oldName := data.Manage.Banner.Name
137 s.loadBannerForm(db, r, data.Manage.Banner)
138 buf, err := s.loadBannerFormFile(db, r, data.Manage.Banner)
139 if err != nil {
140 data.ManageError(err.Error())
141 return
142 }
143
144 err = data.Manage.Banner.Validate()
145 if err != nil {
146 data.ManageError(err.Error())
147 return
148 }
149
150 if data.Manage.Banner.Name != oldName {
151 if buf == nil && filepath.Ext(data.Manage.Banner.Name) != filepath.Ext(oldName) {
152 data.ManageError("File extension was modified in banner name: to change a banner's file extension, upload a new file")
153 return
154 }
155 match := db.BannerByName(data.Manage.Banner.Name)
156 if match != nil {
157 data.ManageError("Banner with that name already exists")
158 return
159 }
160 oldPath := filepath.Join(s.config.Root, "banner", oldName)
161 newPath := filepath.Join(s.config.Root, "banner", data.Manage.Banner.Name)
162 err = os.Rename(oldPath, newPath)
163 if err != nil {
164 log.Fatalf("failed to rename banner from %s to %s: %s", oldPath, newPath, err)
165 }
166 }
167
168 if buf != nil {
169 bannerPath := filepath.Join(s.config.Root, "banner", data.Manage.Banner.Name)
170 err = os.WriteFile(bannerPath, buf, NewFilePermission)
171 if err != nil {
172 log.Fatalf("failed to write banner image at %s: %s", bannerPath, err)
173 }
174 }
175
176 db.UpdateBanner(data.Manage.Banner)
177 s.refreshBannerCache(db)
178 s.rebuildAll(db, false)
179
180 s.log(db, data.Account, nil, fmt.Sprintf("Updated >>/banner/%d", data.Manage.Banner.ID), "")
181
182 data.Redirect(w, r, "/sriracha/banner/")
183 return
184 }
185 return
186 }
187
188 if r.Method == http.MethodPost {
189 if s.forbidden(w, data, "banner.add") {
190 return
191 }
192 b := &Banner{}
193 s.loadBannerForm(db, r, b)
194 buf, err := s.loadBannerFormFile(db, r, b)
195 if err != nil {
196 data.ManageError(err.Error())
197 return
198 } else if buf == nil {
199 data.ManageError("upload a file to add a banner")
200 return
201 }
202
203 err = b.Validate()
204 if err != nil {
205 data.ManageError(err.Error())
206 return
207 }
208
209 match := db.BannerByName(b.Name)
210 if match != nil {
211 data.ManageError("Banner with that name already exists")
212 return
213 }
214
215 bannerPath := filepath.Join(s.config.Root, "banner", b.Name)
216 err = os.WriteFile(bannerPath, buf, NewFilePermission)
217 if err != nil {
218 log.Fatalf("failed to write banner image at %s: %s", bannerPath, err)
219 }
220
221 db.AddBanner(b)
222 s.refreshBannerCache(db)
223 s.rebuildAll(db, false)
224
225 s.log(db, data.Account, nil, fmt.Sprintf("Added >>/banner/%d", b.ID), "")
226
227 data.Redirect(w, r, "/sriracha/banner/")
228 return
229 }
230
231 data.Manage.Banners = db.AllBanners()
232 }
233
View as plain text