mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-01 22:26:24 -07:00
replace my half-baked tripcode generator with a better one, improve post sanitizing/cookie saving
This commit is contained in:
parent
92a4ff638f
commit
7340a030bf
9 changed files with 205 additions and 179 deletions
2
Makefile
2
Makefile
|
@ -3,7 +3,7 @@ GOCHAN_DEBUG=1
|
|||
GOCHAN_VERBOSE=2
|
||||
GOCHAN_VERBOSITY=0 # This is set by "make release/debug/verbose"
|
||||
|
||||
GOCHAN_VERSION=1.6
|
||||
GOCHAN_VERSION=1.7
|
||||
GOCHAN_BUILDTIME=$(shell date +%y%m%d.%H%M)
|
||||
ifeq ($(GOOS), windows)
|
||||
GOCHAN_BIN=gochan.exe
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Gochan
|
||||
A multi-threaded imageboard server written in Go
|
||||
A semi-standalone imageboard server written in Go
|
||||
|
||||
http://gochan.org
|
||||
|
||||
|
|
2
dist.sh
2
dist.sh
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
VERSION=1.6
|
||||
VERSION=1.7
|
||||
GOOS_ORIG=$GOOS
|
||||
|
||||
function copyStuff {
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
<h1>404: File not found</h1>
|
||||
<img src="/error/lol 404.gif" border="0" alt="">
|
||||
<p>The requested file could not be found on this server. Are you just typing random stuff in the address bar? If you followed a link from this site here, then post <a href="/site">here</a></p>
|
||||
<hr><address>http://gochan.org powered by Gochan v1.6</address>
|
||||
<hr><address>http://gochan.org powered by Gochan v1.7</address>
|
||||
</body>
|
||||
</html>
|
|
@ -7,6 +7,6 @@
|
|||
<h1>500: Internal Server error</h1>
|
||||
<img src="/error/derpy server.gif" border="0" alt="">
|
||||
<p>The server encountered an error while trying to serve the page, and we apologize for the inconvenience. The <a href="https://en.wikipedia.org/wiki/Idiot">system administrator</a> will try to fix things as soon has he/she/it can.</p>
|
||||
<hr><address>http://gochan.org powered by Gochan v1.6</address>
|
||||
<hr><address>http://gochan.org powered by Gochan v1.7</address>
|
||||
</body>
|
||||
</html>
|
335
src/posting.go
335
src/posting.go
|
@ -22,13 +22,14 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aquilax/tripcode"
|
||||
"github.com/disintegration/imaging"
|
||||
crypt "github.com/nyarla/go-crypt"
|
||||
)
|
||||
|
||||
const (
|
||||
whitespace_match = "[\000-\040]"
|
||||
gt = ">"
|
||||
whitespaceMatch = "[\000-\040]"
|
||||
gt = ">"
|
||||
yearInSeconds = 31536000
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,26 +38,14 @@ var (
|
|||
all_boards []interface{}
|
||||
)
|
||||
|
||||
func generateTripCode(input string) string {
|
||||
re := regexp.MustCompile("[^\\.-z]") // remove every ASCII character before . and after z
|
||||
|
||||
input += " " // padding
|
||||
input = strings.Replace(input, "&", "&", -1)
|
||||
input = strings.Replace(input, "\\'", "'", -1)
|
||||
|
||||
salt := string(re.ReplaceAllLiteral([]byte(input), []byte(".")))
|
||||
salt = byteByByteReplace(salt[1:3], ":;<=>?@[\\]^_`", "ABCDEFGabcdef") // stole-I MEAN BORROWED from Kusaba X
|
||||
return crypt.Crypt(input, salt)[3:]
|
||||
}
|
||||
|
||||
// buildBoards builds one or all boards. If all == true, all boards will have their pages built.
|
||||
// If all == false, the board with the id equal to the value specified as which.
|
||||
// The return value is a string of HTML with debug information produced by the build process.
|
||||
func buildBoards(all bool, which int) (html string) {
|
||||
// if all is set to true, ignore which, otherwise, which = build only specified boardid
|
||||
if !all {
|
||||
_board, _ := getBoardArr(map[string]interface{}{"id": which}, "")
|
||||
board := _board[0]
|
||||
boardArr, _ := getBoardArr(map[string]interface{}{"id": which}, "")
|
||||
board := boardArr[0]
|
||||
html += buildBoardPages(&board) + "<br />\n"
|
||||
html += buildThreads(true, board.ID, 0)
|
||||
return
|
||||
|
@ -174,27 +163,18 @@ func buildBoardPages(board *BoardsTable) (html string) {
|
|||
// Otherwise, limit the replies to the configured value for normal threads.
|
||||
numRepliesOnBoardPage = config.RepliesOnBoardPage
|
||||
}
|
||||
/*
|
||||
SELECT * FROM (
|
||||
SELECT * FROM `gc_posts` WHERE
|
||||
`boardid` = board.ID AND
|
||||
`parentid` = op_post.ID AND
|
||||
`deleted_timestamp` = '000'
|
||||
ORDER BY `id` DESC LIMIT config.RepliesOnBoardPage
|
||||
) AS posts ORDER BY id ASC;
|
||||
*/
|
||||
|
||||
posts_in_thread, err = getPostArr(map[string]interface{}{
|
||||
"boardid": board.ID,
|
||||
"parentid": op_post.ID,
|
||||
"deleted_timestamp": nil_timestamp,
|
||||
}, fmt.Sprintf(" ORDER BY `id` DESC LIMIT %d", numRepliesOnBoardPage))
|
||||
|
||||
posts_in_thread = reverse(posts_in_thread)
|
||||
|
||||
if err != nil {
|
||||
html += err.Error() + "<br />"
|
||||
}
|
||||
|
||||
posts_in_thread = reverse(posts_in_thread)
|
||||
|
||||
if len(posts_in_thread) > 0 {
|
||||
// Store the posts to show on board page
|
||||
thread.BoardReplies = posts_in_thread
|
||||
|
@ -384,19 +364,19 @@ func buildThreads(all bool, boardid, threadid int) (html string) {
|
|||
// TODO: detect which page will be built and only build that one and the board page
|
||||
// if all is set to true, ignore which, otherwise, which = build only specified boardid
|
||||
if !all {
|
||||
//_thread, _ := getPostArr("SELECT * FROM " + config.DBprefix + "posts WHERE `boardid` = " + strconv.Itoa(boardid) + " AND `id` = " + strconv.Itoa(threadid) + " AND `parentid` = 0 AND `deleted_timestamp` = '" + nil_timestamp + "'")
|
||||
_thread, _ := getPostArr(map[string]interface{}{
|
||||
"boardid": boardid,
|
||||
"id": threadid,
|
||||
"parentid": 0,
|
||||
"deleted_timestamp": nil_timestamp,
|
||||
}, "")
|
||||
thread := _thread[0].(PostTable)
|
||||
//thread_struct := thread.(PostTable)
|
||||
html += buildThreadPages(&thread) + "<br />\n"
|
||||
//thread := _thread[0].(PostTable)
|
||||
thread := _thread[0]
|
||||
thread_struct := thread.(PostTable)
|
||||
html += buildThreadPages(&thread_struct) + "<br />\n"
|
||||
return
|
||||
}
|
||||
//threads, _ := getPostArr("SELECT * FROM " + config.DBprefix + "posts WHERE `boardid` = " + strconv.Itoa(boardid) + " AND `parentid` = 0 AND `deleted_timestamp` = '" + nil_timestamp + "'")
|
||||
|
||||
threads, _ := getPostArr(map[string]interface{}{
|
||||
"boardid": boardid,
|
||||
"parentid": 0,
|
||||
|
@ -414,7 +394,7 @@ func buildThreads(all bool, boardid, threadid int) (html string) {
|
|||
|
||||
// buildThreadPages builds the pages for a thread given by a PostTable object.
|
||||
func buildThreadPages(op *PostTable) (html string) {
|
||||
var board_dir string
|
||||
var boardDir string
|
||||
var anonymous string
|
||||
var replies []interface{}
|
||||
var current_page_file *os.File
|
||||
|
@ -430,7 +410,7 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
return
|
||||
}
|
||||
|
||||
err = stmt.QueryRow(op.BoardID).Scan(&board_dir, &anonymous)
|
||||
err = stmt.QueryRow(op.BoardID).Scan(&boardDir, &anonymous)
|
||||
if err != nil {
|
||||
errortext = "Failed getting board directory and board's anonymous setting from post: " + err.Error()
|
||||
html += errortext + "<br />\n"
|
||||
|
@ -439,7 +419,6 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
return
|
||||
}
|
||||
|
||||
//replies, err = getPostArr("SELECT * FROM " + config.DBprefix + "posts WHERE `boardid` = " + strconv.Itoa(op.BoardID) + " AND `parentid` = " + strconv.Itoa(op.ID) + " AND `deleted_timestamp` = '" + nil_timestamp + "' ORDER BY `id` ASC")
|
||||
replies, err = getPostArr(map[string]interface{}{
|
||||
"boardid": op.BoardID,
|
||||
"parentid": op.ID,
|
||||
|
@ -452,17 +431,23 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
println(1, errortext)
|
||||
return
|
||||
}
|
||||
os.Remove(path.Join(config.DocumentRoot, board_dir, "res", strconv.Itoa(op.ID)+".html"))
|
||||
os.Remove(path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".html"))
|
||||
|
||||
thread_pages := paginate(config.PostsPerThreadPage, replies)
|
||||
var repliesInterface []interface{}
|
||||
for _, reply := range replies {
|
||||
repliesInterface = append(repliesInterface, reply)
|
||||
}
|
||||
|
||||
//thread_pages := paginate(config.PostsPerThreadPage, replies)
|
||||
thread_pages := paginate(config.PostsPerThreadPage, repliesInterface)
|
||||
for i, _ := range thread_pages {
|
||||
thread_pages[i] = append([]interface{}{op}, thread_pages[i]...)
|
||||
}
|
||||
deleteMatchingFiles(path.Join(config.DocumentRoot, board_dir, "res"), "^"+strconv.Itoa(op.ID)+"p")
|
||||
deleteMatchingFiles(path.Join(config.DocumentRoot, boardDir, "res"), "^"+strconv.Itoa(op.ID)+"p")
|
||||
|
||||
op.NumPages = len(thread_pages)
|
||||
|
||||
current_page_filepath := path.Join(config.DocumentRoot, board_dir, "res", strconv.Itoa(op.ID)+".html")
|
||||
current_page_filepath := path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".html")
|
||||
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
if err != nil {
|
||||
errortext = "Failed opening " + current_page_filepath + ": " + err.Error()
|
||||
|
@ -477,8 +462,9 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
&Wrapper{IName: "sections_w", Data: all_sections},
|
||||
&Wrapper{IName: "posts_w", Data: append([]interface{}{op}, replies...)},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
errortext = "Failed building /" + board_dir + "/res/" + strconv.Itoa(op.ID) + ".html: " + err.Error()
|
||||
fmt.Sprintf("Failed building /%s/res/%d.html: %s", boardDir, op.ID, err.Error())
|
||||
html += errortext + "<br />\n"
|
||||
println(1, errortext)
|
||||
errorLog.Print(errortext)
|
||||
|
@ -486,14 +472,14 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
}
|
||||
|
||||
// Put together the thread JSON
|
||||
thread_json_file, err := os.OpenFile(path.Join(config.DocumentRoot, board_dir, "res", strconv.Itoa(op.ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
threadJSONFile, err := os.OpenFile(path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+".json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
defer func() {
|
||||
if thread_json_file != nil {
|
||||
thread_json_file.Close()
|
||||
if threadJSONFile != nil {
|
||||
threadJSONFile.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
errortext = "Failed opening /" + board_dir + "/res/" + strconv.Itoa(op.ID) + ".json: " + err.Error()
|
||||
errortext = fmt.Sprintf("Failed opening /%s/res/%d.json: %s", boardDir, op.ID, err.Error())
|
||||
html += errortext + "<br />\n"
|
||||
println(1, errortext)
|
||||
errorLog.Print(errortext)
|
||||
|
@ -510,12 +496,10 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
// Iterate through each reply, which are of type PostTable
|
||||
for _, post_int := range replies {
|
||||
post := post_int.(PostTable)
|
||||
|
||||
post_obj := makePostJSON(post, anonymous)
|
||||
|
||||
thread_json_wrapper.Posts = append(thread_json_wrapper.Posts, post_obj)
|
||||
}
|
||||
thread_json, err := json.Marshal(thread_json_wrapper)
|
||||
threadJSON, err := json.Marshal(thread_json_wrapper)
|
||||
|
||||
if err != nil {
|
||||
errortext = "Failed to marshal to JSON: " + err.Error()
|
||||
|
@ -525,24 +509,23 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
return
|
||||
}
|
||||
|
||||
_, err = thread_json_file.Write(thread_json)
|
||||
|
||||
_, err = threadJSONFile.Write(threadJSON)
|
||||
if err != nil {
|
||||
errortext = "Failed writing /" + board_dir + "/res/" + strconv.Itoa(op.ID) + ".json: " + err.Error()
|
||||
errortext = fmt.Sprintf("Failed writing /%s/res/%d.json: %s", boardDir, op.ID, err.Error())
|
||||
errorLog.Println(errortext)
|
||||
println(1, errortext)
|
||||
html += errortext + "<br />\n"
|
||||
return
|
||||
}
|
||||
|
||||
success_text := "Built /" + board_dir + "/" + strconv.Itoa(op.ID) + " successfully"
|
||||
success_text := fmt.Sprintf("Built /%s/%d successfully", boardDir, op.ID)
|
||||
html += success_text + "<br />\n"
|
||||
println(2, success_text)
|
||||
|
||||
for page_num, page_posts := range thread_pages {
|
||||
op.CurrentPage = page_num
|
||||
|
||||
current_page_filepath := path.Join(config.DocumentRoot, board_dir, "res", strconv.Itoa(op.ID)+"p"+strconv.Itoa(op.CurrentPage+1)+".html")
|
||||
current_page_filepath := path.Join(config.DocumentRoot, boardDir, "res", strconv.Itoa(op.ID)+"p"+strconv.Itoa(op.CurrentPage+1)+".html")
|
||||
current_page_file, err = os.OpenFile(current_page_filepath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0777)
|
||||
if err != nil {
|
||||
errortext = "Failed opening " + current_page_filepath + ": " + err.Error()
|
||||
|
@ -551,19 +534,26 @@ func buildThreadPages(op *PostTable) (html string) {
|
|||
errorLog.Println(errortext)
|
||||
return
|
||||
}
|
||||
|
||||
// var postsSanitized []interface{}
|
||||
// for i := 0; i < len(page_posts); i++ {
|
||||
// postsSanitized = append(postsSanitized, sanitizePost(page_posts[i].(PostTable)))
|
||||
// }
|
||||
|
||||
err = renderTemplate(img_threadpage_tmpl, "threadpage", current_page_file,
|
||||
&Wrapper{IName: "boards_", Data: all_boards},
|
||||
&Wrapper{IName: "sections_w", Data: all_sections},
|
||||
//&Wrapper{IName: "posts_w", Data: postsSanitized},
|
||||
&Wrapper{IName: "posts_w", Data: page_posts},
|
||||
)
|
||||
if err != nil {
|
||||
errortext = fmt.Sprintf("Failed building /%s/%d: %s", board_dir, op.ID, err.Error())
|
||||
errortext = fmt.Sprintf("Failed building /%s/%d: %s", boardDir, op.ID, err.Error())
|
||||
html += errortext + "<br />\n"
|
||||
println(1, errortext)
|
||||
errorLog.Print(errortext)
|
||||
return
|
||||
}
|
||||
success_text := fmt.Sprintf("Built /%s/%dp%d successfully", board_dir, op.ID, op.CurrentPage+1)
|
||||
success_text := fmt.Sprintf("Built /%s/%dp%d successfully", boardDir, op.ID, op.CurrentPage+1)
|
||||
html += success_text + "<br />\n"
|
||||
println(2, success_text)
|
||||
}
|
||||
|
@ -590,7 +580,7 @@ func buildFrontPage() (html string) {
|
|||
}
|
||||
|
||||
// get front pages
|
||||
rows, err := db.Query("SELECT * FROM `" + config.DBprefix + "frontpage`;")
|
||||
rows, err := db.Query("SELECT * FROM `" + config.DBprefix + "frontpage`")
|
||||
if err != nil {
|
||||
errortext = "Failed getting front page rows: " + err.Error()
|
||||
errorLog.Print(errortext)
|
||||
|
@ -694,7 +684,7 @@ func buildBoardListJSON() (html string) {
|
|||
board_list_wrapper.Boards = append(board_list_wrapper.Boards, board_obj)
|
||||
}
|
||||
|
||||
board_json, err := json.Marshal(board_list_wrapper)
|
||||
boardJSON, err := json.Marshal(board_list_wrapper)
|
||||
|
||||
if err != nil {
|
||||
errortext = "Failed marshal to JSON: " + err.Error()
|
||||
|
@ -702,7 +692,7 @@ func buildBoardListJSON() (html string) {
|
|||
println(1, errortext)
|
||||
return errortext + "<br />\n"
|
||||
}
|
||||
_, err = board_list_file.Write(board_json)
|
||||
_, err = board_list_file.Write(boardJSON)
|
||||
|
||||
if err != nil {
|
||||
errortext = "Failed writing boards.json file: " + err.Error()
|
||||
|
@ -713,6 +703,26 @@ func buildBoardListJSON() (html string) {
|
|||
return "Board list JSON rebuilt successfully.<br />"
|
||||
}
|
||||
|
||||
// bumps the given thread on the given board and returns true if it exists
|
||||
// or false on error
|
||||
func bumpThread(postID, boardID int) bool {
|
||||
stmt, err := db.Prepare("UPDATE `" + config.DBprefix + "posts` SET `bumped` = ? WHERE `id` = ? AND `boardid` = ?")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer func() {
|
||||
if stmt != nil {
|
||||
stmt.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = stmt.Exec(time.Now(), postID, boardID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Checks check poster's name/tripcode/file checksum (from PostTable post) for banned status
|
||||
// returns true if the user is banned
|
||||
func checkBannedStatus(post *PostTable, writer *http.ResponseWriter) ([]interface{}, error) {
|
||||
|
@ -888,49 +898,57 @@ func insertPost(post PostTable, bump bool) (sql.Result, error) {
|
|||
return result, err
|
||||
}
|
||||
|
||||
// called when a user accesses /post. Parse form data, then insert and build
|
||||
func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
||||
startTime := benchmarkTimer("makePost", time.Now(), true)
|
||||
request = *r
|
||||
writer = w
|
||||
var maxMessageLength int
|
||||
var errorText string
|
||||
var post PostTable
|
||||
domain := r.Host
|
||||
var formName string
|
||||
var nameCookie string
|
||||
var formEmail string
|
||||
|
||||
// fix new cookie domain for when you use a port number
|
||||
chopPortNumRegex := regexp.MustCompile("(.+|\\w+):(\\d+)$")
|
||||
domain = chopPortNumRegex.Split(domain, -1)[0]
|
||||
|
||||
post := PostTable{}
|
||||
post.IName = "post"
|
||||
post.ParentID, _ = strconv.Atoi(request.FormValue("threadid"))
|
||||
post.BoardID, _ = strconv.Atoi(request.FormValue("boardid"))
|
||||
|
||||
var emailCommand string
|
||||
//postName := html.EscapeString(escapeString(request.FormValue("postname")))
|
||||
postName := html.EscapeString(request.FormValue("postname"))
|
||||
formName = request.FormValue("postname")
|
||||
|
||||
if strings.Index(postName, "#") == -1 {
|
||||
post.Name = postName
|
||||
} else if strings.Index(postName, "#") == 0 {
|
||||
post.Tripcode = generateTripCode(postName[1:])
|
||||
} else if strings.Index(postName, "#") > 0 {
|
||||
postNameArr := strings.SplitN(postName, "#", 2)
|
||||
if strings.Index(formName, "#") == -1 {
|
||||
post.Name = formName
|
||||
} else if strings.Index(formName, "#") == 0 {
|
||||
post.Tripcode = tripcode.Tripcode(formName[1:])
|
||||
} else if strings.Index(formName, "#") > 0 {
|
||||
postNameArr := strings.SplitN(formName, "#", 2)
|
||||
post.Name = postNameArr[0]
|
||||
post.Tripcode = generateTripCode(postNameArr[1])
|
||||
post.Tripcode = tripcode.Tripcode(postNameArr[1])
|
||||
}
|
||||
|
||||
postEmail := escapeString(request.FormValue("postemail"))
|
||||
if strings.Index(postEmail, "noko") == -1 && strings.Index(postEmail, "sage") == -1 {
|
||||
post.Email = html.EscapeString(escapeString(postEmail))
|
||||
} else if strings.Index(postEmail, "#") > 1 {
|
||||
postEmailArr := strings.SplitN(postEmail, "#", 2)
|
||||
post.Email = html.EscapeString(escapeString(postEmailArr[0]))
|
||||
emailCommand = postEmailArr[1]
|
||||
} else if postEmail == "noko" || postEmail == "sage" {
|
||||
emailCommand = postEmail
|
||||
nameCookie = post.Name + post.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})
|
||||
|
||||
if strings.Index(formEmail, "noko") == -1 && strings.Index(formEmail, "sage") == -1 {
|
||||
post.Email = formEmail
|
||||
} else if strings.Index(formEmail, "#") > 1 {
|
||||
formEmailArr := strings.SplitN(formEmail, "#", 2)
|
||||
post.Email = formEmailArr[0]
|
||||
emailCommand = formEmailArr[1]
|
||||
} else if formEmail == "noko" || formEmail == "sage" {
|
||||
emailCommand = formEmail
|
||||
post.Email = ""
|
||||
}
|
||||
post.Subject = html.EscapeString(escapeString(request.FormValue("postsubject")))
|
||||
post.MessageText = strings.Trim(escapeString(request.FormValue("postmsg")), "\r\n")
|
||||
|
||||
post.Subject = request.FormValue("postsubject")
|
||||
post.MessageText = strings.Trim(request.FormValue("postmsg"), "\r\n")
|
||||
|
||||
stmt, err := db.Prepare("SELECT `max_message_length` from `" + config.DBprefix + "boards` WHERE `id` = ?")
|
||||
if err != nil {
|
||||
|
@ -953,36 +971,21 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
serveErrorPage(w, "Post body is too long")
|
||||
return
|
||||
}
|
||||
post.MessageHTML = html.EscapeString(post.MessageText)
|
||||
post.MessageHTML = sanitizeHTML(post.MessageText)
|
||||
formatMessage(&post)
|
||||
|
||||
post.Password = md5Sum(request.FormValue("postpassword"))
|
||||
println(1, postName)
|
||||
println(1, formName)
|
||||
|
||||
// Reverse escapes
|
||||
post_name_cookie := strings.Replace(postName, "&", "&", -1)
|
||||
post_name_cookie = strings.Replace(post_name_cookie, "\\'", "'", -1)
|
||||
post_name_cookie = strings.Replace(url.QueryEscape(post_name_cookie), "+", "%20", -1)
|
||||
println(1, post_name_cookie)
|
||||
nameCookie = strings.Replace(formName, "&", "&", -1)
|
||||
nameCookie = strings.Replace(nameCookie, "\\'", "'", -1)
|
||||
nameCookie = strings.Replace(url.QueryEscape(nameCookie), "+", "%20", -1)
|
||||
printf(1, "nameCookie: %s\n", nameCookie)
|
||||
|
||||
http.SetCookie(writer, &http.Cookie{Name: "name", Value: post_name_cookie, Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))), MaxAge: 31536000})
|
||||
// http.SetCookie(writer, &http.Cookie{Name: "name", Value: post_name_cookie, Path: "/", Domain: config.Domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))),MaxAge: 31536000})
|
||||
if emailCommand == "" {
|
||||
http.SetCookie(writer, &http.Cookie{Name: "email", Value: post.Email, Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))), MaxAge: 31536000})
|
||||
// http.SetCookie(writer, &http.Cookie{Name: "email", Value: post.Email, Path: "/", Domain: config.Domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))),MaxAge: 31536000})
|
||||
} else {
|
||||
if emailCommand == "noko" {
|
||||
if post.Email == "" {
|
||||
http.SetCookie(writer, &http.Cookie{Name: "email", Value: "noko", Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))), MaxAge: 31536000})
|
||||
// http.SetCookie(writer, &http.Cookie{Name: "email", Value:"noko", Path: "/", Domain: config.Domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))),MaxAge: 31536000})
|
||||
} else {
|
||||
http.SetCookie(writer, &http.Cookie{Name: "email", Value: post.Email + "#noko", Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))), MaxAge: 31536000})
|
||||
//http.SetCookie(writer, &http.Cookie{Name: "email", Value: post.Email + "#noko", Path: "/", Domain: config.Domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))),MaxAge: 31536000})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http.SetCookie(writer, &http.Cookie{Name: "password", Value: request.FormValue("postpassword"), Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))), MaxAge: 31536000})
|
||||
//http.SetCookie(writer, &http.Cookie{Name: "password", Value: request.FormValue("postpassword"), Path: "/", Domain: config.Domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(31536000))),MaxAge: 31536000})
|
||||
// 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: request.FormValue("postpassword"), Path: "/", Domain: domain, RawExpires: getSpecificSQLDateTime(time.Now().Add(time.Duration(yearInSeconds))), MaxAge: yearInSeconds})
|
||||
|
||||
post.IP = getRealIP(&request)
|
||||
post.Timestamp = time.Now()
|
||||
|
@ -995,6 +998,7 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
if !validReferrer(request) {
|
||||
accessLog.Print("Rejected post from possible spambot @ " + post.IP)
|
||||
//TODO: insert post into temporary post table and add to report list
|
||||
// or maybe not
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1009,12 +1013,11 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
default:
|
||||
}
|
||||
|
||||
file, handler, uploaderr := request.FormFile("imagefile")
|
||||
if uploaderr != nil {
|
||||
file, handler, err := request.FormFile("imagefile")
|
||||
if err != nil {
|
||||
// no file was uploaded
|
||||
post.Filename = ""
|
||||
accessLog.Print("Receiving post from " + request.RemoteAddr + ", referred from: " + request.Referer())
|
||||
|
||||
} else {
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
|
@ -1022,26 +1025,25 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
} else {
|
||||
post.FilenameOriginal = html.EscapeString(handler.Filename)
|
||||
filetype := getFileExtension(post.FilenameOriginal)
|
||||
thumb_filetype := filetype
|
||||
if thumb_filetype == "gif" {
|
||||
thumb_filetype = "jpg"
|
||||
thumbFiletype := filetype
|
||||
if thumbFiletype == "gif" {
|
||||
thumbFiletype = "jpg"
|
||||
}
|
||||
post.FilenameOriginal = escapeString(post.FilenameOriginal)
|
||||
post.Filename = getNewFilename() + "." + getFileExtension(post.FilenameOriginal)
|
||||
board_arr, _ := getBoardArr(map[string]interface{}{"id": request.FormValue("boardid")}, "") // getBoardArr("`id` = " + request.FormValue("boardid"))
|
||||
if len(board_arr) == 0 {
|
||||
boardArr, _ := getBoardArr(map[string]interface{}{"id": request.FormValue("boardid")}, "")
|
||||
if len(boardArr) == 0 {
|
||||
serveErrorPage(w, "No boards have been created yet")
|
||||
}
|
||||
_board_dir, _ := getBoardArr(map[string]interface{}{"id": request.FormValue("boardid")}, "") // getBoardArr("`id` = " + request.FormValue("boardid"))
|
||||
board_dir := _board_dir[0].Dir
|
||||
file_path := path.Join(config.DocumentRoot, "/"+board_dir+"/src/", post.Filename)
|
||||
thumb_path := path.Join(config.DocumentRoot, "/"+board_dir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "t."+thumb_filetype, -1))
|
||||
catalog_thumb_path := path.Join(config.DocumentRoot, "/"+board_dir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "c."+thumb_filetype, -1))
|
||||
_boardDir, _ := getBoardArr(map[string]interface{}{"id": request.FormValue("boardid")}, "")
|
||||
boardDir := _boardDir[0].Dir
|
||||
filePath := path.Join(config.DocumentRoot, "/"+boardDir+"/src/", post.Filename)
|
||||
thumbPath := path.Join(config.DocumentRoot, "/"+boardDir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "t."+thumbFiletype, -1))
|
||||
catalogThumbPath := path.Join(config.DocumentRoot, "/"+boardDir+"/thumb/", strings.Replace(post.Filename, "."+filetype, "c."+thumbFiletype, -1))
|
||||
|
||||
err := ioutil.WriteFile(file_path, data, 0777)
|
||||
err := ioutil.WriteFile(filePath, data, 0777)
|
||||
if err != nil {
|
||||
errorText = "Couldn't write file \"" + post.Filename + "\"" + err.Error()
|
||||
println(1, errorText)
|
||||
println(0, errorText)
|
||||
errorLog.Println(errorText)
|
||||
serveErrorPage(w, "Couldn't write file \""+post.FilenameOriginal+"\"")
|
||||
return
|
||||
|
@ -1051,7 +1053,7 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
post.FileChecksum = fmt.Sprintf("%x", md5.Sum(data))
|
||||
|
||||
// Attempt to load uploaded file with imaging library
|
||||
img, err := imaging.Open(file_path)
|
||||
img, err := imaging.Open(filePath)
|
||||
if err != nil {
|
||||
errorText = "Couldn't open uploaded file \"" + post.Filename + "\"" + err.Error()
|
||||
errorLog.Println(errorText)
|
||||
|
@ -1061,11 +1063,11 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
return
|
||||
} else {
|
||||
// Get image filesize
|
||||
stat, err := os.Stat(file_path)
|
||||
stat, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
errorLog.Println(err.Error())
|
||||
println(1, err.Error())
|
||||
serveErrorPage(w, err.Error())
|
||||
errorLog.Println("Couldn't get image filesize: " + err.Error())
|
||||
println(1, "Couldn't get image filesize: "+err.Error())
|
||||
serveErrorPage(w, "Couldn't get image filesize: "+err.Error())
|
||||
} else {
|
||||
post.Filesize = int(stat.Size())
|
||||
}
|
||||
|
@ -1088,7 +1090,7 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
serveErrorPage(w, "missing /spoiler.png")
|
||||
return
|
||||
} else {
|
||||
err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumb_path)
|
||||
err = syscall.Symlink(path.Join(config.DocumentRoot, "spoiler.png"), thumbPath)
|
||||
if err != nil {
|
||||
serveErrorPage(w, err.Error())
|
||||
return
|
||||
|
@ -1098,54 +1100,60 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
// If image fits in thumbnail size, symlink thumbnail to original
|
||||
post.ThumbW = img.Bounds().Max.X
|
||||
post.ThumbH = img.Bounds().Max.Y
|
||||
err := syscall.Symlink(file_path, thumb_path)
|
||||
err := syscall.Symlink(filePath, thumbPath)
|
||||
if err != nil {
|
||||
serveErrorPage(w, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var thumbnail image.Image
|
||||
var catalog_thumbnail image.Image
|
||||
var catalogThumbnail image.Image
|
||||
if post.ParentID == 0 {
|
||||
// If this is a new thread, generate thumbnail and catalog thumbnail
|
||||
thumbnail = createThumbnail(img, "op")
|
||||
catalog_thumbnail = createThumbnail(img, "catalog")
|
||||
err = imaging.Save(catalog_thumbnail, catalog_thumb_path)
|
||||
catalogThumbnail = createThumbnail(img, "catalog")
|
||||
println(1, catalogThumbPath)
|
||||
err = imaging.Save(catalogThumbnail, catalogThumbPath)
|
||||
if err != nil {
|
||||
serveErrorPage(w, err.Error())
|
||||
errorLog.Println("Couldn't generate catalog thumbnail: " + err.Error())
|
||||
serveErrorPage(w, "Couldn't generate catalog thumbnail: "+err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
thumbnail = createThumbnail(img, "reply")
|
||||
}
|
||||
err = imaging.Save(thumbnail, thumb_path)
|
||||
err = imaging.Save(thumbnail, thumbPath)
|
||||
if err != nil {
|
||||
println(1, err.Error())
|
||||
errorLog.Println(err.Error())
|
||||
serveErrorPage(w, err.Error())
|
||||
println(0, "Couldn't save thumbnail: "+err.Error())
|
||||
errorLog.Println("Couldn't save thumbnail: " + err.Error())
|
||||
serveErrorPage(w, "Couldn't save thumbnail: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if post.FilenameOriginal != "" {
|
||||
//post.FilenameOriginal = sanitizeHTML(post.FilenameOriginal)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(post.MessageText) == "" && post.Filename == "" {
|
||||
serveErrorPage(w, "Post must contain a message if no image is uploaded.")
|
||||
return
|
||||
}
|
||||
post_delay := sinceLastPost(&post)
|
||||
if post_delay > -1 {
|
||||
if post.ParentID == 0 && post_delay < config.NewThreadDelay {
|
||||
|
||||
postDelay := sinceLastPost(&post)
|
||||
if postDelay > -1 {
|
||||
if post.ParentID == 0 && postDelay < config.NewThreadDelay {
|
||||
serveErrorPage(w, "Please wait before making a new thread.")
|
||||
return
|
||||
} else if post.ParentID > 0 && post_delay < config.ReplyDelay {
|
||||
} else if post.ParentID > 0 && postDelay < config.ReplyDelay {
|
||||
serveErrorPage(w, "Please wait before making a reply.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isbanned, err := checkBannedStatus(&post, &w)
|
||||
isBanned, err := checkBannedStatus(&post, &w)
|
||||
if err != nil {
|
||||
errorText = "Error in checkBannedStatus: " + err.Error()
|
||||
serveErrorPage(w, err.Error())
|
||||
|
@ -1154,11 +1162,11 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(isbanned) > 0 {
|
||||
if len(isBanned) > 0 {
|
||||
var banpage_buffer bytes.Buffer
|
||||
var banpage_html string
|
||||
banpage_buffer.Write([]byte(""))
|
||||
err = renderTemplate(banpage_tmpl, "banpage", &banpage_buffer, &Wrapper{IName: "bans", Data: isbanned})
|
||||
err = renderTemplate(banpage_tmpl, "banpage", &banpage_buffer, &Wrapper{IName: "bans", Data: isBanned})
|
||||
if err != nil {
|
||||
fmt.Fprintf(writer, banpage_html+err.Error()+"\n</body>\n</html>")
|
||||
println(1, err.Error())
|
||||
|
@ -1169,6 +1177,7 @@ func makePost(w http.ResponseWriter, r *http.Request, data interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
post = sanitizePost(post)
|
||||
result, err := insertPost(post, emailCommand != "sage")
|
||||
if err != nil {
|
||||
serveErrorPage(w, err.Error())
|
||||
|
@ -1199,42 +1208,42 @@ func formatMessage(post *PostTable) {
|
|||
message := post.MessageHTML
|
||||
|
||||
// prepare each line to be formatted
|
||||
post_lines := strings.Split(message, "\\r\\n")
|
||||
for i, line := range post_lines {
|
||||
trimmed_line := strings.TrimSpace(line)
|
||||
line_words := strings.Split(trimmed_line, " ")
|
||||
is_greentext := false // if true, append </span> to end of line
|
||||
for w, word := range line_words {
|
||||
postLines := strings.Split(message, "\\r\\n")
|
||||
for i, line := range postLines {
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
lineWords := strings.Split(trimmedLine, " ")
|
||||
isGreentext := false // if true, append </span> to end of line
|
||||
for w, word := range lineWords {
|
||||
if strings.LastIndex(word, gt+gt) == 0 {
|
||||
//word is a backlink
|
||||
_, err := strconv.Atoi(word[8:])
|
||||
if err == nil {
|
||||
// the link is in fact, a valid int
|
||||
var board_dir string
|
||||
var link_parent int
|
||||
var boardDir string
|
||||
var linkParent int
|
||||
stmt, _ := db.Prepare("SELECT `dir`,`parentid` FROM " + config.DBprefix + "posts," + config.DBprefix + "boards WHERE " + config.DBprefix + "posts.id = ?")
|
||||
stmt.QueryRow(word[8:]).Scan(&board_dir, &link_parent)
|
||||
stmt.QueryRow(word[8:]).Scan(&boardDir, &linkParent)
|
||||
// get post board dir
|
||||
|
||||
if board_dir == "" {
|
||||
line_words[w] = "<a href=\"javascript:;\"><strike>" + word + "</strike></a>"
|
||||
} else if link_parent == 0 {
|
||||
line_words[w] = "<a href=\"/" + board_dir + "/res/" + word[8:] + ".html\">" + word + "</a>"
|
||||
if boardDir == "" {
|
||||
lineWords[w] = "<a href=\"javascript:;\"><strike>" + word + "</strike></a>"
|
||||
} else if linkParent == 0 {
|
||||
lineWords[w] = "<a href=\"/" + boardDir + "/res/" + word[8:] + ".html\">" + word + "</a>"
|
||||
} else {
|
||||
line_words[w] = "<a href=\"/" + board_dir + "/res/" + strconv.Itoa(link_parent) + ".html#" + word[8:] + "\">" + word + "</a>"
|
||||
lineWords[w] = "<a href=\"/" + boardDir + "/res/" + strconv.Itoa(linkParent) + ".html#" + word[8:] + "\">" + word + "</a>"
|
||||
}
|
||||
}
|
||||
} else if strings.Index(word, gt) == 0 && w == 0 {
|
||||
// word is at the beginning of a line, and is greentext
|
||||
is_greentext = true
|
||||
line_words[w] = "<span class=\"greentext\">" + word
|
||||
isGreentext = true
|
||||
lineWords[w] = "<span class=\"greentext\">" + word
|
||||
}
|
||||
}
|
||||
line = strings.Join(line_words, " ")
|
||||
if is_greentext {
|
||||
line = strings.Join(lineWords, " ")
|
||||
if isGreentext {
|
||||
line += "</span>"
|
||||
}
|
||||
post_lines[i] = line
|
||||
postLines[i] = line
|
||||
}
|
||||
post.MessageHTML = strings.Join(post_lines, "<br />")
|
||||
post.MessageHTML = strings.Join(postLines, "<br />")
|
||||
}
|
||||
|
|
21
src/sql.go
21
src/sql.go
|
@ -8,6 +8,7 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
@ -18,8 +19,8 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
db *sql.DB
|
||||
db_connected = false
|
||||
db *sql.DB
|
||||
dbConnected = false
|
||||
)
|
||||
|
||||
// escapeString and escapeQuotes copied from github.com/ziutek/mymysql/native/codecs.go
|
||||
|
@ -93,11 +94,11 @@ func connectToSQLServer() {
|
|||
if num_rows >= 16 {
|
||||
// the initial setup has already been run
|
||||
needsInitialSetup = false
|
||||
db_connected = true
|
||||
dbConnected = true
|
||||
println(0, "complete.")
|
||||
return
|
||||
} else {
|
||||
// does the initialsetupdb.sql exist?
|
||||
// check if initialsetupdb.sql still exists
|
||||
_, err := os.Stat("initialsetupdb.sql")
|
||||
if err != nil {
|
||||
println(0, "Initial setup file (initialsetupdb.sql) missing. Please reinstall gochan")
|
||||
|
@ -131,9 +132,19 @@ func connectToSQLServer() {
|
|||
}
|
||||
println(0, "complete.")
|
||||
needsInitialSetup = false
|
||||
db_connected = true
|
||||
dbConnected = true
|
||||
}
|
||||
}
|
||||
|
||||
func getSQLDateTime() string {
|
||||
now := time.Now()
|
||||
return now.Format(mysql_datetime_format)
|
||||
}
|
||||
|
||||
func getSpecificSQLDateTime(t time.Time) string {
|
||||
return t.Format(mysql_datetime_format)
|
||||
}
|
||||
|
||||
func makeInsertString(table string, columns []string) string {
|
||||
columnString := ""
|
||||
valuePlaceholders := ""
|
||||
|
|
14
src/util.go
14
src/util.go
|
@ -367,13 +367,19 @@ func reverse(arr []interface{}) (reversed []interface{}) {
|
|||
return
|
||||
}
|
||||
|
||||
func sanitizeHTML(input string) (output string) {
|
||||
output = html.EscapeString(input)
|
||||
return
|
||||
}
|
||||
|
||||
// sanitize/escape HTML strings in a post. This should be run immediately before
|
||||
// the post is inserted into the database
|
||||
func sanitizeHTML(post PostTable) PostTable {
|
||||
func sanitizePost(post PostTable) PostTable {
|
||||
sanitized := post
|
||||
html.EscapeString(sanitized.Name)
|
||||
html.EscapeString(sanitized.Email)
|
||||
html.EscapeString(sanitized.Subject)
|
||||
sanitized.Name = sanitizeHTML(sanitized.Name)
|
||||
sanitized.Email = sanitizeHTML(sanitized.Email)
|
||||
sanitized.Subject = sanitizeHTML(sanitized.Subject)
|
||||
sanitized.Password = sanitizeHTML(sanitized.Password)
|
||||
return sanitized
|
||||
}
|
||||
|
||||
|
|
|
@ -53,8 +53,8 @@
|
|||
<tr><td>No images after</td><td><input type="text" name="noimagesafter" value="0"/></td></tr>
|
||||
<tr><td>Max message length</td><td><input type="text" name="maxmessagelength" value="8192"/></td></tr>
|
||||
<tr><td>Embeds allowed</td><td><input type="checkbox" name="embedsallowed" /></td></tr>
|
||||
<tr><td>Redirect to thread</td><td><input type="checkbox" name="redirecttothread" /></td></tr>
|
||||
<tr><td>Require an uploaded file</td><td><input type="checkbox" name="require_file" checked/></td></tr>
|
||||
<tr><td>Redirect to thread</td><td><input type="checkbox" name="redirecttothread" checked/></td></tr>
|
||||
<tr><td>Require an uploaded file</td><td><input type="checkbox" name="require_file" /></td></tr>
|
||||
<tr><td>Enable catalog</td><td><input type="checkbox" name="enablecatalog" checked /></td></tr>
|
||||
</table>
|
||||
<input type="submit" /></form>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue