mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-03 07:36:23 -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
|
||||
done
|
||||
|
||||
# if [ -d /lib/systemd/system ]; then
|
||||
# echo "Installing systemd service file"
|
||||
# cp gochan.service /lib/systemd/system/gochan.service
|
||||
# systemctl daemon-reload
|
||||
# fi
|
||||
if [ -d /lib/systemd/system ]; then
|
||||
echo "Installing systemd service file"
|
||||
cp $symarg $PWD/gochan.service /lib/systemd/system/gochan.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
echo "Installation complete. Make sure to set the following values in gochan.json:"
|
||||
echo "DocumentRoot => $documentroot"
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
|
||||
"DateTimeFormat": "Mon, January 02, 2006 15:04 PM",
|
||||
"AkismetAPIKey": "",
|
||||
"UseCaptcha": false,
|
||||
"CaptchaWidth": 240,
|
||||
"CaptchaHeight": 80,
|
||||
"CaptchaMinutesTimeout": 15,
|
||||
"EnableGeoIP": true,
|
||||
"_comment": "set GeoIPDBlocation to cf to use Cloudflare's GeoIP",
|
||||
"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"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var versionStr string
|
||||
|
@ -30,6 +31,9 @@ func main() {
|
|||
handleError(0, customError(err))
|
||||
os.Exit(2)
|
||||
}
|
||||
initCaptcha()
|
||||
tempCleanerTicker = time.NewTicker(time.Minute * 5)
|
||||
go tempCleaner()
|
||||
|
||||
sc := make(chan os.Signal, 1)
|
||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
|
||||
|
|
|
@ -1115,4 +1115,17 @@ var manage_functions = map[string]ManageFunction{
|
|||
"\t\t</form>"
|
||||
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
|
||||
}},
|
||||
}
|
||||
|
|
107
src/posting.go
107
src/posting.go
|
@ -17,7 +17,6 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
@ -28,14 +27,16 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
whitespaceMatch = "[\000-\040]"
|
||||
gt = ">"
|
||||
yearInSeconds = 31536000
|
||||
gt = ">"
|
||||
yearInSeconds = 31536000
|
||||
)
|
||||
|
||||
var (
|
||||
allSections []BoardSection
|
||||
allBoards []Board
|
||||
allSections []BoardSection
|
||||
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
|
||||
|
@ -282,14 +283,18 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
startTime := benchmarkTimer("makePost", time.Now(), true)
|
||||
var maxMessageLength int
|
||||
var post Post
|
||||
domain := request.Host
|
||||
// domain := request.Host
|
||||
var formName string
|
||||
var nameCookie 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
|
||||
chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
|
||||
domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||
// chopPortNumRegex := regexp.MustCompile(`(.+|\w+):(\d+)$`)
|
||||
// domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||
|
||||
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
|
||||
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))
|
||||
|
@ -301,7 +306,8 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
post.Tripcode = parsedName["tripcode"]
|
||||
|
||||
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") {
|
||||
post.Email = formEmail
|
||||
|
@ -345,8 +351,8 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
nameCookie = strings.Replace(url.QueryEscape(nameCookie), "+", "%20", -1)
|
||||
|
||||
// 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: "password", Value: password, 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, MaxAge: yearInSeconds})
|
||||
|
||||
post.IP = getRealIP(request)
|
||||
post.Timestamp = time.Now()
|
||||
|
@ -383,12 +389,12 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
if err != nil || handler.Size == 0 {
|
||||
// no file was uploaded
|
||||
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())
|
||||
} else {
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
serveErrorPage(writer, handleError(1, "Couldn't read file: "+err.Error()))
|
||||
return
|
||||
} else {
|
||||
post.FilenameOriginal = html.EscapeString(handler.Filename)
|
||||
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))
|
||||
|
||||
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+"\"")
|
||||
return
|
||||
}
|
||||
|
@ -434,16 +440,14 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
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 {
|
||||
err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth)
|
||||
if err != nil {
|
||||
if err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth); err != nil {
|
||||
serveErrorPage(writer, handleError(1, err.Error()))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth_reply)
|
||||
if err != nil {
|
||||
if err := createVideoThumbnail(filePath, thumbPath, config.ThumbWidth_reply); err != nil {
|
||||
serveErrorPage(writer, handleError(1, err.Error()))
|
||||
return
|
||||
}
|
||||
|
@ -489,6 +493,7 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
if err != nil {
|
||||
os.Remove(filePath)
|
||||
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")
|
||||
return
|
||||
} else {
|
||||
|
@ -517,12 +522,10 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
if _, err := os.Stat(path.Join(config.DocumentRoot, "spoiler.png")); err != nil {
|
||||
serveErrorPage(writer, "missing /spoiler.png")
|
||||
return
|
||||
} else {
|
||||
err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumbPath)
|
||||
if err != nil {
|
||||
serveErrorPage(writer, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumbPath); err != nil {
|
||||
serveErrorPage(writer, err.Error())
|
||||
return
|
||||
}
|
||||
} else if config.ThumbWidth >= post.ImageW && config.ThumbHeight >= post.ImageH {
|
||||
// If image fits in thumbnail size, symlink thumbnail to original
|
||||
|
@ -596,6 +599,20 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
}
|
||||
|
||||
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 {
|
||||
serveErrorPage(writer, handleError(1, err.Error()))
|
||||
return
|
||||
|
@ -617,6 +634,46 @@ func makePost(writer http.ResponseWriter, request *http.Request) {
|
|||
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 {
|
||||
message = bbcompiler.Compile(message)
|
||||
// prepare each line to be formatted
|
||||
|
|
|
@ -142,6 +142,7 @@ func initServer() {
|
|||
referrerRegex = regexp.MustCompile(config.DomainRegex)
|
||||
|
||||
server.AddNamespace("banned", banHandler)
|
||||
server.AddNamespace("captcha", serveCaptcha)
|
||||
server.AddNamespace("manage", callManageFunction)
|
||||
server.AddNamespace("post", makePost)
|
||||
server.AddNamespace("util", utilHandler)
|
||||
|
|
|
@ -63,6 +63,11 @@ func connectToSQLServer() {
|
|||
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
|
||||
err = queryRowSQL("SELECT value FROM "+config.DBprefix+"info WHERE name = 'version'",
|
||||
[]interface{}{}, []interface{}{&sqlVersionStr})
|
||||
|
|
|
@ -291,6 +291,7 @@ var funcMap = template.FuncMap{
|
|||
|
||||
var (
|
||||
banpage_tmpl *template.Template
|
||||
captcha_tmpl *template.Template
|
||||
catalog_tmpl *template.Template
|
||||
errorpage_tmpl *template.Template
|
||||
front_page_tmpl *template.Template
|
||||
|
@ -328,7 +329,7 @@ func templateError(name string, err error) error {
|
|||
func initTemplates(which ...string) error {
|
||||
var err error
|
||||
buildAll := len(which) == 0 || which[0] == "all"
|
||||
|
||||
resetBoardSectionArrays()
|
||||
for _, t := range which {
|
||||
if buildAll || t == "banpage" {
|
||||
banpage_tmpl, err = loadTemplate("banpage.html", "global_footer.html")
|
||||
|
@ -336,6 +337,12 @@ func initTemplates(which ...string) error {
|
|||
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" {
|
||||
catalog_tmpl, err = loadTemplate("catalog.html", "img_header.html", "global_footer.html")
|
||||
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"`
|
||||
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."`
|
||||
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"`
|
||||
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"`
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
if config.CaptchaWidth == 0 {
|
||||
config.CaptchaWidth = 240
|
||||
}
|
||||
|
||||
if config.CaptchaHeight == 0 {
|
||||
config.CaptchaHeight = 80
|
||||
}
|
||||
|
||||
if config.EnableGeoIP {
|
||||
if config.GeoIPDBlocation == "" {
|
||||
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
|
||||
}
|
||||
|
||||
func jsonError(err string) string {
|
||||
errJSON, _ := marshalJSON("error", err, false)
|
||||
return errJSON
|
||||
}
|
||||
|
||||
func limitArraySize(arr []string, maxSize int) []string {
|
||||
if maxSize > len(arr)-1 || maxSize < 0 {
|
||||
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
|
||||
|
||||
config.vm.provision :shell, path: "bootstrap.sh", env: {
|
||||
:DBTYPE => DBTYPE
|
||||
:DBTYPE => DBTYPE,
|
||||
:FROMDOCKER => ""
|
||||
}, args: "install"
|
||||
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
|
||||
|
||||
# Make sure our shared directories are mounted before nginx starts
|
||||
# service nginx disable
|
||||
update-rc.d nginx enable
|
||||
systemctl disable nginx
|
||||
sed -i 's/WantedBy=multi-user.target/WantedBy=vagrant.mount/' /lib/systemd/system/nginx.service
|
||||
# systemctl daemon-reload
|
||||
# service nginx enable
|
||||
# service nginx restart &
|
||||
systemctl daemon-reload
|
||||
systemctl enable nginx
|
||||
systemctl restart nginx &
|
||||
wait
|
||||
|
||||
mkdir -p /vagrant/lib
|
||||
cd /opt/gochan
|
||||
export GOPATH=/opt/gochan/lib
|
||||
# mkdir /home/vagrant/bin
|
||||
# ln -s /usr/lib/go-1.10/bin/* /home/vagrant/bin/
|
||||
# export PATH="$PATH:/home/vagrant/bin"
|
||||
cd /vagrant
|
||||
export GOPATH=/vagrant/lib
|
||||
echo "export GOPATH=/vagrant/lib" >> /home/vagrant/.bashrc
|
||||
mkdir /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 {
|
||||
chmod -R 755 $1
|
||||
|
@ -153,6 +154,5 @@ fi
|
|||
# systemctl start gochan.service
|
||||
# fi
|
||||
|
||||
echo
|
||||
echo "Server set up, please run \"vagrant ssh\" on your host machine."
|
||||
echo "Then browse to http://172.27.0.3/manage to complete installation."
|
||||
echo "Server set up. You can access it from a browser at http://172.27.0.3/"
|
||||
echo "The first time gochan is run, it will create a simple /test/ board."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue