From 48fefb76c0a908cc3fa00abc9c090ce3ac8cb560 Mon Sep 17 00:00:00 2001 From: FChannel <> Date: Sun, 24 Oct 2021 10:40:45 -0700 Subject: gofiber conversion, index, board posts, board post hooked up --- main.go | 1585 +++++++++++---------------------------------------------------- 1 file changed, 259 insertions(+), 1326 deletions(-) (limited to 'main.go') diff --git a/main.go b/main.go index 4f20ad7..7e228fa 100644 --- a/main.go +++ b/main.go @@ -8,1322 +8,278 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/gofrs/uuid" - _ "github.com/lib/pq" - "html/template" - "io" - "io/ioutil" - "log" - "math/rand" - "mime/multipart" - "net/http" - "net/url" - "os" - "os/exec" - "path" - "regexp" - "strconv" - "strings" - "time" -) - -var Port = ":" + GetConfigValue("instanceport", "3000") -var TP = GetConfigValue("instancetp", "") -var Instance = GetConfigValue("instance", "") -var Domain = TP + "" + Instance -var TorInstance = IsOnion(Instance) - -var authReq = []string{"captcha", "email", "passphrase"} - -var supportedFiles = []string{"image/gif", "image/jpeg", "image/png", "image/webp", "image/apng", "video/mp4", "video/ogg", "video/webm", "audio/mpeg", "audio/ogg", "audio/wav", "audio/wave", "audio/x-wav"} - -var SiteEmail = GetConfigValue("emailaddress", "") //contact@fchan.xyz -var SiteEmailPassword = GetConfigValue("emailpass", "") -var SiteEmailServer = GetConfigValue("emailserver", "") //mail.fchan.xyz -var SiteEmailPort = GetConfigValue("emailport", "") //587 - -var TorProxy = GetConfigValue("torproxy", "") //127.0.0.1:9050 - -var PublicIndexing = strings.ToLower(GetConfigValue("publicindex", "false")) - -var Salt = GetConfigValue("instancesalt", "") - -var activitystreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" - -var MediaHashs = make(map[string]string) - -var ActorCache = make(map[string]Actor) - -var Themes []string - -func main() { - - CreatedNeededDirectories() - - InitCache() - - db := ConnectDB() - - defer db.Close() - - RunDatabaseSchema(db) - - go MakeCaptchas(db, 100) - - *Key = CreateKey(32) - - FollowingBoards = GetActorFollowingDB(db, Domain) - - go StartupArchive(db) - - go CheckInactive(db) - - Boards = GetBoardCollection(db) - - // root actor is used to follow remote feeds that are not local - //name, prefname, summary, auth requirements, restricted - if GetConfigValue("instancename", "") != "" { - CreateNewBoardDB(db, *CreateNewActor("", GetConfigValue("instancename", ""), GetConfigValue("instancesummary", ""), authReq, false)) - if PublicIndexing == "true" { - AddInstanceToIndex(Domain) - } - } - - // get list of themes - themes, err := ioutil.ReadDir("./static/css/themes") - if err != nil { - panic(err) - } - - for _, f := range themes { - if e := path.Ext(f.Name()); e == ".css" { - Themes = append(Themes, strings.TrimSuffix(f.Name(), e)) - } - } - - // Allow access to public media folder - fileServer := http.FileServer(http.Dir("./public")) - http.Handle("/public/", http.StripPrefix("/public", neuter(fileServer))) - - javascriptFiles := http.FileServer(http.Dir("./static")) - http.Handle("/static/", http.StripPrefix("/static", neuter(javascriptFiles))) - - // main routing - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - path := r.URL.Path - - // remove trailing slash - if path != "/" { - re := regexp.MustCompile(`/$`) - path = re.ReplaceAllString(path, "") - } - - var mainActor bool - var mainInbox bool - var mainOutbox bool - var mainFollowing bool - var mainFollowers bool - - var actorMain bool - var actorInbox bool - var actorCatalog bool - var actorOutbox bool - var actorPost bool - var actorFollowing bool - var actorFollowers bool - var actorReported bool - var actorVerification bool - var actorMainPage bool - var actorArchive bool - - var accept = r.Header.Get("Accept") - - var method = r.Method - - var actor = GetActorFromPath(db, path, "/") - - if actor.Name == "main" { - mainActor = (path == "/") - mainInbox = (path == "/inbox") - mainOutbox = (path == "/outbox") - mainFollowing = (path == "/following") - mainFollowers = (path == "/followers") - } else { - actorMain = (path == "/"+actor.Name) - actorInbox = (path == "/"+actor.Name+"/inbox") - actorCatalog = (path == "/"+actor.Name+"/catalog") - actorOutbox = (path == "/"+actor.Name+"/outbox") - actorFollowing = (path == "/"+actor.Name+"/following") - actorFollowers = (path == "/"+actor.Name+"/followers") - actorReported = (path == "/"+actor.Name+"/reported") - actorVerification = (path == "/"+actor.Name+"/verification") - actorArchive = (path == "/"+actor.Name+"/archive") - - escapedActorName := strings.Replace(actor.Name, "*", "\\*", -1) - escapedActorName = strings.Replace(escapedActorName, "^", "\\^", -1) - escapedActorName = strings.Replace(escapedActorName, "$", "\\$", -1) - escapedActorName = strings.Replace(escapedActorName, "?", "\\?", -1) - escapedActorName = strings.Replace(escapedActorName, "+", "\\+", -1) - escapedActorName = strings.Replace(escapedActorName, ".", "\\.", -1) - - re := regexp.MustCompile("/" + escapedActorName + "/[0-9]{1,2}$") - - actorMainPage = re.MatchString(path) - - re = regexp.MustCompile("/" + escapedActorName + "/\\w+") - - actorPost = re.MatchString(path) - } - - if mainActor { - if acceptActivity(accept) { - GetActorInfo(w, db, Domain) - return - } - - IndexGet(w, r, db) - - return - } - - if mainInbox { - if method == "POST" { - - } else { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - } - return - } - - if mainOutbox { - if method == "GET" { - GetActorOutbox(w, r, db) - } else if method == "POST" { - ParseOutboxRequest(w, r, db) - } else { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - } - return - } - - if mainFollowing { - GetActorFollowing(w, db, Domain) - return - } - - if mainFollowers { - GetActorFollowers(w, db, Domain) - return - } - - if actorMain || actorMainPage { - if acceptActivity(accept) { - GetActorInfo(w, db, actor.Id) - return - } - - postNum := r.URL.Query().Get("page") - - page, _ := strconv.Atoi(postNum) - collection, valid := WantToServePage(db, actor.Name, page) - - if valid { - OutboxGet(w, r, db, collection) - } - - return - } - - if actorFollowing { - GetActorFollowing(w, db, actor.Id) - return - } - - if actorFollowers { - GetActorFollowers(w, db, actor.Id) - return - } - - if actorInbox { - if method == "POST" { - ParseInboxRequest(w, r, db) - } else { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - } - return - } - - if actorCatalog { - collection, valid := WantToServeCatalog(db, actor.Name) - if valid { - CatalogGet(w, r, db, collection) - } - return - } - - if actorOutbox { - if method == "GET" { - GetActorOutbox(w, r, db) - } else if method == "POST" { - ParseOutboxRequest(w, r, db) - } else { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - } - return - } - - if actorArchive { - collection, valid := WantToServeArchive(db, actor.Name) - if valid { - ArchiveGet(w, r, db, collection) - } - return - } - - if actorReported { - GetActorReported(w, r, db, actor.Id) - return - } - - if actorVerification { - r.ParseForm() - - code := r.FormValue("code") - - var verify Verify - - verify.Board = actor.Id - verify.Identifier = "post" - - verify = GetVerificationCode(db, verify) - - auth := CreateTripCode(verify.Code) - auth = CreateTripCode(auth) - - if CreateTripCode(auth) == code { - w.WriteHeader(http.StatusOK) - } else { - w.WriteHeader(http.StatusUnauthorized) - } - - w.Write([]byte("")) - } - - //catch all - if actorPost { - if acceptActivity(accept) { - GetActorPost(w, db, path) - return - } - - PostGet(w, r, db) - return - } - - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - }) - - http.HandleFunc("/news/", func(w http.ResponseWriter, r *http.Request) { - timestamp := r.URL.Path[6:] - - if len(timestamp) < 2 { - AllNewsGet(w, r, db) - return - } - - if timestamp[len(timestamp)-1:] == "/" { - timestamp = timestamp[:len(timestamp)-1] - } - - ts, err := strconv.Atoi(timestamp) - if err != nil { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - } else { - NewsGet(w, r, db, ts) - } - }) - - http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) { - - r.ParseMultipartForm(10 << 20) - - file, header, _ := r.FormFile("file") - - if IsPostBlacklist(db, r.FormValue("comment")) { - fmt.Println("\n\nBlacklist post blocked\n\n") - http.Redirect(w, r, Domain+"/", http.StatusMovedPermanently) - return - } - - if file != nil && header.Size > (7<<20) { - w.Write([]byte("7MB max file size")) - return - } - - if r.FormValue("inReplyTo") == "" && file == nil { - w.Write([]byte("Media is required for new posts")) - return - } - - if r.FormValue("inReplyTo") == "" || file == nil { - if r.FormValue("comment") == "" && r.FormValue("subject") == "" { - w.Write([]byte("Comment or Subject required")) - return - } - } - - if len(r.FormValue("comment")) > 2000 { - w.Write([]byte("Comment limit 2000 characters")) - return - } - - if len(r.FormValue("subject")) > 100 || len(r.FormValue("name")) > 100 || len(r.FormValue("options")) > 100 { - w.Write([]byte("Name, Subject or Options limit 100 characters")) - return - } - - if r.FormValue("captcha") == "" { - w.Write([]byte("Incorrect Captcha")) - return - } - - b := bytes.Buffer{} - we := multipart.NewWriter(&b) - - if file != nil { - var fw io.Writer - - fw, err := we.CreateFormFile("file", header.Filename) - - CheckError(err, "error with form file create") - - _, err = io.Copy(fw, file) - - CheckError(err, "error with form file copy") - } - - reply := ParseCommentForReply(r.FormValue("comment")) - - for key, r0 := range r.Form { - if key == "captcha" { - err := we.WriteField(key, r.FormValue("captchaCode")+":"+r.FormValue("captcha")) - CheckError(err, "error with writing captcha field") - } else if key == "name" { - name, tripcode := CreateNameTripCode(r, db) - err := we.WriteField(key, name) - CheckError(err, "error with writing name field") - err = we.WriteField("tripcode", tripcode) - CheckError(err, "error with writing tripcode field") - } else { - err := we.WriteField(key, r0[0]) - CheckError(err, "error with writing field") - } - } - - if r.FormValue("inReplyTo") == "" && reply != "" { - err := we.WriteField("inReplyTo", reply) - CheckError(err, "error with writing inReplyTo field") - } - - we.Close() - - sendTo := r.FormValue("sendTo") - req, err := http.NewRequest("POST", sendTo, &b) - - CheckError(err, "error with post form req") - - req.Header.Set("Content-Type", we.FormDataContentType()) - - resp, err := RouteProxy(req) - - CheckError(err, "error with post form resp") - - defer resp.Body.Close() - - if resp.StatusCode == 200 { - - body, _ := ioutil.ReadAll(resp.Body) - - var obj ObjectBase - - obj = ParseOptions(r, obj) - for _, e := range obj.Option { - if e == "noko" || e == "nokosage" { - http.Redirect(w, r, Domain+"/"+r.FormValue("boardName")+"/"+shortURL(r.FormValue("sendTo"), string(body)), http.StatusMovedPermanently) - return - } - } - - if r.FormValue("returnTo") == "catalog" { - http.Redirect(w, r, Domain+"/"+r.FormValue("boardName")+"/catalog", http.StatusMovedPermanently) - } else { - http.Redirect(w, r, Domain+"/"+r.FormValue("boardName"), http.StatusMovedPermanently) - } - return - } - - if resp.StatusCode == 403 { - w.Write([]byte("Incorrect Captcha")) - return - } - - http.Redirect(w, r, Domain+"/"+r.FormValue("boardName"), http.StatusMovedPermanently) - }) - - http.HandleFunc("/"+*Key+"/", func(w http.ResponseWriter, r *http.Request) { - - id, _ := GetPasswordFromSession(r) - actor := GetActorFromPath(db, r.URL.Path, "/"+*Key+"/") - - if actor.Id == "" { - actor = GetActorFromDB(db, Domain) - } - - if id == "" || (id != actor.Id && id != Domain) { - t := template.Must(template.ParseFiles("./static/verify.html")) - t.Execute(w, "") - return - } - - re := regexp.MustCompile("/" + *Key + "/" + actor.Name + "/follow") - follow := re.MatchString(r.URL.Path) - - re = regexp.MustCompile("/" + *Key + "/" + actor.Name) - manage := re.MatchString(r.URL.Path) - - re = regexp.MustCompile("/" + *Key) - admin := re.MatchString(r.URL.Path) - - re = regexp.MustCompile("/" + *Key + "/follow") - adminFollow := re.MatchString(r.URL.Path) - - if follow || adminFollow { - r.ParseForm() - - following := regexp.MustCompile(`(.+)\/following`) - followers := regexp.MustCompile(`(.+)\/followers`) - - follow := r.FormValue("follow") - actorId := r.FormValue("actor") - - //follow all of boards following - if following.MatchString(follow) { - followingActor := FingerActor(follow) - col := GetActorCollection(followingActor.Following) - - var nObj ObjectBase - nObj.Id = followingActor.Id - - col.Items = append(col.Items, nObj) - - for _, e := range col.Items { - if !IsAlreadyFollowing(db, actorId, e.Id) && e.Id != Domain && e.Id != actorId { - followActivity := MakeFollowActivity(db, actorId, e.Id) - - if FingerActor(e.Id).Id != "" { - MakeActivityRequestOutbox(db, followActivity) - } - } - } - - //follow all of boards followers - } else if followers.MatchString(follow) { - followersActor := FingerActor(follow) - col := GetActorCollection(followersActor.Followers) - - var nObj ObjectBase - nObj.Id = followersActor.Id - - col.Items = append(col.Items, nObj) - - for _, e := range col.Items { - if !IsAlreadyFollowing(db, actorId, e.Id) && e.Id != Domain && e.Id != actorId { - followActivity := MakeFollowActivity(db, actorId, e.Id) - if FingerActor(e.Id).Id != "" { - MakeActivityRequestOutbox(db, followActivity) - } - } - } - - //do a normal follow to a single board - } else { - followActivity := MakeFollowActivity(db, actorId, follow) - - if followActivity.Actor.Id == Domain && !IsActorLocal(db, followActivity.Object.Actor) { - w.Write([]byte("main board can only follow local boards. Create a new board and then follow outside boards from it.")) - return - } - - if FingerActor(follow).Id != "" { - MakeActivityRequestOutbox(db, followActivity) - } - } - - var redirect string - if actor.Name != "main" { - redirect = "/" + actor.Name - } - - http.Redirect(w, r, "/"+*Key+"/"+redirect, http.StatusSeeOther) - } else if manage && actor.Name != "" { - t := template.Must(template.New("").Funcs(template.FuncMap{ - "sub": func(i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/manage.html")) - - follow := GetActorCollection(actor.Following) - follower := GetActorCollection(actor.Followers) - reported := GetActorCollectionReq(r, actor.Id+"/reported") - - var following []string - var followers []string - var reports []Report - - for _, e := range follow.Items { - following = append(following, e.Id) - } - - for _, e := range follower.Items { - followers = append(followers, e.Id) - } - - for _, e := range reported.Items { - var r Report - r.Count = int(e.Size) - r.ID = e.Id - r.Reason = e.Content - reports = append(reports, r) - } - - localReports := GetLocalReportDB(db, actor.Name) - - for _, e := range localReports { - var r Report - r.Count = e.Count - r.ID = e.ID - r.Reason = e.Reason - reports = append(reports, r) - } - - var adminData AdminPage - adminData.Following = following - adminData.Followers = followers - adminData.Reported = reports - adminData.Domain = Domain - adminData.IsLocal = IsActorLocal(db, actor.Id) - - adminData.Title = "Manage /" + actor.Name + "/" - adminData.Boards = Boards - adminData.Board.Name = actor.Name - adminData.Board.Actor = actor - adminData.Key = *Key - adminData.Board.TP = TP - - adminData.Board.Post.Actor = actor.Id - - adminData.AutoSubscribe = GetActorAutoSubscribeDB(db, actor.Id) - - adminData.Themes = &Themes - - if cookie, err := r.Cookie("theme"); err == nil { - adminData.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] - } - - err = t.ExecuteTemplate(w, "layout", adminData) - if err != nil { - // TODO: actual error handling - log.Printf("mod page: %s\n", err) - } - - } else if admin || actor.Id == Domain { - t := template.Must(template.New("").Funcs(template.FuncMap{ - "sub": func(i, j int) int { return i - j }}).ParseFiles("./static/main.html", "./static/nadmin.html")) - - actor := GetActor(Domain) - follow := GetActorCollection(actor.Following).Items - follower := GetActorCollection(actor.Followers).Items - - var following []string - var followers []string - - for _, e := range follow { - following = append(following, e.Id) - } - - for _, e := range follower { - followers = append(followers, e.Id) - } - - var adminData AdminPage - adminData.Following = following - adminData.Followers = followers - adminData.Actor = actor.Id - adminData.Key = *Key - adminData.Domain = Domain - adminData.Board.ModCred, _ = GetPasswordFromSession(r) - - adminData.Boards = Boards - - adminData.Board.Post.Actor = actor.Id - - adminData.PostBlacklist = GetRegexBlacklistDB(db) - - adminData.Themes = &Themes - if cookie, err := r.Cookie("theme"); err == nil { - adminData.ThemeCookie = strings.SplitN(cookie.String(), "=", 2)[1] - } - - err = t.ExecuteTemplate(w, "layout", adminData) - if err != nil { - // TODO: actual error handling - log.Printf("mod page: %s\n", err) - } - } - }) - - http.HandleFunc("/"+*Key+"/addboard", func(w http.ResponseWriter, r *http.Request) { - - actor := GetActorFromDB(db, Domain) - - if !HasValidation(w, r, actor) { - return - } - - var newActorActivity Activity - var board Actor - r.ParseForm() - - var restrict bool - if r.FormValue("restricted") == "True" { - restrict = true - } else { - restrict = false - } - - board.Name = r.FormValue("name") - board.PreferredUsername = r.FormValue("prefname") - board.Summary = r.FormValue("summary") - board.Restricted = restrict - - newActorActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" - newActorActivity.Type = "New" - - var nobj ObjectBase - newActorActivity.Actor = &actor - newActorActivity.Object = &nobj - - newActorActivity.Object.Alias = board.Name - newActorActivity.Object.Name = board.PreferredUsername - newActorActivity.Object.Summary = board.Summary - newActorActivity.Object.Sensitive = board.Restricted - - MakeActivityRequestOutbox(db, newActorActivity) - http.Redirect(w, r, "/"+*Key, http.StatusSeeOther) - }) - - http.HandleFunc("/"+*Key+"/postnews", func(w http.ResponseWriter, r *http.Request) { - - actor := GetActorFromDB(db, Domain) - - if !HasValidation(w, r, actor) { - return - } - - var newsitem NewsItem - - newsitem.Title = r.FormValue("title") - newsitem.Content = template.HTML(r.FormValue("summary")) - - WriteNewsToDB(db, newsitem) - - http.Redirect(w, r, "/", http.StatusSeeOther) - }) - - http.HandleFunc("/"+*Key+"/newsdelete/", func(w http.ResponseWriter, r *http.Request) { - - actor := GetActorFromDB(db, Domain) - - if !HasValidation(w, r, actor) { - return - } - - timestamp := r.URL.Path[13+len(*Key):] - - tsint, err := strconv.Atoi(timestamp) - - if err != nil { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte("404 no path")) - return - } else { - deleteNewsItemFromDB(db, tsint) - http.Redirect(w, r, "/news/", http.StatusSeeOther) - } - }) - - http.HandleFunc("/verify", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "POST" { - r.ParseForm() - identifier := r.FormValue("id") - code := r.FormValue("code") - - var verify Verify - verify.Identifier = identifier - verify.Code = code - - j, _ := json.Marshal(&verify) - - req, err := http.NewRequest("POST", Domain+"/auth", bytes.NewBuffer(j)) - - CheckError(err, "error making verify req") - - req.Header.Set("Content-Type", activitystreams) - - resp, err := http.DefaultClient.Do(req) - - CheckError(err, "error getting verify resp") - - defer resp.Body.Close() - - rBody, _ := ioutil.ReadAll(resp.Body) - - body := string(rBody) - - if resp.StatusCode != 200 { - t := template.Must(template.ParseFiles("./static/verify.html")) - t.Execute(w, "wrong password "+verify.Code) - } else { - - sessionToken, _ := uuid.NewV4() - - _, err := cache.Do("SETEX", sessionToken, "86400", body+"|"+verify.Code) - if err != nil { - t := template.Must(template.ParseFiles("./static/verify.html")) - t.Execute(w, "") - return - } - - http.SetCookie(w, &http.Cookie{ - Name: "session_token", - Value: sessionToken.String(), - Expires: time.Now().UTC().Add(60 * 60 * 48 * time.Second), - }) - - http.Redirect(w, r, "/", http.StatusSeeOther) - } - } else { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("404 no path")) - } - }) - - http.HandleFunc("/banmedia", func(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get("id") - board := r.URL.Query().Get("board") - - _, auth := GetPasswordFromSession(r) - - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } - - col := GetCollectionFromID(id) - - if len(col.OrderedItems) > 0 { - - actor := col.OrderedItems[0].Actor - - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } - - if len(col.OrderedItems[0].Attachment) > 0 { - re := regexp.MustCompile(Domain) - file := re.ReplaceAllString(col.OrderedItems[0].Attachment[0].Href, "") - - f, err := os.Open("." + file) - CheckError(err, "could not open attachment for ban media") - - defer f.Close() - - bytes := make([]byte, 2048) - - _, err = f.Read(bytes) - if err != nil { - fmt.Println("error readin bytes for setting media ban") - } - - if !IsMediaBanned(db, f) { - query := `insert into bannedmedia (hash) values ($1)` - - _, err := db.Exec(query, HashBytes(bytes)) - - CheckError(err, "error inserting banend media into db") - - } - - var obj ObjectBase - obj.Id = id - obj.Actor = actor - - isOP := CheckIfObjectOP(db, obj.Id) - - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 { - OP = col.OrderedItems[0].InReplyTo[0].Id - } - - if !isOP { - TombstoneObject(db, id) - } else { - TombstoneObjectAndReplies(db, id) - } - - if IsIDLocal(db, id) { - go DeleteObjectRequest(db, id) - } - - UnArchiveLast(db, actor) - - if !isOP { - if !IsIDLocal(db, id) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return - } - } else { - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } - } - } - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - }) - - http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get("id") - board := r.URL.Query().Get("board") - _, auth := GetPasswordFromSession(r) + "github.com/gofiber/fiber/v2" + "github.com/gofiber/template/html" + // "github.com/gofrs/uuid" + _ "github.com/lib/pq" - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + "html/template" + // "io" + "io/ioutil" + // "log" + "math/rand" + "mime/multipart" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "regexp" + "strconv" + "strings" + "time" +) - manage := r.URL.Query().Get("manage") - col := GetCollectionFromID(id) - if len(col.OrderedItems) < 1 { - actor := GetActorByNameFromDB(db, board) - if !HasAuth(db, auth, actor.Id) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } +var Port = ":" + GetConfigValue("instanceport", "3000") +var TP = GetConfigValue("instancetp", "") +var Instance = GetConfigValue("instance", "") +var Domain = TP + "" + Instance +var TorInstance = IsOnion(Instance) - if !CheckIfObjectOP(db, id) { - TombstoneObject(db, id) - } else { - TombstoneObjectAndReplies(db, id) - } +var authReq = []string{"captcha", "email", "passphrase"} - UnArchiveLast(db, actor.Id) +var supportedFiles = []string{"image/gif", "image/jpeg", "image/png", "image/webp", "image/apng", "video/mp4", "video/ogg", "video/webm", "audio/mpeg", "audio/ogg", "audio/wav", "audio/wave", "audio/x-wav"} - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else { - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } - } +var SiteEmail = GetConfigValue("emailaddress", "") //contact@fchan.xyz +var SiteEmailPassword = GetConfigValue("emailpass", "") +var SiteEmailServer = GetConfigValue("emailserver", "") //mail.fchan.xyz +var SiteEmailPort = GetConfigValue("emailport", "") //587 - actor := col.OrderedItems[0].Actor +var TorProxy = GetConfigValue("torproxy", "") //127.0.0.1:9050 - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } +var PublicIndexing = strings.ToLower(GetConfigValue("publicindex", "false")) - var obj ObjectBase - obj.Id = id - obj.Actor = actor +var Salt = GetConfigValue("instancesalt", "") - isOP := CheckIfObjectOP(db, obj.Id) +var activitystreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 { - OP = col.OrderedItems[0].InReplyTo[0].Id - } +var MediaHashs = make(map[string]string) - if !isOP { - TombstoneObject(db, id) - } else { - TombstoneObjectAndReplies(db, id) - } +var ActorCache = make(map[string]Actor) - if IsIDLocal(db, id) { - go DeleteObjectRequest(db, id) - } +var Themes []string - UnArchiveLast(db, actor) +var DB *sql.DB - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else if !isOP { - if !IsIDLocal(db, id) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return - } - } else { - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } - }) +func main() { - http.HandleFunc("/deleteattach", func(w http.ResponseWriter, r *http.Request) { + CreatedNeededDirectories() - id := r.URL.Query().Get("id") - board := r.URL.Query().Get("board") + InitCache() - _, auth := GetPasswordFromSession(r) + DB = ConnectDB() - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + defer DB.Close() - manage := r.URL.Query().Get("manage") - col := GetCollectionFromID(id) + RunDatabaseSchema(DB) - if len(col.OrderedItems) < 1 { - if !HasAuth(db, auth, GetActorByNameFromDB(db, board).Id) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + go MakeCaptchas(DB, 100) - DeleteAttachmentFromFile(db, id) - TombstoneAttachmentFromDB(db, id) + *Key = CreateKey(32) - DeletePreviewFromFile(db, id) - TombstonePreviewFromDB(db, id) + FollowingBoards = GetActorFollowingDB(DB, Domain) - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else { - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } - } + go StartupArchive(DB) - actor := col.OrderedItems[0].Actor + go CheckInactive(DB) - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" { - OP = col.OrderedItems[0].InReplyTo[0].Id - } else { - OP = id - } + Boards = GetBoardCollection(DB) - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return + // root actor is used to follow remote feeds that are not local + //name, prefname, summary, auth requirements, restricted + if GetConfigValue("instancename", "") != "" { + CreateNewBoardDB(DB, *CreateNewActor("", GetConfigValue("instancename", ""), GetConfigValue("instancesummary", ""), authReq, false)) + if PublicIndexing == "true" { + AddInstanceToIndex(Domain) } + } - DeleteAttachmentFromFile(db, id) - TombstoneAttachmentFromDB(db, id) - - DeletePreviewFromFile(db, id) - TombstonePreviewFromDB(db, id) + // get list of themes + themes, err := ioutil.ReadDir("./static/css/themes") + if err != nil { + panic(err) + } - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else if !IsIDLocal(db, OP) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return + for _, f := range themes { + if e := path.Ext(f.Name()); e == ".css" { + Themes = append(Themes, strings.TrimSuffix(f.Name(), e)) } - }) - - http.HandleFunc("/marksensitive", func(w http.ResponseWriter, r *http.Request) { - - id := r.URL.Query().Get("id") - board := r.URL.Query().Get("board") - - _, auth := GetPasswordFromSession(r) + } - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + // Allow access to public media folder + fileServer := http.FileServer(http.Dir("./public")) + http.Handle("/public/", http.StripPrefix("/public", neuter(fileServer))) - col := GetCollectionFromID(id) + javascriptFiles := http.FileServer(http.Dir("./static")) + http.Handle("/static/", http.StripPrefix("/static", neuter(javascriptFiles))) - if len(col.OrderedItems) < 1 { - if !HasAuth(db, auth, GetActorByNameFromDB(db, board).Id) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + /* Routing and templates */ - MarkObjectSensitive(db, id, true) + template := html.New("./views", ".html") - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } + TemplateFunctions(template) - actor := col.OrderedItems[0].Actor + app := fiber.New(fiber.Config{ + Views: template, + }) - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" { - OP = col.OrderedItems[0].InReplyTo[0].Id - } else { - OP = id - } + app.Static("/public", "./public") + app.Static("/static", "./views") - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + /* + Main actor + */ - MarkObjectSensitive(db, id, true) + app.Get("/", IndexGet) - if !IsIDLocal(db, OP) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return - } + app.Get("/inbox", func(c *fiber.Ctx) error { + return c.SendString("main inbox") }) - http.HandleFunc("/remove", func(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get("id") - manage := r.URL.Query().Get("manage") - board := r.URL.Query().Get("board") - col := GetCollectionFromID(id) - actor := col.OrderedItems[0].Actor - _, auth := GetPasswordFromSession(r) - - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/outbox", func(c *fiber.Ctx) error { + return c.SendString("main outbox") + }) - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/following", func(c *fiber.Ctx) error { + return c.SendString("main following") + }) - var obj ObjectBase - obj.Id = id - obj.Actor = actor + app.Get("/followers", func(c *fiber.Ctx) error { + return c.SendString("main followers") + }) - isOP := CheckIfObjectOP(db, obj.Id) + /* + Board actor + */ - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 { - OP = col.OrderedItems[0].InReplyTo[0].Id - } + app.Get("/:actor", OutboxGet) - if !isOP { - SetObject(db, id, "Removed") - } else { - SetObjectAndReplies(db, id, "Removed") - } + app.Get("/:actor/:post", PostGet) - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else if !isOP { - if !IsIDLocal(db, id) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return - } - } else { - http.Redirect(w, r, "/"+board, http.StatusSeeOther) - return - } + app.Get("/:actor/inbox", func(c *fiber.Ctx) error { + return c.SendString("actor inbox") }) - http.HandleFunc("/removeattach", func(w http.ResponseWriter, r *http.Request) { - - id := r.URL.Query().Get("id") - manage := r.URL.Query().Get("manage") - board := r.URL.Query().Get("board") - col := GetCollectionFromID(id) - actor := col.OrderedItems[0].Actor - - var OP string - if len(col.OrderedItems[0].InReplyTo) > 0 && col.OrderedItems[0].InReplyTo[0].Id != "" { - OP = col.OrderedItems[0].InReplyTo[0].Id - } else { - OP = id - } - - _, auth := GetPasswordFromSession(r) + app.Get("/:actor/outbox", func(c *fiber.Ctx) error { + return c.SendString("actor outbox") + }) - if id == "" || auth == "" { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/:actor/following", func(c *fiber.Ctx) error { + return c.SendString("actor following") + }) - if !HasAuth(db, auth, actor) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/:actor/followers", func(c *fiber.Ctx) error { + return c.SendString("actor followers") + }) - SetAttachmentFromDB(db, id, "Removed") - SetPreviewFromDB(db, id, "Removed") + app.Get("/:actor/reported", func(c *fiber.Ctx) error { + return c.SendString("actor reported") + }) - if manage == "t" { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } else if !IsIDLocal(db, OP) { - http.Redirect(w, r, "/"+board+"/"+remoteShort(OP), http.StatusSeeOther) - return - } else { - http.Redirect(w, r, OP, http.StatusSeeOther) - return - } + app.Get("/:actor/archive", func(c *fiber.Ctx) error { + return c.SendString("actor archive") }) - http.HandleFunc("/report", func(w http.ResponseWriter, r *http.Request) { + app.Get("/post", func(c *fiber.Ctx) error { + return c.SendString("actor post") + }) - r.ParseForm() + /* + Admin routes + */ - id := r.FormValue("id") - board := r.FormValue("board") - reason := r.FormValue("comment") - close := r.FormValue("close") + app.Get("/verify", func(c *fiber.Ctx) error { + return c.SendString("admin verify") + }) - actor := GetActorFromPath(db, id, "/") - _, auth := GetPasswordFromSession(r) + app.Get("/auth", func(c *fiber.Ctx) error { + return c.SendString("admin auth") + }) - var captcha = r.FormValue("captchaCode") + ":" + r.FormValue("captcha") + app.Get("/"+*Key+"/", func(c *fiber.Ctx) error { + return c.SendString("admin key") + }) - if len(reason) > 100 { - w.Write([]byte("Report comment limit 100 characters")) - return - } + app.Get("/"+*Key+"/addboard", func(c *fiber.Ctx) error { + return c.SendString("admin addboard ") + }) - if close != "1" && !CheckCaptcha(db, captcha) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("captcha required")) - return - } + app.Get("/"+*Key+"/postnews", func(c *fiber.Ctx) error { + return c.SendString("admin post news") + }) - if close == "1" { - if !HasAuth(db, auth, actor.Id) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/"+*Key+"/newsdelete", func(c *fiber.Ctx) error { + return c.SendString("admin news delete") + }) - if !IsIDLocal(db, id) { - CloseLocalReportDB(db, id, board) - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } + app.Get("/news", func(c *fiber.Ctx) error { + return c.SendString("admin news") + }) - reported := DeleteReportActivity(db, id) - if reported { - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) - return - } + /* + Board managment + */ - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) - return - } + app.Get("/banmedia", func(c *fiber.Ctx) error { + return c.SendString("board ban media") + }) - if !IsIDLocal(db, id) { - CreateLocalReportDB(db, id, board, reason) - http.Redirect(w, r, "/"+board+"/"+remoteShort(id), http.StatusSeeOther) - return - } + app.Get("/delete", func(c *fiber.Ctx) error { + return c.SendString("board delete") + }) - reported := ReportActivity(db, id, reason) - if reported { - http.Redirect(w, r, id, http.StatusSeeOther) - return - } + app.Get("/deleteattach", func(c *fiber.Ctx) error { + return c.SendString("board delete attach") + }) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) + app.Get("/marksensitive", func(c *fiber.Ctx) error { + return c.SendString("board mark sensitive") }) - http.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) { - var verify Verify - defer r.Body.Close() + app.Get("/remove", func(c *fiber.Ctx) error { + return c.SendString("board remove") + }) - body, _ := ioutil.ReadAll(r.Body) + app.Get("/removeattach", func(c *fiber.Ctx) error { + return c.SendString("Hello World") + }) - err := json.Unmarshal(body, &verify) + app.Get("/addtoindex", func(c *fiber.Ctx) error { + return c.SendString("board add to index") + }) - CheckError(err, "error get verify from json") + app.Get("/poparchive", func(c *fiber.Ctx) error { + return c.SendString("board pop archive") + }) - v := GetVerificationByCode(db, verify.Code) + app.Get("/autosubscribe", func(c *fiber.Ctx) error { + return c.SendString("board autosubscribe") + }) - if v.Identifier == verify.Identifier { - w.Write([]byte(v.Board)) - return - } + app.Get("/blacklist", func(c *fiber.Ctx) error { + return c.SendString("board blacklist") + }) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("")) + app.Get("/report", func(c *fiber.Ctx) error { + return c.SendString("board report") }) - http.HandleFunc("/.well-known/webfinger", func(w http.ResponseWriter, r *http.Request) { - acct := r.URL.Query()["resource"] + app.Get("/.well-known/webfinger", func(c *fiber.Ctx) error { + acct := c.Query("resource") if len(acct) < 1 { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("resource needs a value")) - return + c.Status(fiber.StatusBadRequest) + return c.Send([]byte("resource needs a value")) } - acct[0] = strings.Replace(acct[0], "acct:", "", -1) + acct = strings.Replace(acct, "acct:", "", -1) - actorDomain := strings.Split(acct[0], "@") + actorDomain := strings.Split(acct, "@") if len(actorDomain) < 2 { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("accpets only subject form of acct:board@instance")) - return + c.Status(fiber.StatusBadRequest) + return c.Send([]byte("accpets only subject form of acct:board@instance")) } if actorDomain[0] == "main" { @@ -1332,10 +288,9 @@ func main() { actorDomain[0] = "/" + actorDomain[0] } - if !IsActorLocal(db, TP+""+actorDomain[1]+""+actorDomain[0]) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("actor not local")) - return + if !IsActorLocal(DB, TP+""+actorDomain[1]+""+actorDomain[0]) { + c.Status(fiber.StatusBadRequest) + return c.Send([]byte("actor not local")) } var finger Webfinger @@ -1350,107 +305,20 @@ func main() { enc, _ := json.Marshal(finger) - w.Header().Set("Content-Type", activitystreams) - w.Write(enc) - - }) - - http.HandleFunc("/addtoindex", func(w http.ResponseWriter, r *http.Request) { - actor := r.URL.Query().Get("id") - - if Domain != "https://fchan.xyz" { - return - } - - go AddInstanceToIndexDB(db, actor) - }) - - http.HandleFunc("/poparchive", func(w http.ResponseWriter, r *http.Request) { - - actor := GetActorFromDB(db, Domain) - - if !HasValidation(w, r, actor) { - return - } - - id := r.URL.Query().Get("id") - board := r.URL.Query().Get("board") - - SetObjectType(db, id, "Note") - - http.Redirect(w, r, "/"+board+"/archive", http.StatusSeeOther) - }) - - http.HandleFunc("/blacklist", func(w http.ResponseWriter, r *http.Request) { - - actor := GetActorFromDB(db, Domain) - - if !HasValidation(w, r, actor) { - return - } - - if r.Method == "GET" { - id := r.URL.Query().Get("remove") - - if id != "" { - i, _ := strconv.Atoi(id) - DeleteRegexBlacklistDB(db, i) - } - - } else { - regex := r.FormValue("regex") - testCase := r.FormValue("testCase") - - if regex == "" { - http.Redirect(w, r, "/", http.StatusSeeOther) - return - } - - r.ParseForm() - - re := regexp.MustCompile(regex) - - if testCase == "" { - WriteRegexBlacklistDB(db, regex) - } else if re.MatchString(testCase) { - WriteRegexBlacklistDB(db, regex) - } - } - - http.Redirect(w, r, "/"+*Key+"#regex", http.StatusSeeOther) + c.Set("Content-Type", activitystreams) + return c.Send(enc) }) - http.HandleFunc("/api/media", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("hash") != "" { - RouteImages(w, r.URL.Query().Get("hash")) - } - }) - - http.HandleFunc("/autosubscribe", func(w http.ResponseWriter, r *http.Request) { - - if !HasValidation(w, r, GetActorFromDB(db, Domain)) { - return - } - - board := r.URL.Query().Get("board") - actor := GetActorByNameFromDB(db, board) - - SetActorAutoSubscribeDB(db, actor.Id) - autoSub := GetActorAutoSubscribeDB(db, actor.Id) - - if autoSub { - AutoFollow(db, actor.Id) - } - - http.Redirect(w, r, "/"+*Key+"/"+board, http.StatusSeeOther) + app.Get("/api/media", func(c *fiber.Ctx) error { + return c.SendString("api media") }) fmt.Println("Server for " + Domain + " running on port " + Port) fmt.Println("Mod key: " + *Key) - PrintAdminAuth(db) + PrintAdminAuth(DB) - http.ListenAndServe(Port, nil) + app.Listen(Port) } func CheckError(e error, m string) error { @@ -3002,3 +1870,68 @@ func CheckInactiveInstances(db *sql.DB) map[string]string { return instances } + +func TemplateFunctions(engine *html.Engine) { + engine.AddFunc( + "mod", mod, + ) + + engine.AddFunc( + "sub", sub, + ) + + engine.AddFunc( + "unixtoreadable", unixToReadable, + ) + + engine.AddFunc("proxy", func(url string) string { + return MediaProxy(url) + }) + + engine.AddFunc("short", func(actorName string, url string) string { + return shortURL(actorName, url) + }) + + engine.AddFunc("parseAttachment", func(obj ObjectBase, catalog bool) template.HTML { + return ParseAttachment(obj, catalog) + }) + + engine.AddFunc("parseContent", func(board Actor, op string, content string, thread ObjectBase) template.HTML { + return ParseContent(DB, board, op, content, thread) + }) + + engine.AddFunc("shortImg", func(url string) string { + return ShortImg(url) + }) + + engine.AddFunc("convertSize", func(size int64) string { + return ConvertSize(size) + }) + + engine.AddFunc("isOnion", func(url string) bool { + return IsOnion(url) + }) + + engine.AddFunc("parseReplyLink", func(actorId string, op string, id string, content string) template.HTML { + actor := FingerActor(actorId) + title := strings.ReplaceAll(ParseLinkTitle(actor.Id, op, content), `/\<`, ">") + link := ">>" + shortURL(actor.Outbox, id) + "" + return template.HTML(link) + }) + + engine.AddFunc("add", func(i, j int) int { + return i + j + }) + + engine.AddFunc( + "timeToReadableLong", timeToReadableLong, + ) + + engine.AddFunc( + "timeToUnix", timeToUnix, + ) + + engine.AddFunc( + "sub", sub, + ) +} -- cgit v1.2.3