mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-04 03:56:24 -07:00
Add basic captcha support
This commit is contained in:
parent
9a247cc7b2
commit
21e01d7708
15 changed files with 292 additions and 46 deletions
10
build.sh
10
build.sh
|
@ -292,11 +292,11 @@ while [ -n "$1" ]; do
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# if [ -d /lib/systemd/system ]; then
|
if [ -d /lib/systemd/system ]; then
|
||||||
# echo "Installing systemd service file"
|
echo "Installing systemd service file"
|
||||||
# cp gochan.service /lib/systemd/system/gochan.service
|
cp $symarg $PWD/gochan.service /lib/systemd/system/gochan.service
|
||||||
# systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
# fi
|
fi
|
||||||
|
|
||||||
echo "Installation complete. Make sure to set the following values in gochan.json:"
|
echo "Installation complete. Make sure to set the following values in gochan.json:"
|
||||||
echo "DocumentRoot => $documentroot"
|
echo "DocumentRoot => $documentroot"
|
||||||
|
|
|
@ -73,6 +73,10 @@
|
||||||
|
|
||||||
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
|
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
|
||||||
"AkismetAPIKey": "",
|
"AkismetAPIKey": "",
|
||||||
|
"UseCaptcha": false,
|
||||||
|
"CaptchaWidth": 240,
|
||||||
|
"CaptchaHeight": 80,
|
||||||
|
"CaptchaMinutesTimeout": 15,
|
||||||
"EnableGeoIP": true,
|
"EnableGeoIP": true,
|
||||||
"_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP",
|
"_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP",
|
||||||
"GeoIPDBlocation": "/usr/share/GeoIP/GeoIP.dat",
|
"GeoIPDBlocation": "/usr/share/GeoIP/GeoIP.dat",
|
||||||
|
|
107
src/captcha.go
Normal file
107
src/captcha.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/mojocn/base64Captcha"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
charCaptchaCfg base64Captcha.ConfigCharacter
|
||||||
|
)
|
||||||
|
|
||||||
|
type captchaJSON struct {
|
||||||
|
CaptchaID string `json:"id"`
|
||||||
|
Base64String string `json:"image"`
|
||||||
|
Result string `json:"-"`
|
||||||
|
TempPostIndex string `json:"-"`
|
||||||
|
EmailCmd string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func initCaptcha() {
|
||||||
|
charCaptchaCfg = base64Captcha.ConfigCharacter{
|
||||||
|
Height: config.CaptchaHeight, // originally 60
|
||||||
|
Width: config.CaptchaWidth, // originally 240
|
||||||
|
Mode: base64Captcha.CaptchaModeNumberAlphabet,
|
||||||
|
ComplexOfNoiseText: base64Captcha.CaptchaComplexLower,
|
||||||
|
ComplexOfNoiseDot: base64Captcha.CaptchaComplexLower,
|
||||||
|
IsUseSimpleFont: true,
|
||||||
|
IsShowHollowLine: false,
|
||||||
|
IsShowNoiseDot: true,
|
||||||
|
IsShowNoiseText: false,
|
||||||
|
IsShowSlimeLine: true,
|
||||||
|
IsShowSineLine: false,
|
||||||
|
CaptchaLen: 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveCaptcha(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var err error
|
||||||
|
if err = request.ParseForm(); err != nil {
|
||||||
|
serveErrorPage(writer, err.Error())
|
||||||
|
errorLog.Println(customError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPostIndexStr := request.FormValue("temppostindex")
|
||||||
|
var tempPostIndex int
|
||||||
|
if tempPostIndex, err = strconv.Atoi(tempPostIndexStr); err != nil {
|
||||||
|
tempPostIndexStr = "-1"
|
||||||
|
tempPostIndex = 0
|
||||||
|
}
|
||||||
|
emailCommand := request.FormValue("emailcmd")
|
||||||
|
|
||||||
|
id, b64 := getCaptchaImage()
|
||||||
|
captchaStruct := captchaJSON{id, b64, "", tempPostIndexStr, emailCommand}
|
||||||
|
useJSON := request.FormValue("json") == "1"
|
||||||
|
if useJSON {
|
||||||
|
writer.Header().Add("Content-Type", "application/json")
|
||||||
|
str, _ := marshalJSON("", captchaStruct, false)
|
||||||
|
writer.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if request.FormValue("reload") == "Reload" {
|
||||||
|
request.Form.Del("reload")
|
||||||
|
request.Form.Add("didreload", "1")
|
||||||
|
serveCaptcha(writer, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.Header().Add("Content-Type", "text/html")
|
||||||
|
captchaID := request.FormValue("captchaid")
|
||||||
|
captchaAnswer := request.FormValue("captchaanswer")
|
||||||
|
if captchaID != "" && request.FormValue("didreload") != "1" {
|
||||||
|
goodAnswer := base64Captcha.VerifyCaptcha(captchaID, captchaAnswer)
|
||||||
|
if goodAnswer {
|
||||||
|
if tempPostIndex > -1 && tempPostIndex < len(tempPosts) {
|
||||||
|
// came from a /post redirect, insert the specified temporary post
|
||||||
|
// and redirect to the thread
|
||||||
|
insertPost(&tempPosts[tempPostIndex], emailCommand == "noko")
|
||||||
|
buildBoards(tempPosts[tempPostIndex].BoardID)
|
||||||
|
buildFrontPage()
|
||||||
|
url := tempPosts[tempPostIndex].GetURL(false)
|
||||||
|
|
||||||
|
// move the end Post to the current index and remove the old end Post. We don't
|
||||||
|
// really care about order as long as tempPost validation doesn't get jumbled up
|
||||||
|
tempPosts[tempPostIndex] = tempPosts[len(tempPosts)-1]
|
||||||
|
tempPosts = tempPosts[:len(tempPosts)-1]
|
||||||
|
http.Redirect(writer, request, url, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
captchaStruct.Result = "Incorrect CAPTCHA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = captcha_tmpl.Execute(writer, captchaStruct); err != nil {
|
||||||
|
handleError(0, customError(err))
|
||||||
|
fmt.Fprintf(writer, "Error executing captcha template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCaptchaImage() (captchaID string, chaptchaB64 string) {
|
||||||
|
var captchaInstance base64Captcha.CaptchaInterface
|
||||||
|
captchaID, captchaInstance = base64Captcha.GenerateCaptcha("", charCaptchaCfg)
|
||||||
|
chaptchaB64 = base64Captcha.CaptchaWriteToBase64Encoding(captchaInstance)
|
||||||
|
return
|
||||||
|
}
|
12
src/captcha_test.go
Normal file
12
src/captcha_test.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCaptchaImage(t *testing.T) {
|
||||||
|
initCaptcha()
|
||||||
|
captchaID, captchaB64 := getCaptchaImage()
|
||||||
|
fmt.Println("captchaID:", captchaID, "\ncaptchaB64:", captchaB64)
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionStr string
|
var versionStr string
|
||||||
|
@ -30,6 +31,9 @@ func main() {
|
||||||
handleError(0, customError(err))
|
handleError(0, customError(err))
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
initCaptcha()
|
||||||
|
tempCleanerTicker = time.NewTicker(time.Minute * 5)
|
||||||
|
go tempCleaner()
|
||||||
|
|
||||||
sc := make(chan os.Signal, 1)
|
sc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
||||||
|
|
|
@ -1115,4 +1115,17 @@ var manage_functions = map[string]ManageFunction{
|
||||||
"\t\t</form>"
|
"\t\t</form>"
|
||||||
return
|
return
|
||||||
}},
|
}},
|
||||||
|
"tempposts": {
|
||||||
|
Permissions: 3,
|
||||||
|
Callback: func(writer http.ResponseWriter, request *http.Request) (html string) {
|
||||||
|
html += "<h1 class=\"manage-header\">Temporary posts</h1>"
|
||||||
|
if len(tempPosts) == 0 {
|
||||||
|
html += "No temporary posts<br />\n"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for p, post := range tempPosts {
|
||||||
|
html += fmt.Sprintf("Post[%d]: %#v<br />\n", p, post)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -28,7 +27,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
whitespaceMatch = "[\000-\040]"
|
|
||||||
gt = ">"
|
gt = ">"
|
||||||
yearInSeconds = 31536000
|
yearInSeconds = 31536000
|
||||||
)
|
)
|
||||||
|
@ -36,6 +34,9 @@ const (
|
||||||
var (
|
var (
|
||||||
allSections []BoardSection
|
allSections []BoardSection
|
||||||
allBoards []Board
|
allBoards []Board
|
||||||
|
tempPosts []Post
|
||||||
|
tempCleanerTicker *time.Ticker
|
||||||
|
tempCleanerQuit = make(chan struct{})
|
||||||
)
|
)
|
||||||
|
|
||||||
// bumps the given thread on the given board and returns true if there were no errors
|
// bumps the given thread on the given board and returns true if there were no errors
|
||||||
|
@ -282,14 +283,18 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
startTime := benchmarkTimer("makePost", time.Now(), true)
|
startTime := benchmarkTimer("makePost", time.Now(), true)
|
||||||
var maxMessageLength int
|
var maxMessageLength int
|
||||||
var post Post
|
var post Post
|
||||||
domain := request.Host
|
// domain := request.Host
|
||||||
var formName string
|
var formName string
|
||||||
var nameCookie string
|
var nameCookie string
|
||||||
var formEmail string
|
var formEmail string
|
||||||
|
|
||||||
|
if request.Method == "GET" {
|
||||||
|
http.Redirect(writer, request, config.SiteWebfolder, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
// fix new cookie domain for when you use a port number
|
// fix new cookie domain for when you use a port number
|
||||||
chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
|
// chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
|
||||||
domain = chopPortNumRegex.Split(domain, -1)[0]
|
// domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||||
|
|
||||||
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
|
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
|
||||||
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))
|
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))
|
||||||
|
@ -301,7 +306,8 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
post.Tripcode = parsedName["tripcode"]
|
post.Tripcode = parsedName["tripcode"]
|
||||||
|
|
||||||
formEmail = request.FormValue("postemail")
|
formEmail = request.FormValue("postemail")
|
||||||
http.SetCookie(writer, &http.Cookie{Name: "email", Value: formEmail, Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(yearInSeconds))), MaxAge: yearInSeconds})
|
|
||||||
|
http.SetCookie(writer, &http.Cookie{Name: "email", Value: formEmail, MaxAge: yearInSeconds})
|
||||||
|
|
||||||
if !strings.Contains(formEmail, "noko") && !strings.Contains(formEmail, "sage") {
|
if !strings.Contains(formEmail, "noko") && !strings.Contains(formEmail, "sage") {
|
||||||
post.Email = formEmail
|
post.Email = formEmail
|
||||||
|
@ -345,8 +351,8 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
nameCookie = strings.Replace(url.QueryEscape(nameCookie), "+", "%20", -1)
|
nameCookie = strings.Replace(url.QueryEscape(nameCookie), "+", "%20", -1)
|
||||||
|
|
||||||
// add name and email cookies that will expire in a year (31536000 seconds)
|
// add name and email cookies that will expire in a year (31536000 seconds)
|
||||||
http.SetCookie(writer, &http.Cookie{Name: "name", Value: nameCookie, Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(yearInSeconds))), MaxAge: yearInSeconds})
|
http.SetCookie(writer, &http.Cookie{Name: "name", Value: nameCookie, MaxAge: yearInSeconds})
|
||||||
http.SetCookie(writer, &http.Cookie{Name: "password", Value: password, Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(yearInSeconds))), MaxAge: yearInSeconds})
|
http.SetCookie(writer, &http.Cookie{Name: "password", Value: password, MaxAge: yearInSeconds})
|
||||||
|
|
||||||
post.IP = getRealIP(request)
|
post.IP = getRealIP(request)
|
||||||
post.Timestamp = time.Now()
|
post.Timestamp = time.Now()
|
||||||
|
@ -383,12 +389,12 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
if err != nil || handler.Size == 0 {
|
if err != nil || handler.Size == 0 {
|
||||||
// no file was uploaded
|
// no file was uploaded
|
||||||
post.Filename = ""
|
post.Filename = ""
|
||||||
accessLog.Print("Receiving post from " + post.IP + ", referred from: " + request.Referer())
|
|
||||||
accessLog.Printf("Receiving post from %s, referred from: %s", post.IP, request.Referer())
|
accessLog.Printf("Receiving post from %s, referred from: %s", post.IP, request.Referer())
|
||||||
} else {
|
} else {
|
||||||
data, err := ioutil.ReadAll(file)
|
data, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serveErrorPage(writer, handleError(1, "Couldn't read file: "+err.Error()))
|
serveErrorPage(writer, handleError(1, "Couldn't read file: "+err.Error()))
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
post.FilenameOriginal = html.EscapeString(handler.Filename)
|
post.FilenameOriginal = html.EscapeString(handler.Filename)
|
||||||
filetype := getFileExtension(post.FilenameOriginal)
|
filetype := getFileExtension(post.FilenameOriginal)
|
||||||
|
@ -410,7 +416,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
catalogThumbPath := path.Join(config.DocumentRoot, "/"+boardDir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "c."+thumbFiletype, -1))
|
catalogThumbPath := path.Join(config.DocumentRoot, "/"+boardDir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "c."+thumbFiletype, -1))
|
||||||
|
|
||||||
if err = ioutil.WriteFile(filePath, data, 0777); err != nil {
|
if err = ioutil.WriteFile(filePath, data, 0777); err != nil {
|
||||||
handleError(0, "Couldn't write file \""+post.Filename+"\""+err.Error())
|
handleError(0, "Couldn't write file '%s': %s\n", post.Filename, err.Error())
|
||||||
serveErrorPage(writer, "Couldn't write file \""+post.FilenameOriginal+"\"")
|
serveErrorPage(writer, "Couldn't write file \""+post.FilenameOriginal+"\"")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -434,16 +440,14 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accessLog.Print("Receiving post with video: " + handler.Filename + " from " + post.IP + ", referrer: " + request.Referer())
|
accessLog.Printf("Receiving post with video: %s from %s, referrer: %s", handler.Filename, post.IP, request.Referer())
|
||||||
if post.ParentID == 0 {
|
if post.ParentID == 0 {
|
||||||
err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth)
|
if err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth); err != nil {
|
||||||
if err != nil {
|
|
||||||
serveErrorPage(writer, handleError(1, err.Error()))
|
serveErrorPage(writer, handleError(1, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth_reply)
|
if err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth_reply); err != nil {
|
||||||
if err != nil {
|
|
||||||
serveErrorPage(writer, handleError(1, err.Error()))
|
serveErrorPage(writer, handleError(1, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -489,6 +493,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(filePath)
|
os.Remove(filePath)
|
||||||
handleError(1, "Couldn't open uploaded file \""+post.Filename+"\""+err.Error())
|
handleError(1, "Couldn't open uploaded file \""+post.Filename+"\""+err.Error())
|
||||||
|
handleError(1, "Couldn't open uploaded file \"%s\": %s\n", post.Filename, err.Error())
|
||||||
serveErrorPage(writer, "Upload filetype not supported")
|
serveErrorPage(writer, "Upload filetype not supported")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
@ -517,13 +522,11 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
if _, err := os.Stat(path.Join(config.DocumentRoot, "spoiler.png")); err != nil {
|
if _, err := os.Stat(path.Join(config.DocumentRoot, "spoiler.png")); err != nil {
|
||||||
serveErrorPage(writer, "missing /spoiler.png")
|
serveErrorPage(writer, "missing /spoiler.png")
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumbPath)
|
if err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumbPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
serveErrorPage(writer, err.Error())
|
serveErrorPage(writer, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if config.ThumbWidth >= post.ImageW && config.ThumbHeight >= post.ImageH {
|
} else if config.ThumbWidth >= post.ImageW && config.ThumbHeight >= post.ImageH {
|
||||||
// If image fits in thumbnail size, symlink thumbnail to original
|
// If image fits in thumbnail size, symlink thumbnail to original
|
||||||
post.ThumbW = img.Bounds().Max.X
|
post.ThumbW = img.Bounds().Max.X
|
||||||
|
@ -596,6 +599,20 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
post.Sanitize()
|
post.Sanitize()
|
||||||
|
|
||||||
|
if config.UseCaptcha {
|
||||||
|
captchaID := request.FormValue("captchaid")
|
||||||
|
captchaAnswer := request.FormValue("captchaanswer")
|
||||||
|
if captchaID == "" && captchaAnswer == "" {
|
||||||
|
// browser isn't using JS, save post data to tempPosts and show captcha
|
||||||
|
request.Form.Add("temppostindex", strconv.Itoa(len(tempPosts)))
|
||||||
|
request.Form.Add("emailcmd", emailCommand)
|
||||||
|
tempPosts = append(tempPosts, post)
|
||||||
|
serveCaptcha(writer, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err = insertPost(&post, emailCommand != "sage"); err != nil {
|
if err = insertPost(&post, emailCommand != "sage"); err != nil {
|
||||||
serveErrorPage(writer, handleError(1, err.Error()))
|
serveErrorPage(writer, handleError(1, err.Error()))
|
||||||
return
|
return
|
||||||
|
@ -617,6 +634,46 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
||||||
benchmarkTimer("makePost", startTime, false)
|
benchmarkTimer("makePost", startTime, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tempCleaner() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tempCleanerTicker.C:
|
||||||
|
for p, post := range tempPosts {
|
||||||
|
if !time.Now().After(post.Timestamp.Add(time.Minute * 5)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// temporary post is >= 5 minutes, time to prune it
|
||||||
|
tempPosts[p] = tempPosts[len(tempPosts)-1]
|
||||||
|
tempPosts = tempPosts[:len(tempPosts)-1]
|
||||||
|
if post.FilenameOriginal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
board, err := getBoardFromID(post.BoardID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSrc := path.Join(config.DocumentRoot, board.Dir, "src", post.FilenameOriginal)
|
||||||
|
if err = os.Remove(fileSrc); err != nil {
|
||||||
|
printf(0, "Error pruning temporary upload for %s: %s", fileSrc, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbSrc := getThumbnailPath("thread", fileSrc)
|
||||||
|
if err = os.Remove(thumbSrc); err != nil {
|
||||||
|
printf(0, "Error pruning temporary upload for %s: %s", thumbSrc, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.ParentID == 0 {
|
||||||
|
catalogSrc := getThumbnailPath("catalog", fileSrc)
|
||||||
|
if err = os.Remove(catalogSrc); err != nil {
|
||||||
|
printf(0, "Error pruning temporary upload for %s: %s", catalogSrc, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func formatMessage(message string) string {
|
func formatMessage(message string) string {
|
||||||
message = bbcompiler.Compile(message)
|
message = bbcompiler.Compile(message)
|
||||||
// prepare each line to be formatted
|
// prepare each line to be formatted
|
||||||
|
|
|
@ -142,6 +142,7 @@ func initServer() {
|
||||||
referrerRegex = regexp.MustCompile(config.DomainRegex)
|
referrerRegex = regexp.MustCompile(config.DomainRegex)
|
||||||
|
|
||||||
server.AddNamespace("banned", banHandler)
|
server.AddNamespace("banned", banHandler)
|
||||||
|
server.AddNamespace("captcha", serveCaptcha)
|
||||||
server.AddNamespace("manage", callManageFunction)
|
server.AddNamespace("manage", callManageFunction)
|
||||||
server.AddNamespace("post", makePost)
|
server.AddNamespace("post", makePost)
|
||||||
server.AddNamespace("util", utilHandler)
|
server.AddNamespace("util", utilHandler)
|
||||||
|
|
|
@ -63,6 +63,11 @@ func connectToSQLServer() {
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err = execSQL("TRUNCATE TABLE " + config.DBprefix + "sessions"); err != nil {
|
||||||
|
handleError(0, "failed: %s\n", customError(err))
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
var sqlVersionStr string
|
var sqlVersionStr string
|
||||||
err = queryRowSQL("SELECT value FROM "+config.DBprefix+"info WHERE name = 'version'",
|
err = queryRowSQL("SELECT value FROM "+config.DBprefix+"info WHERE name = 'version'",
|
||||||
[]interface{}{}, []interface{}{&sqlVersionStr})
|
[]interface{}{}, []interface{}{&sqlVersionStr})
|
||||||
|
|
|
@ -291,6 +291,7 @@ var funcMap = template.FuncMap{
|
||||||
|
|
||||||
var (
|
var (
|
||||||
banpage_tmpl *template.Template
|
banpage_tmpl *template.Template
|
||||||
|
captcha_tmpl *template.Template
|
||||||
catalog_tmpl *template.Template
|
catalog_tmpl *template.Template
|
||||||
errorpage_tmpl *template.Template
|
errorpage_tmpl *template.Template
|
||||||
front_page_tmpl *template.Template
|
front_page_tmpl *template.Template
|
||||||
|
@ -328,7 +329,7 @@ func templateError(name string, err error) error {
|
||||||
func initTemplates(which ...string) error {
|
func initTemplates(which ...string) error {
|
||||||
var err error
|
var err error
|
||||||
buildAll := len(which) == 0 || which[0] == "all"
|
buildAll := len(which) == 0 || which[0] == "all"
|
||||||
|
resetBoardSectionArrays()
|
||||||
for _, t := range which {
|
for _, t := range which {
|
||||||
if buildAll || t == "banpage" {
|
if buildAll || t == "banpage" {
|
||||||
banpage_tmpl, err = loadTemplate("banpage.html", "global_footer.html")
|
banpage_tmpl, err = loadTemplate("banpage.html", "global_footer.html")
|
||||||
|
@ -336,6 +337,12 @@ func initTemplates(which ...string) error {
|
||||||
return templateError("banpage.html", err)
|
return templateError("banpage.html", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if buildAll || t == "captcha" {
|
||||||
|
captcha_tmpl, err = loadTemplate("captcha.html")
|
||||||
|
if err != nil {
|
||||||
|
return templateError("captcha.html", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if buildAll || t == "catalog" {
|
if buildAll || t == "catalog" {
|
||||||
catalog_tmpl, err = loadTemplate("catalog.html", "img_header.html", "global_footer.html")
|
catalog_tmpl, err = loadTemplate("catalog.html", "img_header.html", "global_footer.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
14
src/types.go
14
src/types.go
|
@ -341,8 +341,12 @@ type GochanConfig struct {
|
||||||
NewTabOnOutlinks bool `description:"If checked, links to external sites will open in a new tab." default:"checked"`
|
NewTabOnOutlinks bool `description:"If checked, links to external sites will open in a new tab." default:"checked"`
|
||||||
EnableQuickReply bool `description:"If checked, an optional quick reply box is used. This may end up being removed." default:"checked"`
|
EnableQuickReply bool `description:"If checked, an optional quick reply box is used. This may end up being removed." default:"checked"`
|
||||||
|
|
||||||
DateTimeFormat string `description:"The format used for dates. See <a href=\"https://golang.org/pkg/time/#Time.Format\">here</a> for more info."`
|
DateTimeFormat string `description:"The format used for dates. See <a href=\"https://golang.org/pkg/time/#Time.Format\">here</a> for more info." default:"Mon, January 02, 2006 15:04 PM"`
|
||||||
AkismetAPIKey string `description:"The API key to be sent to Akismet for post spam checking. If the key is invalid, Akismet won't be used."`
|
AkismetAPIKey string `description:"The API key to be sent to Akismet for post spam checking. If the key is invalid, Akismet won't be used."`
|
||||||
|
UseCaptcha bool `description:"If checked, a captcha will be generated"`
|
||||||
|
CaptchaWidth int `description:"Width of the generated captcha image" default:"240"`
|
||||||
|
CaptchaHeight int `description:"Height of the generated captcha image" default:"80"`
|
||||||
|
CaptchaMinutesExpire int `description:"Number of minutes before a user has to enter a new CAPTCHA before posting. If <1 they have to submit one for every post." default:"15"`
|
||||||
EnableGeoIP bool `description:"If checked, this enables the usage of GeoIP for posts." default:"checked"`
|
EnableGeoIP bool `description:"If checked, this enables the usage of GeoIP for posts." default:"checked"`
|
||||||
GeoIPDBlocation string `description:"Specifies the location of the GeoIP database file. If you're using CloudFlare, you can set it to cf to rely on CloudFlare for GeoIP information." default:"/usr/share/GeoIP/GeoIP.dat"`
|
GeoIPDBlocation string `description:"Specifies the location of the GeoIP database file. If you're using CloudFlare, you can set it to cf to rely on CloudFlare for GeoIP information." default:"/usr/share/GeoIP/GeoIP.dat"`
|
||||||
MaxRecentPosts int `description:"The maximum number of posts to show on the Recent Posts list on the front page." default:"3"`
|
MaxRecentPosts int `description:"The maximum number of posts to show on the Recent Posts list on the front page." default:"3"`
|
||||||
|
@ -587,6 +591,14 @@ DefaultStyle must refer to a given Style's Filename field. If DefaultStyle does
|
||||||
config.DateTimeFormat = "Mon, January 02, 2006 15:04 PM"
|
config.DateTimeFormat = "Mon, January 02, 2006 15:04 PM"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.CaptchaWidth == 0 {
|
||||||
|
config.CaptchaWidth = 240
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.CaptchaHeight == 0 {
|
||||||
|
config.CaptchaHeight = 80
|
||||||
|
}
|
||||||
|
|
||||||
if config.EnableGeoIP {
|
if config.EnableGeoIP {
|
||||||
if config.GeoIPDBlocation == "" {
|
if config.GeoIPDBlocation == "" {
|
||||||
println(0, "GeoIPDBlocation not set in gochan.json, disabling EnableGeoIP.")
|
println(0, "GeoIPDBlocation not set in gochan.json, disabling EnableGeoIP.")
|
||||||
|
|
|
@ -536,6 +536,11 @@ func marshalJSON(tag string, data interface{}, indent bool) (string, error) {
|
||||||
return string(jsonBytes), err
|
return string(jsonBytes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jsonError(err string) string {
|
||||||
|
errJSON, _ := marshalJSON("error", err, false)
|
||||||
|
return errJSON
|
||||||
|
}
|
||||||
|
|
||||||
func limitArraySize(arr []string, maxSize int) []string {
|
func limitArraySize(arr []string, maxSize int) []string {
|
||||||
if maxSize > len(arr)-1 || maxSize < 0 {
|
if maxSize > len(arr)-1 || maxSize < 0 {
|
||||||
return arr
|
return arr
|
||||||
|
|
18
templates/captcha.html
Normal file
18
templates/captcha.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Gochan CAPTCHA</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{with .Result}}{{$.Result}}{{end}}
|
||||||
|
<form action="/captcha" method="POST">
|
||||||
|
<img src="{{.Base64String}}" /><br />
|
||||||
|
<input type="text" name="captchaanswer" autocomplete="off" />
|
||||||
|
<input type="hidden" name="captchaid" value="{{.CaptchaID}}" />
|
||||||
|
{{with .EmailCmd}}<input type="hidden" name="emailcmd" value="{{$.EmailCmd}}" />{{end}}
|
||||||
|
{{with .TempPostIndex}}<input type="hidden" name="temppostindex" value="{{$.TempPostIndex}}" />{{end}}
|
||||||
|
<input type="submit" value="Submit" /><br />
|
||||||
|
<input type="submit" name="reload" value="Reload" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
vagrant/Vagrantfile
vendored
3
vagrant/Vagrantfile
vendored
|
@ -16,6 +16,7 @@ Vagrant.configure("2") do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
config.vm.provision :shell, path: "bootstrap.sh", env: {
|
config.vm.provision :shell, path: "bootstrap.sh", env: {
|
||||||
:DBTYPE => DBTYPE
|
:DBTYPE => DBTYPE,
|
||||||
|
:FROMDOCKER => ""
|
||||||
}, args: "install"
|
}, args: "install"
|
||||||
end
|
end
|
||||||
|
|
|
@ -76,20 +76,21 @@ ln -sf /etc/nginx/sites-available/gochan.nginx /etc/nginx/sites-enabled/
|
||||||
sed -e 's/sendfile on;/sendfile off;/' -i /etc/nginx/nginx.conf
|
sed -e 's/sendfile on;/sendfile off;/' -i /etc/nginx/nginx.conf
|
||||||
|
|
||||||
# Make sure our shared directories are mounted before nginx starts
|
# Make sure our shared directories are mounted before nginx starts
|
||||||
# service nginx disable
|
systemctl disable nginx
|
||||||
update-rc.d nginx enable
|
|
||||||
sed -i 's/WantedBy=multi-user.target/WantedBy=vagrant.mount/' /lib/systemd/system/nginx.service
|
sed -i 's/WantedBy=multi-user.target/WantedBy=vagrant.mount/' /lib/systemd/system/nginx.service
|
||||||
# systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
# service nginx enable
|
systemctl enable nginx
|
||||||
# service nginx restart &
|
systemctl restart nginx &
|
||||||
wait
|
wait
|
||||||
|
|
||||||
mkdir -p /vagrant/lib
|
mkdir -p /vagrant/lib
|
||||||
cd /opt/gochan
|
cd /vagrant
|
||||||
export GOPATH=/opt/gochan/lib
|
export GOPATH=/vagrant/lib
|
||||||
# mkdir /home/vagrant/bin
|
echo "export GOPATH=/vagrant/lib" >> /home/vagrant/.bashrc
|
||||||
# ln -s /usr/lib/go-1.10/bin/* /home/vagrant/bin/
|
mkdir /home/vagrant/bin
|
||||||
# export PATH="$PATH:/home/vagrant/bin"
|
ln -s /usr/lib/go-1.10/bin/* /home/vagrant/bin/
|
||||||
|
export PATH="$PATH:/home/vagrant/bin"
|
||||||
|
echo 'export PATH="$$PATH:/home/vagrant/bin"'
|
||||||
|
|
||||||
function changePerms {
|
function changePerms {
|
||||||
chmod -R 755 $1
|
chmod -R 755 $1
|
||||||
|
@ -153,6 +154,5 @@ fi
|
||||||
# systemctl start gochan.service
|
# systemctl start gochan.service
|
||||||
# fi
|
# fi
|
||||||
|
|
||||||
echo
|
echo "Server set up. You can access it from a browser at http://172.27.0.3/"
|
||||||
echo "Server set up, please run \"vagrant ssh\" on your host machine."
|
echo "The first time gochan is run, it will create a simple /test/ board."
|
||||||
echo "Then browse to http://172.27.0.3/manage to complete installation."
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue