Compare commits
	
		
			2 commits
		
	
	
		
			
				ff92e30232
			
			...
			
				3a7d82a396
			
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							
							
								
							
							
	
	
		
			
		
	
	3a7d82a396 | 
						
						
							|||
| 
							
							
								
							
							
	
	
		
			
		
	
	70d3892b15 | 
						
						
							
					 9 changed files with 517 additions and 356 deletions
				
			
		
							
								
								
									
										45
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										45
									
								
								Makefile
									
										
									
									
									
								
							| 
						 | 
					@ -16,41 +16,30 @@ build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build_linux_amd64:
 | 
					build_linux_amd64:
 | 
				
			||||||
	cd src \
 | 
						cd src \
 | 
				
			||||||
	&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-${APPVERSION}-linux-amd64 cmd/collerd/main.go \
 | 
						&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-linux-amd64 cmd/collerd/main.go \
 | 
				
			||||||
	&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-${APPVERSION}-linux-amd64 cmd/coller/main.go \
 | 
						&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-linux-amd64 cmd/coller/main.go \
 | 
				
			||||||
	&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-${APPVERSION}-linux-amd64 cmd/copier/main.go
 | 
						&& GOOS=linux GOARCH=amd64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-linux-amd64 cmd/copier/main.go
 | 
				
			||||||
 | 
					 | 
				
			||||||
archive_linux_amd64:
 | 
					 | 
				
			||||||
	mkdir -p releases/coller-${APPVERSION}-linux-amd64 \
 | 
					 | 
				
			||||||
	&& cp bin/collerd-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/collerd \
 | 
					 | 
				
			||||||
	&& cp bin/coller-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/coller \
 | 
					 | 
				
			||||||
	&& cp bin/copier-${APPVERSION}-linux-amd64 releases/coller-${APPVERSION}-linux-amd64/copier \
 | 
					 | 
				
			||||||
	&& cd releases/ \
 | 
					 | 
				
			||||||
	&& tar cvpzf coller-${APPVERSION}-linux-amd64.tar.gz coller-${APPVERSION}-linux-amd64
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
build_darwin_arm64:
 | 
					build_darwin_arm64:
 | 
				
			||||||
	cd src \
 | 
						cd src \
 | 
				
			||||||
	&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-${APPVERSION}-darwin-arm64 cmd/collerd/main.go \
 | 
						&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/collerd-darwin-arm64 cmd/collerd/main.go \
 | 
				
			||||||
	&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-${APPVERSION}-darwin-arm64 cmd/coller/main.go \
 | 
						&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/coller-darwin-arm64 cmd/coller/main.go \
 | 
				
			||||||
	&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-${APPVERSION}-darwin-arm64 cmd/copier/main.go
 | 
						&& GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" -o ../bin/copier-darwin-arm64 cmd/copier/main.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
archive_darwin_arm64:
 | 
					create_release:
 | 
				
			||||||
	mkdir -p releases/coller-${APPVERSION}-darwin-arm64 \
 | 
						mkdir -p releases/${APPVERSION} \
 | 
				
			||||||
	&& cp bin/collerd-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/collerd \
 | 
						&& cp -p bin/collerd-linux-amd64 releases/${APPVERSION}/collerd-linux-amd64 \
 | 
				
			||||||
	&& cp bin/coller-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/coller \
 | 
						&& cp -p bin/coller-linux-amd64 releases/${APPVERSION}/coller-linux-amd64 \
 | 
				
			||||||
	&& cp bin/copier-${APPVERSION}-darwin-arm64 releases/coller-${APPVERSION}-darwin-arm64/copier \
 | 
						&& cp -p bin/copier-linux-amd64 releases/${APPVERSION}/copier-linux-amd64 \
 | 
				
			||||||
	&& cd releases/ \
 | 
						&& cp -p bin/collerd-darwin-arm64 releases/${APPVERSION}/collerd-darwin-arm64 \
 | 
				
			||||||
	&& tar cvpzf coller-${APPVERSION}-darwin-arm64.tar.gz coller-${APPVERSION}-darwin-arm64
 | 
						&& cp -p bin/coller-darwin-arm64 releases/${APPVERSION}/coller-darwin-arm64 \
 | 
				
			||||||
 | 
						&& cp -p bin/copier-darwin-arm64 releases/${APPVERSION}/copier-darwin-arm64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
checksum:
 | 
					checksum:
 | 
				
			||||||
	cd releases \
 | 
						cd releases/${APPVERSION} \
 | 
				
			||||||
	&& sha256sum *.tar.gz > checksums.txt
 | 
						&& sha256sum collerd-* coller-* copier-* > checksums.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
clean_for_releases:
 | 
					releases: build_linux_amd64 build_darwin_arm64 create_release checksum
 | 
				
			||||||
	rm -rf releases/coller-${APPVERSION}-linux-amd64 \
 | 
					 | 
				
			||||||
	&& rm -rf releases/coller-${APPVERSION}-darwin-arm64
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
releases: build_linux_amd64 build_darwin_arm64 archive_linux_amd64 archive_darwin_arm64 checksum clean_for_releases
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
releases_with_docker:
 | 
					releases_with_docker:
 | 
				
			||||||
	docker run -it -v $(shell pwd):/mnt -w /mnt -e "UID=$(shell id -u)" -e "GID=$(shell id -g)" ${DOCKER_IMAGE} ./docker/build.sh
 | 
						docker run -it -v $(shell pwd):/mnt -w /mnt -e "UID=$(shell id -u)" -e "GID=$(shell id -g)" ${DOCKER_IMAGE} ./docker/build.sh
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,4 +17,4 @@ apt-get install -y libx11-dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
make releases
 | 
					make releases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
