From a52b18ffe4d356e28def03fe83919c057f7e8b85 Mon Sep 17 00:00:00 2001 From: "Stanislav N. aka pztrn" Date: Sun, 22 Dec 2019 01:17:18 +0500 Subject: [PATCH] Linting. --- .drone.yml | 11 ++++- .golangci.yml | 18 +++++++ cmd/fastpastebin/fastpastebin.go | 6 ++- .../dbnotavailable.go | 2 +- .../exported.go | 4 +- domains/indexpage/exported.go | 2 +- domains/pastes/exported.go | 2 +- domains/pastes/paste_get.go | 31 ++++++++---- domains/pastes/paste_post.go | 21 +++++++- domains/pastes/pastes_get.go | 9 +++- internal/captcha/exported.go | 3 +- internal/config/database.go | 4 +- internal/config/http.go | 4 +- internal/config/logging.go | 4 +- internal/config/pastes.go | 4 +- internal/config/struct.go | 12 ++--- internal/context/context.go | 4 +- internal/context/logger.go | 4 ++ internal/database/database.go | 2 +- .../database/dialects/flatfiles/exported.go | 3 +- .../database/dialects/flatfiles/flatfiles.go | 19 +++++++- internal/database/dialects/mysql/exported.go | 3 +- .../dialects/mysql/migrations/1_initial.go | 1 + .../database/dialects/mysql/mysqldatabase.go | 19 ++++++-- .../database/dialects/postgresql/exported.go | 3 +- .../dialects/postgresql/postgresqldatabase.go | 21 ++++++-- internal/database/exported.go | 3 +- internal/database/handler.go | 3 +- .../database/interface/databaseinterface.go | 2 +- internal/pagination/exported.go | 12 +++-- internal/structs/paste.go | 48 +++++++++---------- 31 files changed, 202 insertions(+), 82 deletions(-) create mode 100644 .golangci.yml rename domains/{database_not_available => dbnotavailable}/dbnotavailable.go (98%) rename domains/{database_not_available => dbnotavailable}/exported.go (93%) diff --git a/.drone.yml b/.drone.yml index 75cd4ce..eaa8998 100644 --- a/.drone.yml +++ b/.drone.yml @@ -20,14 +20,18 @@ steps: CGO_ENABLED: 0 commands: - golangci-lint run + depends_on: + - notify-start - name: test - image: golang:1.13.1-alpine + image: golang:1.13.5-alpine environment: GOFLAGS: -mod=vendor CGO_ENABLED: 0 commands: - go test ./... + depends_on: + - notify-start - name: docker image: plugins/docker @@ -40,6 +44,9 @@ steps: from_secret: dockerhub_password repo: pztrn/fastpastebin auto_tag: true + depends_on: + - lint + - test - name: notify-end when: @@ -58,3 +65,5 @@ steps: {{ else }} **{{repo.name}}#{{build.number}}@{{build.commit}}** failed. See {{build.link}}. {{/success}}" + depends_on: + - docker diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..549ef2f --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,18 @@ +run: + deadline: 5m +linters: + enable-all: true + disable: + # Because globals might exist, but according to our codestyle they + # should be lowercased and considered as unexported. + - gochecknoglobals + # While it might be useful it'll create more problems that will solve. + - gocritic + # Complains about main() lengths, which isn't an issue. + - funlen +linters-settings: + lll: + line-length: 420 + gocyclo: + min-complexity: 40 + \ No newline at end of file diff --git a/cmd/fastpastebin/fastpastebin.go b/cmd/fastpastebin/fastpastebin.go index d78c365..a88a03a 100644 --- a/cmd/fastpastebin/fastpastebin.go +++ b/cmd/fastpastebin/fastpastebin.go @@ -31,7 +31,7 @@ import ( "syscall" // local - "go.dev.pztrn.name/fastpastebin/domains/database_not_available" + "go.dev.pztrn.name/fastpastebin/domains/dbnotavailable" "go.dev.pztrn.name/fastpastebin/domains/indexpage" "go.dev.pztrn.name/fastpastebin/domains/pastes" "go.dev.pztrn.name/fastpastebin/internal/captcha" @@ -61,14 +61,16 @@ func main() { captcha.New(c) - database_not_available.New(c) + dbnotavailable.New(c) indexpage.New(c) pastes.New(c) // CTRL+C handler. signalHandler := make(chan os.Signal, 1) shutdownDone := make(chan bool, 1) + signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM) + go func() { <-signalHandler c.Shutdown() diff --git a/domains/database_not_available/dbnotavailable.go b/domains/dbnotavailable/dbnotavailable.go similarity index 98% rename from domains/database_not_available/dbnotavailable.go rename to domains/dbnotavailable/dbnotavailable.go index 00ca6d6..02b43ba 100644 --- a/domains/database_not_available/dbnotavailable.go +++ b/domains/dbnotavailable/dbnotavailable.go @@ -22,7 +22,7 @@ // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -package database_not_available +package dbnotavailable import ( // stdlib diff --git a/domains/database_not_available/exported.go b/domains/dbnotavailable/exported.go similarity index 93% rename from domains/database_not_available/exported.go rename to domains/dbnotavailable/exported.go index fd1e453..cd4c330 100644 --- a/domains/database_not_available/exported.go +++ b/domains/dbnotavailable/exported.go @@ -22,7 +22,7 @@ // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -package database_not_available +package dbnotavailable import ( // local @@ -33,7 +33,7 @@ var ( c *context.Context ) -// New initializes pastes package and adds neccessary HTTP and API +// New initializes pastes package and adds necessary HTTP and API // endpoints. func New(cc *context.Context) { c = cc diff --git a/domains/indexpage/exported.go b/domains/indexpage/exported.go index d55e5db..f326f80 100644 --- a/domains/indexpage/exported.go +++ b/domains/indexpage/exported.go @@ -33,7 +33,7 @@ var ( c *context.Context ) -// New initializes pastes package and adds neccessary HTTP and API +// New initializes pastes package and adds necessary HTTP and API // endpoints. func New(cc *context.Context) { c = cc diff --git a/domains/pastes/exported.go b/domains/pastes/exported.go index 552bd59..ef7d526 100644 --- a/domains/pastes/exported.go +++ b/domains/pastes/exported.go @@ -40,7 +40,7 @@ var ( c *context.Context ) -// New initializes pastes package and adds neccessary HTTP and API +// New initializes pastes package and adds necessary HTTP and API // endpoints. func New(cc *context.Context) { c = cc diff --git a/domains/pastes/paste_get.go b/domains/pastes/paste_get.go index c004e3c..159436d 100644 --- a/domains/pastes/paste_get.go +++ b/domains/pastes/paste_get.go @@ -34,12 +34,6 @@ const ( // for some cases, e.g. public paste won't check for timestamp and cookie // value (they both will be ignored), but private will. func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) { - // We should check if database connection available. - //dbConn := c.Database.GetDatabaseConnection() - //if c.Config.Database.Type != "flatfiles" && dbConn == nil { - // return ec.Redirect(http.StatusFound, "/database_not_available") - //} - // Get paste. paste, err1 := c.Database.GetPaste(pasteID) if err1 != nil { @@ -91,21 +85,25 @@ func pasteGETWebInterface(ec echo.Context) error { // If passed timestamp is invalid (isn't a real UNIX timestamp) we // will show 404 Not Found error and spam about that in logs. var timestamp int64 + tsProvidedStr := ec.Param("timestamp") if tsProvidedStr != "" { tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64) if err != nil { c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste") + errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found") + return ec.HTML(http.StatusBadRequest, errtpl) - } else { - timestamp = tsProvided } + + timestamp = tsProvided } // Check if we have "PASTE-PASTEID" cookie defined. It is required // for private pastes. var cookieValue string + cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr) if err1 == nil { cookieValue = cookie.Value @@ -137,6 +135,7 @@ func pasteGETWebInterface(ec echo.Context) error { if paste.KeepFor != 0 && paste.KeepForUnitType != 0 { pasteExpirationString = paste.GetExpirationTime().Format("2006-01-02 @ 15:04:05") + " UTC" } + pasteData["pasteExpiration"] = pasteExpirationString if paste.Private { @@ -170,10 +169,12 @@ func pasteGETWebInterface(ec echo.Context) error { } // Create buffer and format into it. buf := new(bytes.Buffer) + err4 := formatter.Format(buf, style, lexered) if err4 != nil { c.Logger.Error().Err(err4).Msg("Failed to format paste data") } + pasteData["pastedata"] = buf.String() // Get template and format it. @@ -194,7 +195,9 @@ func pastePasswordedVerifyGet(ec echo.Context) error { paste, err1 := c.Database.GetPaste(pasteID) if err1 != nil { c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data") + errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found") + return ec.HTML(http.StatusBadRequest, errtpl) } @@ -229,6 +232,7 @@ func pastePasswordedVerifyGet(ec echo.Context) error { func pastePasswordedVerifyPost(ec echo.Context) error { // We should check if database connection available. dbConn := c.Database.GetDatabaseConnection() + // nolint if c.Config.Database.Type != "flatfiles" && dbConn == nil { return ec.Redirect(http.StatusFound, "/database_not_available") } @@ -245,13 +249,16 @@ func pastePasswordedVerifyPost(ec echo.Context) error { if err1 != nil { c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found") + return ec.HTML(http.StatusBadRequest, errtpl) } params, err2 := ec.FormParams() if err2 != nil { c.Logger.Debug().Msg("No form parameters passed") + errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found") + return ec.HTML(http.StatusBadRequest, errtpl) } @@ -268,7 +275,8 @@ func pastePasswordedVerifyPost(ec echo.Context) error { } errtpl := templater.GetErrorTemplate(ec, "Invalid password. Please, try again.") - return ec.HTML(http.StatusBadRequest, string(errtpl)) + + return ec.HTML(http.StatusBadRequest, errtpl) } // GET for "/pastes/:id/raw", raw paste output. @@ -301,18 +309,23 @@ func pasteRawGETWebInterface(ec echo.Context) error { // Check if we have a private paste and it's parameters are correct. if paste.Private { tsProvidedStr := ec.Param("timestamp") + tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64) if err2 != nil { c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste") + return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") } + pasteTs := paste.CreatedAt.Unix() if tsProvided != pasteTs { c.Logger.Error().Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Int64("paste timestamp", pasteTs).Msg("Incorrect timestamp provided for private paste") + return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found") } } + // nolint // ToDo: figure out how to handle passworded pastes here. // Return error for now. if paste.Password != "" { diff --git a/domains/pastes/paste_post.go b/domains/pastes/paste_post.go index 53fba38..f822b26 100644 --- a/domains/pastes/paste_post.go +++ b/domains/pastes/paste_post.go @@ -31,28 +31,37 @@ func pastePOSTWebInterface(ec echo.Context) error { params, err := ec.FormParams() if err != nil { c.Logger.Error().Msg("Passed paste form is empty") + errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste") + return ec.HTML(http.StatusBadRequest, errtpl) } + c.Logger.Debug().Msgf("Received parameters: %+v", params) // Do nothing if paste contents is empty. if len(params["paste-contents"][0]) == 0 { c.Logger.Debug().Msg("Empty paste submitted, ignoring") + errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.") + return ec.HTML(http.StatusBadRequest, errtpl) } + // nolint if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" { c.Logger.Debug().Str("field value", params["paste-keep-for"][0]).Msg("'Keep paste for' field have invalid value") errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") + return ec.HTML(http.StatusBadRequest, errtpl) } // Verify captcha. if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) { c.Logger.Debug().Str("captcha ID", params["paste-captcha-id"][0]).Str("captcha solution", params["paste-captcha-solution"][0]).Msg("Invalid captcha solution") + errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.") + return ec.HTML(http.StatusBadRequest, errtpl) } @@ -70,26 +79,33 @@ func pastePOSTWebInterface(ec echo.Context) error { // Defaulting to "forever". keepFor := 0 keepForUnit := 0 + if params["paste-keep-for"][0] != "forever" { keepForUnitRegex := regexp.MustCompile("[Mmhd]") keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0] + var err error + keepFor, err = strconv.Atoi(keepForRaw) if err != nil { if params["paste-keep-for"][0] == "forever" { c.Logger.Debug().Msg("Keeping paste forever!") + keepFor = 0 } else { c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer") + errtpl := templater.GetErrorTemplate(ec, "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;).") + return ec.HTML(http.StatusBadRequest, errtpl) } } keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0] - keepForUnit = structs.PASTE_KEEPS_CORELLATION[keepForUnitRaw] + keepForUnit = structs.PasteKeepsCorellation[keepForUnitRaw] } + paste.KeepFor = keepFor paste.KeepForUnitType = keepForUnit @@ -107,6 +123,7 @@ func pastePOSTWebInterface(ec echo.Context) error { paste.Private = false privateCheckbox, privateCheckboxFound := params["paste-private"] pastePassword, pastePasswordFound := params["paste-password"] + if privateCheckboxFound && privateCheckbox[0] == "on" || pastePasswordFound && pastePassword[0] != "" { paste.Private = true } @@ -118,7 +135,9 @@ func pastePOSTWebInterface(ec echo.Context) error { id, err2 := c.Database.SavePaste(paste) if err2 != nil { c.Logger.Error().Err(err2).Msg("Failed to save paste") + errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.") + return ec.HTML(http.StatusBadRequest, errtpl) } diff --git a/domains/pastes/pastes_get.go b/domains/pastes/pastes_get.go index f542fb1..acf4b60 100644 --- a/domains/pastes/pastes_get.go +++ b/domains/pastes/pastes_get.go @@ -48,7 +48,9 @@ func pastesGET(ec echo.Context) error { } pageFromParamRaw := ec.Param("page") + var page = 1 + if pageFromParamRaw != "" { pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0] page, _ = strconv.Atoi(pageRaw) @@ -65,12 +67,15 @@ func pastesGET(ec echo.Context) error { // Show "No pastes to show" on any error for now. if err3 != nil { c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database") + noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.") + return ec.HTML(http.StatusOK, noPastesToShowTpl) } if len(pastes) > 0 { pastesString = "" + for i := range pastes { pasteDataMap := make(map[string]string) pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID) @@ -79,7 +84,9 @@ func pastesGET(ec echo.Context) error { // Get max 4 lines of each paste. pasteDataSplitted := strings.Split(pastes[i].Data, "\n") + var pasteData string + if len(pasteDataSplitted) < 4 { pasteData = pastes[i].Data } else { @@ -100,5 +107,5 @@ func pastesGET(ec echo.Context) error { pasteListTpl := templater.GetTemplate(ec, "pastelist_list.html", map[string]string{"pastes": pastesString, "pagination": paginationHTML}) - return ec.HTML(http.StatusOK, string(pasteListTpl)) + return ec.HTML(http.StatusOK, pasteListTpl) } diff --git a/internal/captcha/exported.go b/internal/captcha/exported.go index b062d4f..b36323d 100644 --- a/internal/captcha/exported.go +++ b/internal/captcha/exported.go @@ -39,7 +39,7 @@ var ( log zerolog.Logger ) -// New initializes captcha package and adds neccessary HTTP and API +// New initializes captcha package and adds necessary HTTP and API // endpoints. func New(cc *context.Context) { c = cc @@ -53,6 +53,7 @@ func New(cc *context.Context) { func NewCaptcha() string { s := captcha.New() log.Debug().Str("captcha string", s).Msg("Created new captcha string") + return s } diff --git a/internal/config/database.go b/internal/config/database.go index e224c19..7f71fdc 100644 --- a/internal/config/database.go +++ b/internal/config/database.go @@ -24,8 +24,8 @@ package config -// ConfigDatabase describes database configuration. -type ConfigDatabase struct { +// Database describes database configuration. +type Database struct { Type string `yaml:"type"` Path string `yaml:"path"` Address string `yaml:"address"` diff --git a/internal/config/http.go b/internal/config/http.go index 4171611..2120818 100644 --- a/internal/config/http.go +++ b/internal/config/http.go @@ -24,8 +24,8 @@ package config -// ConfigHTTP describes HTTP server configuration. -type ConfigHTTP struct { +// HTTP describes HTTP server configuration. +type HTTP struct { Address string `yaml:"address"` Port string `yaml:"port"` AllowInsecure bool `yaml:"allow_insecure"` diff --git a/internal/config/logging.go b/internal/config/logging.go index fd8aa42..df6539f 100644 --- a/internal/config/logging.go +++ b/internal/config/logging.go @@ -24,8 +24,8 @@ package config -// ConfigLogging describes logger configuration. -type ConfigLogging struct { +// Logging describes logger configuration. +type Logging struct { LogToFile bool `yaml:"log_to_file"` FileName string `yaml:"filename"` LogLevel string `yaml:"loglevel"` diff --git a/internal/config/pastes.go b/internal/config/pastes.go index a230f67..d7c7ec9 100644 --- a/internal/config/pastes.go +++ b/internal/config/pastes.go @@ -24,7 +24,7 @@ package config -// ConfigPastes describes pastes subsystem configuration. -type ConfigPastes struct { +// Pastes describes pastes subsystem configuration. +type Pastes struct { Pagination int `yaml:"pagination"` } diff --git a/internal/config/struct.go b/internal/config/struct.go index b802963..1b52ffa 100644 --- a/internal/config/struct.go +++ b/internal/config/struct.go @@ -24,10 +24,10 @@ package config -// ConfigStruct describes whole configuration. -type ConfigStruct struct { - Database ConfigDatabase `yaml:"database"` - Logging ConfigLogging `yaml:"logging"` - HTTP ConfigHTTP `yaml:"http"` - Pastes ConfigPastes `yaml:"pastes"` +// Struct describes whole configuration. +type Struct struct { + Database Database `yaml:"database"` + Logging Logging `yaml:"logging"` + HTTP HTTP `yaml:"http"` + Pastes Pastes `yaml:"pastes"` } diff --git a/internal/context/context.go b/internal/context/context.go index 83dcec9..d64a819 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -46,7 +46,7 @@ import ( // contains everything every part of application need, like configuration // access, logger, etc. type Context struct { - Config *config.ConfigStruct + Config *config.Struct Database databaseinterface.Interface Echo *echo.Echo Flagger *flagger.Flagger @@ -104,7 +104,7 @@ func (c *Context) LoadConfiguration() { c.Logger.Debug().Msgf("Configuration file path: %s", configPath) - c.Config = &config.ConfigStruct{} + c.Config = &config.Struct{} // Read configuration file. fileData, err2 := ioutil.ReadFile(normalizedConfigPath) diff --git a/internal/context/logger.go b/internal/context/logger.go index de9383b..718f750 100644 --- a/internal/context/logger.go +++ b/internal/context/logger.go @@ -15,6 +15,7 @@ import ( // Puts memory usage into log lines. func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) { var m runtime.MemStats + runtime.ReadMemStats(&m) e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024)) @@ -28,6 +29,7 @@ func (c *Context) initializeLogger() { output := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339} output.FormatLevel = func(i interface{}) string { var v string + if ii, ok := i.(string); ok { ii = strings.ToUpper(ii) switch ii { @@ -47,6 +49,7 @@ func (c *Context) initializeLogger() { v = ii } } + return fmt.Sprintf("| %s |", v) } @@ -59,6 +62,7 @@ func (c *Context) initializeLogger() { func (c *Context) initializeLoggerPost() { // Set log level. c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel) + switch c.Config.Logging.LogLevel { case "DEBUG": zerolog.SetGlobalLevel(zerolog.DebugLevel) diff --git a/internal/database/database.go b/internal/database/database.go index 288fc2b..48e78b6 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -30,7 +30,7 @@ import ( // local "go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles" - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" "go.dev.pztrn.name/fastpastebin/internal/database/dialects/mysql" "go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql" "go.dev.pztrn.name/fastpastebin/internal/structs" diff --git a/internal/database/dialects/flatfiles/exported.go b/internal/database/dialects/flatfiles/exported.go index c2ac7c0..3c384ce 100644 --- a/internal/database/dialects/flatfiles/exported.go +++ b/internal/database/dialects/flatfiles/exported.go @@ -27,7 +27,7 @@ package flatfiles import ( // local "go.dev.pztrn.name/fastpastebin/internal/context" - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" ) var ( @@ -38,5 +38,6 @@ var ( func New(cc *context.Context) { c = cc f = &FlatFiles{} + c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) } diff --git a/internal/database/dialects/flatfiles/flatfiles.go b/internal/database/dialects/flatfiles/flatfiles.go index 0b54ff5..4d7dfac 100644 --- a/internal/database/dialects/flatfiles/flatfiles.go +++ b/internal/database/dialects/flatfiles/flatfiles.go @@ -54,15 +54,18 @@ func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) { ff.writeMutex.Lock() pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json") c.Logger.Debug().Msgf("Trying to load paste data from '%s'...", pastePath) + pasteInBytes, err := ioutil.ReadFile(pastePath) if err != nil { c.Logger.Debug().Msgf("Failed to read paste from storage: %s", err.Error()) return nil, err } + c.Logger.Debug().Msgf("Loaded %d bytes: %s", len(pasteInBytes), string(pasteInBytes)) ff.writeMutex.Unlock() paste := &structs.Paste{} + err = json.Unmarshal(pasteInBytes, paste) if err != nil { c.Logger.Error().Msgf("Failed to parse paste: %s", err.Error()) @@ -83,6 +86,7 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { // Iteration one - get only public pastes. var publicPastes []*Index + for _, paste := range ff.pastesIndex { if !paste.Private { publicPastes = append(publicPastes, paste) @@ -92,7 +96,9 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { c.Logger.Debug().Msgf("%+v", publicPastes) // Iteration two - get paginated pastes. + // nolint var pastesData []structs.Paste + for idx, paste := range publicPastes { if len(pastesData) == c.Config.Pastes.Pagination { break @@ -107,10 +113,12 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) { c.Logger.Debug().Msgf("Paste with index %d isn't in pagination query: too high index", idx) break } + c.Logger.Debug().Msgf("Getting paste data (ID: %d, index: %d)", paste.ID, idx) // Get paste data. pasteData := &structs.Paste{} + pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json")) if err != nil { c.Logger.Error().Msgf("Failed to read paste data: %s", err.Error()) @@ -160,13 +168,16 @@ func (ff *FlatFiles) Initialize() { curUser, err := user.Current() if err != nil { c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!") + path = strings.Replace(path, "~", "/", -1) } + path = strings.Replace(path, "~", curUser.HomeDir, -1) } path, _ = filepath.Abs(path) ff.path = path + c.Logger.Debug().Msgf("Storage path is now: %s", ff.path) // Create directory if necessary. @@ -209,29 +220,35 @@ func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) { // Write paste data on disk. filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes")) pasteID := len(filesOnDisk) + 1 - c.Logger.Debug().Msgf("Writing paste to disk, ID will be " + strconv.Itoa(pasteID)) p.ID = pasteID + + c.Logger.Debug().Msgf("Writing paste to disk, ID will be " + strconv.Itoa(pasteID)) + data, err := json.Marshal(p) if err != nil { ff.writeMutex.Unlock() return 0, err } + err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644) if err != nil { ff.writeMutex.Unlock() return 0, err } + // Add it to cache. indexData := &Index{} indexData.ID = pasteID indexData.Private = p.Private ff.pastesIndex = append(ff.pastesIndex, indexData) ff.writeMutex.Unlock() + return int64(pasteID), nil } func (ff *FlatFiles) Shutdown() { c.Logger.Info().Msg("Saving indexes...") + indexData, err := json.Marshal(ff.pastesIndex) if err != nil { c.Logger.Error().Msgf("Failed to encode index data into JSON: %s", err.Error()) diff --git a/internal/database/dialects/mysql/exported.go b/internal/database/dialects/mysql/exported.go index 20f4e41..9e06184 100644 --- a/internal/database/dialects/mysql/exported.go +++ b/internal/database/dialects/mysql/exported.go @@ -27,7 +27,7 @@ package mysql import ( // local "go.dev.pztrn.name/fastpastebin/internal/context" - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" ) var ( @@ -38,5 +38,6 @@ var ( func New(cc *context.Context) { c = cc d = &Database{} + c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) } diff --git a/internal/database/dialects/mysql/migrations/1_initial.go b/internal/database/dialects/mysql/migrations/1_initial.go index 66c6360..c8dd86a 100644 --- a/internal/database/dialects/mysql/migrations/1_initial.go +++ b/internal/database/dialects/mysql/migrations/1_initial.go @@ -30,6 +30,7 @@ import ( ) func InitialUp(tx *sql.Tx) error { + // nolint _, err := tx.Exec("CREATE TABLE `pastes` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Paste ID', `title` text NOT NULL COMMENT 'Paste title', `data` longtext NOT NULL COMMENT 'Paste data', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Paste creation timestamp', `keep_for` int(4) NOT NULL DEFAULT 1 COMMENT 'Keep for integer. 0 - forever.', `keep_for_unit_type` int(1) NOT NULL DEFAULT 1 COMMENT 'Keep for unit type. 1 - minutes, 2 - hours, 3 - days, 4 - months.', PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='Pastes';") if err != nil { return err diff --git a/internal/database/dialects/mysql/mysqldatabase.go b/internal/database/dialects/mysql/mysqldatabase.go index e7bd25c..34d3e68 100644 --- a/internal/database/dialects/mysql/mysqldatabase.go +++ b/internal/database/dialects/mysql/mysqldatabase.go @@ -69,7 +69,9 @@ func (db *Database) GetDatabaseConnection() *sql.DB { // GetPaste returns a single paste by ID. func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { db.check() + p := &structs.Paste{} + err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID) if err != nil { return nil, err @@ -80,8 +82,11 @@ func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { db.check() - var pastesRaw []structs.Paste - var pastes []structs.Paste + + var ( + pastesRaw []structs.Paste + pastes []structs.Paste + ) // Pagination. var startPagination = 0 @@ -105,8 +110,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { func (db *Database) GetPastesPages() int { db.check() - var pastesRaw []structs.Paste - var pastes []structs.Paste + + var ( + pastesRaw []structs.Paste + pastes []structs.Paste + ) + err := db.db.Get(&pastesRaw, "SELECT * FROM `pastes` WHERE private != true") if err != nil { return 1 @@ -155,6 +164,7 @@ func (db *Database) Initialize() { _ = dbConn.MustExec("SET @@session.time_zone='+00:00';") c.Logger.Info().Msg("Database connection established") + db.db = dbConn // Perform migrations. @@ -164,6 +174,7 @@ func (db *Database) Initialize() { func (db *Database) SavePaste(p *structs.Paste) (int64, error) { db.check() + result, err := db.db.NamedExec("INSERT INTO `pastes` (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt)", p) if err != nil { return 0, err diff --git a/internal/database/dialects/postgresql/exported.go b/internal/database/dialects/postgresql/exported.go index 3793d85..2fd991b 100644 --- a/internal/database/dialects/postgresql/exported.go +++ b/internal/database/dialects/postgresql/exported.go @@ -27,7 +27,7 @@ package postgresql import ( // local "go.dev.pztrn.name/fastpastebin/internal/context" - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" ) var ( @@ -38,5 +38,6 @@ var ( func New(cc *context.Context) { c = cc d = &Database{} + c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) } diff --git a/internal/database/dialects/postgresql/postgresqldatabase.go b/internal/database/dialects/postgresql/postgresqldatabase.go index 61118f3..bb93397 100644 --- a/internal/database/dialects/postgresql/postgresqldatabase.go +++ b/internal/database/dialects/postgresql/postgresqldatabase.go @@ -36,6 +36,7 @@ import ( // other "github.com/jmoiron/sqlx" + // postgresql adapter _ "github.com/lib/pq" ) @@ -70,7 +71,9 @@ func (db *Database) GetDatabaseConnection() *sql.DB { // GetPaste returns a single paste by ID. func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { db.check() + p := &structs.Paste{} + err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID) if err != nil { return nil, err @@ -88,8 +91,11 @@ func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { db.check() - var pastesRaw []structs.Paste - var pastes []structs.Paste + + var ( + pastesRaw []structs.Paste + pastes []structs.Paste + ) // Pagination. var startPagination = 0 @@ -119,8 +125,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) { func (db *Database) GetPastesPages() int { db.check() - var pastesRaw []structs.Paste - var pastes []structs.Paste + + var ( + pastesRaw []structs.Paste + pastes []structs.Paste + ) + err := db.db.Get(&pastesRaw, "SELECT * FROM pastes WHERE private != true") if err != nil { return 1 @@ -164,6 +174,7 @@ func (db *Database) Initialize() { } c.Logger.Info().Msg("Database connection established") + db.db = dbConn // Perform migrations. @@ -173,12 +184,14 @@ func (db *Database) Initialize() { func (db *Database) SavePaste(p *structs.Paste) (int64, error) { db.check() + stmt, err := db.db.PrepareNamed("INSERT INTO pastes (title, data, created_at, keep_for, keep_for_unit_type, language, private, password, password_salt) VALUES (:title, :data, :created_at, :keep_for, :keep_for_unit_type, :language, :private, :password, :password_salt) RETURNING id") if err != nil { return 0, err } var id int64 + err = stmt.Get(&id, p) if err != nil { return 0, err diff --git a/internal/database/exported.go b/internal/database/exported.go index ef1f2fa..30678aa 100644 --- a/internal/database/exported.go +++ b/internal/database/exported.go @@ -27,7 +27,7 @@ package database import ( // local "go.dev.pztrn.name/fastpastebin/internal/context" - "go.dev.pztrn.name/fastpastebin/internal/database/interface" + databaseinterface "go.dev.pztrn.name/fastpastebin/internal/database/interface" ) var ( @@ -39,5 +39,6 @@ var ( func New(cc *context.Context) { c = cc d = &Database{} + c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{})) } diff --git a/internal/database/handler.go b/internal/database/handler.go index 4eae7dd..62f76b6 100644 --- a/internal/database/handler.go +++ b/internal/database/handler.go @@ -29,7 +29,8 @@ import ( "database/sql" // local - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" "go.dev.pztrn.name/fastpastebin/internal/structs" ) diff --git a/internal/database/interface/databaseinterface.go b/internal/database/interface/databaseinterface.go index b2c8342..dfadcaf 100644 --- a/internal/database/interface/databaseinterface.go +++ b/internal/database/interface/databaseinterface.go @@ -29,7 +29,7 @@ import ( "database/sql" // local - "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" + dialectinterface "go.dev.pztrn.name/fastpastebin/internal/database/dialects/interface" "go.dev.pztrn.name/fastpastebin/internal/structs" ) diff --git a/internal/pagination/exported.go b/internal/pagination/exported.go index 54768ce..4014eae 100644 --- a/internal/pagination/exported.go +++ b/internal/pagination/exported.go @@ -10,6 +10,7 @@ import ( ) // CreateHTML creates pagination HTML based on passed parameters. +// nolint func CreateHTML(currentPage int, pages int, linksBase string) string { // Load templates. paginationHTMLRaw, err := static.ReadFile("pagination.html") @@ -38,12 +39,15 @@ func CreateHTML(currentPage int, pages int, linksBase string) string { paginationString = strings.Replace(string(paginationLinkCurrentRaw), "{pageNum}", strconv.Itoa(currentPage), -1) } else { paginationString = strings.Replace(string(paginationLinkRaw), "{pageNum}", "1", -1) - paginationString = strings.Replace(string(paginationString), "{paginationLink}", linksBase+"1", -1) + paginationString = strings.Replace(paginationString, "{paginationLink}", linksBase+"1", -1) } - var ellipsisStartAdded = false - var ellipsisEndAdded = false - i := 2 + var ( + ellipsisStartAdded = false + ellipsisEndAdded = false + i = 2 + ) + for i <= pages { if pages > 5 { if currentPage-3 < i && currentPage+3 > i || i == pages { diff --git a/internal/structs/paste.go b/internal/structs/paste.go index 2858c6a..83a1301 100644 --- a/internal/structs/paste.go +++ b/internal/structs/paste.go @@ -36,22 +36,22 @@ import ( ) const ( - PASTE_KEEP_FOREVER = 0 - PASTE_KEEP_FOR_MINUTES = 1 - PASTE_KEEP_FOR_HOURS = 2 - PASTE_KEEP_FOR_DAYS = 3 - PASTE_KEEP_FOR_MONTHS = 4 + PasteKeepForever = 0 + PasteKeepForMinutes = 1 + PasteKeepForHours = 2 + PasteKeepForDays = 3 + PasteKeepForMonths = 4 charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) var ( - PASTE_KEEPS_CORELLATION = map[string]int{ - "M": PASTE_KEEP_FOR_MINUTES, - "h": PASTE_KEEP_FOR_HOURS, - "d": PASTE_KEEP_FOR_DAYS, - "m": PASTE_KEEP_FOR_MONTHS, - "forever": PASTE_KEEP_FOREVER, + PasteKeepsCorellation = map[string]int{ + "M": PasteKeepForMinutes, + "h": PasteKeepForHours, + "d": PasteKeepForDays, + "m": PasteKeepForMonths, + "forever": PasteKeepForever, } ) @@ -74,6 +74,7 @@ func (p *Paste) CreatePassword(password string) error { // Create salt - random string. seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) saltBytes := make([]byte, 64) + for i := range saltBytes { saltBytes[i] = charset[seededRand.Intn(len(charset))] } @@ -86,6 +87,7 @@ func (p *Paste) CreatePassword(password string) error { if err != nil { return err } + passwordHashBytes := sha256.Sum256(passwordCrypted) p.Password = fmt.Sprintf("%x", passwordHashBytes) @@ -100,16 +102,17 @@ func (p *Paste) GenerateCryptedCookieValue() string { func (p *Paste) GetExpirationTime() time.Time { var expirationTime time.Time + switch p.KeepForUnitType { - case PASTE_KEEP_FOREVER: + case PasteKeepForever: expirationTime = time.Now().UTC().Add(time.Hour * 1) - case PASTE_KEEP_FOR_MINUTES: + case PasteKeepForMinutes: expirationTime = p.CreatedAt.Add(time.Minute * time.Duration(p.KeepFor)) - case PASTE_KEEP_FOR_HOURS: + case PasteKeepForHours: expirationTime = p.CreatedAt.Add(time.Hour * time.Duration(p.KeepFor)) - case PASTE_KEEP_FOR_DAYS: + case PasteKeepForDays: expirationTime = p.CreatedAt.Add(time.Hour * 24 * time.Duration(p.KeepFor)) - case PASTE_KEEP_FOR_MONTHS: + case PasteKeepForMonths: expirationTime = p.CreatedAt.Add(time.Hour * 24 * 30 * time.Duration(p.KeepFor)) } @@ -121,11 +124,7 @@ func (p *Paste) IsExpired() bool { curTime := time.Now().UTC() expirationTime := p.GetExpirationTime() - if curTime.Sub(expirationTime).Seconds() > 0 { - return true - } - - return false + return curTime.Sub(expirationTime).Seconds() > 0 } // VerifyPassword verifies that provided password is valid. @@ -135,12 +134,9 @@ func (p *Paste) VerifyPassword(password string) bool { if err != nil { return false } + passwordHashBytes := sha256.Sum256(passwordCrypted) providedPassword := fmt.Sprintf("%x", passwordHashBytes) - if providedPassword == p.Password { - return true - } - - return false + return providedPassword == p.Password }