diff --git a/cmd/paste-abroad/main.go b/cmd/paste-abroad/main.go new file mode 100644 index 0000000..17fcb71 --- /dev/null +++ b/cmd/paste-abroad/main.go @@ -0,0 +1,34 @@ +package main + +import ( + limit "github.com/gin-contrib/size" + "github.com/gin-gonic/gin" + + "github.com/leafee98/paste-abroad/internel/router" + "github.com/leafee98/paste-abroad/internel/config" + "github.com/leafee98/paste-abroad/internel/storage/sqlite" + + "fmt" +) + +func main() { + c, err := config.Load("./config.toml") + if err != nil { + fmt.Printf("Error when loading config: %v", err) + return + } + + store, err := sqlite.New(c.DBString, c.IdLength) + if err != nil { + fmt.Println(err) + } + + r := gin.Default() + + r.Use(limit.RequestSizeLimiter(c.MaxPasteSize)) + + router.Init(r, store, c.DefaultLifetime) + + r.Run(":8081") +} + diff --git a/go.mod b/go.mod index aca8eb8..c9ba186 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,29 @@ module github.com/leafee98/paste-abroad go 1.19 require ( - github.com/fasthttp/router v1.4.14 + github.com/BurntSushi/toml v1.2.1 + github.com/gin-contrib/size v0.0.0-20230212012657-e14a14094dc4 + github.com/gin-gonic/gin v1.8.2 github.com/mattn/go-sqlite3 v1.14.16 - github.com/valyala/fasthttp v1.44.0 ) require ( - github.com/andybalholm/brotli v1.0.4 // indirect - github.com/klauspost/compress v1.15.9 // indirect - github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/internel/config/config.go b/internel/config/config.go new file mode 100644 index 0000000..c41d7ba --- /dev/null +++ b/internel/config/config.go @@ -0,0 +1,28 @@ +package config + +import ( + "github.com/BurntSushi/toml" + + "os" +) + +type Config struct { + DefaultLifetime int64 `toml:"lifetime"` + IdLength int `toml:"id_length"` + Database string `toml:"database"` + DBString string `toml:"db_string"` + MaxPasteSize int64 `toml:"max_paste_size"` +} + +func Load(path string) (*Config, error) { + buffer, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + config_content := string(buffer) + + var c Config + toml.Decode(config_content, &c) + return &c, nil +} diff --git a/internel/router/router.go b/internel/router/router.go index 5dfc895..430a063 100644 --- a/internel/router/router.go +++ b/internel/router/router.go @@ -13,16 +13,15 @@ import ( var store storage.Storage -var expireTime int64 +var defaultExpireTime int64 -func New(s storage.Storage, expireTime int) *gin.Engine { +func Init(e *gin.Engine, s storage.Storage, expireTime int64) { + defaultExpireTime = expireTime store = s - var r = gin.Default() - r.POST("/", PostPaste) - r.GET("/raw/:id", GetPasteRaw) - r.GET("/:id", GetPaste) - return r + e.POST("/", PostPaste) + e.GET("/raw/:id", GetPasteRaw) + e.GET("/:id", GetPaste) } // PostPaste require two parameters from either URL or form-data @@ -42,10 +41,20 @@ func PostPaste(ctx *gin.Context) { content_buffer.ReadFrom(c_reader) content = content_buffer.Bytes() } else { - content = []byte(requestValue(ctx, "c", false)) + content_str, exists := getRequestValue(ctx, "c", false) + if !exists { + ctx.String(400, "cannot get paste content: missing parameter \"c\"") + return + } + content = []byte(content_str) } case "application/x-www-form-urlencoded": - content = []byte(requestValue(ctx, "c", false)) + content_str, exists := getRequestValue(ctx, "c", false) + if !exists { + ctx.String(400, "cannot get paste content: missing parameter \"c\"") + return + } + content = []byte(content_str) default: ctx.String(400, "unrecognized content type: %s", ctx.ContentType()) return @@ -56,13 +65,13 @@ func PostPaste(ctx *gin.Context) { life_int, err := strconv.ParseInt(life, 10, 64) if err != nil { - life_int = expireTime + life_int = defaultExpireTime } paste := storage.Paste{ Content: content, // Content: content_buffer.Bytes(), - Plain: !isEncrypt(encrypt), + Encrypt: isEncrypt(encrypt), Expire: life_int * 1000 * 3600 * 24 + time.Now().UnixMilli(), } @@ -73,7 +82,7 @@ func PostPaste(ctx *gin.Context) { return } - log.Printf("Saved a paste { id: %s, plain: %t, expire: %d}", id, paste.Plain, paste.Expire) + log.Printf("Saved a paste { id: %s, encrypt: %t, expire: %d}", id, paste.Encrypt, paste.Expire) ctx.String(200, id) } @@ -93,10 +102,10 @@ func GetPasteRaw(ctx * gin.Context) { return } - if content.Plain { - ctx.Data(200, "text/plain", content.Content) - } else { + if content.Encrypt { ctx.Data(200, "application/octet-stream", content.Content) + } else { + ctx.Data(200, "text/plain", content.Content) } } @@ -121,6 +130,25 @@ func requestValue(ctx *gin.Context, key string, urlPrefer bool) string { return value } +func getRequestValue(ctx *gin.Context, key string, urlPrefer bool) (string, bool) { + var value string + var exists bool + + if urlPrefer { + value, exists = ctx.GetQuery(key) + if !exists { + value, exists = ctx.GetPostForm(key) + } + } else { + value, exists = ctx.GetPostForm(key) + if !exists { + value, exists = ctx.GetQuery(key) + } + } + + return value, exists +} + func isEncrypt(s string) bool { ture_values := []string{ "t", "true", "y", "yes", "1" } diff --git a/internel/storage/sqlite/sqlite.go b/internel/storage/sqlite/sqlite.go index 3803ca8..11ec3e0 100644 --- a/internel/storage/sqlite/sqlite.go +++ b/internel/storage/sqlite/sqlite.go @@ -39,7 +39,7 @@ func initDatabase(db *sql.DB) error { create table if not exists paste ( id text unique, content blob, - plain integer, + encrypt integer, expire integer ); @@ -62,22 +62,22 @@ func (s *StorageSqlite) Close() error { } func (s *StorageSqlite) Save(p *storage.Paste) (string, error) { - stmt, err := s.db.Prepare(`insert into paste (id, content, plain, expire) values (?, ?, ?, ?);`) + stmt, err := s.db.Prepare(`insert into paste (id, content, encrypt, expire) values (?, ?, ?, ?);`) if err != nil { return "", err } defer stmt.Close() var id string - var plain = 0 + var encrypt = 0 - if p.Plain { - plain = 1 + if p.Encrypt { + encrypt = 1 } for true { id = utils.GenerateId(s.idLength) - _, err = stmt.Exec(id, p.Content, plain, p.Expire) + _, err = stmt.Exec(id, p.Content, encrypt, p.Expire) if err == nil { break } else if errors.Is(err, sqlite3.ErrConstraint) { @@ -91,7 +91,7 @@ func (s *StorageSqlite) Save(p *storage.Paste) (string, error) { } func (s *StorageSqlite) Get(id string) (*storage.Paste, error) { - stmt, err := s.db.Prepare(`select content, plain, expire from paste where id = ?`) + stmt, err := s.db.Prepare(`select content, encrypt, expire from paste where id = ?`) if err != nil { return nil, err } @@ -108,14 +108,14 @@ func (s *StorageSqlite) Get(id string) (*storage.Paste, error) { } defer rows.Close() - var plain = 0 + var encrypt = 0 - if err = rows.Scan(&p.Content, &plain, &p.Expire); err != nil { + if err = rows.Scan(&p.Content, &encrypt, &p.Expire); err != nil { return nil, err } - if plain != 0 { - p.Plain = true + if encrypt != 0 { + p.Encrypt = true } return &p, nil diff --git a/internel/storage/sqlite_test.go b/internel/storage/sqlite_test.go index 0e21c54..5131db6 100644 --- a/internel/storage/sqlite_test.go +++ b/internel/storage/sqlite_test.go @@ -35,12 +35,12 @@ func TestSqlite(t * testing.T) { func testGetSave(s *sqlite.StorageSqlite, t * testing.T) { var a = storage.Paste { Content: []byte("abc"), - Plain: true, + Encrypt: false, Expire: time.Now().UnixMilli() + 15 * 1000, } var b = storage.Paste { Content: []byte("def"), - Plain: true, + Encrypt: false, Expire: time.Now().UnixMilli() - 15 * 1000, } diff --git a/internel/storage/storage.go b/internel/storage/storage.go index 75d6c02..c9fe3a9 100644 --- a/internel/storage/storage.go +++ b/internel/storage/storage.go @@ -7,7 +7,7 @@ type Storage interface { } type Paste struct { - Plain bool + Encrypt bool Content []byte Expire int64 } diff --git a/internel/utils/utils.go b/internel/utils/utils.go new file mode 100644 index 0000000..241c61c --- /dev/null +++ b/internel/utils/utils.go @@ -0,0 +1,16 @@ +package utils + +import ( + "math/rand" +) + +var letters = []rune("abcdefghijklmnopqrstuvwxyz") + +// GenearteId will create a string contains 8 lowercase alphabets +func GenerateId(length int) string { + buffer := make([]rune, length) + for i := range buffer { + buffer[i] = letters[rand.Intn(len(letters))] + } + return string(buffer) +}