chown ${UID}:${GID} -R releases
 | 
					chown ${UID}:${GID} -R bin releases
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,3 +117,12 @@ func ToLowerStringSlice(src []string) (dst []string) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func InSlice(s []string, elem string) bool {
 | 
				
			||||||
 | 
						for _, v := range s {
 | 
				
			||||||
 | 
							if v == elem {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,3 +85,25 @@ func TestToLowerStringsSlice(t *testing.T) {
 | 
				
			||||||
		t.Logf("got '%s', want '%s'", got, expected)
 | 
							t.Logf("got '%s', want '%s'", got, expected)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestInSlice(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							elem     string
 | 
				
			||||||
 | 
							s        []string
 | 
				
			||||||
 | 
							expected bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{"linux", []string{"linux", "darwin"}, true},
 | 
				
			||||||
 | 
							{"windows", []string{"linux", "darwin"}, false},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range tests {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprintf("TestInSlice#%s", tc.elem), func(t *testing.T) {
 | 
				
			||||||
 | 
								got := InSlice(tc.s, tc.elem)
 | 
				
			||||||
 | 
								if got != tc.expected {
 | 
				
			||||||
 | 
									t.Errorf("got '%t', want '%t'", got, tc.expected)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									t.Logf("got '%t', want '%t'", got, tc.expected)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										149
									
								
								src/server/handlers_api.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/server/handlers_api.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,149 @@
 | 
				
			||||||
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.riou.xyz/jriou/coller/internal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HealthHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						fmt.Fprintf(w, "OK")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CreateNoteHandler struct {
 | 
				
			||||||
 | 
						logger        *slog.Logger
 | 
				
			||||||
 | 
						db            *Database
 | 
				
			||||||
 | 
						maxUploadSize int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CreateNotePayload struct {
 | 
				
			||||||
 | 
						Content         string `json:"content"`
 | 
				
			||||||
 | 
						Password        string `json:"password"`
 | 
				
			||||||
 | 
						Encrypted       bool   `json:"encrypted"`
 | 
				
			||||||
 | 
						Expiration      int    `json:"expiration"`
 | 
				
			||||||
 | 
						DeleteAfterRead bool   `json:"delete_after_read"`
 | 
				
			||||||
 | 
						Language        string `json:"language"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CreateNoteResponse struct {
 | 
				
			||||||
 | 
						ID string `json:"id"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize)
 | 
				
			||||||
 | 
						defer r.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var body CreateNotePayload
 | 
				
			||||||
 | 
						err := json.NewDecoder(bodyReader).Decode(&body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							WriteError(w, "could not decode payload to create note", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content, err := internal.Decode(body.Content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							WriteError(w, "could not decode content", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						note, err := h.db.Create(content, body.Password, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							WriteError(w, "could not create note", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
						json.NewEncoder(w).Encode(CreateNoteResponse{ID: note.ID})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetNoteHandler struct {
 | 
				
			||||||
 | 
						logger *slog.Logger
 | 
				
			||||||
 | 
						db     *Database
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						id := mux.Vars(r)["id"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						note, err := h.db.Get(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							WriteError(w, "could not get note", err)
 | 
				
			||||||
 | 
						} else if note == nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if note.Encrypted {
 | 
				
			||||||
 | 
								w.Header().Set("Content-Type", "application/octet-stream")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							fmt.Fprint(w, string(note.Content))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetProtectedNoteHandler struct {
 | 
				
			||||||
 | 
						logger *slog.Logger
 | 
				
			||||||
 | 
						db     *Database
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						id := vars["id"]
 | 
				
			||||||
 | 
						password := vars["password"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						note, err := h.db.Get(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							WriteError(w, "could not get note", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						} else if note == nil {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if password != "" && note.Encrypted {
 | 
				
			||||||
 | 
							note.Content, err = internal.Decrypt(note.Content, password)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								WriteError(w, "could not decrypt note", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
						fmt.Fprint(w, string(note.Content))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ClientHandler struct {
 | 
				
			||||||
 | 
						logger  *slog.Logger
 | 
				
			||||||
 | 
						version string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *ClientHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.logger.Debug("rendering client redirection")
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						os := vars["os"]
 | 
				
			||||||
 | 
						arch := vars["arch"]
 | 
				
			||||||
 | 
						clientName := vars["clientName"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !internal.InSlice(supportedOSes, os) || !internal.InSlice(supportedArches, arch) || !internal.InSlice(supportedClients, clientName) {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusNotFound)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						version := h.version
 | 
				
			||||||
 | 
						if version == "" {
 | 
				
			||||||
 | 
							version = "latest"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						http.Redirect(w, r, fmt.Sprintf("https://git.riou.xyz/jriou/%s/releases/download/%s/%s-%s-%s", clientName, version, clientName, os, arch), http.StatusMovedPermanently)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										234
									
								
								src/server/handlers_web.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/server/handlers_web.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,234 @@
 | 
				
			||||||
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log/slog"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.riou.xyz/jriou/coller/internal"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PageData struct {
 | 
				
			||||||
 | 
						Title                  string
 | 
				
			||||||
 | 
						Version                string
 | 
				
			||||||
 | 
						Expirations            []int
 | 
				
			||||||
 | 
						Expiration             int
 | 
				
			||||||
 | 
						Languages              []string
 | 
				
			||||||
 | 
						Err                    error
 | 
				
			||||||
 | 
						URL                    string
 | 
				
			||||||
 | 
						Note                   *Note
 | 
				
			||||||
 | 
						EnableUploadFileButton bool
 | 
				
			||||||
 | 
						BootstrapDirectory     string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HomeHandler struct {
 | 
				
			||||||
 | 
						Templates *template.Template
 | 
				
			||||||
 | 
						PageData  PageData
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.Templates.ExecuteTemplate(w, "index", h.PageData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CreateNoteWithFormHandler struct {
 | 
				
			||||||
 | 
						Templates     *template.Template
 | 
				
			||||||
 | 
						PageData      PageData
 | 
				
			||||||
 | 
						logger        *slog.Logger
 | 
				
			||||||
 | 
						db            *Database
 | 
				
			||||||
 | 
						maxUploadSize int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.PageData.Err = nil
 | 
				
			||||||
 | 
						templateName := "create"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("parsing multipart form")
 | 
				
			||||||
 | 
						err := r.ParseMultipartForm(h.maxUploadSize)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							h.PageData.Err = err
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("parsing content")
 | 
				
			||||||
 | 
						content := []byte(r.FormValue("content"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("parsing file")
 | 
				
			||||||
 | 
						file, handler, err := r.FormFile("file")
 | 
				
			||||||
 | 
						if err != nil && !errors.Is(err, http.ErrMissingFile) {
 | 
				
			||||||
 | 
							h.PageData.Err = err
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !errors.Is(err, http.ErrMissingFile) {
 | 
				
			||||||
 | 
							defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.logger.Debug("checking file size")
 | 
				
			||||||
 | 
							if handler.Size > h.maxUploadSize {
 | 
				
			||||||
 | 
								h.PageData.Err = fmt.Errorf("file too large (%d > %d)", handler.Size, h.maxUploadSize)
 | 
				
			||||||
 | 
								h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.logger.Debug("checking file content type")
 | 
				
			||||||
 | 
							if !strings.HasPrefix(handler.Header.Get("Content-Type"), "text/") {
 | 
				
			||||||
 | 
								h.PageData.Err = fmt.Errorf("text file expected (got %s)", handler.Header.Get("Content-Type"))
 | 
				
			||||||
 | 
								h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.logger.Debug("reading uploaded file")
 | 
				
			||||||
 | 
							var fileContent bytes.Buffer
 | 
				
			||||||
 | 
							n, err := io.Copy(&fileContent, file)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								h.PageData.Err = err
 | 
				
			||||||
 | 
								h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							h.logger.Debug("file uploaded", slog.Any("bytes", n))
 | 
				
			||||||
 | 
							if n != 0 {
 | 
				
			||||||
 | 
								content = fileContent.Bytes()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("checking content")
 | 
				
			||||||
 | 
						if content == nil || len(content) == 0 {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("empty note")
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("checking inputs")
 | 
				
			||||||
 | 
						noPassword := r.FormValue("no-password")
 | 
				
			||||||
 | 
						password := r.FormValue("password")
 | 
				
			||||||
 | 
						expiration := r.FormValue("expiration")
 | 
				
			||||||
 | 
						deleteAfterRead := r.FormValue("delete-after-read")
 | 
				
			||||||
 | 
						language := r.FormValue("language")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if password == "" && noPassword == "" {
 | 
				
			||||||
 | 
							h.logger.Debug("generating password")
 | 
				
			||||||
 | 
							password = internal.GenerateChars(passwordLength)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("computing expiration")
 | 
				
			||||||
 | 
						var expirationInt int
 | 
				
			||||||
 | 
						if expiration == "Expiration" {
 | 
				
			||||||
 | 
							expirationInt = 0
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							expirationInt, _ = strconv.Atoi(expiration)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("saving note to the database")
 | 
				
			||||||
 | 
						note, err := h.db.Create(content, password, password != "", expirationInt, deleteAfterRead != "", language)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							h.PageData.Err = err
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("building note url")
 | 
				
			||||||
 | 
						var scheme = "http://"
 | 
				
			||||||
 | 
						if r.TLS != nil {
 | 
				
			||||||
 | 
							scheme = "https://"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.PageData.URL = fmt.Sprintf("%s%s/%s", scheme, r.Host, note.ID)
 | 
				
			||||||
 | 
						if password != "" {
 | 
				
			||||||
 | 
							h.PageData.URL += "/" + password
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("rendering page")
 | 
				
			||||||
 | 
						h.Templates.ExecuteTemplate(w, "create", h.PageData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetWebNoteHandler struct {
 | 
				
			||||||
 | 
						Templates *template.Template
 | 
				
			||||||
 | 
						PageData  PageData
 | 
				
			||||||
 | 
						logger    *slog.Logger
 | 
				
			||||||
 | 
						db        *Database
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *GetWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.PageData.Err = nil
 | 
				
			||||||
 | 
						templateName := "note"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						id := vars["id"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						note, err := h.db.Get(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("could not find note: %v", err)
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if note == nil {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if note.Encrypted {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("note is encrypted")
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.PageData.Note = note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("rendering note web page")
 | 
				
			||||||
 | 
						h.Templates.ExecuteTemplate(w, "note", h.PageData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.PageData.Err = nil
 | 
				
			||||||
 | 
						templateName := "note"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						id := vars["id"]
 | 
				
			||||||
 | 
						password := vars["password"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						note, err := h.db.Get(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("could not find note: %v", err)
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if note == nil {
 | 
				
			||||||
 | 
							h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
 | 
				
			||||||
 | 
							h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if password != "" && note.Encrypted {
 | 
				
			||||||
 | 
							note.Content, err = internal.Decrypt(note.Content, password)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								h.PageData.Err = fmt.Errorf("could not decrypt note: %v", err)
 | 
				
			||||||
 | 
								h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.PageData.Note = note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.logger.Debug("rendering protected note web page")
 | 
				
			||||||
 | 
						h.Templates.ExecuteTemplate(w, "note", h.PageData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *ClientsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						h.logger.Debug("rendering clients web page")
 | 
				
			||||||
 | 
						h.Templates.ExecuteTemplate(w, "clients", h.PageData)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,12 @@
 | 
				
			||||||
package server
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"embed"
 | 
						"embed"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"log/slog"
 | 
						"log/slog"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gorilla/mux"
 | 
						"github.com/gorilla/mux"
 | 
				
			||||||
| 
						 | 
					@ -19,7 +15,12 @@ import (
 | 
				
			||||||
	"git.riou.xyz/jriou/coller/internal"
 | 
						"git.riou.xyz/jriou/coller/internal"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var passwordLength = internal.MIN_PASSWORD_LENGTH
 | 
					var (
 | 
				
			||||||
 | 
						passwordLength   = internal.MIN_PASSWORD_LENGTH
 | 
				
			||||||
 | 
						supportedOSes    = []string{"linux", "darwin"}
 | 
				
			||||||
 | 
						supportedArches  = []string{"amd64", "arm64"}
 | 
				
			||||||
 | 
						supportedClients = []string{"coller", "copier"}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Server struct {
 | 
					type Server struct {
 | 
				
			||||||
	logger  *slog.Logger
 | 
						logger  *slog.Logger
 | 
				
			||||||
| 
						 | 
					@ -74,293 +75,6 @@ func WriteError(w http.ResponseWriter, message string, err error) {
 | 
				
			||||||
	}.ToJSON())
 | 
						}.ToJSON())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func HealthHandler(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	fmt.Fprintf(w, "OK")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CreateNoteHandler struct {
 | 
					 | 
				
			||||||
	logger        *slog.Logger
 | 
					 | 
				
			||||||
	db            *Database
 | 
					 | 
				
			||||||
	maxUploadSize int64
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CreateNotePayload struct {
 | 
					 | 
				
			||||||
	Content         string `json:"content"`
 | 
					 | 
				
			||||||
	Password        string `json:"password"`
 | 
					 | 
				
			||||||
	Encrypted       bool   `json:"encrypted"`
 | 
					 | 
				
			||||||
	Expiration      int    `json:"expiration"`
 | 
					 | 
				
			||||||
	DeleteAfterRead bool   `json:"delete_after_read"`
 | 
					 | 
				
			||||||
	Language        string `json:"language"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CreateNoteResponse struct {
 | 
					 | 
				
			||||||
	ID string `json:"id"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "application/json")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize)
 | 
					 | 
				
			||||||
	defer r.Body.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var body CreateNotePayload
 | 
					 | 
				
			||||||
	err := json.NewDecoder(bodyReader).Decode(&body)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		WriteError(w, "could not decode payload to create note", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	content, err := internal.Decode(body.Content)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		WriteError(w, "could not decode content", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	note, err := h.db.Create(content, body.Password, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		WriteError(w, "could not create note", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	w.WriteHeader(http.StatusOK)
 | 
					 | 
				
			||||||
	json.NewEncoder(w).Encode(CreateNoteResponse{ID: note.ID})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GetNoteHandler struct {
 | 
					 | 
				
			||||||
	logger *slog.Logger
 | 
					 | 
				
			||||||
	db     *Database
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	id := mux.Vars(r)["id"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	note, err := h.db.Get(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		WriteError(w, "could not get note", err)
 | 
					 | 
				
			||||||
	} else if note == nil {
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusNotFound)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		if note.Encrypted {
 | 
					 | 
				
			||||||
			w.Header().Set("Content-Type", "application/octet-stream")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusOK)
 | 
					 | 
				
			||||||
		fmt.Fprint(w, string(note.Content))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GetProtectedNoteHandler struct {
 | 
					 | 
				
			||||||
	logger *slog.Logger
 | 
					 | 
				
			||||||
	db     *Database
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	vars := mux.Vars(r)
 | 
					 | 
				
			||||||
	id := vars["id"]
 | 
					 | 
				
			||||||
	password := vars["password"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	note, err := h.db.Get(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		WriteError(w, "could not get note", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	} else if note == nil {
 | 
					 | 
				
			||||||
		w.WriteHeader(http.StatusNotFound)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if password != "" && note.Encrypted {
 | 
					 | 
				
			||||||
		note.Content, err = internal.Decrypt(note.Content, password)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			WriteError(w, "could not decrypt note", err)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	w.WriteHeader(http.StatusOK)
 | 
					 | 
				
			||||||
	fmt.Fprint(w, string(note.Content))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type PageData struct {
 | 
					 | 
				
			||||||
	Title                  string
 | 
					 | 
				
			||||||
	Version                string
 | 
					 | 
				
			||||||
	Expirations            []int
 | 
					 | 
				
			||||||
	Expiration             int
 | 
					 | 
				
			||||||
	Languages              []string
 | 
					 | 
				
			||||||
	Err                    error
 | 
					 | 
				
			||||||
	URL                    string
 | 
					 | 
				
			||||||
	Note                   *Note
 | 
					 | 
				
			||||||
	EnableUploadFileButton bool
 | 
					 | 
				
			||||||
	BootstrapDirectory     string
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type HomeHandler struct {
 | 
					 | 
				
			||||||
	Templates *template.Template
 | 
					 | 
				
			||||||
	PageData  PageData
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	h.Templates.ExecuteTemplate(w, "index", h.PageData)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type CreateNoteWithFormHandler struct {
 | 
					 | 
				
			||||||
	Templates     *template.Template
 | 
					 | 
				
			||||||
	PageData      PageData
 | 
					 | 
				
			||||||
	logger        *slog.Logger
 | 
					 | 
				
			||||||
	db            *Database
 | 
					 | 
				
			||||||
	maxUploadSize int64
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	h.PageData.Err = nil
 | 
					 | 
				
			||||||
	templateName := "create"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("parsing multipart form")
 | 
					 | 
				
			||||||
	err := r.ParseMultipartForm(h.maxUploadSize)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = err
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("parsing content")
 | 
					 | 
				
			||||||
	content := []byte(r.FormValue("content"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("parsing file")
 | 
					 | 
				
			||||||
	file, handler, err := r.FormFile("file")
 | 
					 | 
				
			||||||
	if err != nil && !errors.Is(err, http.ErrMissingFile) {
 | 
					 | 
				
			||||||
		h.PageData.Err = err
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !errors.Is(err, http.ErrMissingFile) {
 | 
					 | 
				
			||||||
		defer file.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h.logger.Debug("checking file size")
 | 
					 | 
				
			||||||
		if handler.Size > h.maxUploadSize {
 | 
					 | 
				
			||||||
			h.PageData.Err = fmt.Errorf("file too large (%d > %d)", handler.Size, h.maxUploadSize)
 | 
					 | 
				
			||||||
			h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h.logger.Debug("checking file content type")
 | 
					 | 
				
			||||||
		if !strings.HasPrefix(handler.Header.Get("Content-Type"), "text/") {
 | 
					 | 
				
			||||||
			h.PageData.Err = fmt.Errorf("text file expected (got %s)", handler.Header.Get("Content-Type"))
 | 
					 | 
				
			||||||
			h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h.logger.Debug("reading uploaded file")
 | 
					 | 
				
			||||||
		var fileContent bytes.Buffer
 | 
					 | 
				
			||||||
		n, err := io.Copy(&fileContent, file)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			h.PageData.Err = err
 | 
					 | 
				
			||||||
			h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		h.logger.Debug("file uploaded", slog.Any("bytes", n))
 | 
					 | 
				
			||||||
		if n != 0 {
 | 
					 | 
				
			||||||
			content = fileContent.Bytes()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("checking content")
 | 
					 | 
				
			||||||
	if content == nil || len(content) == 0 {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("empty note")
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("checking inputs")
 | 
					 | 
				
			||||||
	noPassword := r.FormValue("no-password")
 | 
					 | 
				
			||||||
	password := r.FormValue("password")
 | 
					 | 
				
			||||||
	expiration := r.FormValue("expiration")
 | 
					 | 
				
			||||||
	deleteAfterRead := r.FormValue("delete-after-read")
 | 
					 | 
				
			||||||
	language := r.FormValue("language")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if password == "" && noPassword == "" {
 | 
					 | 
				
			||||||
		h.logger.Debug("generating password")
 | 
					 | 
				
			||||||
		password = internal.GenerateChars(passwordLength)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("computing expiration")
 | 
					 | 
				
			||||||
	var expirationInt int
 | 
					 | 
				
			||||||
	if expiration == "Expiration" {
 | 
					 | 
				
			||||||
		expirationInt = 0
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		expirationInt, _ = strconv.Atoi(expiration)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("saving note to the database")
 | 
					 | 
				
			||||||
	note, err := h.db.Create(content, password, password != "", expirationInt, deleteAfterRead != "", language)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = err
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("building note url")
 | 
					 | 
				
			||||||
	var scheme = "http://"
 | 
					 | 
				
			||||||
	if r.TLS != nil {
 | 
					 | 
				
			||||||
		scheme = "https://"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.PageData.URL = fmt.Sprintf("%s%s/%s", scheme, r.Host, note.ID)
 | 
					 | 
				
			||||||
	if password != "" {
 | 
					 | 
				
			||||||
		h.PageData.URL += "/" + password
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("rendering page")
 | 
					 | 
				
			||||||
	h.Templates.ExecuteTemplate(w, "create", h.PageData)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GetWebNoteHandler struct {
 | 
					 | 
				
			||||||
	Templates *template.Template
 | 
					 | 
				
			||||||
	PageData  PageData
 | 
					 | 
				
			||||||
	logger    *slog.Logger
 | 
					 | 
				
			||||||
	db        *Database
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (h *GetWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					 | 
				
			||||||
	h.PageData.Err = nil
 | 
					 | 
				
			||||||
	templateName := "note"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	vars := mux.Vars(r)
 | 
					 | 
				
			||||||
	id := vars["id"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	note, err := h.db.Get(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("could not find note: %v", err)
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if note == nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if note.Encrypted {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("note is encrypted")
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.PageData.Note = note
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("rendering note web page")
 | 
					 | 
				
			||||||
	h.Templates.ExecuteTemplate(w, "note", h.PageData)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type GetProtectedWebNoteHandler struct {
 | 
					type GetProtectedWebNoteHandler struct {
 | 
				
			||||||
	Templates *template.Template
 | 
						Templates *template.Template
 | 
				
			||||||
	PageData  PageData
 | 
						PageData  PageData
 | 
				
			||||||
| 
						 | 
					@ -368,41 +82,10 @@ type GetProtectedWebNoteHandler struct {
 | 
				
			||||||
	db        *Database
 | 
						db        *Database
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
					type ClientsHandler struct {
 | 
				
			||||||
	h.PageData.Err = nil
 | 
						Templates *template.Template
 | 
				
			||||||
	templateName := "note"
 | 
						PageData  PageData
 | 
				
			||||||
 | 
						logger    *slog.Logger
 | 
				
			||||||
	vars := mux.Vars(r)
 | 
					 | 
				
			||||||
	id := vars["id"]
 | 
					 | 
				
			||||||
	password := vars["password"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	note, err := h.db.Get(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("could not find note: %v", err)
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if note == nil {
 | 
					 | 
				
			||||||
		h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
 | 
					 | 
				
			||||||
		h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if password != "" && note.Encrypted {
 | 
					 | 
				
			||||||
		note.Content, err = internal.Decrypt(note.Content, password)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			h.PageData.Err = fmt.Errorf("could not decrypt note: %v", err)
 | 
					 | 
				
			||||||
			h.Templates.ExecuteTemplate(w, templateName, h.PageData)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.PageData.Note = note
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.logger.Debug("rendering protected note web page")
 | 
					 | 
				
			||||||
	h.Templates.ExecuteTemplate(w, "note", h.PageData)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//go:embed templates/*
 | 
					//go:embed templates/*
 | 
				
			||||||
| 
						 | 
					@ -456,6 +139,14 @@ func (s *Server) Start() error {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	r.Path("/create").Handler(createNoteWithFormHandler).Methods("POST")
 | 
						r.Path("/create").Handler(createNoteWithFormHandler).Methods("POST")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clientsHandler := &ClientsHandler{
 | 
				
			||||||
 | 
							Templates: templates,
 | 
				
			||||||
 | 
							PageData:  p,
 | 
				
			||||||
 | 
							logger:    s.logger,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.Path("/clients.html").Handler(clientsHandler).Methods("GET")
 | 
				
			||||||
 | 
						r.Path("/clients/{os:[a-z]+}-{arch:[a-z0-9]+}/{clientName:[a-z]+}").Handler(&ClientHandler{logger: s.logger, version: p.Version}).Methods("GET")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protectedWebNoteHandler := &GetProtectedWebNoteHandler{
 | 
						protectedWebNoteHandler := &GetProtectedWebNoteHandler{
 | 
				
			||||||
		Templates: templates,
 | 
							Templates: templates,
 | 
				
			||||||
		PageData:  p,
 | 
							PageData:  p,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/server/templates/clients.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/server/templates/clients.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					{{define "clients"}}
 | 
				
			||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en" data-bs-theme="light">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{{block "head" .}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    {{block "header" .}}{{end}}
 | 
				
			||||||
 | 
					    <div class="container mb-4">
 | 
				
			||||||
 | 
					        <p class="fs-4">Command-line clients</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="container mb-4">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <table class="table">
 | 
				
			||||||
 | 
					            <thead>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th scope="col">Name</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Docs</th>
 | 
				
			||||||
 | 
					                    <th scope="col">OS</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Arch</th>
 | 
				
			||||||
 | 
					                    <th scope="col">Download</th>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </thead>
 | 
				
			||||||
 | 
					            <tbody>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th scope="row" rowspan="2">coller</th>
 | 
				
			||||||
 | 
					                    <td rowspan="2"><a
 | 
				
			||||||
 | 
					                            href="https://git.riou.xyz/jriou/coller/src/{{if .Version}}tag/{{.Version}}{{else}}branch/main{{end}}/src/cmd/coller/README.md">📄</a>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td>Linux</td>
 | 
				
			||||||
 | 
					                    <td>x86-64</td>
 | 
				
			||||||
 | 
					                    <td><a href="/clients/linux-amd64/coller">💾</a></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>macOS</td>
 | 
				
			||||||
 | 
					                    <td>ARM64</td>
 | 
				
			||||||
 | 
					                    <td><a href="/clients/darwin-arm64/coller">💾</a></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <th scope="row" rowspan="2">copier</th>
 | 
				
			||||||
 | 
					                    <td rowspan="2"><a
 | 
				
			||||||
 | 
					                            href="https://git.riou.xyz/jriou/coller/src/{{if .Version}}tag/{{.Version}}{{else}}branch/main{{end}}/src/cmd/copier/README.md">📄</a>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td>Linux</td>
 | 
				
			||||||
 | 
					                    <td>x86-64</td>
 | 
				
			||||||
 | 
					                    <td><a href="/clients/linux-amd64/copier">💾</a></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>macOS</td>
 | 
				
			||||||
 | 
					                    <td>ARM64</td>
 | 
				
			||||||
 | 
					                    <td><a href="/clients/darwin-arm64/copier">💾</a></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {{block "footer" .}}{{end}}
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
 | 
					{{end}}
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,11 @@
 | 
				
			||||||
            <a class="d-flex mb-3 mb-md-0 me-md-auto text-dark text-decoration-none" id="titleHeader" href="/">
 | 
					            <a class="d-flex mb-3 mb-md-0 me-md-auto text-dark text-decoration-none" id="titleHeader" href="/">
 | 
				
			||||||
                <span class="fs-3">{{.Title}}</span>
 | 
					                <span class="fs-3">{{.Title}}</span>
 | 
				
			||||||
            </a>
 | 
					            </a>
 | 
				
			||||||
 | 
					            <ul class="nav nav-pills align-items-center">
 | 
				
			||||||
 | 
					                <li class="nav-item px-2">
 | 
				
			||||||
 | 
					                    <a href="/clients.html">Command-line clients</a>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					            </ul>
 | 
				
			||||||
            <ul class="nav nav-pills align-items-center">
 | 
					            <ul class="nav nav-pills align-items-center">
 | 
				
			||||||
                <li class="nav-item">
 | 
					                <li class="nav-item">
 | 
				
			||||||
                    <div class="form-check form-switch"
 | 
					                    <div class="form-check form-switch"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue