This commit is contained in:
Stanislav Nikitin 2019-12-22 01:17:18 +05:00
parent da4bc379d8
commit a52b18ffe4
No known key found for this signature in database
GPG Key ID: 106900B32F8192EE
31 changed files with 202 additions and 82 deletions

View File

@ -20,14 +20,18 @@ steps:
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
- golangci-lint run - golangci-lint run
depends_on:
- notify-start
- name: test - name: test
image: golang:1.13.1-alpine image: golang:1.13.5-alpine
environment: environment:
GOFLAGS: -mod=vendor GOFLAGS: -mod=vendor
CGO_ENABLED: 0 CGO_ENABLED: 0
commands: commands:
- go test ./... - go test ./...
depends_on:
- notify-start
- name: docker - name: docker
image: plugins/docker image: plugins/docker
@ -40,6 +44,9 @@ steps:
from_secret: dockerhub_password from_secret: dockerhub_password
repo: pztrn/fastpastebin repo: pztrn/fastpastebin
auto_tag: true auto_tag: true
depends_on:
- lint
- test
- name: notify-end - name: notify-end
when: when:
@ -58,3 +65,5 @@ steps:
{{ else }} {{ else }}
**{{repo.name}}#{{build.number}}@{{build.commit}}** failed. See {{build.link}}. **{{repo.name}}#{{build.number}}@{{build.commit}}** failed. See {{build.link}}.
{{/success}}" {{/success}}"
depends_on:
- docker

18
.golangci.yml Normal file
View File

@ -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

View File

@ -31,7 +31,7 @@ import (
"syscall" "syscall"
// local // 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/indexpage"
"go.dev.pztrn.name/fastpastebin/domains/pastes" "go.dev.pztrn.name/fastpastebin/domains/pastes"
"go.dev.pztrn.name/fastpastebin/internal/captcha" "go.dev.pztrn.name/fastpastebin/internal/captcha"
@ -61,14 +61,16 @@ func main() {
captcha.New(c) captcha.New(c)
database_not_available.New(c) dbnotavailable.New(c)
indexpage.New(c) indexpage.New(c)
pastes.New(c) pastes.New(c)
// CTRL+C handler. // CTRL+C handler.
signalHandler := make(chan os.Signal, 1) signalHandler := make(chan os.Signal, 1)
shutdownDone := make(chan bool, 1) shutdownDone := make(chan bool, 1)
signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM) signal.Notify(signalHandler, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
<-signalHandler <-signalHandler
c.Shutdown() c.Shutdown()

View File

@ -22,7 +22,7 @@
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package database_not_available package dbnotavailable
import ( import (
// stdlib // stdlib

View File

@ -22,7 +22,7 @@
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package database_not_available package dbnotavailable
import ( import (
// local // local
@ -33,7 +33,7 @@ var (
c *context.Context c *context.Context
) )
// New initializes pastes package and adds neccessary HTTP and API // New initializes pastes package and adds necessary HTTP and API
// endpoints. // endpoints.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc

View File

@ -33,7 +33,7 @@ var (
c *context.Context c *context.Context
) )
// New initializes pastes package and adds neccessary HTTP and API // New initializes pastes package and adds necessary HTTP and API
// endpoints. // endpoints.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc

View File

@ -40,7 +40,7 @@ var (
c *context.Context c *context.Context
) )
// New initializes pastes package and adds neccessary HTTP and API // New initializes pastes package and adds necessary HTTP and API
// endpoints. // endpoints.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc

View File

@ -34,12 +34,6 @@ const (
// for some cases, e.g. public paste won't check for timestamp and cookie // for some cases, e.g. public paste won't check for timestamp and cookie
// value (they both will be ignored), but private will. // value (they both will be ignored), but private will.
func pasteGetData(pasteID int, timestamp int64, cookieValue string) (*structs.Paste, string) { 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. // Get paste.
paste, err1 := c.Database.GetPaste(pasteID) paste, err1 := c.Database.GetPaste(pasteID)
if err1 != nil { 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 // If passed timestamp is invalid (isn't a real UNIX timestamp) we
// will show 404 Not Found error and spam about that in logs. // will show 404 Not Found error and spam about that in logs.
var timestamp int64 var timestamp int64
tsProvidedStr := ec.Param("timestamp") tsProvidedStr := ec.Param("timestamp")
if tsProvidedStr != "" { if tsProvidedStr != "" {
tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64) tsProvided, err := strconv.ParseInt(tsProvidedStr, 10, 64)
if err != nil { if err != nil {
c.Logger.Error().Err(err).Int("paste ID", pasteID).Int64("provided timestamp", tsProvided).Msg("Invalid timestamp provided for getting private paste") 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") errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDStr+" not found")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} else {
timestamp = tsProvided
} }
timestamp = tsProvided
} }
// Check if we have "PASTE-PASTEID" cookie defined. It is required // Check if we have "PASTE-PASTEID" cookie defined. It is required
// for private pastes. // for private pastes.
var cookieValue string var cookieValue string
cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr) cookie, err1 := ec.Cookie("PASTE-" + pasteIDStr)
if err1 == nil { if err1 == nil {
cookieValue = cookie.Value cookieValue = cookie.Value
@ -137,6 +135,7 @@ func pasteGETWebInterface(ec echo.Context) error {
if paste.KeepFor != 0 && paste.KeepForUnitType != 0 { if paste.KeepFor != 0 && paste.KeepForUnitType != 0 {
pasteExpirationString = paste.GetExpirationTime().Format("2006-01-02 @ 15:04:05") + " UTC" pasteExpirationString = paste.GetExpirationTime().Format("2006-01-02 @ 15:04:05") + " UTC"
} }
pasteData["pasteExpiration"] = pasteExpirationString pasteData["pasteExpiration"] = pasteExpirationString
if paste.Private { if paste.Private {
@ -170,10 +169,12 @@ func pasteGETWebInterface(ec echo.Context) error {
} }
// Create buffer and format into it. // Create buffer and format into it.
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err4 := formatter.Format(buf, style, lexered) err4 := formatter.Format(buf, style, lexered)
if err4 != nil { if err4 != nil {
c.Logger.Error().Err(err4).Msg("Failed to format paste data") c.Logger.Error().Err(err4).Msg("Failed to format paste data")
} }
pasteData["pastedata"] = buf.String() pasteData["pastedata"] = buf.String()
// Get template and format it. // Get template and format it.
@ -194,7 +195,9 @@ func pastePasswordedVerifyGet(ec echo.Context) error {
paste, err1 := c.Database.GetPaste(pasteID) paste, err1 := c.Database.GetPaste(pasteID)
if err1 != nil { if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data") c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste data")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found") errtpl := templater.GetErrorTemplate(ec, "Paste #"+pasteIDRaw+" not found")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }
@ -229,6 +232,7 @@ func pastePasswordedVerifyGet(ec echo.Context) error {
func pastePasswordedVerifyPost(ec echo.Context) error { func pastePasswordedVerifyPost(ec echo.Context) error {
// We should check if database connection available. // We should check if database connection available.
dbConn := c.Database.GetDatabaseConnection() dbConn := c.Database.GetDatabaseConnection()
// nolint
if c.Config.Database.Type != "flatfiles" && dbConn == nil { if c.Config.Database.Type != "flatfiles" && dbConn == nil {
return ec.Redirect(http.StatusFound, "/database_not_available") return ec.Redirect(http.StatusFound, "/database_not_available")
} }
@ -245,13 +249,16 @@ func pastePasswordedVerifyPost(ec echo.Context) error {
if err1 != nil { if err1 != nil {
c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste") c.Logger.Error().Err(err1).Int("paste ID", pasteID).Msg("Failed to get paste")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found") errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }
params, err2 := ec.FormParams() params, err2 := ec.FormParams()
if err2 != nil { if err2 != nil {
c.Logger.Debug().Msg("No form parameters passed") c.Logger.Debug().Msg("No form parameters passed")
errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found") errtpl := templater.GetErrorTemplate(ec, "Paste #"+strconv.Itoa(pasteID)+" not found")
return ec.HTML(http.StatusBadRequest, errtpl) 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.") 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. // 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. // Check if we have a private paste and it's parameters are correct.
if paste.Private { if paste.Private {
tsProvidedStr := ec.Param("timestamp") tsProvidedStr := ec.Param("timestamp")
tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64) tsProvided, err2 := strconv.ParseInt(tsProvidedStr, 10, 64)
if err2 != nil { if err2 != nil {
c.Logger.Error().Err(err2).Int("paste ID", pasteID).Str("provided timestamp", tsProvidedStr).Msg("Invalid timestamp provided for getting private paste") 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") return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
} }
pasteTs := paste.CreatedAt.Unix() pasteTs := paste.CreatedAt.Unix()
if tsProvided != pasteTs { 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") 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") return ec.String(http.StatusBadRequest, "Paste #"+pasteIDRaw+" not found")
} }
} }
// nolint
// ToDo: figure out how to handle passworded pastes here. // ToDo: figure out how to handle passworded pastes here.
// Return error for now. // Return error for now.
if paste.Password != "" { if paste.Password != "" {

View File

@ -31,28 +31,37 @@ func pastePOSTWebInterface(ec echo.Context) error {
params, err := ec.FormParams() params, err := ec.FormParams()
if err != nil { if err != nil {
c.Logger.Error().Msg("Passed paste form is empty") c.Logger.Error().Msg("Passed paste form is empty")
errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste") errtpl := templater.GetErrorTemplate(ec, "Cannot create empty paste")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }
c.Logger.Debug().Msgf("Received parameters: %+v", params) c.Logger.Debug().Msgf("Received parameters: %+v", params)
// Do nothing if paste contents is empty. // Do nothing if paste contents is empty.
if len(params["paste-contents"][0]) == 0 { if len(params["paste-contents"][0]) == 0 {
c.Logger.Debug().Msg("Empty paste submitted, ignoring") c.Logger.Debug().Msg("Empty paste submitted, ignoring")
errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.") errtpl := templater.GetErrorTemplate(ec, "Empty pastes aren't allowed.")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }
// nolint
if !strings.ContainsAny(params["paste-keep-for"][0], "Mmhd") && params["paste-keep-for"][0] != "forever" { 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") 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 ;).") 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) return ec.HTML(http.StatusBadRequest, errtpl)
} }
// Verify captcha. // Verify captcha.
if !captcha.Verify(params["paste-captcha-id"][0], params["paste-captcha-solution"][0]) { 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") 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.") errtpl := templater.GetErrorTemplate(ec, "Invalid captcha solution.")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }
@ -70,26 +79,33 @@ func pastePOSTWebInterface(ec echo.Context) error {
// Defaulting to "forever". // Defaulting to "forever".
keepFor := 0 keepFor := 0
keepForUnit := 0 keepForUnit := 0
if params["paste-keep-for"][0] != "forever" { if params["paste-keep-for"][0] != "forever" {
keepForUnitRegex := regexp.MustCompile("[Mmhd]") keepForUnitRegex := regexp.MustCompile("[Mmhd]")
keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0] keepForRaw := regexInts.FindAllString(params["paste-keep-for"][0], 1)[0]
var err error var err error
keepFor, err = strconv.Atoi(keepForRaw) keepFor, err = strconv.Atoi(keepForRaw)
if err != nil { if err != nil {
if params["paste-keep-for"][0] == "forever" { if params["paste-keep-for"][0] == "forever" {
c.Logger.Debug().Msg("Keeping paste forever!") c.Logger.Debug().Msg("Keeping paste forever!")
keepFor = 0 keepFor = 0
} else { } else {
c.Logger.Debug().Err(err).Msg("Failed to parse 'Keep for' integer") 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 ;).") 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) return ec.HTML(http.StatusBadRequest, errtpl)
} }
} }
keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0] keepForUnitRaw := keepForUnitRegex.FindAllString(params["paste-keep-for"][0], 1)[0]
keepForUnit = structs.PASTE_KEEPS_CORELLATION[keepForUnitRaw] keepForUnit = structs.PasteKeepsCorellation[keepForUnitRaw]
} }
paste.KeepFor = keepFor paste.KeepFor = keepFor
paste.KeepForUnitType = keepForUnit paste.KeepForUnitType = keepForUnit
@ -107,6 +123,7 @@ func pastePOSTWebInterface(ec echo.Context) error {
paste.Private = false paste.Private = false
privateCheckbox, privateCheckboxFound := params["paste-private"] privateCheckbox, privateCheckboxFound := params["paste-private"]
pastePassword, pastePasswordFound := params["paste-password"] pastePassword, pastePasswordFound := params["paste-password"]
if privateCheckboxFound && privateCheckbox[0] == "on" || pastePasswordFound && pastePassword[0] != "" { if privateCheckboxFound && privateCheckbox[0] == "on" || pastePasswordFound && pastePassword[0] != "" {
paste.Private = true paste.Private = true
} }
@ -118,7 +135,9 @@ func pastePOSTWebInterface(ec echo.Context) error {
id, err2 := c.Database.SavePaste(paste) id, err2 := c.Database.SavePaste(paste)
if err2 != nil { if err2 != nil {
c.Logger.Error().Err(err2).Msg("Failed to save paste") c.Logger.Error().Err(err2).Msg("Failed to save paste")
errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.") errtpl := templater.GetErrorTemplate(ec, "Failed to save paste. Please, try again later.")
return ec.HTML(http.StatusBadRequest, errtpl) return ec.HTML(http.StatusBadRequest, errtpl)
} }

View File

@ -48,7 +48,9 @@ func pastesGET(ec echo.Context) error {
} }
pageFromParamRaw := ec.Param("page") pageFromParamRaw := ec.Param("page")
var page = 1 var page = 1
if pageFromParamRaw != "" { if pageFromParamRaw != "" {
pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0] pageRaw := regexInts.FindAllString(pageFromParamRaw, 1)[0]
page, _ = strconv.Atoi(pageRaw) page, _ = strconv.Atoi(pageRaw)
@ -65,12 +67,15 @@ func pastesGET(ec echo.Context) error {
// Show "No pastes to show" on any error for now. // Show "No pastes to show" on any error for now.
if err3 != nil { if err3 != nil {
c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database") c.Logger.Error().Err(err3).Msg("Failed to get pastes list from database")
noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.") noPastesToShowTpl := templater.GetErrorTemplate(ec, "No pastes to show.")
return ec.HTML(http.StatusOK, noPastesToShowTpl) return ec.HTML(http.StatusOK, noPastesToShowTpl)
} }
if len(pastes) > 0 { if len(pastes) > 0 {
pastesString = "" pastesString = ""
for i := range pastes { for i := range pastes {
pasteDataMap := make(map[string]string) pasteDataMap := make(map[string]string)
pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID) pasteDataMap["pasteID"] = strconv.Itoa(pastes[i].ID)
@ -79,7 +84,9 @@ func pastesGET(ec echo.Context) error {
// Get max 4 lines of each paste. // Get max 4 lines of each paste.
pasteDataSplitted := strings.Split(pastes[i].Data, "\n") pasteDataSplitted := strings.Split(pastes[i].Data, "\n")
var pasteData string var pasteData string
if len(pasteDataSplitted) < 4 { if len(pasteDataSplitted) < 4 {
pasteData = pastes[i].Data pasteData = pastes[i].Data
} else { } 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}) 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)
} }

View File

@ -39,7 +39,7 @@ var (
log zerolog.Logger log zerolog.Logger
) )
// New initializes captcha package and adds neccessary HTTP and API // New initializes captcha package and adds necessary HTTP and API
// endpoints. // endpoints.
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
@ -53,6 +53,7 @@ func New(cc *context.Context) {
func NewCaptcha() string { func NewCaptcha() string {
s := captcha.New() s := captcha.New()
log.Debug().Str("captcha string", s).Msg("Created new captcha string") log.Debug().Str("captcha string", s).Msg("Created new captcha string")
return s return s
} }

View File

@ -24,8 +24,8 @@
package config package config
// ConfigDatabase describes database configuration. // Database describes database configuration.
type ConfigDatabase struct { type Database struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Path string `yaml:"path"` Path string `yaml:"path"`
Address string `yaml:"address"` Address string `yaml:"address"`

View File

@ -24,8 +24,8 @@
package config package config
// ConfigHTTP describes HTTP server configuration. // HTTP describes HTTP server configuration.
type ConfigHTTP struct { type HTTP struct {
Address string `yaml:"address"` Address string `yaml:"address"`
Port string `yaml:"port"` Port string `yaml:"port"`
AllowInsecure bool `yaml:"allow_insecure"` AllowInsecure bool `yaml:"allow_insecure"`

View File

@ -24,8 +24,8 @@
package config package config
// ConfigLogging describes logger configuration. // Logging describes logger configuration.
type ConfigLogging struct { type Logging struct {
LogToFile bool `yaml:"log_to_file"` LogToFile bool `yaml:"log_to_file"`
FileName string `yaml:"filename"` FileName string `yaml:"filename"`
LogLevel string `yaml:"loglevel"` LogLevel string `yaml:"loglevel"`

View File

@ -24,7 +24,7 @@
package config package config
// ConfigPastes describes pastes subsystem configuration. // Pastes describes pastes subsystem configuration.
type ConfigPastes struct { type Pastes struct {
Pagination int `yaml:"pagination"` Pagination int `yaml:"pagination"`
} }

View File

@ -24,10 +24,10 @@
package config package config
// ConfigStruct describes whole configuration. // Struct describes whole configuration.
type ConfigStruct struct { type Struct struct {
Database ConfigDatabase `yaml:"database"` Database Database `yaml:"database"`
Logging ConfigLogging `yaml:"logging"` Logging Logging `yaml:"logging"`
HTTP ConfigHTTP `yaml:"http"` HTTP HTTP `yaml:"http"`
Pastes ConfigPastes `yaml:"pastes"` Pastes Pastes `yaml:"pastes"`
} }

View File

@ -46,7 +46,7 @@ import (
// contains everything every part of application need, like configuration // contains everything every part of application need, like configuration
// access, logger, etc. // access, logger, etc.
type Context struct { type Context struct {
Config *config.ConfigStruct Config *config.Struct
Database databaseinterface.Interface Database databaseinterface.Interface
Echo *echo.Echo Echo *echo.Echo
Flagger *flagger.Flagger Flagger *flagger.Flagger
@ -104,7 +104,7 @@ func (c *Context) LoadConfiguration() {
c.Logger.Debug().Msgf("Configuration file path: %s", configPath) c.Logger.Debug().Msgf("Configuration file path: %s", configPath)
c.Config = &config.ConfigStruct{} c.Config = &config.Struct{}
// Read configuration file. // Read configuration file.
fileData, err2 := ioutil.ReadFile(normalizedConfigPath) fileData, err2 := ioutil.ReadFile(normalizedConfigPath)

View File

@ -15,6 +15,7 @@ import (
// Puts memory usage into log lines. // Puts memory usage into log lines.
func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) { func (c *Context) getMemoryUsage(e *zerolog.Event, level zerolog.Level, message string) {
var m runtime.MemStats var m runtime.MemStats
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
e.Str("memalloc", fmt.Sprintf("%dMB", m.Alloc/1024/1024)) 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 := zerolog.ConsoleWriter{Out: os.Stdout, NoColor: false, TimeFormat: time.RFC3339}
output.FormatLevel = func(i interface{}) string { output.FormatLevel = func(i interface{}) string {
var v string var v string
if ii, ok := i.(string); ok { if ii, ok := i.(string); ok {
ii = strings.ToUpper(ii) ii = strings.ToUpper(ii)
switch ii { switch ii {
@ -47,6 +49,7 @@ func (c *Context) initializeLogger() {
v = ii v = ii
} }
} }
return fmt.Sprintf("| %s |", v) return fmt.Sprintf("| %s |", v)
} }
@ -59,6 +62,7 @@ func (c *Context) initializeLogger() {
func (c *Context) initializeLoggerPost() { func (c *Context) initializeLoggerPost() {
// Set log level. // Set log level.
c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel) c.Logger.Info().Msgf("Setting logger level: %s", c.Config.Logging.LogLevel)
switch c.Config.Logging.LogLevel { switch c.Config.Logging.LogLevel {
case "DEBUG": case "DEBUG":
zerolog.SetGlobalLevel(zerolog.DebugLevel) zerolog.SetGlobalLevel(zerolog.DebugLevel)

View File

@ -30,7 +30,7 @@ import (
// local // local
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/flatfiles" "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/mysql"
"go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql" "go.dev.pztrn.name/fastpastebin/internal/database/dialects/postgresql"
"go.dev.pztrn.name/fastpastebin/internal/structs" "go.dev.pztrn.name/fastpastebin/internal/structs"

View File

@ -27,7 +27,7 @@ package flatfiles
import ( import (
// local // local
"go.dev.pztrn.name/fastpastebin/internal/context" "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 ( var (
@ -38,5 +38,6 @@ var (
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
f = &FlatFiles{} f = &FlatFiles{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
} }

View File

@ -54,15 +54,18 @@ func (ff *FlatFiles) GetPaste(pasteID int) (*structs.Paste, error) {
ff.writeMutex.Lock() ff.writeMutex.Lock()
pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json") pastePath := filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json")
c.Logger.Debug().Msgf("Trying to load paste data from '%s'...", pastePath) c.Logger.Debug().Msgf("Trying to load paste data from '%s'...", pastePath)
pasteInBytes, err := ioutil.ReadFile(pastePath) pasteInBytes, err := ioutil.ReadFile(pastePath)
if err != nil { if err != nil {
c.Logger.Debug().Msgf("Failed to read paste from storage: %s", err.Error()) c.Logger.Debug().Msgf("Failed to read paste from storage: %s", err.Error())
return nil, err return nil, err
} }
c.Logger.Debug().Msgf("Loaded %d bytes: %s", len(pasteInBytes), string(pasteInBytes)) c.Logger.Debug().Msgf("Loaded %d bytes: %s", len(pasteInBytes), string(pasteInBytes))
ff.writeMutex.Unlock() ff.writeMutex.Unlock()
paste := &structs.Paste{} paste := &structs.Paste{}
err = json.Unmarshal(pasteInBytes, paste) err = json.Unmarshal(pasteInBytes, paste)
if err != nil { if err != nil {
c.Logger.Error().Msgf("Failed to parse paste: %s", err.Error()) 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. // Iteration one - get only public pastes.
var publicPastes []*Index var publicPastes []*Index
for _, paste := range ff.pastesIndex { for _, paste := range ff.pastesIndex {
if !paste.Private { if !paste.Private {
publicPastes = append(publicPastes, paste) publicPastes = append(publicPastes, paste)
@ -92,7 +96,9 @@ func (ff *FlatFiles) GetPagedPastes(page int) ([]structs.Paste, error) {
c.Logger.Debug().Msgf("%+v", publicPastes) c.Logger.Debug().Msgf("%+v", publicPastes)
// Iteration two - get paginated pastes. // Iteration two - get paginated pastes.
// nolint
var pastesData []structs.Paste var pastesData []structs.Paste
for idx, paste := range publicPastes { for idx, paste := range publicPastes {
if len(pastesData) == c.Config.Pastes.Pagination { if len(pastesData) == c.Config.Pastes.Pagination {
break 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) c.Logger.Debug().Msgf("Paste with index %d isn't in pagination query: too high index", idx)
break break
} }
c.Logger.Debug().Msgf("Getting paste data (ID: %d, index: %d)", paste.ID, idx) c.Logger.Debug().Msgf("Getting paste data (ID: %d, index: %d)", paste.ID, idx)
// Get paste data. // Get paste data.
pasteData := &structs.Paste{} pasteData := &structs.Paste{}
pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json")) pasteRawData, err := ioutil.ReadFile(filepath.Join(ff.path, "pastes", strconv.Itoa(paste.ID)+".json"))
if err != nil { if err != nil {
c.Logger.Error().Msgf("Failed to read paste data: %s", err.Error()) c.Logger.Error().Msgf("Failed to read paste data: %s", err.Error())
@ -160,13 +168,16 @@ func (ff *FlatFiles) Initialize() {
curUser, err := user.Current() curUser, err := user.Current()
if err != nil { if err != nil {
c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!") c.Logger.Error().Msg("Failed to get current user. Will replace '~' for '/' in storage path!")
path = strings.Replace(path, "~", "/", -1) path = strings.Replace(path, "~", "/", -1)
} }
path = strings.Replace(path, "~", curUser.HomeDir, -1) path = strings.Replace(path, "~", curUser.HomeDir, -1)
} }
path, _ = filepath.Abs(path) path, _ = filepath.Abs(path)
ff.path = path ff.path = path
c.Logger.Debug().Msgf("Storage path is now: %s", ff.path) c.Logger.Debug().Msgf("Storage path is now: %s", ff.path)
// Create directory if necessary. // Create directory if necessary.
@ -209,29 +220,35 @@ func (ff *FlatFiles) SavePaste(p *structs.Paste) (int64, error) {
// Write paste data on disk. // Write paste data on disk.
filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes")) filesOnDisk, _ := ioutil.ReadDir(filepath.Join(ff.path, "pastes"))
pasteID := len(filesOnDisk) + 1 pasteID := len(filesOnDisk) + 1
c.Logger.Debug().Msgf("Writing paste to disk, ID will be " + strconv.Itoa(pasteID))
p.ID = pasteID p.ID = pasteID
c.Logger.Debug().Msgf("Writing paste to disk, ID will be " + strconv.Itoa(pasteID))
data, err := json.Marshal(p) data, err := json.Marshal(p)
if err != nil { if err != nil {
ff.writeMutex.Unlock() ff.writeMutex.Unlock()
return 0, err return 0, err
} }
err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644) err = ioutil.WriteFile(filepath.Join(ff.path, "pastes", strconv.Itoa(pasteID)+".json"), data, 0644)
if err != nil { if err != nil {
ff.writeMutex.Unlock() ff.writeMutex.Unlock()
return 0, err return 0, err
} }
// Add it to cache. // Add it to cache.
indexData := &Index{} indexData := &Index{}
indexData.ID = pasteID indexData.ID = pasteID
indexData.Private = p.Private indexData.Private = p.Private
ff.pastesIndex = append(ff.pastesIndex, indexData) ff.pastesIndex = append(ff.pastesIndex, indexData)
ff.writeMutex.Unlock() ff.writeMutex.Unlock()
return int64(pasteID), nil return int64(pasteID), nil
} }
func (ff *FlatFiles) Shutdown() { func (ff *FlatFiles) Shutdown() {
c.Logger.Info().Msg("Saving indexes...") c.Logger.Info().Msg("Saving indexes...")
indexData, err := json.Marshal(ff.pastesIndex) indexData, err := json.Marshal(ff.pastesIndex)
if err != nil { if err != nil {
c.Logger.Error().Msgf("Failed to encode index data into JSON: %s", err.Error()) c.Logger.Error().Msgf("Failed to encode index data into JSON: %s", err.Error())

View File

@ -27,7 +27,7 @@ package mysql
import ( import (
// local // local
"go.dev.pztrn.name/fastpastebin/internal/context" "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 ( var (
@ -38,5 +38,6 @@ var (
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
d = &Database{} d = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
} }

View File

@ -30,6 +30,7 @@ import (
) )
func InitialUp(tx *sql.Tx) error { 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';") _, 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 { if err != nil {
return err return err

View File

@ -69,7 +69,9 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
// GetPaste returns a single paste by ID. // GetPaste returns a single paste by ID.
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check() db.check()
p := &structs.Paste{} p := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID) err := db.db.Get(p, db.db.Rebind("SELECT * FROM `pastes` WHERE id=?"), pasteID)
if err != nil { if err != nil {
return nil, err 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) { func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
db.check() db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste var (
pastesRaw []structs.Paste
pastes []structs.Paste
)
// Pagination. // Pagination.
var startPagination = 0 var startPagination = 0
@ -105,8 +110,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
func (db *Database) GetPastesPages() int { func (db *Database) GetPastesPages() int {
db.check() 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") err := db.db.Get(&pastesRaw, "SELECT * FROM `pastes` WHERE private != true")
if err != nil { if err != nil {
return 1 return 1
@ -155,6 +164,7 @@ func (db *Database) Initialize() {
_ = dbConn.MustExec("SET @@session.time_zone='+00:00';") _ = dbConn.MustExec("SET @@session.time_zone='+00:00';")
c.Logger.Info().Msg("Database connection established") c.Logger.Info().Msg("Database connection established")
db.db = dbConn db.db = dbConn
// Perform migrations. // Perform migrations.
@ -164,6 +174,7 @@ func (db *Database) Initialize() {
func (db *Database) SavePaste(p *structs.Paste) (int64, error) { func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
db.check() 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) 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 { if err != nil {
return 0, err return 0, err

View File

@ -27,7 +27,7 @@ package postgresql
import ( import (
// local // local
"go.dev.pztrn.name/fastpastebin/internal/context" "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 ( var (
@ -38,5 +38,6 @@ var (
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
d = &Database{} d = &Database{}
c.Database.RegisterDialect(dialectinterface.Interface(Handler{})) c.Database.RegisterDialect(dialectinterface.Interface(Handler{}))
} }

View File

@ -36,6 +36,7 @@ import (
// other // other
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
// postgresql adapter
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -70,7 +71,9 @@ func (db *Database) GetDatabaseConnection() *sql.DB {
// GetPaste returns a single paste by ID. // GetPaste returns a single paste by ID.
func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) { func (db *Database) GetPaste(pasteID int) (*structs.Paste, error) {
db.check() db.check()
p := &structs.Paste{} p := &structs.Paste{}
err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID) err := db.db.Get(p, db.db.Rebind("SELECT * FROM pastes WHERE id=$1"), pasteID)
if err != nil { if err != nil {
return nil, err 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) { func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
db.check() db.check()
var pastesRaw []structs.Paste
var pastes []structs.Paste var (
pastesRaw []structs.Paste
pastes []structs.Paste
)
// Pagination. // Pagination.
var startPagination = 0 var startPagination = 0
@ -119,8 +125,12 @@ func (db *Database) GetPagedPastes(page int) ([]structs.Paste, error) {
func (db *Database) GetPastesPages() int { func (db *Database) GetPastesPages() int {
db.check() 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") err := db.db.Get(&pastesRaw, "SELECT * FROM pastes WHERE private != true")
if err != nil { if err != nil {
return 1 return 1
@ -164,6 +174,7 @@ func (db *Database) Initialize() {
} }
c.Logger.Info().Msg("Database connection established") c.Logger.Info().Msg("Database connection established")
db.db = dbConn db.db = dbConn
// Perform migrations. // Perform migrations.
@ -173,12 +184,14 @@ func (db *Database) Initialize() {
func (db *Database) SavePaste(p *structs.Paste) (int64, error) { func (db *Database) SavePaste(p *structs.Paste) (int64, error) {
db.check() 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") 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 { if err != nil {
return 0, err return 0, err
} }
var id int64 var id int64
err = stmt.Get(&id, p) err = stmt.Get(&id, p)
if err != nil { if err != nil {
return 0, err return 0, err

View File

@ -27,7 +27,7 @@ package database
import ( import (
// local // local
"go.dev.pztrn.name/fastpastebin/internal/context" "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 ( var (
@ -39,5 +39,6 @@ var (
func New(cc *context.Context) { func New(cc *context.Context) {
c = cc c = cc
d = &Database{} d = &Database{}
c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{})) c.RegisterDatabaseInterface(databaseinterface.Interface(Handler{}))
} }

View File

@ -29,7 +29,8 @@ import (
"database/sql" "database/sql"
// local // 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" "go.dev.pztrn.name/fastpastebin/internal/structs"
) )

View File

@ -29,7 +29,7 @@ import (
"database/sql" "database/sql"
// local // 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" "go.dev.pztrn.name/fastpastebin/internal/structs"
) )

View File

@ -10,6 +10,7 @@ import (
) )
// CreateHTML creates pagination HTML based on passed parameters. // CreateHTML creates pagination HTML based on passed parameters.
// nolint
func CreateHTML(currentPage int, pages int, linksBase string) string { func CreateHTML(currentPage int, pages int, linksBase string) string {
// Load templates. // Load templates.
paginationHTMLRaw, err := static.ReadFile("pagination.html") 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) paginationString = strings.Replace(string(paginationLinkCurrentRaw), "{pageNum}", strconv.Itoa(currentPage), -1)
} else { } else {
paginationString = strings.Replace(string(paginationLinkRaw), "{pageNum}", "1", -1) 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 (
var ellipsisEndAdded = false ellipsisStartAdded = false
i := 2 ellipsisEndAdded = false
i = 2
)
for i <= pages { for i <= pages {
if pages > 5 { if pages > 5 {
if currentPage-3 < i && currentPage+3 > i || i == pages { if currentPage-3 < i && currentPage+3 > i || i == pages {

View File

@ -36,22 +36,22 @@ import (
) )
const ( const (
PASTE_KEEP_FOREVER = 0 PasteKeepForever = 0
PASTE_KEEP_FOR_MINUTES = 1 PasteKeepForMinutes = 1
PASTE_KEEP_FOR_HOURS = 2 PasteKeepForHours = 2
PASTE_KEEP_FOR_DAYS = 3 PasteKeepForDays = 3
PASTE_KEEP_FOR_MONTHS = 4 PasteKeepForMonths = 4
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
) )
var ( var (
PASTE_KEEPS_CORELLATION = map[string]int{ PasteKeepsCorellation = map[string]int{
"M": PASTE_KEEP_FOR_MINUTES, "M": PasteKeepForMinutes,
"h": PASTE_KEEP_FOR_HOURS, "h": PasteKeepForHours,
"d": PASTE_KEEP_FOR_DAYS, "d": PasteKeepForDays,
"m": PASTE_KEEP_FOR_MONTHS, "m": PasteKeepForMonths,
"forever": PASTE_KEEP_FOREVER, "forever": PasteKeepForever,
} }
) )
@ -74,6 +74,7 @@ func (p *Paste) CreatePassword(password string) error {
// Create salt - random string. // Create salt - random string.
seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
saltBytes := make([]byte, 64) saltBytes := make([]byte, 64)
for i := range saltBytes { for i := range saltBytes {
saltBytes[i] = charset[seededRand.Intn(len(charset))] saltBytes[i] = charset[seededRand.Intn(len(charset))]
} }
@ -86,6 +87,7 @@ func (p *Paste) CreatePassword(password string) error {
if err != nil { if err != nil {
return err return err
} }
passwordHashBytes := sha256.Sum256(passwordCrypted) passwordHashBytes := sha256.Sum256(passwordCrypted)
p.Password = fmt.Sprintf("%x", passwordHashBytes) p.Password = fmt.Sprintf("%x", passwordHashBytes)
@ -100,16 +102,17 @@ func (p *Paste) GenerateCryptedCookieValue() string {
func (p *Paste) GetExpirationTime() time.Time { func (p *Paste) GetExpirationTime() time.Time {
var expirationTime time.Time var expirationTime time.Time
switch p.KeepForUnitType { switch p.KeepForUnitType {
case PASTE_KEEP_FOREVER: case PasteKeepForever:
expirationTime = time.Now().UTC().Add(time.Hour * 1) 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)) 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)) 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)) 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)) 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() curTime := time.Now().UTC()
expirationTime := p.GetExpirationTime() expirationTime := p.GetExpirationTime()
if curTime.Sub(expirationTime).Seconds() > 0 { return curTime.Sub(expirationTime).Seconds() > 0
return true
}
return false
} }
// VerifyPassword verifies that provided password is valid. // VerifyPassword verifies that provided password is valid.
@ -135,12 +134,9 @@ func (p *Paste) VerifyPassword(password string) bool {
if err != nil { if err != nil {
return false return false
} }
passwordHashBytes := sha256.Sum256(passwordCrypted) passwordHashBytes := sha256.Sum256(passwordCrypted)
providedPassword := fmt.Sprintf("%x", passwordHashBytes) providedPassword := fmt.Sprintf("%x", passwordHashBytes)
if providedPassword == p.Password { return providedPassword == p.Password
return true
}
return false
} }