commit 4be58013b64c8eeb3127593184f14964df0483cb Author: Nulo Date: Thu Jan 19 20:26:46 2023 -0300 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d07f89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +files/ +db.db diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c9f8730 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module gitea.nulo.in/Nulo/cacher + +go 1.19 + +require ( + gitea.nulo.in/Nulo/go-migrate v0.0.4 // indirect + github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/matoous/go-nanoid/v2 v2.0.0 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b7b00f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +gitea.nulo.in/Nulo/go-migrate v0.0.3 h1:Y9b7Jix1CSZturT9t5Dw8XooSOJt38e2vsFu+e8j+FM= +gitea.nulo.in/Nulo/go-migrate v0.0.3/go.mod h1:7omz1cegf/PsF9xe7uMrnkAMFjlkaVDNzqD/o2RZjkc= +gitea.nulo.in/Nulo/go-migrate v0.0.4 h1:/6rnMJciD5CDfxYUFb9FWCxgFxb+5tEusIcTIbf7irA= +gitea.nulo.in/Nulo/go-migrate v0.0.4/go.mod h1:kSNRgNsHB+6SkZz4vnjih+sfruILdEBvBhHOHUMnZO0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek= +github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= +github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..33d7597 --- /dev/null +++ b/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + + migrate "gitea.nulo.in/Nulo/go-migrate" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" +) + +var admin_secret string + +func main() { + if len(os.Args) < 2 { + log.Fatal("Missing port argument") + } + + admin_secret = os.Getenv("CACHER_ADMIN_SECRET") + if len(admin_secret) == 0 { + log.Fatal("Missing CACHER_ADMIN_SECRET") + } + + err := os.MkdirAll("./files/", 0700) + if err != nil { + log.Fatalf("MkdirAll: %w", err) + } + + db := sqlx.MustConnect("sqlite3", "file:./db.db?_foreign_keys=true") + migrator := migrate.Sqlx{ + Migrations: []migrate.SqlxMigration{ + migrate.SqlxQueryMigration( + "0001_create_caches", + `create table caches( + name text not null primary key, + secret text not null + );`, + "", + ), + }, + } + err = migrator.Migrate(db.DB, "sqlite3") + if err != nil { + log.Fatalf("Migrate() err = %v; want nil", err) + } + + http.HandleFunc("/", index) + http.Handle("/cache/", &cache{db: db}) + http.ListenAndServe(os.Args[1], nil) +} + +func index(w http.ResponseWriter, req *http.Request) { + w.Header().Add("Content-Type", "text/html; charset=utf-8") + fmt.Fprintf(w, ` +cacher API + +

cacher

+ +

POST /cache/$NAME

+ +

Crea el cache y devuelve un JSON con el secreto.

+
curl --request POST -H "Authorization: Bearer $ADMIN_SECRET" $URL/cache/$CACHE_NAME
+ +

PUT /cache/$NAME

+ +

Guarda en el cache un archivo arbitrario.

+
curl --request PUT --upload-file $ARCHIVO -H "Authorization: Bearer $CACHE_SECRET" $URL/cache/$CACHE_NAME
+ +

GET /cache/$NAME

+ +

EnvĂ­a el archivo en el cache.

+
curl --output $FILE -H "Authorization: Bearer $CACHE_SECRET" $URL/cache/$CACHE_NAME
+`) +} diff --git a/save_cache.go b/save_cache.go new file mode 100644 index 0000000..209b1f5 --- /dev/null +++ b/save_cache.go @@ -0,0 +1,156 @@ +package main + +import ( + "database/sql" + "encoding/json" + "io" + "log" + "net/http" + "os" + "path" + "strings" + + "github.com/jmoiron/sqlx" + gonanoid "github.com/matoous/go-nanoid/v2" + "github.com/mattn/go-sqlite3" +) + +func badRequest(w http.ResponseWriter) { + http.Error(w, "bad request", http.StatusBadRequest) +} + +func getAdminAuth(w http.ResponseWriter, req *http.Request) bool { + auth := req.Header.Get("Authorization") + if len(auth) == 0 { + badRequest(w) + return false + } + if auth == "Bearer "+admin_secret { + return true + } + return false +} + +func (r *cache) getCache(w http.ResponseWriter, req *http.Request, name string) *dbCache { + auth := req.Header.Get("Authorization") + if len(auth) == 0 { + badRequest(w) + return nil + } + + var c dbCache + err := r.db.Get(&c, "SELECT secret FROM caches WHERE name = ?", name) + if err != nil { + if err == sql.ErrNoRows { + http.Error(w, "cache doesn't exist", http.StatusNotFound) + } else { + log.Printf("Get(SELECT): %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + } + return nil + } + + if auth == "Bearer "+c.Secret { + return &c + } else { + http.Error(w, "wrong secret", http.StatusUnauthorized) + return nil + } +} + +type cache struct { + db *sqlx.DB +} + +type dbCache struct { + Secret string +} + +type responseNewCache struct { + Secret string `json:"secret"` +} + +func (r *cache) ServeHTTP(w http.ResponseWriter, req *http.Request) { + parts := strings.Split(req.URL.Path[1:], "/") + log.Println(parts) + if len(parts) != 2 { + http.Error(w, "wrong amount of parts", http.StatusBadRequest) + return + + } + name := parts[1] + + if req.Method == "POST" { + if !getAdminAuth(w, req) { + return + } + if strings.ContainsAny(name, "/") { + badRequest(w) + return + } + secret, err := gonanoid.New() + if err != nil { + log.Printf("Error generating ID: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + _, err = r.db.Exec("INSERT INTO caches(name, secret) VALUES(?, ?)", name, secret) + if err != nil { + if err.(sqlite3.Error).Code == sqlite3.ErrConstraint { + http.Error(w, "cache already exists", http.StatusConflict) + } else { + log.Printf("Error saving cache DB entry: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + } + return + } + + byt, err := json.Marshal(responseNewCache{Secret: secret}) + if err != nil { + log.Printf("Marshal: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + w.Header().Add("Content-Encoding", "application/json") + w.Write(byt) + } else if req.Method == "PUT" { + c := r.getCache(w, req, name) + if c == nil { + return + } + + file, err := os.Create(path.Join("./files/", name)) + if err != nil { + log.Printf("os.Create: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + _, err = io.Copy(file, req.Body) + if err != nil { + log.Printf("io.Copy: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + } else if req.Method == "GET" { + c := r.getCache(w, req, name) + if c == nil { + return + } + + file, err := os.Open(path.Join("./files/", name)) + if err != nil { + log.Printf("os.Open: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + _, err = io.Copy(w, file) + if err != nil { + log.Printf("io.Copy: %w", err) + http.Error(w, "server error", http.StatusInternalServerError) + return + } + } else { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } +}