2018-04-30 22:37:36 +05:00
// Fast Paste Bin - uberfast and easy-to-use pastebin.
//
// Copyright (c) 2018, Stanislav N. aka pztrn and Fast Paste Bin
// developers.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2018-04-30 22:31:48 +05:00
package pastes
2018-04-30 18:42:17 +05:00
import (
// stdlib
2018-05-01 14:41:36 +05:00
"bytes"
//"html"
2018-04-30 18:42:17 +05:00
"net/http"
"regexp"
"strconv"
"strings"
"time"
// local
"github.com/pztrn/fastpastebin/api/http/static"
2018-05-01 02:37:51 +05:00
"github.com/pztrn/fastpastebin/pagination"
2018-04-30 18:42:17 +05:00
// other
2018-05-01 14:41:36 +05:00
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters"
htmlfmt "github.com/alecthomas/chroma/formatters/html"
2018-05-01 02:37:51 +05:00
"github.com/alecthomas/chroma/lexers"
2018-05-01 14:41:36 +05:00
"github.com/alecthomas/chroma/styles"
2018-04-30 18:42:17 +05:00
"github.com/labstack/echo"
)
var (
regexInts = regexp . MustCompile ( "[0-9]+" )
)
2018-04-30 22:31:48 +05:00
// GET for "/paste/PASTE_ID".
2018-04-30 18:42:17 +05:00
func pasteGET ( ec echo . Context ) error {
errhtml , err := static . ReadFile ( "error.html" )
if err != nil {
return ec . String ( http . StatusNotFound , "error.html wasn't found!" )
}
pasteIDRaw := ec . Param ( "id" )
// We already get numbers from string, so we will not check strconv.Atoi()
// error.
pasteID , _ := strconv . Atoi ( regexInts . FindAllString ( pasteIDRaw , 1 ) [ 0 ] )
c . Logger . Debug ( ) . Msgf ( "Requesting paste #%+v" , pasteID )
// Get paste.
2018-04-30 23:07:07 +05:00
paste , err1 := GetByID ( pasteID )
2018-04-30 18:42:17 +05:00
if err1 != nil {
c . Logger . Error ( ) . Msgf ( "Failed to get paste #%d from database: %s" , pasteID , err1 . Error ( ) )
2018-05-01 02:37:51 +05:00
errhtmlAsString := strings . Replace ( string ( errhtml ) , "{error}" , "Paste #" + strconv . Itoa ( pasteID ) + " not found" , 1 )
return ec . HTML ( http . StatusBadRequest , errhtmlAsString )
2018-04-30 18:42:17 +05:00
}
pasteHTML , err2 := static . ReadFile ( "paste.html" )
if err2 != nil {
return ec . String ( http . StatusNotFound , "parse.html wasn't found!" )
}
2018-05-01 14:41:36 +05:00
// Highlight.
// Get lexer.
lexer := lexers . Get ( paste . Language )
if lexer == nil {
lexer = lexers . Fallback
}
// Tokenize paste data.
lexered , err3 := lexer . Tokenise ( nil , paste . Data )
if err3 != nil {
c . Logger . Error ( ) . Msgf ( "Failed to tokenize paste data: %s" , err3 . Error ( ) )
}
// Get style for HTML output.
style := styles . Get ( "monokai" )
if style == nil {
style = styles . Fallback
}
// Get HTML formatter.
formatter := chroma . Formatter ( htmlfmt . New ( htmlfmt . WithLineNumbers ( ) , htmlfmt . LineNumbersInTable ( ) ) )
if formatter == nil {
formatter = formatters . Fallback
}
// Create buffer and format into it.
buf := new ( bytes . Buffer )
err4 := formatter . Format ( buf , style , lexered )
if err4 != nil {
c . Logger . Error ( ) . Msgf ( "Failed to format paste data: %s" , err4 . Error ( ) )
}
// Escape paste data.
//pasteData := html.EscapeString(buf.String())
pasteHTMLAsString := strings . Replace ( string ( pasteHTML ) , "{pastedata}" , buf . String ( ) , 1 )
2018-04-30 18:42:17 +05:00
return ec . HTML ( http . StatusOK , string ( pasteHTMLAsString ) )
}
2018-04-30 22:31:48 +05:00
// POST for "/paste/" which will create new paste and redirect to
// "/pastes/CREATED_PASTE_ID".
2018-04-30 18:42:17 +05:00
func pastePOST ( ec echo . Context ) error {
errhtml , err := static . ReadFile ( "error.html" )
if err != nil {
return ec . String ( http . StatusNotFound , "error.html wasn't found!" )
}
params , err := ec . FormParams ( )
if err != nil {
c . Logger . Debug ( ) . Msg ( "No form parameters passed" )
return ec . HTML ( http . StatusBadRequest , string ( errhtml ) )
}
c . Logger . Debug ( ) . Msgf ( "Received parameters: %+v" , params )
2018-05-01 02:37:51 +05:00
// Do nothing if paste contents is empty.
if len ( params [ "paste-contents" ] [ 0 ] ) == 0 {
c . Logger . Debug ( ) . Msg ( "Empty paste submitted, ignoring" )
errhtmlAsString := strings . Replace ( string ( errhtml ) , "{error}" , "Empty pastes aren't allowed." , 1 )
return ec . HTML ( http . StatusBadRequest , errhtmlAsString )
}
2018-04-30 18:42:17 +05:00
if ! strings . ContainsAny ( params [ "paste-keep-for" ] [ 0 ] , "Mmhd" ) {
c . Logger . Debug ( ) . Msgf ( "'Keep paste for' field have invalid value: %s" , params [ "paste-keep-for" ] [ 0 ] )
2018-05-01 02:37:51 +05:00
errhtmlAsString := strings . Replace ( string ( errhtml ) , "{error}" , "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;)." , 1 )
return ec . HTML ( http . StatusBadRequest , errhtmlAsString )
2018-04-30 18:42:17 +05:00
}
2018-04-30 22:31:48 +05:00
paste := & Paste {
2018-05-01 02:37:51 +05:00
Title : params [ "paste-title" ] [ 0 ] ,
Data : params [ "paste-contents" ] [ 0 ] ,
Language : params [ "paste-language" ] [ 0 ] ,
2018-04-30 18:42:17 +05:00
}
// Paste creation time in UTC.
createdAt := time . Now ( ) . UTC ( )
paste . CreatedAt = & createdAt
// Parse "keep for" field.
// Get integers and strings separately.
keepForUnitRegex := regexp . MustCompile ( "[Mmhd]" )
keepForRaw := regexInts . FindAllString ( params [ "paste-keep-for" ] [ 0 ] , 1 ) [ 0 ]
keepFor , err1 := strconv . Atoi ( keepForRaw )
if err1 != nil {
c . Logger . Debug ( ) . Msgf ( "Failed to parse 'Keep for' integer: %s" , err1 . Error ( ) )
2018-05-01 02:37:51 +05:00
errhtmlAsString := strings . Replace ( string ( errhtml ) , "{error}" , "Invalid 'Paste should be available for' parameter passed. Please do not try to hack us ;)." , 1 )
return ec . HTML ( http . StatusBadRequest , errhtmlAsString )
2018-04-30 18:42:17 +05:00
}
paste . KeepFor = keepFor
keepForUnitRaw := keepForUnitRegex . FindAllString ( params [ "paste-keep-for" ] [ 0 ] , 1 ) [ 0 ]
2018-04-30 22:31:48 +05:00
keepForUnit := PASTE_KEEPS_CORELLATION [ keepForUnitRaw ]
2018-04-30 18:42:17 +05:00
paste . KeepForUnitType = keepForUnit
2018-05-01 02:37:51 +05:00
// Try to autodetect if it was selected.
if params [ "paste-language" ] [ 0 ] == "autodetect" {
lexer := lexers . Analyse ( params [ "paste-language" ] [ 0 ] )
if lexer != nil {
paste . Language = lexer . Config ( ) . Name
} else {
paste . Language = "text"
}
}
2018-04-30 23:07:07 +05:00
id , err2 := Save ( paste )
2018-04-30 18:42:17 +05:00
if err2 != nil {
c . Logger . Debug ( ) . Msgf ( "Failed to save paste: %s" , err2 . Error ( ) )
2018-05-01 02:37:51 +05:00
errhtmlAsString := strings . Replace ( string ( errhtml ) , "{error}" , "Failed to save paste. Please, try again later." , 1 )
return ec . HTML ( http . StatusBadRequest , errhtmlAsString )
2018-04-30 18:42:17 +05:00
}
newPasteIDAsString := strconv . FormatInt ( id , 10 )
c . Logger . Debug ( ) . Msgf ( "Paste saved, URL: /paste/" + newPasteIDAsString )
return ec . Redirect ( http . StatusFound , "/paste/" + newPasteIDAsString )
}
2018-04-30 22:31:48 +05:00
// GET for "/pastes/", a list of publicly available pastes.
2018-04-30 18:42:17 +05:00
func pastesGET ( ec echo . Context ) error {
pasteListHTML , err1 := static . ReadFile ( "pastelist_list.html" )
if err1 != nil {
return ec . String ( http . StatusNotFound , "pastelist_list.html wasn't found!" )
}
pasteElementHTML , err2 := static . ReadFile ( "pastelist_paste.html" )
if err2 != nil {
return ec . String ( http . StatusNotFound , "pastelist_paste.html wasn't found!" )
}
2018-05-01 02:37:51 +05:00
pageFromParamRaw := ec . Param ( "page" )
2018-04-30 18:42:17 +05:00
var page = 1
if pageFromParamRaw != "" {
pageRaw := regexInts . FindAllString ( pageFromParamRaw , 1 ) [ 0 ]
page , _ = strconv . Atoi ( pageRaw )
}
c . Logger . Debug ( ) . Msgf ( "Requested page #%d" , page )
// Get pastes IDs.
2018-04-30 23:07:07 +05:00
pastes , err3 := GetPagedPastes ( page )
2018-04-30 18:42:17 +05:00
c . Logger . Debug ( ) . Msgf ( "Got %d pastes" , len ( pastes ) )
var pastesString = "No pastes to show."
// Show "No pastes to show" on any error for now.
if err3 != nil {
c . Logger . Error ( ) . Msgf ( "Failed to get pastes list from database: %s" , err3 . Error ( ) )
pasteListHTMLAsString := strings . Replace ( string ( pasteListHTML ) , "{pastes}" , pastesString , 1 )
return ec . HTML ( http . StatusOK , string ( pasteListHTMLAsString ) )
}
if len ( pastes ) > 0 {
pastesString = ""
for i := range pastes {
pasteString := strings . Replace ( string ( pasteElementHTML ) , "{pasteID}" , strconv . Itoa ( pastes [ i ] . ID ) , 2 )
pasteString = strings . Replace ( pasteString , "{pasteTitle}" , pastes [ i ] . Title , 1 )
pasteString = strings . Replace ( pasteString , "{pasteDate}" , pastes [ i ] . CreatedAt . Format ( "2006-01-02 @ 15:04:05" ) , 1 )
// Get max 4 lines of each paste.
pasteDataSplitted := strings . Split ( pastes [ i ] . Data , "\n" )
var pasteData = ""
if len ( pasteDataSplitted ) < 4 {
pasteData = pastes [ i ] . Data
} else {
pasteData = strings . Join ( pasteDataSplitted [ 0 : 4 ] , "\n" )
}
pasteString = strings . Replace ( pasteString , "{pasteData}" , pasteData , 1 )
pastesString += pasteString
}
}
pasteListHTMLAsString := strings . Replace ( string ( pasteListHTML ) , "{pastes}" , pastesString , 1 )
2018-05-01 02:37:51 +05:00
pages := GetPastesPages ( )
c . Logger . Debug ( ) . Msgf ( "Total pages: %d, current: %d" , pages , page )
paginationHTML := pagination . CreateHTML ( page , pages , "/pastes/" )
pasteListHTMLAsString = strings . Replace ( pasteListHTMLAsString , "{pagination}" , paginationHTML , - 1 )
2018-04-30 18:42:17 +05:00
return ec . HTML ( http . StatusOK , string ( pasteListHTMLAsString ) )
}