From b26bd10926447ed59cbf263aef087bb7c04f35eb Mon Sep 17 00:00:00 2001 From: Alexander Neonxp Kiryukhin Date: Sun, 20 Oct 2024 20:38:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20/x?= =?UTF-8?q?/file*=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20Dockerfil?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 23 +++++++++++++ etc/node.yaml | 3 ++ files/priv/.gitkeep | 1 + files/pub/.gitkeep | 1 + files/usr/.gitkeep | 1 + pkg/api/api.go | 8 +++-- pkg/api/file.go | 51 +++++++++++++++++++++++++++++ pkg/api/list.go | 11 +++++-- pkg/api/message.go | 15 ++++++--- pkg/config/config.go | 13 ++++---- pkg/fetcher/fetcher.go | 19 +++++++++++ pkg/idec/blacklist.go | 65 +++++++++++++++++++++++++++++++++++++ pkg/idec/files.go | 74 ++++++++++++++++++++++++++++++++++++++++++ pkg/idec/idec.go | 1 + pkg/idec/point.go | 4 +-- pkg/model/file.go | 7 ++++ 16 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 Dockerfile create mode 100644 files/priv/.gitkeep create mode 100644 files/pub/.gitkeep create mode 100644 files/usr/.gitkeep create mode 100644 pkg/api/file.go create mode 100644 pkg/idec/blacklist.go create mode 100644 pkg/idec/files.go create mode 100644 pkg/model/file.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0edaa5e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# Build backend +FROM golang:1.23.2-alpine3.20 AS backend +ARG VERSION +WORKDIR /srv +RUN apk update --no-cache && apk add --no-cache tzdata +COPY go.mod go.sum ./ +RUN go mod download && go mod verify +COPY . . +RUN go build -o app . + +# Runtime container +FROM alpine:3.20 +WORKDIR /srv +RUN apk update --no-cache && apk add --no-cache ca-certificates + +COPY --from=backend /usr/share/zoneinfo/Europe/Moscow /usr/share/zoneinfo/Europe/Moscow +COPY --from=backend /srv/app /srv/app + +ENV TZ=Europe/Moscow + +EXPOSE 8000 + +ENTRYPOINT ["/srv/app"] \ No newline at end of file diff --git a/etc/node.yaml b/etc/node.yaml index f0d86de..0e3c2d4 100644 --- a/etc/node.yaml +++ b/etc/node.yaml @@ -2,6 +2,7 @@ listen: :8000 store: ./store.db node: iinet.ru logger_type: 3 + echos: node.local: description: Локалка @@ -47,3 +48,5 @@ fetch: - music.14 - std.english - difrex.blog + +files_directory: files \ No newline at end of file diff --git a/files/priv/.gitkeep b/files/priv/.gitkeep new file mode 100644 index 0000000..76a7f05 --- /dev/null +++ b/files/priv/.gitkeep @@ -0,0 +1 @@ +Private node files \ No newline at end of file diff --git a/files/pub/.gitkeep b/files/pub/.gitkeep new file mode 100644 index 0000000..a0362e8 --- /dev/null +++ b/files/pub/.gitkeep @@ -0,0 +1 @@ +Public node files \ No newline at end of file diff --git a/files/usr/.gitkeep b/files/usr/.gitkeep new file mode 100644 index 0000000..90116bd --- /dev/null +++ b/files/usr/.gitkeep @@ -0,0 +1 @@ +Users node files \ No newline at end of file diff --git a/pkg/api/api.go b/pkg/api/api.go index 6bbdd30..fd55f1d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -30,15 +30,19 @@ func (a *API) Run(ctx context.Context) error { mux := http.NewServeMux() mux.HandleFunc(`GET /list.txt`, a.getListHandler) - mux.HandleFunc(`GET /blacklist.txt`, a.getBlacklistTxtHandler) + mux.HandleFunc(`GET /blacklist.txt`, a.getBlacklistHandler) mux.HandleFunc(`GET /u/e/{ids...}`, a.getEchosHandler) mux.HandleFunc(`GET /u/m/{ids...}`, a.getBundleHandler) - mux.HandleFunc(`GET /u/point/{pauth}/{tmsg}`, a.getPointHandler) + mux.HandleFunc(`GET /u/point/{pauth}/{tmsg}`, a.postPointHandler) mux.HandleFunc(`POST /u/point`, a.postPointHandler) mux.HandleFunc(`GET /m/{msgID}`, a.getMessageHandler) mux.HandleFunc(`GET /e/{id}`, a.getEchoHandler) mux.HandleFunc(`GET /x/features`, a.getFeaturesHandler) mux.HandleFunc(`GET /x/c/{ids...}`, a.getEchosInfo) + mux.HandleFunc(`POST /x/filelist`, a.getFilelistHandler) + mux.HandleFunc(`GET /x/filelist/{pauth}`, a.getFilelistHandler) + mux.HandleFunc(`POST /x/file`, a.getFileHandler) + mux.HandleFunc(`GET /x/file/{filename}`, a.getFileHandler) srv := http.Server{ Addr: a.config.Listen, diff --git a/pkg/api/file.go b/pkg/api/file.go new file mode 100644 index 0000000..e2fa8d9 --- /dev/null +++ b/pkg/api/file.go @@ -0,0 +1,51 @@ +package api + +import ( + "fmt" + "net/http" +) + +func (a *API) getFilelistHandler(w http.ResponseWriter, r *http.Request) { + pauth := r.PathValue("pauth") + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + + return + } + form := r.PostForm + if form.Has("pauth") { + pauth = form.Get("pauth") + } + + files, err := a.idec.FilesList(pauth) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + + return + } + + for _, file := range files { + fmt.Fprintf(w, "%s:%d:%s\n", file.FullName, file.Size, file.Name) + } +} + +func (a *API) getFileHandler(w http.ResponseWriter, r *http.Request) { + filename := r.PathValue("filename") + pauth := "" + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + + return + } + form := r.PostForm + if form.Has("pauth") { + pauth = form.Get("pauth") + } + if form.Has("filename") { + filename = form.Get("filename") + } + + if err := a.idec.GetFile(pauth, filename, w); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } +} diff --git a/pkg/api/list.go b/pkg/api/list.go index 812bb0f..80a34ea 100644 --- a/pkg/api/list.go +++ b/pkg/api/list.go @@ -3,6 +3,7 @@ package api import ( "fmt" "net/http" + "strings" ) func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) { @@ -17,6 +18,12 @@ func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) { } } -func (a *API) getBlacklistTxtHandler(w http.ResponseWriter, r *http.Request) { - // TODO +func (a *API) getBlacklistHandler(w http.ResponseWriter, r *http.Request) { + list, err := a.idec.GetBlacklist() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Fprint(w, strings.Join(list, "\n")) } diff --git a/pkg/api/message.go b/pkg/api/message.go index 1c28285..81e863f 100644 --- a/pkg/api/message.go +++ b/pkg/api/message.go @@ -35,16 +35,21 @@ func (a *API) getMessageHandler(w http.ResponseWriter, r *http.Request) { } func (a *API) postPointHandler(w http.ResponseWriter, r *http.Request) { + msg, pauth := r.PathValue("tmsg"), r.PathValue("pauth") if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - form := r.PostForm - a.savePointMessage(w, form.Get("tmsg"), form.Get("pauth")) -} -func (a *API) getPointHandler(w http.ResponseWriter, r *http.Request) { - a.savePointMessage(w, r.PathValue("tmsg"), r.PathValue("pauth")) + form := r.PostForm + if form.Has("tmsg") { + msg = form.Get("tmsg") + } + if form.Has("pauth") { + pauth = form.Get("pauth") + } + + a.savePointMessage(w, msg, pauth) } func (a *API) savePointMessage(w http.ResponseWriter, rawMessage, auth string) error { diff --git a/pkg/config/config.go b/pkg/config/config.go index 0c967fd..9fd58de 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -7,12 +7,13 @@ import ( ) type Config struct { - Listen string `yaml:"listen"` - Node string `yaml:"node"` - Store string `yaml:"store"` - LoggerType int `yaml:"logger_type"` - Echos map[string]Echo `yaml:"echos"` - Fetch []Node `yaml:"fetch"` + Listen string `yaml:"listen"` + Node string `yaml:"node"` + Store string `yaml:"store"` + LoggerType int `yaml:"logger_type"` + Echos map[string]Echo `yaml:"echos"` + Fetch []Node `yaml:"fetch"` + FilesDirectory string `yaml:"files_directory"` } type Node struct { diff --git a/pkg/fetcher/fetcher.go b/pkg/fetcher/fetcher.go index 0a79a70..5083889 100644 --- a/pkg/fetcher/fetcher.go +++ b/pkg/fetcher/fetcher.go @@ -43,6 +43,9 @@ func (f *Fetcher) Run(ctx context.Context) error { if err := f.downloadMessages(node, messagesToDownloads); err != nil { return err } + if err := f.downloadBlacklist(node); err != nil { + return err + } } log.Println("finished") return nil @@ -133,6 +136,22 @@ func (f *Fetcher) downloadMessagesChunk(node config.Node, messages []string) err return nil } +func (f *Fetcher) downloadBlacklist(node config.Node) error { + p := formatCommand(node, "blacklist.txt") + resp, err := f.client.Get(p) + if err != nil { + return err + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + lines := strings.Split(string(data), "\n") + + return f.idec.MergeBlacklist(lines) +} + func formatCommand(node config.Node, method string, args ...string) string { segments := []string{node.Addr, method} segments = append(segments, args...) diff --git a/pkg/idec/blacklist.go b/pkg/idec/blacklist.go new file mode 100644 index 0000000..a74f541 --- /dev/null +++ b/pkg/idec/blacklist.go @@ -0,0 +1,65 @@ +package idec + +import ( + "log" + + "gitrepo.ru/neonxp/idecnode/pkg/model" + "go.etcd.io/bbolt" +) + +const blacklistMessage = "<Удалено по черному списку>" + +func (i *IDEC) GetBlacklist() ([]string, error) { + list := []string{} + + return list, i.db.View(func(tx *bbolt.Tx) error { + bucket := tx.Bucket([]byte(blacklist)) + if bucket == nil { + return nil + } + + return bucket.ForEach(func(k, _ []byte) error { + list = append(list, string(k)) + + return nil + }) + }) +} + +func (i *IDEC) MergeBlacklist(list []string) error { + return i.db.Update(func(tx *bbolt.Tx) error { + bucket, err := tx.CreateBucketIfNotExists([]byte(blacklist)) + if err != nil { + return err + } + messages, err := tx.CreateBucketIfNotExists([]byte(msgBucket)) + if err != nil { + return err + } + + for _, k := range list { + if k == "" { + continue + } + if err := bucket.Put([]byte(k), []byte{}); err != nil { + return err + } + msgBytes := messages.Get([]byte(k)) + if msgBytes == nil { + continue + } + msg, err := model.MessageFromBundle(k, string(msgBytes)) + if err != nil { + log.Println(err) + continue + } + msg.Subject = blacklistMessage + msg.Message = blacklistMessage + if err := messages.Put([]byte(k), []byte(msg.Bundle())); err != nil { + log.Println(err) + } + } + + return nil + }) +} diff --git a/pkg/idec/files.go b/pkg/idec/files.go new file mode 100644 index 0000000..8fd73b4 --- /dev/null +++ b/pkg/idec/files.go @@ -0,0 +1,74 @@ +package idec + +import ( + "errors" + "io" + "os" + "path/filepath" + "strings" + + "gitrepo.ru/neonxp/idecnode/pkg/model" +) + +var ErrFileNotAllowed = errors.New("file not allowed") + +func (i *IDEC) FilesList(pauth string) ([]model.File, error) { + fileDirs := i.allowedDirs(pauth) + res := []model.File{} + for _, dir := range fileDirs { + files, err := filepath.Glob(dir + "/*") + if err != nil { + return nil, err + } + for _, f := range files { + fi, err := os.Stat(f) + if err != nil { + return nil, err + } + + res = append(res, model.File{ + Name: fi.Name(), + Size: fi.Size(), + FullName: strings.TrimPrefix(f, i.config.FilesDirectory), + }) + } + } + + return res, nil +} + +func (i *IDEC) GetFile(pauth string, path string, w io.Writer) error { + file := filepath.Clean(filepath.Join(i.config.FilesDirectory, path)) + allowed := false + allowedDirs := i.allowedDirs(pauth) + for _, dir := range allowedDirs { + if filepath.HasPrefix(file, dir) { + allowed = true + } + } + if !allowed { + return ErrFileNotAllowed + } + + fp, err := os.Open(file) + if err != nil { + return err + } + defer fp.Close() + + _, err = io.Copy(w, fp) + + return err +} + +func (i *IDEC) allowedDirs(pauth string) []string { + fileDirs := []string{ + filepath.Join(i.config.FilesDirectory, "pub"), + } + if pauth != "" { + if _, err := i.GetPointByAuth(pauth); err == nil { + fileDirs = append(fileDirs, filepath.Join(i.config.FilesDirectory, "priv")) + } + } + return fileDirs +} diff --git a/pkg/idec/idec.go b/pkg/idec/idec.go index 0692d65..d50c200 100644 --- a/pkg/idec/idec.go +++ b/pkg/idec/idec.go @@ -20,6 +20,7 @@ var ( const ( msgBucket = "_msg" points = "_points" + blacklist = "_blacklist" ) type IDEC struct { diff --git a/pkg/idec/point.go b/pkg/idec/point.go index 897c1bb..74bfc47 100644 --- a/pkg/idec/point.go +++ b/pkg/idec/point.go @@ -13,7 +13,7 @@ import ( var errPointFound = errors.New("point found") -func (i *IDEC) GetPointByAuth(auth string) (*model.Point, error) { +func (i *IDEC) GetPointByAuth(pauth string) (*model.Point, error) { point := new(model.Point) return point, i.db.View(func(tx *bbolt.Tx) error { @@ -25,7 +25,7 @@ func (i *IDEC) GetPointByAuth(auth string) (*model.Point, error) { if err := gob.NewDecoder(bytes.NewBuffer(v)).Decode(point); err != nil { return err } - if point.AuthString == auth { + if point.AuthString == pauth { return errPointFound } diff --git a/pkg/model/file.go b/pkg/model/file.go new file mode 100644 index 0000000..d87f581 --- /dev/null +++ b/pkg/model/file.go @@ -0,0 +1,7 @@ +package model + +type File struct { + Name string + Size int64 + FullName string +}