Split up into three files, refactor a bit.

このコミットが含まれているのは:
Christian Heller 2016-02-12 21:25:43 +01:00
コミット 157944f332
3個のファイルの変更485行の追加454行の削除

293
handlers.go ノーマルファイル
ファイルの表示

@ -0,0 +1,293 @@
package main
import "bufio"
import "crypto/rand"
import "encoding/base64"
import "golang.org/x/crypto/bcrypt"
import "gopkg.in/gomail.v2"
import "log"
import "github.com/gorilla/mux"
import "net/http"
import "os"
import "strconv"
import "strings"
import "time"
func passwordResetRequestGetHandler(w http.ResponseWriter, r *http.Request) {
if "" == mailuser {
execTemplate(w, "nopwresetrequest.html", "")
} else {
execTemplate(w, "pwresetrequest.html", "")
}
}
func passwordResetRequestPostHandler(w http.ResponseWriter, r *http.Request) {
preparePasswordReset := func(name string) {
if "" == mailuser {
return
}
var target string
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
return
} else if "" == tokens[1] {
return
}
target = tokens[1]
b := make([]byte, 64)
if _, err := rand.Read(b); err != nil {
log.Fatal("Random string generation failed", err)
}
urlPart := base64.URLEncoding.EncodeToString(b)
strTime := strconv.Itoa(int(time.Now().Unix()))
appendToFile(pwResetPath, urlPart+"\t"+name+"\t"+strTime)
m := gomail.NewMessage()
m.SetHeader("From", mailuser)
m.SetHeader("To", target)
m.SetHeader("Subject", "password reset link")
msg := myself + "/passwordreset/" + urlPart
m.SetBody("text/plain", msg)
if err := dialer.DialAndSend(m); err != nil {
log.Fatal("Can't send mail", err)
}
}
go preparePasswordReset(r.FormValue("name"))
http.Redirect(w, r, "/", 302)
}
func passwordResetLinkGetHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
if tokens, e := getFromFileEntryFor(pwResetPath, urlPart, 3); e == nil {
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file", err)
}
if createTime+resetLinkExp >= int(time.Now().Unix()) {
name := tokens[0]
tokensUser, err := getFromFileEntryFor(loginsPath, name,
5)
if err != nil {
log.Fatal("Can't read from loings file", err)
}
if "" != tokensUser[2] {
type data struct {
Secret string
Question string
}
err := templ.ExecuteTemplate(w,
"pwresetquestion.html", data{
Secret: urlPart,
Question: tokensUser[2]})
if err != nil {
log.Fatal("Trouble executing template",
err)
}
} else {
execTemplate(w, "pwreset.html", urlPart)
}
return
}
}
http.Redirect(w, r, "/404", 302)
}
func passwordResetLinkPostHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
name := r.FormValue("name")
if tokens, e := getFromFileEntryFor(pwResetPath, urlPart, 3); e == nil {
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file",
err)
}
if tokens[0] == name &&
createTime+resetLinkExp >= int(time.Now().Unix()) {
tokensOld, err := getFromFileEntryFor(loginsPath, name,
5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
if "" != tokensOld[2] &&
nil != bcrypt.CompareHashAndPassword(
[]byte(tokensOld[3]),
[]byte(r.FormValue("secanswer"))) {
http.Redirect(w, r, "/", 302)
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
tokensOld[0] = hash
line := name + "\t" + strings.Join(tokensOld, "\t")
replaceLineStartingWith(loginsPath, tokens[0], line)
removeLineStartingWith(pwResetPath, urlPart)
execTemplate(w, "feedset.html", "")
return
}
}
http.Redirect(w, r, "/", 302)
}
func signUpFormHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "nosignup.html", "")
return
}
execTemplate(w, "signupform.html", "")
}
func signUpHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "error.html",
"Account creation currently not allowed.")
return
}
name := r.FormValue("name")
if "" == name || !onlyLegalRunes(name) || len(name) > 140 {
execTemplate(w, "error.html", "Illegal name.")
return
}
if _, err := getFromFileEntryFor(loginsPath, name, 5); err == nil {
execTemplate(w, "error.html", "Username taken.")
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
mail := ""
if "" != r.FormValue("mail") {
mail, err = newMailAddress(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
appendToFile(loginsPath,
name+"\t"+hash+"\t"+mail+"\t"+secquestion+"\t"+secanswer)
execTemplate(w, "feedset.html", "")
}
func accountSetPwHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newPassword, 0)
}
func accountSetMailHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newMailAddress, 1)
}
func accountSetQuestionHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
tokens[2] = secquestion
tokens[3] = secanswer
replaceLineStartingWith(loginsPath, name,
name+"\t"+strings.Join(tokens, "\t"))
execTemplate(w, "feedset.html", "")
}
func listHandler(w http.ResponseWriter, r *http.Request) {
file := openFile(loginsPath)
defer file.Close()
scanner := bufio.NewScanner(bufio.NewReader(file))
var dir []string
tokens := tokensFromLine(scanner, 5)
for 0 != len(tokens) {
dir = append(dir, tokens[0])
tokens = tokensFromLine(scanner, 5)
}
type data struct{ Dir []string }
err := templ.ExecuteTemplate(w, "list.html", data{Dir: dir})
if err != nil {
log.Fatal("Trouble executing template", err)
}
}
func twtxtPostHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
text := r.FormValue("twt")
twtsFile := feedsPath + "/" + name
createFileIfNotExists(twtsFile)
text = strings.Replace(text, "\n", " ", -1)
appendToFile(twtsFile, time.Now().Format(time.RFC3339)+"\t"+text)
http.Redirect(w, r, "/"+feedsDir+"/"+name, 302)
}
func twtxtHandler(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
if !onlyLegalRunes(name) {
execTemplate(w, "error.html", "Bad path.")
return
}
path := feedsPath + "/" + name
if _, err := os.Stat(path); err != nil {
execTemplate(w, "error.html", "Empty twtxt for user.")
return
}
http.ServeFile(w, r, path)
}
func handleRoutes() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/", handleTemplate("index.html", ""))
router.HandleFunc("/feeds", listHandler).Methods("GET")
router.HandleFunc("/feeds/", listHandler)
router.HandleFunc("/accountsetquestion",
handleTemplate("accountsetquestion.html", "")).Methods("GET")
router.HandleFunc("/accountsetquestion", accountSetQuestionHandler).
Methods("POST")
router.HandleFunc("/accountsetmail",
handleTemplate("accountsetmail.html", "")).Methods("GET")
router.HandleFunc("/accountsetmail", accountSetMailHandler).
Methods("POST")
router.HandleFunc("/accountsetpw", handleTemplate("accountsetpw.html",
"")).Methods("GET")
router.HandleFunc("/accountsetpw", accountSetPwHandler).Methods("POST")
router.HandleFunc("/account", handleTemplate("account.html", ""))
router.HandleFunc("/signup", signUpFormHandler).Methods("GET")
router.HandleFunc("/signup", signUpHandler).Methods("POST")
router.HandleFunc("/feeds", twtxtPostHandler).Methods("POST")
router.HandleFunc("/feeds/{name}", twtxtHandler)
router.HandleFunc("/info", handleTemplate("info.html", contact))
router.HandleFunc("/passwordreset", passwordResetRequestPostHandler).
Methods("POST")
router.HandleFunc("/passwordreset", passwordResetRequestGetHandler).
Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkGetHandler).Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkPostHandler).Methods("POST")
router.HandleFunc("/style.css",
func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, templPath+"/style.css")
})
return router
}

165
io.go ノーマルファイル
ファイルの表示

@ -0,0 +1,165 @@
// htwtxt hosted twtxt server; see README for copyright and license info
package main
import "bufio"
import "errors"
import "log"
import "os"
import "strings"
import "io/ioutil"
const loginsFile = "logins.txt"
const feedsDir = "feeds"
const ipDelaysFile = "ip_delays.txt"
const pwResetFile = "password_reset.txt"
var certPath string
var dataDir string
var feedsPath string
var ipDelaysPath string
var keyPath string
var loginsPath string
var pwResetPath string
var templPath string
func createFileIfNotExists(path string) {
if _, err := os.Stat(path); err != nil {
file, err := os.Create(path)
if err != nil {
log.Fatal("Can't create file: ", err)
}
file.Close()
}
}
func openFile(path string) *os.File {
file, err := os.Open(path)
if err != nil {
file.Close()
log.Fatal("Can't open file for reading", err)
}
return file
}
func linesFromFile(path string) []string {
text, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal("Can't read file", err)
}
if string(text) == "" {
return []string{}
}
return strings.Split(string(text), "\n")
}
func writeAtomic(path, text string) {
tmpFile := path + "_tmp"
if err := ioutil.WriteFile(tmpFile, []byte(text), 0600); err != nil {
log.Fatal("Trouble writing file", err)
}
if err := os.Rename(path, path+"_"); err != nil {
log.Fatal("Trouble moving file", err)
}
if err := os.Rename(tmpFile, path); err != nil {
log.Fatal("Trouble moving file", err)
}
if err := os.Remove(path + "_"); err != nil {
log.Fatal("Trouble removing file", err)
}
}
func writeLinesAtomic(path string, lines []string) {
writeAtomic(path, strings.Join(lines, "\n"))
}
func appendToFile(path string, msg string) {
text, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal("Can't read file", err)
}
writeAtomic(path, string(text)+msg+"\n")
}
func removeLineStartingWith(path, token string) {
lines := linesFromFile(path)
lineNumber := -1
for lineCount := 0; lineCount < len(lines); lineCount += 1 {
line := lines[lineCount]
tokens := strings.Split(line, "\t")
if 0 == strings.Compare(token, tokens[0]) {
lineNumber = lineCount
break
}
}
lines = append(lines[:lineNumber], lines[lineNumber+1:]...)
writeLinesAtomic(path, lines)
}
func removeLineFromFile(path string, lineNumber int) {
lines := linesFromFile(ipDelaysPath)
lines = append(lines[:lineNumber], lines[lineNumber+1:]...)
writeLinesAtomic(path, lines)
}
func replaceLineStartingWith(path, token, newLine string) {
lines := linesFromFile(path)
for i, line := range lines {
tokens := strings.Split(line, "\t")
if 0 == strings.Compare(token, tokens[0]) {
lines[i] = newLine
break
}
}
writeLinesAtomic(path, lines)
}
func tokensFromLine(scanner *bufio.Scanner, nTokensExpected int) []string {
if !scanner.Scan() {
return []string{}
}
line := scanner.Text()
tokens := strings.Split(line, "\t")
if len(tokens) != nTokensExpected {
log.Fatal("Line in file had unexpected number of tokens")
}
return tokens
}
func getFromFileEntryFor(path, token string,
numberTokensExpected int) ([]string, error) {
file := openFile(path)
defer file.Close()
scanner := bufio.NewScanner(bufio.NewReader(file))
tokens := tokensFromLine(scanner, numberTokensExpected)
for 0 != len(tokens) {
if 0 == strings.Compare(tokens[0], token) {
return tokens[1:], nil
}
tokens = tokensFromLine(scanner, numberTokensExpected)
}
return []string{}, errors.New("")
}
func initFilesAndDirs() {
log.Println("Using as templates dir:", templPath)
log.Println("Using as data dir:", dataDir)
loginsPath = dataDir + "/" + loginsFile
feedsPath = dataDir + "/" + feedsDir
ipDelaysPath = dataDir + "/" + ipDelaysFile
pwResetPath = dataDir + "/" + pwResetFile
if "" != keyPath {
log.Println("Using TLS.")
if _, err := os.Stat(certPath); err != nil {
log.Fatal("No certificate file found.")
}
if _, err := os.Stat(keyPath); err != nil {
log.Fatal("No server key file found.")
}
}
createFileIfNotExists(loginsPath)
createFileIfNotExists(pwResetPath)
createFileIfNotExists(ipDelaysPath)
// TODO: Handle err here.
_ = os.Mkdir(feedsPath, 0700)
}

481
main.go
ファイルの表示

@ -2,18 +2,13 @@
package main
import "bufio"
import "crypto/rand"
import "encoding/base64"
import "errors"
import "flag"
import "fmt"
import "github.com/gorilla/mux"
import "golang.org/x/crypto/bcrypt"
import "golang.org/x/crypto/ssh/terminal"
import "gopkg.in/gomail.v2"
import "html/template"
import "io/ioutil"
import "log"
import "net"
import "net/http"
@ -23,145 +18,14 @@ import "strings"
import "syscall"
import "time"
const loginsFile = "logins.txt"
const feedsDir = "feeds"
const ipDelaysFile = "ip_delays.txt"
const pwResetFile = "password_reset.txt"
const legalUrlChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
"0123456789_"
const resetLinkExp = 1800
var dataDir string
var feedsPath string
var ipDelaysPath string
var loginsPath string
var mailpassword string
var mailport int
var mailserver string
var contact string
var dialer *gomail.Dialer
var mailuser string
var myself string
var pwResetPath string
var signupOpen bool
var templ *template.Template
var templPath string
func createFileIfNotExists(path string) {
if _, err := os.Stat(path); err != nil {
file, err := os.Create(path)
if err != nil {
log.Fatal("Can't create file: ", err)
}
file.Close()
}
}
func openFile(path string) *os.File {
file, err := os.Open(path)
if err != nil {
file.Close()
log.Fatal("Can't open file for reading", err)
}
return file
}
func linesFromFile(path string) []string {
text, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal("Can't read file", err)
}
if string(text) == "" {
return []string{}
}
return strings.Split(string(text), "\n")
}
func writeAtomic(path, text string) {
tmpFile := path + "_tmp"
if err := ioutil.WriteFile(tmpFile, []byte(text), 0600); err != nil {
log.Fatal("Trouble writing file", err)
}
if err := os.Rename(path, path+"_"); err != nil {
log.Fatal("Trouble moving file", err)
}
if err := os.Rename(tmpFile, path); err != nil {
log.Fatal("Trouble moving file", err)
}
if err := os.Remove(path + "_"); err != nil {
log.Fatal("Trouble removing file", err)
}
}
func writeLinesAtomic(path string, lines []string) {
writeAtomic(path, strings.Join(lines, "\n"))
}
func appendToFile(path string, msg string) {
text, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal("Can't read file", err)
}
writeAtomic(path, string(text)+msg+"\n")
}
func removeLineStartingWith(path, token string) {
lines := linesFromFile(path)
lineNumber := -1
for lineCount := 0; lineCount < len(lines); lineCount += 1 {
line := lines[lineCount]
tokens := strings.Split(line, "\t")
if 0 == strings.Compare(token, tokens[0]) {
lineNumber = lineCount
break
}
}
lines = append(lines[:lineNumber], lines[lineNumber+1:]...)
writeLinesAtomic(path, lines)
}
func removeLineFromFile(path string, lineNumber int) {
lines := linesFromFile(ipDelaysPath)
lines = append(lines[:lineNumber], lines[lineNumber+1:]...)
writeLinesAtomic(path, lines)
}
func replaceLineStartingWith(path, token, newLine string) {
lines := linesFromFile(path)
for i, line := range lines {
tokens := strings.Split(line, "\t")
if 0 == strings.Compare(token, tokens[0]) {
lines[i] = newLine
break
}
}
writeLinesAtomic(path, lines)
}
func tokensFromLine(scanner *bufio.Scanner, nTokensExpected int) []string {
if !scanner.Scan() {
return []string{}
}
line := scanner.Text()
tokens := strings.Split(line, "\t")
if len(tokens) != nTokensExpected {
log.Fatal("Line in file had unexpected number of tokens")
}
return tokens
}
func getFromFileEntryFor(path, token string,
numberTokensExpected int) ([]string, error) {
file := openFile(path)
defer file.Close()
scanner := bufio.NewScanner(bufio.NewReader(file))
tokens := tokensFromLine(scanner, numberTokensExpected)
for 0 != len(tokens) {
if 0 == strings.Compare(tokens[0], token) {
return tokens[1:], nil
}
tokens = tokensFromLine(scanner, numberTokensExpected)
}
return []string{}, errors.New("")
}
func execTemplate(w http.ResponseWriter, file string, input string) {
type data struct{ Msg string }
@ -179,6 +43,8 @@ func handleTemplate(path, msg string) func(w http.ResponseWriter,
}
func onlyLegalRunes(str string) bool {
const legalUrlChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz0123456789_"
for _, ru := range str {
if !(strings.ContainsRune(legalUrlChars, ru)) {
return false
@ -315,37 +181,6 @@ func changeLoginField(w http.ResponseWriter, r *http.Request,
execTemplate(w, "feedset.html", "")
}
func prepPasswordReset(name string) {
if "" == mailserver {
return
}
var target string
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
return
} else if "" == tokens[1] {
return
}
target = tokens[1]
b := make([]byte, 64)
if _, err := rand.Read(b); err != nil {
log.Fatal("Random string generation failed", err)
}
urlPart := base64.URLEncoding.EncodeToString(b)
strTime := strconv.Itoa(int(time.Now().Unix()))
appendToFile(pwResetPath, urlPart+"\t"+name+"\t"+strTime)
m := gomail.NewMessage()
m.SetHeader("From", mailuser)
m.SetHeader("To", target)
m.SetHeader("Subject", "password reset link")
msg := myself + "/passwordreset/" + urlPart
m.SetBody("text/plain", msg)
d := gomail.NewPlainDialer(mailserver, mailport, mailuser, mailpassword)
if err := d.DialAndSend(m); err != nil {
log.Fatal("Can't send mail", err)
}
}
func nameMyself(ssl bool, port int) string {
addresses, err := net.InterfaceAddrs()
if err != nil {
@ -358,7 +193,6 @@ func nameMyself(ssl bool, port int) string {
if ipnet.IP.To4() != nil {
ip = ipnet.IP.String()
}
}
}
s := ""
@ -368,16 +202,20 @@ func nameMyself(ssl bool, port int) string {
return "http" + s + "://" + ip + ":" + strconv.Itoa(port)
}
func readOptions() (*int, *string, *string, *string) {
portPtr := flag.Int("port", 8000, "port to serve")
keyPtr := flag.String("key", "", "SSL key file")
certPtr := flag.String("cert", "", "SSL certificate file")
func readOptions() (string, int, string, int) {
var mailpw string
var mailport int
var mailserver string
var port int
flag.IntVar(&port, "port", 8000, "port to serve")
flag.StringVar(&keyPath, "key", "", "SSL key file")
flag.StringVar(&certPath, "cert", "", "SSL certificate file")
flag.StringVar(&templPath, "templates",
os.Getenv("GOPATH")+"/src/htwtxt/templates",
"directory where to expect HTML templates")
flag.StringVar(&dataDir, "dir", os.Getenv("HOME")+"/htwtxt",
"directory to store feeds and login data")
contactPtr := flag.String("contact",
flag.StringVar(&contact, "contact",
"[operator passed no contact info to server]",
"operator contact info to display on info page")
flag.BoolVar(&signupOpen, "signup", false,
@ -392,8 +230,8 @@ func readOptions() (*int, *string, *string, *string) {
if "" != mailserver && ("" == mailuser || 0 == mailport) {
log.Fatal("Mail server usage needs username and port number")
}
if ("" == *keyPtr && "" != *certPtr) ||
("" != *keyPtr && "" == *certPtr) {
if ("" == keyPath && "" != certPath) ||
("" != keyPath && "" == certPath) {
log.Fatal("Expect either both key and certificate or none.")
}
if "" != mailserver {
@ -402,293 +240,28 @@ func readOptions() (*int, *string, *string, *string) {
if err != nil {
log.Fatal("Trouble reading password")
}
mailpassword = string(bytePassword)
mailpw = string(bytePassword)
}
return portPtr, keyPtr, certPtr, contactPtr
}
func cssHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, templPath+"/style.css")
}
func passwordResetRequestGetHandler(w http.ResponseWriter, r *http.Request) {
if "" == mailserver {
execTemplate(w, "nopwresetrequest.html", "")
} else {
execTemplate(w, "pwresetrequest.html", "")
}
}
func passwordResetRequestPostHandler(w http.ResponseWriter, r *http.Request) {
go prepPasswordReset(r.FormValue("name"))
http.Redirect(w, r, "/", 302)
}
func passwordResetLinkGetHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
if tokens, e := getFromFileEntryFor(pwResetPath, urlPart, 3); e == nil {
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file", err)
}
if createTime+resetLinkExp >= int(time.Now().Unix()) {
name := tokens[0]
tokensUser, err := getFromFileEntryFor(loginsPath, name,
5)
if err != nil {
log.Fatal("Can't read from loings file", err)
}
if "" != tokensUser[2] {
type data struct {
Secret string
Question string
}
err := templ.ExecuteTemplate(w,
"pwresetquestion.html", data{
Secret: urlPart,
Question: tokensUser[2]})
if err != nil {
log.Fatal("Trouble executing template",
err)
}
} else {
execTemplate(w, "pwreset.html", urlPart)
}
return
}
}
http.Redirect(w, r, "/404", 302)
}
func passwordResetLinkPostHandler(w http.ResponseWriter, r *http.Request) {
urlPart := mux.Vars(r)["secret"]
name := r.FormValue("name")
if tokens, e := getFromFileEntryFor(pwResetPath, urlPart, 3); e == nil {
createTime, err := strconv.Atoi(tokens[1])
if err != nil {
log.Fatal("Can't read time from pw reset file",
err)
}
if tokens[0] == name &&
createTime+resetLinkExp >= int(time.Now().Unix()) {
tokensOld, err := getFromFileEntryFor(loginsPath, name,
5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
if "" != tokensOld[2] &&
nil != bcrypt.CompareHashAndPassword(
[]byte(tokensOld[3]),
[]byte(r.FormValue("secanswer"))) {
http.Redirect(w, r, "/", 302)
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
tokensOld[0] = hash
line := name + "\t" + strings.Join(tokensOld, "\t")
replaceLineStartingWith(loginsPath, tokens[0], line)
removeLineStartingWith(pwResetPath, urlPart)
execTemplate(w, "feedset.html", "")
return
}
}
http.Redirect(w, r, "/", 302)
}
func signUpFormHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "nosignup.html", "")
return
}
execTemplate(w, "signupform.html", "")
}
func signUpHandler(w http.ResponseWriter, r *http.Request) {
if !signupOpen {
execTemplate(w, "error.html",
"Account creation currently not allowed.")
return
}
name := r.FormValue("name")
if "" == name || !onlyLegalRunes(name) || len(name) > 140 {
execTemplate(w, "error.html", "Illegal name.")
return
}
if _, err := getFromFileEntryFor(loginsPath, name, 5); err == nil {
execTemplate(w, "error.html", "Username taken.")
return
}
hash, err := newPassword(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
mail := ""
if "" != r.FormValue("mail") {
mail, err = newMailAddress(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
appendToFile(loginsPath,
name+"\t"+hash+"\t"+mail+"\t"+secquestion+"\t"+secanswer)
execTemplate(w, "feedset.html", "")
}
func accountSetPwHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newPassword, 0)
}
func accountSetMailHandler(w http.ResponseWriter, r *http.Request) {
changeLoginField(w, r, newMailAddress, 1)
}
func accountSetQuestionHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
var secquestion, secanswer string
if "" != r.FormValue("secquestion") || "" != r.FormValue("secanswer") {
secquestion, secanswer, err = newSecurityQuestion(w, r)
if err != nil {
execTemplate(w, "error.html", err.Error())
return
}
}
tokens, err := getFromFileEntryFor(loginsPath, name, 5)
if err != nil {
log.Fatal("Can't get entry for user", err)
}
tokens[2] = secquestion
tokens[3] = secanswer
replaceLineStartingWith(loginsPath, name,
name+"\t"+strings.Join(tokens, "\t"))
execTemplate(w, "feedset.html", "")
}
func listHandler(w http.ResponseWriter, r *http.Request) {
file := openFile(loginsPath)
defer file.Close()
scanner := bufio.NewScanner(bufio.NewReader(file))
var dir []string
tokens := tokensFromLine(scanner, 5)
for 0 != len(tokens) {
dir = append(dir, tokens[0])
tokens = tokensFromLine(scanner, 5)
}
type data struct{ Dir []string }
err := templ.ExecuteTemplate(w, "list.html", data{Dir: dir})
if err != nil {
log.Fatal("Trouble executing template", err)
}
}
func twtxtPostHandler(w http.ResponseWriter, r *http.Request) {
name, err := login(w, r)
if err != nil {
return
}
text := r.FormValue("twt")
twtsFile := feedsPath + "/" + name
createFileIfNotExists(twtsFile)
text = strings.Replace(text, "\n", " ", -1)
appendToFile(twtsFile, time.Now().Format(time.RFC3339)+"\t"+text)
http.Redirect(w, r, "/"+feedsDir+"/"+name, 302)
}
func twtxtHandler(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
if !onlyLegalRunes(name) {
execTemplate(w, "error.html", "Bad path.")
return
}
path := feedsPath + "/" + name
if _, err := os.Stat(path); err != nil {
execTemplate(w, "error.html", "Empty twtxt for user.")
return
}
http.ServeFile(w, r, path)
return mailserver, mailport, mailpw, port
}
func main() {
var err error
portPtr, keyPtr, certPtr, contactPtr := readOptions()
log.Println("Using as templates dir:", templPath)
log.Println("Using as data dir:", dataDir)
loginsPath = dataDir + "/" + loginsFile
feedsPath = dataDir + "/" + feedsDir
ipDelaysPath = dataDir + "/" + ipDelaysFile
pwResetPath = dataDir + "/" + pwResetFile
if "" != *keyPtr {
log.Println("Using TLS.")
if _, err := os.Stat(*certPtr); err != nil {
log.Fatal("No certificate file found.")
}
if _, err := os.Stat(*keyPtr); err != nil {
log.Fatal("No server key file found.")
}
}
createFileIfNotExists(loginsPath)
createFileIfNotExists(pwResetPath)
createFileIfNotExists(ipDelaysPath)
myself = nameMyself("" != *keyPtr, *portPtr)
// TODO: Handle err here.
_ = os.Mkdir(feedsPath, 0700)
mailserver, mailport, mailpw, port := readOptions()
initFilesAndDirs()
myself = nameMyself("" != keyPath, port)
templ, err = template.New("main").ParseGlob(templPath + "/*.html")
if err != nil {
log.Fatal("Can't set up new template: ", err)
}
router := mux.NewRouter()
router.HandleFunc("/", handleTemplate("index.html", ""))
router.HandleFunc("/feeds", listHandler).Methods("GET")
router.HandleFunc("/feeds/", listHandler)
router.HandleFunc("/accountsetquestion",
handleTemplate("accountsetquestion.html", "")).Methods("GET")
router.HandleFunc("/accountsetquestion", accountSetQuestionHandler).
Methods("POST")
router.HandleFunc("/accountsetmail",
handleTemplate("accountsetmail.html", "")).Methods("GET")
router.HandleFunc("/accountsetmail", accountSetMailHandler).
Methods("POST")
router.HandleFunc("/accountsetpw", handleTemplate("accountsetpw.html",
"")).Methods("GET")
router.HandleFunc("/accountsetpw", accountSetPwHandler).Methods("POST")
router.HandleFunc("/account", handleTemplate("account.html", ""))
router.HandleFunc("/signup", signUpFormHandler).Methods("GET")
router.HandleFunc("/signup", signUpHandler).Methods("POST")
router.HandleFunc("/feeds", twtxtPostHandler).Methods("POST")
router.HandleFunc("/feeds/{name}", twtxtHandler)
router.HandleFunc("/info", handleTemplate("info.html", *contactPtr))
router.HandleFunc("/passwordreset", passwordResetRequestPostHandler).
Methods("POST")
router.HandleFunc("/passwordreset", passwordResetRequestGetHandler).
Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkGetHandler).Methods("GET")
router.HandleFunc("/passwordreset/{secret}",
passwordResetLinkPostHandler).Methods("POST")
router.HandleFunc("/style.css", cssHandler)
http.Handle("/", router)
log.Println("serving at port", *portPtr)
if "" != *keyPtr {
err = http.ListenAndServeTLS(":"+strconv.Itoa(*portPtr),
*certPtr, *keyPtr, nil)
http.Handle("/", handleRoutes())
dialer = gomail.NewPlainDialer(mailserver, mailport, mailuser, mailpw)
log.Println("serving at port", port)
if "" != keyPath {
err = http.ListenAndServeTLS(":"+strconv.Itoa(port),
certPath, keyPath, nil)
} else {
err = http.ListenAndServe(":"+strconv.Itoa(*portPtr), nil)
err = http.ListenAndServe(":"+strconv.Itoa(port), nil)
}
if err != nil {
log.Fatal("ListenAndServe: ", err)