Gitea
3 years ago
15 changed files with 302 additions and 70 deletions
@ -1,22 +0,0 @@ |
|||||||
/* |
|
||||||
gochat - A dead simple real time webchat. |
|
||||||
Copyright (C) 2022 Kasyanov Nikolay Alexeyevich (Unbewohnte) |
|
||||||
This file is a part of gochat |
|
||||||
This program is free software: you can redistribute it and/or modify |
|
||||||
it under the terms of the GNU General Public License as published by |
|
||||||
the Free Software Foundation, either version 3 of the License, or |
|
||||||
(at your option) any later version. |
|
||||||
This program is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
GNU General Public License for more details. |
|
||||||
You should have received a copy of the GNU General Public License |
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
function to_hex_string(byteArr) { |
|
||||||
return Array.from(byteArr, function(byte) { |
|
||||||
return ('0' + (byte & 0xFF).toString(16).slice(-2)); |
|
||||||
}).join('') |
|
||||||
} |
|
@ -0,0 +1,7 @@ |
|||||||
|
package api |
||||||
|
|
||||||
|
const AttachmentFormPostKey string = "attachment" |
||||||
|
|
||||||
|
type PartialAttachmentURL struct { |
||||||
|
URL string `json:"url"` |
||||||
|
} |
@ -0,0 +1,115 @@ |
|||||||
|
package server |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"time" |
||||||
|
|
||||||
|
"unbewohnte.xyz/Unbewohnte/gochat/api" |
||||||
|
"unbewohnte.xyz/Unbewohnte/gochat/log" |
||||||
|
) |
||||||
|
|
||||||
|
// Handle incoming attachments
|
||||||
|
func (s *Server) handleAttachments(w http.ResponseWriter, req *http.Request) { |
||||||
|
userAuthHeader := api.GetUserAuthHeaderData(req) |
||||||
|
if !s.db.DoesUserExist(userAuthHeader.Name) { |
||||||
|
http.Error(w, "Not authorized", http.StatusUnauthorized) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
userDB, _ := s.db.GetUser(userAuthHeader.Name) |
||||||
|
if userDB.SecretHash != userAuthHeader.SecretHash { |
||||||
|
http.Error(w, "Not authorized", http.StatusUnauthorized) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// accept incoming file
|
||||||
|
file, header, err := req.FormFile(api.AttachmentFormPostKey) |
||||||
|
if err != nil { |
||||||
|
log.Error("could not get attached file: %s", err) |
||||||
|
http.Error(w, "Error getting attached file", http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
defer file.Close() |
||||||
|
|
||||||
|
if uint64(header.Size) > api.MaxAttachmentSize { |
||||||
|
http.Error(w, "Too big file", http.StatusBadRequest) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
localFilename := fmt.Sprintf("%s_%d_%s", userDB.Name, header.Size, header.Filename) |
||||||
|
localFile, err := os.Create(filepath.Join(s.workingDir, attachmentsDirName, localFilename)) |
||||||
|
if err != nil { |
||||||
|
log.Error("could not create local attachment file: %s", err) |
||||||
|
http.Error(w, "Could not create file", http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
defer localFile.Close() |
||||||
|
|
||||||
|
_, err = io.Copy(localFile, file) |
||||||
|
if err != nil { |
||||||
|
log.Error("could not copy attachment file contents: %s", err) |
||||||
|
http.Error(w, "Could not copy file contents", http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// send partial URL pointing to the file
|
||||||
|
url := api.PartialAttachmentURL{ |
||||||
|
URL: fmt.Sprintf("%s/%s", attachmentsDirName, localFilename), |
||||||
|
} |
||||||
|
urlJsonBytes, err := json.Marshal(&url) |
||||||
|
if err != nil { |
||||||
|
log.Error("could not marshal partial attachment URL for \"%s\": %s", localFilename, err) |
||||||
|
http.Error(w, "Error constructing a request", http.StatusInternalServerError) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
w.Write(urlJsonBytes) |
||||||
|
} |
||||||
|
|
||||||
|
// Remove the oldest attachments when the memory limit is exceeded
|
||||||
|
func manageAttachmentsStorage(attachmentsDirPath string, sizeLimit uint64, checkDelay time.Duration) { |
||||||
|
for { |
||||||
|
dirEntries, err := os.ReadDir(attachmentsDirPath) |
||||||
|
if err != nil { |
||||||
|
log.Error("error reading attachments directory: %s", err) |
||||||
|
} |
||||||
|
|
||||||
|
var dirSize uint64 = 0 |
||||||
|
var oldestAttachmentModTime time.Time = time.Now() |
||||||
|
var oldestAttachmentPath string = "" |
||||||
|
var oldestAttachmentSize uint64 = 0 |
||||||
|
if dirEntries != nil { |
||||||
|
for _, entry := range dirEntries { |
||||||
|
entryInfo, err := entry.Info() |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
entrySize := entryInfo.Size() |
||||||
|
dirSize += uint64(entrySize) |
||||||
|
|
||||||
|
entryModTime := entryInfo.ModTime() |
||||||
|
if entryModTime.Before(oldestAttachmentModTime) { |
||||||
|
oldestAttachmentModTime = entryModTime |
||||||
|
oldestAttachmentPath = filepath.Join(attachmentsDirPath, entry.Name()) |
||||||
|
oldestAttachmentSize = uint64(entrySize) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if dirSize > sizeLimit { |
||||||
|
// A cleanup !
|
||||||
|
os.Remove(oldestAttachmentPath) |
||||||
|
log.Info( |
||||||
|
"removed %s during attachments storage management. Cleared %d bytes", |
||||||
|
oldestAttachmentPath, oldestAttachmentSize) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
time.Sleep(checkDelay) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue